@godxjp/ui 32.0.5 → 32.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 (52) hide show
  1. package/README.md +2 -2
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/Checkbox.json +1 -1
  4. package/agent/components/CheckboxGroup.json +6 -0
  5. package/agent/components/CommandPalette.json +6 -0
  6. package/agent/components/DatePicker.json +6 -0
  7. package/agent/components/DropdownMenu.json +5 -0
  8. package/agent/components/PasswordStrength.json +3 -3
  9. package/agent/components/SearchInput.json +6 -0
  10. package/agent/components/Select.json +6 -0
  11. package/agent/components/Toaster.json +15 -4
  12. package/agent/components-index.json +1 -1
  13. package/agent/components.json +54 -8
  14. package/agent/index.json +6 -6
  15. package/agent/llms.txt +6 -6
  16. package/agent/patterns/card-grid-selection.json +21 -0
  17. package/agent/patterns/dependent-selects.json +20 -0
  18. package/agent/patterns-index.json +39 -0
  19. package/agent/patterns.json +41 -0
  20. package/agent/tokens.json +18 -0
  21. package/dist/components/data-entry/checkbox-group.d.ts +1 -1
  22. package/dist/components/data-entry/checkbox-group.js +58 -19
  23. package/dist/components/data-entry/command-palette.d.ts +21 -1
  24. package/dist/components/data-entry/command-palette.js +34 -34
  25. package/dist/components/data-entry/date-picker.js +21 -1
  26. package/dist/components/data-entry/index.d.ts +1 -1
  27. package/dist/components/data-entry/password-strength.d.ts +2 -0
  28. package/dist/components/data-entry/password-strength.js +8 -7
  29. package/dist/components/data-entry/search-input.d.ts +1 -1
  30. package/dist/components/data-entry/search-input.js +22 -2
  31. package/dist/components/data-entry/search-select.js +63 -11
  32. package/dist/components/feedback/sonner.d.ts +7 -1
  33. package/dist/components/feedback/sonner.js +22 -2
  34. package/dist/components/feedback/use-toast.d.ts +3 -1
  35. package/dist/components/feedback/use-toast.js +10 -1
  36. package/dist/components/navigation/dropdown-menu.d.ts +7 -1
  37. package/dist/components/navigation/dropdown-menu.js +18 -2
  38. package/dist/contracts/measurement.json +1 -1
  39. package/dist/i18n/messages/en.json +19 -1
  40. package/dist/i18n/messages/ja.json +18 -1
  41. package/dist/i18n/messages/vi.json +18 -1
  42. package/dist/lib/shortcut.d.ts +17 -0
  43. package/dist/lib/shortcut.js +37 -0
  44. package/dist/props/components/data-entry.prop.d.ts +36 -0
  45. package/dist/styles/control.css +23 -0
  46. package/dist/styles/layers.json +6 -1
  47. package/dist/styles/navigation-layout.css +26 -0
  48. package/dist/styles/table-layout.css +14 -0
  49. package/dist/styles.css +1 -1
  50. package/dist/tokens/components/navigation.css +6 -0
  51. package/dist/tokens/foundation.css +2 -0
  52. package/package.json +2 -2
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 — 161 components, 2183 tokens, 43 cardinal rules — is published in two forms from one
21
+ The catalog — 161 components, 2186 tokens, 43 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
 
@@ -55,7 +55,7 @@ static files straight from this public repo — no hosting, no deploy step:
55
55
  | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 47 KB | all 161 as name + group + tagline — **fetch this first** |
56
56
  | [`agent/components/<Name>.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json) | 1 KB–36 KB | one file per component, each with its `importPath` — **this is the route to take** |
57
57
  | [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.2 MB | all 161 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
58
- | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 841 KB | all 2183 design tokens, their values and why they exist |
58
+ | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 842 KB | all 2186 design tokens, their values and why they exist |
59
59
  | [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
60
60
  | [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 21 KB | the 43 cardinal rules |
61
61
  <!-- prettier-ignore-end -->
@@ -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.0.5.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 32.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.
@@ -43,7 +43,7 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
43
43
  **You cannot run a process** (ChatGPT web · Claude.ai · anything fetching URLs)
44
44
  → These files are for you. Fetch in this order:
45
45
 
46
- 0. `patterns-index.json` — 22 whole-task patterns as name + tagline + tags. **If your
46
+ 0. `patterns-index.json` — 24 whole-task patterns as name + tagline + tags. **If your
47
47
  task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
48
48
  filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
49
49
  copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
@@ -56,10 +56,10 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
56
56
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
57
57
  3. `rules.json` — 43 cardinal rules. The ones about raw HTML and hardcoded colour are not
58
58
  style advice.
59
- 4. `tokens.json` — 2183 design tokens, each tagged with its `tier`. **If you were handed a
60
- brand, read the 210 `foundation` entries first** — `--primary`, `--background`,
59
+ 4. `tokens.json` — 2186 design tokens, each tagged with its `tier`. **If you were handed a
60
+ brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
61
61
  `--radius`, `--font-size-base` are the handful everything else derives from. The
62
- 1861 `component` entries are per-part knobs; reach for one only when a role is
62
+ 1863 `component` entries are per-part knobs; reach for one only when a role is
63
63
  right everywhere except one component.
64
64
  5. `anti-ai-tells.json` — 26 shapes that make generated UI look generated, each with the
65
65
  fix. Read before you reach for a gradient hero or a wall of coloured chips.
@@ -143,9 +143,9 @@ has stopped following the brand.
143
143
 
144
144
  | `tier` | count | what it is | set it? |
145
145
  |---|---|---|---|
146
- | `foundation` | 210 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
146
+ | `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
147
147
  | `semantic` | 112 | 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 |
148
- | `component` | 1861 | 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` |
148
+ | `component` | 1863 | 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` |
149
149
 
150
150
  A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
151
151
  value, so the real default is computed where the element paints it. Set it and yours wins.
@@ -60,7 +60,7 @@
60
60
  "DO use the controlled pattern (`checked` + `onCheckedChange`) for any form-bound checkbox. `onCheckedChange` receives `boolean | 'indeterminate'` — always coerce with `!!v` or an explicit guard before storing in state.",
61
61
  "DO use `Checkbox.Group` (alias for CheckboxGroup) with the `options` prop when you have ≥2 choices from an array — it renders each item inside a `Field` (label + optional description), generates stable ids automatically, and manages the `string[]` value array. NEVER hand-roll a loop of bare `<Checkbox>` elements for a multi-select list.",
62
62
  "DO pass `name` on `Checkbox.Group` (not on individual checkboxes) when the group must submit as form fields — the group propagates the name to each internal checkbox so the browser serialises all checked values under that key.",
63
- "DON'T use `checked='indeterminate'` on `Checkbox.Group` children — indeterminate is only meaningful on a parent 'select-all' control you wire manually; the group itself does not auto-compute it.",
63
+ "DON'T use `checked='indeterminate'` on `Checkbox.Group` children, and DON'T hand-wire a select-all box above a group — pass `selectAll` (or `selectAll={{ label: '…' }}`) on `Checkbox.Group` with `options`. It renders a leading box derived from the value (checked / `mixed` / clear), a muted `{n} of {m} selected` count (`common.selectedOfTotal`, numbers via Intl.NumberFormat) and a Separator; one click is ONE `onValueChange` with every enabled value; disabled options are never counted, added or removed; the box has no `name`, so a native submit never carries it.",
64
64
  "DON'T wrap a standalone Checkbox in `Field` manually — `Field` is the internal composition primitive that `Checkbox.Group` uses. For a single boolean with a label, pass the label as children (`<Checkbox …>label</Checkbox>`, the catalog example) — it renders the same row. Use `<Field id='x' label='…' description='…'><Checkbox id='x' … /></Field>` only when the row needs a description line — Field owns the label-to-control id wiring, the description slot and the row rhythm, and a hand-rolled flex row owns none of them; for a full labelled-checkbox with description, use `Field` directly only if you need a one-off item outside a group."
65
65
  ],
66
66
  "useCases": [
@@ -60,6 +60,11 @@
60
60
  "description": "Fires with the checked values array.",
61
61
  "name": "onValueChange",
62
62
  "type": "(value: string[]) => void"
63
+ },
64
+ {
65
+ "description": "`options` mode only. Renders a leading select-all box whose state is derived from the value — checked when every enabled option is selected, `mixed` (indeterminate) on a partial selection, clear otherwise — followed by a muted `{n} of {m} selected` count (`common.selectedOfTotal`, numbers via Intl.NumberFormat) and a Separator. One click is ONE `onValueChange`: from mixed or clear it adds every enabled value, from checked it removes them. Disabled options are never counted, added or removed. The box has no `name`, so a native submit never carries it. `{ label }` replaces the default `dataEntry.checkboxGroup.selectAll` text.",
66
+ "name": "selectAll",
67
+ "type": "boolean | { label?: React.ReactNode }"
63
68
  }
64
69
  ],
65
70
  "related": [
@@ -77,6 +82,7 @@
77
82
  ],
78
83
  "tagline": "Multi-select checkbox list from an options array or manual children — use `options` prop for the data-driven path, never hand-roll individual Checkbox items in a group.",
79
84
  "usage": [
85
+ "DO pass `selectAll` instead of hand-wiring a parent checkbox above the group — the derived tri-state, the n-of-m count and the disabled-option rule are built in: `<CheckboxGroup options={channels} defaultValue={['email']} selectAll />`.",
80
86
  "DO use the `options` prop for any data-driven list — it auto-generates IDs, handles checked state, and wires up Field (label + description) for each item. NEVER hand-roll individual `<Checkbox>` elements inside a loop when you have an options array.",
81
87
  "DO pass `name` when inside an HTML form so each checkbox submits its value under the same field name, giving the server a multi-value array. Without `name`, native form submission silently drops all values.",
82
88
  "Controlled vs uncontrolled: pass `value` + `onChange` together for controlled usage (e.g. react-hook-form). Pass `defaultValue` alone for uncontrolled usage. Do NOT mix both — if `value` is provided, `defaultValue` is ignored and onChange must update value externally or the UI freezes.",
@@ -81,6 +81,11 @@
81
81
  "description": "Consumer-supplied error content.",
82
82
  "name": "error",
83
83
  "type": "ReactNode"
84
+ },
85
+ {
86
+ "description": "Recent queries (gh#1249), rendered as the FIRST group only while the query is empty, followed by a trailing \"Clear recent searches\" row. Choosing a query calls `onSelect(query)` and closes; the clear row calls `onClear()` and keeps the palette open. The library never reads or writes `localStorage` — the consumer stores, de-duplicates and caps `items`; duplicates are rendered as given. `label` replaces the default heading (`dataEntry.commandPalette.recent`).",
87
+ "name": "recent",
88
+ "type": "{ items: string[]; onSelect: (query: string) => void; onClear: () => void; label?: ReactNode }"
84
89
  }
85
90
  ],
86
91
  "rules": [],
@@ -88,6 +93,7 @@
88
93
  "tagline": "Searchable command dialog with controlled or uncontrolled open state and consumer-owned selection.",
89
94
  "usage": [
90
95
  "Provide localized labels and real command groups; the component does not fetch commands.",
96
+ "Recent searches: keep the list in your own store (state, URL, server, localStorage), de-duplicate it, and pass it as `recent` — the palette only renders it while the query is empty.",
91
97
  "Use either `open` plus `onOpenChange` or `defaultOpen`; do not mirror both state models.",
92
98
  "Search-as-you-type against an API: read the query from `onSearchChange`, pass the results back as `groups`, and set `shouldFilter={false}` so the rows are not scored a second time against the same string.",
93
99
  "The empty-state contract: with `shouldFilter` (default) cmdk decides — items exist, the query matches none. With `shouldFilter={false}` the PALETTE decides from props — `labels.empty` renders when `groups` carries no items, and never while `loading` or `error` is set. Hold `loading` for the whole in-flight window: a request that has not answered yet is not an empty result, and asserting over cmdk's own empty node races an async group that populates a frame late."
@@ -132,6 +132,12 @@
132
132
  "name": "allowEmpty",
133
133
  "type": "[boolean, boolean]"
134
134
  },
135
+ {
136
+ "defaultValue": "true",
137
+ "description": "With `range`: a polite live summary (`aria-live=\"polite\"`, always mounted) under the DAY grid — the start date alone while only the start is held, then `Intl.DateTimeFormat#formatRange` of both edges plus the INCLUSIVE day count through `Intl.PluralRules` (1 → 7 reads \"Mar 1 – 7, 2026 · 7 days\"; ja 「7日間」). Muted `Text size=\"xs\" tabular`, no new token. The month / quarter / year period grids ignore it. Independently of this flag, while only the start is chosen the `to` edge's placeholder becomes \"Select end date\" (`dataEntry.dateRangePicker.selectEnd`). `false` removes the summary.",
138
+ "name": "showSummary",
139
+ "type": "boolean"
140
+ },
135
141
  {
136
142
  "defaultValue": "true",
137
143
  "description": "Normalise the selection into ascending order (antd `order`). ONE rule seen through two value shapes: a `range` picked backwards is SWAPPED, a `multiple` selection is SORTED. Pass false to keep pick order.",
@@ -51,6 +51,11 @@
51
51
  "description": "On DropdownMenuContent. Ant Design `arrow` — paints the pointer at the anchored edge (default off). Sized from --dropdown-arrow-{width,height,background} rather than Radix's built-in 10×5.",
52
52
  "name": "arrow",
53
53
  "type": "boolean"
54
+ },
55
+ {
56
+ "description": "On DropdownMenuItem (gh#1246). A muted second line under the label — the two-line row, parity with `MegaMenuLinkProp.description`. Rendered as react-aria `<Text slot=\"description\">` (`data-slot=\"dropdown-menu-item-description\"`), so the row's accessible NAME stays the label and the description becomes its `aria-describedby`. A string label is handed to the collection as `textValue`, so typeahead still reaches the row by its label; pass `textValue` yourself when the label is not a plain string. Sized by --menu-item-description-{font-size,space-block}; rows without one keep the one-line height.",
57
+ "name": "description",
58
+ "type": "React.ReactNode"
54
59
  }
55
60
  ],
56
61
  "related": [
@@ -5,7 +5,7 @@
5
5
  "name": "PasswordStrength",
6
6
  "props": [
7
7
  {
8
- "description": "The current password value to evaluate.",
8
+ "description": "The current password value to evaluate. While it is empty (or whitespace only) the meter is NEUTRAL (gh#1247): no segment lit, no tone, the label reads `labels.empty` (\"Enter a password\" / 「パスワードを入力」), every checklist item is `data-state=\"pending\"`, and the live region stays silent — no \"Weak\" verdict before anything is typed.",
9
9
  "name": "value",
10
10
  "required": true,
11
11
  "type": "string"
@@ -28,9 +28,9 @@
28
28
  "type": "number"
29
29
  },
30
30
  {
31
- "description": "Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing: the score words, the checklist line per rule (length / upper / lower / number / symbol), the sr-only passed / failed prefixes, and the polite live-region sentence (`announcement(strength)`). Pass only the ones your product words differently.",
31
+ "description": "Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing: the score words, the neutral empty-field line (`empty`, gh#1247), the checklist line per rule (length / upper / lower / number / symbol), the sr-only passed / failed prefixes, and the polite live-region sentence (`announcement(strength)`). Pass only the ones your product words differently.",
32
32
  "name": "labels",
33
- "type": "{ weak?: string; fair?: string; strong?: string; rules?: Partial<Record<PasswordRule, string>>; passed?: string; failed?: string; announcement?: (strength: string) => string }"
33
+ "type": "{ weak?: string; fair?: string; strong?: string; empty?: string; rules?: Partial<Record<PasswordRule, string>>; passed?: string; failed?: string; announcement?: (strength: string) => string }"
34
34
  }
35
35
  ],
36
36
  "related": [
@@ -68,6 +68,12 @@
68
68
  "name": "disabled",
69
69
  "type": "boolean"
70
70
  },
71
+ {
72
+ "defaultValue": "false",
73
+ "description": "A global shortcut that FOCUSES the field (gh#1250). `false` (default) binds none — two search boxes on one page must not fight over one key; `true` is ⌘K on Apple platforms and Ctrl+K elsewhere (never both, so Ctrl+K keeps its editing meaning on a Mac); a combo string (`\"mod+/\"`, `\"mod+shift+f\"`) is read exactly as CommandPalette `shortcut`. A trailing aria-hidden key cap shows the combo while the field is empty — the clear ✕ takes the slot once there is text. Never fires during IME composition, nor while `disabled`.",
74
+ "name": "shortcut",
75
+ "type": "boolean | string"
76
+ },
71
77
  {
72
78
  "description": "Class on the `<input>` itself. SearchInput renders a WRAPPER (label, icon, clear button, input), so `className` lands on that wrapper and never reaches the field — this is the second handle, for the case where the field and its chrome need different treatment. Layout and colour still belong to tokens; use it for the rare geometry a token cannot reach.",
73
79
  "name": "inputClassName",
@@ -349,6 +349,12 @@
349
349
  "name": "maxTagTextLength",
350
350
  "type": "number"
351
351
  },
352
+ {
353
+ "defaultValue": "false",
354
+ "description": "multiple/tags — hold the panel's picks as a DRAFT until OK commits them (gh#1248; antd's `needConfirm`, the same word TimePicker/DatePicker use). Row toggles move a pending set and report nothing; the footer's OK (Button size sm) fires ONE onValueChange with the whole set and closes; its Clear empties the pending set; Escape or an outside click discards the draft and the trigger keeps the committed values. onSelect/onDeselect do not fire for draft toggles. A chip's ✕ and Backspace on the closed trigger still commit at once. Default false: every toggle commits.",
355
+ "name": "needConfirm",
356
+ "type": "boolean"
357
+ },
352
358
  {
353
359
  "description": "antd `tagRender` (multiple/tags) — render one chip yourself. Exactly the shape TagInput's tagRender takes. The onClose handed in is the same remover the built-in ✕ calls, so a custom chip cannot end up unremovable; supplying tagRender withdraws the built-in ✕.",
354
360
  "name": "tagRender",
@@ -1,5 +1,5 @@
1
1
  {
2
- "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere — import toast from \"sonner\"\nimport { toast } from \"sonner\";\ntoast.success(\"クーポンを公開しました\");\ntoast.error(\"保存に失敗しました\");",
2
+ "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere\nimport { toast } from \"@godxjp/ui/feedback\";\ntoast.success(\"クーポンを公開しました\"); // auto-dismiss after --toast-duration\ntoast.error(\"保存に失敗しました\"); // also carries a close button",
3
3
  "group": "feedback",
4
4
  "importPath": "@godxjp/ui/feedback",
5
5
  "name": "Toaster",
@@ -14,6 +14,16 @@
14
14
  "description": "Enable Sonner rich variant colours.",
15
15
  "name": "richColors",
16
16
  "type": "boolean"
17
+ },
18
+ {
19
+ "description": "Auto-dismiss time in ms for every toast. Omit it to let the `--toast-duration` token (default 4000ms) decide — read once on mount from the ThemeScope host or `<html>` (gh#1253). A per-call `toast(…, { duration })` still wins.",
20
+ "name": "duration",
21
+ "type": "number"
22
+ },
23
+ {
24
+ "description": "Sonner's switch for a close button on EVERY toast. Leave it unset for the default: `toast.error` / `toast.warning` from `@godxjp/ui/feedback` carry a close button, `toast.success` / `toast.info` stay auto-dismiss only (gh#1253). The button is named via `t(\"feedback.toast.close\")`.",
25
+ "name": "closeButton",
26
+ "type": "boolean"
17
27
  }
18
28
  ],
19
29
  "related": [
@@ -22,11 +32,12 @@
22
32
  "Dialog — use when the user must make a conscious decision (confirm delete, resolve conflict) before proceeding. Toaster toasts are fire-and-forget; Dialog blocks until the user responds."
23
33
  ],
24
34
  "rules": [],
25
- "tagline": "Mount once at app root to enable toasts. IMPORTANT: trigger toasts via `import { toast } from \"sonner\"` — NOT from @godxjp/ui.",
35
+ "tagline": "Mount once at app root to enable toasts. Trigger them with `import { toast } from \"@godxjp/ui/feedback\"` — Sonner's API, with a close button on `toast.error` / `toast.warning` by default.",
26
36
  "usage": [
27
37
  "DO: Mount exactly ONE `<Toaster richColors />` at the app root (e.g. inside your layout or AppShell children). Multiple mounts create duplicate toast stacks — there is no provider context, only DOM portals.",
28
- "DO: Import `toast` from `\"sonner\"` directly (not from `@godxjp/ui`) to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`.",
29
- "DON'T: Try to import a `toast` helper from `@godxjp/ui/feedback` — it does not exist. The component re-exports only the `Toaster` mount; the imperative API lives in the `sonner` package.",
38
+ "DO: Import `toast` from `\"@godxjp/ui/feedback\"` to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`. It is Sonner's own API with one house default: `toast.error` and `toast.warning` carry a close button (opt out per call with `{ closeButton: false }`), success/info stay auto-dismiss only (gh#1253).",
39
+ "DON'T: Import `toast` from `\"sonner\"` directly — it works, but it skips the close-button default on error/warning. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
40
+ "DO: Tune how long toasts stay with the `--toast-duration` token in the theme (default 4000ms) rather than a `duration` prop on every mount. Never truncate a toast's body — it wraps.",
30
41
  "DO: Let the wrapper handle theming — it uses `useDocumentTheme()` to sync with the document `dark` class and `prefers-color-scheme` automatically. Never pass a hardcoded `theme` prop unless you are deliberately overriding.",
31
42
  "DON'T: Use `Toaster` for persistent errors or blocking confirmations. Toasts auto-dismiss; they are not a substitute for `Alert` (inline persistent warnings) or `Dialog` (decisions requiring user input).",
32
43
  "DO: Pass `position` to relocate the stack if a persistent sidebar/footer would obscure the default `bottom-right`. The wrapper already sets a safe `mobileOffset`; don't add redundant mobile offsets unless your layout differs."
@@ -453,7 +453,7 @@
453
453
  {
454
454
  "group": "feedback",
455
455
  "name": "Toaster",
456
- "tagline": "Mount once at app root to enable toasts. IMPORTANT: trigger toasts via `import { toast } from \"sonner\"` — NOT from @godxjp/ui."
456
+ "tagline": "Mount once at app root to enable toasts. Trigger them with `import { toast } from \"@godxjp/ui/feedback\"` — Sonner's API, with a close button on `toast.error` / `toast.warning` by default."
457
457
  },
458
458
  {
459
459
  "group": "navigation",
@@ -6242,6 +6242,12 @@
6242
6242
  "name": "disabled",
6243
6243
  "type": "boolean"
6244
6244
  },
6245
+ {
6246
+ "defaultValue": "false",
6247
+ "description": "A global shortcut that FOCUSES the field (gh#1250). `false` (default) binds none — two search boxes on one page must not fight over one key; `true` is ⌘K on Apple platforms and Ctrl+K elsewhere (never both, so Ctrl+K keeps its editing meaning on a Mac); a combo string (`\"mod+/\"`, `\"mod+shift+f\"`) is read exactly as CommandPalette `shortcut`. A trailing aria-hidden key cap shows the combo while the field is empty — the clear ✕ takes the slot once there is text. Never fires during IME composition, nor while `disabled`.",
6248
+ "name": "shortcut",
6249
+ "type": "boolean | string"
6250
+ },
6245
6251
  {
6246
6252
  "description": "Class on the `<input>` itself. SearchInput renders a WRAPPER (label, icon, clear button, input), so `className` lands on that wrapper and never reaches the field — this is the second handle, for the case where the field and its chrome need different treatment. Layout and colour still belong to tokens; use it for the rare geometry a token cannot reach.",
6247
6253
  "name": "inputClassName",
@@ -6750,6 +6756,12 @@
6750
6756
  "name": "maxTagTextLength",
6751
6757
  "type": "number"
6752
6758
  },
6759
+ {
6760
+ "defaultValue": "false",
6761
+ "description": "multiple/tags — hold the panel's picks as a DRAFT until OK commits them (gh#1248; antd's `needConfirm`, the same word TimePicker/DatePicker use). Row toggles move a pending set and report nothing; the footer's OK (Button size sm) fires ONE onValueChange with the whole set and closes; its Clear empties the pending set; Escape or an outside click discards the draft and the trigger keeps the committed values. onSelect/onDeselect do not fire for draft toggles. A chip's ✕ and Backspace on the closed trigger still commit at once. Default false: every toggle commits.",
6762
+ "name": "needConfirm",
6763
+ "type": "boolean"
6764
+ },
6753
6765
  {
6754
6766
  "description": "antd `tagRender` (multiple/tags) — render one chip yourself. Exactly the shape TagInput's tagRender takes. The onClose handed in is the same remover the built-in ✕ calls, so a custom chip cannot end up unremovable; supplying tagRender withdraws the built-in ✕.",
6755
6767
  "name": "tagRender",
@@ -7139,7 +7151,7 @@
7139
7151
  "DO use the controlled pattern (`checked` + `onCheckedChange`) for any form-bound checkbox. `onCheckedChange` receives `boolean | 'indeterminate'` — always coerce with `!!v` or an explicit guard before storing in state.",
7140
7152
  "DO use `Checkbox.Group` (alias for CheckboxGroup) with the `options` prop when you have ≥2 choices from an array — it renders each item inside a `Field` (label + optional description), generates stable ids automatically, and manages the `string[]` value array. NEVER hand-roll a loop of bare `<Checkbox>` elements for a multi-select list.",
7141
7153
  "DO pass `name` on `Checkbox.Group` (not on individual checkboxes) when the group must submit as form fields — the group propagates the name to each internal checkbox so the browser serialises all checked values under that key.",
7142
- "DON'T use `checked='indeterminate'` on `Checkbox.Group` children — indeterminate is only meaningful on a parent 'select-all' control you wire manually; the group itself does not auto-compute it.",
7154
+ "DON'T use `checked='indeterminate'` on `Checkbox.Group` children, and DON'T hand-wire a select-all box above a group — pass `selectAll` (or `selectAll={{ label: '…' }}`) on `Checkbox.Group` with `options`. It renders a leading box derived from the value (checked / `mixed` / clear), a muted `{n} of {m} selected` count (`common.selectedOfTotal`, numbers via Intl.NumberFormat) and a Separator; one click is ONE `onValueChange` with every enabled value; disabled options are never counted, added or removed; the box has no `name`, so a native submit never carries it.",
7143
7155
  "DON'T wrap a standalone Checkbox in `Field` manually — `Field` is the internal composition primitive that `Checkbox.Group` uses. For a single boolean with a label, pass the label as children (`<Checkbox …>label</Checkbox>`, the catalog example) — it renders the same row. Use `<Field id='x' label='…' description='…'><Checkbox id='x' … /></Field>` only when the row needs a description line — Field owns the label-to-control id wiring, the description slot and the row rhythm, and a hand-rolled flex row owns none of them; for a full labelled-checkbox with description, use `Field` directly only if you need a one-off item outside a group."
7144
7156
  ],
7145
7157
  "useCases": [
@@ -7376,6 +7388,12 @@
7376
7388
  "name": "allowEmpty",
7377
7389
  "type": "[boolean, boolean]"
7378
7390
  },
7391
+ {
7392
+ "defaultValue": "true",
7393
+ "description": "With `range`: a polite live summary (`aria-live=\"polite\"`, always mounted) under the DAY grid — the start date alone while only the start is held, then `Intl.DateTimeFormat#formatRange` of both edges plus the INCLUSIVE day count through `Intl.PluralRules` (1 → 7 reads \"Mar 1 – 7, 2026 · 7 days\"; ja 「7日間」). Muted `Text size=\"xs\" tabular`, no new token. The month / quarter / year period grids ignore it. Independently of this flag, while only the start is chosen the `to` edge's placeholder becomes \"Select end date\" (`dataEntry.dateRangePicker.selectEnd`). `false` removes the summary.",
7394
+ "name": "showSummary",
7395
+ "type": "boolean"
7396
+ },
7379
7397
  {
7380
7398
  "defaultValue": "true",
7381
7399
  "description": "Normalise the selection into ascending order (antd `order`). ONE rule seen through two value shapes: a `range` picked backwards is SWAPPED, a `multiple` selection is SORTED. Pass false to keep pick order.",
@@ -7886,7 +7904,7 @@
7886
7904
  ]
7887
7905
  },
7888
7906
  {
7889
- "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere — import toast from \"sonner\"\nimport { toast } from \"sonner\";\ntoast.success(\"クーポンを公開しました\");\ntoast.error(\"保存に失敗しました\");",
7907
+ "example": "// app root — mount once\nimport { Toaster } from \"@godxjp/ui/feedback\";\n<>{children}<Toaster richColors /></>\n\n// anywhere\nimport { toast } from \"@godxjp/ui/feedback\";\ntoast.success(\"クーポンを公開しました\"); // auto-dismiss after --toast-duration\ntoast.error(\"保存に失敗しました\"); // also carries a close button",
7890
7908
  "group": "feedback",
7891
7909
  "importPath": "@godxjp/ui/feedback",
7892
7910
  "name": "Toaster",
@@ -7901,6 +7919,16 @@
7901
7919
  "description": "Enable Sonner rich variant colours.",
7902
7920
  "name": "richColors",
7903
7921
  "type": "boolean"
7922
+ },
7923
+ {
7924
+ "description": "Auto-dismiss time in ms for every toast. Omit it to let the `--toast-duration` token (default 4000ms) decide — read once on mount from the ThemeScope host or `<html>` (gh#1253). A per-call `toast(…, { duration })` still wins.",
7925
+ "name": "duration",
7926
+ "type": "number"
7927
+ },
7928
+ {
7929
+ "description": "Sonner's switch for a close button on EVERY toast. Leave it unset for the default: `toast.error` / `toast.warning` from `@godxjp/ui/feedback` carry a close button, `toast.success` / `toast.info` stay auto-dismiss only (gh#1253). The button is named via `t(\"feedback.toast.close\")`.",
7930
+ "name": "closeButton",
7931
+ "type": "boolean"
7904
7932
  }
7905
7933
  ],
7906
7934
  "related": [
@@ -7909,11 +7937,12 @@
7909
7937
  "Dialog — use when the user must make a conscious decision (confirm delete, resolve conflict) before proceeding. Toaster toasts are fire-and-forget; Dialog blocks until the user responds."
7910
7938
  ],
7911
7939
  "rules": [],
7912
- "tagline": "Mount once at app root to enable toasts. IMPORTANT: trigger toasts via `import { toast } from \"sonner\"` — NOT from @godxjp/ui.",
7940
+ "tagline": "Mount once at app root to enable toasts. Trigger them with `import { toast } from \"@godxjp/ui/feedback\"` — Sonner's API, with a close button on `toast.error` / `toast.warning` by default.",
7913
7941
  "usage": [
7914
7942
  "DO: Mount exactly ONE `<Toaster richColors />` at the app root (e.g. inside your layout or AppShell children). Multiple mounts create duplicate toast stacks — there is no provider context, only DOM portals.",
7915
- "DO: Import `toast` from `\"sonner\"` directly (not from `@godxjp/ui`) to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`.",
7916
- "DON'T: Try to import a `toast` helper from `@godxjp/ui/feedback` — it does not exist. The component re-exports only the `Toaster` mount; the imperative API lives in the `sonner` package.",
7943
+ "DO: Import `toast` from `\"@godxjp/ui/feedback\"` to fire toasts anywhere: `toast.success(…)`, `toast.error(…)`, `toast.warning(…)`, `toast.info(…)`, `toast.loading(…)`, `toast.promise(…)`. It is Sonner's own API with one house default: `toast.error` and `toast.warning` carry a close button (opt out per call with `{ closeButton: false }`), success/info stay auto-dismiss only (gh#1253).",
7944
+ "DON'T: Import `toast` from `\"sonner\"` directly — it works, but it skips the close-button default on error/warning. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
7945
+ "DO: Tune how long toasts stay with the `--toast-duration` token in the theme (default 4000ms) rather than a `duration` prop on every mount. Never truncate a toast's body — it wraps.",
7917
7946
  "DO: Let the wrapper handle theming — it uses `useDocumentTheme()` to sync with the document `dark` class and `prefers-color-scheme` automatically. Never pass a hardcoded `theme` prop unless you are deliberately overriding.",
7918
7947
  "DON'T: Use `Toaster` for persistent errors or blocking confirmations. Toasts auto-dismiss; they are not a substitute for `Alert` (inline persistent warnings) or `Dialog` (decisions requiring user input).",
7919
7948
  "DO: Pass `position` to relocate the stack if a persistent sidebar/footer would obscure the default `bottom-right`. The wrapper already sets a safe `mobileOffset`; don't add redundant mobile offsets unless your layout differs."
@@ -8284,6 +8313,11 @@
8284
8313
  "description": "On DropdownMenuContent. Ant Design `arrow` — paints the pointer at the anchored edge (default off). Sized from --dropdown-arrow-{width,height,background} rather than Radix's built-in 10×5.",
8285
8314
  "name": "arrow",
8286
8315
  "type": "boolean"
8316
+ },
8317
+ {
8318
+ "description": "On DropdownMenuItem (gh#1246). A muted second line under the label — the two-line row, parity with `MegaMenuLinkProp.description`. Rendered as react-aria `<Text slot=\"description\">` (`data-slot=\"dropdown-menu-item-description\"`), so the row's accessible NAME stays the label and the description becomes its `aria-describedby`. A string label is handed to the collection as `textValue`, so typeahead still reaches the row by its label; pass `textValue` yourself when the label is not a plain string. Sized by --menu-item-description-{font-size,space-block}; rows without one keep the one-line height.",
8319
+ "name": "description",
8320
+ "type": "React.ReactNode"
8287
8321
  }
8288
8322
  ],
8289
8323
  "related": [
@@ -10631,6 +10665,11 @@
10631
10665
  "description": "Fires with the checked values array.",
10632
10666
  "name": "onValueChange",
10633
10667
  "type": "(value: string[]) => void"
10668
+ },
10669
+ {
10670
+ "description": "`options` mode only. Renders a leading select-all box whose state is derived from the value — checked when every enabled option is selected, `mixed` (indeterminate) on a partial selection, clear otherwise — followed by a muted `{n} of {m} selected` count (`common.selectedOfTotal`, numbers via Intl.NumberFormat) and a Separator. One click is ONE `onValueChange`: from mixed or clear it adds every enabled value, from checked it removes them. Disabled options are never counted, added or removed. The box has no `name`, so a native submit never carries it. `{ label }` replaces the default `dataEntry.checkboxGroup.selectAll` text.",
10671
+ "name": "selectAll",
10672
+ "type": "boolean | { label?: React.ReactNode }"
10634
10673
  }
10635
10674
  ],
10636
10675
  "related": [
@@ -10648,6 +10687,7 @@
10648
10687
  ],
10649
10688
  "tagline": "Multi-select checkbox list from an options array or manual children — use `options` prop for the data-driven path, never hand-roll individual Checkbox items in a group.",
10650
10689
  "usage": [
10690
+ "DO pass `selectAll` instead of hand-wiring a parent checkbox above the group — the derived tri-state, the n-of-m count and the disabled-option rule are built in: `<CheckboxGroup options={channels} defaultValue={['email']} selectAll />`.",
10651
10691
  "DO use the `options` prop for any data-driven list — it auto-generates IDs, handles checked state, and wires up Field (label + description) for each item. NEVER hand-roll individual `<Checkbox>` elements inside a loop when you have an options array.",
10652
10692
  "DO pass `name` when inside an HTML form so each checkbox submits its value under the same field name, giving the server a multi-value array. Without `name`, native form submission silently drops all values.",
10653
10693
  "Controlled vs uncontrolled: pass `value` + `onChange` together for controlled usage (e.g. react-hook-form). Pass `defaultValue` alone for uncontrolled usage. Do NOT mix both — if `value` is provided, `defaultValue` is ignored and onChange must update value externally or the UI freezes.",
@@ -12088,7 +12128,7 @@
12088
12128
  "name": "PasswordStrength",
12089
12129
  "props": [
12090
12130
  {
12091
- "description": "The current password value to evaluate.",
12131
+ "description": "The current password value to evaluate. While it is empty (or whitespace only) the meter is NEUTRAL (gh#1247): no segment lit, no tone, the label reads `labels.empty` (\"Enter a password\" / 「パスワードを入力」), every checklist item is `data-state=\"pending\"`, and the live region stays silent — no \"Weak\" verdict before anything is typed.",
12092
12132
  "name": "value",
12093
12133
  "required": true,
12094
12134
  "type": "string"
@@ -12111,9 +12151,9 @@
12111
12151
  "type": "number"
12112
12152
  },
12113
12153
  {
12114
- "description": "Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing: the score words, the checklist line per rule (length / upper / lower / number / symbol), the sr-only passed / failed prefixes, and the polite live-region sentence (`announcement(strength)`). Pass only the ones your product words differently.",
12154
+ "description": "Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing: the score words, the neutral empty-field line (`empty`, gh#1247), the checklist line per rule (length / upper / lower / number / symbol), the sr-only passed / failed prefixes, and the polite live-region sentence (`announcement(strength)`). Pass only the ones your product words differently.",
12115
12155
  "name": "labels",
12116
- "type": "{ weak?: string; fair?: string; strong?: string; rules?: Partial<Record<PasswordRule, string>>; passed?: string; failed?: string; announcement?: (strength: string) => string }"
12156
+ "type": "{ weak?: string; fair?: string; strong?: string; empty?: string; rules?: Partial<Record<PasswordRule, string>>; passed?: string; failed?: string; announcement?: (strength: string) => string }"
12117
12157
  }
12118
12158
  ],
12119
12159
  "related": [
@@ -13398,6 +13438,11 @@
13398
13438
  "description": "Consumer-supplied error content.",
13399
13439
  "name": "error",
13400
13440
  "type": "ReactNode"
13441
+ },
13442
+ {
13443
+ "description": "Recent queries (gh#1249), rendered as the FIRST group only while the query is empty, followed by a trailing \"Clear recent searches\" row. Choosing a query calls `onSelect(query)` and closes; the clear row calls `onClear()` and keeps the palette open. The library never reads or writes `localStorage` — the consumer stores, de-duplicates and caps `items`; duplicates are rendered as given. `label` replaces the default heading (`dataEntry.commandPalette.recent`).",
13444
+ "name": "recent",
13445
+ "type": "{ items: string[]; onSelect: (query: string) => void; onClear: () => void; label?: ReactNode }"
13401
13446
  }
13402
13447
  ],
13403
13448
  "rules": [],
@@ -13405,6 +13450,7 @@
13405
13450
  "tagline": "Searchable command dialog with controlled or uncontrolled open state and consumer-owned selection.",
13406
13451
  "usage": [
13407
13452
  "Provide localized labels and real command groups; the component does not fetch commands.",
13453
+ "Recent searches: keep the list in your own store (state, URL, server, localStorage), de-duplicate it, and pass it as `recent` — the palette only renders it while the query is empty.",
13408
13454
  "Use either `open` plus `onOpenChange` or `defaultOpen`; do not mirror both state models.",
13409
13455
  "Search-as-you-type against an API: read the query from `onSearchChange`, pass the results back as `groups`, and set `shouldFilter={false}` so the rows are not scored a second time against the same string.",
13410
13456
  "The empty-state contract: with `shouldFilter` (default) cmdk decides — items exist, the query matches none. With `shouldFilter={false}` the PALETTE decides from props — `labels.empty` renders when `groups` carries no items, and never while `loading` or `error` is set. Hold `loading` for the whole in-flight window: a request that has not answered yet is not an empty result, and asserting over cmdk's own empty node races an async group that populates a frame late."
package/agent/index.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "counts": {
3
3
  "anti-ai-tells": 26,
4
4
  "components": 161,
5
- "patterns": 22,
5
+ "patterns": 24,
6
6
  "rules": 43,
7
- "tokens": 2183,
7
+ "tokens": 2186,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "files": [
@@ -48,19 +48,19 @@
48
48
  "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.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.0.5/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.1.0/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
55
55
  "tokenTiers": {
56
56
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
57
57
  "counts": {
58
- "component": 1861,
59
- "foundation": 210,
58
+ "component": 1863,
59
+ "foundation": 211,
60
60
  "semantic": 112
61
61
  },
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "32.0.5"
65
+ "version": "32.1.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 161 components, 2183 design tokens,
4
- > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.0.5.
3
+ > A Japanese-enterprise React design system: 161 components, 2186 design tokens,
4
+ > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.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.0.5`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@32.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
@@ -14,11 +14,11 @@ that can only fetch URLs.
14
14
 
15
15
  ## Catalog
16
16
 
17
- - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 22 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.
17
+ - [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.
18
18
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 47 KB — all 161 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
19
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–36 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.
20
20
  - [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.
21
- - [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` — 210 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 112 `semantic` roles, 1861 `component` knobs.
21
+ - [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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 112 `semantic` roles, 1863 `component` knobs.
22
22
  - [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.
23
23
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 43 cardinal rules.
24
24
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v32.0.5/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v32.1.0/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "aliases": [
3
+ "card-selection",
4
+ "selectable-cards",
5
+ "bulk-delete-cards",
6
+ "grid-bulk-actions"
7
+ ],
8
+ "code": "// Live page: /showcase/card-grid-selection (docs/showcase/card-grid-selection.tsx).\n// DataTable.BulkActions reads the table context only when one exists — outside a table pass\n// `count`: it renders nothing at 0, and the localised \"N selected\" + a labelled region above 0.\n// The Checkbox's inline label is the item name: the name is the click target and the\n// accessible name. Do NOT make the whole Card an onClick div.\nimport * as React from \"react\";\nimport { FolderOpen, Trash2 } from \"lucide-react\";\nimport { Card, CardContent, DataTable, EmptyState } from \"@godxjp/ui/data-display\";\nimport { Checkbox } from \"@godxjp/ui/data-entry\";\nimport { AlertDialog, Toaster, toast } from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Flex, ResponsiveGrid } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\ntype Item = { id: string; name: string };\n\nexport function SelectableCardGrid({ source }: { source: Item[] }) {\n const { t } = useTranslation();\n const [items, setItems] = React.useState(source);\n const [selected, setSelected] = React.useState<Set<string>>(new Set());\n const [confirmOpen, setConfirmOpen] = React.useState(false);\n const [confirmCount, setConfirmCount] = React.useState(0); // frozen: selection clears on confirm\n\n const all = items.length > 0 && items.every((i) => selected.has(i.id));\n const selectAll: boolean | \"indeterminate\" = all ? true : selected.size > 0 ? \"indeterminate\" : false;\n\n const deleteSelected = () => {\n const removed = new Set(selected);\n setItems((cur) => cur.filter((i) => !removed.has(i.id)));\n setSelected(new Set());\n toast(t(\"files.deleted\", { count: removed.size }), {\n action: {\n label: t(\"files.undo\"),\n // restore in the SOURCE order, not appended at the end\n onClick: () => setItems((cur) => source.filter((i) => removed.has(i.id) || cur.some((c) => c.id === i.id))),\n },\n });\n };\n\n if (items.length === 0) {\n return (\n <EmptyState\n icon={FolderOpen}\n variant=\"section\"\n title={t(\"files.emptyTitle\")}\n action={<Button variant=\"outline\" onClick={() => setItems(source)}>{t(\"files.reset\")}</Button>}\n />\n );\n }\n\n return (\n <Flex direction=\"col\" gap=\"md\">\n <Flex direction=\"row\" align=\"center\" gap=\"md\" wrap>\n <Checkbox\n checked={selectAll}\n onCheckedChange={(next) => setSelected(next === true ? new Set(items.map((i) => i.id)) : new Set())}\n >\n {t(\"files.selectAll\")}\n </Checkbox>\n <DataTable.BulkActions count={selected.size}>\n <Button variant=\"destructive\" size=\"sm\" onClick={() => { setConfirmCount(selected.size); setConfirmOpen(true); }}>\n <Trash2 aria-hidden=\"true\" />\n {t(\"files.delete\")}\n </Button>\n </DataTable.BulkActions>\n </Flex>\n <ResponsiveGrid columns={{ base: 1, sm: 2, lg: 3 }}>\n {items.map((item) => (\n <Card key={item.id} className={selected.has(item.id) ? \"border-primary\" : undefined}>\n <CardContent>\n <Checkbox\n checked={selected.has(item.id)}\n onCheckedChange={(on) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (on === true) next.add(item.id); else next.delete(item.id);\n return next;\n })\n }\n >\n {item.name}\n </Checkbox>\n </CardContent>\n </Card>\n ))}\n </ResponsiveGrid>\n <AlertDialog\n open={confirmOpen}\n onOpenChange={setConfirmOpen}\n variant=\"destructive\"\n title={t(\"files.confirmTitle\", { count: confirmCount })}\n confirmLabel={t(\"files.delete\")}\n onConfirm={deleteSelected}\n />\n <Toaster />{/* once per app — usually in the root layout, not here */}\n </Flex>\n );\n}",
9
+ "name": "card-grid-selection",
10
+ "tagline": "Select cards in a grid and act on them in bulk — Card + Checkbox per item, the real DataTable.BulkActions driven by count outside a table, indeterminate select-all, AlertDialog before delete, toast with Undo after, EmptyState when nothing is left.",
11
+ "tags": [
12
+ "selection",
13
+ "bulk",
14
+ "cards",
15
+ "grid",
16
+ "delete",
17
+ "undo",
18
+ "empty",
19
+ "checkbox"
20
+ ]
21
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "aliases": [
3
+ "cascading-selects",
4
+ "country-state-city",
5
+ "chained-selects",
6
+ "linked-selects"
7
+ ],
8
+ "code": "// Live page: /showcase/form-dependent-selects (docs/showcase/form-dependent-selects.tsx).\n// Three rules: (1) child disabled until parent has a value + helper saying why;\n// (2) child loading while its options fetch; (3) parent change resets EVERY descendant to \"\".\n// Reset to \"\", Select's controlled empty value — undefined flips it to uncontrolled.\nimport * as React from \"react\";\nimport { Form, FormField, Select } from \"@godxjp/ui/data-entry\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\ntype Option = { value: string; label: string };\ntype Address = { country: string; region: string; city: string };\n\n// Fetch options for a parent value. The answer is stored WITH the parent it answers, so\n// `loading` is derived and a slow answer for an OLD parent is never shown on the new one.\nfunction useChildOptions(parent: string, fetchOptions: (parent: string) => Promise<Option[]>) {\n const [answer, setAnswer] = React.useState<{ parent: string; options: Option[] }>({ parent: \"\", options: [] });\n React.useEffect(() => {\n if (!parent) return;\n let live = true;\n void fetchOptions(parent).then((options) => { if (live) setAnswer({ parent, options }); });\n return () => { live = false; };\n }, [parent, fetchOptions]);\n const ready = parent !== \"\" && answer.parent === parent;\n return { options: ready ? answer.options : [], loading: parent !== \"\" && !ready };\n}\n\nexport function AddressFields({\n fetchRegions,\n fetchCities,\n}: {\n fetchRegions: (country: string) => Promise<Option[]>; // stable (module-level or useCallback)\n fetchCities: (region: string) => Promise<Option[]>;\n}) {\n const { t, locale } = useTranslation();\n const [value, setValue] = React.useState<Address>({ country: \"\", region: \"\", city: \"\" });\n const regions = useChildOptions(value.country, fetchRegions);\n const cities = useChildOptions(value.region, fetchCities);\n\n // ISO 3166-1 alpha-2 → the reader's language, no catalogue needed.\n const countries = React.useMemo(() => {\n const names = new Intl.DisplayNames([locale], { type: \"region\" });\n return [\"JP\", \"US\", \"VN\"].map((code) => ({ value: code, label: names.of(code) ?? code }));\n }, [locale]);\n\n return (\n <Form controlWidth=\"20rem\">\n <Flex direction=\"col\" gap=\"md\">\n <FormField id=\"country\" label={t(\"address.country\")}>\n <Select\n value={value.country}\n onValueChange={(country: string) => setValue({ country, region: \"\", city: \"\" })}\n options={countries}\n placeholder={t(\"address.countryPlaceholder\")}\n />\n </FormField>\n <FormField\n id=\"region\"\n label={t(\"address.region\")}\n helper={value.country ? undefined : t(\"address.selectCountryFirst\")}\n >\n <Select\n value={value.region}\n onValueChange={(region: string) => setValue({ ...value, region, city: \"\" })}\n options={regions.options}\n disabled={!value.country}\n loading={regions.loading}\n placeholder={t(\"address.regionPlaceholder\")}\n />\n </FormField>\n <FormField\n id=\"city\"\n label={t(\"address.city\")}\n helper={value.region ? undefined : t(\"address.selectRegionFirst\")}\n >\n <Select\n value={value.city}\n onValueChange={(city: string) => setValue({ ...value, city })}\n options={cities.options}\n disabled={!value.region}\n loading={cities.loading}\n placeholder={t(\"address.cityPlaceholder\")}\n />\n </FormField>\n </Flex>\n </Form>\n );\n}",
9
+ "name": "dependent-selects",
10
+ "tagline": "Country → state → city: three Selects where each child is disabled until its parent has a value (FormField helper says why), loading while its options fetch, and reset to \"\" when the parent changes. A composition, not a component — reach for Cascader only when it is ONE field.",
11
+ "tags": [
12
+ "form",
13
+ "select",
14
+ "dependent",
15
+ "cascading",
16
+ "address",
17
+ "loading",
18
+ "reset"
19
+ ]
20
+ }