@godxjp/ui 28.13.0 → 30.0.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/agent/START-HERE.md +29 -10
- package/agent/components/Anchor.json +6 -1
- package/agent/components/AppShell.json +1 -1
- package/agent/components/AreaChart.json +19 -1
- package/agent/components/BarChart.json +11 -1
- package/agent/components/Cascader.json +1 -1
- package/agent/components/CompactBarTrend.json +1 -1
- package/agent/components/DataState.json +1 -1
- package/agent/components/DataTable.json +4 -4
- package/agent/components/FormField.json +1 -1
- package/agent/components/InfiniteQueryState.json +1 -1
- package/agent/components/Input.json +1 -1
- package/agent/components/LineChart.json +20 -2
- package/agent/components/ListRow.json +1 -1
- package/agent/components/Masonry.json +1 -1
- package/agent/components/MasterDetail.json +1 -1
- package/agent/components/PasswordStrength.json +1 -1
- package/agent/components/PermissionMatrix.json +1 -1
- package/agent/components/Select.json +1 -0
- package/agent/components/Sidebar.json +1 -1
- package/agent/components/Table.json +8 -3
- package/agent/components/ThemeScope.json +49 -0
- package/agent/components/Topbar.json +1 -0
- package/agent/components/TopbarItem.json +2 -1
- package/agent/components/Transfer.json +1 -1
- package/agent/components/TreeSelect.json +1 -1
- package/agent/components/UploadCropDialog.json +1 -1
- package/agent/components/formatDate.json +1 -1
- package/agent/components-index.json +5 -0
- package/agent/components.json +138 -30
- package/agent/index.json +19 -9
- package/agent/llms.txt +10 -10
- package/agent/patterns/tenant-brand-color.json +28 -0
- package/agent/patterns-index.json +27 -0
- package/agent/patterns.json +28 -0
- package/agent/rules.json +15 -0
- package/agent/tokens.json +5055 -1060
- package/dist/app/index.d.ts +3 -0
- package/dist/app/index.js +3 -0
- package/dist/app/tenant-theme.d.ts +80 -0
- package/dist/app/tenant-theme.js +154 -0
- package/dist/app/theme-axes.d.ts +14 -1
- package/dist/app/theme-axes.js +24 -31
- package/dist/components/charts/chart-cartesian.d.ts +5 -1
- package/dist/components/charts/chart-cartesian.js +15 -8
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/badge.js +21 -3
- package/dist/components/data-display/carousel.js +4 -4
- package/dist/components/data-display/data-table.js +13 -2
- package/dist/components/data-display/permission-matrix.js +1 -1
- package/dist/components/data-display/table.d.ts +11 -2
- package/dist/components/data-display/table.js +19 -3
- package/dist/components/data-entry/control-appearance.d.ts +12 -6
- package/dist/components/data-entry/control-appearance.js +1 -1
- package/dist/components/data-entry/select.js +4 -3
- package/dist/components/feedback/dialog.js +6 -3
- package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
- package/dist/components/feedback/overlay-header-tone.js +4 -4
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +6 -9
- package/dist/components/feedback/sonner.js +16 -3
- package/dist/components/general/button.js +22 -5
- package/dist/components/layout/affix.js +15 -1
- package/dist/components/layout/sidebar.js +7 -1
- package/dist/components/navigation/anchor.d.ts +1 -1
- package/dist/components/navigation/anchor.js +5 -4
- package/dist/components/navigation/app-setting-picker.js +1 -1
- package/dist/components/navigation/pagination.js +1 -1
- package/dist/components/navigation/tabs.js +15 -2
- package/dist/components/query/infinite-query-state.d.ts +22 -6
- package/dist/contracts/measurement.json +1 -1
- package/dist/lib/control-styles.d.ts +31 -11
- package/dist/lib/control-styles.js +6 -6
- package/dist/lib/overlay-portal.d.ts +20 -0
- package/dist/lib/overlay-portal.js +93 -0
- package/dist/props/components/app.prop.d.ts +12 -0
- package/dist/props/components/charts.prop.d.ts +30 -0
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/navigation.prop.d.ts +21 -2
- package/dist/props/components/query.prop.d.ts +36 -2
- package/dist/props/registry.d.ts +46 -1
- package/dist/props/registry.js +38 -3
- package/dist/styles/alert-layout.css +39 -19
- package/dist/styles/badge-layout.css +11 -7
- package/dist/styles/base.css +14 -5
- package/dist/styles/card-layout.css +29 -16
- package/dist/styles/chart-layout.css +27 -5
- package/dist/styles/control.css +225 -103
- package/dist/styles/data-display-layout.css +178 -76
- package/dist/styles/data-entry-layout.css +35 -99
- package/dist/styles/dialog-layout.css +58 -28
- package/dist/styles/float-button-layout.css +5 -5
- package/dist/styles/focus-ring.css +11 -7
- package/dist/styles/layout.css +53 -25
- package/dist/styles/logo-layout.css +3 -3
- package/dist/styles/motion.css +2 -2
- package/dist/styles/navigation-layout.css +117 -44
- package/dist/styles/shell-layout.css +90 -124
- package/dist/styles/table-layout.css +66 -24
- package/dist/styles/text-layout.css +13 -4
- package/dist/styles/toggle.css +9 -3
- package/dist/tokens/components/actions.css +1 -1
- package/dist/tokens/components/attachments.css +4 -4
- package/dist/tokens/components/badge.css +4 -4
- package/dist/tokens/components/banner.css +1 -1
- package/dist/tokens/components/callout.css +1 -1
- package/dist/tokens/components/card.css +13 -8
- package/dist/tokens/components/chart.css +11 -2
- package/dist/tokens/components/chat-bubble.css +2 -2
- package/dist/tokens/components/control.css +41 -22
- package/dist/tokens/components/conversations.css +2 -1
- package/dist/tokens/components/data-display.css +23 -18
- package/dist/tokens/components/data-entry.css +1 -1
- package/dist/tokens/components/descriptions.css +2 -2
- package/dist/tokens/components/draggable-panel.css +2 -2
- package/dist/tokens/components/feedback.css +29 -9
- package/dist/tokens/components/float-button.css +1 -1
- package/dist/tokens/components/legal-document.css +2 -2
- package/dist/tokens/components/logo.css +2 -2
- package/dist/tokens/components/mega-menu.css +6 -4
- package/dist/tokens/components/navigation.css +27 -13
- package/dist/tokens/components/segmented.css +11 -6
- package/dist/tokens/components/separator.css +1 -1
- package/dist/tokens/components/shell.css +24 -10
- package/dist/tokens/components/table.css +13 -5
- package/dist/tokens/components/thought-chain.css +2 -2
- package/dist/tokens/components/toggle.css +3 -1
- package/dist/tokens/components/tree.css +3 -1
- package/dist/tokens/components/upload.css +8 -8
- package/dist/tokens/components/welcome.css +1 -1
- package/dist/tokens/foundation.css +30 -3
- package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
- package/docs/CUSTOMER-THEMING.md +637 -1
- package/docs/FRAME-COVERAGE-REPORT.md +3 -2
- package/docs/GLASSMORPHISM-STANDARD.md +196 -0
- package/docs/THEME-API-COVERAGE.md +538 -0
- package/docs/TOKEN-RESOLUTION.md +195 -0
- package/docs/TOKENS.md +63 -24
- package/docs/asset-modules.d.ts +7 -0
- package/docs/data-display/charts.tsx +80 -0
- package/docs/data-display/data-table/index.tsx +30 -0
- package/docs/data-display/popover.tsx +1 -1
- package/docs/data-display/table.tsx +52 -0
- package/docs/feedback/sheet.tsx +10 -10
- package/docs/foundation/density.tsx +4 -4
- package/docs/i18n/messages/en.json +506 -0
- package/docs/i18n/messages/ja.json +506 -0
- package/docs/i18n/messages/vi.json +506 -0
- package/docs/layout/account-chip.tsx +2 -2
- package/docs/layout/responsive-grid.tsx +1 -1
- package/docs/navigation/toolbar.tsx +20 -12
- package/docs/providers/theme-scope.tsx +186 -0
- package/docs/showcase/caimono-price-comparison.tsx +911 -0
- package/docs/showcase/case4-login.tsx +2 -2
- package/docs/showcase/permission-matrix.tsx +13 -5
- package/docs/showcase/table-pagination.tsx +2 -1
- package/docs/showcase/tenant-brand-color.tsx +338 -0
- package/docs/showcase/theme-lab.tsx +2125 -0
- package/docs/themes/flat.css +462 -0
- package/docs/themes/glassmorphism.css +958 -0
- package/docs/themes/index.ts +219 -0
- package/docs/themes/neubrutalism.css +475 -0
- package/package.json +4 -3
- package/scripts/explain-token.mjs +382 -0
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`
|
|
6
|
+
**This catalog describes `@godxjp/ui` 30.0.0.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -43,24 +43,28 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
|
|
|
43
43
|
**You cannot run a process** (ChatGPT web · Claude.ai · anything fetching URLs)
|
|
44
44
|
→ These files are for you. Fetch in this order:
|
|
45
45
|
|
|
46
|
-
0. `patterns-index.json` —
|
|
46
|
+
0. `patterns-index.json` — 20 whole-task patterns as name + tagline + tags. **If your
|
|
47
47
|
task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
|
|
48
48
|
filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
|
|
49
49
|
copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
|
|
50
|
-
1. `components-index.json` — 45 KB, all
|
|
50
|
+
1. `components-index.json` — 45 KB, all 171 components as name + group +
|
|
51
51
|
tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
|
|
52
52
|
names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
|
|
53
53
|
`SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
|
|
54
54
|
first; it exists because that is the mistake.
|
|
55
|
-
2. `components/<Name>.json` — one file per component (1 KB–
|
|
55
|
+
2. `components/<Name>.json` — one file per component (1 KB–34 KB, median 6 KB), carrying its props,
|
|
56
56
|
its `importPath`, and its examples. Fetch only the handful you picked in step 1.
|
|
57
|
-
3. `rules.json` —
|
|
57
|
+
3. `rules.json` — 50 cardinal rules. The ones about raw HTML and hardcoded colour are not
|
|
58
58
|
style advice.
|
|
59
|
-
4. `tokens.json` —
|
|
59
|
+
4. `tokens.json` — 2070 design tokens, each tagged with its `tier`. **If you were handed a
|
|
60
|
+
brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
|
|
61
|
+
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
62
|
+
1756 `component` entries are per-part knobs; reach for one only when a role is
|
|
63
|
+
right everywhere except one component.
|
|
60
64
|
5. `anti-ai-tells.json` — 26 shapes that make generated UI look generated, each with the
|
|
61
65
|
fix. Read before you reach for a gradient hero or a wall of coloured chips.
|
|
62
66
|
|
|
63
|
-
**Do not fetch `components.json`.** It is 1.
|
|
67
|
+
**Do not fetch `components.json`.** It is 1.2 MB, and most URL fetchers truncate a
|
|
64
68
|
response that size and return the head without telling you. You get the first few entries, believe
|
|
65
69
|
you read the catalog, and answer the rest from memory — which is the failure this file exists to
|
|
66
70
|
prevent. The per-component files say the same thing without the cliff.
|
|
@@ -131,9 +135,24 @@ So: **build any layout you like out of primitives, and express every visual deci
|
|
|
131
135
|
a documented prop.** You keep full freedom of composition and lose none of the theming. The moment
|
|
132
136
|
you write a literal, that pixel stops following the theme and the tenant override silently skips it.
|
|
133
137
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
### Which token do you actually set — the `tier` field answers it
|
|
139
|
+
|
|
140
|
+
Every entry in `tokens.json` carries a `tier`, and the tier tells you whether you are invited to set
|
|
141
|
+
it. Start at the top and **stop at the first tier that does the job**; each step down is a value that
|
|
142
|
+
has stopped following the brand.
|
|
143
|
+
|
|
144
|
+
| `tier` | count | what it is | set it? |
|
|
145
|
+
|---|---|---|---|
|
|
146
|
+
| `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
|
|
147
|
+
| `semantic` | 103 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
|
|
148
|
+
| `component` | 1756 | 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
|
+
|
|
150
|
+
A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
|
|
151
|
+
value, so the real default is computed where the element paints it. Set it and yours wins.
|
|
152
|
+
|
|
153
|
+
**There is no fourth option.** If a colour, radius or size you need is not in this file, the answer
|
|
154
|
+
is not a hand-written CSS rule — say the token is missing and ask. A literal is invisible to every
|
|
155
|
+
theme, every tenant scope and every audit in this package.
|
|
137
156
|
|
|
138
157
|
---
|
|
139
158
|
|
|
@@ -46,7 +46,12 @@
|
|
|
46
46
|
"type": "number"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
"description": "
|
|
49
|
+
"description": "gh#890. The scroll box the sections are measured in AND `Affix` pins the nav against — one function, both halves. `Affix`'s own name and shape (`AffixTargetProp`), the same lazy getter `FloatButton.BackTop.target` already spells here. `null`, or an absent `target`, means the viewport. Wins over `getContainer` when both are given.",
|
|
50
|
+
"name": "target",
|
|
51
|
+
"type": "() => Window | HTMLElement | null"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "antd `getContainer`, default `() => window` — the scroll box holding the sections. Superseded by `target` (gh#890), which mirrors `Affix`'s own spelling for the identical idea; kept live for a call site written before `target` existed.",
|
|
50
55
|
"name": "getContainer",
|
|
51
56
|
"type": "() => HTMLElement | Window"
|
|
52
57
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { AppShell, Sidebar } from \"@godxjp/ui/layout\";\nimport { LayoutDashboard, Users } from \"lucide-react\";\nimport { router } from \"@inertiajs/react\";\n\nconst sidebar = (\n <Sidebar\n activeId=\"/dashboard\"\n onSelect={(id) => router.visit(id)}\n sections={[{ items: [\n { id: \"/dashboard\", label: \"ダッシュボード\", icon: LayoutDashboard },\n { id: \"/users\", label: \"ユーザー\", icon: Users },\n ] }]}\n product={{ name: \"JOVY CRM\", role: \"本部\", color: \"var(--color-primary)\" }}\n />\n);\n\nexport function CrmLayout({ children }: {
|
|
2
|
+
"example": "import { AppShell, Sidebar } from \"@godxjp/ui/layout\";\nimport { LayoutDashboard, Users } from \"lucide-react\";\nimport { router } from \"@inertiajs/react\";\n\nconst sidebar = (\n <Sidebar\n activeId=\"/dashboard\"\n onSelect={(id) => router.visit(id)}\n sections={[{ items: [\n { id: \"/dashboard\", label: \"ダッシュボード\", icon: LayoutDashboard },\n { id: \"/users\", label: \"ユーザー\", icon: Users },\n ] }]}\n product={{ name: \"JOVY CRM\", role: \"本部\", color: \"var(--color-primary)\" }}\n />\n);\n\nexport function CrmLayout({ children }: { children: React.ReactNode }) {\n return <AppShell sidebar={sidebar}>{children}</AppShell>;\n}",
|
|
3
3
|
"group": "layout",
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "AppShell",
|
|
@@ -73,12 +73,28 @@
|
|
|
73
73
|
"name": "stacked",
|
|
74
74
|
"type": "boolean"
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
"description": "Explicit [min, max] for the VALUE axis (y vertical, x on a horizontal bar). Omit to let the data set it. A non-zero baseline is a charting-ETHICS call: legitimate where the SHAPE is the message and zero is not a reference (price, temperature, an index, a latency percentile), misleading wherever the reader compares magnitudes — which is every bar chart.",
|
|
78
|
+
"name": "valueDomain",
|
|
79
|
+
"type": "[number, number]"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"description": "Explicit tick positions on the value axis, in data units. Values outside valueDomain are not drawn.",
|
|
83
|
+
"name": "valueTicks",
|
|
84
|
+
"type": "number[]"
|
|
85
|
+
},
|
|
76
86
|
{
|
|
77
87
|
"defaultValue": "false",
|
|
78
88
|
"description": "Render smooth (monotone) areas instead of straight segments.",
|
|
79
89
|
"name": "curved",
|
|
80
90
|
"type": "boolean"
|
|
81
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"defaultValue": "false",
|
|
94
|
+
"description": "Draw a marker at every data point. Off by default — markers crowd a dense series.",
|
|
95
|
+
"name": "showDots",
|
|
96
|
+
"type": "boolean"
|
|
97
|
+
},
|
|
82
98
|
{
|
|
83
99
|
"description": "Message shown when `data` is empty.",
|
|
84
100
|
"name": "emptyMessage",
|
|
@@ -96,7 +112,9 @@
|
|
|
96
112
|
"DO import from the charts entry: `import { AreaChart } from \"@godxjp/ui/charts\";` (recharts optional peer required).",
|
|
97
113
|
"DO import only the chart a screen uses — `import { AreaChart } from \"@godxjp/ui/charts/area-chart\";` — when the `./charts` barrel should not link the whole chart family. Without the `recharts` peer the build then fails ONCE, naming the package and the fix.",
|
|
98
114
|
"DO use `stacked` to show how parts accumulate into a total over time.",
|
|
99
|
-
"DON'T overlay more than 2-3 unstacked areas — fill opacity makes dense overlays unreadable; switch to LineChart."
|
|
115
|
+
"DON'T overlay more than 2-3 unstacked areas — fill opacity makes dense overlays unreadable; switch to LineChart.",
|
|
116
|
+
"DO split the band's hue from the line's with `series[].fillColor` when a brand specifies both (a dark line over a lighter wash). The band's DENSITY is the `--chart-area-fill-alpha` theme token, not a prop.",
|
|
117
|
+
"DON'T tune the band by dropping a page-local CSS rule on `.recharts-area-area` — `--chart-area-fill-alpha` is the supported knob and it follows a scoped [data-tenant] region."
|
|
100
118
|
],
|
|
101
119
|
"useCases": [
|
|
102
120
|
"Cumulative volume over time (e.g. total transactions per day).",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "ChartDatum[]"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
"description": "Plotted series: { dataKey, label?, color? }.",
|
|
14
|
+
"description": "Plotted series: { dataKey, label?, color?, fillColor? }. `fillColor` paints the filled band independently of the line's `color`; it defaults to `color`.",
|
|
15
15
|
"name": "series",
|
|
16
16
|
"required": true,
|
|
17
17
|
"type": "ChartSeriesProp[]"
|
|
@@ -67,6 +67,16 @@
|
|
|
67
67
|
"name": "numberFormat",
|
|
68
68
|
"type": "Intl.NumberFormatOptions"
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
"description": "Explicit [min, max] for the VALUE axis (y vertical, x on a horizontal bar). Omit to let the data set it. A non-zero baseline is a charting-ETHICS call: legitimate where the SHAPE is the message and zero is not a reference (price, temperature, an index, a latency percentile), misleading wherever the reader compares magnitudes — which is every bar chart.",
|
|
72
|
+
"name": "valueDomain",
|
|
73
|
+
"type": "[number, number]"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "Explicit tick positions on the value axis, in data units. Values outside valueDomain are not drawn.",
|
|
77
|
+
"name": "valueTicks",
|
|
78
|
+
"type": "number[]"
|
|
79
|
+
},
|
|
70
80
|
{
|
|
71
81
|
"defaultValue": "false",
|
|
72
82
|
"description": "Stack series into one bar instead of grouping side by side.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "{`import { Cascader } from \"@godxjp/ui/data-entry\";\n\nconst REGIONS = [\n {\n value: \"jp\",\n label: \"日本\",\n
|
|
2
|
+
"example": "{`import { Cascader } from \"@godxjp/ui/data-entry\";\n\nconst REGIONS = [\n {\n value: \"jp\",\n label: \"日本\",\n children: [\n {\n value: \"tokyo\",\n label: \"東京都\",\n children: [\n { value: \"shinjuku\", label: \"新宿区\" },\n { value: \"shibuya\", label: \"渋谷区\" },\n ],\n },\n ],\n },\n {\n value: \"vn\",\n label: \"Việt Nam\",\n children: [\n {\n value: \"hcm\",\n label: \"TP. Hồ Chí Minh\",\n children: [\n { value: \"q1\", label: \"Quận 1\" },\n { value: \"q3\", label: \"Quận 3\" },\n ],\n },\n ],\n },\n];\n\n// Controlled single-path\nfunction RegionPicker() {\n const [path, setPath] = React.useState<string[]>([]);\n\n return (\n <Cascader\n options={REGIONS}\n value={path}\n onValueChange={(v) => setPath(v as string[])}\n showSearch\n placeholder=\"Select region…\"\n />\n );\n}\n\n// Multi-path (multiple selection)\nfunction MultiRegionPicker() {\n const [paths, setPaths] = React.useState<string[][]>([]);\n\n return (\n <Cascader\n options={REGIONS}\n multiple\n value={paths}\n onValueChange={(v) => setPaths(v as string[][])}\n showSearch\n />\n );\n}\n\n// With custom field names (data uses 'name'/'id'/'nodes')\n<Cascader\n options={rawApiData}\n fieldNames={{ label: \"name\", value: \"id\", children: \"nodes\" }}\n defaultValue={[\"dept-1\", \"team-3\"]}\n/>\n\n// changeOnSelect: lets user pick a branch node (not only leaves)\n<Cascader\n options={REGIONS}\n changeOnSelect\n onValueChange={(v) => console.log(\"path\", v)}\n/>\n`}",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "Cascader",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { CompactBarTrend } from \"@godxjp/ui/charts/compact-bar-trend\";\n\n<CompactBarTrend\n label={t(\"dashboard.newOrganizations7d\")}\n description={t(\"dashboard.newOrganizationsHint\")}\n data={trend}\n categoryKey=\"date\"\n valueKey=\"count\"\n emphasizedIndex={-1}\n size=\"xs\"\n footer={<Text size=\"xs\" tone=\"muted\">{t(\"dashboard.lastUpdated\", { at })}</Text>}\n/>",
|
|
2
|
+
"example": "import { CompactBarTrend } from \"@godxjp/ui/charts/compact-bar-trend\";\nimport { Text } from \"@godxjp/ui/general\";\n\n<CompactBarTrend\n label={t(\"dashboard.newOrganizations7d\")}\n description={t(\"dashboard.newOrganizationsHint\")}\n data={trend}\n categoryKey=\"date\"\n valueKey=\"count\"\n emphasizedIndex={-1}\n size=\"xs\"\n footer={<Text size=\"xs\" tone=\"muted\">{t(\"dashboard.lastUpdated\", { at: new Date().toISOString() })}</Text>}\n/>",
|
|
3
3
|
"group": "data-display",
|
|
4
4
|
"importPath": "@godxjp/ui/charts/compact-bar-trend",
|
|
5
5
|
"name": "CompactBarTrend",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { DataState } from \"@godxjp/ui/query\";\n\n<DataState query={membersQuery} skeleton={<SkeletonTable />} isEmpty={(d) => d.items.length === 0} empty={<EmptyState title=\"会員なし\" />}>\n {(d) => <MemberTable items={d.items} />}\n</DataState>",
|
|
2
|
+
"example": "import { useQuery } from \"@tanstack/react-query\";\nimport { DataState } from \"@godxjp/ui/query\";\n\ntype MembersPage = { items: { id: string; name: string }[] };\n\n// T flows from the query, so `d` is MembersPage in BOTH callbacks. If you find yourself\n// annotating them, the query is untyped — fix that, never write `(d: any)`.\nconst membersQuery = useQuery<MembersPage>({ queryKey: [\"members\"], queryFn: fetchMembers });\n\n<DataState query={membersQuery} skeleton={<SkeletonTable />} isEmpty={(d) => d.items.length === 0} empty={<EmptyState title=\"会員なし\" />}>\n {(d) => <MemberTable items={d.items} />}\n</DataState>",
|
|
3
3
|
"group": "data-display",
|
|
4
4
|
"importPath": "@godxjp/ui/query",
|
|
5
5
|
"name": "DataState",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"absorbed": [
|
|
3
3
|
"DataGrid"
|
|
4
4
|
],
|
|
5
|
-
"example": "import { useState } from \"react\";\nimport { Badge, DataTable, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { EmptyState } from \"@godxjp/ui/data-display\";\n\ntype Invoice = {\n id: string;\n customer: string;\n amount: number;\n status: \"paid\" | \"pending\" | \"overdue\";\n};\n\nconst columns: ColumnDef<Invoice>[] = [\n { key: \"id\", header: \"Invoice #\", width: \"w-32\" },\n { key: \"customer\", header: \"Customer\" },\n {\n key: \"status\",\n header: \"Status\",\n render: (row) => (\n <Badge\n
|
|
5
|
+
"example": "import { useState } from \"react\";\nimport { Badge, DataTable, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { EmptyState } from \"@godxjp/ui/data-display\";\n\ntype Invoice = {\n id: string;\n customer: string;\n amount: number;\n status: \"paid\" | \"pending\" | \"overdue\";\n};\n\nconst columns: ColumnDef<Invoice>[] = [\n { key: \"id\", header: \"Invoice #\", width: \"w-32\" },\n { key: \"customer\", header: \"Customer\" },\n {\n key: \"status\",\n header: \"Status\",\n render: (row) => (\n <Badge\n tone={\n row.status === \"paid\" ? \"success\" : row.status === \"overdue\" ? \"destructive\" : \"warning\"\n }\n >\n {row.status}\n </Badge>\n ),\n },\n { key: \"amount\", header: \"Amount\", align: \"right\", sortable: true },\n];\n\nexport default function InvoiceList({\n invoices,\n loading,\n}: {\n invoices: Invoice[];\n loading: boolean;\n}) {\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [sort, setSort] = useState<{ key: string; direction: \"asc\" | \"desc\" } | undefined>();\n\n return (\n <DataTable\n data={invoices}\n columns={columns}\n getRowId={(row) => row.id}\n selectable\n selected={selected}\n onSelectChange={setSelected}\n sort={sort}\n onSortChange={setSort}\n loading={loading}\n empty={\n <EmptyState\n title=\"No invoices found\"\n description=\"Adjust your filters or create a new invoice.\"\n />\n }\n >\n <DataTable.Toolbar>\n <DataTable.BulkActions>\n <button type=\"button\" onClick={() => setSelected(new Set())}>\n Mark paid\n </button>\n </DataTable.BulkActions>\n <DataTable.DensityToggle />\n </DataTable.Toolbar>\n </DataTable>\n );\n}",
|
|
6
6
|
"group": "data-display",
|
|
7
7
|
"importPath": "@godxjp/ui/data-display",
|
|
8
8
|
"name": "DataTable",
|
|
@@ -86,13 +86,13 @@
|
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"defaultValue": "'default'",
|
|
89
|
-
"description": "Named collection contract — the SAME preset the Table primitive owns, forwarded to the table DataTable renders. 'default' emits NO attribute and matches no selector, so an existing DataTable is byte-identical. 'action-collection' is the canonical dense approval/action queue: below collapseBelow the desktop INTRINSIC column widths give way to the token-owned column-PRIORITY measures (--table-action-collection-*) under table-layout: fixed, cells wrap, and the bordered surface drops its --table-surface-min-inline-size floor — so requester · target · reason · requested date · row actions all stay inside a 390px frame with no horizontal scroll. Mark each column with `priority` on its ColumnDef. Semantics are untouched (no display change, no role rewriting, no card swap), so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. Measured: table 1182 / 766 / 388px at 1440 / 1024 / 390, document scrollWidth === clientWidth at every width, LTR and RTL.",
|
|
89
|
+
"description": "Named collection contract — the SAME preset the Table primitive owns, forwarded to the table DataTable renders. 'default' emits NO attribute and matches no selector, so an existing DataTable is byte-identical. 'action-collection' is the canonical dense approval/action queue: below collapseBelow the desktop INTRINSIC column widths give way to the token-owned column-PRIORITY measures (--table-action-collection-*) under table-layout: fixed, cells wrap, and the bordered surface drops its --table-surface-min-inline-size floor — so requester · target · reason · requested date · row actions all stay inside a 390px frame with no horizontal scroll. Mark each column with `priority` on its ColumnDef. Semantics are untouched (no display change, no role rewriting, no card swap), so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. Measured: table 1182 / 766 / 388px at 1440 / 1024 / 390, document scrollWidth === clientWidth at every width, LTR and RTL. 'stacked-record-collection' is the other direction, for a WIDE, HETEROGENEOUS record set that has no sensible narrow column measure: below collapseBelow the <thead> hides and every <tr> becomes a bordered key-value card (--table-stacked-collection-*). Each cell then carries its column's header inline above the value, DERIVED from the same ColumnDef.header the <th> uses — so the card cannot drift from the table, and a column with a deliberately empty header names itself with ariaLabel (gh#864). The labels are aria-hidden: the real <th> is still in the DOM and remains the accessible-name source, so screen-reader table navigation is unchanged at every width. Reach for this instead of building a parallel Card tree beside the table; two trees for one dataset means two sets of labels to keep in sync.",
|
|
90
90
|
"name": "preset",
|
|
91
|
-
"type": "'default' | 'action-collection'"
|
|
91
|
+
"type": "'default' | 'action-collection' | 'stacked-record-collection'"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"defaultValue": "'sm'",
|
|
95
|
-
"description": "Step at which preset=\"action-collection\" switches to the compact priority measures,
|
|
95
|
+
"description": "Step at which preset=\"action-collection\" switches to the compact priority measures, or preset=\"stacked-record-collection\" folds its rows into cards. Measured against the TABLE'S OWN container (a container query on sm 40rem · md 48rem · lg 64rem · xl 80rem), not the viewport — a table inside a master rail collapses before the page does, and the same table folds by the width it is GIVEN. Ignored while preset is 'default'.",
|
|
96
96
|
"name": "collapseBelow",
|
|
97
97
|
"type": "'sm' | 'md' | 'lg' | 'xl'"
|
|
98
98
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { FormField, Input } from \"@godxjp/ui/data-entry\";\n\n<FormField id=\"coupon-name\" label=\"クーポン名\" required error={errors.name} helper=\"最大50文字\">\n <Input id=\"coupon-name\" placeholder=\"春の花粉症対策15%OFF\" value={name} onValueChange={(
|
|
2
|
+
"example": "import { FormField, Input } from \"@godxjp/ui/data-entry\";\n\n<FormField id=\"coupon-name\" label=\"クーポン名\" required error={errors.name} helper=\"最大50文字\">\n <Input id=\"coupon-name\" placeholder=\"春の花粉症対策15%OFF\" value={name} onValueChange={(v) => setName(v)} />\n</FormField>",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "FormField",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { InfiniteQueryState, flattenItemPages } from \"@godxjp/ui/query\";\n\n<InfiniteQueryState query={q} skeleton={<SkeletonRows />} flatten={flattenItemPages} isEmpty={(it) => it.length === 0}>\n {(items) => items.map((a) => <ActivityRow key={a.id} activity={a} />)}\n</InfiniteQueryState>",
|
|
2
|
+
"example": "import { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { InfiniteQueryState, flattenItemPages } from \"@godxjp/ui/query\";\n\ntype Activity = { id: string; label: string };\n\n// `flattenItemPages` constrains the page to `{ items: TItem[] }`, so the query must be typed:\n// an untyped one makes the page `unknown`, which cannot satisfy that constraint.\nconst q = useInfiniteQuery<{ items: Activity[]; cursor?: string }>({\n queryKey: [\"activity\"],\n queryFn: fetchActivityPage,\n initialPageParam: undefined,\n getNextPageParam: (last) => last.cursor,\n});\n\n<InfiniteQueryState query={q} skeleton={<SkeletonRows />} flatten={flattenItemPages} isEmpty={(it) => it.length === 0}>\n {(items) => items.map((a) => <ActivityRow key={a.id} activity={a} />)}\n</InfiniteQueryState>",
|
|
3
3
|
"group": "data-display",
|
|
4
4
|
"importPath": "@godxjp/ui/query",
|
|
5
5
|
"name": "InfiniteQueryState",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { Input } from \"@godxjp/ui/data-entry\";\n\n<Input id=\"qty\" type=\"number\" placeholder=\"例: 500\" value={value} onValueChange={(
|
|
2
|
+
"example": "import { Input } from \"@godxjp/ui/data-entry\";\n\n<Input id=\"qty\" type=\"number\" placeholder=\"例: 500\" value={value} onValueChange={(v) => setValue(v)} />",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "Input",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "ChartDatum[]"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
"description": "Plotted series: { dataKey, label?, color? }. Colour defaults to the --chart-1..6 palette.",
|
|
14
|
+
"description": "Plotted series: { dataKey, label?, color?, fillColor? }. Colour defaults to the --chart-1..6 palette; fillColor is the AreaChart band only.",
|
|
15
15
|
"name": "series",
|
|
16
16
|
"required": true,
|
|
17
17
|
"type": "ChartSeriesProp[]"
|
|
@@ -67,12 +67,28 @@
|
|
|
67
67
|
"name": "numberFormat",
|
|
68
68
|
"type": "Intl.NumberFormatOptions"
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
"description": "Explicit [min, max] for the VALUE axis (y vertical, x on a horizontal bar). Omit to let the data set it. A non-zero baseline is a charting-ETHICS call: legitimate where the SHAPE is the message and zero is not a reference (price, temperature, an index, a latency percentile), misleading wherever the reader compares magnitudes — which is every bar chart.",
|
|
72
|
+
"name": "valueDomain",
|
|
73
|
+
"type": "[number, number]"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "Explicit tick positions on the value axis, in data units. Values outside valueDomain are not drawn.",
|
|
77
|
+
"name": "valueTicks",
|
|
78
|
+
"type": "number[]"
|
|
79
|
+
},
|
|
70
80
|
{
|
|
71
81
|
"defaultValue": "false",
|
|
72
82
|
"description": "Render smooth (monotone) lines instead of straight segments.",
|
|
73
83
|
"name": "curved",
|
|
74
84
|
"type": "boolean"
|
|
75
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"defaultValue": "false",
|
|
88
|
+
"description": "Draw a marker at every data point. Off by default — markers crowd a dense series.",
|
|
89
|
+
"name": "showDots",
|
|
90
|
+
"type": "boolean"
|
|
91
|
+
},
|
|
76
92
|
{
|
|
77
93
|
"description": "Message shown when `data` is empty (defaults to a localized 'no data').",
|
|
78
94
|
"name": "emptyMessage",
|
|
@@ -93,7 +109,9 @@
|
|
|
93
109
|
"DO install the `recharts` optional peer dependency in the consuming app — charts are the only part of @godxjp/ui that needs it, so apps without charts never pay for it.",
|
|
94
110
|
"DO pass an i18n'd `label` — it is both the visible caption and the accessible name; the component also emits a screen-reader list of the plotted values (WCAG 1.1.1).",
|
|
95
111
|
"DO pre-translate each series' `label`; pass `numberFormat` (e.g. { style: 'currency', currency: 'JPY' }) and the axis/tooltip numbers localize automatically via Intl.",
|
|
96
|
-
"DON'T hand-roll an SVG/canvas chart or drop raw recharts into a page — LineChart owns the colour tokens, locale formatting, empty state, and accessibility wiring."
|
|
112
|
+
"DON'T hand-roll an SVG/canvas chart or drop raw recharts into a page — LineChart owns the colour tokens, locale formatting, empty state, and accessibility wiring.",
|
|
113
|
+
"DO retune the line weight and the grid dash with the `--chart-series-stroke-width` / `--chart-grid-line-dash` theme tokens, globally or on a scoped [data-tenant] region. They are house style; there is no prop for them.",
|
|
114
|
+
"DON'T reach for `valueDomain` to make a flat trend look dramatic — cropping the axis magnifies every wobble and the reader cannot tell a 2% drift from a collapse. Crop only where zero is not a meaningful reference, and say the range."
|
|
97
115
|
],
|
|
98
116
|
"useCases": [
|
|
99
117
|
"Revenue / KPI trend over months in a dashboard.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Smartphone } from \"lucide-react\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// The three scenarios below are three CARDS on one page, so they are wrapped in a Flex — the gap\n// between sibling cards belongs to the stack, never to the cards (audit: sibling-cards-need-flex).\n<Flex direction=\"col\" gap=\"lg\">\n<Card>\n <CardHeader>\n <CardTitle>アクティブなセッション</CardTitle>\n </CardHeader>\n <CardContent flush>\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"iPhone 15 · Tokyo\"\n description=\"最終アクセス 2分前\"\n trailing={<Badge status=\"active\" />}\n />\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"MacBook Pro · Osaka\"\n description=\"最終アクセス 3日前\"\n trailing={<Button size=\"xs\" variant=\"outline\">ログアウト</Button>}\n />\n </CardContent>\n</Card>\n\n// Notifications — unread dot + emphasized surface, wrapping title, two inline actions\n<Card>\n <CardContent flush>\n <ListRow\n unread\n align=\"start\"\n overflow=\"wrap\"\n title=\"組織「グローバル・トランスフォーメーション推進本部」への招待が届いています\"\n description=\"2026-07-30 09:12 JST\"\n trailing={\n <>\n <Button size=\"xs\" variant=\"ghost\">既読にする</Button>\n <Button size=\"xs\" variant=\"outline\">開く</Button>\n </>\n }\n />\n <ListRow unread={false} align=\"start\" overflow=\"wrap\" title=\"請求書が発行されました\" description=\"2026-07-28 18:40 JST\" />\n </CardContent>\n</Card>\n\n
|
|
2
|
+
"example": "import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Smartphone } from \"lucide-react\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// The three scenarios below are three CARDS on one page, so they are wrapped in a Flex — the gap\n// between sibling cards belongs to the stack, never to the cards (audit: sibling-cards-need-flex).\n<Flex direction=\"col\" gap=\"lg\">\n<Card>\n <CardHeader>\n <CardTitle>アクティブなセッション</CardTitle>\n </CardHeader>\n <CardContent flush>\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"iPhone 15 · Tokyo\"\n description=\"最終アクセス 2分前\"\n trailing={<Badge status=\"active\" />}\n />\n <ListRow\n leading={<Smartphone aria-hidden=\"true\" className=\"size-4\" />}\n title=\"MacBook Pro · Osaka\"\n description=\"最終アクセス 3日前\"\n trailing={<Button size=\"xs\" variant=\"outline\">ログアウト</Button>}\n />\n </CardContent>\n</Card>\n\n// Notifications — unread dot + emphasized surface, wrapping title, two inline actions\n<Card>\n <CardContent flush>\n <ListRow\n unread\n align=\"start\"\n overflow=\"wrap\"\n title=\"組織「グローバル・トランスフォーメーション推進本部」への招待が届いています\"\n description=\"2026-07-30 09:12 JST\"\n trailing={\n <>\n <Button size=\"xs\" variant=\"ghost\">既読にする</Button>\n <Button size=\"xs\" variant=\"outline\">開く</Button>\n </>\n }\n />\n <ListRow unread={false} align=\"start\" overflow=\"wrap\" title=\"請求書が発行されました\" description=\"2026-07-28 18:40 JST\" />\n </CardContent>\n</Card>\n\n{/* A list of LINKS — `as=\"li\"` gives the list item, `asChild` gives the whole-row link, and the\n item carries the divider. Never wrap the row in your own li, and never reach for a raw ul:\n `marker=\"none\"` is the semantic list container (no bullet, no indent, gap token). */}\n<Card>\n <CardContent flush>\n <Flex as=\"ul\" marker=\"none\" direction=\"col\" gap=\"none\">\n {projects.map((project) => (\n <ListRow key={project.key} as=\"li\" asChild title={project.name} description={project.key}>\n <Link href={`/projects/${project.key}`} />\n </ListRow>\n ))}\n </Flex>\n </CardContent>\n</Card>\n</Flex>",
|
|
3
3
|
"group": "data-display",
|
|
4
4
|
"importPath": "@godxjp/ui/data-display",
|
|
5
5
|
"name": "ListRow",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"docPath": "layout/masonry.tsx",
|
|
3
|
-
"example": "import { Masonry } from \"@godxjp/ui/layout\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n<Masonry\n columns={{ base: 1, sm: 2, lg: 3 }}\n gap=\"md\"\n items={notes.map((note) => ({ key: note.id, data: note }))}\n itemRender={({ data }) => (\n <Card>\n <CardContent>\n <Text>{data
|
|
3
|
+
"example": "import { Masonry } from \"@godxjp/ui/layout\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\n\n// T flows from `items`, so `data` in itemRender is whatever you put there — give the\n// collection a shape and the render callback needs no annotation.\ntype Note = { id: string; body: string };\nconst notes: Note[] = useNotes();\n\n<Masonry\n columns={{ base: 1, sm: 2, lg: 3 }}\n gap=\"md\"\n items={notes.map((note) => ({ key: note.id, data: note }))}\n itemRender={({ data }) => (\n <Card>\n <CardContent>\n <Text>{data?.body}</Text>\n </CardContent>\n </Card>\n )}\n onLayoutChange={(layout) => console.log(layout.length, \"tiles placed\")}\n/>",
|
|
4
4
|
"group": "layout",
|
|
5
5
|
"importPath": "@godxjp/ui/layout",
|
|
6
6
|
"name": "Masonry",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { MasterDetail } from \"@godxjp/ui/layout\";\n\n// Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).\n<MasterDetail\n masterLabel=\"Teams\"\n detailLabel=\"Selected team\"\n detailId=\"team-detail\"\n master={<TeamTable onRowClick={select} detailId=\"team-detail\" />}\n>\n <TeamDetail team={selected} />\n</MasterDetail>\n\n// Leading navigator rail instead.\n<MasterDetail
|
|
2
|
+
"example": "import { MasterDetail } from \"@godxjp/ui/layout\";\n\n// Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).\n<MasterDetail\n masterLabel=\"Teams\"\n detailLabel=\"Selected team\"\n detailId=\"team-detail\"\n master={<TeamTable onRowClick={select} detailId=\"team-detail\" />}\n>\n <TeamDetail team={selected} />\n</MasterDetail>\n\n// Leading navigator rail instead.\n<MasterDetail\n rail=\"master\"\n railWidth=\"compact\"\n masterLabel=\"Categories\"\n master={<CategoryNav activeId={activeCategory} onSelect={setActiveCategory} />}\n>\n <SettingsForm />\n</MasterDetail>\n\n// A long real collection: bound the master so it scrolls in place and the detail\n// stays near the top of a stacked mobile page.\n<MasterDetail\n masterViewport=\"compact\"\n masterLabel=\"Members\"\n detailLabel=\"Selected member\"\n master={<MemberList onRowClick={select} />}\n>\n <MemberDetail member={selected} />\n</MasterDetail>",
|
|
3
3
|
"group": "layout",
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "MasterDetail",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { PasswordInput, PasswordStrength } from \"@godxjp/ui/data-entry\";\n\
|
|
2
|
+
"example": "import { PasswordInput, PasswordStrength } from \"@godxjp/ui/data-entry\";\n\nexport default function PasswordBlock() {\n const [value, setValue] = useState(\"\");\n return (\n <div className=\"ui-stack\">\n <PasswordInput value={value} onChange={(event) => setValue(event.target.value)} />\n <PasswordStrength value={value} rules={[\"length\", \"upper\", \"lower\", \"number\", \"symbol\"]} />\n </div>\n );\n}",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "PasswordStrength",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"docPath": "data-display/permission-matrix.tsx",
|
|
3
|
-
"example": "import { Card, CardContent, PermissionMatrix } from \"@godxjp/ui/data-display\";\nimport { grantKey } from \"@godxjp/ui/lib/permission-grid\";\n\nconst grants = new Set(rolePermissions.map((rp) => grantKey(rp.roleId, rp.permissionId)));\n\n<Card>\n <CardContent flush>\n <PermissionMatrix\n roles={roles}\n permissions={permissions}\n grants={grants}\n onGrantChange={(roleId, permissionId, granted) => mutate({ roleId, permissionId, granted })}\n />\n </CardContent>\n</Card>",
|
|
3
|
+
"example": "import { Card, CardContent, PermissionMatrix } from \"@godxjp/ui/data-display\";\nimport { grantKey } from \"@godxjp/ui/lib/permission-grid\";\n\nconst grants = new Set<string>(rolePermissions.map((rp) => grantKey(rp.roleId, rp.permissionId)));\n\n<Card>\n <CardContent flush>\n <PermissionMatrix\n roles={roles}\n permissions={permissions}\n grants={grants}\n onGrantChange={(roleId, permissionId, granted) => mutate({ roleId, permissionId, granted })}\n />\n </CardContent>\n</Card>",
|
|
4
4
|
"group": "data-display",
|
|
5
5
|
"importPath": "@godxjp/ui/data-display",
|
|
6
6
|
"name": "PermissionMatrix",
|
|
@@ -371,6 +371,7 @@
|
|
|
371
371
|
"usage": [
|
|
372
372
|
"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.",
|
|
373
373
|
"DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
|
|
374
|
+
"NAMING A SELECT WITH NO VISIBLE LABEL: put `aria-label` on `<Select>`, NOT on `<SelectTrigger>`. Both render the identical button attribute — Select forwards its name down through SelectFieldA11yContext and the trigger writes last — but only the root spelling also names the react-aria field, and a root that cannot see a name warns once per render (gh#869). Inside a FormField or Field, pass nothing: the label id reaches both levels on its own. `aria-labelledby` pointing at your own element works on either level and is the right spelling when a visible heading already names the control.",
|
|
374
375
|
"READING THE SELECTED CODE FROM THE DOM: the trigger publishes `data-value` = the selected VALUE, alongside the `data-field` key it inherits from FormField. Use that in e2e tests and screen automation — the trigger's visible text is the option LABEL (東京本社), and the only other place the code lives is the aria-hidden, 1px-clipped native <select> react-aria renders so a native submit (and browser autofill) carries the value. `data-value` is absent while nothing is selected, and it tracks uncontrolled picks too.",
|
|
375
376
|
"DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.",
|
|
376
377
|
"A Select is safe inside a draggable element (a Kanban card with draggable=true) and inside a `contain: paint` / `transform` app region: the aria-hidden native <select> fallback is held at its static position beside the trigger (position: absolute, 1px clipped), so the browser's drag image stays the card's own box instead of reaching to the region's corner (gh#708). No wrapper or consumer CSS is needed.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "\n{`import { useState } from \"react\";\nimport { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from \"lucide-react\";\nimport { Link } from \"react-router-dom\";\nimport { AppShell, createSidebarLink } from \"@godxjp/ui/layout\";\nimport { Sidebar, type
|
|
2
|
+
"example": "\n{`import { useState } from \"react\";\nimport { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from \"lucide-react\";\nimport { Link } from \"react-router-dom\";\nimport { AppShell, createSidebarLink } from \"@godxjp/ui/layout\";\nimport { Sidebar, type SidebarSectionProp } from \"@godxjp/ui/layout\";\nimport { Topbar, TopbarItem } from \"@godxjp/ui/layout\";\n\n// The WHOLE router integration: pass the element type, the library composes every row\n// (icon · label · badge · active · collapsed rail). Inertia: inertiaSidebarLink(Link) from\n// \"@godxjp/ui/inertia\". Next.js: createSidebarLink(Link).\nconst NavLink = createSidebarLink(Link, \"to\");\n\nconst sections: SidebarSectionProp[] = [\n {\n label: \"Accounting\",\n items: [\n { id: \"dashboard\", label: \"Dashboard\", icon: LayoutDashboard, href: \"/dashboard\" },\n {\n id: \"ledger\",\n label: \"Ledger\",\n icon: BookOpen,\n children: [\n { id: \"journal\", label: \"Journal\", icon: FileText, href: \"/ledger/journal\" },\n { id: \"chart-of-accounts\", label: \"Chart of Accounts\", icon: CreditCard, href: \"/ledger/coa\" },\n ],\n },\n ],\n },\n {\n label: \"Administration\",\n items: [\n { id: \"users\", label: \"Users\", icon: Users, href: \"/users\" },\n { id: \"roles\", label: \"Roles\", icon: Shield, href: \"/roles\", disabled: true },\n ],\n },\n];\n\nexport default function Shell() {\n const [activeId, setActiveId] = useState(\"dashboard\");\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <AppShell\n sidebarCollapsed={collapsed}\n sidebar={\n <Sidebar\n activeId={activeId}\n collapsed={collapsed}\n onSelect={setActiveId}\n sections={sections}\n linkComponent={NavLink}\n product={{ name: \"CoreBooks\", role: \"Admin Console\", color: \"hsl(var(--primary))\" }}\n onProductClick={() => {/* open entity switcher */}}\n footer={\n <div className=\"text-muted-foreground text-xs\">\n <div className=\"text-foreground font-medium\">Satoshi Yamamoto</div>\n <div>Online · Tokyo branch</div>\n </div>\n }\n />\n }\n topbar={\n <Topbar\n start={\n <>\n {/* A bar cell is a TopbarItem, never a Button: a Button in a bar is a\n --control-height pill floating in a taller strip, with its own hover\n fill and its own focus ring. */}\n <TopbarItem aria-label=\"メニュー\" onClick={() => setCollapsed((c) => !c)}>\n <PanelLeft />\n </TopbarItem>\n <Logo mark=\"godx\" label=\"CoreBooks\" />\n </>\n }\n end={<TopbarItem aria-label=\"検索\" onClick={() => {}}><Search /></TopbarItem>}\n />\n }\n >\n <>{/* page content */}</>\n </AppShell>\n );\n}`}\n",
|
|
3
3
|
"group": "layout",
|
|
4
4
|
"importPath": "@godxjp/ui/layout",
|
|
5
5
|
"name": "Sidebar",
|
|
@@ -56,15 +56,20 @@
|
|
|
56
56
|
"name": "striped",
|
|
57
57
|
"type": "boolean"
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"description": "On TableRow (gh#876): the row's STATE — a leading-edge rail plus a weak wash, the SAME six tones and meanings `DataTable rowTone` already paints. Writes `data-tone`, the attribute the paint is keyed on — `<TableRow data-tone=\"warning\">` still works unchanged, `tone` is just the typed, discoverable route to it. Never the only signal (WCAG 1.4.1): keep the reason in a cell (a Badge, a status column) and let the rail make that cell findable. DO NOT reach for a `bg-<status>/…` utility on a TableRow instead — that bypasses the token-owned wash and the theme can no longer retune it (gh#872).",
|
|
61
|
+
"name": "tone",
|
|
62
|
+
"type": "\"primary\" | \"success\" | \"warning\" | \"info\" | \"attention\" | \"destructive\""
|
|
63
|
+
},
|
|
59
64
|
{
|
|
60
65
|
"defaultValue": "\"default\"",
|
|
61
|
-
"description": "Named collection contract. \"default\" emits no attribute and keeps the plain table. \"action-collection\" is the canonical dense approval/action queue: the desktop INTRINSIC column widths (which make a five-column queue wider than its card and force a horizontal scroll at 390) are replaced by table-layout: fixed plus the token-owned column PRIORITY measures (--table-action-collection-*), and cells wrap. Mark each column with `priority` on its TableHead AND its TableCell. Semantics are untouched — no display change, no role rewriting, no card transformation — so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440.",
|
|
66
|
+
"description": "Named collection contract. \"default\" emits no attribute and keeps the plain table. \"action-collection\" is the canonical dense approval/action queue: the desktop INTRINSIC column widths (which make a five-column queue wider than its card and force a horizontal scroll at 390) are replaced by table-layout: fixed plus the token-owned column PRIORITY measures (--table-action-collection-*), and cells wrap. Mark each column with `priority` on its TableHead AND its TableCell. Semantics are untouched — no display change, no role rewriting, no card transformation — so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. \"stacked-record-collection\" is the other direction, for a WIDE, HETEROGENEOUS record set with no sensible narrow column measure: below collapseBelow the <thead> hides and every <tr> becomes a bordered key-value card (--table-stacked-collection-*). Give each TableCell a `label` — its column header — because the <th> association it normally reads from is the thing that just went away; DataTable derives that label from ColumnDef.header for you (gh#864). The label is aria-hidden and the real <th> stays in the DOM, so the accessible name and table navigation are unchanged.",
|
|
62
67
|
"name": "preset",
|
|
63
|
-
"type": "\"default\" | \"action-collection\""
|
|
68
|
+
"type": "\"default\" | \"action-collection\" | \"stacked-record-collection\""
|
|
64
69
|
},
|
|
65
70
|
{
|
|
66
71
|
"defaultValue": "\"sm\"",
|
|
67
|
-
"description": "Step at which preset=\"action-collection\" switches to the compact priority measures,
|
|
72
|
+
"description": "Step at which preset=\"action-collection\" switches to the compact priority measures, or preset=\"stacked-record-collection\" folds its rows into cards. Measured against the TABLE'S OWN container (a container query), not the viewport — a table inside a master rail collapses before the page does. Ignored while preset is \"default\".",
|
|
68
73
|
"name": "collapseBelow",
|
|
69
74
|
"type": "\"sm\" | \"md\" | \"lg\" | \"xl\""
|
|
70
75
|
},
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { ThemeScope, tenantTheme } from \"@godxjp/ui/app\";\n\n// The customer's colour on one region — and on every overlay that region opens.\n<ThemeScope style={tenantTheme(customer.brandHex).vars} data-tenant={customer.slug}>\n <Dialog>\n <DialogTrigger asChild><Button>Review</Button></DialogTrigger>\n <DialogContent><DialogTitle>Review</DialogTitle></DialogContent>\n </Dialog>\n</ThemeScope>",
|
|
3
|
+
"group": "providers",
|
|
4
|
+
"importPath": "@godxjp/ui/app",
|
|
5
|
+
"name": "ThemeScope",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "The themed region. Overlays opened anywhere below it follow its tokens.",
|
|
9
|
+
"name": "children",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "ReactNode"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Classes on the scope element — this is where `dark` goes when a REGION is dark rather than the whole page.",
|
|
15
|
+
"name": "className",
|
|
16
|
+
"required": false,
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "DOM id of the scope element.",
|
|
21
|
+
"name": "id",
|
|
22
|
+
"required": false,
|
|
23
|
+
"type": "string"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"related": [
|
|
27
|
+
"OverlayPortalProvider — the other half of the same statement: it decides WHERE an overlay lands, ThemeScope decides which TOKENS it inherits there. They compose; a ThemeScope inside one hosts itself in that container.",
|
|
28
|
+
"tenantTheme — the function that turns a customer hex into the declarations you put ON a ThemeScope (`style={tenantTheme(hex).vars}`). It computes the colours; ThemeScope is what carries them past the portal boundary.",
|
|
29
|
+
"AppProvider — page-level theme axes (theme/brand/density/fontSize). Use it for the whole app; ThemeScope is for one region that differs from it."
|
|
30
|
+
],
|
|
31
|
+
"rules": [
|
|
32
|
+
5
|
|
33
|
+
],
|
|
34
|
+
"storyPath": "app/ThemeScope.stories.tsx",
|
|
35
|
+
"tagline": "Makes a themed REGION reach the overlays it opens. Every overlay portals to document.body, so custom-property inheritance stops at the portal boundary and a tenant-themed region's Dialog, Select listbox, Popover and Toast paint the package defaults. Wrap the region in ThemeScope and they carry the region's tokens.",
|
|
36
|
+
"usage": [
|
|
37
|
+
"DO wrap the region, then theme it the way you already do — `style={tenantTheme(hex).vars}`, `data-tenant=\"acme\"`, `className=\"dark\"`, or a stylesheet rule that never mentions React. ThemeScope reads the COMPUTED tokens at its own element, so all of those paths behave identically; it has no theme prop and needs none.",
|
|
38
|
+
"DO nest it. An inner ThemeScope inside an outer one wins for the overlays opened below it, because its scope already inherits the outer's tokens and it diffs against the document root.",
|
|
39
|
+
"DO mount it inside an OverlayPortalProvider when you have one. It puts its host INSIDE that container, so a shadow-rooted app can be tenant-themed as well — the two providers compose rather than compete.",
|
|
40
|
+
"DON'T reach for `OverlayPortalProvider container={themedWrapper}` to solve this. It works until the wrapper sits inside an `overflow: hidden`, a `transform` or a `contain` ancestor, and then the region clips its own overlays — a colour bug traded for a layout bug that is harder to see. `container` stays for the shadow-DOM case it was built for.",
|
|
41
|
+
"DON'T expect it on a page with no scoped theme. With nothing themed it carries nothing, and with no ThemeScope at all every overlay behaves exactly as before.",
|
|
42
|
+
"DON'T assume a CLASS-keyed rule travels. What crosses the boundary is the custom-property delta — the tokens. A rule written as `.dark .my-thing { background: #111 }` in app CSS is not a token and does not follow the overlay."
|
|
43
|
+
],
|
|
44
|
+
"useCases": [
|
|
45
|
+
"A multi-tenant screen where one region wears a customer's brand colour from `tenantTheme(hex)` — the button was already right, and this is what makes the dialog it opens right too.",
|
|
46
|
+
"A dark region on a light page (a preview pane, an editor canvas): `className=\"dark\"` on the region, and its Select listbox and Popover stay dark instead of flashing the page's light popover surface.",
|
|
47
|
+
"A `[data-tenant]` theme written entirely in the consumer's own stylesheet, with no React theming provider anywhere — the documented way to theme a region in this package, and the case the design was chosen to cover."
|
|
48
|
+
]
|
|
49
|
+
}
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
|
|
69
69
|
"DO build the sidebar toggle as a `TopbarItem` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell's `sidebarCollapsed`. There is no baked toggle — but there IS a bar CELL, and it is not a Button: a Button in a slot is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill instead of the cell. The same holds for the notifications bell and the account trigger.",
|
|
70
70
|
"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) — icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.",
|
|
71
|
+
"DON'T wrap two+ TopbarItems in a `<Flex>` to group them in one slot (gh#883) — the slot is already a flex line with its own gap, and a `<Flex>` wrapper collapses to 16px and takes every item inside it down with it. Use a fragment (`<>…</>`, no DOM node) or pass an array instead; see TopbarItem's own DON'T for the measured before/after.",
|
|
71
72
|
"DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props — they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.",
|
|
72
73
|
"DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself.",
|
|
73
74
|
"DO decide, explicitly, what happens to the `center` slot at 1100px and below. It is REMOVED there by default (`--topbar-center-compact-display: none`) so it cannot cover the start or end clusters when a 16rem sidebar is docked — which also means a global search trigger in `center` is gone on tablets AND phones. This default arrived in 18.6.0 and changed behaviour for consumers who touched nothing but their lockfile. If your center content already has a compact presentation (an icon-only search trigger), opt back in globally with `:root { --topbar-center-compact-display: flex; }`; if it does not, move the trigger into `end` for compact widths. Never re-create either behaviour with a page-local media query.",
|
|
@@ -69,7 +69,8 @@
|
|
|
69
69
|
"DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
|
|
70
70
|
"DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
|
|
71
71
|
"DO collapse a cell by breakpoint with its own props, never by hand-wrapping the glyph: `<TopbarItem asChild icon={<Target />} labelHideBelow=\"sm\"><a href=\"/goals\">Goals</a></TopbarItem>` replaces `<Flex hideFrom=\"sm\"><Icon as={Target} size=\"md\" /></Flex>` — icon-only below sm, the label still the accessible name. Add `iconHideFrom` for a label-only cell from a step up (gh#726).",
|
|
72
|
-
"DON'T reach for it outside a Topbar — a full-bleed cell needs a bar to bleed to. Use Button anywhere else."
|
|
72
|
+
"DON'T reach for it outside a Topbar — a full-bleed cell needs a bar to bleed to. Use Button anywhere else.",
|
|
73
|
+
"DON'T wrap two or more TopbarItems in a `<Flex>` to place them together in one slot (gh#883) — a `<Flex>` is a REAL element with `align-self: auto` by default, so it sits between the item and the slot, collapses to its own content height, and every item inside it stretches only to THAT (measured: a 47px bar down to 16px). The slot (`.ui-topbar-start`/`-center`/`-end`) is already a flex line with its own `gap`, so it needs no wrapper at all: put a JSX FRAGMENT `<>…</>` around the items (renders no DOM node, so each item is still the slot's DIRECT child) or pass an array — `end={[<TopbarItem key=\"notifications\" …/>, <TopbarItem key=\"account\" …/>]}` — either way every item keeps its own `align-self: stretch` reaching the real bar height. Reach for `<Flex>` there only when you deliberately want the group NOT full height (rare in a topbar)."
|
|
73
74
|
],
|
|
74
75
|
"useCases": [
|
|
75
76
|
"Account / user-menu trigger in the topbar end slot",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { useState } from \"react\";\nimport { Transfer } from \"@godxjp/ui/data-entry\";\n\nconst ALL_ACCOUNTS = [\n {
|
|
2
|
+
"example": "import { useState } from \"react\";\nimport { Transfer } from \"@godxjp/ui/data-entry\";\n\nconst ALL_ACCOUNTS = [\n { key: \"1010\", title: \"Cash\", description: \"Asset\" },\n { key: \"1020\", title: \"Accounts Receivable\", description: \"Asset\" },\n { key: \"2010\", title: \"Accounts Payable\", description: \"Liability\" },\n { key: \"3010\", title: \"Revenue\", description: \"Income\" },\n { key: \"4010\", title: \"Cost of Goods Sold\", description: \"Expense\", disabled: true },\n];\n\nexport function AccountMapping() {\n const [targetKeys, setTargetKeys] = useState<string[]>([\"1010\"]);\n\n return (\n <Transfer\n dataSource={ALL_ACCOUNTS}\n targetKeys={targetKeys}\n onValueChange={(nextKeys) => setTargetKeys(nextKeys)}\n titles={[\"Available Accounts\", \"Mapped Accounts\"]}\n showSearch\n />\n );\n}",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "Transfer",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "import { useState } from \"react\";\nimport { FormField, TreeSelect } from \"@godxjp/ui/data-entry\";\n\nconst accountTree = [\n {\n value: \"assets\",\n label: \"Assets\",\n
|
|
2
|
+
"example": "import { useState } from \"react\";\nimport { FormField, TreeSelect } from \"@godxjp/ui/data-entry\";\n\nconst accountTree = [\n {\n value: \"assets\",\n label: \"Assets\",\n children: [\n { value: \"current-assets\", label: \"Current Assets\", children: [\n { value: \"cash\", label: \"Cash\" },\n { value: \"ar\", label: \"Accounts Receivable\" },\n ],\n },\n { value: \"fixed-assets\", label: \"Fixed Assets\", children: [\n { value: \"equipment\", label: \"Equipment\" },\n ],\n },\n ],\n },\n {\n value: \"liabilities\",\n label: \"Liabilities\",\n children: [\n { value: \"ap\", label: \"Accounts Payable\" },\n ],\n },\n];\n\n// Single-select (returns string | undefined)\nexport function AccountPicker() {\n const [account, setAccount] = useState<string | undefined>();\n return (\n <FormField id=\"account-picker\" label=\"GL Account\">\n <TreeSelect\n id=\"account-picker\"\n treeData={accountTree}\n value={account}\n onValueChange={(v) => setAccount(v as string | undefined)}\n showSearch\n treeDefaultExpandAll\n placeholder=\"Select account…\"\n allowClear\n />\n </FormField>\n );\n}\n\n// Multi-select with checkboxes + cascade + SHOW_PARENT display\nexport function DepartmentFilter() {\n const [selected, setSelected] = useState<string[]>([]);\n return (\n <TreeSelect\n id=\"dept-filter\"\n treeData={accountTree}\n value={selected}\n onValueChange={(v) => setSelected(v as string[])}\n treeCheckable\n showCheckedStrategy={TreeSelect.SHOW_PARENT}\n showSearch\n placeholder=\"Filter by department…\"\n />\n );\n}",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "TreeSelect",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"example": "{`import { useState } from \"react\";\nimport { UploadCropDialog } from \"@godxjp/ui/
|
|
2
|
+
"example": "{`import { useState } from \"react\";\nimport { UploadCropDialog } from \"@godxjp/ui/data-entry\"; // internal — prefer Upload variant=\"avatar-crop\" instead\n\nexport function AvatarField() {\n const [cropFile, setCropFile] = useState<File | null>(null);\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0] ?? null;\n setCropFile(file);\n e.target.value = \"\"; // reset so re-selecting same file fires onChange\n };\n\n const handleConfirm = (cropped: File) => {\n // cropped is always image/jpeg 256×256\n const form = new FormData();\n form.append(\"avatar\", cropped);\n fetch(\"/api/avatar\", { method: \"POST\", body: form });\n };\n\n return (\n <>\n <input type=\"file\" accept=\"image/*\" onChange={handleFileChange} />\n <UploadCropDialog\n open={cropFile !== null}\n onOpenChange={(open) => { if (!open) setCropFile(null); }}\n file={cropFile}\n onConfirm={handleConfirm}\n />\n </>\n );\n}`}",
|
|
3
3
|
"group": "data-entry",
|
|
4
4
|
"importPath": "@godxjp/ui/data-entry",
|
|
5
5
|
"name": "UploadCropDialog",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"example": "import { formatDate } from \"@godxjp/ui/datetime\";\n\nformatDate(coupon.validFrom); // \"2026-05-01\"\nformatDate(order.createdAt, { kind: \"relative\" }); // \"3日前\"",
|
|
3
3
|
"group": "providers",
|
|
4
|
-
"importPath": "@godxjp/ui/
|
|
4
|
+
"importPath": "@godxjp/ui/datetime",
|
|
5
5
|
"name": "formatDate",
|
|
6
6
|
"props": [
|
|
7
7
|
{
|