{
  "aliases": [
    "filter-layouts",
    "faceted-filter",
    "faceted-sidebar",
    "saved-views-filter",
    "docked-filter-panel",
    "filter-panel",
    "filter-menu"
  ],
  "code": "// Live gallery: docs page navigation/filter-bar-layouts (every layout working, JA/EN/VI data,\n// measured at 1280 and 390: filters 12px apart, no sideways overflow).\n//\n// THE ONE RULE ALL ELEVEN SHARE: filters are PACKED at the start of the bar with its one gap.\n// The only element at the far end is FilterBar's actions slot (with the clear button). A Flex/Card\n// of FormFields above a table — worst with justify=\"between\" — is ui-audit hand-rolled-filter-bar.\n//\n// a  inline packed ........ 2–4 frequent filters · Ant Design ProTable query form (collapsed)\n//    <FilterBar search={…} filters={[…]} resultCount={n} onClear={…}> + FilterBarGroup(Switch)\n// b  search + Filter menu .. search does most of the work · Linear, GitHub issues\n// c  promoted + More ....... more than 2–4 filters · Shopify Polaris IndexFilters → pattern list-filters\n// d  saved views ........... a view = a whole filter set · Polaris IndexTable views, Jira\n//    views={<Tabs controls=\"results-id\">…<TabsTrigger count={n} countLabel=\"件\">…} on FilterBar\n// e  two rows .............. the query is the main input · Gmail, Amazon\n//    <Flex direction=\"col\" gap=\"sm\"><SearchInput …/><FilterBar filters={[…]}>…</FilterBar></Flex>\n// f  actions at the end .... page actions + pinned bar · ProTable toolbar, Stripe lists\n//    <FilterBar sticky search filters actions={<><Button>CSV</Button><Button>追加</Button></>} />\n// g  faceted sidebar ....... many multi-select facets with counts · Amazon, Rakuten\n// h  compact in a card ..... a secondary table owns its filters · GitHub file / Actions tables\n//    <Card><CardContent><Flex direction=\"col\" gap=\"sm\"><CardTitle/><FilterBar clearDisplay=\"icon\">…\n//    </FilterBar></Flex></CardContent><CardContent flush><DataTable density=\"compact\"/></CardContent></Card>\n//    (FilterBar has no size prop; density comes from the app/PageContainer density axis.)\n// i  date range ............ time is the first filter · Stripe, Google Analytics\n//    <FilterBarGroup label=\"期間\"><Segmented options={7日/30日/四半期/期間指定} /></FilterBarGroup>\n//    + <DatePicker range /> in a second FilterBarGroup only when 期間指定 is picked\n// j  phone ................. below 640px every FilterBar stacks; extra filters in <SheetContent side=\"bottom\">\n// k  docked panel .......... long sessions on one table · Airtable/Notion view filters, Jira, Gmail\n//\n// SUBMIT-DRIVEN SHAPES (docs page navigation/search-form) — when a live bar is the wrong tool:\n// l  検索条件フォーム ....... many conditions / heavy query / users expect 検索 · Ant Design\n//    ProComponents QueryFilter → component SearchForm, pattern search-form\n// m  condition builder ...... field / operator / value rows, all · any · \"1 AND (2 OR 3)\", saved\n//    views · kintone filters, Salesforce filter logic → pattern condition-builder\n// n  advanced search ........ label | input | what the row means · Google Advanced Search → pattern search-form\n// o  list view + docked panel  list-view picker, pin, search, Filters panel · Salesforce Lightning\n//    list views → pattern condition-builder\n//\n// Below: b, g and k in full — the three that compose more than FilterBar.\nimport * as React from \"react\";\nimport { ListFilter, PanelRightClose, PanelRightOpen, SlidersHorizontal } from \"lucide-react\";\nimport { Card, CardContent, CardHeader, CardTitle, DataTable, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { Checkbox } from \"@godxjp/ui/data-entry\";\nimport {\n  Sheet, SheetBody, SheetClose, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,\n} from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { useMediaQuery } from \"@godxjp/ui/hooks\";\nimport { Flex, MasterDetail, ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@godxjp/ui/layout\";\nimport {\n  DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger,\n  FilterBar, FilterBarGroup,\n} from \"@godxjp/ui/navigation\";\nimport type { PanelImperativeHandle } from \"react-resizable-panels\";\n\ntype Row = { id: string; name: string; country: string };\ntype Option = { value: string; label: string };\nconst COUNTRIES: Option[] = [{ value: \"vn\", label: \"Việt Nam\" }, { value: \"ph\", label: \"Philippines\" }];\nconst toggle = (list: string[], v: string, on: boolean) => (on ? [...list, v] : list.filter((x) => x !== v));\n\n// ── b. Search first + ONE Filter menu + chips (Linear / GitHub) ──────────────────────────────\nexport function SearchFirstMenu({ q, setQ, countries, setCountries }: {\n  q: string; setQ: (v: string) => void; countries: string[]; setCountries: (v: string[]) => void;\n}) {\n  const chips = countries.map((c) => ({ value: c, label: \"国: \" + COUNTRIES.find((o) => o.value === c)?.label }));\n  return (\n    <FilterBar\n      search={{ value: q, onValueChange: setQ, placeholder: \"Search\", ariaLabel: \"Search\" }}\n      chips={chips}\n      onChipRemove={(v) => setCountries(countries.filter((c) => c !== v))}\n      hasActiveFilters={chips.length > 0 || q !== \"\"}\n      onClear={() => { setQ(\"\"); setCountries([]); }}\n      clearDisplay=\"icon\"\n    >\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button variant=\"outline\" count={chips.length} showZero={false} countLabel=\"filters applied\">\n            <ListFilter aria-hidden=\"true\" />Filter\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent>\n          <DropdownMenuLabel>国 · Country</DropdownMenuLabel>\n          {COUNTRIES.map((c) => (\n            <DropdownMenuCheckboxItem key={c.value} checked={countries.includes(c.value)}\n              onCheckedChange={(on) => setCountries(toggle(countries, c.value, on === true))}>\n              {c.label}\n            </DropdownMenuCheckboxItem>\n          ))}\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </FilterBar>\n  );\n}\n\n// Facets: a column of FilterBarGroups holding checkboxes — shared by g and k.\nfunction Facets({ countries, setCountries }: { countries: string[]; setCountries: (v: string[]) => void }) {\n  return (\n    <FilterBarGroup label=\"国 · Country\">\n      <Flex direction=\"col\" gap=\"xs\">\n        {COUNTRIES.map((c) => (\n          <Checkbox key={c.value} checked={countries.includes(c.value)}\n            onCheckedChange={(on) => setCountries(toggle(countries, c.value, on === true))}>\n            {c.label}\n          </Checkbox>\n        ))}\n      </Flex>\n    </FilterBarGroup>\n  );\n}\n\n// ── g. Faceted sidebar (Amazon / Rakuten): MasterDetail rail on the left; a Sheet when narrow ─\nexport function FacetedList({ rows, columns, countries, setCountries }: {\n  rows: Row[]; columns: ColumnDef<Row>[]; countries: string[]; setCountries: (v: string[]) => void;\n}) {\n  const wide = useMediaQuery(\"(min-width: 768px)\");\n  const table = <Card><CardContent flush><DataTable data={rows} columns={columns} getRowId={(r) => r.id} /></CardContent></Card>;\n  if (wide) {\n    return (\n      <MasterDetail rail=\"master\" railWidth=\"narrow\" collapseBelow={false} masterLabel=\"絞り込み\"\n        detailLabel=\"一覧\" master={<Facets countries={countries} setCountries={setCountries} />}>\n        {table}\n      </MasterDetail>\n    );\n  }\n  return (\n    <Flex direction=\"col\" gap=\"sm\">\n      <FilterBar hasActiveFilters={countries.length > 0} onClear={() => setCountries([])}>\n        <Sheet>\n          <SheetTrigger asChild>\n            <Button variant=\"outline\" count={countries.length} showZero={false} countLabel=\"filters applied\">\n              <SlidersHorizontal aria-hidden=\"true\" />絞り込み\n            </Button>\n          </SheetTrigger>\n          <SheetContent side=\"left\">\n            <SheetHeader><SheetTitle>絞り込み</SheetTitle></SheetHeader>\n            <SheetBody><Facets countries={countries} setCountries={setCountries} /></SheetBody>\n            <SheetFooter><SheetClose asChild><Button>結果を表示</Button></SheetClose></SheetFooter>\n          </SheetContent>\n        </Sheet>\n      </FilterBar>\n      {table}\n    </Flex>\n  );\n}\n\n// ── k. Docked filter panel (Airtable / Jira / Gmail side panel) ──────────────────────────────\n// Persistent (not an overlay), resizable, collapsible, remembers open/closed; chips stay in the\n// bar above the table; below 1024px the same facets open in a Sheet instead.\nconst DOCK_KEY = \"my-app:list-filter-panel-open\";\nexport function DockedFilterList({ rows, columns, countries, setCountries }: {\n  rows: Row[]; columns: ColumnDef<Row>[]; countries: string[]; setCountries: (v: string[]) => void;\n}) {\n  const docked = useMediaQuery(\"(min-width: 1024px)\");\n  const panelRef = React.useRef<PanelImperativeHandle | null>(null);\n  const [open, setOpen] = React.useState(() => {\n    try { return localStorage.getItem(DOCK_KEY) !== \"false\"; } catch { return true; }\n  });\n  const remember = (next: boolean) => {\n    setOpen(next);\n    try { localStorage.setItem(DOCK_KEY, String(next)); } catch { /* not remembered, still works */ }\n  };\n  const chips = countries.map((c) => ({ value: c, label: \"国: \" + COUNTRIES.find((o) => o.value === c)?.label }));\n  const table = <Card><CardContent flush><DataTable data={rows} columns={columns} getRowId={(r) => r.id} /></CardContent></Card>;\n  return (\n    <Flex direction=\"col\" gap=\"sm\">\n      <FilterBar\n        chips={chips}\n        onChipRemove={(v) => setCountries(countries.filter((c) => c !== v))}\n        hasActiveFilters={chips.length > 0}\n        onClear={() => setCountries([])}\n        actions={docked ? (\n          <Button variant=\"outline\" size=\"sm\" aria-expanded={open} aria-controls=\"list-filter-panel\"\n            count={chips.length} showZero={false} countLabel=\"filters applied\"\n            onClick={() => { if (open) panelRef.current?.collapse(); else panelRef.current?.expand(); remember(!open); }}>\n            {open ? <PanelRightClose aria-hidden=\"true\" /> : <PanelRightOpen aria-hidden=\"true\" />}\n            {open ? \"フィルターパネルを閉じる\" : \"フィルターパネルを開く\"}\n          </Button>\n        ) : (\n          <Sheet>\n            <SheetTrigger asChild>\n              <Button variant=\"outline\" size=\"sm\" count={chips.length} showZero={false} countLabel=\"filters applied\">\n                <SlidersHorizontal aria-hidden=\"true\" />フィルター\n              </Button>\n            </SheetTrigger>\n            <SheetContent side=\"right\">\n              <SheetHeader><SheetTitle>フィルター</SheetTitle></SheetHeader>\n              <SheetBody><Facets countries={countries} setCountries={setCountries} /></SheetBody>\n            </SheetContent>\n          </Sheet>\n        )}\n      />\n      {docked ? (\n        <ResizablePanelGroup orientation=\"horizontal\">\n          <ResizablePanel id=\"list-main\" defaultSize=\"72%\" minSize=\"50%\">{table}</ResizablePanel>\n          <ResizableHandle />\n          <ResizablePanel id=\"list-filter-panel\" panelRef={panelRef} collapsible collapsedSize=\"0%\"\n            defaultSize={open ? \"28%\" : \"0%\"} minSize=\"20%\" maxSize=\"40%\"\n            onResize={(size) => { const next = size.asPercentage > 0; if (next !== open) remember(next); }}>\n            {/* Render nothing while collapsed: content squeezed to 0px wraps one glyph per line. */}\n            {open && (\n              <Card>\n                <CardHeader><CardTitle level={3}>フィルター</CardTitle></CardHeader>\n                <CardContent><Facets countries={countries} setCountries={setCountries} /></CardContent>\n              </Card>\n            )}\n          </ResizablePanel>\n        </ResizablePanelGroup>\n      ) : table}\n    </Flex>\n  );\n}\n// SplitPane is the non-resizable alternative for k (its aside is always inline-end; aside={null}\n// closes it). A LEFT docked panel is g's MasterDetail rail=\"master\".",
  "name": "filter-bar-layouts",
  "tagline": "Eleven list-filter layouts from leading products, all FilterBar + existing primitives: (a) inline packed, (b) search-first + ONE Filter menu + chips, (c) promoted + More-filters Sheet, (d) saved views above the bar, (e) wide search then filters, (f) actions at the end + sticky, (g) faceted sidebar, (h) compact bar in a table card, (i) date range + quick ranges, (j) phone stack + Sheet, (k) docked side panel. Filters always pack at the START.",
  "tags": [
    "filter",
    "filters",
    "layout",
    "faceted",
    "facets",
    "saved views",
    "docked",
    "panel",
    "sidebar",
    "chips",
    "date range",
    "mobile",
    "list",
    "datatable"
  ]
}
