@godxjp/ui 33.24.0 → 33.25.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.
- package/README.md +14 -3
- package/agent/START-HERE.md +5 -5
- package/agent/components/CatalogDetail.json +7 -3
- package/agent/components/DatePicker.json +1 -1
- package/agent/components/DropdownMenu.json +1 -0
- package/agent/components/EditorialCard.json +2 -1
- package/agent/components/Image.json +6 -1
- package/agent/components/Pagination.json +5 -0
- package/agent/components/Popover.json +1 -0
- package/agent/components/Select.json +2 -1
- package/agent/components/Toaster.json +1 -1
- package/agent/components.json +26 -8
- package/agent/index.json +5 -5
- package/agent/llms.txt +6 -6
- package/agent/patterns/notice-bar.json +16 -0
- package/agent/patterns-index.json +15 -0
- package/agent/patterns.json +16 -0
- package/agent/rules.json +2 -2
- package/agent/tokens.json +37 -7
- package/dist/components/charts/chart-cartesian.js +5 -1
- package/dist/components/charts/chart-summary.d.ts +1 -1
- package/dist/components/charts/chart-summary.js +17 -6
- package/dist/components/charts/compact-bar-trend.js +4 -3
- package/dist/components/charts/pie-chart.js +2 -1
- package/dist/components/data-display/image.d.ts +1 -0
- package/dist/components/data-display/image.js +9 -1
- package/dist/components/data-display/popover.js +11 -0
- package/dist/components/data-entry/calendar.js +1 -1
- package/dist/components/data-entry/date-picker.js +22 -5
- package/dist/components/general/button.js +2 -2
- package/dist/components/layout/affix.js +1 -1
- package/dist/components/layout/editorial-catalog.d.ts +1 -1
- package/dist/components/layout/editorial-catalog.js +53 -15
- package/dist/components/navigation/dropdown-menu.d.ts +13 -3
- package/dist/components/navigation/dropdown-menu.js +36 -6
- package/dist/components/navigation/pagination.d.ts +1 -1
- package/dist/components/navigation/pagination.js +13 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +1 -0
- package/dist/i18n/messages/ja.json +1 -0
- package/dist/i18n/messages/vi.json +1 -0
- package/dist/props/components/data-display.prop.d.ts +2 -0
- package/dist/props/components/layout.prop.d.ts +7 -2
- package/dist/props/components/navigation.prop.d.ts +3 -0
- package/dist/props/registry.d.ts +15 -3
- package/dist/props/registry.js +18 -1
- package/dist/styles/application.css +24 -0
- package/dist/styles/base.css +11 -0
- package/dist/styles/control.css +32 -19
- package/dist/styles/core.css +1 -0
- package/dist/styles/data-display-layout.css +7 -0
- package/dist/styles/dialog-layout.css +9 -0
- package/dist/styles/editorial-layout.css +2076 -0
- package/dist/styles/editorial.css +3 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/layers.json +39 -8
- package/dist/styles/layout.css +0 -1987
- package/dist/styles/precompiled/alert-layout.css +2 -0
- package/dist/styles/precompiled/badge-layout.css +2 -0
- package/dist/styles/precompiled/base.css +2 -0
- package/dist/styles/precompiled/card-layout.css +2 -0
- package/dist/styles/precompiled/chart-layout.css +2 -0
- package/dist/styles/precompiled/control.css +2 -0
- package/dist/styles/precompiled/data-display-layout.css +2 -0
- package/dist/styles/precompiled/data-entry-layout.css +2 -0
- package/dist/styles/precompiled/dialog-layout.css +2 -0
- package/dist/styles/precompiled/editorial-account-layout.css +2 -0
- package/dist/styles/precompiled/editorial-layout.css +2 -0
- package/dist/styles/precompiled/float-button-layout.css +2 -0
- package/dist/styles/precompiled/form-layout.css +2 -0
- package/dist/styles/precompiled/icon-layout.css +2 -0
- package/dist/styles/precompiled/layout.css +2 -0
- package/dist/styles/precompiled/logo-layout.css +2 -0
- package/dist/styles/precompiled/motion.css +2 -0
- package/dist/styles/precompiled/navigation-layout.css +2 -0
- package/dist/styles/precompiled/shell-layout.css +2 -0
- package/dist/styles/precompiled/table-layout.css +2 -0
- package/dist/styles/precompiled/text-layout.css +2 -0
- package/dist/styles/precompiled/toggle.css +2 -0
- package/dist/styles/precompiled/vendor-day-picker.css +2 -0
- package/dist/styles/precompiled/vendor-sonner.css +2 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/components/control.css +3 -0
- package/dist/tokens/components/editorial.css +9 -6
- package/docs/CONSUMER-RULES.md +35 -0
- package/package.json +4 -2
- package/scripts/prune-css.mjs +56 -8
- package/scripts/ui-audit.mjs +36 -0
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 — 212 components,
|
|
21
|
+
The catalog — 212 components, 3203 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,9 +56,9 @@ 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) | 47 KB | all 212 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–38 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.4 MB | all 212 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) | 1.4 MB | all
|
|
59
|
+
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 1.4 MB | all 3203 design tokens, their values and why they exist |
|
|
60
60
|
| [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
|
|
61
|
-
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) |
|
|
61
|
+
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 24 KB | the 44 cardinal rules |
|
|
62
62
|
<!-- prettier-ignore-end -->
|
|
63
63
|
<!-- agent-catalog-files:end -->
|
|
64
64
|
|
|
@@ -226,6 +226,17 @@ only when a used component renders them. **Re-run it whenever the set of compone
|
|
|
226
226
|
changes and after every upgrade** — the emitted header says so, and the tool refuses to run against
|
|
227
227
|
a manifest from a different package version.
|
|
228
228
|
|
|
229
|
+
No Tailwind (you import `@godxjp/ui/styles.css`)? Add `--precompiled` (gh#1455):
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npx godxjp-ui prune-css src --out src/godx-ui.css --precompiled
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The emitted file imports the precompiled slices instead (`@import "@godxjp/ui/styles/precompiled/base";` and one per kept layer): the same precompiled CSS as
|
|
236
|
+
`styles.css`, cut per manifest file at build time (`base` = tokens, preflight and the utilities
|
|
237
|
+
the components use; one slice per layer and vendor sheet). Your bundler only has to inline plain
|
|
238
|
+
`@import`s. Import it INSTEAD of `@godxjp/ui/styles.css`.
|
|
239
|
+
|
|
229
240
|
## Golden ratio (φ ≈ 1.618)
|
|
230
241
|
|
|
231
242
|
One token `--phi-unit` drives page/section/card spacing; micro control gaps use the 4px grid. Density (`compact` | `default` | `comfortable`) retunes `--phi-unit` with control + table heights together.
|
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.25.0.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -48,7 +48,7 @@ inspect every screenshot at every width region by region (no top-N cap), and han
|
|
|
48
48
|
→ These files are for you. Read `DESIGN.md` (in this folder) first: tokens, scarcity rules and
|
|
49
49
|
screen anatomy, the visual target the rules below do not give you. Then fetch in this order:
|
|
50
50
|
|
|
51
|
-
0. `patterns-index.json` —
|
|
51
|
+
0. `patterns-index.json` — 29 whole-task patterns as name + tagline + tags. **If your
|
|
52
52
|
task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
|
|
53
53
|
filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
|
|
54
54
|
copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
|
|
@@ -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` — 3203 design tokens, each tagged with its `tier`. **If you were handed a
|
|
65
65
|
brand, read the 223 `foundation` entries first** — `--primary`, `--background`,
|
|
66
66
|
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
67
|
-
|
|
67
|
+
2867 `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` | 223 | 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` | 2867 | 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.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail
|
|
2
|
+
"example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail image={{ src: \"/reference.svg\", alt: \"Reference\", priority: true }} overline={<Eyebrow>Pokémon</Eyebrow>} title={name} meta={<CatalogDetailTag>SAR</CatalogDetailTag>} summary={{ label: \"Market\", price: \"¥32,800\" }} facts={[{ key: \"set\", label: \"Expansion\", value: \"151\" }]} />",
|
|
3
3
|
"group": "layout",
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "CatalogDetail",
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
"type": "\"card\" | \"sealed\""
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "Custom picture; used when image is omitted.",
|
|
15
15
|
"name": "media",
|
|
16
|
-
"required": true,
|
|
17
16
|
"type": "ReactNode"
|
|
18
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Kit Image keyed per src. Art frame exposes data-status loading|loaded|error and aria-busy; token-driven skeleton respects reduced motion. priority requests eager/high priority. Takes precedence over media.",
|
|
20
|
+
"name": "image",
|
|
21
|
+
"type": "{ src: string; alt: string; width?: number | string; height?: number | string; fallback?: string; priority?: boolean }"
|
|
22
|
+
},
|
|
19
23
|
{
|
|
20
24
|
"description": "Save toggle over the frame.",
|
|
21
25
|
"name": "favorite",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"type": "\"date\" | \"week\" | \"month\" | \"quarter\" | \"year\""
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
|
-
"description": "CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
|
|
126
|
+
"description": "With no confirmation, holds the first endpoint internally while open, emits a complete range or clear, and discards half-ranges on close. CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
|
|
127
127
|
"name": "range",
|
|
128
128
|
"type": "boolean"
|
|
129
129
|
},
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
],
|
|
90
90
|
"tagline": "Dropdown menu (react-aria). Compose DropdownMenu/DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/DropdownMenuSeparator. `trigger` picks the gestures — click (default), hover, or contextMenu, which is what replaced the deleted ContextMenu component.",
|
|
91
91
|
"usage": [
|
|
92
|
+
"DO: Use only menu collection nodes inside DropdownMenuContent. Arbitrary headers, notification lists and links belong in Popover + ListRow; development diagnostics and the dropdown-menu-non-menu-children audit rule name this fix.",
|
|
92
93
|
"DO compose the full sub-part tree: DropdownMenu (root) → DropdownMenuTrigger (with asChild to delegate to your Button/icon) → DropdownMenuContent → DropdownMenuItem / DropdownMenuSeparator / DropdownMenuLabel / DropdownMenuGroup. Omitting any level (e.g. rendering DropdownMenuContent without DropdownMenu as ancestor) breaks Radix context and the menu will not open.",
|
|
93
94
|
"DO use DropdownMenuTrigger with asChild and pass a godx-ui Button or icon Button as the child — never render a raw <button> or <div> as the trigger, and never omit asChild when the child is already a button-like element (double-button nesting breaks a11y).",
|
|
94
95
|
"DO use variant='destructive' on DropdownMenuItem for irreversible actions (delete, revoke, void) — this applies the semantic destructive colour token automatically without any className override.",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
],
|
|
66
66
|
"tagline": "A whole-card link (gh#1435). `variant=\"journal\"`: an edge-to-edge cover, category, title, two lines of description and a footer with a circled arrow; the link is named by its title (aria-labelledby). `variant=\"spotlight\"`: kicker, title and a line beside a tilted picture. Pass the link element (router Link or `<a href>`) as `link`; the card content is rendered into it. Geometry: `--editorial-journal-*`, `--editorial-spotlight-*`.",
|
|
67
67
|
"usage": [
|
|
68
|
-
"DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`."
|
|
68
|
+
"DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`.",
|
|
69
|
+
"DO use kit Image with preview={false} for media inside the whole-card link. Spotlight sizes the Image frame to fill its slot, including placeholder and fallback (gh#1474)."
|
|
69
70
|
]
|
|
70
71
|
}
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"importPath": "@godxjp/ui/data-display",
|
|
9
9
|
"name": "Image",
|
|
10
10
|
"props": [
|
|
11
|
+
{
|
|
12
|
+
"description": "Reports resolved status, including cached images; error remains final while showing fallback.",
|
|
13
|
+
"name": "onStatusChange",
|
|
14
|
+
"type": "(status: \"loading\" | \"loaded\" | \"error\") => void"
|
|
15
|
+
},
|
|
11
16
|
{
|
|
12
17
|
"description": "Image URL.",
|
|
13
18
|
"name": "src",
|
|
@@ -29,7 +34,7 @@
|
|
|
29
34
|
"type": "string"
|
|
30
35
|
},
|
|
31
36
|
{
|
|
32
|
-
"description": "
|
|
37
|
+
"description": "After hydration, painted over the frame while the picture loads; SSR leaves pictures uncovered. `true` = a muted block. Child content can shrink to fit the frame.",
|
|
33
38
|
"name": "placeholder",
|
|
34
39
|
"type": "ReactNode | boolean"
|
|
35
40
|
},
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
"importPath": "@godxjp/ui/navigation",
|
|
5
5
|
"name": "Pagination",
|
|
6
6
|
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Pointer enter, focus or touch start on enabled page/previous/next controls, including simple and responsive forms. May repeat: preload/cache the target without changing value. Disabled or out-of-range targets do not emit.",
|
|
9
|
+
"name": "onPageIntent",
|
|
10
|
+
"type": "(page: number) => void"
|
|
11
|
+
},
|
|
7
12
|
{
|
|
8
13
|
"description": "Tên khả truy cập của landmark phân trang. Bắt buộc khi một trang có nhiều bộ phân trang.",
|
|
9
14
|
"name": "ariaLabel",
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
],
|
|
114
114
|
"tagline": "React Aria floating panel anchored to a trigger — always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",
|
|
115
115
|
"usage": [
|
|
116
|
+
"DO: PopoverContent scrolls automatically when its available height caps content. Focus rings remain contained at scroll edges, including flush panels.",
|
|
116
117
|
"HOVER CARD: `<Popover openOn=\"hover\"><PopoverTrigger asChild><a href=…>@yamada</a></PopoverTrigger><PopoverContent>…</PopoverContent></Popover>` for RICH preview content (an avatar + bio, an entity summary). For a short plain-text hint use Tooltip. DON'T put essential information only in a hover card — touch never opens it; make the same content reachable from the trigger's destination. There is no `HoverCard` component.",
|
|
117
118
|
"DO compose: <Popover> → <PopoverTrigger asChild> → <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.",
|
|
118
119
|
"DO set `<PopoverContent width=\"auto\">` when the child brings its own measure — a Calendar, a chart, a fixed-width preview. The panel's own 18rem clips them, and a `w-*` utility on className is a per-call-site constant no service theme can retune, which is exactly what `flush` exists to avoid on the padding axis. `width=\"trigger\"` matches the anchor, the shape every select-like control wants.",
|
|
@@ -268,7 +268,7 @@
|
|
|
268
268
|
"type": "React.ReactNode"
|
|
269
269
|
},
|
|
270
270
|
{
|
|
271
|
-
"description": "antd `popupMatchSelectWidth`. true (default)
|
|
271
|
+
"description": "antd `popupMatchSelectWidth`. true (default): the trigger width is the floor and the popup grows to its longest row, up to --select-content-max-width (32rem) and the viewport less --select-content-viewport-inset, then wraps rows. false lets it hug its rows below the trigger width; a number pins it to that many pixels. The width never depends on where the popup is placed, so it cannot change while open.",
|
|
272
272
|
"name": "popupMatchSelectWidth",
|
|
273
273
|
"type": "boolean | number"
|
|
274
274
|
},
|
|
@@ -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
|
+
"DO: Searchable popup widths, including numeric popupMatchSelectWidth, are capped by --search-select-panel-max-width and --search-select-panel-viewport-inset, using the viewport width excluding classic scrollbars.",
|
|
401
402
|
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
402
403
|
"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.",
|
|
403
404
|
"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.",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"usage": [
|
|
43
43
|
"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.",
|
|
44
44
|
"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).",
|
|
45
|
-
"DON'T: Import `toast` from `\"sonner\"` directly —
|
|
45
|
+
"DON'T: Import `toast` from `\"sonner\"` directly — a consumer-installed version can own a different toast store from the kit Toaster, silently hiding toasts. Use `import { toast } from \"@godxjp/ui\"` (also available from `@godxjp/ui/feedback`); this also preserves close-button defaults. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
|
|
46
46
|
"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.",
|
|
47
47
|
"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.",
|
|
48
48
|
"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).",
|
package/agent/components.json
CHANGED
|
@@ -1685,7 +1685,8 @@
|
|
|
1685
1685
|
],
|
|
1686
1686
|
"tagline": "A whole-card link (gh#1435). `variant=\"journal\"`: an edge-to-edge cover, category, title, two lines of description and a footer with a circled arrow; the link is named by its title (aria-labelledby). `variant=\"spotlight\"`: kicker, title and a line beside a tilted picture. Pass the link element (router Link or `<a href>`) as `link`; the card content is rendered into it. Geometry: `--editorial-journal-*`, `--editorial-spotlight-*`.",
|
|
1687
1687
|
"usage": [
|
|
1688
|
-
"DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`."
|
|
1688
|
+
"DO lay journal cards out with `ResponsiveGrid preset=\"trio\"`.",
|
|
1689
|
+
"DO use kit Image with preview={false} for media inside the whole-card link. Spotlight sizes the Image frame to fill its slot, including placeholder and fallback (gh#1474)."
|
|
1689
1690
|
]
|
|
1690
1691
|
},
|
|
1691
1692
|
{
|
|
@@ -1983,7 +1984,7 @@
|
|
|
1983
1984
|
]
|
|
1984
1985
|
},
|
|
1985
1986
|
{
|
|
1986
|
-
"example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail
|
|
1987
|
+
"example": "import { CatalogDetail, CatalogDetailTag, Eyebrow } from \"@godxjp/ui/layout\";\n\n<CatalogDetail image={{ src: \"/reference.svg\", alt: \"Reference\", priority: true }} overline={<Eyebrow>Pokémon</Eyebrow>} title={name} meta={<CatalogDetailTag>SAR</CatalogDetailTag>} summary={{ label: \"Market\", price: \"¥32,800\" }} facts={[{ key: \"set\", label: \"Expansion\", value: \"151\" }]} />",
|
|
1987
1988
|
"group": "layout",
|
|
1988
1989
|
"importPath": "@godxjp/ui/layout",
|
|
1989
1990
|
"name": "CatalogDetail",
|
|
@@ -1995,11 +1996,15 @@
|
|
|
1995
1996
|
"type": "\"card\" | \"sealed\""
|
|
1996
1997
|
},
|
|
1997
1998
|
{
|
|
1998
|
-
"description": "
|
|
1999
|
+
"description": "Custom picture; used when image is omitted.",
|
|
1999
2000
|
"name": "media",
|
|
2000
|
-
"required": true,
|
|
2001
2001
|
"type": "ReactNode"
|
|
2002
2002
|
},
|
|
2003
|
+
{
|
|
2004
|
+
"description": "Kit Image keyed per src. Art frame exposes data-status loading|loaded|error and aria-busy; token-driven skeleton respects reduced motion. priority requests eager/high priority. Takes precedence over media.",
|
|
2005
|
+
"name": "image",
|
|
2006
|
+
"type": "{ src: string; alt: string; width?: number | string; height?: number | string; fallback?: string; priority?: boolean }"
|
|
2007
|
+
},
|
|
2003
2008
|
{
|
|
2004
2009
|
"description": "Save toggle over the frame.",
|
|
2005
2010
|
"name": "favorite",
|
|
@@ -9172,7 +9177,7 @@
|
|
|
9172
9177
|
"type": "React.ReactNode"
|
|
9173
9178
|
},
|
|
9174
9179
|
{
|
|
9175
|
-
"description": "antd `popupMatchSelectWidth`. true (default)
|
|
9180
|
+
"description": "antd `popupMatchSelectWidth`. true (default): the trigger width is the floor and the popup grows to its longest row, up to --select-content-max-width (32rem) and the viewport less --select-content-viewport-inset, then wraps rows. false lets it hug its rows below the trigger width; a number pins it to that many pixels. The width never depends on where the popup is placed, so it cannot change while open.",
|
|
9176
9181
|
"name": "popupMatchSelectWidth",
|
|
9177
9182
|
"type": "boolean | number"
|
|
9178
9183
|
},
|
|
@@ -9302,6 +9307,7 @@
|
|
|
9302
9307
|
],
|
|
9303
9308
|
"tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
|
|
9304
9309
|
"usage": [
|
|
9310
|
+
"DO: Searchable popup widths, including numeric popupMatchSelectWidth, are capped by --search-select-panel-max-width and --search-select-panel-viewport-inset, using the viewport width excluding classic scrollbars.",
|
|
9305
9311
|
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
9306
9312
|
"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.",
|
|
9307
9313
|
"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.",
|
|
@@ -10129,7 +10135,7 @@
|
|
|
10129
10135
|
"type": "\"date\" | \"week\" | \"month\" | \"quarter\" | \"year\""
|
|
10130
10136
|
},
|
|
10131
10137
|
{
|
|
10132
|
-
"description": "CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
|
|
10138
|
+
"description": "With no confirmation, holds the first endpoint internally while open, emits a complete range or clear, and discards half-ranges on close. CARDINALITY: two endpoints in one control (antd `DatePicker.RangePicker`). value/defaultValue and the callback become `DateRange`; the pair submits as `${name}_from` / `${name}_to`. Composes with `picker`, so a month range is `<DatePicker range picker=\"month\" />`. Never place two DatePickers side by side to fake this.",
|
|
10133
10139
|
"name": "range",
|
|
10134
10140
|
"type": "boolean"
|
|
10135
10141
|
},
|
|
@@ -10705,7 +10711,7 @@
|
|
|
10705
10711
|
"usage": [
|
|
10706
10712
|
"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.",
|
|
10707
10713
|
"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).",
|
|
10708
|
-
"DON'T: Import `toast` from `\"sonner\"` directly —
|
|
10714
|
+
"DON'T: Import `toast` from `\"sonner\"` directly — a consumer-installed version can own a different toast store from the kit Toaster, silently hiding toasts. Use `import { toast } from \"@godxjp/ui\"` (also available from `@godxjp/ui/feedback`); this also preserves close-button defaults. Also note `toast.promise` settling into an error does not gain the button; pass `closeButton` there yourself if the failure must stay dismissible.",
|
|
10709
10715
|
"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.",
|
|
10710
10716
|
"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.",
|
|
10711
10717
|
"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).",
|
|
@@ -10907,6 +10913,11 @@
|
|
|
10907
10913
|
"importPath": "@godxjp/ui/navigation",
|
|
10908
10914
|
"name": "Pagination",
|
|
10909
10915
|
"props": [
|
|
10916
|
+
{
|
|
10917
|
+
"description": "Pointer enter, focus or touch start on enabled page/previous/next controls, including simple and responsive forms. May repeat: preload/cache the target without changing value. Disabled or out-of-range targets do not emit.",
|
|
10918
|
+
"name": "onPageIntent",
|
|
10919
|
+
"type": "(page: number) => void"
|
|
10920
|
+
},
|
|
10910
10921
|
{
|
|
10911
10922
|
"description": "Tên khả truy cập của landmark phân trang. Bắt buộc khi một trang có nhiều bộ phân trang.",
|
|
10912
10923
|
"name": "ariaLabel",
|
|
@@ -11124,6 +11135,7 @@
|
|
|
11124
11135
|
],
|
|
11125
11136
|
"tagline": "Dropdown menu (react-aria). Compose DropdownMenu/DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/DropdownMenuSeparator. `trigger` picks the gestures — click (default), hover, or contextMenu, which is what replaced the deleted ContextMenu component.",
|
|
11126
11137
|
"usage": [
|
|
11138
|
+
"DO: Use only menu collection nodes inside DropdownMenuContent. Arbitrary headers, notification lists and links belong in Popover + ListRow; development diagnostics and the dropdown-menu-non-menu-children audit rule name this fix.",
|
|
11127
11139
|
"DO compose the full sub-part tree: DropdownMenu (root) → DropdownMenuTrigger (with asChild to delegate to your Button/icon) → DropdownMenuContent → DropdownMenuItem / DropdownMenuSeparator / DropdownMenuLabel / DropdownMenuGroup. Omitting any level (e.g. rendering DropdownMenuContent without DropdownMenu as ancestor) breaks Radix context and the menu will not open.",
|
|
11128
11140
|
"DO use DropdownMenuTrigger with asChild and pass a godx-ui Button or icon Button as the child — never render a raw <button> or <div> as the trigger, and never omit asChild when the child is already a button-like element (double-button nesting breaks a11y).",
|
|
11129
11141
|
"DO use variant='destructive' on DropdownMenuItem for irreversible actions (delete, revoke, void) — this applies the semantic destructive colour token automatically without any className override.",
|
|
@@ -13841,6 +13853,7 @@
|
|
|
13841
13853
|
],
|
|
13842
13854
|
"tagline": "React Aria floating panel anchored to a trigger — always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",
|
|
13843
13855
|
"usage": [
|
|
13856
|
+
"DO: PopoverContent scrolls automatically when its available height caps content. Focus rings remain contained at scroll edges, including flush panels.",
|
|
13844
13857
|
"HOVER CARD: `<Popover openOn=\"hover\"><PopoverTrigger asChild><a href=…>@yamada</a></PopoverTrigger><PopoverContent>…</PopoverContent></Popover>` for RICH preview content (an avatar + bio, an entity summary). For a short plain-text hint use Tooltip. DON'T put essential information only in a hover card — touch never opens it; make the same content reachable from the trigger's destination. There is no `HoverCard` component.",
|
|
13845
13858
|
"DO compose: <Popover> → <PopoverTrigger asChild> → <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.",
|
|
13846
13859
|
"DO set `<PopoverContent width=\"auto\">` when the child brings its own measure — a Calendar, a chart, a fixed-width preview. The panel's own 18rem clips them, and a `w-*` utility on className is a per-call-site constant no service theme can retune, which is exactly what `flush` exists to avoid on the padding axis. `width=\"trigger\"` matches the anchor, the shape every select-like control wants.",
|
|
@@ -18526,6 +18539,11 @@
|
|
|
18526
18539
|
"importPath": "@godxjp/ui/data-display",
|
|
18527
18540
|
"name": "Image",
|
|
18528
18541
|
"props": [
|
|
18542
|
+
{
|
|
18543
|
+
"description": "Reports resolved status, including cached images; error remains final while showing fallback.",
|
|
18544
|
+
"name": "onStatusChange",
|
|
18545
|
+
"type": "(status: \"loading\" | \"loaded\" | \"error\") => void"
|
|
18546
|
+
},
|
|
18529
18547
|
{
|
|
18530
18548
|
"description": "Image URL.",
|
|
18531
18549
|
"name": "src",
|
|
@@ -18547,7 +18565,7 @@
|
|
|
18547
18565
|
"type": "string"
|
|
18548
18566
|
},
|
|
18549
18567
|
{
|
|
18550
|
-
"description": "
|
|
18568
|
+
"description": "After hydration, painted over the frame while the picture loads; SSR leaves pictures uncovered. `true` = a muted block. Child content can shrink to fit the frame.",
|
|
18551
18569
|
"name": "placeholder",
|
|
18552
18570
|
"type": "ReactNode | boolean"
|
|
18553
18571
|
},
|
package/agent/index.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"counts": {
|
|
3
3
|
"anti-ai-tells": 35,
|
|
4
4
|
"components": 212,
|
|
5
|
-
"patterns":
|
|
5
|
+
"patterns": 29,
|
|
6
6
|
"rules": 44,
|
|
7
|
-
"tokens":
|
|
7
|
+
"tokens": 3203,
|
|
8
8
|
"vocabulary": 16
|
|
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.25.0/agent/index.json"
|
|
58
58
|
},
|
|
59
59
|
"source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
|
|
60
60
|
"start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
|
|
61
61
|
"tokenTiers": {
|
|
62
62
|
"component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
|
|
63
63
|
"counts": {
|
|
64
|
-
"component":
|
|
64
|
+
"component": 2867,
|
|
65
65
|
"foundation": 223,
|
|
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.25.0"
|
|
72
72
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system: 212 components,
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 212 components, 3203 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.25.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@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.25.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -15,11 +15,11 @@ that can only fetch URLs.
|
|
|
15
15
|
|
|
16
16
|
## Catalog
|
|
17
17
|
|
|
18
|
-
- [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json):
|
|
18
|
+
- [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 29 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
|
|
19
19
|
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 47 KB — all 212 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–38 KB, median 5 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.4 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` — 223 `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` — 223 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 2867 `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.25.0/agent/...`. A catalog that does not match the installed
|
|
31
31
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
32
32
|
|
|
33
33
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"aliases": [
|
|
3
|
+
"consent-bar",
|
|
4
|
+
"cookie-consent"
|
|
5
|
+
],
|
|
6
|
+
"code": "// Register noticeBarDocs copy in your application message catalogue (en/ja/vi recipe: docs/feedback/notice-bar.tsx).\n// Framework-Component Test: C3 FAIL; compose existing primitives.\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Button, Text, Heading } from \"@godxjp/ui/general\";\nimport { Flex, PageContainer } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\n/**\n * Composition, gh#1463. C1 PASS; C2 FAIL (ordinary region + native Tab order);\n * C3 FAIL (Card + Flex + Button + fixed section); C4/C5/C6 PASS; C7 FAIL\n * (persistent non-modal consent is not a named primitive in two reference libraries).\n * Consent policy/storage belongs to the application. No focus trap, live role or auto focus.\n * Portal to body avoids paint/transform ancestors turning fixed into a local pin.\n */\nexport default function Demo() {\n const { t } = useTranslation();\n const [decision, setDecision] = useState<string | null>(null);\n const [mounted, setMounted] = useState(false);\n const region = useRef<HTMLElement>(null);\n const host = useRef<HTMLDivElement>(null);\n const [direction, setDirection] = useState(\"ltr\");\n useEffect(() => {\n setDirection(host.current ? getComputedStyle(host.current).direction : \"ltr\");\n setMounted(true);\n }, []);\n useEffect(() => {\n const node = region.current;\n if (!mounted || decision !== null || !node) return;\n const root = document.documentElement;\n const previous = root.style.getPropertyValue(\"--toast-offset-bottom\");\n const priority = root.style.getPropertyPriority(\"--toast-offset-bottom\");\n const base = getComputedStyle(root).getPropertyValue(\"--toast-offset-bottom\").trim() || \"0px\";\n const publish = () => root.style.setProperty(\"--toast-offset-bottom\", `calc(${base} + ${node.offsetHeight}px)`);\n publish();\n const observer = new ResizeObserver(publish);\n observer.observe(node);\n return () => {\n observer.disconnect();\n if (previous) root.style.setProperty(\"--toast-offset-bottom\", previous, priority);\n else root.style.removeProperty(\"--toast-offset-bottom\");\n };\n }, [mounted, decision]);\n return <>\n <div ref={host}><PageContainer title={t(\"noticeBarDocs.page\")}>\n <Text as=\"p\">{t(\"noticeBarDocs.body\")}</Text>\n <Button variant=\"outline\" onClick={() => setDecision(null)}>{t(\"noticeBarDocs.reopen\")}</Button>\n {decision !== null && <Text as=\"p\">{t(decision)}</Text>}\n <section id=\"privacy\"><Heading level={2}>{t(\"noticeBarDocs.privacy\")}</Heading><Text as=\"p\">{t(\"noticeBarDocs.policy\")}</Text></section>\n </PageContainer></div>\n {mounted && decision === null && createPortal(\n <section ref={region} dir={direction} style={{position: \"fixed\", insetInline: 0, insetBlockEnd: 0, zIndex: \"var(--sticky-z-index)\", paddingBlockEnd: \"env(safe-area-inset-bottom, 0px)\", paddingInlineStart: direction === \"rtl\" ? \"env(safe-area-inset-right, 0px)\" : \"env(safe-area-inset-left, 0px)\", paddingInlineEnd: direction === \"rtl\" ? \"env(safe-area-inset-left, 0px)\" : \"env(safe-area-inset-right, 0px)\", background: \"hsl(var(--card))\", color: \"hsl(var(--card-foreground))\"}} role=\"region\" aria-label={t(\"noticeBarDocs.title\")}>\n <Card><CardContent>\n <Flex align=\"center\" justify=\"between\" gap=\"lg\" wrap>\n <Flex direction=\"col\" gap=\"xs\" style={{flex: \"1 1 var(--page-measure-narrow)\", minInlineSize: 0, overflowWrap: \"anywhere\"}}>\n <Text weight=\"medium\">{t(\"noticeBarDocs.title\")}</Text>\n <Text as=\"p\">{t(\"noticeBarDocs.description\")} <a href=\"#privacy\" style={{textDecorationLine: \"underline\"}}>{t(\"noticeBarDocs.privacy\")}</a></Text>\n </Flex>\n <Flex gap=\"sm\" wrap>\n <Button variant=\"outline\" onClick={() => setDecision(\"noticeBarDocs.declined\")}>{t(\"noticeBarDocs.decline\")}</Button>\n <Button onClick={() => setDecision(\"noticeBarDocs.accepted\")}>{t(\"noticeBarDocs.accept\")}</Button>\n </Flex>\n </Flex>\n </CardContent></Card>\n </section>, document.body)}\n </>;\n}\n",
|
|
7
|
+
"name": "notice-bar",
|
|
8
|
+
"tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
|
|
9
|
+
"tags": [
|
|
10
|
+
"feedback",
|
|
11
|
+
"consent",
|
|
12
|
+
"cookie",
|
|
13
|
+
"notice",
|
|
14
|
+
"fixed"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"aliases": [
|
|
4
|
+
"consent-bar",
|
|
5
|
+
"cookie-consent"
|
|
6
|
+
],
|
|
7
|
+
"name": "notice-bar",
|
|
8
|
+
"tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
|
|
9
|
+
"tags": [
|
|
10
|
+
"feedback",
|
|
11
|
+
"consent",
|
|
12
|
+
"cookie",
|
|
13
|
+
"notice",
|
|
14
|
+
"fixed"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
2
17
|
{
|
|
3
18
|
"name": "common-fixes",
|
|
4
19
|
"tagline": "Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before → after.",
|
package/agent/patterns.json
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"aliases": [
|
|
4
|
+
"consent-bar",
|
|
5
|
+
"cookie-consent"
|
|
6
|
+
],
|
|
7
|
+
"code": "// Register noticeBarDocs copy in your application message catalogue (en/ja/vi recipe: docs/feedback/notice-bar.tsx).\n// Framework-Component Test: C3 FAIL; compose existing primitives.\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Button, Text, Heading } from \"@godxjp/ui/general\";\nimport { Flex, PageContainer } from \"@godxjp/ui/layout\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\n/**\n * Composition, gh#1463. C1 PASS; C2 FAIL (ordinary region + native Tab order);\n * C3 FAIL (Card + Flex + Button + fixed section); C4/C5/C6 PASS; C7 FAIL\n * (persistent non-modal consent is not a named primitive in two reference libraries).\n * Consent policy/storage belongs to the application. No focus trap, live role or auto focus.\n * Portal to body avoids paint/transform ancestors turning fixed into a local pin.\n */\nexport default function Demo() {\n const { t } = useTranslation();\n const [decision, setDecision] = useState<string | null>(null);\n const [mounted, setMounted] = useState(false);\n const region = useRef<HTMLElement>(null);\n const host = useRef<HTMLDivElement>(null);\n const [direction, setDirection] = useState(\"ltr\");\n useEffect(() => {\n setDirection(host.current ? getComputedStyle(host.current).direction : \"ltr\");\n setMounted(true);\n }, []);\n useEffect(() => {\n const node = region.current;\n if (!mounted || decision !== null || !node) return;\n const root = document.documentElement;\n const previous = root.style.getPropertyValue(\"--toast-offset-bottom\");\n const priority = root.style.getPropertyPriority(\"--toast-offset-bottom\");\n const base = getComputedStyle(root).getPropertyValue(\"--toast-offset-bottom\").trim() || \"0px\";\n const publish = () => root.style.setProperty(\"--toast-offset-bottom\", `calc(${base} + ${node.offsetHeight}px)`);\n publish();\n const observer = new ResizeObserver(publish);\n observer.observe(node);\n return () => {\n observer.disconnect();\n if (previous) root.style.setProperty(\"--toast-offset-bottom\", previous, priority);\n else root.style.removeProperty(\"--toast-offset-bottom\");\n };\n }, [mounted, decision]);\n return <>\n <div ref={host}><PageContainer title={t(\"noticeBarDocs.page\")}>\n <Text as=\"p\">{t(\"noticeBarDocs.body\")}</Text>\n <Button variant=\"outline\" onClick={() => setDecision(null)}>{t(\"noticeBarDocs.reopen\")}</Button>\n {decision !== null && <Text as=\"p\">{t(decision)}</Text>}\n <section id=\"privacy\"><Heading level={2}>{t(\"noticeBarDocs.privacy\")}</Heading><Text as=\"p\">{t(\"noticeBarDocs.policy\")}</Text></section>\n </PageContainer></div>\n {mounted && decision === null && createPortal(\n <section ref={region} dir={direction} style={{position: \"fixed\", insetInline: 0, insetBlockEnd: 0, zIndex: \"var(--sticky-z-index)\", paddingBlockEnd: \"env(safe-area-inset-bottom, 0px)\", paddingInlineStart: direction === \"rtl\" ? \"env(safe-area-inset-right, 0px)\" : \"env(safe-area-inset-left, 0px)\", paddingInlineEnd: direction === \"rtl\" ? \"env(safe-area-inset-left, 0px)\" : \"env(safe-area-inset-right, 0px)\", background: \"hsl(var(--card))\", color: \"hsl(var(--card-foreground))\"}} role=\"region\" aria-label={t(\"noticeBarDocs.title\")}>\n <Card><CardContent>\n <Flex align=\"center\" justify=\"between\" gap=\"lg\" wrap>\n <Flex direction=\"col\" gap=\"xs\" style={{flex: \"1 1 var(--page-measure-narrow)\", minInlineSize: 0, overflowWrap: \"anywhere\"}}>\n <Text weight=\"medium\">{t(\"noticeBarDocs.title\")}</Text>\n <Text as=\"p\">{t(\"noticeBarDocs.description\")} <a href=\"#privacy\" style={{textDecorationLine: \"underline\"}}>{t(\"noticeBarDocs.privacy\")}</a></Text>\n </Flex>\n <Flex gap=\"sm\" wrap>\n <Button variant=\"outline\" onClick={() => setDecision(\"noticeBarDocs.declined\")}>{t(\"noticeBarDocs.decline\")}</Button>\n <Button onClick={() => setDecision(\"noticeBarDocs.accepted\")}>{t(\"noticeBarDocs.accept\")}</Button>\n </Flex>\n </Flex>\n </CardContent></Card>\n </section>, document.body)}\n </>;\n}\n",
|
|
8
|
+
"name": "notice-bar",
|
|
9
|
+
"tagline": "Fixed non-modal consent region: opaque Card, wrapping actions, safe-area padding, measured Toaster clearance; application owns consent policy. Recipe: docs/feedback/notice-bar.tsx, isolate feedback-notice-bar.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"feedback",
|
|
12
|
+
"consent",
|
|
13
|
+
"cookie",
|
|
14
|
+
"notice",
|
|
15
|
+
"fixed"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
2
18
|
{
|
|
3
19
|
"code": "// ───────────────────────────────────────────────────────────────────────\n// 0) ★ MOST COMMON: <Card> body has NO padding (content is flush against the edges)\n// Cause: the bare <Card> has ZERO inner padding — it MUST contain <CardContent>.\n// Don't hand-roll padding with className=\"p-4\" on the Card either.\n// ❌ <Card><Flex direction=\"col\" gap=\"md\">…fields…</Flex></Card> // flush, no padding\n// ❌ <Card className=\"p-4\">…fields…</Card> // hand-rolled padding\n// ✅ <Card><CardContent><Flex direction=\"col\" gap=\"md\">…fields…</Flex></CardContent></Card>\n// Titles → <CardHeader><CardTitle>. Only go flush deliberately for a full-bleed table:\n// ✅ <Card><CardContent flush><DataTable/></CardContent></Card>\n// GENERAL RULE — compose godx-ui primitives FULLY; never hand-roll what one ships:\n// padding → CardContent (not p-4) · controls → Input/Select/Button (not raw <input>/<select>/<button>)\n// vertical spacing → Flex direction=\"col\" gap (not space-y-*) · empty rows → DataTable's built-in\n// empty / <EmptyState> (not a custom data.length===0 guard). If a primitive exists, USE it.\n\n// 1) StatCard shows a DOUBLE border (too thick)\n// Cause: StatCard IS already a bordered Card. Don't wrap it.\n// ❌ <Card><CardContent><StatCard label=\"x\" value=\"1\" /></CardContent></Card>\n// ✅ dashboard only, with comparison/trend:\n// <ResponsiveGrid columns={4}><StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /></ResponsiveGrid>\n// A list or detail page has NO StatCard row — a row count is not a KPI (Pagination showTotal).\n\n// 2) Badge renders grey with a ○ (no colour) for localized/tier labels\n// Cause: it auto-maps only English lifecycle keys.\n// ✅ <Badge status=\"active\">公開中</Badge> // lifecycle → keep icon\n// ✅ <Badge status=\"プレミアム\" tone=\"neutral\" icon={null} /> // tier → ONE neutral tone\n// Colour is for status only; a tier/category/scope is plain text or a neutral pill.\n\n// 3) Table text collapses to one char per line, or a chip wraps\n// ✅ give long columns a width: { key: \"name\", header: \"氏名\", width: \"w-64\" }\n// (cells + chips are pinned white-space: nowrap by the library.)\n\n// 4) Empty (icon/action) column header shows a blank grey block\n// ✅ header: \"\" renders a transparent cell — leave it empty, don't inject a space.\n\n// 5) DataTable columns are crushed / squeezed\n// Cause: the table is nested in a narrow grid column.\n// ❌ <ResponsiveGrid columns={3}><div className=\"lg:col-span-2\"><Card><DataTable/></Card></div></ResponsiveGrid>\n// ✅ Table gets its OWN full-width row: <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 6) Toolbar (filter bar) has no padding (sticks to the edge)\n// Cause: it's inside CardContent flush (flush strips padding — that's for tables).\n// ❌ <Card><CardContent flush><Toolbar/><DataTable/></CardContent></Card>\n// ✅ <Toolbar/> then <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 7) Sidebar footer looks washed-out / off-design\n// Cause: raw opacity-*/text-[11px]. Use semantic tokens.\n// ✅ <div className=\"text-muted-foreground text-xs\">\n// <div className=\"text-foreground font-medium\">{name}</div><div>{role}</div></div>\n\n// 8) Inertia: \"Objects are not valid as a React child {errors, auth, …}\"\n// Cause: persistent layout passed as a render fn. Use the ARRAY form.\n// ❌ Page.layout = (page) => <Layout>{page}</Layout>\n// ✅ Page.layout = [Layout] // Layout is ({children}) => ...\n\n// 9) Inertia v3 hydration mismatch (\"server rendered text didn't match the client\")\n// Cause: Math.random()/argless new Date() during render (SSR ≠ client).\n// ✅ seed deterministically by index, or compute in an event handler.\n\n// 10) Hide a column on mobile / sign-aware KPI delta\n// ✅ columns: [{ key: \"email\", header: \"メール\", hiddenOnMobile: true }]\n// ✅ <StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /> // + green / - red; inverse flips",
|
|
4
20
|
"name": "common-fixes",
|
package/agent/rules.json
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"title": "TypeScript strict"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
|
-
"body": "The runtime stack is what `package.json` declares: react-aria-components / react-aria / react-stately, cmdk, sonner, lucide-react, @tanstack/react-table, react-day-picker, class-variance-authority + clsx + tailwind-merge. A new dependency needs an ADR saying why it is the canonical choice and why an existing one cannot do it.",
|
|
58
|
+
"body": "The runtime stack is what `package.json` declares: react-aria-components / react-aria / react-stately, cmdk, sonner, lucide-react, @tanstack/react-table, react-day-picker, class-variance-authority + clsx + tailwind-merge. Consumers must import toast from @godxjp/ui (or @godxjp/ui/feedback), never directly from sonner: two installed versions own separate toast stores and silently miss the kit Toaster. The audit rule no-direct-sonner-import enforces this. A new dependency needs an ADR saying why it is the canonical choice and why an existing one cannot do it.",
|
|
59
59
|
"number": 14,
|
|
60
60
|
"title": "Every third-party library is a deliberate, declared choice"
|
|
61
61
|
},
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"title": "Long text columns get an explicit width"
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
|
-
"body": "Author and verify every page at 320–390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction=\"col\"`, control rows = the default `direction=\"row\"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) — never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them).
|
|
163
|
+
"body": "Author and verify every page at 320–390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction=\"col\"`, control rows = the default `direction=\"row\"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) — never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them). Load `@godxjp/ui/styles` (Tailwind v4) or `styles.css` (precompiled) for all families, or `styles/application` to omit editorial/catalog/holding layouts; `styles/editorial` adds that family after the application entry. Existing `styles/core` still carries all families. Fonts are opt-in. For a content-aware subset run `npx @godxjp/ui prune-css src --out <your stylesheet>` and import its generated file instead; add `--precompiled` without Tailwind, or `--exclude-family editorial` to reject accidental use of the omitted family. Both paths use the package-owned layer dependency graph. Re-run after upgrades/import changes; never hand-pick layout files.",
|
|
164
164
|
"number": 40,
|
|
165
165
|
"title": "Pages are mobile-first"
|
|
166
166
|
},
|