From 1dd0ea91015ee51964cd086def2f3f4648d41b91 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 19 Feb 2024 12:23:33 -0600 Subject: [PATCH] Seed basic command-k data --- package.json | 3 +- pnpm-lock.yaml | 12 ++++ src/app/layout.tsx | 2 +- src/components/CommandK.tsx | 54 -------------- src/components/CommandKClient.tsx | 116 ++++++++++++++++++++++++++++++ src/components/Modal.tsx | 10 ++- src/site/CommandK.tsx | 53 ++++++++++++++ 7 files changed, 193 insertions(+), 57 deletions(-) delete mode 100644 src/components/CommandK.tsx create mode 100644 src/components/CommandKClient.tsx create mode 100644 src/site/CommandK.tsx diff --git a/package.json b/package.json index 6d9cdb21..936095c7 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "sonner": "^1.4.0", "tailwindcss": "3.4.1", "ts-exif-parser": "^0.2.2", - "typescript": "5.3.3" + "typescript": "5.3.3", + "use-debounce": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0d7071d..08dc4266 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ dependencies: typescript: specifier: 5.3.3 version: 5.3.3 + use-debounce: + specifier: ^10.0.0 + version: 10.0.0(react@18.2.0) packages: @@ -7569,6 +7572,15 @@ packages: tslib: 2.6.2 dev: false + /use-debounce@10.0.0(react@18.2.0): + resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1dd04743..6e40f293 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,7 +13,7 @@ import Footer from '@/site/Footer'; import { Suspense } from 'react'; import FooterClient from '@/site/FooterClient'; import NavClient from '@/site/NavClient'; -import CommandK from '@/components/CommandK'; +import CommandK from '@/site/CommandK'; import '../site/globals.css'; diff --git a/src/components/CommandK.tsx b/src/components/CommandK.tsx deleted file mode 100644 index a42187f2..00000000 --- a/src/components/CommandK.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import { Command } from 'cmdk'; -import { useEffect, useState } from 'react'; -import Modal from './Modal'; -import { clsx } from 'clsx/lite'; - -const LISTENER_KEYDOWN = 'keydown'; - -export default function CommandK() { - const [open, setOpen] = useState(false); - - useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); - setOpen((open) => !open); - } - }; - document.addEventListener(LISTENER_KEYDOWN, down); - return () => document.removeEventListener(LISTENER_KEYDOWN, down); - }, []); - - const renderItem = (item: string) => - - {item} - ; - - return ( - - setOpen(false)} fast> - - - No results found. - - {renderItem('a')} - {renderItem('b')} - - {renderItem('c')} - - - {renderItem('Apple')} - - - - ); -} diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx new file mode 100644 index 00000000..c3bc9dbd --- /dev/null +++ b/src/components/CommandKClient.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { Command } from 'cmdk'; +import { useEffect, useState } from 'react'; +import Modal from './Modal'; +import { clsx } from 'clsx/lite'; +import { useDebounce } from 'use-debounce'; +import Spinner from './Spinner'; +import { useRouter } from 'next/navigation'; + +const LISTENER_KEYDOWN = 'keydown'; + +export default function CommandKClient({ + isLoading, + onQueryChange, + sections = [], +}: { + isLoading?: boolean + onQueryChange?: (query: string) => void + sections?: { + heading: string + items: { + label: string + path: string + }[] + }[] +}) { + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(''); + const [queryDebounced] = useDebounce(query, 1000); + + const router = useRouter(); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + document.addEventListener(LISTENER_KEYDOWN, down); + return () => document.removeEventListener(LISTENER_KEYDOWN, down); + }, []); + + useEffect(() => { + if (queryDebounced) { + onQueryChange?.(queryDebounced); + } + }, [queryDebounced, onQueryChange]); + + return ( + + setOpen(false)} + fast + > +
+
+ Search for photos, tags, cameras, and film +
+
+ setQuery(e.currentTarget.value)} + className="w-full !min-w-0" + style={{ paddingRight: '2rem' }} + /> + {isLoading && + + + } +
+ + + + ); +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 7c879bd4..646b4d84 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -12,11 +12,15 @@ import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; export default function Modal({ onClosePath, onClose, + className, + anchor = 'center', children, fast, }: { onClosePath?: string onClose?: () => void + className?: string + anchor?: 'top' | 'center' children: ReactNode fast?: boolean }) { @@ -51,7 +55,10 @@ export default function Modal({ return ( []), + getUniqueCamerasCached().catch(() => []), + getUniqueFilmSimulationsCached().catch(() => []), + ]); + + return ({ + label: tag, + path: pathForTag(tag), + })), + }, { + heading: 'Cameras', + items: cameras.map(({ camera }) => ({ + label: formatCameraText(camera), + path: pathForCamera(camera), + })), + }, { + heading: 'Film Simulations', + items: filmSimulations.map(({ simulation }) => ({ + label: simulation, + path: pathForFilmSimulation(simulation), + })), + }, + ]} + />; +}