{
  "docPath": "navigation/toolbar",
  "example": "import { Toolbar, ToolbarGroup } from \"@godxjp/ui/navigation\";\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\";\n\n<Toolbar sticky hasActiveFilters={hasFilters} onClear={clearAll}>\n  <SearchInput placeholder=\"氏名・メールで検索\" value={q} onSearch={setQ} />\n  <ToolbarGroup label=\"ステータス\">\n    <Select value={status} onValueChange={setStatus}>\n      <SelectTrigger aria-label=\"ステータス\"><SelectValue /></SelectTrigger>\n      <SelectContent>\n        <SelectItem value=\"all\">すべて</SelectItem>\n        <SelectItem value=\"active\">有効</SelectItem>\n      </SelectContent>\n    </Select>\n  </ToolbarGroup>\n</Toolbar>",
  "group": "navigation",
  "importPath": "@godxjp/ui/navigation",
  "name": "Toolbar",
  "props": [
    {
      "description": "Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup.",
      "name": "children",
      "required": true,
      "type": "ReactNode"
    },
    {
      "description": "Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button.",
      "name": "onClear",
      "type": "() => void"
    },
    {
      "defaultValue": "true",
      "description": "Whether any filter is applied — gates the clear-all button visibility.",
      "name": "hasActiveFilters",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Pin the strip to the top of its scroll container while the list scrolls beneath it. Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs.",
      "name": "sticky",
      "type": "boolean"
    },
    {
      "description": "Typed model — canonical search slot rendered FIRST in the strip with the token-owned consistent width (--filter-bar-search-width, full-width below 640px). Controlled triad value/defaultValue/onValueChange plus SearchInput's debounced onSearch. ANY model prop (search/filters/chips/onChipRemove/actions/resultCount/loading/disabled/error) switches the bar to the canonical model layout; with none of them the bar stays the plain children-composition toolbar unchanged.",
      "name": "search",
      "type": "FilterBarSearchProp"
    },
    {
      "description": "Typed model — labelled domain-neutral Select filters rendered after the search slot in array order. Each: { value (stable identity), label (rendered as the control's REAL <label htmlFor> — WCAG 2.5.3), options, selected/defaultSelected/onSelectedChange, placeholder, disabled }. Width is token-owned (--filter-bar-filter-width).",
      "name": "filters",
      "type": "FilterBarFilterProp[]"
    },
    {
      "description": "Typed model — applied-filter chips as pure consumer data ({ value, label, disabled }), rendered in a labelled row under the strip. Lifecycle: ADD = include the chip, REMOVE = onChipRemove(value) via each chip's × button, CLEAR-ALL = onClear. Keep label a string so the remove button's accessible name can quote it.",
      "name": "chips",
      "type": "FilterBarChipProp[]"
    },
    {
      "description": "Remove ONE chip by its value. Required for chips to render their × remove button (each gets a localized 'Remove filter: {label}' accessible name).",
      "name": "onChipRemove",
      "type": "(value: string) => void"
    },
    {
      "description": "Typed model — trailing action slot (e.g. the primary 'Add' Button) parked at the inline end of the strip, after the reset button in DOM/keyboard order.",
      "name": "actions",
      "type": "ReactNode"
    },
    {
      "description": "Typed model — localized, CLDR-pluralized result count ('{count} results' / '{count} 件の結果' / '{count} kết quả') announced politely via a role='status' line under the strip. 0 renders '0 results' as the visible empty state.",
      "name": "resultCount",
      "type": "number"
    },
    {
      "description": "Typed model — marks the strip aria-busy (and sets data-loading on the root) while results are being (re)fetched.",
      "name": "loading",
      "type": "boolean"
    },
    {
      "description": "Typed model — disables every model-rendered control: search, filter Selects, the reset button and the chip remove buttons. Custom children stay consumer-owned.",
      "name": "disabled",
      "type": "boolean"
    },
    {
      "description": "Typed model — consumer error content announced via role='alert' in the meta line, replacing the result count while present.",
      "name": "error",
      "type": "ReactNode"
    },
    {
      "defaultValue": "\"label\"",
      "description": "How the clear-all button shows itself (gh#1264). `label` is ✕ + the localized \"Clear filters\"; `icon` is the ✕ alone — same accessible name, a tooltip with the text, a control-height square instead of ~150px — so a bar with a few active filters keeps one row.",
      "name": "clearDisplay",
      "type": "\"label\" | \"icon\""
    },
    {
      "description": "Typed model — the saved-VIEW switcher rendered ABOVE the filter strip (e.g. `<Tabs>` of Open / Mine / All), the GitHub/Linear layout (gh#1264). A view selects a whole filter set, so it is not one more filter inside the strip; do not pass view tabs as children.",
      "name": "views",
      "type": "ReactNode"
    },
    {
      "defaultValue": "\"above\"",
      "description": "Where `views` sits (gh#1267). `above` is its own row over the strip; `inline` puts it at the START of the strip's first row so an idle bar stays ONE row (with `above`, an idle bar measured 55 → 95px and one table row fewer above the fold at 1440×900).",
      "name": "viewsPlacement",
      "type": "\"above\" | \"inline\""
    },
    {
      "description": "Extra classes on the root element.",
      "name": "className",
      "type": "string"
    }
  ],
  "related": [
    "ToolbarGroup — the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).",
    "DataTable.Toolbar — the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.",
    "SearchInput — the debounced free-text control placed as the first child of Toolbar.",
    "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf.",
    "Sheet — holds the filters beyond the 2–4 inline ones, opened by ONE 'More filters' Button inside the bar (pattern `list-filters`, rule #51). Not a ScrollArea: a filter strip never scrolls sideways.",
    "SearchForm — the submit-driven 「検索条件フォーム」 for many conditions or an explicit 「検索」 button (rule #51 decision table); FilterBar is the live strip."
  ],
  "rules": [
    23,
    44,
    45,
    46,
    51
  ],
  "storyPath": "navigation/toolbar.tsx",
  "subParts": [
    "ToolbarGroup",
    "FilterBar",
    "FilterBarGroup"
  ],
  "tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. It WRAPS and never scrolls sideways; many filters → 2–4 inline + ONE 'More filters' Sheet (rule #51, pattern `list-filters`). `FilterBar`/`FilterBarGroup` are the same components under their alias names. Eleven layouts (inline, Filter menu + chips, More-filters Sheet, saved views, two rows, actions + sticky, faceted sidebar, compact in a card, date range, phone, docked panel) → pattern `filter-bar-layouts`.",
  "usage": [
    "DO prefer the typed model on a canonical list page — `search`/`filters`/`chips`/`onChipRemove`/`actions`/`resultCount` (+ `loading`/`disabled`/`error`) make the bar own layout, widths, chip lifecycle and keyboard order through --filter-bar-* tokens while ALL state stays consumer data. Children composition remains fully supported and unchanged when no model prop is passed.",
    "DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.",
    "DO drive the clear-all button with `hasActiveFilters` + `onClear` — it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.",
    "DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.",
    "DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
    "DO let the strip WRAP — cardinal rule #51 (`get_rule 51`). It stacks below 640px and wraps onto extra rows above; it never scrolls sideways, and neither may anything you wrap it in (`ScrollArea orientation=\"horizontal\"`, an `overflow-x-auto` box): the `overflow` prop and its `scroll` strategy were removed in 33.0.0 because a sideways strip hid filters off-screen and clipped the first label. When the filters are many, keep the 2–4 most-used ones inline and put the rest behind ONE 'More filters' Button (`count` = hidden filters applied) that opens a Sheet holding them as FilterBarGroup + control; every applied filter, inline or hidden, is a chip in `chips` (the chips row wraps too). Copy-paste composition: pattern `list-filters` (`get_pattern list-filters`); there is no dedicated component. Never re-implement the geometry in the page: no page-local flex/grid/width/overflow rules on a filter strip.",
    "DO keep filters PACKED at the start of the bar with its one gap. Never lay them out yourself in a Flex/div/Card/ResponsiveGrid of FormFields above the table, and never spread them with `justify=\"between\"|\"around\"|\"evenly\"` — the only far-end element of a filter bar is its `actions` slot (with the clear button). ui-audit `hand-rolled-filter-bar` flags both. For a layout other than one strip (saved views, a Filter menu with chips, a faceted sidebar, a docked side panel, a compact bar inside a table card, a date range with quick ranges, a phone Sheet), copy pattern `filter-bar-layouts` (docs page navigation/filter-bar-layouts).",
    "DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
    "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y.",
    "Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
    "Compose real controls as children when the model doesn't fit; filter state and URL synchronization remain consumer-owned. Children also render INSIDE the model layout (after the typed filters) for one-off custom controls like a date-range picker.",
    "Give each FilterBarGroup a `controlId` matching its single control's `id` so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader. Typed `filters` wire this automatically.",
    "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap (above), token-owned."
  ],
  "useCases": [
    "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
    "Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.",
    "Filtered views that surface applied conditions as removable chips (clear-one via each chip's × Button, clear-all via `onClear`).",
    "Filter layout choices — saved views filter tabs above the bar, a search-first bar with one Filter menu, a faceted filter sidebar with per-facet counts, a docked filter panel beside the table, a compact table toolbar, a date range filter with quick ranges, a phone filter Sheet: pattern `filter-bar-layouts`."
  ]
}
