From 0b91771e90d1a1b8114215c20157cab3b8ffc0fd Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Wed, 20 Dec 2023 09:59:02 +0530 Subject: [PATCH] Implement projects search functionality in home page (#11) * Implement search functionality with downshift * Show project details in suggestions and handle selection * Rename component to ProjectSearch * Use renamed component --- packages/frontend/package.json | 1 + packages/frontend/src/assets/projects.json | 78 ++++++++++++---- .../src/components/RepositoryList.tsx | 2 +- .../frontend/src/components/SearchBar.tsx | 64 ++++++------- .../components/{ => projects}/ProjectCard.tsx | 6 +- .../src/components/projects/ProjectSearch.tsx | 93 +++++++++++++++++++ packages/frontend/src/pages/Projects.tsx | 6 +- packages/frontend/src/types/project.ts | 2 + yarn.lock | 25 ++++- 9 files changed, 219 insertions(+), 58 deletions(-) rename packages/frontend/src/components/{ => projects}/ProjectCard.tsx (87%) create mode 100644 packages/frontend/src/components/projects/ProjectSearch.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2624d81e..3fb67862 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -11,6 +11,7 @@ "@types/node": "^16.18.68", "@types/react": "^18.2.42", "@types/react-dom": "^18.2.17", + "downshift": "^8.2.3", "luxon": "^3.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/frontend/src/assets/projects.json b/packages/frontend/src/assets/projects.json index 7ee58608..584aeb9e 100644 --- a/packages/frontend/src/assets/projects.json +++ b/packages/frontend/src/assets/projects.json @@ -2,14 +2,16 @@ { "id": 1, "icon": "^", + "name": "iglotools", "title": "Iglotools", - "domain": "iglotools.com", + "organization": "Airfoil", + "domain": "iglotools.co", "createdAt": "2023-12-07T04:20:00", - "createdBy": "Bob", - "deployment": "iglotools.snowball.com", + "createdBy": "Alice", + "deployment": "iglotools.snowballtools.co", "source": "feature/add-remote-control", "latestCommit": { - "message": "Subscription added", + "message": "subscription added", "createdAt": "2023-12-11T04:20:00", "branch": "main" } @@ -17,14 +19,16 @@ { "id": 2, "icon": "^", - "title": "snowball-starter-kit", - "domain": "snowball-starter-kit.com", + "name": "snowball-starter-kit", + "title": "Snowball Starter Kit", + "organization": "Snowball", + "domain": "starterkit.snowballtools.com", "createdAt": "2023-12-04T04:20:00", - "createdBy": "Erin", - "deployment": "snowball-starter-kit.com", + "createdBy": "Bob", + "deployment": "deploy.snowballtools.com", "source": "prod/add-docker-compose", "latestCommit": { - "message": "404 added", + "message": "component updates", "createdAt": "2023-12-11T04:20:00", "branch": "staging" } @@ -32,14 +36,16 @@ { "id": 3, "icon": "^", - "title": "passkeys-demo", - "domain": "passkeys-demo.com", + "name": "web3-android", + "title": "Web3 Android", + "organization": "Personal", + "domain": "web3fordroids.com", "createdAt": "2023-12-01T04:20:00", "createdBy": "Charlie", - "deployment": "passkeys-demo.com", + "deployment": "deploy.web3fordroids.com", "source": "dev/style-page", "latestCommit": { - "message": "Design system integrated", + "message": "No repo connected", "createdAt": "2023-12-01T04:20:00", "branch": "main" } @@ -47,14 +53,50 @@ { "id": 4, "icon": "^", - "title": "watcher-tool", - "domain": "azimuth-watcher.com", + "name": "passkeys-demo", + "title": "Passkeys Demo", + "organization": "Airfoil", + "domain": "passkeys.iglootools.xyz", + "createdAt": "2023-12-01T04:20:00", + "createdBy": "David", + "deployment": "demo.passkeys.xyz", + "source": "dev/style-page", + "latestCommit": { + "message": "hello world", + "createdAt": "2023-12-01T04:20:00", + "branch": "main" + } + }, + { + "id": 5, + "icon": "^", + "name": "iglootools", + "title": "Iglootools", + "organization": "Airfoil", + "domain": "iglotools.xyz", "createdAt": "2023-12-11T04:20:00", - "createdBy": "Alice", - "deployment": "azimuth-watcher.com", + "createdBy": "Erin", + "deployment": "staging.snowballtools.com", "source": "prod/fix-error", "latestCommit": { - "message": "Listen for subscription", + "message": "404 added", + "createdAt": "2023-12-09T04:20:00", + "branch": "main" + } + }, + { + "id": 6, + "icon": "^", + "name": "iglootools", + "title": "Iglootools", + "organization": "Airfoil", + "domain": "iglotools.xyz", + "createdAt": "2023-12-11T04:20:00", + "createdBy": "Frank", + "deployment": "iglotools.snowballtools.com", + "source": "prod/fix-error", + "latestCommit": { + "message": "design system integrated", "createdAt": "2023-12-09T04:20:00", "branch": "prod" } diff --git a/packages/frontend/src/components/RepositoryList.tsx b/packages/frontend/src/components/RepositoryList.tsx index 9c3f02bf..9ee678e8 100644 --- a/packages/frontend/src/components/RepositoryList.tsx +++ b/packages/frontend/src/components/RepositoryList.tsx @@ -16,7 +16,7 @@ const RepositoryList = () => { />
- {}} placeholder="Search for repositorry" /> + {}} placeholder="Search for repository" />
{repositoryDetails.map((repo, key) => { diff --git a/packages/frontend/src/components/SearchBar.tsx b/packages/frontend/src/components/SearchBar.tsx index 8eb5e34c..6587825d 100644 --- a/packages/frontend/src/components/SearchBar.tsx +++ b/packages/frontend/src/components/SearchBar.tsx @@ -1,42 +1,42 @@ -import React from 'react'; -import { useForm, SubmitHandler } from 'react-hook-form'; +import React, { ChangeEventHandler, forwardRef } from 'react'; + +import { Input } from '@material-tailwind/react'; interface SearchBarProps { - handler: (searchText: SearchInputs) => void; + onChange: ChangeEventHandler; + value?: string; placeholder?: string; } -interface SearchInputs { - search: string; -} - -const SearchBar: React.FC = ({ - handler, - placeholder = 'Search', -}) => { - const { register, handleSubmit } = useForm({ - defaultValues: { - search: '', - }, - }); - - const onSubmit: SubmitHandler = (data) => { - handler(data); - }; - +const SearchBar: React.ForwardRefRenderFunction< + HTMLInputElement, + SearchBarProps +> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => { return ( -
-
^
-
- -
+
+ +
+ ^ +
); }; -export default SearchBar; +export default forwardRef(SearchBar); diff --git a/packages/frontend/src/components/ProjectCard.tsx b/packages/frontend/src/components/projects/ProjectCard.tsx similarity index 87% rename from packages/frontend/src/components/ProjectCard.tsx rename to packages/frontend/src/components/projects/ProjectCard.tsx index 8d4d9486..2e8719fd 100644 --- a/packages/frontend/src/components/ProjectCard.tsx +++ b/packages/frontend/src/components/projects/ProjectCard.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { relativeTime } from '../utils/time'; -import { ProjectDetails } from '../types/project'; +import { relativeTime } from '../../utils/time'; +import { ProjectDetails } from '../../types/project'; interface ProjectCardProps { project: ProjectDetails; @@ -15,7 +15,7 @@ const ProjectCard: React.FC = ({ project }) => {
{project.icon}
- {project.title} + {project.name}

{project.domain}

diff --git a/packages/frontend/src/components/projects/ProjectSearch.tsx b/packages/frontend/src/components/projects/ProjectSearch.tsx new file mode 100644 index 00000000..c4f7890e --- /dev/null +++ b/packages/frontend/src/components/projects/ProjectSearch.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { useCombobox } from 'downshift'; + +import { + List, + ListItem, + ListItemPrefix, + Card, + Typography, +} from '@material-tailwind/react'; + +import SearchBar from '../SearchBar'; +import { ProjectDetails } from '../../types/project'; +import projectsData from '../../assets/projects.json'; + +interface ProjectsSearchProps { + onChange?: (data: ProjectDetails) => void; +} + +const ProjectSearch = ({ onChange }: ProjectsSearchProps) => { + const [items, setItems] = useState([]); + const [selectedItem, setSelectedItem] = useState(null); + + const { + isOpen, + getMenuProps, + getInputProps, + getItemProps, + highlightedIndex, + } = useCombobox({ + onInputValueChange({ inputValue }) { + setItems( + inputValue + ? projectsData.filter((project) => project.title.includes(inputValue)) + : [], + ); + }, + items, + itemToString(item) { + return item ? item.title : ''; + }, + selectedItem, + onSelectedItemChange: ({ selectedItem: newSelectedItem }) => { + if (newSelectedItem) { + setSelectedItem(newSelectedItem); + + if (onChange) { + onChange(newSelectedItem); + } + } + }, + }); + + return ( +
+ + +

Suggestions

+ + {items.map((item, index) => ( + + + ^ + +
+ + {item.title} + + + {item.organization} + +
+
+ ))} +
+
+
+ ); +}; + +export default ProjectSearch; diff --git a/packages/frontend/src/pages/Projects.tsx b/packages/frontend/src/pages/Projects.tsx index 6a155fcf..0bef140c 100644 --- a/packages/frontend/src/pages/Projects.tsx +++ b/packages/frontend/src/pages/Projects.tsx @@ -3,17 +3,17 @@ import { Link } from 'react-router-dom'; import { Button } from '@material-tailwind/react'; -import SearchBar from '../components/SearchBar'; -import ProjectCard from '../components/ProjectCard'; +import ProjectCard from '../components/projects/ProjectCard'; import HorizontalLine from '../components/HorizontalLine'; import projectsDetail from '../assets/projects.json'; +import ProjectSearch from '../components/projects/ProjectSearch'; const Projects = () => { return (
- {}} /> + {}} />
^
^
diff --git a/packages/frontend/src/types/project.ts b/packages/frontend/src/types/project.ts index 675ef729..602a53e6 100644 --- a/packages/frontend/src/types/project.ts +++ b/packages/frontend/src/types/project.ts @@ -1,6 +1,8 @@ export interface ProjectDetails { icon: string; + name: string; title: string; + organization: string; domain: string; id: number; createdAt: string; diff --git a/yarn.lock b/yarn.lock index fda211ce..685c9863 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1146,6 +1146,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.22.15": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" + integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" @@ -4322,6 +4329,11 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" +compute-scroll-into-view@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz#753f11d972596558d8fe7c6bcbc8497690ab4c87" + integrity sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -5103,6 +5115,17 @@ dotenv@~16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +downshift@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/downshift/-/downshift-8.2.3.tgz#27106a5d9f408a6f6f9350ca465801d07e52db87" + integrity sha512-1HkvqaMTZpk24aqnXaRDnT+N5JCbpFpW+dCogB11+x+FCtfkFX0MbAO4vr/JdXi1VYQF174KjNUveBXqaXTPtg== + dependencies: + "@babel/runtime" "^7.22.15" + compute-scroll-into-view "^3.0.3" + prop-types "^15.8.1" + react-is "^18.2.0" + tslib "^2.6.2" + duplexer@^0.1.1, duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -10361,7 +10384,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==