import { useEffect, useMemo, useState } from 'react'; import { Link, useSearchParams } from 'react-router'; import { Search, Sparkles } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { useClient } from 'hooks/useClient'; import { AssetViewer } from './AssetViewer'; import { PhotoGrid } from './PhotoGrid'; import { SelectionBar } from './SelectionBar'; import { ALBUM_PARAM, QUERY_PARAM, photosSectionPath, thumbUrl, toGridAsset } from './shared'; import { useSearch } from './usePhotosData'; import { useSelection } from './useSelection'; // Search. The query lives in `?q=`, so a search is a link — the same reason the lightbox lives in `?asset=`. // // Smart search is Immich's CLIP model ("red bicycle", "birthday cake") and is the default because it is the // thing Immich has that a file browser does not. The toggle falls back to filename/metadata matching. export const SearchSection = () => { const { token } = useClient(); const [params, setParams] = useSearchParams(); const submitted = params.get(QUERY_PARAM) ?? ''; const [draft, setDraft] = useState(submitted); const [smart, setSmart] = useState(true); // The box follows the URL, so a link into /photos/search?q=… arrives with its own query already typed. useEffect(() => setDraft(submitted), [submitted]); const { data, isFetching } = useSearch({ query: submitted, smart, enabled: submitted.length > 0 }); const assets = useMemo(() => (data?.assets.items ?? []).map(toGridAsset), [data]); const selection = useSelection(assets.map((asset) => asset.id)); const submit = (ev: React.FormEvent) => { ev.preventDefault(); setParams((current) => { const next = new URLSearchParams(current); if (draft.trim()) next.set(QUERY_PARAM, draft.trim()); else next.delete(QUERY_PARAM); return next; }); }; return (
{selection.isSelecting ? ( ) : (
setDraft(ev.target.value)} placeholder={smart ? 'Describe a photo — "sunset over water"' : 'Filename or path'} className="h-8" />
)}
{!submitted &&

Search your library.

} {submitted && isFetching &&

Searching…

} {data && data.albums.items.length > 0 && (

Albums

{data.albums.items.map((album) => (
{album.albumThumbnailAssetId && ( )}

{album.albumName}

))}
)} {submitted && !isFetching && ( No matches.

} /> )}
asset.id)} token={token} />
); };