@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
@@ -149,8 +149,6 @@
149
149
 
150
150
  --filter-bar-sticky-background: initial;
151
151
 
152
- --filter-bar-scroll-padding-y: var(--space-1);
153
-
154
152
  --filter-bar-search-width: 20rem;
155
153
  --filter-bar-filter-width: var(--filter-picker-width-md);
156
154
  --filter-bar-chip-gap: var(--space-2);
@@ -5,7 +5,7 @@ should look like) is in the package's `DESIGN.md`. Everything else in `docs/` is
5
5
 
6
6
  1. Load styles with `import "@godxjp/ui/styles.css"` (precompiled, no Tailwind needed) or `@import "@godxjp/ui/styles"` (Tailwind v4). Neither ships font files: fonts are opt-in since v32 (`pnpm add @fontsource/noto-sans-jp @fontsource/m-plus-2`, then `@import "@godxjp/ui/styles/fonts.css"` after the styles import; the GoDX preset does this for you). Never cherry-pick `*-layout.css`.
7
7
  2. **Mount `<AppProvider defaultLocale="…">` once, at the root.** Without it the kit's OWN strings (dialog close/cancel/delete, counters, picker labels) fall to the resting locale (`<html lang>` when it names a registered locale, else `en`) and pickers to UTC (gh#1005). A development build now warns once when a kit component renders outside it; `syncI18nLocale(locale, fallback)` is the escape for an app that truly cannot mount the provider. Every page is `<PageContainer title extra footer>` (a `subtitle` only when it adds a fact the title does not); its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
8
- 3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
+ 3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`. A list's filters are ONE `<FilterBar>` that wraps at every width — never a sideways-scrolling strip of filters or filter chips; past 2–4 filters the rest go behind one "More filters" `Sheet` (cardinal rule #51, pattern `list-filters`, audit rules `filter-bar-no-overflow` and `filter-strip-no-scroll`).
9
9
  4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`. A LIST of those rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children — `marker="none"` keeps the `<ul>`, the `<li>` semantics and the gap token while dropping the bullet and the indent. Never a raw `<ul>`/`<ol>` (it carries no gap token), never `<div role="list">` + `<div role="listitem">`, and never a wrapper around each row: the divider is `:not(:last-child)` among siblings, so a row alone in its own wrapper is always the last one and EVERY divider disappears silently. A bulleted prose list is the same `<Flex as="ul">` without `marker`.
10
10
  5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below. **Fields live in a `<Form layout="horizontal" labelWidth controlWidth>`** — never a group of `FormField`s without one, never a hand-rolled `<Flex>` row of fields (a row that belongs together is `<Flex attached>` or `<Form columns>`); size each control for its content with `controlWidth` (GOV.UK text-input width: a port ~7rem, a short enum ~10rem); three or more fields is a page, not a Dialog. The audit rules are `formfield-needs-form`, `dialog-form-too-big` and `select-width-hint`. **A case the kit cannot express is an issue, not a hand-roll:** open it on godx-jp/godxjp-ui, use the nearest valid composition, and mark it `// TODO(godxjp-ui#<n>)` (gh#998). **One action row, one Button size:** every `Button` under one parent takes the same `size` — a missing size is `default`, and `icon-sm` pairs with `sm`, `icon-xs` with `xs`, `icon` with default. Never `size="sm"` beside a default Button. The audit rules are `mixed-button-size` (static) and `mixed-button-height` (visual-audit, within 0.5px) (gh#1084).
11
11
  6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`. A STANDALONE glyph is `<Icon as={Lock} size="sm" tone="muted" />` from `@godxjp/ui/general`: a bare lucide icon carries its own `width="24" height="24"` and only four places re-size it for you (`Button`, a `DropdownMenuItem`, a `TopbarItem`, a `ListRow` leading slot), so everywhere else — in a `Text`, a table cell, an `<a>` — it draws at 24px beside 14px type. `size-4` and `w-[16px]` are closed to you by rules 3 and 8, and `size={16}` hard-codes a number the theme owns; `Icon` puts the glyph on the `--icon-size-*` scale (`2xs…4xl`) instead, and is `aria-hidden` unless you give it a `label` (then it is `role="img"` with that name). The audit rule is `lucide-icon-needs-size`.
@@ -0,0 +1,60 @@
1
+ # Migrating to @godxjp/ui v33
2
+
3
+ Off-cycle approved: Pham Thai Duong 2026-10-15 — FilterBar overflow="scroll" taught consumers to build sideways-scrolling filter bars; removing it cannot wait for the next scheduled major.
4
+
5
+ v33 removes one thing: the horizontally scrolling filter bar. `FilterBar` (and its alias
6
+ `Toolbar`) no longer accepts an `overflow` prop. A filter bar now always wraps. All six packages
7
+ (`@godxjp/ui`, `@godxjp/ui-mcp`, `@godxjp/markdown`, `@godxjp/editor`, `@godxjp/block-editor` and
8
+ `@godxjp/chat`) move to 33.0.0 together.
9
+
10
+ ## Why
11
+
12
+ The library recommended `overflow="scroll"` for list pages with many filters and long Japanese,
13
+ English or Vietnamese labels. A consumer list page followed that advice, and the result was
14
+ measured at 1240px in Vietnamese: the filter strip was 1945px wide inside a 921px frame, the first
15
+ filter label was clipped at the inline-start edge, and the user had to scroll sideways to reach
16
+ filters they could not see. Leading products do not do this. GitHub, Linear, Gmail, Jira and
17
+ Shopify Polaris IndexFilters never scroll a filter bar sideways; they let it wrap, or they collapse
18
+ the rarely used filters behind a single "More filters" or "Add filter" control.
19
+
20
+ ## Breaking changes
21
+
22
+ ### FilterBar / Toolbar `overflow` is removed
23
+
24
+ - The `overflow` prop is gone from `FilterBar` and `Toolbar`, and the `FilterBarOverflowProp` type
25
+ is no longer exported. Passing `overflow` is now a TypeScript error.
26
+ - The strip no longer carries a `data-overflow` attribute, and the
27
+ `.ui-toolbar[data-overflow="scroll"]` rules are removed from the stylesheet.
28
+ - The bar always wraps: below 640px the filters stack in one column, from 640px up they flow onto
29
+ as many rows as they need. This is the behaviour the old default `overflow="wrap"` already had,
30
+ so a bar that never passed `overflow`, or passed `overflow="wrap"`, renders exactly as before.
31
+
32
+ ### Token `--filter-bar-scroll-padding-y` is removed
33
+
34
+ It only reserved the scrollbar gutter under the removed scroll strip. A theme that sets it can
35
+ delete the line; nothing reads it any more.
36
+
37
+ `Tabs overflow`, `Topbar overflow` and the chat attachments `overflow` are different components
38
+ and are unchanged.
39
+
40
+ ## How to upgrade
41
+
42
+ 1. Upgrade all six packages to 33.0.x together.
43
+ 2. Run `npx godxjp-ui audit <src>`. The new
44
+ `filter-bar-no-overflow` rule reports every `<FilterBar>` or `<Toolbar>` that still passes
45
+ `overflow`. The TypeScript build reports the same places.
46
+ 3. Delete the `overflow` prop at each place it reports. If a page used `overflow="scroll"` because
47
+ it has many filters, check how tall the wrapped bar is. When it takes more than two rows, keep
48
+ the few filters people use most in the bar and move the rest behind one "More filters" Button
49
+ that opens a Sheet holding them as `FilterBarGroup`s. The library has no dedicated component for
50
+ this; it is a composition of Button, Sheet and FilterBarGroup, and the applied filters can still
51
+ show as `chips` on the bar.
52
+ 4. Remove any page CSS that targeted `[data-overflow]` on the filter strip, and any theme line that
53
+ set `--filter-bar-scroll-padding-y`.
54
+ 5. Run `npx godxjp-ui sync-rules` and restart your coding agent so it stops recommending the
55
+ removed prop.
56
+
57
+ ## Support
58
+
59
+ v32 receives no further releases. A security fix for v32 is an owner exception, decided case by
60
+ case.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "32.15.0",
4
- "godxUiMcp": "32.15.0",
3
+ "version": "33.1.0",
4
+ "godxUiMcp": "33.1.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -21,6 +21,9 @@ your theme, or import a preset (`@godxjp/ui/themes/godx.css`). Details: `docs/CU
21
21
  surface (a table, a form block), never a frame around every section.
22
22
  - No `subtitle` that restates the `title`. Leave it out unless it adds a fact.
23
23
  - Filters sit ABOVE the table they filter, never after it.
24
+ - A filter bar (`FilterBar`) WRAPS and never scrolls sideways — no horizontal strip of filters
25
+ or filter chips. 2–4 filters inline; the rest behind ONE "More filters" `Sheet`. `get_rule 51`,
26
+ `get_pattern list-filters`.
24
27
  - KPI tiles (`StatCard`) only on dashboards, 3–4 at most. A list page opens with its list.
25
28
  - `EmptyState` = one sentence + one action. Inside a Card it has no icon.
26
29
  - `Alert` only for something the user must act on now; not a page decoration.
@@ -477,6 +477,19 @@ const RULES = [
477
477
  message:
478
478
  "Abstract value components use onValueChange, not onChange. Reserve onChange for DOM events.",
479
479
  },
480
+ {
481
+ id: "filter-bar-no-overflow",
482
+ replacement: "the default wrapping FilterBar",
483
+ severity: "error",
484
+ spansElement: true,
485
+ // `overflow` was removed from FilterBar/Toolbar in 33.0.0. Its `scroll` value made the bar one
486
+ // horizontally scrolling strip — measured in a consumer at 1240px (vi): 1945px of filters in a
487
+ // 921px frame, the first label clipped. Tabs and Topbar keep their own `overflow`; only the
488
+ // filter bar's opening tag is matched (`<FilterBarGroup` and `<DataTable.Toolbar` are not).
489
+ test: new RegExp(`<(?:FilterBar|Toolbar)\\b${ATTRS}\\boverflow=`, "g"),
490
+ message:
491
+ "FilterBar/Toolbar has no `overflow` prop (removed in 33.0.0) — a filter bar wraps and never scrolls sideways. Delete the prop; with many filters keep the common ones in the bar and put the rest behind one 'More filters' Button that opens a Sheet.",
492
+ },
480
493
  {
481
494
  id: "no-arbitrary-hex",
482
495
  replacement: "a semantic token",
@@ -912,7 +925,7 @@ const RULES = [
912
925
  standard:
913
926
  "WCAG 2.2 SC 2.1.1 (Keyboard) · WAI-ARIA 1.2 (group) · Deque axe-core scrollable-region-focusable",
914
927
  message:
915
- 'Hand-rolled scrollport (overflow-auto / -scroll on your own element) — use <ScrollArea label={t("…")} orientation> so the box that scrolls is a tab stop with a role and a localized name, and stops being one when there is nothing to scroll (gh#821). Without it, anything below the fold is reachable by mouse and by nothing else (WCAG 2.2 SC 2.1.1). axe only catches the instances whose content happens to have no focusable child, so this checks the markup rather than one render (gh#825). overflow-hidden is a clipping box, not a scrollport, and is not flagged.',
928
+ 'Hand-rolled scrollport (overflow-auto / -scroll on your own element) — use <ScrollArea label={t("…")} orientation> so the box that scrolls is a tab stop with a role and a localized name, and stops being one when there is nothing to scroll (gh#821). Without it, anything below the fold is reachable by mouse and by nothing else (WCAG 2.2 SC 2.1.1). axe only catches the instances whose content happens to have no focusable child, so this checks the markup rather than one render (gh#825). overflow-hidden is a clipping box, not a scrollport, and is not flagged. A strip of FILTERS or form controls is not a scrollport at all — it wraps (`filter-strip-no-scroll`, cardinal rule #51); do not move it into a horizontal ScrollArea.',
916
929
  },
917
930
  {
918
931
  id: "no-hand-rolled-break-anywhere",
@@ -2499,6 +2512,53 @@ for (const dir of SCAN_DIRS) {
2499
2512
  }
2500
2513
  }
2501
2514
 
2515
+ /*
2516
+ * A HAND-ROLLED sideways filter strip (cardinal rule #51). `filter-bar-no-overflow` only sees
2517
+ * the removed prop; the same strip rebuilt as `<Flex className="overflow-x-auto">` or
2518
+ * `<ScrollArea orientation="horizontal">` around FilterBar/FormField/Select/SearchInput is
2519
+ * the defect 33.0.0 removed (1240px vi: 1945px of filters in a 921px frame, the first label
2520
+ * clipped). WCAG 2.2 SC 1.4.10 excepts two-dimensional content, not search fields or controls,
2521
+ * so a box whose body holds a table (an editable grid) or Cards (a kanban board, a carousel of
2522
+ * records, each with its own controls) is left alone. Only the
2523
+ * INLINE axis is matched: `overflow-auto` / `overflow-y-auto` are vertical panes, where a
2524
+ * Sheet of stacked filters legitimately scrolls. Chip rows (ToggleGroup/Segmented) are not
2525
+ * matched: the same component is an editor toolbar, which SC 1.4.10 does except.
2526
+ */
2527
+ const FILTER_CONTROL =
2528
+ /<(?:FilterBar|Toolbar|FilterBarGroup|ToolbarGroup|FormField|SearchInput|Select|Input|DatePicker|DateRangePicker|TimePicker|NumberInput|Cascader|TreeSelect)(?=[\s/>])/;
2529
+ const HOLDS_2D_CONTENT = /<(?:Table|DataTable|table|Card)(?=[\s/>])/;
2530
+ const scrollOpeners = new Set();
2531
+ for (const m of scanContent.matchAll(/(?<![\w-])overflow-x-(?:auto|scroll)(?![\w-])/g)) {
2532
+ const at = scanContent.lastIndexOf("<", m.index);
2533
+ if (at >= 0 && jsxOpeningEnd(scanContent, at) > m.index) scrollOpeners.add(at);
2534
+ }
2535
+ for (const m of scanContent.matchAll(/<ScrollArea(?=[\s>])/g)) {
2536
+ const open = scanContent.slice(m.index, jsxOpeningEnd(scanContent, m.index) + 1);
2537
+ if (/\borientation\s*=\s*\{?\s*["'](?:horizontal|both)["']/.test(open)) {
2538
+ scrollOpeners.add(m.index);
2539
+ }
2540
+ }
2541
+ for (const at of scrollOpeners) {
2542
+ const tag = /^<([A-Za-z][\w.]*)/.exec(scanContent.slice(at))?.[1];
2543
+ if (!tag) continue;
2544
+ const openEnd = jsxOpeningEnd(scanContent, at);
2545
+ if (/\/\s*$/.test(scanContent.slice(at, openEnd))) continue;
2546
+ const close = closingTagRange(scanContent, tag, openEnd + 1);
2547
+ const body = scanContent.slice(openEnd + 1, close ? close.start : scanContent.length);
2548
+ if (!FILTER_CONTROL.test(body) || HOLDS_2D_CONTENT.test(body)) continue;
2549
+ const lineNo = lineAt(at);
2550
+ if (suppressed("filter-strip-no-scroll", lineNo - 1)) continue;
2551
+ findings.push({
2552
+ file: rel,
2553
+ line: lineNo,
2554
+ rule: "filter-strip-no-scroll",
2555
+ severity: "error",
2556
+ standard: "WCAG 2.2 SC 1.4.10 (Reflow) · @godxjp/ui cardinal rule #51",
2557
+ message: `A sideways-scrolling strip of filters/controls (<${tag}> scrolls on the inline axis around FilterBar/FormField/Select/SearchInput). A filter bar WRAPS at every width and never scrolls sideways: drop the scroller and let <FilterBar> wrap; keep the 2–4 most-used filters inline and put the rest behind ONE "More filters" Button that opens a Sheet (MCP: get_rule 51, get_pattern list-filters).`,
2558
+ snippet: scanContent.slice(at, at + 120).replace(/\s+/g, " "),
2559
+ });
2560
+ }
2561
+
2502
2562
  for (const m of scanContent.matchAll(/<(Dialog\.Body|DialogBody)(?=[\s>])[\s\S]*?<\/\1>/g)) {
2503
2563
  const count = (m[0].match(/<FormField(?=[\s>/])/g) ?? []).length;
2504
2564
  if (count < 3) continue;