mirror of
https://github.com/snowball-tools/snowballtools-base.git
synced 2024-11-17 08:09:20 +00:00
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
This commit is contained in:
parent
3c220c5dc6
commit
0b91771e90
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ const RepositoryList = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className="basis-2/3">
|
||||
<SearchBar handler={() => {}} placeholder="Search for repositorry" />
|
||||
<SearchBar onChange={() => {}} placeholder="Search for repository" />
|
||||
</div>
|
||||
</div>
|
||||
{repositoryDetails.map((repo, key) => {
|
||||
|
@ -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<HTMLInputElement>;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
interface SearchInputs {
|
||||
search: string;
|
||||
}
|
||||
|
||||
const SearchBar: React.FC<SearchBarProps> = ({
|
||||
handler,
|
||||
placeholder = 'Search',
|
||||
}) => {
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
search: '',
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<SearchInputs> = (data) => {
|
||||
handler(data);
|
||||
};
|
||||
|
||||
const SearchBar: React.ForwardRefRenderFunction<
|
||||
HTMLInputElement,
|
||||
SearchBarProps
|
||||
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
|
||||
return (
|
||||
<div className="w-full flex">
|
||||
<div className="text-gray-300">^</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
{...register('search')}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
className="grow text-gray-700 border-none focus:outline-none text-xs"
|
||||
/>
|
||||
</form>
|
||||
<div className="relative flex w-full gap-2">
|
||||
<Input
|
||||
variant="standard"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
type="search"
|
||||
placeholder={placeholder}
|
||||
containerProps={{
|
||||
className: 'min-w-[288px]',
|
||||
}}
|
||||
className="pl-9 placeholder:text-blue-gray-300 focus:!border-blue-gray-300"
|
||||
labelProps={{
|
||||
className: 'before:content-none after:content-none',
|
||||
}}
|
||||
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
|
||||
crossOrigin={undefined}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<div className="!absolute left-3 top-[13px]">
|
||||
<i>^</i>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
export default forwardRef(SearchBar);
|
||||
|
@ -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<ProjectCardProps> = ({ project }) => {
|
||||
<div>{project.icon}</div>
|
||||
<div className="grow">
|
||||
<Link to={`projects/${project.id}`} className="text-sm text-gray-700">
|
||||
{project.title}
|
||||
{project.name}
|
||||
</Link>
|
||||
<p className="text-sm text-gray-400">{project.domain}</p>
|
||||
</div>
|
93
packages/frontend/src/components/projects/ProjectSearch.tsx
Normal file
93
packages/frontend/src/components/projects/ProjectSearch.tsx
Normal file
@ -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<ProjectDetails[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<ProjectDetails | null>(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 (
|
||||
<div className="relative">
|
||||
<SearchBar {...getInputProps()} />
|
||||
<Card
|
||||
className={`absolute w-1/2 max-h-100 overflow-y-scroll ${
|
||||
!(isOpen && items.length) && 'hidden'
|
||||
}`}
|
||||
>
|
||||
<p>Suggestions</p>
|
||||
<List {...getMenuProps()}>
|
||||
{items.map((item, index) => (
|
||||
<ListItem
|
||||
selected={highlightedIndex === index || selectedItem === item}
|
||||
key={item.id}
|
||||
{...getItemProps({ item, index })}
|
||||
>
|
||||
<ListItemPrefix>
|
||||
<i>^</i>
|
||||
</ListItemPrefix>
|
||||
<div>
|
||||
<Typography variant="h6" color="blue-gray">
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="small"
|
||||
color="gray"
|
||||
className="font-normal"
|
||||
>
|
||||
{item.organization}
|
||||
</Typography>
|
||||
</div>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSearch;
|
@ -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 (
|
||||
<div className="bg-white rounded-3xl h-full">
|
||||
<div className="flex p-4">
|
||||
<div className="grow">
|
||||
<SearchBar handler={() => {}} />
|
||||
<ProjectSearch onChange={() => {}} />
|
||||
</div>
|
||||
<div className="text-gray-300">^</div>
|
||||
<div className="text-gray-300">^</div>
|
||||
|
@ -1,6 +1,8 @@
|
||||
export interface ProjectDetails {
|
||||
icon: string;
|
||||
name: string;
|
||||
title: string;
|
||||
organization: string;
|
||||
domain: string;
|
||||
id: number;
|
||||
createdAt: string;
|
||||
|
25
yarn.lock
25
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==
|
||||
|
Loading…
Reference in New Issue
Block a user