@godxjp/ui 32.4.3 → 32.6.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/DESIGN.md +201 -0
- package/README.md +1 -0
- package/agent/DESIGN.md +201 -0
- package/agent/START-HERE.md +22 -26
- package/agent/anti-ai-tells.json +56 -2
- package/agent/components/Text.json +4 -2
- package/agent/components.json +4 -2
- package/agent/index.json +9 -3
- package/agent/llms.txt +5 -4
- package/agent/patterns/badge-coloring.json +2 -2
- package/agent/patterns/common-fixes.json +1 -1
- package/agent/patterns/confirm-destructive.json +2 -2
- package/agent/patterns/inertia-detail-page.json +2 -2
- package/agent/patterns/inertia-list-page.json +2 -2
- package/agent/patterns/page-sections.json +5 -3
- package/agent/patterns/settings-section-rows.json +2 -2
- package/agent/patterns-index.json +9 -7
- package/agent/patterns.json +16 -14
- package/dist/components/data-entry/select.js +5 -4
- package/dist/components/general/typography.js +4 -4
- package/dist/contracts/measurement.json +1 -1
- package/dist/props/components/general.prop.d.ts +6 -0
- package/dist/props/registry.d.ts +1 -1
- package/dist/props/registry.js +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +9 -1
- package/dist/styles/layers.json +1 -1
- package/dist/styles/text-layout.css +14 -1
- package/dist/styles.css +1 -1
- package/docs/CONSUMER-RULES.md +4 -3
- package/docs/README.md +1 -1
- package/package.json +3 -2
- package/scripts/_agent-setup.mjs +49 -76
- package/scripts/consumer-rule.md +32 -68
- package/scripts/ui-audit.mjs +152 -22
package/DESIGN.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: alpha
|
|
3
|
+
name: godxjp-ui
|
|
4
|
+
description: "Restrained Japanese-enterprise admin UI. Warm off-white canvas, near-black ink, one neutral accent, hairline borders, 14px/1.7 type, dense tables. Hierarchy comes from spacing steps and type, not from boxes, colour or shadow."
|
|
5
|
+
|
|
6
|
+
colors:
|
|
7
|
+
canvas: "#fdfdfc" # --background (= --card, --popover)
|
|
8
|
+
ink: "#24231e" # --foreground
|
|
9
|
+
ink-muted: "#68665e" # --muted-foreground
|
|
10
|
+
ink-tertiary: "#928f87" # --text-tertiary (captions, meta)
|
|
11
|
+
primary: "#18181b" # --primary. Neutral placeholder: set your brand here
|
|
12
|
+
on-primary: "#fdfdfc" # --primary-foreground
|
|
13
|
+
ring: "{colors.primary}" # --ring follows --primary; no separate focus hue
|
|
14
|
+
muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)
|
|
15
|
+
accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)
|
|
16
|
+
hairline: "#eeedec" # --border (dividers, card edges, table rules)
|
|
17
|
+
control-edge: "#90877f" # --input (the edge of an input; 3:1 against canvas)
|
|
18
|
+
success: "#68be8d" # --success 若竹
|
|
19
|
+
warning: "#f8b500" # --warning 山吹
|
|
20
|
+
info: "#4c6cb3" # --info 群青
|
|
21
|
+
attention: "#eb6101" # --attention 朱 (non-destructive urgency: late, overdue)
|
|
22
|
+
destructive: "#b7282e" # --destructive 茜 (delete, failed, irreversible)
|
|
23
|
+
text-success: "#09673b" # --text-success (status as text, AA on canvas)
|
|
24
|
+
text-warning: "#8f5600" # --text-warning
|
|
25
|
+
text-info: "#1d48a5" # --text-info
|
|
26
|
+
text-error: "#aa181f" # --text-error
|
|
27
|
+
|
|
28
|
+
typography:
|
|
29
|
+
family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"
|
|
30
|
+
base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)
|
|
31
|
+
page-title: { size: "~20px (--heading-h1)", weight: 500, lineHeight: 1.25 }
|
|
32
|
+
section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }
|
|
33
|
+
card-title: { size: "14px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
|
|
34
|
+
body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body
|
|
35
|
+
caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }
|
|
36
|
+
weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600
|
|
37
|
+
numerals: tabular # Text tabular / Badge tabular on every number column
|
|
38
|
+
|
|
39
|
+
rounded:
|
|
40
|
+
base: 6px # --radius (rides --scaling)
|
|
41
|
+
steps: "--radius-sm/md/lg/xl = radius / φ², / φ, ×1, × φ"
|
|
42
|
+
card: "~9.7px (--card-radius = radius × φ)"
|
|
43
|
+
pill: 9999px # --radius-pill: switch, radio, pill badge only
|
|
44
|
+
|
|
45
|
+
spacing:
|
|
46
|
+
scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"
|
|
47
|
+
stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap
|
|
48
|
+
inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap
|
|
49
|
+
page: { x: 24px, y: 24px } # --space-page-x / -y
|
|
50
|
+
section: 16px # --space-section
|
|
51
|
+
|
|
52
|
+
density:
|
|
53
|
+
axis: "AppProvider density / PageContainer density / data-density"
|
|
54
|
+
compact: "--scaling 0.92 (data-heavy tables)"
|
|
55
|
+
default: "--scaling 1 (control and row height 32px, --band-height-md)"
|
|
56
|
+
comfortable: "--scaling 1.08 (login, public, touch)"
|
|
57
|
+
touch-floor: 44px # --band-height-xl
|
|
58
|
+
|
|
59
|
+
components:
|
|
60
|
+
button-primary:
|
|
61
|
+
{
|
|
62
|
+
background: "{colors.primary}",
|
|
63
|
+
text: "{colors.on-primary}",
|
|
64
|
+
height: "--control-height",
|
|
65
|
+
radius: "{rounded.base}",
|
|
66
|
+
}
|
|
67
|
+
button-secondary: { variant: outline, edge: "{colors.control-edge}" }
|
|
68
|
+
input:
|
|
69
|
+
{
|
|
70
|
+
background: "{colors.canvas}",
|
|
71
|
+
edge: "{colors.control-edge}",
|
|
72
|
+
focus: "edge recolours to {colors.ring}",
|
|
73
|
+
}
|
|
74
|
+
card:
|
|
75
|
+
{
|
|
76
|
+
background: "{colors.canvas}",
|
|
77
|
+
border: "1px {colors.hairline}",
|
|
78
|
+
shadow: "--shadow-sm",
|
|
79
|
+
radius: "{rounded.card}",
|
|
80
|
+
padding: "--card-space-inset",
|
|
81
|
+
}
|
|
82
|
+
table:
|
|
83
|
+
{ rule: "1px {colors.hairline}", rowHeight: "--table-row-height", header: "{colors.ink-muted}" }
|
|
84
|
+
badge: { variant: outline, tone: "status role only" }
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Overview
|
|
88
|
+
|
|
89
|
+
A calm, dense tool for people who use it all day. The look is restrained Japanese enterprise: 渋み (low chroma), 間 (even breathing room), 簡素 (nothing that does not carry information). Distinctiveness comes from exact spacing, alignment and type, never from decoration.
|
|
90
|
+
|
|
91
|
+
The default `--primary` is a neutral ink (`240 6% 10%`). It is a placeholder, not a brand. Set the brand once with `--primary` in your theme, `tenantTheme('#RRGGBB')` from `@godxjp/ui/app`, a `data-brand` preset (`brand`, `crm`, `logistics`, `partner`, `slate`) or `@godxjp/ui/themes/godx.css`. `--ring` follows `--primary`.
|
|
92
|
+
|
|
93
|
+
## Colors
|
|
94
|
+
|
|
95
|
+
Colour is scarce. Most of the screen is canvas, ink and hairline.
|
|
96
|
+
|
|
97
|
+
- **Accent (`--primary`)**: the one primary action per view, focus ring, links, the selected nav item. Nothing else: no tinted headers, avatars, icons or section backgrounds.
|
|
98
|
+
- **Status (若竹 success, 山吹 warning, 群青 info, 朱 attention, 茜 destructive)**: only to state a status. Use `Badge status=…` (maps lifecycle keys to a fixed tone and icon) or `tone`. For status as text use the `--text-*` roles. Status colours are never re-themed per tenant.
|
|
99
|
+
- 朱 attention for urgency that is not destructive (late, overdue); 茜 only for loss (delete, failed, irreversible).
|
|
100
|
+
- `--accent` is the hover/selected surface, not a brand colour. `--muted` is for quiet fills.
|
|
101
|
+
- No gradients, glows or tinted page sections on app surfaces.
|
|
102
|
+
|
|
103
|
+
## Typography
|
|
104
|
+
|
|
105
|
+
- One family. Hierarchy from size steps, weight 400/500/700 and `ink` vs `ink-muted`. Page title ~20px; anything larger belongs on a marketing page.
|
|
106
|
+
- Body 14px / 1.7. Captions and meta 12.5px muted. `Text tabular` on every number, time and money column.
|
|
107
|
+
- Japanese prose: `Text break="phrase"` so lines break between phrases.
|
|
108
|
+
- Copy is quiet and factual: no praise, no alarm, no emoji, no em dash in UI strings. A success message states the fact (「承認しました」).
|
|
109
|
+
|
|
110
|
+
## Layout & Density
|
|
111
|
+
|
|
112
|
+
- Hierarchy is spacing first: related items `gap="xs"`/`"sm"`, groups `"md"`, sections `"lg"`/`"xl"`. If every gap is the same, nothing is grouped.
|
|
113
|
+
- A section is a `Heading` plus spacing. Add a `Separator` when two sections must not read as one. Use a surface only when it is a real boundary (see Components).
|
|
114
|
+
- Pick one density per page up front: `compact` for review grids, `default` everywhere else, `comfortable` for login and touch.
|
|
115
|
+
- Content measure: forms and settings in `PageContainer measure="narrow"` or `"medium"`; tables use the full width.
|
|
116
|
+
- Spacing comes from component props (`gap`, `pad`) and tokens, never `p-4`/`mt-6` literals.
|
|
117
|
+
|
|
118
|
+
## Elevation
|
|
119
|
+
|
|
120
|
+
- Flat. A card is canvas with a 1px `--border` hairline and a near-invisible `--shadow-sm`; nothing heavier at rest.
|
|
121
|
+
- `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.
|
|
122
|
+
- No hover lift on data cards. Depth never signals importance.
|
|
123
|
+
|
|
124
|
+
## Components
|
|
125
|
+
|
|
126
|
+
- **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`.
|
|
127
|
+
- **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.
|
|
128
|
+
- **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header.
|
|
129
|
+
- **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.
|
|
130
|
+
- **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).
|
|
131
|
+
- **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.
|
|
132
|
+
- **StatCard** only on a dashboard, always with `delta` that compares (vs last week, vs target). **EmptyState**: one sentence and at most one action.
|
|
133
|
+
- **Alert** only for something the user must act on now. **Badge** only for status; one per row at most.
|
|
134
|
+
|
|
135
|
+
## Admin Screen Anatomy
|
|
136
|
+
|
|
137
|
+
### List
|
|
138
|
+
|
|
139
|
+
Header: title, then `extra` with the primary create action last. Below it a `Toolbar` with search and filters. Then one Card holding the `DataTable` (`CardContent flush`) and `Pagination`. No KPI row, no subtitle that restates the title, no filter Card separate from the table.
|
|
140
|
+
|
|
141
|
+
### Detail
|
|
142
|
+
|
|
143
|
+
Header: record name as title, status `Badge` beside it, actions in `extra` (one primary). Body: `Descriptions` for metadata, then sections (Heading + content) for related records and history (`Timeline`). Use `Tabs` only when sections are independent and long. No Card per section; no icon beside every heading.
|
|
144
|
+
|
|
145
|
+
### Form
|
|
146
|
+
|
|
147
|
+
`Form` in a narrow measure; fields grouped by Heading sections, not Cards. Labels via `FormField`; helper text only when it adds information. One primary submit and a secondary cancel at the end, or in a sticky footer for long forms. Errors inline on the field plus `FormErrors` summary on submit.
|
|
148
|
+
|
|
149
|
+
### Settings
|
|
150
|
+
|
|
151
|
+
Each section is a Heading (plus one muted line if it adds a fact) over its content: `Descriptions` for read-only values, `ListRow` rows in one `Card` with `CardContent flush` for controls (`trailing` holds the control). Separate sections with spacing or a `Separator`. Changes that apply instantly need no Save button; a page with a Save button applies nothing until saved. Destructive actions last, in one `Card accent="destructive"`, each behind `AlertDialog`.
|
|
152
|
+
|
|
153
|
+
### Dashboard
|
|
154
|
+
|
|
155
|
+
The only archetype with KPI tiles: at most four `StatCard`s, each with a comparison `delta` (use `inverse` when up is bad). Below, panels in `ResponsiveGrid`, each a Card with a title and one "view all" link: a work queue, a short table, a chart. Every number answers "compared to what".
|
|
156
|
+
|
|
157
|
+
### Approval queue
|
|
158
|
+
|
|
159
|
+
A list whose rows are decisions. Show requester, target, reason, submitted time; approve and reject reachable from the row (kebab or row buttons); with `DataTable`, also in `BulkActions`. Escalated or overdue rows get one status Badge, not a coloured row.
|
|
160
|
+
|
|
161
|
+
## Do's and Don'ts
|
|
162
|
+
|
|
163
|
+
### Do
|
|
164
|
+
|
|
165
|
+
- Lead with the data. The first thing below the header is the table, form or record.
|
|
166
|
+
- Use one `Button` default variant per view; the rest `outline` or `ghost`.
|
|
167
|
+
- Group by spacing, then by heading, then by Separator, and only then by Card.
|
|
168
|
+
- Write the subtitle only when it adds a fact the title does not (date, scope, count).
|
|
169
|
+
- Right-align numbers, `tabular` digits, ISO/`Intl` dates.
|
|
170
|
+
|
|
171
|
+
### Don't
|
|
172
|
+
|
|
173
|
+
- A subtitle that restates or translates the title.
|
|
174
|
+
- A StatCard row above a list or form.
|
|
175
|
+
- A Card around every section, each with Header + Title + Description.
|
|
176
|
+
- An EmptyState with a large icon inside a Card; an icon in every card header.
|
|
177
|
+
- Alert banners as page decoration; several Alerts stacked.
|
|
178
|
+
- Badge walls: a badge per cell, rainbow tones, badges for non-status labels.
|
|
179
|
+
- Uniform `gap` everywhere; arbitrary px spacing.
|
|
180
|
+
- Coloured avatars, tinted section backgrounds, gradients, hover lift.
|
|
181
|
+
|
|
182
|
+
## Responsive
|
|
183
|
+
|
|
184
|
+
- Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.
|
|
185
|
+
- `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.
|
|
186
|
+
- `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.
|
|
187
|
+
- Header `extra` wraps under the title; a toolbar wraps its filters. Touch targets 44px at `comfortable`.
|
|
188
|
+
|
|
189
|
+
## Iteration Guide
|
|
190
|
+
|
|
191
|
+
1. Pick the archetype, call `get_exemplar` and build from that screen, not from memory.
|
|
192
|
+
2. Run `node node_modules/@godxjp/ui/scripts/ui-audit.mjs` until it is clean.
|
|
193
|
+
3. Screenshot at 390, 768 and 1280 next to the exemplar's live preview.
|
|
194
|
+
4. List the differences, fix the top 3 (structure before styling). Stop after 3 rounds.
|
|
195
|
+
|
|
196
|
+
## Known Gaps
|
|
197
|
+
|
|
198
|
+
- The default primary is neutral by design; a product without a brand colour looks monochrome until one is set.
|
|
199
|
+
- `ui-audit` checks primitives and tokens, not structure: a page can pass it and still show every Don't above.
|
|
200
|
+
- Exemplar previews deploy on release tags, so they can lag `main`. On GitHub Pages a showcase URL answers HTTP 404 and renders through `404.html`, so open it in a browser; a plain fetch sees the 404.
|
|
201
|
+
- Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.
|
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ static files straight from this public repo — no hosting, no deploy step:
|
|
|
51
51
|
| --- | --- | --- |
|
|
52
52
|
| [`agent/START-HERE.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md) | 11 KB | **read first** — self-contained: the four rules, the token override model, a page that passes review |
|
|
53
53
|
| [`agent/llms.txt`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/llms.txt) | 3 KB | the [llms.txt](https://llmstxt.org/) entry point |
|
|
54
|
+
| [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 12 KB | the visual spec: tokens, colour scarcity, admin screen anatomy, do/don't |
|
|
54
55
|
| [`agent/index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json) | 3 KB | manifest: version, counts, every file's URL |
|
|
55
56
|
| [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 47 KB | all 161 as name + group + tagline — **fetch this first** |
|
|
56
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** |
|
package/agent/DESIGN.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: alpha
|
|
3
|
+
name: godxjp-ui
|
|
4
|
+
description: "Restrained Japanese-enterprise admin UI. Warm off-white canvas, near-black ink, one neutral accent, hairline borders, 14px/1.7 type, dense tables. Hierarchy comes from spacing steps and type, not from boxes, colour or shadow."
|
|
5
|
+
|
|
6
|
+
colors:
|
|
7
|
+
canvas: "#fdfdfc" # --background (= --card, --popover)
|
|
8
|
+
ink: "#24231e" # --foreground
|
|
9
|
+
ink-muted: "#68665e" # --muted-foreground
|
|
10
|
+
ink-tertiary: "#928f87" # --text-tertiary (captions, meta)
|
|
11
|
+
primary: "#18181b" # --primary. Neutral placeholder: set your brand here
|
|
12
|
+
on-primary: "#fdfdfc" # --primary-foreground
|
|
13
|
+
ring: "{colors.primary}" # --ring follows --primary; no separate focus hue
|
|
14
|
+
muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)
|
|
15
|
+
accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)
|
|
16
|
+
hairline: "#eeedec" # --border (dividers, card edges, table rules)
|
|
17
|
+
control-edge: "#90877f" # --input (the edge of an input; 3:1 against canvas)
|
|
18
|
+
success: "#68be8d" # --success 若竹
|
|
19
|
+
warning: "#f8b500" # --warning 山吹
|
|
20
|
+
info: "#4c6cb3" # --info 群青
|
|
21
|
+
attention: "#eb6101" # --attention 朱 (non-destructive urgency: late, overdue)
|
|
22
|
+
destructive: "#b7282e" # --destructive 茜 (delete, failed, irreversible)
|
|
23
|
+
text-success: "#09673b" # --text-success (status as text, AA on canvas)
|
|
24
|
+
text-warning: "#8f5600" # --text-warning
|
|
25
|
+
text-info: "#1d48a5" # --text-info
|
|
26
|
+
text-error: "#aa181f" # --text-error
|
|
27
|
+
|
|
28
|
+
typography:
|
|
29
|
+
family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"
|
|
30
|
+
base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)
|
|
31
|
+
page-title: { size: "~20px (--heading-h1)", weight: 500, lineHeight: 1.25 }
|
|
32
|
+
section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }
|
|
33
|
+
card-title: { size: "14px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
|
|
34
|
+
body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body
|
|
35
|
+
caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }
|
|
36
|
+
weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600
|
|
37
|
+
numerals: tabular # Text tabular / Badge tabular on every number column
|
|
38
|
+
|
|
39
|
+
rounded:
|
|
40
|
+
base: 6px # --radius (rides --scaling)
|
|
41
|
+
steps: "--radius-sm/md/lg/xl = radius / φ², / φ, ×1, × φ"
|
|
42
|
+
card: "~9.7px (--card-radius = radius × φ)"
|
|
43
|
+
pill: 9999px # --radius-pill: switch, radio, pill badge only
|
|
44
|
+
|
|
45
|
+
spacing:
|
|
46
|
+
scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"
|
|
47
|
+
stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap
|
|
48
|
+
inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap
|
|
49
|
+
page: { x: 24px, y: 24px } # --space-page-x / -y
|
|
50
|
+
section: 16px # --space-section
|
|
51
|
+
|
|
52
|
+
density:
|
|
53
|
+
axis: "AppProvider density / PageContainer density / data-density"
|
|
54
|
+
compact: "--scaling 0.92 (data-heavy tables)"
|
|
55
|
+
default: "--scaling 1 (control and row height 32px, --band-height-md)"
|
|
56
|
+
comfortable: "--scaling 1.08 (login, public, touch)"
|
|
57
|
+
touch-floor: 44px # --band-height-xl
|
|
58
|
+
|
|
59
|
+
components:
|
|
60
|
+
button-primary:
|
|
61
|
+
{
|
|
62
|
+
background: "{colors.primary}",
|
|
63
|
+
text: "{colors.on-primary}",
|
|
64
|
+
height: "--control-height",
|
|
65
|
+
radius: "{rounded.base}",
|
|
66
|
+
}
|
|
67
|
+
button-secondary: { variant: outline, edge: "{colors.control-edge}" }
|
|
68
|
+
input:
|
|
69
|
+
{
|
|
70
|
+
background: "{colors.canvas}",
|
|
71
|
+
edge: "{colors.control-edge}",
|
|
72
|
+
focus: "edge recolours to {colors.ring}",
|
|
73
|
+
}
|
|
74
|
+
card:
|
|
75
|
+
{
|
|
76
|
+
background: "{colors.canvas}",
|
|
77
|
+
border: "1px {colors.hairline}",
|
|
78
|
+
shadow: "--shadow-sm",
|
|
79
|
+
radius: "{rounded.card}",
|
|
80
|
+
padding: "--card-space-inset",
|
|
81
|
+
}
|
|
82
|
+
table:
|
|
83
|
+
{ rule: "1px {colors.hairline}", rowHeight: "--table-row-height", header: "{colors.ink-muted}" }
|
|
84
|
+
badge: { variant: outline, tone: "status role only" }
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Overview
|
|
88
|
+
|
|
89
|
+
A calm, dense tool for people who use it all day. The look is restrained Japanese enterprise: 渋み (low chroma), 間 (even breathing room), 簡素 (nothing that does not carry information). Distinctiveness comes from exact spacing, alignment and type, never from decoration.
|
|
90
|
+
|
|
91
|
+
The default `--primary` is a neutral ink (`240 6% 10%`). It is a placeholder, not a brand. Set the brand once with `--primary` in your theme, `tenantTheme('#RRGGBB')` from `@godxjp/ui/app`, a `data-brand` preset (`brand`, `crm`, `logistics`, `partner`, `slate`) or `@godxjp/ui/themes/godx.css`. `--ring` follows `--primary`.
|
|
92
|
+
|
|
93
|
+
## Colors
|
|
94
|
+
|
|
95
|
+
Colour is scarce. Most of the screen is canvas, ink and hairline.
|
|
96
|
+
|
|
97
|
+
- **Accent (`--primary`)**: the one primary action per view, focus ring, links, the selected nav item. Nothing else: no tinted headers, avatars, icons or section backgrounds.
|
|
98
|
+
- **Status (若竹 success, 山吹 warning, 群青 info, 朱 attention, 茜 destructive)**: only to state a status. Use `Badge status=…` (maps lifecycle keys to a fixed tone and icon) or `tone`. For status as text use the `--text-*` roles. Status colours are never re-themed per tenant.
|
|
99
|
+
- 朱 attention for urgency that is not destructive (late, overdue); 茜 only for loss (delete, failed, irreversible).
|
|
100
|
+
- `--accent` is the hover/selected surface, not a brand colour. `--muted` is for quiet fills.
|
|
101
|
+
- No gradients, glows or tinted page sections on app surfaces.
|
|
102
|
+
|
|
103
|
+
## Typography
|
|
104
|
+
|
|
105
|
+
- One family. Hierarchy from size steps, weight 400/500/700 and `ink` vs `ink-muted`. Page title ~20px; anything larger belongs on a marketing page.
|
|
106
|
+
- Body 14px / 1.7. Captions and meta 12.5px muted. `Text tabular` on every number, time and money column.
|
|
107
|
+
- Japanese prose: `Text break="phrase"` so lines break between phrases.
|
|
108
|
+
- Copy is quiet and factual: no praise, no alarm, no emoji, no em dash in UI strings. A success message states the fact (「承認しました」).
|
|
109
|
+
|
|
110
|
+
## Layout & Density
|
|
111
|
+
|
|
112
|
+
- Hierarchy is spacing first: related items `gap="xs"`/`"sm"`, groups `"md"`, sections `"lg"`/`"xl"`. If every gap is the same, nothing is grouped.
|
|
113
|
+
- A section is a `Heading` plus spacing. Add a `Separator` when two sections must not read as one. Use a surface only when it is a real boundary (see Components).
|
|
114
|
+
- Pick one density per page up front: `compact` for review grids, `default` everywhere else, `comfortable` for login and touch.
|
|
115
|
+
- Content measure: forms and settings in `PageContainer measure="narrow"` or `"medium"`; tables use the full width.
|
|
116
|
+
- Spacing comes from component props (`gap`, `pad`) and tokens, never `p-4`/`mt-6` literals.
|
|
117
|
+
|
|
118
|
+
## Elevation
|
|
119
|
+
|
|
120
|
+
- Flat. A card is canvas with a 1px `--border` hairline and a near-invisible `--shadow-sm`; nothing heavier at rest.
|
|
121
|
+
- `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.
|
|
122
|
+
- No hover lift on data cards. Depth never signals importance.
|
|
123
|
+
|
|
124
|
+
## Components
|
|
125
|
+
|
|
126
|
+
- **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`.
|
|
127
|
+
- **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.
|
|
128
|
+
- **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header.
|
|
129
|
+
- **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.
|
|
130
|
+
- **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).
|
|
131
|
+
- **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.
|
|
132
|
+
- **StatCard** only on a dashboard, always with `delta` that compares (vs last week, vs target). **EmptyState**: one sentence and at most one action.
|
|
133
|
+
- **Alert** only for something the user must act on now. **Badge** only for status; one per row at most.
|
|
134
|
+
|
|
135
|
+
## Admin Screen Anatomy
|
|
136
|
+
|
|
137
|
+
### List
|
|
138
|
+
|
|
139
|
+
Header: title, then `extra` with the primary create action last. Below it a `Toolbar` with search and filters. Then one Card holding the `DataTable` (`CardContent flush`) and `Pagination`. No KPI row, no subtitle that restates the title, no filter Card separate from the table.
|
|
140
|
+
|
|
141
|
+
### Detail
|
|
142
|
+
|
|
143
|
+
Header: record name as title, status `Badge` beside it, actions in `extra` (one primary). Body: `Descriptions` for metadata, then sections (Heading + content) for related records and history (`Timeline`). Use `Tabs` only when sections are independent and long. No Card per section; no icon beside every heading.
|
|
144
|
+
|
|
145
|
+
### Form
|
|
146
|
+
|
|
147
|
+
`Form` in a narrow measure; fields grouped by Heading sections, not Cards. Labels via `FormField`; helper text only when it adds information. One primary submit and a secondary cancel at the end, or in a sticky footer for long forms. Errors inline on the field plus `FormErrors` summary on submit.
|
|
148
|
+
|
|
149
|
+
### Settings
|
|
150
|
+
|
|
151
|
+
Each section is a Heading (plus one muted line if it adds a fact) over its content: `Descriptions` for read-only values, `ListRow` rows in one `Card` with `CardContent flush` for controls (`trailing` holds the control). Separate sections with spacing or a `Separator`. Changes that apply instantly need no Save button; a page with a Save button applies nothing until saved. Destructive actions last, in one `Card accent="destructive"`, each behind `AlertDialog`.
|
|
152
|
+
|
|
153
|
+
### Dashboard
|
|
154
|
+
|
|
155
|
+
The only archetype with KPI tiles: at most four `StatCard`s, each with a comparison `delta` (use `inverse` when up is bad). Below, panels in `ResponsiveGrid`, each a Card with a title and one "view all" link: a work queue, a short table, a chart. Every number answers "compared to what".
|
|
156
|
+
|
|
157
|
+
### Approval queue
|
|
158
|
+
|
|
159
|
+
A list whose rows are decisions. Show requester, target, reason, submitted time; approve and reject reachable from the row (kebab or row buttons); with `DataTable`, also in `BulkActions`. Escalated or overdue rows get one status Badge, not a coloured row.
|
|
160
|
+
|
|
161
|
+
## Do's and Don'ts
|
|
162
|
+
|
|
163
|
+
### Do
|
|
164
|
+
|
|
165
|
+
- Lead with the data. The first thing below the header is the table, form or record.
|
|
166
|
+
- Use one `Button` default variant per view; the rest `outline` or `ghost`.
|
|
167
|
+
- Group by spacing, then by heading, then by Separator, and only then by Card.
|
|
168
|
+
- Write the subtitle only when it adds a fact the title does not (date, scope, count).
|
|
169
|
+
- Right-align numbers, `tabular` digits, ISO/`Intl` dates.
|
|
170
|
+
|
|
171
|
+
### Don't
|
|
172
|
+
|
|
173
|
+
- A subtitle that restates or translates the title.
|
|
174
|
+
- A StatCard row above a list or form.
|
|
175
|
+
- A Card around every section, each with Header + Title + Description.
|
|
176
|
+
- An EmptyState with a large icon inside a Card; an icon in every card header.
|
|
177
|
+
- Alert banners as page decoration; several Alerts stacked.
|
|
178
|
+
- Badge walls: a badge per cell, rainbow tones, badges for non-status labels.
|
|
179
|
+
- Uniform `gap` everywhere; arbitrary px spacing.
|
|
180
|
+
- Coloured avatars, tinted section backgrounds, gradients, hover lift.
|
|
181
|
+
|
|
182
|
+
## Responsive
|
|
183
|
+
|
|
184
|
+
- Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.
|
|
185
|
+
- `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.
|
|
186
|
+
- `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.
|
|
187
|
+
- Header `extra` wraps under the title; a toolbar wraps its filters. Touch targets 44px at `comfortable`.
|
|
188
|
+
|
|
189
|
+
## Iteration Guide
|
|
190
|
+
|
|
191
|
+
1. Pick the archetype, call `get_exemplar` and build from that screen, not from memory.
|
|
192
|
+
2. Run `node node_modules/@godxjp/ui/scripts/ui-audit.mjs` until it is clean.
|
|
193
|
+
3. Screenshot at 390, 768 and 1280 next to the exemplar's live preview.
|
|
194
|
+
4. List the differences, fix the top 3 (structure before styling). Stop after 3 rounds.
|
|
195
|
+
|
|
196
|
+
## Known Gaps
|
|
197
|
+
|
|
198
|
+
- The default primary is neutral by design; a product without a brand colour looks monochrome until one is set.
|
|
199
|
+
- `ui-audit` checks primitives and tokens, not structure: a page can pass it and still show every Don't above.
|
|
200
|
+
- Exemplar previews deploy on release tags, so they can lag `main`. On GitHub Pages a showcase URL answers HTTP 404 and renders through `404.html`, so open it in a browser; a plain fetch sees the 404.
|
|
201
|
+
- Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.
|
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` 32.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 32.6.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.
|
|
@@ -38,10 +38,13 @@ disk, and costs far fewer tokens than fetching a 1 MB JSON.
|
|
|
38
38
|
{ "mcpServers": { "godx-ui": { "command": "npx", "args": ["@godxjp/ui-mcp@<installed version>"] } } }
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Then
|
|
41
|
+
Then, before any JSX: `get_design` (the visual target), `route_consumer_task`, `get_exemplar` for
|
|
42
|
+
the closest screen, `search_components` → `get_component`. After building, screenshot at
|
|
43
|
+
390 / 768 / 1280 beside the exemplar and fix the top 3 differences, at most 3 rounds.
|
|
42
44
|
|
|
43
45
|
**You cannot run a process** (ChatGPT web · Claude.ai · anything fetching URLs)
|
|
44
|
-
→ These files are for you.
|
|
46
|
+
→ These files are for you. Read `DESIGN.md` (in this folder) first: tokens, scarcity rules and
|
|
47
|
+
screen anatomy, the visual target the rules below do not give you. Then fetch in this order:
|
|
45
48
|
|
|
46
49
|
0. `patterns-index.json` — 24 whole-task patterns as name + tagline + tags. **If your
|
|
47
50
|
task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
|
|
@@ -61,7 +64,7 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
|
|
|
61
64
|
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
62
65
|
1872 `component` entries are per-part knobs; reach for one only when a role is
|
|
63
66
|
right everywhere except one component.
|
|
64
|
-
5. `anti-ai-tells.json` —
|
|
67
|
+
5. `anti-ai-tells.json` — 35 shapes that make generated UI look generated, each with the
|
|
65
68
|
fix. Read before you reach for a gradient hero or a wall of coloured chips.
|
|
66
69
|
|
|
67
70
|
**Do not fetch `components.json`.** It is 1.2 MB, and most URL fetchers truncate a
|
|
@@ -143,7 +146,7 @@ has stopped following the brand.
|
|
|
143
146
|
|
|
144
147
|
| `tier` | count | what it is | set it? |
|
|
145
148
|
|---|---|---|---|
|
|
146
|
-
| `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.**
|
|
149
|
+
| `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** 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` |
|
|
147
150
|
| `semantic` | 112 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
|
|
148
151
|
| `component` | 1872 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
|
|
149
152
|
|
|
@@ -160,35 +163,27 @@ theme, every tenant scope and every audit in this package.
|
|
|
160
163
|
|
|
161
164
|
```tsx
|
|
162
165
|
import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
163
|
-
import { Card,
|
|
166
|
+
import { Card, CardContent, DataTable } from "@godxjp/ui/data-display";
|
|
164
167
|
import { Button } from "@godxjp/ui/general";
|
|
165
|
-
import { Select } from "@godxjp/ui/data-entry";
|
|
166
|
-
import { Alert, AlertTitle, AlertDescription } from "@godxjp/ui/feedback";
|
|
168
|
+
import { Select, SearchInput } from "@godxjp/ui/data-entry";
|
|
167
169
|
|
|
168
170
|
export default function InvoicesPage() {
|
|
169
171
|
return (
|
|
170
|
-
<PageContainer title="請求一覧"
|
|
171
|
-
<Flex direction="col" gap="
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
172
|
+
<PageContainer title="請求一覧" extra={<Button>請求を作成</Button>}>
|
|
173
|
+
<Flex direction="col" gap="sm">
|
|
174
|
+
{/* filters sit ABOVE the table they filter */}
|
|
175
|
+
<Flex direction="row" gap="sm" wrap>
|
|
176
|
+
<SearchInput aria-label="請求を検索" />
|
|
177
|
+
<Select width="auto" options={statuses} defaultValue="unpaid" aria-label="状態" />
|
|
178
|
+
<Button variant="outline">CSV</Button>
|
|
179
|
+
</Flex>
|
|
176
180
|
|
|
181
|
+
{/* `flush` because DataTable draws its own edges — a padded body double-insets it */}
|
|
177
182
|
<Card>
|
|
178
|
-
<CardHeader>
|
|
179
|
-
<CardTitle level={2}>未収</CardTitle>
|
|
180
|
-
<CardDescription>期限順。金額は税込。</CardDescription>
|
|
181
|
-
</CardHeader>
|
|
182
|
-
{/* `flush` because DataTable draws its own edges — a padded body double-insets it */}
|
|
183
183
|
<CardContent flush>
|
|
184
|
-
<DataTable data={rows} columns={columns} getRowId={(r) => r.id}
|
|
184
|
+
<DataTable data={rows} columns={columns} getRowId={(r) => r.id} />
|
|
185
185
|
</CardContent>
|
|
186
186
|
</Card>
|
|
187
|
-
|
|
188
|
-
<Flex direction="row" gap="sm" wrap>
|
|
189
|
-
<Select width="auto" options={statuses} defaultValue="unpaid" aria-label="状態" />
|
|
190
|
-
<Button variant="outline" size="sm">CSV</Button>
|
|
191
|
-
</Flex>
|
|
192
187
|
</Flex>
|
|
193
188
|
</PageContainer>
|
|
194
189
|
);
|
|
@@ -196,7 +191,8 @@ export default function InvoicesPage() {
|
|
|
196
191
|
```
|
|
197
192
|
|
|
198
193
|
Note what is absent: no colour, no pixel, no raw control, no `<div className="flex gap-4">` where
|
|
199
|
-
`Flex` exists
|
|
194
|
+
`Flex` exists — and no subtitle restating the title, no KPI row above the list, no banner, no Card
|
|
195
|
+
header repeating the page title. The page answers one question: which invoice next.
|
|
200
196
|
|
|
201
197
|
**Take the import path from the catalog, not from the group.** Every entry in
|
|
202
198
|
`components/<Name>.json` carries an `importPath`; it is the only one guaranteed to resolve. The
|
package/agent/anti-ai-tells.json
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
{
|
|
129
129
|
"body": "Empty state with a chrome / pastel 3D icon (coin, key, shield)\nfloating in the center. Looks like every NFT marketplace from\n2021.",
|
|
130
130
|
"category": "imagery",
|
|
131
|
-
"fix": "
|
|
131
|
+
"fix": "No illustration. In an admin screen an EmptyState is one\nsentence and one action (`<EmptyState title action />`, no `icon`).\nA first-run page outside a Card may take one small line icon via\n`icon={Inbox}`; never a large glyph or a 3D render.",
|
|
132
132
|
"name": "Floating 3D crypto icon"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
@@ -137,6 +137,60 @@
|
|
|
137
137
|
"fix": "Solid background (`--background`). If you need depth, use a\nsubtle 1px border + `--card` background tint. Reserve high-effort\nbackgrounds for pages where they matter (marketing hero, product\nshowcase) — not every internal screen.",
|
|
138
138
|
"name": "Decorative gradient mesh background"
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
"body": "Every section of a page wrapped in Card + CardHeader + CardTitle\n+ CardDescription. Boxes everywhere, so no box means anything.",
|
|
142
|
+
"category": "structure",
|
|
143
|
+
"fix": "Mark a section with `<Heading level={2}>` and section spacing\n(PageContainer children). Card only for a real boundary: a table\nsurface, a selectable item. ui-audit: card-per-section.",
|
|
144
|
+
"name": "Card per section"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"body": "A Card (or StatCard) inside a Card: two borders for one boundary.",
|
|
148
|
+
"category": "structure",
|
|
149
|
+
"fix": "One Card; group inside it with Heading + Flex or Separator.\nui-audit: nested-card.",
|
|
150
|
+
"name": "Nested Card"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"body": "PageContainer title=\"クーポン管理\" subtitle=\"クーポンの一覧です\".\nThe subtitle repeats the title in more words.",
|
|
154
|
+
"category": "copy",
|
|
155
|
+
"fix": "Omit subtitle. Use it only for a fact the title lacks (scope,\nowner, last sync time).",
|
|
156
|
+
"name": "Subtitle restating the title"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"body": "3–4 StatCards above a list or detail page: 件数, 公開中, 合計.\nA row count is not a KPI.",
|
|
160
|
+
"category": "structure",
|
|
161
|
+
"fix": "Drop the row; the total is `<Pagination showTotal>`, a record's\nfigures are Descriptions rows. StatCard rows belong on a dashboard\nwith comparison or trend (delta). ui-audit: kpi-row-off-dashboard.",
|
|
162
|
+
"name": "KPI tile row on a non-dashboard page"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"body": "A large icon + title + description centred inside a Card or\ntable surface.",
|
|
166
|
+
"category": "structure",
|
|
167
|
+
"fix": "One sentence + one action: `<EmptyState title action />`, no\n`icon` (or variant=\"compact\"). ui-audit: empty-state-icon-in-card.",
|
|
168
|
+
"name": "Icon EmptyState inside a Card"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"body": "An info Alert at the top of each page explaining the page, or\nseveral Alerts repeating the same notice.",
|
|
172
|
+
"category": "structure",
|
|
173
|
+
"fix": "Alert only for a state that blocks or changes the task. Field\nerrors in FormField, results in a toast, standing info in plain\ntext. ui-audit: alert-per-page.",
|
|
174
|
+
"name": "Alert banner on every page"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"body": "A Badge in every table column, tiers and categories in\ndifferent colours. Colour stops meaning status.",
|
|
178
|
+
"category": "visual",
|
|
179
|
+
"fix": "Badge for status only, one status column per table. Tiers,\ncategories and scopes are plain text (or one neutral tone).\nui-audit: badge-wall.",
|
|
180
|
+
"name": "Badge wall"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"body": "The same gap everywhere: between sections, between a heading\nand its content, between fields. No hierarchy.",
|
|
184
|
+
"category": "layout",
|
|
185
|
+
"fix": "Two steps: section step (PageContainer children / gap=\"lg\")\nand item step (gap=\"sm\" inside a section).",
|
|
186
|
+
"name": "Uniform spacing"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"body": "A Lucide icon before each CardTitle, section heading and button\nlabel. Decoration that adds no meaning.",
|
|
190
|
+
"category": "visual",
|
|
191
|
+
"fix": "Text alone. An icon only where it is the affordance (icon-only\nbutton with aria-label) or carries status.",
|
|
192
|
+
"name": "Icon in every heading and button"
|
|
193
|
+
},
|
|
140
194
|
{
|
|
141
195
|
"body": "Settings page with 40 form fields in a single scroll. User\nloses their place, can't find the field they came for.",
|
|
142
196
|
"category": "structure",
|
|
@@ -146,7 +200,7 @@
|
|
|
146
200
|
{
|
|
147
201
|
"body": "Click a Button → Dialog opens → click \"Edit\" → another Dialog\nopens → click \"Confirm\" → AlertDialog opens. Triple stack;\nuser loses context.",
|
|
148
202
|
"category": "structure",
|
|
149
|
-
"fix": "Use Sheet for the FIRST level (side panel), Dialog for the\nconfirm. Or, redesign the flow so the edit is INLINE in the\nfirst Dialog (no second Dialog needed).
|
|
203
|
+
"fix": "Use Sheet for the FIRST level (side panel), Dialog for the\nconfirm. Or, redesign the flow so the edit is INLINE in the\nfirst Dialog (no second Dialog needed). A destructive confirm\n(AlertDialog preset, or Dialog variant=\"destructive\") is correct —\nbut ONE deep, not three.",
|
|
150
204
|
"name": "Modal-in-modal-in-modal"
|
|
151
205
|
},
|
|
152
206
|
{
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"defaultValue": "\"normal\"",
|
|
62
|
-
"description": "Where an over-long unbroken token may break. THE prop for a machine identifier — an email address, a coupon code, a login id, a template key — in a DataTable/TableCell or a Descriptions value: `break=\"anywhere\"` emits `overflow-wrap: anywhere` (and releases a table cell's inherited `nowrap`), so the column shrinks to the viewport instead of scrolling the page. Use it INSTEAD of `className=\"[overflow-wrap:anywhere] break-words whitespace-normal\"`. NOT `whitespace=\"pre-wrap\"`: that is for typed line breaks, and its `break-word` does not lower min-content, so in a table cell it breaks nothing (measured: a 71-char token holds a 320px table at 630px with pre-wrap, 320px with break=\"anywhere\"). Independent of `whitespace` (both may be set); `truncate` wins (dev builds warn); `clamp` composes.",
|
|
62
|
+
"description": "Where an over-long unbroken token may break. THE prop for a machine identifier — an email address, a coupon code, a login id, a template key — in a DataTable/TableCell or a Descriptions value: `break=\"anywhere\"` emits `overflow-wrap: anywhere` (and releases a table cell's inherited `nowrap`), so the column shrinks to the viewport instead of scrolling the page. Use it INSTEAD of `className=\"[overflow-wrap:anywhere] break-words whitespace-normal\"`. NOT `whitespace=\"pre-wrap\"`: that is for typed line breaks, and its `break-word` does not lower min-content, so in a table cell it breaks nothing (measured: a 71-char token holds a 320px table at 630px with pre-wrap, 320px with break=\"anywhere\"). `break=\"phrase\"` is THE prop for Japanese PROSE in a narrow measure — a trainee's katakana name, an organisation, a status in a DataTable cell: it breaks between phrases (`word-break: auto-phrase`, Chrome 119+) instead of between any two characters, never starts a line with a small kana or 「ー」 (`line-break: strict`), adds `text-wrap: pretty`, and releases the cell's `nowrap` (measured in a 974px 8-column table: 「チャン・ヴァ|ン・ミン」 → 「チャン・|ヴァン・|ミン」, 4 mid-katakana breaks → 0). It keeps every phrase whole, so the column is as wide as its longest phrase: a 20+ character katakana compound scrolls the table rather than being cut. Browsers without `auto-phrase` keep their own CJK breaking. Independent of `whitespace` (both may be set); `truncate` wins (dev builds warn); `clamp` composes.",
|
|
63
63
|
"name": "break",
|
|
64
|
-
"type": "\"normal\" | \"anywhere\""
|
|
64
|
+
"type": "\"normal\" | \"anywhere\" | \"phrase\""
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"description": "Tabular figures for aligned numbers.",
|
|
@@ -195,6 +195,7 @@
|
|
|
195
195
|
"usage": [
|
|
196
196
|
"DO use `<Text>` for ALL body / inline / caption text instead of a styled `<span>`/`<p>`. Pick `size` from the scale; never write `text-[13px]`/`text-[11px]` or `font-semibold` by hand.",
|
|
197
197
|
"DO use `tone` for colour (`muted`/`primary`/semantic), `tabular` for numbers, `mono` for codes — not `className=\"text-muted-foreground font-mono tabular-nums\"`.",
|
|
198
|
+
"DO use `break=\"phrase\"` for Japanese prose (a name, an organisation, a status) in a table cell — never `className=\"[word-break:auto-phrase]\"`; it is not for a machine id, which is `break=\"anywhere\"`.",
|
|
198
199
|
"DO use `break=\"anywhere\"` for an email, code or id in a table cell or Descriptions value — never `className=\"[overflow-wrap:anywhere] break-words whitespace-normal\"`, and never `whitespace=\"pre-wrap\"`, which does not shrink a table column.",
|
|
199
200
|
"DO use `clamp={n}` for a description limited to n lines (card grids) and `truncate` for a one-line ellipsis — never `className=\"line-clamp-2\"` (banned utility). They are mutually exclusive; `clamp` wins.",
|
|
200
201
|
"For a heading, use `<Heading level>` instead of a large-size `<Text>`.",
|
|
@@ -205,6 +206,7 @@
|
|
|
205
206
|
"A muted caption under a value: `<Text size=\"xs\" tone=\"muted\">2026年5月度</Text>`.",
|
|
206
207
|
"A monospace id in a list row: `<Text size=\"xs\" mono tone=\"muted\">RC-204881</Text>`.",
|
|
207
208
|
"A staff email in a narrow DataTable cell at 320px, wrapping instead of scrolling the page: `<Text size=\"sm\" break=\"anywhere\">{staff.email}</Text>`.",
|
|
209
|
+
"A trainee's katakana name in a DataTable cell, breaking between phrases instead of mid-katakana: `render: (row) => <Text break=\"phrase\">{row.name}</Text>` (pair long-heading columns with `headerWrap: true`).",
|
|
208
210
|
"An emphasized inline figure: `<Text weight=\"medium\" tabular>¥1,240,000</Text>`.",
|
|
209
211
|
"A service-card description clamped to 2 lines on a narrow (390px) index: `<Text as=\"p\" size=\"sm\" tone=\"muted\" clamp={2}>{description}</Text>`.",
|
|
210
212
|
"An issue subject linking out of a table cell, wrapping to two lines: `<Text asChild link truncate={false}><Link href={`/view/${key}`}>{subject}</Link></Text>`.",
|