diff --git a/packages/frontend/src/components/projects/ProjectSearchBar.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx similarity index 57% rename from packages/frontend/src/components/projects/ProjectSearchBar.tsx rename to packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx index c2d986f2..f88709ef 100644 --- a/packages/frontend/src/components/projects/ProjectSearchBar.tsx +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx @@ -6,15 +6,14 @@ import { useDebounce } from 'usehooks-ts'; import SearchBar from 'components/SearchBar'; import { useGQLClient } from 'context/GQLClientContext'; import { cn } from 'utils/classnames'; -import { InfoRoundFilledIcon } from 'components/shared/CustomIcon'; -import { Avatar } from 'components/shared/Avatar'; -import { getInitials } from 'utils/geInitials'; +import { ProjectSearchBarItem } from './ProjectSearchBarItem'; +import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; -interface ProjectsSearchProps { +interface ProjectSearchBarProps { onChange?: (data: Project) => void; } -const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { +export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => { const [items, setItems] = useState([]); const [selectedItem, setSelectedItem] = useState(null); const client = useGQLClient(); @@ -60,7 +59,7 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { }, [fetchProjects, debouncedInputValue]); return ( -
+
{

{items.map((item, index) => ( -
- -
-

- {item.name} -

-

- {item.organization.name} -

-
-
+ item={item} + active={highlightedIndex === index || selectedItem === item} + /> ))} ) : ( -
-
- -
-

- No projects matching this name -

-
+ )}
); }; - -export default ProjectSearchBar; diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx new file mode 100644 index 00000000..01f5b803 --- /dev/null +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx @@ -0,0 +1,84 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import * as Dialog from '@radix-ui/react-dialog'; +import { Button } from 'components/shared/Button'; +import { CrossIcon, SearchIcon } from 'components/shared/CustomIcon'; +import { Input } from 'components/shared/Input'; +import { useGQLClient } from 'context/GQLClientContext'; +import { Project } from 'gql-client'; +import { useDebounce } from 'usehooks-ts'; +import { ProjectSearchBarItem } from './ProjectSearchBarItem'; +import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; + +interface ProjectSearchBarDialogProps extends Dialog.DialogProps { + onClose?: () => void; +} + +export const ProjectSearchBarDialog = ({ + onClose, + ...props +}: ProjectSearchBarDialogProps) => { + const [items, setItems] = useState([]); + const [inputValue, setInputValue] = useState(''); + const client = useGQLClient(); + + const debouncedInputValue = useDebounce(inputValue, 500); + + const fetchProjects = useCallback( + async (inputValue: string) => { + const { searchProjects } = await client.searchProjects(inputValue); + setItems(searchProjects); + }, + [client], + ); + + useEffect(() => { + if (debouncedInputValue) { + fetchProjects(debouncedInputValue); + } + }, [fetchProjects, debouncedInputValue]); + + const handleClose = () => { + setInputValue(''); + setItems([]); + onClose?.(); + }; + console.log(items); + return ( + + +
+ +
+ } + placeholder="Search" + appearance="borderless" + value={inputValue} + autoFocus + onChange={(e) => setInputValue(e.target.value)} + /> + +
+ {/* Content */} +
+ {items.length > 0 + ? items.map((item) => ( + <> +
+

+ Suggestions +

+
+ + + )) + : inputValue && } +
+
+
+
+
+ ); +}; diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarEmpty.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarEmpty.tsx new file mode 100644 index 00000000..342969c2 --- /dev/null +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarEmpty.tsx @@ -0,0 +1,24 @@ +import { InfoRoundFilledIcon } from 'components/shared/CustomIcon'; +import React, { ComponentPropsWithoutRef } from 'react'; +import { cn } from 'utils/classnames'; + +interface ProjectSearchBarEmptyProps extends ComponentPropsWithoutRef<'div'> {} + +export const ProjectSearchBarEmpty = ({ + className, + ...props +}: ProjectSearchBarEmptyProps) => { + return ( +
+
+ +
+

+ No projects matching this name +

+
+ ); +}; diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx new file mode 100644 index 00000000..5afd8a84 --- /dev/null +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx @@ -0,0 +1,59 @@ +import { Avatar } from 'components/shared/Avatar'; +import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift'; +import { Project } from 'gql-client'; +import React, { ComponentPropsWithoutRef, forwardRef } from 'react'; +import { OmitCommon } from 'types/common'; +import { cn } from 'utils/classnames'; +import { getInitials } from 'utils/geInitials'; + +/** + * Represents a type that merges ComponentPropsWithoutRef<'li'> with certain exclusions. + * @type {MergedComponentPropsWithoutRef} + */ +type MergedComponentPropsWithoutRef = OmitCommon< + ComponentPropsWithoutRef<'div'>, + Omit< + Overwrite, + 'index' | 'item' + > +>; + +interface ProjectSearchBarItemProps extends MergedComponentPropsWithoutRef { + item: Project; + active?: boolean; +} + +const ProjectSearchBarItem = forwardRef< + HTMLDivElement, + ProjectSearchBarItemProps +>(({ item, active, ...props }, ref) => { + return ( +
+ +
+

+ {item.name} +

+

{item.organization.name}

+
+
+ ); +}); + +ProjectSearchBarItem.displayName = 'ProjectSearchBarItem'; + +export { ProjectSearchBarItem }; diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/index.ts b/packages/frontend/src/components/projects/ProjectSearchBar/index.ts new file mode 100644 index 00000000..6fd8929b --- /dev/null +++ b/packages/frontend/src/components/projects/ProjectSearchBar/index.ts @@ -0,0 +1,2 @@ +export * from './ProjectSearchBar'; +export * from './ProjectSearchBarDialog'; diff --git a/packages/frontend/src/layouts/ProjectSearch.tsx b/packages/frontend/src/layouts/ProjectSearch.tsx index e2b90f42..9f94a2a6 100644 --- a/packages/frontend/src/layouts/ProjectSearch.tsx +++ b/packages/frontend/src/layouts/ProjectSearch.tsx @@ -2,16 +2,14 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; import { User } from 'gql-client'; -// import { Tooltip } from '@material-tailwind/react'; - import HorizontalLine from 'components/HorizontalLine'; -import ProjectSearchBar from 'components/projects/ProjectSearchBar'; import { useGQLClient } from 'context/GQLClientContext'; import { NotificationBellIcon, PlusIcon } from 'components/shared/CustomIcon'; import { Button } from 'components/shared/Button'; import { Avatar } from 'components/shared/Avatar'; import { getInitials } from 'utils/geInitials'; -import { formatAddress } from '../utils/format'; +import { formatAddress } from 'utils/format'; +import { ProjectSearchBar } from 'components/projects/ProjectSearchBar'; const ProjectSearch = () => { const navigate = useNavigate();