Skip to content

Commit

Permalink
add MenuSearch
Browse files Browse the repository at this point in the history
Signed-off-by: yelias <[email protected]>
  • Loading branch information
yelias committed Dec 22, 2024
1 parent fee7584 commit 8d0a137
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 39 deletions.
8 changes: 2 additions & 6 deletions workspaces/frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@ import {
Title,
} from '@patternfly/react-core';
import { BarsIcon } from '@patternfly/react-icons';
import NamespaceSelector from '~/shared/components/NamespaceSelector';
import { NamespaceProvider } from './context/NamespaceContextProvider';
import NamespaceSelector from '../shared/components/NamespaceSelector';
import AppRoutes from './AppRoutes';
import NavSidebar from './NavSidebar';

const App: React.FC = () => {
const masthead = (
<Masthead>
<MastheadToggle>
<PageToggleButton
id="page-nav-toggle"
variant="plain"
aria-label="Dashboard navigation"
>
<PageToggleButton id="page-nav-toggle" variant="plain" aria-label="Dashboard navigation">
<BarsIcon />
</PageToggleButton>
</MastheadToggle>
Expand Down
30 changes: 9 additions & 21 deletions workspaces/frontend/src/app/context/NamespaceContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import React, {
useState,
useContext,
ReactNode,
useMemo,
useCallback,
} from 'react';
import useMount from '../hooks/useMount';
import React, { useState, useContext, ReactNode, useMemo, useCallback } from 'react';
import useMount from '~/app/hooks/useMount';

interface NamespaceContextState {
namespaces: string[];
selectedNamespace: string;
setSelectedNamespace: (namespace: string) => void;
}

const NamespaceContext = React.createContext<NamespaceContextState | undefined>(
undefined
);
const NamespaceContext = React.createContext<NamespaceContextState | undefined>(undefined);

export const useNamespaceContext = () => {
export const useNamespaceContext = (): NamespaceContextState => {
const context = useContext(NamespaceContext);
if (!context) {
throw new Error(
"useNamespaceContext must be used within a NamespaceProvider"
);
throw new Error('useNamespaceContext must be used within a NamespaceProvider');
}
return context;
};
Expand All @@ -31,11 +21,9 @@ interface NamespaceProviderProps {
children: ReactNode;
}

export const NamespaceProvider: React.FC<NamespaceProviderProps> = ({
children,
}) => {
export const NamespaceProvider: React.FC<NamespaceProviderProps> = ({ children }) => {
const [namespaces, setNamespaces] = useState<string[]>([]);
const [selectedNamespace, setSelectedNamespace] = useState<string>("");
const [selectedNamespace, setSelectedNamespace] = useState<string>('');

// Todo: Need to replace with actual API call
const fetchNamespaces = useCallback(async () => {
Expand All @@ -44,12 +32,12 @@ export const NamespaceProvider: React.FC<NamespaceProviderProps> = ({
};
const namespaceNames = mockNamespaces.data.map((ns) => ns.name);
setNamespaces(namespaceNames);
setSelectedNamespace(namespaceNames.length > 0 ? namespaceNames[0] : "");
setSelectedNamespace(namespaceNames.length > 0 ? namespaceNames[0] : '');
}, []);
useMount(fetchNamespaces);
const namespacesContextValues = useMemo(
() => ({ namespaces, selectedNamespace, setSelectedNamespace }),
[namespaces, selectedNamespace]
[namespaces, selectedNamespace],
);
return (
<NamespaceContext.Provider value={namespacesContextValues}>
Expand Down
14 changes: 7 additions & 7 deletions workspaces/frontend/src/app/hooks/useMount.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect } from "react"
import { useEffect } from 'react';

const useMount = (callback:()=>void): void => {
useEffect(() => {
callback();
}, []);
}
const useMount = (callback: () => void): void => {
useEffect(() => {
callback();
}, [callback]);
};

export default useMount;
export default useMount;
83 changes: 78 additions & 5 deletions workspaces/frontend/src/shared/components/NamespaceSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,70 @@
import React, { FC, useMemo, useState } from 'react';
import React, { FC, useMemo, useState, useEffect } from 'react';
import {
Dropdown,
DropdownItem,
MenuToggle,
DropdownList,
DropdownProps,
MenuSearch,
MenuSearchInput,
InputGroup,
InputGroupItem,
SearchInput,
Button,
ButtonVariant,
Divider,
} from '@patternfly/react-core';
import { useNamespaceContext } from '../../app/context/NamespaceContextProvider';
import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon';
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';

const NamespaceSelector: FC = () => {
const { namespaces, selectedNamespace, setSelectedNamespace } = useNamespaceContext();
const [isOpen, setIsOpen] = useState<boolean>(false);
const [searchInputValue, setSearchInputValue] = useState<string>('');
const [filteredNamespaces, setFilteredNamespaces] = useState<string[]>(namespaces);

useEffect(() => {
setFilteredNamespaces(namespaces);
}, [namespaces]);

const onToggleClick = () => {
if (!isOpen) {
onClearSearch();
}
setIsOpen(!isOpen);
};

const onSearchInputChange = (value: string) => {
setSearchInputValue(value);
};

const onSearchButtonClick = () => {
const filtered =
searchInputValue === ''
? namespaces
: namespaces.filter((ns) => ns.toLowerCase().includes(searchInputValue.toLowerCase()));
setFilteredNamespaces(filtered);
};

const onEnterPressed = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
onSearchButtonClick();
}
};

const onSelect: DropdownProps['onSelect'] = (_event, value) => {
setSelectedNamespace(value as string);
setIsOpen(false);
};

const onClearSearch = () => {
setSearchInputValue('');
setFilteredNamespaces(namespaces);
};

const dropdownItems = useMemo(
() =>
namespaces.map((ns) => (
filteredNamespaces.map((ns) => (
<DropdownItem
key={ns}
itemId={ns}
Expand All @@ -29,7 +74,7 @@ const NamespaceSelector: FC = () => {
{ns}
</DropdownItem>
)),
[namespaces],
[filteredNamespaces],
);

return (
Expand All @@ -38,7 +83,7 @@ const NamespaceSelector: FC = () => {
toggle={(toggleRef) => (
<MenuToggle
ref={toggleRef}
onClick={() => setIsOpen(!isOpen)}
onClick={onToggleClick}
isExpanded={isOpen}
className="namespace-select-toggle"
data-testid="namespace-toggle"
Expand All @@ -47,8 +92,36 @@ const NamespaceSelector: FC = () => {
</MenuToggle>
)}
isOpen={isOpen}
isScrollable
data-testid="namespace-dropdown"
>
<MenuSearch>
<MenuSearchInput>
<InputGroup>
<InputGroupItem isFill>
<SearchInput
value={searchInputValue}
placeholder="Search Namespace"
onChange={(_event, value) => onSearchInputChange(value)}
onKeyDown={onEnterPressed}
onClear={onClearSearch}
// resetButtonLabel="Clear search"
aria-labelledby="namespace-search-button"
/>
</InputGroupItem>
<InputGroupItem>
<Button
variant={ButtonVariant.control}
aria-label="Search namespace"
id="namespace-search-button"
onClick={onSearchButtonClick}
icon={<SearchIcon aria-hidden="true" />}
/>
</InputGroupItem>
</InputGroup>
</MenuSearchInput>
</MenuSearch>
<Divider />
<DropdownList>{dropdownItems}</DropdownList>
</Dropdown>
);
Expand Down

0 comments on commit 8d0a137

Please sign in to comment.