38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
import { useEffect, useId, useRef } from "react";
|
|
import type { ReactNode } from "react";
|
|
|
|
export type SearchToolbarProps = {
|
|
readonly filters?: ReactNode;
|
|
readonly onQueryChange: (query: string) => void;
|
|
readonly onSearchChange: (search: string) => void;
|
|
readonly search: string;
|
|
};
|
|
|
|
export function SearchToolbar({ filters, onQueryChange, onSearchChange, search }: SearchToolbarProps) {
|
|
const searchId = useId();
|
|
const didMountRef = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (!didMountRef.current) {
|
|
didMountRef.current = true;
|
|
return undefined;
|
|
}
|
|
|
|
const timeoutId = window.setTimeout(() => onQueryChange(search), 300);
|
|
|
|
return () => window.clearTimeout(timeoutId);
|
|
}, [onQueryChange, search]);
|
|
|
|
return (
|
|
<section aria-label="검색 도구" className="flex flex-col gap-3 rounded-lg border border-border bg-card p-4 sm:flex-row sm:items-end sm:justify-between">
|
|
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
<label className="text-sm font-semibold" htmlFor={searchId}>
|
|
검색어
|
|
</label>
|
|
<input className="rounded-md border border-input bg-card px-3 py-2 text-base text-foreground" id={searchId} onChange={(event) => onSearchChange(event.currentTarget.value)} type="search" value={search} />
|
|
</div>
|
|
{filters === undefined ? null : <div className="flex flex-col gap-2 sm:min-w-48">{filters}</div>}
|
|
</section>
|
|
);
|
|
}
|