From 261254576bc0dd28eb63dd8a202e3bd0be9c5603 Mon Sep 17 00:00:00 2001 From: Sonny Date: Wed, 17 May 2023 12:55:59 +0200 Subject: [PATCH] feat: add search item cursor --- src/components/Modal/Modal.tsx | 6 +-- src/components/SearchModal/SearchList.tsx | 11 +++- src/components/SearchModal/SearchListItem.tsx | 53 +++++++++++++++++-- src/components/SearchModal/SearchModal.tsx | 32 +++++------ src/pages/index.tsx | 2 +- src/types.d.ts | 1 + 6 files changed, 79 insertions(+), 26 deletions(-) diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index a646aba..f50a0bb 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -33,14 +33,14 @@ export default function Modal({ onClick={handleWrapperClick} initial={{ opacity: 0 }} animate={{ opacity: 1 }} - exit={{ opacity: 0, transition: { duration: 0.1 } }} + exit={{ opacity: 0, transition: { duration: 0.1, delay: 0.1 } }} > {!noHeader && (
diff --git a/src/components/SearchModal/SearchList.tsx b/src/components/SearchModal/SearchList.tsx index 857d780..aeca1cb 100644 --- a/src/components/SearchModal/SearchList.tsx +++ b/src/components/SearchModal/SearchList.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import { ReactNode, useMemo } from "react"; import { SearchItem } from "types"; import SearchListItem from "./SearchListItem"; @@ -8,14 +8,18 @@ import styles from "./search.module.scss"; export default function SearchList({ items, noItem, + cursor, + setCursor, }: { items: SearchItem[]; noItem?: ReactNode; + cursor: number; + setCursor: (cursor: number) => void; }) { return (
    {items.length > 0 ? ( - items.map((item) => ( + items.map((item, index) => ( )) diff --git a/src/components/SearchModal/SearchListItem.tsx b/src/components/SearchModal/SearchListItem.tsx index cb8d89f..2f4ea78 100644 --- a/src/components/SearchModal/SearchListItem.tsx +++ b/src/components/SearchModal/SearchListItem.tsx @@ -1,23 +1,66 @@ +import { motion } from "framer-motion"; import LinkTag from "next/link"; import { AiOutlineFolder } from "react-icons/ai"; import LinkFavicon from "components/Links/LinkFavicon"; import { SearchItem } from "types"; +import { useEffect, useId, useRef, useState } from "react"; import styles from "./search.module.scss"; -export default function SearchListItem({ item }: { item: SearchItem }) { +export default function SearchListItem({ + item, + index, + selected, + setCursor, +}: { + item: SearchItem; + index?: number; + selected: boolean; + setCursor: (cursor: number) => void; +}) { + const id = useId(); + const ref = useRef(null); + + const [isHover, setHover] = useState(false); + const { name, type, url } = item; + + useEffect(() => { + if (selected && !isHover) { + console.log(selected, ref.current); + ref.current?.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, [isHover, selected]); + return ( -
  • + { + setCursor(index); + setHover(true); + }} + onMouseLeave={() => setHover(false)} + key={id} + > {type === "link" ? ( - + ) : ( - + )} {name} + {selected && "selected"} -
  • + ); } diff --git a/src/components/SearchModal/SearchModal.tsx b/src/components/SearchModal/SearchModal.tsx index 841fd80..f682879 100644 --- a/src/components/SearchModal/SearchModal.tsx +++ b/src/components/SearchModal/SearchModal.tsx @@ -11,7 +11,7 @@ import SearchList from "./SearchList"; import * as Keys from "constants/keys"; import { GOOGLE_SEARCH_URL } from "constants/search-urls"; -import { Category, Link, SearchItem } from "types"; +import { Category, SearchItem } from "types"; import styles from "./search.module.scss"; @@ -19,13 +19,11 @@ export default function SearchModal({ close, handleSelectCategory, categories, - favorites, items, }: { close: any; handleSelectCategory: (category: Category) => void; categories: Category[]; - favorites: Link[]; items: SearchItem[]; }) { const autoFocusRef = useAutoFocus(); @@ -51,12 +49,16 @@ export default function SearchModal({ [items, search] ); - useHotkeys(Keys.ARROW_LEFT, () => { - console.log("left"); + useHotkeys(Keys.ARROW_UP, () => setCursor((cursor) => (cursor -= 1)), { + enableOnFormTags: ["INPUT"], + enabled: itemsCompletion.length > 1 && cursor !== 0, + preventDefault: true, }); - - useHotkeys(Keys.ARROW_RIGHT, () => { - console.log("right"); + useHotkeys(Keys.ARROW_DOWN, () => setCursor((cursor) => (cursor += 1)), { + enableOnFormTags: ["INPUT"], + enabled: + itemsCompletion.length > 1 && cursor !== itemsCompletion.length - 1, + preventDefault: true, }); const handleSearchInputChange = useCallback((value) => { @@ -74,19 +76,17 @@ export default function SearchModal({ return close(); } - // TODO: replace "firstItem" by a "cursor" - const firstItem = itemsCompletion[0]; - - const category = categories.find((c) => c.id === firstItem.id); - if (firstItem.type === "category" && category) { + const selectedItem = itemsCompletion[cursor]; + const category = categories.find((c) => c.id === selectedItem.id); + if (selectedItem.type === "category" && category) { handleSelectCategory(category); return close(); } - window.open(firstItem.url); + window.open(selectedItem.url); close(); }, - [categories, close, handleSelectCategory, itemsCompletion, search] + [categories, close, cursor, handleSelectCategory, itemsCompletion, search] ); return ( @@ -121,6 +121,8 @@ export default function SearchModal({ type: item.type, }))} noItem={} + cursor={cursor} + setCursor={setCursor} /> )}