@godxjp/ui 32.15.0 → 33.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +4 -4
  3. package/agent/DESIGN.md +2 -2
  4. package/agent/START-HERE.md +15 -14
  5. package/agent/components/Card.json +1 -1
  6. package/agent/components/CardBar.json +4 -2
  7. package/agent/components/ScrollArea.json +1 -0
  8. package/agent/components/Tabs.json +2 -2
  9. package/agent/components/ToggleGroup.json +3 -1
  10. package/agent/components/Toolbar.json +7 -12
  11. package/agent/components-index.json +2 -2
  12. package/agent/components.json +18 -18
  13. package/agent/index.json +7 -7
  14. package/agent/llms.txt +8 -8
  15. package/agent/patterns/data-table-page.json +1 -1
  16. package/agent/patterns/inertia-list-page.json +1 -1
  17. package/agent/patterns/list-filters.json +24 -0
  18. package/agent/patterns-index.json +23 -0
  19. package/agent/patterns.json +26 -2
  20. package/agent/rules.json +5 -0
  21. package/agent/tokens.json +0 -6
  22. package/dist/components/navigation/index.d.ts +1 -1
  23. package/dist/components/navigation/toolbar.d.ts +2 -2
  24. package/dist/components/navigation/toolbar.js +0 -3
  25. package/dist/contracts/measurement.json +1 -1
  26. package/dist/props/components/navigation.prop.d.ts +10 -12
  27. package/dist/props/registry.d.ts +0 -5
  28. package/dist/props/registry.js +0 -5
  29. package/dist/styles/layers.json +1 -1
  30. package/dist/styles/layout.css +0 -33
  31. package/dist/styles.css +1 -1
  32. package/dist/tokens/components/navigation.css +0 -2
  33. package/docs/CONSUMER-RULES.md +1 -1
  34. package/docs/migrations/v33.md +60 -0
  35. package/package.json +2 -2
  36. package/scripts/consumer-rule.md +3 -0
  37. package/scripts/ui-audit.mjs +61 -1
package/DESIGN.md CHANGED
@@ -125,7 +125,7 @@ Colour is scarce. Most of the screen is canvas, ink and hairline.
125
125
 
126
126
  - **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`. Omit `title` only when the content draws the page's one `<h1>` (a document reader); PageContainer then renders no heading.
127
127
  - **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.
128
- - **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header.
128
+ - **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header. It wraps and never scrolls sideways; the 2–4 most-used filters stay inline and the rest go behind one "More filters" Button that opens a `Sheet` (cardinal rule #51, pattern `list-filters`).
129
129
  - **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.
130
130
  - **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).
131
131
  - **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.
@@ -192,7 +192,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
192
192
  - Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.
193
193
  - `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.
194
194
  - `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.
195
- - Header `extra` wraps under the title; a toolbar wraps its filters. Touch targets 44px at `comfortable`.
195
+ - Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.
196
196
  - Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.
197
197
  - Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.
198
198
  - Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
package/README.md CHANGED
@@ -18,7 +18,7 @@ npm i @godxjp/ui
18
18
 
19
19
  <!-- agent-catalog-counts:start -->
20
20
 
21
- The catalog — 163 components, 2229 tokens, 43 cardinal rules — is published in two forms from one
21
+ The catalog — 163 components, 2228 tokens, 44 cardinal rules — is published in two forms from one
22
22
  source. **Entry point for either: [`AGENTS.md`](AGENTS.md).**
23
23
  <!-- agent-catalog-counts:end -->
24
24
 
@@ -53,12 +53,12 @@ static files straight from this public repo — no hosting, no deploy step:
53
53
  | [`agent/llms.txt`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/llms.txt) | 3 KB | the [llms.txt](https://llmstxt.org/) entry point |
54
54
  | [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 17 KB | the visual spec: tokens, colour scarcity, admin screen anatomy, do/don't |
55
55
  | [`agent/index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json) | 3 KB | manifest: version, counts, every file's URL |
56
- | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 48 KB | all 163 as name + group + tagline — **fetch this first** |
56
+ | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 49 KB | all 163 as name + group + tagline — **fetch this first** |
57
57
  | [`agent/components/<Name>.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json) | 1 KB–37 KB | one file per component, each with its `importPath` — **this is the route to take** |
58
58
  | [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.2 MB | all 163 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
59
- | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 857 KB | all 2229 design tokens, their values and why they exist |
59
+ | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 857 KB | all 2228 design tokens, their values and why they exist |
60
60
  | [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
61
- | [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 21 KB | the 43 cardinal rules |
61
+ | [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 22 KB | the 44 cardinal rules |
62
62
  <!-- prettier-ignore-end -->
63
63
  <!-- agent-catalog-files:end -->
64
64
 
package/agent/DESIGN.md CHANGED
@@ -125,7 +125,7 @@ Colour is scarce. Most of the screen is canvas, ink and hairline.
125
125
 
126
126
  - **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`. Omit `title` only when the content draws the page's one `<h1>` (a document reader); PageContainer then renders no heading.
127
127
  - **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.
128
- - **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header.
128
+ - **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header. It wraps and never scrolls sideways; the 2–4 most-used filters stay inline and the rest go behind one "More filters" Button that opens a `Sheet` (cardinal rule #51, pattern `list-filters`).
129
129
  - **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.
130
130
  - **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).
131
131
  - **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.
@@ -192,7 +192,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
192
192
  - Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.
193
193
  - `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.
194
194
  - `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.
195
- - Header `extra` wraps under the title; a toolbar wraps its filters. Touch targets 44px at `comfortable`.
195
+ - Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.
196
196
  - Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.
197
197
  - Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.
198
198
  - Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 32.15.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 33.1.0.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -48,23 +48,23 @@ inspect every screenshot at every width region by region (no top-N cap), and han
48
48
  → These files are for you. Read `DESIGN.md` (in this folder) first: tokens, scarcity rules and
49
49
  screen anatomy, the visual target the rules below do not give you. Then fetch in this order:
50
50
 
51
- 0. `patterns-index.json` — 24 whole-task patterns as name + tagline + tags. **If your
51
+ 0. `patterns-index.json` — 25 whole-task patterns as name + tagline + tags. **If your
52
52
  task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
53
53
  filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
54
54
  copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
55
- 1. `components-index.json` — 48 KB, all 163 components as name + group +
55
+ 1. `components-index.json` — 49 KB, all 163 components as name + group +
56
56
  tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
57
57
  names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
58
58
  `SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
59
59
  first; it exists because that is the mistake.
60
60
  2. `components/<Name>.json` — one file per component (1 KB–37 KB, median 6 KB), carrying its props,
61
61
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
62
- 3. `rules.json` — 43 cardinal rules. The ones about raw HTML and hardcoded colour are not
62
+ 3. `rules.json` — 44 cardinal rules. The ones about raw HTML and hardcoded colour are not
63
63
  style advice.
64
- 4. `tokens.json` — 2229 design tokens, each tagged with its `tier`. **If you were handed a
64
+ 4. `tokens.json` — 2228 design tokens, each tagged with its `tier`. **If you were handed a
65
65
  brand, read the 216 `foundation` entries first** — `--primary`, `--background`,
66
66
  `--radius`, `--font-size-base` are the handful everything else derives from. The
67
- 1900 `component` entries are per-part knobs; reach for one only when a role is
67
+ 1899 `component` entries are per-part knobs; reach for one only when a role is
68
68
  right everywhere except one component.
69
69
  5. `anti-ai-tells.json` — 35 shapes that make generated UI look generated, each with the
70
70
  fix. Read before you reach for a gradient hero or a wall of coloured chips.
@@ -150,7 +150,7 @@ has stopped following the brand.
150
150
  |---|---|---|---|
151
151
  | `foundation` | 216 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** The default `--primary` is a neutral placeholder: set it (`:root { --primary: <H> <S>% <L>%; }`, HSL components, no `hsl()` wrapper) or import a theme preset such as `@godxjp/ui/themes/godx.css` |
152
152
  | `semantic` | 113 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
153
- | `component` | 1900 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
153
+ | `component` | 1899 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
154
154
 
155
155
  A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
156
156
  value, so the real default is computed where the element paints it. Set it and yours wins.
@@ -167,18 +167,19 @@ theme, every tenant scope and every audit in this package.
167
167
  import { PageContainer, Flex } from "@godxjp/ui/layout";
168
168
  import { Card, CardContent, DataTable } from "@godxjp/ui/data-display";
169
169
  import { Button } from "@godxjp/ui/general";
170
- import { Select, SearchInput } from "@godxjp/ui/data-entry";
170
+ import { FilterBar } from "@godxjp/ui/navigation";
171
171
 
172
172
  export default function InvoicesPage() {
173
173
  return (
174
174
  <PageContainer title="請求一覧" extra={<Button>請求を作成</Button>}>
175
175
  <Flex direction="col" gap="sm">
176
- {/* filters sit ABOVE the table they filter */}
177
- <Flex direction="row" gap="sm" wrap>
178
- <SearchInput aria-label="請求を検索" />
179
- <Select width="auto" options={statuses} defaultValue="unpaid" aria-label="状態" />
180
- <Button variant="outline">CSV</Button>
181
- </Flex>
176
+ {/* filters sit ABOVE the table they filter, in ONE FilterBar that wraps — never a
177
+ sideways-scrolling strip (rule #51); many filters → patterns/list-filters.json */}
178
+ <FilterBar
179
+ search={{ ariaLabel: "請求を検索", placeholder: "請求を検索" }}
180
+ filters={[{ value: "status", label: "状態", options: statuses, defaultSelected: "unpaid" }]}
181
+ actions={<Button variant="outline">CSV</Button>}
182
+ />
182
183
 
183
184
  {/* `flush` because DataTable draws its own edges — a padded body double-insets it */}
184
185
  <Card>
@@ -84,7 +84,7 @@
84
84
  "CardHeader",
85
85
  "CardTitle"
86
86
  ],
87
- "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab toolbar/filter strip (list controls, filter chips) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card.",
87
+ "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab TOOL strip inside the card (composer or editor tools) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. List FILTERS (search, filter selects, filter chips) are NOT a CardBar: CardBar's main slot scrolls sideways, and a filter strip never does — they go in a FilterBar ABOVE the card, which wraps (cardinal rule #51, pattern `list-filters`).",
88
88
  "usage": [
89
89
  "DO always wrap body content in <CardContent> — the bare <Card> div has zero inner padding; content renders flush against card edges without it. Never add className=\"p-4\" directly on <Card> as a substitute.",
90
90
  "DO put titles/descriptions in <CardHeader>/<CardTitle>/<CardDescription>. Use <CardHeader banded> for a visually separated muted-background header band (mirrors <CardFooter separated>). Pair with <CardAction> inside a flex-row CardHeader for header-level action buttons.",
@@ -33,14 +33,16 @@
33
33
  ],
34
34
  "related": [
35
35
  "Card",
36
- "Flex"
36
+ "Flex",
37
+ "Toolbar — the list-page filter strip (alias FilterBar); it wraps, so filters go there rather than in a CardBar."
37
38
  ],
38
39
  "rules": [
39
40
  9
40
41
  ],
41
42
  "tagline": "Inline card toolbar with scoped inset and divider edges.",
42
43
  "usage": [
43
- "Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars."
44
+ "Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars.",
45
+ "DON'T put list filters (search, filter selects, filter chips) in a CardBar: its main slot scrolls sideways when crowded, and a filter strip wraps (cardinal rule #51). Filters are a Toolbar/FilterBar above the card."
44
46
  ],
45
47
  "useCases": [
46
48
  "Composer tool strips and view tabs."
@@ -84,6 +84,7 @@
84
84
  "DO reach for ScrollArea rather than your own `overflow-auto` div: it is the same native scrolling, plus the token-styled scrollbar, the keyboard tab stop the axe rule wants, and bottom anchoring. A bare overflow div gets none of that and each call site re-decides the scrollbar's look.",
85
85
  "DON'T put ScrollArea inside a flex parent without giving it a `flex-1` or fixed size — it will collapse to zero height and appear broken.",
86
86
  "For horizontal-only scrolling use `orientation=\"horizontal\"` with a width constraint; the content is allowed to grow past the box on that axis, so a row of non-shrinking columns overflows instead of squashing.",
87
+ "DON'T use a horizontal ScrollArea for a strip of filters, search fields, form controls or filter chips — a filter strip WRAPS (cardinal rule #51; ui-audit `filter-strip-no-scroll`). Use FilterBar, and past 2–4 filters the `list-filters` pattern's 'More filters' Sheet.",
87
88
  "DO use `viewportRef` (or plain `ref`) when you need the scrolling element, never a `querySelector` for an internal attribute — `[data-radix-scroll-area-viewport]` is gone with Radix, and any selector matches the wrong node the moment two ScrollAreas nest.",
88
89
  "DO build a live stream (chat, log tail, streaming response, activity feed) with `anchor=\"bottom\"` instead of writing `viewport.scrollTop = viewport.scrollHeight` in an effect. That naive version is the BUG, not the feature: it yanks a reader who has scrolled up back to the bottom on every arriving message (WCAG 3.2.5).",
89
90
  "DO pair `anchor=\"bottom\"` with `onAnchoredChange` and a real Button (\"jump to newest\"). A keyboard user who has scrolled up needs a focusable route back to new content — the anchor alone is a pointer affordance.",
@@ -91,7 +91,7 @@
91
91
  },
92
92
  {
93
93
  "defaultValue": "\"scroll\"",
94
- "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Toolbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
94
+ "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Topbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
95
95
  "name": "overflow",
96
96
  "type": "\"scroll\" | \"menu\""
97
97
  },
@@ -135,7 +135,7 @@
135
135
  ],
136
136
  "related": [
137
137
  "Steps (@godxjp/ui/navigation) — sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.",
138
- "Toolbar / ToolbarGroup (@godxjp/ui/navigation) — horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
138
+ "Toolbar / ToolbarGroup (@godxjp/ui/navigation) — the list-page filter strip; it WRAPS onto extra rows and never scrolls sideways the way a tab strip may (rule #51). Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
139
139
  "DropdownMenu (@godxjp/ui/navigation) — use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a Segmented control is a lighter alternative to a full tab strip."
140
140
  ],
141
141
  "rules": [],
@@ -73,7 +73,8 @@
73
73
  ],
74
74
  "rules": [
75
75
  3,
76
- 13
76
+ 13,
77
+ 51
77
78
  ],
78
79
  "storyPath": "data-entry/toggle-group.tsx",
79
80
  "subParts": [
@@ -89,6 +90,7 @@
89
90
  "DO build a tag-filter panel as `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\">` with one counted `ToggleGroupItem` per tag — that is the whole antd `Tag.CheckableTag` row, one tab stop per chip, no `Tag` component needed (gh#734).",
90
91
  "DO set `size`/`variant` on an individual ToggleGroupItem only when that ONE item must differ from the group.",
91
92
  "DO add `wrap` to a TAG FILTER ROW — `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\" wrap>` is the whole folder-tag panel however many tags the folder has. This is what replaces the hand-built `<ul>` of individual `<Toggle>`s a row wider than its rail used to force (gh#741): that list loses exactly what the group owns — the shared `variant`/`size`/`shape` context, ONE `value`/`onValueChange`, and the group's arrow-key traversal, which keeps walking the chips in DOM order across the wrapped lines.",
93
+ "DON'T put a filter-chip row in a horizontal scroller (`overflow-x-auto`, `ScrollArea orientation=\"horizontal\"`) with `shrink-0 whitespace-nowrap` chips — at every width, phone included, a chip row that filters a list takes `wrap` (cardinal rule #51). A sideways chip row hides chips past the edge with no affordance, and this kit ships no primitive that guarantees the first chip unclipped and a visible scroll cue, so the scroller is a hand-roll.",
92
94
  "DON'T set `wrap` on a 3–4 item segmented group. Measured at a 320px rail it is one 32px line either way, so the prop buys nothing and only adds a way for a toolbar to reflow.",
93
95
  "DON'T pass size='default' — it is not a member of the `xs | sm | md | lg` union. Omit `size` for the md default.",
94
96
  "DO give the group an accessible name (`aria-label`) — it renders a `group` of toggle buttons (single, the default), a `radiogroup` (single + `disallowEmptySelection`) or a `toolbar` of toggle buttons (multiple), and all three need a name."
@@ -28,12 +28,6 @@
28
28
  "name": "sticky",
29
29
  "type": "boolean"
30
30
  },
31
- {
32
- "defaultValue": "'wrap'",
33
- "description": "Responsive overflow strategy. 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) — use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll.",
34
- "name": "overflow",
35
- "type": "'wrap' | 'scroll'"
36
- },
37
31
  {
38
32
  "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.",
39
33
  "name": "search",
@@ -106,13 +100,15 @@
106
100
  "ToolbarGroup — the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).",
107
101
  "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.",
108
102
  "SearchInput — the debounced free-text control placed as the first child of Toolbar.",
109
- "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."
103
+ "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf.",
104
+ "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."
110
105
  ],
111
106
  "rules": [
112
107
  23,
113
108
  44,
114
109
  45,
115
- 46
110
+ 46,
111
+ 51
116
112
  ],
117
113
  "storyPath": "navigation/toolbar.tsx",
118
114
  "subParts": [
@@ -120,21 +116,20 @@
120
116
  "FilterBar",
121
117
  "FilterBarGroup"
122
118
  ],
123
- "tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. `FilterBar`/`FilterBarGroup` are the same components under their alias names.",
119
+ "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.",
124
120
  "usage": [
125
121
  "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.",
126
122
  "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`.",
127
123
  "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.",
128
124
  "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.",
129
125
  "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.",
130
- "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
126
+ "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.",
131
127
  "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.",
132
128
  "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.",
133
129
  "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.",
134
130
  "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.",
135
131
  "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.",
136
- "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
137
- "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/scroll (above), token-owned."
132
+ "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."
138
133
  ],
139
134
  "useCases": [
140
135
  "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
@@ -215,7 +215,7 @@
215
215
  {
216
216
  "group": "data-display",
217
217
  "name": "Card",
218
- "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab toolbar/filter strip (list controls, filter chips) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card."
218
+ "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab TOOL strip inside the card (composer or editor tools) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. List FILTERS (search, filter selects, filter chips) are NOT a CardBar: CardBar's main slot scrolls sideways, and a filter strip never does — they go in a FilterBar ABOVE the card, which wraps (cardinal rule #51, pattern `list-filters`)."
219
219
  },
220
220
  {
221
221
  "group": "data-display",
@@ -483,7 +483,7 @@
483
483
  {
484
484
  "group": "navigation",
485
485
  "name": "Toolbar",
486
- "tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. `FilterBar`/`FilterBarGroup` are the same components under their alias names."
486
+ "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."
487
487
  },
488
488
  {
489
489
  "group": "providers",
@@ -391,14 +391,16 @@
391
391
  ],
392
392
  "related": [
393
393
  "Card",
394
- "Flex"
394
+ "Flex",
395
+ "Toolbar — the list-page filter strip (alias FilterBar); it wraps, so filters go there rather than in a CardBar."
395
396
  ],
396
397
  "rules": [
397
398
  9
398
399
  ],
399
400
  "tagline": "Inline card toolbar with scoped inset and divider edges.",
400
401
  "usage": [
401
- "Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars."
402
+ "Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars.",
403
+ "DON'T put list filters (search, filter selects, filter chips) in a CardBar: its main slot scrolls sideways when crowded, and a filter strip wraps (cardinal rule #51). Filters are a Toolbar/FilterBar above the card."
402
404
  ],
403
405
  "useCases": [
404
406
  "Composer tool strips and view tabs."
@@ -3727,7 +3729,7 @@
3727
3729
  "CardHeader",
3728
3730
  "CardTitle"
3729
3731
  ],
3730
- "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab toolbar/filter strip (list controls, filter chips) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card.",
3732
+ "tagline": "Surface container with optional accent stripe, variant fill (including the Ant Design borderless edge), hoverable lift, and density. ⚠️ The bare <Card> has NO inner padding — body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className=\"p-4\"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For Ant Design's card-head TAB STRIP (tabs under the title, inside the card border) use the `tabList`/`activeTabKey`/`defaultActiveTabKey`/`onTabChange`/`extra`/`tabProps` props — the Card renders the strip and the children become the selected tab's body. For a non-tab TOOL strip inside the card (composer or editor tools) use <CardBar extra={…}> — a positionable bar that auto-draws its separator from its position (top→bottom border, bottom→top border, middle→both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. List FILTERS (search, filter selects, filter chips) are NOT a CardBar: CardBar's main slot scrolls sideways, and a filter strip never does — they go in a FilterBar ABOVE the card, which wraps (cardinal rule #51, pattern `list-filters`).",
3731
3733
  "usage": [
3732
3734
  "DO always wrap body content in <CardContent> — the bare <Card> div has zero inner padding; content renders flush against card edges without it. Never add className=\"p-4\" directly on <Card> as a substitute.",
3733
3735
  "DO put titles/descriptions in <CardHeader>/<CardTitle>/<CardDescription>. Use <CardHeader banded> for a visually separated muted-background header band (mirrors <CardFooter separated>). Pair with <CardAction> inside a flex-row CardHeader for header-level action buttons.",
@@ -8134,7 +8136,7 @@
8134
8136
  },
8135
8137
  {
8136
8138
  "defaultValue": "\"scroll\"",
8137
- "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Toolbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
8139
+ "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Topbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
8138
8140
  "name": "overflow",
8139
8141
  "type": "\"scroll\" | \"menu\""
8140
8142
  },
@@ -8178,7 +8180,7 @@
8178
8180
  ],
8179
8181
  "related": [
8180
8182
  "Steps (@godxjp/ui/navigation) — sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.",
8181
- "Toolbar / ToolbarGroup (@godxjp/ui/navigation) — horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
8183
+ "Toolbar / ToolbarGroup (@godxjp/ui/navigation) — the list-page filter strip; it WRAPS onto extra rows and never scrolls sideways the way a tab strip may (rule #51). Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.",
8182
8184
  "DropdownMenu (@godxjp/ui/navigation) — use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a Segmented control is a lighter alternative to a full tab strip."
8183
8185
  ],
8184
8186
  "rules": [],
@@ -8584,12 +8586,6 @@
8584
8586
  "name": "sticky",
8585
8587
  "type": "boolean"
8586
8588
  },
8587
- {
8588
- "defaultValue": "'wrap'",
8589
- "description": "Responsive overflow strategy. 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) — use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll.",
8590
- "name": "overflow",
8591
- "type": "'wrap' | 'scroll'"
8592
- },
8593
8589
  {
8594
8590
  "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.",
8595
8591
  "name": "search",
@@ -8662,13 +8658,15 @@
8662
8658
  "ToolbarGroup — the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).",
8663
8659
  "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.",
8664
8660
  "SearchInput — the debounced free-text control placed as the first child of Toolbar.",
8665
- "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."
8661
+ "Badge — compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf.",
8662
+ "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."
8666
8663
  ],
8667
8664
  "rules": [
8668
8665
  23,
8669
8666
  44,
8670
8667
  45,
8671
- 46
8668
+ 46,
8669
+ 51
8672
8670
  ],
8673
8671
  "storyPath": "navigation/toolbar.tsx",
8674
8672
  "subParts": [
@@ -8676,21 +8674,20 @@
8676
8674
  "FilterBar",
8677
8675
  "FilterBarGroup"
8678
8676
  ],
8679
- "tagline": "List-page filter strip — SearchInput + labelled ToolbarGroup filter slots (or the typed `search`/`filters`/`chips` model) + a clear-all affordance, optionally sticky. `FilterBar`/`FilterBarGroup` are the same components under their alias names.",
8677
+ "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.",
8680
8678
  "usage": [
8681
8679
  "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.",
8682
8680
  "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`.",
8683
8681
  "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.",
8684
8682
  "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.",
8685
8683
  "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.",
8686
- "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
8684
+ "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.",
8687
8685
  "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.",
8688
8686
  "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.",
8689
8687
  "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.",
8690
8688
  "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.",
8691
8689
  "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.",
8692
- "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
8693
- "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/scroll (above), token-owned."
8690
+ "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."
8694
8691
  ],
8695
8692
  "useCases": [
8696
8693
  "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
@@ -11131,6 +11128,7 @@
11131
11128
  "DO reach for ScrollArea rather than your own `overflow-auto` div: it is the same native scrolling, plus the token-styled scrollbar, the keyboard tab stop the axe rule wants, and bottom anchoring. A bare overflow div gets none of that and each call site re-decides the scrollbar's look.",
11132
11129
  "DON'T put ScrollArea inside a flex parent without giving it a `flex-1` or fixed size — it will collapse to zero height and appear broken.",
11133
11130
  "For horizontal-only scrolling use `orientation=\"horizontal\"` with a width constraint; the content is allowed to grow past the box on that axis, so a row of non-shrinking columns overflows instead of squashing.",
11131
+ "DON'T use a horizontal ScrollArea for a strip of filters, search fields, form controls or filter chips — a filter strip WRAPS (cardinal rule #51; ui-audit `filter-strip-no-scroll`). Use FilterBar, and past 2–4 filters the `list-filters` pattern's 'More filters' Sheet.",
11134
11132
  "DO use `viewportRef` (or plain `ref`) when you need the scrolling element, never a `querySelector` for an internal attribute — `[data-radix-scroll-area-viewport]` is gone with Radix, and any selector matches the wrong node the moment two ScrollAreas nest.",
11135
11133
  "DO build a live stream (chat, log tail, streaming response, activity feed) with `anchor=\"bottom\"` instead of writing `viewport.scrollTop = viewport.scrollHeight` in an effect. That naive version is the BUG, not the feature: it yanks a reader who has scrolled up back to the bottom on every arriving message (WCAG 3.2.5).",
11136
11134
  "DO pair `anchor=\"bottom\"` with `onAnchoredChange` and a real Button (\"jump to newest\"). A keyboard user who has scrolled up needs a focusable route back to new content — the anchor alone is a pointer affordance.",
@@ -12028,7 +12026,8 @@
12028
12026
  ],
12029
12027
  "rules": [
12030
12028
  3,
12031
- 13
12029
+ 13,
12030
+ 51
12032
12031
  ],
12033
12032
  "storyPath": "data-entry/toggle-group.tsx",
12034
12033
  "subParts": [
@@ -12044,6 +12043,7 @@
12044
12043
  "DO build a tag-filter panel as `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\">` with one counted `ToggleGroupItem` per tag — that is the whole antd `Tag.CheckableTag` row, one tab stop per chip, no `Tag` component needed (gh#734).",
12045
12044
  "DO set `size`/`variant` on an individual ToggleGroupItem only when that ONE item must differ from the group.",
12046
12045
  "DO add `wrap` to a TAG FILTER ROW — `<ToggleGroup type=\"multiple\" variant=\"soft\" shape=\"pill\" size=\"xs\" wrap>` is the whole folder-tag panel however many tags the folder has. This is what replaces the hand-built `<ul>` of individual `<Toggle>`s a row wider than its rail used to force (gh#741): that list loses exactly what the group owns — the shared `variant`/`size`/`shape` context, ONE `value`/`onValueChange`, and the group's arrow-key traversal, which keeps walking the chips in DOM order across the wrapped lines.",
12046
+ "DON'T put a filter-chip row in a horizontal scroller (`overflow-x-auto`, `ScrollArea orientation=\"horizontal\"`) with `shrink-0 whitespace-nowrap` chips — at every width, phone included, a chip row that filters a list takes `wrap` (cardinal rule #51). A sideways chip row hides chips past the edge with no affordance, and this kit ships no primitive that guarantees the first chip unclipped and a visible scroll cue, so the scroller is a hand-roll.",
12047
12047
  "DON'T set `wrap` on a 3–4 item segmented group. Measured at a 320px rail it is one 32px line either way, so the prop buys nothing and only adds a way for a toolbar to reflow.",
12048
12048
  "DON'T pass size='default' — it is not a member of the `xs | sm | md | lg` union. Omit `size` for the md default.",
12049
12049
  "DO give the group an accessible name (`aria-label`) — it renders a `group` of toggle buttons (single, the default), a `radiogroup` (single + `disallowEmptySelection`) or a `toolbar` of toggle buttons (multiple), and all three need a name."
package/agent/index.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "counts": {
3
3
  "anti-ai-tells": 35,
4
4
  "components": 163,
5
- "patterns": 24,
6
- "rules": 43,
7
- "tokens": 2229,
5
+ "patterns": 25,
6
+ "rules": 44,
7
+ "tokens": 2228,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "file": "components-index.json",
19
- "note": "48 KB — name + group + tagline for all 163. FETCH THIS FIRST, then fetch only the components you chose.",
19
+ "note": "49 KB — name + group + tagline for all 163. FETCH THIS FIRST, then fetch only the components you chose.",
20
20
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
21
21
  },
22
22
  {
@@ -54,19 +54,19 @@
54
54
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
55
55
  "read": {
56
56
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
57
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.15.0/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.1.0/agent/index.json"
58
58
  },
59
59
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
60
60
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
61
61
  "tokenTiers": {
62
62
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
63
63
  "counts": {
64
- "component": 1900,
64
+ "component": 1899,
65
65
  "foundation": 216,
66
66
  "semantic": 113
67
67
  },
68
68
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
69
69
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
70
70
  },
71
- "version": "32.15.0"
71
+ "version": "33.1.0"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 163 components, 2229 design tokens,
4
- > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.15.0.
3
+ > A Japanese-enterprise React design system: 163 components, 2228 design tokens,
4
+ > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.1.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@32.15.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.1.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -15,19 +15,19 @@ that can only fetch URLs.
15
15
 
16
16
  ## Catalog
17
17
 
18
- - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 24 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 163 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
18
+ - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 25 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 49 KB — all 163 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
20
20
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
21
21
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
22
- - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1900 `component` knobs.
22
+ - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1899 `component` knobs.
23
23
  - [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
24
- - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 43 cardinal rules.
24
+ - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
25
25
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
26
26
 
27
27
  ## Pinning
28
28
 
29
29
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
30
- the tag: `.../godx-jp/godxjp-ui/v32.15.0/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.1.0/agent/...`. A catalog that does not match the installed
31
31
  package describes props that are absent, or hides props that are present, and says nothing either way.
32
32
 
33
33
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -4,7 +4,7 @@
4
4
  "table-state",
5
5
  "list-page"
6
6
  ],
7
- "code": "import { useState } from \"react\";\nimport { DataState } from \"@godxjp/ui/query\";\nimport { Card, CardContent, DataTable, EmptyState, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { SkeletonTable } from \"@godxjp/ui/feedback\";\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\";\nimport { Toolbar, ToolbarGroup, Pagination } from \"@godxjp/ui/navigation\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { useQuery, keepPreviousData } from \"@tanstack/react-query\";\n\ntype Row = { id: string; name: string; status: string };\n\nexport function MembersTablePage({ orgId }: { orgId?: string }) {\n const [q, setQ] = useState(\"\");\n const [status, setStatus] = useState(\"all\");\n const [page, setPage] = useState(1);\n const pageSize = 20;\n\n // placeholderData: keepPreviousData → on page/filter change the PREVIOUS page stays visible\n // (a stale refresh) instead of flashing a skeleton. enabled gates on the prerequisite (orgId).\n const query = useQuery({\n queryKey: [\"members\", orgId, q, status, page],\n queryFn: () => fetchMembers({ orgId: orgId!, q, status, page, pageSize }),\n enabled: Boolean(orgId),\n placeholderData: keepPreviousData,\n });\n\n const columns: ColumnDef<Row>[] = [\n { key: \"name\", header: \"氏名\", render: (r) => <span className=\"font-medium\">{r.name}</span> },\n { key: \"status\", header: \"ステータス\" },\n ];\n\n return (\n <Flex direction=\"col\" gap=\"lg\">\n {/* Toolbar (filter bar) is STANDALONE and always visible so the user can change filters even\n while a query is idle/loading/empty. Changing a filter resets to page 1. */}\n <Toolbar hasActiveFilters={q !== \"\" || status !== \"all\"}\n onClear={() => { setQ(\"\"); setStatus(\"all\"); setPage(1); }}>\n <SearchInput placeholder=\"氏名で検索\" value={q} onSearch={(v) => { setQ(v); setPage(1); }} />\n <ToolbarGroup label=\"ステータス\">\n <Select value={status} onValueChange={(v) => { setStatus(v); setPage(1); }}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">すべて</SelectItem>\n <SelectItem value=\"active\">有効</SelectItem>\n <SelectItem value=\"invited\">招待中</SelectItem>\n </SelectContent>\n </Select>\n </ToolbarGroup>\n </Toolbar>\n\n {/* The table surface owns exactly ONE card. Pagination lives INSIDE the success branch, so it\n is hidden during prerequisite / loading / empty / error and for single-page results. */}\n <DataState\n query={query}\n prerequisite={<EmptyState variant=\"section\" title=\"組織を選択してください\" />}\n skeleton={<Card><CardContent flush><SkeletonTable rows={8} columns={2} /></CardContent></Card>}\n empty={<EmptyState variant=\"section\" title=\"該当するメンバーがいません\" description=\"検索条件を変更してください。\" />}\n isEmpty={(d) => d.items.length === 0}\n >\n {(d) => {\n const totalPages = Math.ceil(d.total / pageSize);\n return (\n <>\n <Card><CardContent flush><DataTable data={d.items} columns={columns} getRowId={(r) => r.id} /></CardContent></Card>\n {totalPages > 1 && (\n <Pagination value={page} total={d.total} pageSize={pageSize} showTotal onValueChange={setPage} />\n )}\n </>\n );\n }}\n </DataState>\n </Flex>\n );\n}\n// Responsive: Pagination's showTotal range + controls stack at narrow widths automatically.\n// A transient retry keeps q/status/page (they live in component state, not the query) — the\n// same page reloads in place; the user never loses their filters.",
7
+ "code": "import { useState } from \"react\";\nimport { DataState } from \"@godxjp/ui/query\";\nimport { Card, CardContent, DataTable, EmptyState, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { SkeletonTable } from \"@godxjp/ui/feedback\";\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\";\nimport { Toolbar, ToolbarGroup, Pagination } from \"@godxjp/ui/navigation\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { useQuery, keepPreviousData } from \"@tanstack/react-query\";\n\ntype Row = { id: string; name: string; status: string };\n\nexport function MembersTablePage({ orgId }: { orgId?: string }) {\n const [q, setQ] = useState(\"\");\n const [status, setStatus] = useState(\"all\");\n const [page, setPage] = useState(1);\n const pageSize = 20;\n\n // placeholderData: keepPreviousData → on page/filter change the PREVIOUS page stays visible\n // (a stale refresh) instead of flashing a skeleton. enabled gates on the prerequisite (orgId).\n const query = useQuery({\n queryKey: [\"members\", orgId, q, status, page],\n queryFn: () => fetchMembers({ orgId: orgId!, q, status, page, pageSize }),\n enabled: Boolean(orgId),\n placeholderData: keepPreviousData,\n });\n\n const columns: ColumnDef<Row>[] = [\n { key: \"name\", header: \"氏名\", render: (r) => <span className=\"font-medium\">{r.name}</span> },\n { key: \"status\", header: \"ステータス\" },\n ];\n\n return (\n <Flex direction=\"col\" gap=\"lg\">\n {/* Toolbar (filter bar) is STANDALONE and always visible so the user can change filters even\n while a query is idle/loading/empty. Changing a filter resets to page 1. It WRAPS, never\n scrolls sideways (rule #51); more than 2–4 filters → the \"list-filters\" pattern. */}\n <Toolbar hasActiveFilters={q !== \"\" || status !== \"all\"}\n onClear={() => { setQ(\"\"); setStatus(\"all\"); setPage(1); }}>\n <SearchInput placeholder=\"氏名で検索\" value={q} onSearch={(v) => { setQ(v); setPage(1); }} />\n <ToolbarGroup label=\"ステータス\">\n <Select value={status} onValueChange={(v) => { setStatus(v); setPage(1); }}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">すべて</SelectItem>\n <SelectItem value=\"active\">有効</SelectItem>\n <SelectItem value=\"invited\">招待中</SelectItem>\n </SelectContent>\n </Select>\n </ToolbarGroup>\n </Toolbar>\n\n {/* The table surface owns exactly ONE card. Pagination lives INSIDE the success branch, so it\n is hidden during prerequisite / loading / empty / error and for single-page results. */}\n <DataState\n query={query}\n prerequisite={<EmptyState variant=\"section\" title=\"組織を選択してください\" />}\n skeleton={<Card><CardContent flush><SkeletonTable rows={8} columns={2} /></CardContent></Card>}\n empty={<EmptyState variant=\"section\" title=\"該当するメンバーがいません\" description=\"検索条件を変更してください。\" />}\n isEmpty={(d) => d.items.length === 0}\n >\n {(d) => {\n const totalPages = Math.ceil(d.total / pageSize);\n return (\n <>\n <Card><CardContent flush><DataTable data={d.items} columns={columns} getRowId={(r) => r.id} /></CardContent></Card>\n {totalPages > 1 && (\n <Pagination value={page} total={d.total} pageSize={pageSize} showTotal onValueChange={setPage} />\n )}\n </>\n );\n }}\n </DataState>\n </Flex>\n );\n}\n// Responsive: Pagination's showTotal range + controls stack at narrow widths automatically.\n// A transient retry keeps q/status/page (they live in component state, not the query) — the\n// same page reloads in place; the user never loses their filters.",
8
8
  "name": "data-table-page",
9
9
  "tagline": "Filter (Toolbar) + table surface + bottom pagination driven by a query state machine — pagination shows ONLY for successful multi-page data; filters/page survive a transient retry.",
10
10
  "tags": [