import { useState } from "react"; import { Plus } from "lucide-react"; import { Button, Text } from "@godxjp/ui/general"; import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@godxjp/ui/data-entry"; import { Flex, PageContainer } from "@godxjp/ui/layout"; import { FilterBar, FilterBarGroup } from "@godxjp/ui/navigation"; const STATUSES = [ { value: "all", label: "All statuses" }, { value: "active", label: "Active" }, { value: "paused", label: "Paused" }, ]; const MODEL_STATUSES = [ { value: "active", label: "有効" }, { value: "invited", label: "招待中" }, { value: "suspended", label: "停止" }, ]; const MODEL_ROLES = [ { value: "member", label: "Thành viên" }, { value: "manager", label: "Quản lý" }, ]; /** FilterBar — domain-neutral list filtering with consumer-owned state and clear behavior. */ export default function Demo() { const [query, setQuery] = useState(""); const [status, setStatus] = useState("all"); const [department, setDepartment] = useState("all"); const [approval, setApproval] = useState("all"); const [period, setPeriod] = useState("all"); const hasActiveFilters = query !== "" || status !== "all"; // Typed model — ALL state is consumer data; the bar only renders it. const [modelQuery, setModelQuery] = useState("田中"); const [modelStatus, setModelStatus] = useState("active"); const [modelRole, setModelRole] = useState(""); const modelChips = [ ...(modelQuery !== "" ? [{ value: "q", label: `検索: ${modelQuery}` }] : []), ...(modelStatus !== "" ? [ { value: "status", label: `状態: ${MODEL_STATUSES.find((s) => s.value === modelStatus)?.label ?? modelStatus}`, }, ] : []), ...(modelRole !== "" ? [ { value: "role", label: `Vai trò: ${MODEL_ROLES.find((r) => r.value === modelRole)?.label ?? modelRole}`, }, ] : []), ]; const removeModelChip = (value: string) => { if (value === "q") setModelQuery(""); if (value === "status") setModelStatus(""); if (value === "role") setModelRole(""); }; const clearModelFilters = () => { setModelQuery(""); setModelStatus(""); setModelRole(""); }; const modelResultCount = modelChips.length === 0 ? 128 : 42 - modelChips.length * 11; return ( {/* Typed model — search/filters/chips/reset/result-count/actions as DATA. The bar owns layout, token widths, chip lifecycle and keyboard order; the page owns every piece of state. Remove a chip, reset, or pick a filter and watch the chips row and the localized result count follow. */} typed model · search + filters + chips + reset + result count + actions, all consumer-controlled data 0} resultCount={modelResultCount} actions={ } /> {/* error — the ONE sentence that says why a filter set was refused, and the reason this page has an example at all: `.ui-filter-bar-error` was painted with the destructive FILL tier (2.95:1 on dark, gh#612) and NO route rendered it, so no sweep could ever have caught that. Static, not behind a submit: a surface that needs an interaction is a surface `check:contrast` does not measure. */} error · role="alert" line under the bar, painted from the TEXT tier {/* overflow="wrap" (default) — stacked below 640px, wrapping rows above. */} overflow="wrap" (default) · a small filter set wraps onto extra rows { setQuery(""); setStatus("all"); }} > {/* controlId makes the visible caption the Select's real {/* overflow="scroll" — one bounded inline-scrolling row at >= 640px. The long JA/EN/VI captions are the point: with `wrap` this same set becomes a three-row strip. */} overflow="scroll" · many filters with long JA/EN/VI labels stay on ONE bounded row (still stacked below 640px) { setDepartment("all"); setApproval("all"); setPeriod("all"); }} > ); }