import { useId, useRef, useState } from "react"; import { Link } from "react-router-dom"; import { usePublicContent } from "../use-public-content.tsx"; type SearchDialogProps = { className?: string; onBeforeOpen?: () => void; }; export function SearchDialog({ className = "", onBeforeOpen, }: SearchDialogProps) { const dialogId = useId(); const dialogRef = useRef(null); const inputRef = useRef(null); const triggerRef = useRef(null); const [query, setQuery] = useState(""); const normalizedQuery = query.trim().toLocaleLowerCase("ko-KR"); // Keyed on the empty query, then filtered here, rather than one request per // keystroke. This is a type-ahead: re-querying per character would replace the // result list with a loading skeleton on every key, which is a worse dialog // than a stale-free local filter. The predicate is the same one the catalog // applies for a non-empty query, so the visible result set is unchanged. const view = usePublicContent(["tech-log", "search", "dialog"], async (queries) => ({ entities: await queries.searchPublicContent(""), })); const results = (view.data?.entities ?? []).filter((entity) => normalizedQuery ? [entity.title, entity.summary, entity.topic, entity.project, ...(entity.topics ?? [])] .filter((value): value is string => Boolean(value)) .some((value) => value.toLocaleLowerCase("ko-KR").includes(normalizedQuery)) : true, ); function open() { onBeforeOpen?.(); dialogRef.current?.showModal(); requestAnimationFrame(() => inputRef.current?.focus()); } function close() { dialogRef.current?.close(); } return ( <> triggerRef.current?.focus()} onKeyDown={(event) => { if (event.key === "Escape") { event.preventDefault(); close(); } }} onClick={(event) => { if (event.target === event.currentTarget) close(); }} >

Public records

TechLog 검색

{results.length}개의 공개 기록

{results.length > 0 ? ( <>
    {results.map((entry) => (
  • {entry.contentType} {entry.title}

    {entry.summary}

  • ))}
{normalizedQuery ? ( 전체 검색 결과 보기 ) : null} ) : (

일치하는 공개 기록이 없습니다.

)}
); }