@godxjp/ui 33.10.1 → 33.11.2
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.
- package/DESIGN.md +1 -0
- package/README.md +2 -2
- package/agent/DESIGN.md +1 -0
- package/agent/START-HERE.md +4 -4
- package/agent/components/Cascader.json +1 -0
- package/agent/components/DatePicker.json +1 -0
- package/agent/components/Input.json +1 -0
- package/agent/components/NumberInput.json +1 -0
- package/agent/components/PasswordInput.json +1 -0
- package/agent/components/SearchInput.json +1 -0
- package/agent/components/Select.json +1 -0
- package/agent/components/TimePicker.json +1 -0
- package/agent/components/Topbar.json +8 -2
- package/agent/components/TopbarItem.json +7 -1
- package/agent/components/TreeSelect.json +1 -0
- package/agent/components.json +24 -3
- package/agent/index.json +4 -4
- package/agent/llms.txt +5 -5
- package/agent/tokens.json +19 -1
- package/dist/components/data-entry/input.js +16 -1
- package/dist/components/data-entry/search-input.js +1 -1
- package/dist/components/layout/topbar-item.d.ts +1 -0
- package/dist/components/layout/topbar-item.js +9 -4
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +25 -3
- package/dist/contracts/measurement.json +1 -1
- package/dist/props/components/layout.prop.d.ts +13 -0
- package/dist/props/registry.d.ts +9 -1
- package/dist/props/registry.js +10 -0
- package/dist/styles/control.css +131 -3
- package/dist/styles/layers.json +1 -1
- package/dist/styles/shell-layout.css +38 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/components/control.css +4 -1
- package/dist/tokens/components/shell.css +3 -0
- package/docs/TOKENS.md +11 -0
- package/package.json +2 -2
- package/scripts/visual-audit.mjs +33 -3
package/DESIGN.md
CHANGED
|
@@ -195,6 +195,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
|
|
|
195
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
|
+
- Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.
|
|
198
199
|
- Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
|
|
199
200
|
- A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.
|
|
200
201
|
|
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 — 169 components,
|
|
21
|
+
The catalog — 169 components, 2238 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
|
|
|
@@ -56,7 +56,7 @@ static files straight from this public repo — no hosting, no deploy step:
|
|
|
56
56
|
| [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 52 KB | all 169 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.3 MB | all 169 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) |
|
|
59
|
+
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 858 KB | all 2238 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
61
|
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 23 KB | the 44 cardinal rules |
|
|
62
62
|
<!-- prettier-ignore-end -->
|
package/agent/DESIGN.md
CHANGED
|
@@ -195,6 +195,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
|
|
|
195
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
|
+
- Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.
|
|
198
199
|
- Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
|
|
199
200
|
- A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.
|
|
200
201
|
|
package/agent/START-HERE.md
CHANGED
|
@@ -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` 33.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 33.11.2.** 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.
|
|
@@ -61,10 +61,10 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
|
|
|
61
61
|
its `importPath`, and its examples. Fetch only the handful you picked in step 1.
|
|
62
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` —
|
|
64
|
+
4. `tokens.json` — 2238 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
|
-
|
|
67
|
+
1909 `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` |
|
|
153
|
+
| `component` | 1909 | 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.
|
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
"storyPath": "data-entry/cascader.tsx",
|
|
197
197
|
"tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
|
|
198
198
|
"usage": [
|
|
199
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
199
200
|
"DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
|
|
200
201
|
"DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both — providing value without onChange makes the field read-only (the internal state won't update).",
|
|
201
202
|
"DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.",
|
|
@@ -264,6 +264,7 @@
|
|
|
264
264
|
"storyPath": "data-entry/date-picker.tsx",
|
|
265
265
|
"tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
|
|
266
266
|
"usage": [
|
|
267
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
267
268
|
"ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
|
|
268
269
|
"DO use `name` to make the field form-submittable — it emits ISO-8601 at the picker's own precision (`2026-03` for a month, `2026` for a year, `2026-03-01` for a day). No hidden input of your own is needed.",
|
|
269
270
|
"DO wrap it in FormField like every other labelled control; FormField injects the id/aria wiring onto the input.",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"storyPath": "data-entry/input.tsx",
|
|
106
106
|
"tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
|
|
107
107
|
"usage": [
|
|
108
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The trailing slot is measured, so adjacent actions and counters reserve their combined width; allowClear still replaces trailingIcon/suffix.",
|
|
108
109
|
"DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
|
|
109
110
|
"DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
|
|
110
111
|
"DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
|
|
@@ -140,6 +140,7 @@
|
|
|
140
140
|
"storyPath": "data-entry/number-input.tsx",
|
|
141
141
|
"tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
|
|
142
142
|
"usage": [
|
|
143
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The increment/decrement actions sit side by side on touch screens so each owns a non-overlapping target.",
|
|
143
144
|
"DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
|
|
144
145
|
"DO drive it controlled with `value` + `onValueChange` carrying `number | null` (the vocabulary triad — NOT `onChange`). `null` means the field is empty; never substitute 0 for empty.",
|
|
145
146
|
"DON'T pass `value` without `onValueChange` — like every controlled @godxjp/ui input it would freeze. Omit both for uncontrolled (use `defaultValue`).",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"storyPath": "data-entry/password-input.tsx",
|
|
68
68
|
"tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
|
|
69
69
|
"usage": [
|
|
70
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
70
71
|
"DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
|
|
71
72
|
"DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
|
|
72
73
|
"DON'T add your own eye button — it's built in (and excluded from the tab order)."
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"storyPath": "data-entry/search-input.tsx",
|
|
93
93
|
"tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
|
|
94
94
|
"usage": [
|
|
95
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
95
96
|
"DON'T drop a bare SearchInput into a Topbar slot — use `TopbarSearch` (layout), which passes every SearchInput prop through and folds to a search cell below 40rem instead of shrinking under 8ch (ADR C02).",
|
|
96
97
|
"DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
|
|
97
98
|
"DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
|
|
@@ -398,6 +398,7 @@
|
|
|
398
398
|
],
|
|
399
399
|
"tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
|
|
400
400
|
"usage": [
|
|
401
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
401
402
|
"FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
|
|
402
403
|
"DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
|
|
403
404
|
"DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
|
|
@@ -181,6 +181,7 @@
|
|
|
181
181
|
"storyPath": "data-entry/time-picker.tsx",
|
|
182
182
|
"tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
|
|
183
183
|
"usage": [
|
|
184
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
184
185
|
"One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
|
|
185
186
|
"DO give it a name in native forms. It submits HH:mm or HH:mm:ss, regardless of display format; the component manages any hidden canonical field.",
|
|
186
187
|
"DO use the controlled pattern (`value` + `onValueChange`) in React-managed forms (e.g. useForm). For simple HTML forms without React state, omit `value` and use `defaultValue` for the uncontrolled pattern.",
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "Topbar",
|
|
6
6
|
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Opt-in pinned shared-device identity: one TopbarItem truncate with Avatar in icon and a string name. Capped at --topbar-identity-max-inline-size (40%); --topbar-identity-min-text-inline-size reserves 3ch. Stays outside scroll/menu while primary cells retain their minima. Only other clusters follow overflow; children overrides all slots. Do not combine identity with hideBelow, labelHideBelow or iconHideFrom. At extreme widths choose overflow=\"menu\" to keep other controls reachable.",
|
|
9
|
+
"name": "identity",
|
|
10
|
+
"type": "ReactNode"
|
|
11
|
+
},
|
|
7
12
|
{
|
|
8
13
|
"description": "Instance inset using logical token steps.",
|
|
9
14
|
"name": "pad",
|
|
@@ -36,11 +41,11 @@
|
|
|
36
41
|
"defaultValue": "\"scroll\"",
|
|
37
42
|
"description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
|
|
38
43
|
"name": "overflow",
|
|
39
|
-
"type": "\"scroll\" | \"clip\""
|
|
44
|
+
"type": "\"scroll\" | \"clip\" | \"menu\""
|
|
40
45
|
},
|
|
41
46
|
{
|
|
42
47
|
"defaultValue": "undefined",
|
|
43
|
-
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored.",
|
|
48
|
+
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end`/`identity` are ignored.",
|
|
44
49
|
"name": "children",
|
|
45
50
|
"type": "ReactNode"
|
|
46
51
|
},
|
|
@@ -65,6 +70,7 @@
|
|
|
65
70
|
"storyPath": "layout/topbar.tsx",
|
|
66
71
|
"tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
|
|
67
72
|
"usage": [
|
|
73
|
+
"For shared devices use identity={<TopbarItem truncate icon={<Avatar aria-hidden=\"true\"><AvatarFallback>DB</AvatarFallback></Avatar>}>{user.name}</TopbarItem>}, optionally wrapped in DropdownMenuTrigger asChild. Keep the locale picker icon-only with appearance=\"bar\" in end. The name stays visible and bounded; full string children become title and accessible text. AccountChip is a separate avatar/name/action row, not a full-height menu cell.",
|
|
68
74
|
"DO use `TopbarSearch`, not a bare `SearchInput`, for the bar's search (ADR C02): a SearchInput in a slot shrinks with the cluster — measured at 390px in a consumer bar its text area was 0ch. TopbarSearch keeps at least 8ch from 40rem up and folds to a named search cell below it.",
|
|
69
75
|
"DO align the bar's search with the content column (ADR C11) by composition: keep AppShell's default `topbarSpan=\"content\"` and put `TopbarSearch` FIRST in `start` — the bar then starts on the rail edge with the same inset as PageContainer, measured 0px apart at 1024/1280/1440. Under `topbarSpan=\"full\"` the bar starts at the window edge after the brand, so no keyline is shared; there, put the search in `end` deliberately rather than near a column it cannot meet.",
|
|
70
76
|
"DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "TopbarItem",
|
|
6
6
|
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "false",
|
|
9
|
+
"description": "Same vocabulary as Text.truncate: allow the cell to shrink to its control minimum and ellipse only its label. Keeps the icon; overrides labelHideBelow. Full text remains accessible; string children supply title automatically (including asChild). Pass title explicitly for rich children. Use in Topbar identity for a bounded always-visible shared-device name; default phone accounts still use labelHideBelow=\"sm\".",
|
|
10
|
+
"name": "truncate",
|
|
11
|
+
"type": "boolean"
|
|
12
|
+
},
|
|
7
13
|
{
|
|
8
14
|
"description": "The cell's leading glyph, placed in a slot the CELL sizes (--topbar-icon-size). Prefer it over passing the glyph as a child whenever the glyph is wrapped: `.ui-topbar-item > svg` only reaches a DIRECT child, so a glyph inside a `<Flex hideBelow>` renders at lucide's intrinsic 24px — measured at 390px on an org-switcher cell, 1.5x the cell's step (gh#712). Decorative by convention (the cell's name comes from its own aria-label); pass <Icon as={…} size=… label=… /> here when the glyph needs a name or a different step. Rendered with asChild too (gh#726): the sized slot is inserted INSIDE the child element, ahead of that element's own children, and the child element (e.g. an <a>) stays the rendered root with every merged prop.",
|
|
9
15
|
"name": "icon",
|
|
@@ -65,7 +71,7 @@
|
|
|
65
71
|
"storyPath": "layout/topbar-item.tsx",
|
|
66
72
|
"tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
|
|
67
73
|
"usage": [
|
|
68
|
-
"
|
|
74
|
+
"By default collapse the account cell to its avatar on a phone (ADR C04); shared devices opt into Topbar identity + TopbarItem truncate instead: `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
|
|
69
75
|
"DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
|
|
70
76
|
"DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
|
|
71
77
|
"DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
|
|
@@ -214,6 +214,7 @@
|
|
|
214
214
|
"storyPath": "data-entry/tree-select.tsx",
|
|
215
215
|
"tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
|
|
216
216
|
"usage": [
|
|
217
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
217
218
|
"DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
|
|
218
219
|
"DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
|
|
219
220
|
"DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.",
|
package/agent/components.json
CHANGED
|
@@ -1689,6 +1689,11 @@
|
|
|
1689
1689
|
"importPath": "@godxjp/ui/layout",
|
|
1690
1690
|
"name": "Topbar",
|
|
1691
1691
|
"props": [
|
|
1692
|
+
{
|
|
1693
|
+
"description": "Opt-in pinned shared-device identity: one TopbarItem truncate with Avatar in icon and a string name. Capped at --topbar-identity-max-inline-size (40%); --topbar-identity-min-text-inline-size reserves 3ch. Stays outside scroll/menu while primary cells retain their minima. Only other clusters follow overflow; children overrides all slots. Do not combine identity with hideBelow, labelHideBelow or iconHideFrom. At extreme widths choose overflow=\"menu\" to keep other controls reachable.",
|
|
1694
|
+
"name": "identity",
|
|
1695
|
+
"type": "ReactNode"
|
|
1696
|
+
},
|
|
1692
1697
|
{
|
|
1693
1698
|
"description": "Instance inset using logical token steps.",
|
|
1694
1699
|
"name": "pad",
|
|
@@ -1721,11 +1726,11 @@
|
|
|
1721
1726
|
"defaultValue": "\"scroll\"",
|
|
1722
1727
|
"description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
|
|
1723
1728
|
"name": "overflow",
|
|
1724
|
-
"type": "\"scroll\" | \"clip\""
|
|
1729
|
+
"type": "\"scroll\" | \"clip\" | \"menu\""
|
|
1725
1730
|
},
|
|
1726
1731
|
{
|
|
1727
1732
|
"defaultValue": "undefined",
|
|
1728
|
-
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored.",
|
|
1733
|
+
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end`/`identity` are ignored.",
|
|
1729
1734
|
"name": "children",
|
|
1730
1735
|
"type": "ReactNode"
|
|
1731
1736
|
},
|
|
@@ -1750,6 +1755,7 @@
|
|
|
1750
1755
|
"storyPath": "layout/topbar.tsx",
|
|
1751
1756
|
"tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
|
|
1752
1757
|
"usage": [
|
|
1758
|
+
"For shared devices use identity={<TopbarItem truncate icon={<Avatar aria-hidden=\"true\"><AvatarFallback>DB</AvatarFallback></Avatar>}>{user.name}</TopbarItem>}, optionally wrapped in DropdownMenuTrigger asChild. Keep the locale picker icon-only with appearance=\"bar\" in end. The name stays visible and bounded; full string children become title and accessible text. AccountChip is a separate avatar/name/action row, not a full-height menu cell.",
|
|
1753
1759
|
"DO use `TopbarSearch`, not a bare `SearchInput`, for the bar's search (ADR C02): a SearchInput in a slot shrinks with the cluster — measured at 390px in a consumer bar its text area was 0ch. TopbarSearch keeps at least 8ch from 40rem up and folds to a named search cell below it.",
|
|
1754
1760
|
"DO align the bar's search with the content column (ADR C11) by composition: keep AppShell's default `topbarSpan=\"content\"` and put `TopbarSearch` FIRST in `start` — the bar then starts on the rail edge with the same inset as PageContainer, measured 0px apart at 1024/1280/1440. Under `topbarSpan=\"full\"` the bar starts at the window edge after the brand, so no keyline is shared; there, put the search in `end` deliberately rather than near a column it cannot meet.",
|
|
1755
1761
|
"DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
|
|
@@ -1775,6 +1781,12 @@
|
|
|
1775
1781
|
"importPath": "@godxjp/ui/layout",
|
|
1776
1782
|
"name": "TopbarItem",
|
|
1777
1783
|
"props": [
|
|
1784
|
+
{
|
|
1785
|
+
"defaultValue": "false",
|
|
1786
|
+
"description": "Same vocabulary as Text.truncate: allow the cell to shrink to its control minimum and ellipse only its label. Keeps the icon; overrides labelHideBelow. Full text remains accessible; string children supply title automatically (including asChild). Pass title explicitly for rich children. Use in Topbar identity for a bounded always-visible shared-device name; default phone accounts still use labelHideBelow=\"sm\".",
|
|
1787
|
+
"name": "truncate",
|
|
1788
|
+
"type": "boolean"
|
|
1789
|
+
},
|
|
1778
1790
|
{
|
|
1779
1791
|
"description": "The cell's leading glyph, placed in a slot the CELL sizes (--topbar-icon-size). Prefer it over passing the glyph as a child whenever the glyph is wrapped: `.ui-topbar-item > svg` only reaches a DIRECT child, so a glyph inside a `<Flex hideBelow>` renders at lucide's intrinsic 24px — measured at 390px on an org-switcher cell, 1.5x the cell's step (gh#712). Decorative by convention (the cell's name comes from its own aria-label); pass <Icon as={…} size=… label=… /> here when the glyph needs a name or a different step. Rendered with asChild too (gh#726): the sized slot is inserted INSIDE the child element, ahead of that element's own children, and the child element (e.g. an <a>) stays the rendered root with every merged prop.",
|
|
1780
1792
|
"name": "icon",
|
|
@@ -1836,7 +1848,7 @@
|
|
|
1836
1848
|
"storyPath": "layout/topbar-item.tsx",
|
|
1837
1849
|
"tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
|
|
1838
1850
|
"usage": [
|
|
1839
|
-
"
|
|
1851
|
+
"By default collapse the account cell to its avatar on a phone (ADR C04); shared devices opt into Topbar identity + TopbarItem truncate instead: `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
|
|
1840
1852
|
"DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
|
|
1841
1853
|
"DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
|
|
1842
1854
|
"DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
|
|
@@ -6221,6 +6233,7 @@
|
|
|
6221
6233
|
"storyPath": "data-entry/input.tsx",
|
|
6222
6234
|
"tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
|
|
6223
6235
|
"usage": [
|
|
6236
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The trailing slot is measured, so adjacent actions and counters reserve their combined width; allowClear still replaces trailingIcon/suffix.",
|
|
6224
6237
|
"DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
|
|
6225
6238
|
"DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
|
|
6226
6239
|
"DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
|
|
@@ -6378,6 +6391,7 @@
|
|
|
6378
6391
|
"storyPath": "data-entry/number-input.tsx",
|
|
6379
6392
|
"tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
|
|
6380
6393
|
"usage": [
|
|
6394
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The increment/decrement actions sit side by side on touch screens so each owns a non-overlapping target.",
|
|
6381
6395
|
"DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
|
|
6382
6396
|
"DO drive it controlled with `value` + `onValueChange` carrying `number | null` (the vocabulary triad — NOT `onChange`). `null` means the field is empty; never substitute 0 for empty.",
|
|
6383
6397
|
"DON'T pass `value` without `onValueChange` — like every controlled @godxjp/ui input it would freeze. Omit both for uncontrolled (use `defaultValue`).",
|
|
@@ -6488,6 +6502,7 @@
|
|
|
6488
6502
|
"storyPath": "data-entry/search-input.tsx",
|
|
6489
6503
|
"tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
|
|
6490
6504
|
"usage": [
|
|
6505
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
6491
6506
|
"DON'T drop a bare SearchInput into a Topbar slot — use `TopbarSearch` (layout), which passes every SearchInput prop through and folds to a search cell below 40rem instead of shrinking under 8ch (ADR C02).",
|
|
6492
6507
|
"DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
|
|
6493
6508
|
"DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
|
|
@@ -7028,6 +7043,7 @@
|
|
|
7028
7043
|
],
|
|
7029
7044
|
"tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
|
|
7030
7045
|
"usage": [
|
|
7046
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
7031
7047
|
"FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
|
|
7032
7048
|
"DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
|
|
7033
7049
|
"DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
|
|
@@ -7995,6 +8011,7 @@
|
|
|
7995
8011
|
"storyPath": "data-entry/date-picker.tsx",
|
|
7996
8012
|
"tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
|
|
7997
8013
|
"usage": [
|
|
8014
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
7998
8015
|
"ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
|
|
7999
8016
|
"DO use `name` to make the field form-submittable — it emits ISO-8601 at the picker's own precision (`2026-03` for a month, `2026` for a year, `2026-03-01` for a day). No hidden input of your own is needed.",
|
|
8000
8017
|
"DO wrap it in FormField like every other labelled control; FormField injects the id/aria wiring onto the input.",
|
|
@@ -9653,6 +9670,7 @@
|
|
|
9653
9670
|
"storyPath": "data-entry/time-picker.tsx",
|
|
9654
9671
|
"tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
|
|
9655
9672
|
"usage": [
|
|
9673
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
9656
9674
|
"One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
|
|
9657
9675
|
"DO give it a name in native forms. It submits HH:mm or HH:mm:ss, regardless of display format; the component manages any hidden canonical field.",
|
|
9658
9676
|
"DO use the controlled pattern (`value` + `onValueChange`) in React-managed forms (e.g. useForm). For simple HTML forms without React state, omit `value` and use `defaultValue` for the uncontrolled pattern.",
|
|
@@ -9868,6 +9886,7 @@
|
|
|
9868
9886
|
"storyPath": "data-entry/cascader.tsx",
|
|
9869
9887
|
"tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
|
|
9870
9888
|
"usage": [
|
|
9889
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
9871
9890
|
"DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
|
|
9872
9891
|
"DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both — providing value without onChange makes the field read-only (the internal state won't update).",
|
|
9873
9892
|
"DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.",
|
|
@@ -10100,6 +10119,7 @@
|
|
|
10100
10119
|
"storyPath": "data-entry/tree-select.tsx",
|
|
10101
10120
|
"tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
|
|
10102
10121
|
"usage": [
|
|
10122
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
10103
10123
|
"DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
|
|
10104
10124
|
"DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
|
|
10105
10125
|
"DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.",
|
|
@@ -12756,6 +12776,7 @@
|
|
|
12756
12776
|
"storyPath": "data-entry/password-input.tsx",
|
|
12757
12777
|
"tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
|
|
12758
12778
|
"usage": [
|
|
12779
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
12759
12780
|
"DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
|
|
12760
12781
|
"DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
|
|
12761
12782
|
"DON'T add your own eye button — it's built in (and excluded from the tab order)."
|
package/agent/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"components": 169,
|
|
5
5
|
"patterns": 28,
|
|
6
6
|
"rules": 44,
|
|
7
|
-
"tokens":
|
|
7
|
+
"tokens": 2238,
|
|
8
8
|
"vocabulary": 14
|
|
9
9
|
},
|
|
10
10
|
"design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
|
|
@@ -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/v33.
|
|
57
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.11.2/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":
|
|
64
|
+
"component": 1909,
|
|
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": "33.
|
|
71
|
+
"version": "33.11.2"
|
|
72
72
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system: 169 components,
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 169 components, 2238 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.11.2.
|
|
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@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.11.2`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -19,7 +19,7 @@ that can only fetch URLs.
|
|
|
19
19
|
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 52 KB — all 169 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
20
20
|
- [components/<Name>.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.3 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,
|
|
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, 1909 `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
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.
|
|
@@ -27,7 +27,7 @@ that can only fetch URLs.
|
|
|
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/v33.
|
|
30
|
+
the tag: `.../godx-jp/godxjp-ui/v33.11.2/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.
|
package/agent/tokens.json
CHANGED
|
@@ -4067,6 +4067,12 @@
|
|
|
4067
4067
|
"tier": "component",
|
|
4068
4068
|
"value": "1.5rem"
|
|
4069
4069
|
},
|
|
4070
|
+
{
|
|
4071
|
+
"description": "Shared coarse-pointer floor; independent of density and the desktop glyph size.",
|
|
4072
|
+
"name": "--control-affix-touch-target-size",
|
|
4073
|
+
"tier": "component",
|
|
4074
|
+
"value": "var(--band-height-xl)"
|
|
4075
|
+
},
|
|
4070
4076
|
{
|
|
4071
4077
|
"description": "gh#888 — φ tier freezes at :root; default = calc(var(--radius) / var(--radius-ratio) / var(--radius-ratio))",
|
|
4072
4078
|
"name": "--control-affix-action-radius",
|
|
@@ -5139,7 +5145,7 @@
|
|
|
5139
5145
|
"description": "SearchInput's text stops at the clear ✕'s 44px TARGET, not at its 24px box (gh#1148): edge inset + half the box (where the glyph's centre is) + half the target.",
|
|
5140
5146
|
"name": "--search-input-end-padding",
|
|
5141
5147
|
"tier": "component",
|
|
5142
|
-
"value": "calc( var(--search-input-edge-inset) + var(--touch-target-min) / 2 + var(--
|
|
5148
|
+
"value": "calc( var(--search-input-edge-inset) + var(--touch-target-min) / 2 + var(--control-affix-touch-target-size) / 2 )"
|
|
5143
5149
|
},
|
|
5144
5150
|
{
|
|
5145
5151
|
"description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes.",
|
|
@@ -10895,6 +10901,18 @@
|
|
|
10895
10901
|
"tier": "component",
|
|
10896
10902
|
"value": "var(--space-2)"
|
|
10897
10903
|
},
|
|
10904
|
+
{
|
|
10905
|
+
"description": "Shared-device identity gets a bounded share, not the primary controls' width.",
|
|
10906
|
+
"name": "--topbar-identity-max-inline-size",
|
|
10907
|
+
"tier": "component",
|
|
10908
|
+
"value": "40%"
|
|
10909
|
+
},
|
|
10910
|
+
{
|
|
10911
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10912
|
+
"name": "--topbar-identity-min-text-inline-size",
|
|
10913
|
+
"tier": "component",
|
|
10914
|
+
"value": "3ch"
|
|
10915
|
+
},
|
|
10898
10916
|
{
|
|
10899
10917
|
"description": "THE GLASS BAR (gh#831) — the same two declarations two unrelated website showcases each hand-wrote on their own sticky header: a translucent page fill plus a backdrop blur. Both knobs are `initial`, i.e. QUIET by default in exactly the sense the paragraph above describes: a guaranteed-invalid value makes the whole declaration at the call site invalid-at-computed-value time, so `background-color` falls back to its own initial `transparent` and `backdrop-filter` to `none`. The shipped bar therefore paints nothing, creates no backdrop root, and is byte-identical to the pure slot bar it was. A service opts in with `--topbar-background-alpha: 85%` / `--topbar-backdrop-blur-size: 10px`. The TINT is NOT a knob: `.ui-topbar` reads `--background` directly at the call site, so a dark or tenant-scoped subtree re-tints the glass instead of inheriting :root's answer (docs/TOKENS.md · the `:root` freeze rule). Precedent for the blur name: --app-launcher-launchpad-backdrop-blur-size. default = none — the bar paints no fill",
|
|
10900
10918
|
"name": "--topbar-background-alpha",
|
|
@@ -72,6 +72,21 @@ const Input = React.forwardRef(
|
|
|
72
72
|
},
|
|
73
73
|
[ref]
|
|
74
74
|
);
|
|
75
|
+
const setTrailingRef = React.useCallback((node) => {
|
|
76
|
+
if (!node) return;
|
|
77
|
+
const wrapper = node.parentElement;
|
|
78
|
+
const sync = () => wrapper.style.setProperty(
|
|
79
|
+
"--input-trailing-measured-size",
|
|
80
|
+
`${node.getBoundingClientRect().width}px`
|
|
81
|
+
);
|
|
82
|
+
sync();
|
|
83
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(sync);
|
|
84
|
+
observer?.observe(node);
|
|
85
|
+
return () => {
|
|
86
|
+
observer?.disconnect();
|
|
87
|
+
wrapper.style.removeProperty("--input-trailing-measured-size");
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
75
90
|
const [text, setText] = React.useState(() => String(value ?? defaultValue ?? ""));
|
|
76
91
|
const hasText = text.length > 0;
|
|
77
92
|
React.useEffect(() => {
|
|
@@ -167,7 +182,7 @@ const Input = React.forwardRef(
|
|
|
167
182
|
...appearance
|
|
168
183
|
}
|
|
169
184
|
),
|
|
170
|
-
trailing != null || counter !== null ? /* @__PURE__ */ jsxs("span", { className: "ui-input-trailing", children: [
|
|
185
|
+
trailing != null || counter !== null ? /* @__PURE__ */ jsxs("span", { className: "ui-input-trailing", ref: setTrailingRef, children: [
|
|
171
186
|
counter ? /* @__PURE__ */ jsx(
|
|
172
187
|
"span",
|
|
173
188
|
{
|
|
@@ -104,7 +104,7 @@ function SearchInput({
|
|
|
104
104
|
// gh#1250 — while the key cap shows, the text stops before it instead of running under it.
|
|
105
105
|
// gh#1285 — written FROM --search-input-end-padding (minus the ✕ slot it reserves, plus the
|
|
106
106
|
// cap), so a theme that retunes that token still moves this padding (default: edge inset + 6ch, as before).
|
|
107
|
-
showShortcut ? "!pe-[calc(var(--search-input-end-padding)_-_var(--control-icon-size)_-_var(--control-gap)_+_6ch)]" :
|
|
107
|
+
showShortcut ? "!pe-[calc(var(--search-input-end-padding)_-_var(--control-icon-size)_-_var(--control-gap)_+_6ch)]" : void 0,
|
|
108
108
|
inputClassName
|
|
109
109
|
),
|
|
110
110
|
disabled
|
|
@@ -15,6 +15,7 @@ export declare const TopbarItem: React.ForwardRefExoticComponent<Omit<React.Butt
|
|
|
15
15
|
icon?: React.ReactNode;
|
|
16
16
|
iconHideFrom?: import("../../props/index.js").BreakpointProp;
|
|
17
17
|
labelHideBelow?: import("../../props/index.js").BreakpointProp;
|
|
18
|
+
truncate?: boolean;
|
|
18
19
|
badge?: React.ReactNode;
|
|
19
20
|
badgeTone?: import("../../props/components/layout.prop.js").SidebarBadgeToneProp;
|
|
20
21
|
asChild?: boolean;
|
|
@@ -10,6 +10,7 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
10
10
|
icon,
|
|
11
11
|
iconHideFrom,
|
|
12
12
|
labelHideBelow,
|
|
13
|
+
truncate = false,
|
|
13
14
|
badge,
|
|
14
15
|
badgeTone,
|
|
15
16
|
children,
|
|
@@ -26,13 +27,14 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
26
27
|
children: icon
|
|
27
28
|
}
|
|
28
29
|
) : null;
|
|
29
|
-
const labelOf = (
|
|
30
|
+
const labelOf = (label2) => !truncate && labelHideBelow === void 0 || label2 === void 0 || label2 === null || label2 === false ? label2 : /* @__PURE__ */ jsx(
|
|
30
31
|
"span",
|
|
31
32
|
{
|
|
33
|
+
dir: truncate ? "auto" : void 0,
|
|
32
34
|
"data-slot": "topbar-item-label",
|
|
33
35
|
className: "ui-topbar-item-label",
|
|
34
|
-
"data-hide-below": labelHideBelow,
|
|
35
|
-
children:
|
|
36
|
+
"data-hide-below": truncate ? void 0 : labelHideBelow,
|
|
37
|
+
children: label2
|
|
36
38
|
}
|
|
37
39
|
);
|
|
38
40
|
const badgeNode = showBadge ? /* @__PURE__ */ jsx(
|
|
@@ -44,18 +46,21 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
44
46
|
children: badge
|
|
45
47
|
}
|
|
46
48
|
) : null;
|
|
47
|
-
const slotted = asChild && (iconNode !== null || labelHideBelow !== void 0 || badgeNode !== null) && React.isValidElement(children) ? React.cloneElement(
|
|
49
|
+
const slotted = asChild && (iconNode !== null || truncate || labelHideBelow !== void 0 || badgeNode !== null) && React.isValidElement(children) ? React.cloneElement(
|
|
48
50
|
children,
|
|
49
51
|
void 0,
|
|
50
52
|
iconNode,
|
|
51
53
|
labelOf(children.props.children),
|
|
52
54
|
badgeNode
|
|
53
55
|
) : children;
|
|
56
|
+
const label = asChild && React.isValidElement(children) ? children.props.children : children;
|
|
54
57
|
return /* @__PURE__ */ jsx(
|
|
55
58
|
Comp,
|
|
56
59
|
{
|
|
57
60
|
ref,
|
|
58
61
|
"data-slot": "topbar-item",
|
|
62
|
+
"data-truncate": truncate ? "true" : void 0,
|
|
63
|
+
title: truncate && typeof label === "string" ? label : void 0,
|
|
59
64
|
"data-hide-below": hideBelow,
|
|
60
65
|
className: cn("ui-topbar-item ui-focus-ring", className),
|
|
61
66
|
type: asChild ? void 0 : type ?? "button",
|
|
@@ -5,4 +5,4 @@ export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/componen
|
|
|
5
5
|
* Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
|
|
6
6
|
* bar's flex layout; it does NOT bake any chrome.
|
|
7
7
|
*/
|
|
8
|
-
export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
|
|
8
|
+
export declare function Topbar({ start, center, end, identity, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
|