@godxjp/ui 20.0.0 → 20.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/app/theme-axes.d.ts +2 -0
- package/dist/app/theme-axes.js +45 -0
- package/dist/components/charts/chart-cartesian.d.ts +2 -1
- package/dist/components/charts/chart-cartesian.js +52 -3
- package/dist/components/charts/chart-category-axis.d.ts +55 -0
- package/dist/components/charts/chart-category-axis.js +93 -0
- package/dist/components/charts/chart-frame.d.ts +10 -1
- package/dist/components/charts/chart-frame.js +19 -3
- package/dist/components/charts/compact-bar-trend.d.ts +1 -1
- package/dist/components/charts/compact-bar-trend.js +2 -0
- package/dist/components/charts/pie-chart.d.ts +1 -1
- package/dist/components/charts/pie-chart.js +12 -1
- package/dist/components/charts/recharts-peer.d.ts +49 -0
- package/dist/components/charts/recharts-peer.js +45 -0
- package/dist/components/data-display/card.d.ts +12 -1
- package/dist/components/data-display/card.js +22 -4
- package/dist/components/data-display/code-block.js +6 -2
- package/dist/components/data-display/data-table.d.ts +39 -4
- package/dist/components/data-display/data-table.js +802 -270
- package/dist/components/data-display/descriptions.d.ts +27 -6
- package/dist/components/data-display/descriptions.js +58 -17
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/list-row.d.ts +10 -1
- package/dist/components/data-display/list-row.js +7 -1
- package/dist/components/data-display/popover.d.ts +2 -2
- package/dist/components/data-display/popover.js +61 -11
- package/dist/components/data-display/range-timeline.d.ts +38 -0
- package/dist/components/data-display/range-timeline.js +161 -0
- package/dist/components/data-display/scroll-area.js +13 -2
- package/dist/components/data-display/service-launcher-card.js +10 -10
- package/dist/components/data-display/table.d.ts +11 -3
- package/dist/components/data-display/table.js +48 -18
- package/dist/components/data-display/tree-list.js +4 -9
- package/dist/components/data-entry/calendar.d.ts +2 -2
- package/dist/components/data-entry/calendar.js +12 -1
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +188 -24
- package/dist/components/data-entry/color-picker.d.ts +1 -1
- package/dist/components/data-entry/color-picker.js +17 -5
- package/dist/components/data-entry/control-appearance.d.ts +64 -0
- package/dist/components/data-entry/control-appearance.js +39 -0
- package/dist/components/data-entry/control-surface.d.ts +61 -0
- package/dist/components/data-entry/control-surface.js +39 -0
- package/dist/components/data-entry/date-picker.d.ts +1 -1
- package/dist/components/data-entry/date-picker.js +344 -113
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.js +278 -140
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +39 -4
- package/dist/components/data-entry/form.d.ts +4 -0
- package/dist/components/data-entry/form.js +4 -2
- package/dist/components/data-entry/index.d.ts +7 -3
- package/dist/components/data-entry/index.js +10 -1
- package/dist/components/data-entry/input-otp.d.ts +1 -1
- package/dist/components/data-entry/input.d.ts +12 -34
- package/dist/components/data-entry/input.js +92 -24
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-picker.js +47 -10
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.js +51 -11
- package/dist/components/data-entry/number-input.d.ts +7 -0
- package/dist/components/data-entry/number-input.js +147 -98
- package/dist/components/data-entry/password-input.d.ts +1 -1
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/radio.js +61 -13
- package/dist/components/data-entry/search-input.d.ts +1 -1
- package/dist/components/data-entry/search-input.js +16 -2
- package/dist/components/data-entry/search-select.d.ts +2 -2
- package/dist/components/data-entry/search-select.js +209 -78
- package/dist/components/data-entry/select.d.ts +22 -4
- package/dist/components/data-entry/select.js +215 -163
- package/dist/components/data-entry/slider.d.ts +9 -1
- package/dist/components/data-entry/slider.js +87 -9
- package/dist/components/data-entry/switch.d.ts +3 -0
- package/dist/components/data-entry/switch.js +29 -3
- package/dist/components/data-entry/textarea.d.ts +14 -56
- package/dist/components/data-entry/textarea.js +80 -33
- package/dist/components/data-entry/time-picker.d.ts +2 -2
- package/dist/components/data-entry/time-picker.js +333 -109
- package/dist/components/data-entry/time-range-picker.d.ts +5 -0
- package/dist/components/data-entry/time-range-picker.js +89 -0
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +86 -28
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +201 -113
- package/dist/components/data-entry/tree-utils.js +7 -14
- package/dist/components/data-entry/upload-files.d.ts +2 -0
- package/dist/components/data-entry/upload-files.js +31 -0
- package/dist/components/data-entry/upload-request.d.ts +4 -0
- package/dist/components/data-entry/upload-request.js +53 -0
- package/dist/components/data-entry/upload-types.d.ts +28 -0
- package/dist/components/data-entry/upload-types.js +2 -0
- package/dist/components/data-entry/upload.d.ts +2 -2
- package/dist/components/data-entry/upload.js +475 -115
- package/dist/components/feedback/dialog.js +16 -19
- package/dist/components/general/button.d.ts +1 -1
- package/dist/components/general/button.js +5 -1
- package/dist/components/general/index.d.ts +1 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/general/typography.d.ts +3 -0
- package/dist/components/general/typography.js +15 -1
- package/dist/components/general/visually-hidden.d.ts +3 -0
- package/dist/components/general/visually-hidden.js +9 -0
- package/dist/components/layout/app-launcher.d.ts +34 -0
- package/dist/components/layout/app-launcher.js +228 -0
- package/dist/components/layout/app-shell.d.ts +2 -0
- package/dist/components/layout/app-shell.js +20 -7
- package/dist/components/layout/breadcrumb.d.ts +14 -2
- package/dist/components/layout/breadcrumb.js +46 -4
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +23 -2
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.js +5 -1
- package/dist/components/layout/org-switcher.js +20 -1
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +5 -3
- package/dist/components/layout/responsive-grid.d.ts +16 -2
- package/dist/components/layout/responsive-grid.js +29 -2
- package/dist/components/layout/topbar-item.d.ts +3 -0
- package/dist/components/layout/topbar-item.js +18 -3
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +27 -6
- package/dist/components/navigation/dropdown-menu.d.ts +30 -2
- package/dist/components/navigation/dropdown-menu.js +68 -11
- package/dist/components/navigation/pagination.d.ts +2 -2
- package/dist/components/navigation/pagination.js +155 -83
- package/dist/components/navigation/steps.d.ts +2 -2
- package/dist/components/navigation/steps.js +29 -4
- package/dist/components/navigation/tabs.d.ts +5 -33
- package/dist/components/navigation/tabs.js +232 -64
- package/dist/components/ui/input-otp.d.ts +26 -28
- package/dist/components/ui/input-otp.js +50 -7
- package/dist/components/ui/password-input.d.ts +36 -2
- package/dist/components/ui/password-input.js +27 -7
- package/dist/components/ui/rating.d.ts +25 -0
- package/dist/components/ui/rating.js +67 -27
- package/dist/components/ui/segmented.d.ts +9 -0
- package/dist/components/ui/segmented.js +17 -2
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +13 -0
- package/dist/components/ui/tag-input.d.ts +45 -0
- package/dist/components/ui/tag-input.js +109 -27
- package/dist/form/form-context.d.ts +29 -0
- package/dist/form/form-context.js +44 -4
- package/dist/form/form-field-array.d.ts +22 -0
- package/dist/form/form-field-array.js +50 -0
- package/dist/form/form-field-control.d.ts +2 -1
- package/dist/form/form-field-control.js +116 -40
- package/dist/form/form-root.d.ts +2 -1
- package/dist/form/form-root.js +142 -23
- package/dist/form/index.d.ts +3 -1
- package/dist/form/index.js +12 -1
- package/dist/i18n/messages/en.json +56 -5
- package/dist/i18n/messages/ja.json +56 -5
- package/dist/i18n/messages/vi.json +56 -5
- package/dist/inertia/index.d.ts +20 -0
- package/dist/inertia/index.js +60 -1
- package/dist/lib/control-styles.d.ts +25 -3
- package/dist/lib/control-styles.js +9 -3
- package/dist/lib/datetime/picker-format.d.ts +8 -0
- package/dist/lib/datetime/picker-format.js +54 -0
- package/dist/props/components/charts.prop.d.ts +21 -0
- package/dist/props/components/data-display.prop.d.ts +55 -4
- package/dist/props/components/data-entry.prop.d.ts +687 -43
- package/dist/props/components/form.prop.d.ts +142 -4
- package/dist/props/components/general.prop.d.ts +17 -1
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +163 -6
- package/dist/props/components/navigation.prop.d.ts +113 -3
- package/dist/props/registry.d.ts +376 -10
- package/dist/props/registry.js +493 -9
- package/dist/props/vocabulary/data.prop.d.ts +179 -1
- package/dist/props/vocabulary/index.d.ts +4 -4
- package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
- package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
- package/dist/props/vocabulary/shared.prop.d.ts +33 -0
- package/dist/styles/badge-layout.css +2 -1
- package/dist/styles/card-layout.css +26 -4
- package/dist/styles/chart-layout.css +15 -0
- package/dist/styles/control.css +515 -14
- package/dist/styles/data-display-layout.css +241 -6
- package/dist/styles/data-entry-layout.css +13 -0
- package/dist/styles/layout.css +221 -4
- package/dist/styles/navigation-layout.css +227 -0
- package/dist/styles/shell-layout.css +229 -5
- package/dist/styles/table-layout.css +95 -6
- package/dist/styles/text-layout.css +5 -0
- package/dist/tokens/base.css +1 -0
- package/dist/tokens/components/badge.css +1 -0
- package/dist/tokens/components/chart.css +6 -0
- package/dist/tokens/components/control.css +79 -2
- package/dist/tokens/components/data-display.css +9 -0
- package/dist/tokens/components/descriptions.css +11 -0
- package/dist/tokens/components/flex.css +8 -0
- package/dist/tokens/components/navigation.css +55 -0
- package/dist/tokens/components/shell.css +50 -2
- package/dist/tokens/components/table.css +14 -0
- package/dist/tokens/semantic/layout.css +9 -0
- package/docs/CONSUMER-RULES.md +13 -0
- package/docs/DESIGN-AUTHORITY.md +43 -6
- package/docs/DEVELOPMENT.md +4 -3
- package/docs/FORMS.md +151 -6
- package/docs/FRAME-COVERAGE-REPORT.md +29 -16
- package/docs/README.md +1 -1
- package/docs/STANDARDS-vocabulary-tokens.md +1 -1
- package/docs/TESTING.md +15 -6
- package/docs/charts/cjk-category-axis.tsx +81 -0
- package/docs/data-display/card/index.tsx +22 -0
- package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
- package/docs/data-display/data-table/index.tsx +23 -0
- package/docs/data-display/descriptions.tsx +41 -0
- package/docs/data-display/scroll-area.tsx +31 -25
- package/docs/data-display/table.tsx +104 -45
- package/docs/data-display/timeline.tsx +41 -0
- package/docs/data-entry/date-picker.tsx +85 -0
- package/docs/data-entry/date-range-picker.tsx +26 -0
- package/docs/data-entry/form-dynamic-fields.tsx +199 -0
- package/docs/data-entry/form.tsx +74 -4
- package/docs/data-entry/input-otp.tsx +24 -0
- package/docs/data-entry/input.tsx +46 -1
- package/docs/data-entry/month-range-picker.tsx +1 -1
- package/docs/data-entry/select.tsx +27 -0
- package/docs/data-entry/switch.tsx +41 -0
- package/docs/data-entry/textarea.tsx +38 -0
- package/docs/data-entry/time-picker.tsx +85 -1
- package/docs/data-entry/time-range-picker.tsx +55 -0
- package/docs/data-entry/transfer.tsx +17 -0
- package/docs/data-entry/upload.tsx +56 -12
- package/docs/feedback/tooltip.tsx +1 -1
- package/docs/general/activity.tsx +2 -2
- package/docs/general/button/index.tsx +14 -1
- package/docs/general/typography.tsx +14 -1
- package/docs/layout/app-launcher.tsx +151 -0
- package/docs/layout/app-shell.tsx +11 -0
- package/docs/layout/flex.tsx +50 -0
- package/docs/layout/responsive-grid.tsx +21 -1
- package/docs/layout/topbar.tsx +5 -9
- package/docs/navigation/app-setting-picker.tsx +11 -0
- package/docs/navigation/breadcrumb.tsx +48 -0
- package/docs/navigation/dropdown-menu.tsx +21 -0
- package/docs/navigation/pagination.tsx +52 -0
- package/docs/navigation/steps.tsx +37 -0
- package/docs/navigation/tabs.tsx +97 -1
- package/docs/query/button-refetch.tsx +1 -0
- package/package.json +19 -2
- package/scripts/_agent-setup.mjs +19 -2
- package/scripts/init-guinea-pig.mjs +26 -4
- package/scripts/ui-audit.mjs +243 -30
package/docs/DESIGN-AUTHORITY.md
CHANGED
|
@@ -187,6 +187,43 @@ Recorded rather than silently fixed, because each is a real decision:
|
|
|
187
187
|
strokes blur far more visibly than Latin letterforms. Another reason to prefer Carbon's integer
|
|
188
188
|
steps if the scale is ever revisited.
|
|
189
189
|
|
|
190
|
+
## The PROP SURFACE of a component is antd's too, not just its group name
|
|
191
|
+
|
|
192
|
+
antd was already the authority for which components exist and which group they live in. That
|
|
193
|
+
answered "is there a Table" and never answered "what does a Table take". So every prop was decided
|
|
194
|
+
here, one at a time, and the answer drifted per component: `DataTable` grew `pin: "end"` where antd
|
|
195
|
+
has `fixed`, `sortable: true` where antd has `sorter`, and no answer at all for filters, expandable
|
|
196
|
+
rows or a totals row — which is how a consumer ends up hand-rolling a `<tfoot>` and a sticky column
|
|
197
|
+
in page CSS.
|
|
198
|
+
|
|
199
|
+
**The rule: where antd names a capability, this library takes antd's name and antd's semantics.**
|
|
200
|
+
The gap is read out of the INSTALLED types (`antd/es/table/interface.d.ts`,
|
|
201
|
+
`antd/es/table/InternalTable.d.ts` and the `@rc-component/table` interface they extend) — never
|
|
202
|
+
from memory, because antd's own names move between majors (`fixed: 'left'` is deprecated in favour
|
|
203
|
+
of `start` inside rc-table itself).
|
|
204
|
+
|
|
205
|
+
> Đọc sau 20.0.0: bản major ấy đã **gỡ `antd` khỏi devDependencies** cùng máy sinh màu của nó, và
|
|
206
|
+
> `check:no-antd-runtime` canh cho nó không quay lại. Câu trên mô tả cách bề mặt prop này ĐƯỢC ĐỌC
|
|
207
|
+
> lúc antd còn cài, không phải một lời mời cài lại. Lần sau muốn đối chiếu, đọc type ở một checkout
|
|
208
|
+
> riêng rồi ghi số hiệu bản vào đây — đừng thêm dependency.
|
|
209
|
+
|
|
210
|
+
**Three things override antd's spelling, each for a stated reason:**
|
|
211
|
+
|
|
212
|
+
- **Logical over physical.** antd's `fixed: 'left' | 'right'` cannot mirror for an RTL locale, so
|
|
213
|
+
only `start` / `end` are published. Same rule that makes `check:rtl` a gate.
|
|
214
|
+
- **This library's controlled vocabulary wins on values.** antd's `SortOrder` is
|
|
215
|
+
`'ascend' | 'descend'`; here it stays `SortDirectionProp` (`asc` / `desc`), because that type
|
|
216
|
+
already exists and a second spelling of the same axis is exactly what `check:prop-vocabulary`
|
|
217
|
+
exists to prevent.
|
|
218
|
+
- **A capability this library already has keeps its own name.** antd's `size`
|
|
219
|
+
(`small | middle | large`) IS `density` (`compact | default | comfortable`); antd's `locale` IS
|
|
220
|
+
the `t()` layer. Adding the antd spelling as an alias would be duplication, not parity.
|
|
221
|
+
|
|
222
|
+
**A knob that only a fork could reach is not parity either.** antd's `components`,
|
|
223
|
+
`filterDropdown`, `classNames`/`styles` semantic maps and `prefixCls` all exist to let a consumer
|
|
224
|
+
replace the rendered markup. This library answers that layer with tokens (cardinal rule #45), so
|
|
225
|
+
those are deliberately NOT adopted — adopting them would re-open the hole the token tiers close.
|
|
226
|
+
|
|
190
227
|
## Derived colour is AUTHORED, and MEASUREMENT is what makes it authoritative
|
|
191
228
|
|
|
192
229
|
**Twenty values, in `src/tokens/derived.css`:** `--primary-hover`, `--primary-active`,
|
|
@@ -293,7 +330,7 @@ owner chose to ship it off and let whoever needs it turn it on. What that costs,
|
|
|
293
330
|
**Turning it back on is one attribute, on the root element, with no code change:**
|
|
294
331
|
|
|
295
332
|
```html
|
|
296
|
-
<html data-focus-outline="on">
|
|
333
|
+
<html data-focus-outline="on"></html>
|
|
297
334
|
```
|
|
298
335
|
|
|
299
336
|
**The switch is a single multiplier, not a scatter of overrides.** `--focus-outline` is one flag,
|
|
@@ -316,11 +353,11 @@ the **field** indicator on every control — one hairline (1px) in the focus hue
|
|
|
316
353
|
`--control-outline` halo — rather than the heavy 3px outline form. Measured, in Chromium, on
|
|
317
354
|
`ql.test` after the transition settles:
|
|
318
355
|
|
|
319
|
-
| control
|
|
320
|
-
|
|
|
321
|
-
| Input / Select trigger
|
|
322
|
-
| Button (primary)
|
|
323
|
-
| Sidebar nav row / list row | outline `0px`
|
|
356
|
+
| control | switch off | switch on |
|
|
357
|
+
| -------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- |
|
|
358
|
+
| Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px rgb(0,113,189)` + `rgba(0,182,228,0.11) 0 0 0 2px` |
|
|
359
|
+
| Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid rgb(0,113,189)` @ `0px` + same halo |
|
|
360
|
+
| Sidebar nav row / list row | outline `0px` | `outline: 1px solid rgb(0,113,189)` @ **`-1px`** (inset into the row) |
|
|
324
361
|
|
|
325
362
|
The field pair is the recoloured boundary plus the halo, exactly as the convention specifies for
|
|
326
363
|
this seed. The nav row insets its mark into its own shape rather than wrapping an already-shaded surface,
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -117,13 +117,14 @@ A change isn't done until its documentation reflects it:
|
|
|
117
117
|
```bash
|
|
118
118
|
pnpm lint # eslint — self-contained flat config
|
|
119
119
|
pnpm typecheck # tsc --noEmit
|
|
120
|
-
pnpm
|
|
121
|
-
pnpm
|
|
120
|
+
pnpm vitest run src/components/<group>/__tests__ --maxWorkers=2 # ONLY what you touched
|
|
121
|
+
pnpm test # FULL suite — CI only, never from an agent loop
|
|
122
|
+
pnpm preview:build # integration test: examples + docs must build — at most once, pre-PR
|
|
122
123
|
pnpm audit # godxjp-ui-audit — 0 errors for touched files
|
|
123
124
|
pnpm check:mcp-sync # MCP registry ↔ library export drift guard
|
|
124
125
|
```
|
|
125
126
|
|
|
126
|
-
`pnpm verify` and `pnpm verify:release` run these together (verify:release also builds). It needs `pnpm exec playwright install chromium` once locally; see [FRAME-A11Y-CI.md](./FRAME-A11Y-CI.md) for how to run/scope it, read the evidence, and regenerate its baseline after an accessibility fix.
|
|
127
|
+
`pnpm verify` and `pnpm verify:release` run these together (verify:release also builds) — **both include the full suite, so both belong to CI.** Locally, run them at most once immediately before opening a PR, never inside an edit loop and never while other agents are working on the same machine. It needs `pnpm exec playwright install chromium` once locally; see [FRAME-A11Y-CI.md](./FRAME-A11Y-CI.md) for how to run/scope it, read the evidence, and regenerate its baseline after an accessibility fix.
|
|
127
128
|
|
|
128
129
|
All gates are **self-contained** — no internal/external tooling package required. The eslint, prettier, and vitest setup live in the package (`eslint.config.js`, `prettier.config.mjs`, `vitest.config.ts`, `src/test/`), so a fresh checkout can lint/type-check/test without anything beyond the declared devDependencies.
|
|
129
130
|
|
package/docs/FORMS.md
CHANGED
|
@@ -44,11 +44,127 @@ export function CreateCustomerForm({ onSubmit }: { onSubmit: (v: Values) => void
|
|
|
44
44
|
|
|
45
45
|
## API
|
|
46
46
|
|
|
47
|
-
| Export
|
|
48
|
-
|
|
|
49
|
-
| `useZodForm`
|
|
50
|
-
| `FormRoot`
|
|
51
|
-
| `FormFieldControl`
|
|
47
|
+
| Export | Prop type | Purpose |
|
|
48
|
+
| ------------------- | ----------------------------------------- | --------------------------------------------- |
|
|
49
|
+
| `useZodForm` | `ZodSchemaProp` + `UseZodFormOptionsProp` | Hook — injects zodResolver |
|
|
50
|
+
| `FormRoot` | `FormRootProp` | FormProvider + `<form onSubmit>` + layout |
|
|
51
|
+
| `FormFieldControl` | `FormFieldControlProp` | Controller + FormField + error display |
|
|
52
|
+
| `FormFieldArray` | `FormFieldArrayProp` | Repeating rows on RHF's `useFieldArray` |
|
|
53
|
+
| `useFormWatch` | — | Subscribe to one field (antd `Form.useWatch`) |
|
|
54
|
+
| `useFormInstance` | — | The RHF instance, without prop-drilling |
|
|
55
|
+
| `useFormSubmitting` | — | Submit in flight (both paths) |
|
|
56
|
+
| `useFormDisabled` | — | Form-level `disabled`, for the action row |
|
|
57
|
+
|
|
58
|
+
### `FormRoot` — form-level knobs
|
|
59
|
+
|
|
60
|
+
`onSubmit` runs only after validation passes (antd `onFinish`). Everything else is optional:
|
|
61
|
+
|
|
62
|
+
| Prop | antd | Notes |
|
|
63
|
+
| ----------------------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------- |
|
|
64
|
+
| `onSubmitFailed(errors)` | `onFinishFailed` | RHF error tree; the first invalid field is focused regardless |
|
|
65
|
+
| `onSubmitError(error)` | — | Optional error callback; rejected submissions show localized feedback and retain values |
|
|
66
|
+
| `onReset` | — | A native `<Button type="reset">` restores `defaultValues`; this runs after |
|
|
67
|
+
| `scrollToFirstError` | `scrollToFirstError` | Default `true`; centres the field row, honours `prefers-reduced-motion` |
|
|
68
|
+
| `disabled` | Form `disabled` | Disables fields and submission; values remain in state. Buttons read `useFormDisabled()` |
|
|
69
|
+
| `layout` `labelWidth` `controlWidth` `labelAlign` `collapseBelow` | `layout` `labelCol` … | Renders the `Form` layout shell (see the spacing note below) |
|
|
70
|
+
| `density` / `requiredMark` | `size` / `requiredMark` | Forwarded to the same shell |
|
|
71
|
+
| `errors` | — | Server error bag; fields claim their keys, `<FormErrors />` shows the rest |
|
|
72
|
+
|
|
73
|
+
> **Spacing.** A bare `FormRoot` stacks its children with `ui-stack-md`. Passing ANY layout prop
|
|
74
|
+
> switches it to the `Form` shell, whose `--form-block-gap` owns the spacing instead — the two are
|
|
75
|
+
> never combined, so a form does not silently gain double gaps when a `layout` is added.
|
|
76
|
+
|
|
77
|
+
`columns` renders a responsive grid inside the form. Duplicate submissions are ignored until the current attempt settles. Reset returns the form to its defaults.
|
|
78
|
+
|
|
79
|
+
### `FormFieldControl` — field-level knobs
|
|
80
|
+
|
|
81
|
+
| Prop | antd | Notes |
|
|
82
|
+
| ----------------------------------------- | ------------------- | -------------------------------------------------------------------- |
|
|
83
|
+
| `disabled` | Item `disabled` | Defaults to `FormRoot disabled`; `false` re-enables one field |
|
|
84
|
+
| `dependencies` | `dependencies` | Re-validate this field when another changes (after the first submit) |
|
|
85
|
+
| `getValueFromEvent` | `getValueFromEvent` | Replaces the built-in DOM-event / raw-value detection |
|
|
86
|
+
| `normalize` | `normalize` | Transform before storing; return `previousValue` to REJECT a change |
|
|
87
|
+
| `help` | `help` | Shown instead of the resolved validation message |
|
|
88
|
+
| `validateStatus` `hasFeedback` `feedback` | same | Forwarded to `FormField` — the in-flight remote-check state |
|
|
89
|
+
| `preserve` | `preserve` | `false` unregisters the value when the field unmounts |
|
|
90
|
+
|
|
91
|
+
The binding includes both native `onChange` and godx-ui `onValueChange`; spreading it onto Input, Textarea or Select updates the same store. Native fields do not emit the shared callback twice. IDs are unique across sibling forms. Per-field layout, label/control width, label addons and column spans are forwarded to FormField.
|
|
92
|
+
|
|
93
|
+
**There is no `rules` prop, by design.** react-hook-form ignores per-field register rules whenever a
|
|
94
|
+
`resolver` is set, and `useZodForm` always sets one — a `rules` prop would silently do nothing.
|
|
95
|
+
antd's `rules` is the Zod schema here, including the cross-field case:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const schema = z
|
|
99
|
+
.object({ password: z.string().min(8), confirm: z.string() })
|
|
100
|
+
.refine((v) => v.confirm === v.password, { path: ["confirm"], message: "Mật khẩu không khớp" });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pair that refinement with `dependencies={["password"]}` on the `confirm` field so editing the
|
|
104
|
+
password re-runs it.
|
|
105
|
+
|
|
106
|
+
## Dynamic rows — `FormFieldArray` (antd `Form.List`)
|
|
107
|
+
|
|
108
|
+
`FormFieldArray` is react-hook-form's `useFieldArray`, so rows keep their identity across
|
|
109
|
+
insert/remove/move instead of being re-keyed by index. Each row hands back its dotted path prefix;
|
|
110
|
+
build the child field name from it and nested validation lands on the right row.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { FormFieldArray, FormFieldControl, FormRoot, useZodForm } from "@godxjp/ui/form";
|
|
114
|
+
|
|
115
|
+
const schema = z.object({
|
|
116
|
+
contacts: z.array(z.object({ email: z.string().email() })).min(1, "Cần ít nhất một liên hệ"),
|
|
117
|
+
});
|
|
118
|
+
type Values = z.infer<typeof schema>;
|
|
119
|
+
|
|
120
|
+
<FormFieldArray<Values, "contacts"> name="contacts">
|
|
121
|
+
{({ fields, append, remove, move, error, disabled }) => (
|
|
122
|
+
<>
|
|
123
|
+
{error && (
|
|
124
|
+
<Alert tone="destructive">
|
|
125
|
+
<AlertDescription>{error}</AlertDescription>
|
|
126
|
+
</Alert>
|
|
127
|
+
)}
|
|
128
|
+
{fields.map((row, position) => (
|
|
129
|
+
<Card key={row.key} variant="outline">
|
|
130
|
+
<CardContent>
|
|
131
|
+
<FormFieldControl<Values> name={`${row.name}.email`} label={`Liên hệ ${position + 1}`}>
|
|
132
|
+
{(field) => <Input {...field} value={String(field.value ?? "")} />}
|
|
133
|
+
</FormFieldControl>
|
|
134
|
+
<Button type="button" variant="destructive" onClick={() => remove(row.index)}>
|
|
135
|
+
Xoá
|
|
136
|
+
</Button>
|
|
137
|
+
</CardContent>
|
|
138
|
+
</Card>
|
|
139
|
+
))}
|
|
140
|
+
<Button
|
|
141
|
+
type="button"
|
|
142
|
+
variant="outline"
|
|
143
|
+
disabled={disabled}
|
|
144
|
+
onClick={() => append({ email: "" })}
|
|
145
|
+
>
|
|
146
|
+
Thêm liên hệ
|
|
147
|
+
</Button>
|
|
148
|
+
</>
|
|
149
|
+
)}
|
|
150
|
+
</FormFieldArray>;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- `row.key` is RHF's stable id — use it as the React key, **never** the index.
|
|
154
|
+
- `row.index` is the live position; pass it to `remove`/`move`, not the render position.
|
|
155
|
+
- `error` is the ARRAY-level message (a `.min(1)` on the array itself), not a row's.
|
|
156
|
+
- Also available: `prepend`, `insert`, `swap`, `replace`.
|
|
157
|
+
- react-hook-form path only — a server-driven `adapter` owns its own collection state.
|
|
158
|
+
|
|
159
|
+
## Watching a value — `useFormWatch`
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const plan = useFormWatch<Values>("plan"); // one field
|
|
163
|
+
const all = useFormWatch<Values>(); // every value
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Returns `unknown` (the same contract as the render-prop `value`) — narrow at the call site. It
|
|
167
|
+
works on BOTH paths: react-hook-form's `useWatch`, or the adapter's `getValue`/`getValues`.
|
|
52
168
|
|
|
53
169
|
## Forbidden
|
|
54
170
|
|
|
@@ -108,7 +224,9 @@ const form = useForm({ customer_nm: "", action_mode: "regist" });
|
|
|
108
224
|
- Never hand-filter the bag per page; that except-list is exactly what this mechanism removes.
|
|
109
225
|
- `FormFieldControl` forwards its `name`, so `FormRoot` + adapter fields claim their keys too;
|
|
110
226
|
give `<FormErrors errors={form.errors} />` the bag explicitly when there is no surrounding
|
|
111
|
-
`Form errors
|
|
227
|
+
`Form errors` — or pass the bag straight to `FormRoot errors`, which provides the same registry.
|
|
228
|
+
- An adapter may implement the optional `reset()`, which `FormRoot` calls for a native
|
|
229
|
+
`<Button type="reset">`, so the reset button behaves identically on both paths.
|
|
112
230
|
|
|
113
231
|
### Sibling Forms — one bag over several Card+Form sections
|
|
114
232
|
|
|
@@ -141,3 +259,30 @@ import { Form, FormErrors, FormErrorsProvider, FormField, Input } from "@godxjp/
|
|
|
141
259
|
|
|
142
260
|
A nested Form **with** its own `errors` starts a new registry that shadows the provider — its
|
|
143
261
|
claims and messages stay inside it (use this for an embedded sub-form with a separate bag).
|
|
262
|
+
|
|
263
|
+
### Multipart uploads and Inertia visits
|
|
264
|
+
|
|
265
|
+
For a form that submits files together, use `<Upload name="attachments[]" pastable />`.
|
|
266
|
+
The uploader appends staged local files during the native `formdata` event, including
|
|
267
|
+
files selected by dropping or pasting. Completed media uploads remain the responsibility
|
|
268
|
+
of `collectUploadCommitActions`.
|
|
269
|
+
|
|
270
|
+
For an endpoint that redirects back after each uploaded file, connect the reusable
|
|
271
|
+
Inertia bridge. Errors retain the local file for retry; removing or cancelling a file
|
|
272
|
+
cancels its visit. Each request runs asynchronously so selecting another file does not
|
|
273
|
+
silently cancel an unrelated upload.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { inertiaUpload } from "@godxjp/ui/inertia";
|
|
277
|
+
import { Upload } from "@godxjp/ui/data-entry";
|
|
278
|
+
|
|
279
|
+
<Upload
|
|
280
|
+
value={items}
|
|
281
|
+
onValueChange={(next) => setItems(next.filter((item) => item.status !== "done"))}
|
|
282
|
+
onUpload={inertiaUpload(
|
|
283
|
+
(file, callbacks) => router.post(uploadUrl, { file }, { ...callbacks, preserveScroll: true }),
|
|
284
|
+
t("upload.failed"),
|
|
285
|
+
)}
|
|
286
|
+
pastable
|
|
287
|
+
/>;
|
|
288
|
+
```
|
|
@@ -3,16 +3,28 @@
|
|
|
3
3
|
> Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
|
|
4
4
|
> Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
|
|
5
5
|
|
|
6
|
-
- Public components: **
|
|
7
|
-
- With ≥1 frame: **
|
|
6
|
+
- Public components: **137**
|
|
7
|
+
- With ≥1 frame: **133** · zero-frame: **4**
|
|
8
8
|
- Fully covered (every contract axis declared): **0**
|
|
9
9
|
|
|
10
|
+
## UNTESTED — no `/frame/**` route
|
|
11
|
+
|
|
12
|
+
- [ ] `inertiaUpload`
|
|
13
|
+
- [ ] `CardBar`
|
|
14
|
+
- [ ] `VisuallyHidden`
|
|
15
|
+
- [ ] `RangeTimeline`
|
|
16
|
+
|
|
10
17
|
## Per-component contract axes
|
|
11
18
|
|
|
12
19
|
Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
13
20
|
|
|
14
21
|
| Component | Frame | visua | state | owner | conte | respo | inter | acces | prefe | async |
|
|
15
22
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
23
|
+
| inertiaUpload | **✗** | · | · | · | · | · | · | · | · | · |
|
|
24
|
+
| CardBar | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
25
|
+
| TimeRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
26
|
+
| VisuallyHidden | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
27
|
+
| RangeTimeline | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
16
28
|
| PageContainer | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
|
|
17
29
|
| Flex | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
18
30
|
| ResponsiveGrid | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
@@ -55,21 +67,21 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
55
67
|
| Table | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
56
68
|
| DataState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
57
69
|
| InfiniteQueryState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
58
|
-
| Form | ✓ | ✓ |
|
|
70
|
+
| Form | ✓ | ✓ | · | N/A | · | · | · | · | · | · |
|
|
59
71
|
| FormField | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
60
72
|
| FormErrors | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
61
73
|
| Input | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
62
74
|
| NumberInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
63
|
-
| SearchInput | ✓ |
|
|
75
|
+
| SearchInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
64
76
|
| Select | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
65
77
|
| Switch | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
66
78
|
| Textarea | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
67
79
|
| Label | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
68
80
|
| Checkbox | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
69
81
|
| RadioGroup | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
70
|
-
| MonthPicker | ✓ |
|
|
71
|
-
| MonthRangePicker | ✓ |
|
|
72
|
-
| DatePicker | ✓ |
|
|
82
|
+
| MonthPicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
83
|
+
| MonthRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
84
|
+
| DatePicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
73
85
|
| Dialog | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
74
86
|
| AlertDialog | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
75
87
|
| AlertDialogRoot | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
@@ -79,16 +91,16 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
79
91
|
| SkeletonTable | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
80
92
|
| Toaster | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
81
93
|
| Tabs | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
|
|
82
|
-
| Pagination | ✓ |
|
|
94
|
+
| Pagination | ✓ | · | · | · | · | · | · | · | · | · |
|
|
83
95
|
| DropdownMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
84
96
|
| Steps | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
|
|
85
97
|
| Toolbar | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
86
98
|
| AppProvider | ✓ | · | · | · | · | · | · | · | · | · |
|
|
87
99
|
| formatDate | ✓ | · | · | · | · | · | · | · | · | · |
|
|
88
|
-
| TimePicker | ✓ |
|
|
89
|
-
| DateRangePicker | ✓ |
|
|
90
|
-
| Cascader | ✓ |
|
|
91
|
-
| TreeSelect | ✓ |
|
|
100
|
+
| TimePicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
101
|
+
| DateRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
|
|
102
|
+
| Cascader | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
103
|
+
| TreeSelect | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
92
104
|
| Transfer | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
93
105
|
| Upload | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
94
106
|
| UploadCropDialog | ✓ | · | · | · | · | · | · | · | · | · |
|
|
@@ -112,12 +124,12 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
112
124
|
| AspectRatio | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
113
125
|
| Accordion | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
114
126
|
| HoverCard | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
115
|
-
| PasswordInput | ✓ | ✓ |
|
|
127
|
+
| PasswordInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
116
128
|
| PasswordStrength | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
117
|
-
| InputOTP | ✓ |
|
|
129
|
+
| InputOTP | ✓ | · | · | · | · | · | · | · | · | · |
|
|
118
130
|
| Rating | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
119
|
-
| Segmented | ✓ |
|
|
120
|
-
| TagInput | ✓ |
|
|
131
|
+
| Segmented | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
132
|
+
| TagInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
|
|
121
133
|
| ContextMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
122
134
|
| Menubar | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
123
135
|
| NavigationMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
@@ -140,6 +152,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
140
152
|
| AuthAccountSummary | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
141
153
|
| AuthStack | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
142
154
|
| OrgSwitcher | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
155
|
+
| AppLauncher | ✓ | N/A | · | · | · | · | · | · | · | · |
|
|
143
156
|
| FilterBar | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
144
157
|
| PermissionMatrix | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
145
158
|
| BranchScopePicker | ✓ | N/A | · | · | · | · | · | · | · | · |
|
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prop Vocabulary + Design Token Standards
|
|
2
2
|
|
|
3
|
-
These rules are normative for @godxjp/ui
|
|
3
|
+
These rules are normative for @godxjp/ui. They came out of the props-vocabulary / token-consistency debate, whose record lived in `debate/` until that directory was removed — this file is now the normative source, not a mirror of one.
|
|
4
4
|
|
|
5
5
|
1. Prop vocabulary: every exported `*Prop` type in `src/props/components/` MUST have exactly one `COMPONENT_PROP_REGISTRY` entry.
|
|
6
6
|
2. Prop vocabulary: every public property in an exported component prop type MUST map to one `VOCABULARY_REGISTRY` entry or to a `local: true` registry record with a non-empty `reason`.
|
package/docs/TESTING.md
CHANGED
|
@@ -5,10 +5,16 @@ Every component **must** ship with a Vitest suite before merge.
|
|
|
5
5
|
## Run
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
pnpm
|
|
11
|
-
pnpm
|
|
8
|
+
# While working — ONLY the tests for what you touched. Tests sit beside the
|
|
9
|
+
# component in `src/components/<group>/__tests__/`, so scope to that folder.
|
|
10
|
+
pnpm vitest run src/components/<group>/__tests__ --maxWorkers=2
|
|
11
|
+
pnpm vitest watch src/components/<group>/__tests__ # watch one component
|
|
12
|
+
|
|
13
|
+
# CI only — 506 files / 3700+ tests. Do NOT run these from an agent loop or with
|
|
14
|
+
# other agents on the machine: measured 70 vitest workers at load 90, an overheating
|
|
15
|
+
# laptop and a burnt monthly API budget.
|
|
16
|
+
pnpm test # single run — CI
|
|
17
|
+
pnpm test:coverage # v8 coverage report — CI
|
|
12
18
|
```
|
|
13
19
|
|
|
14
20
|
## File location
|
|
@@ -68,7 +74,7 @@ import "@testing-library/jest-dom/vitest";
|
|
|
68
74
|
| `react-hooks/set-state-in-effect` | sync setState in effects |
|
|
69
75
|
| `RenderLoopGuard` in tests | runaway re-renders during Vitest |
|
|
70
76
|
|
|
71
|
-
Run `pnpm lint` before
|
|
77
|
+
Run `pnpm lint` before your scoped test run — official React Compiler rules align with test guard.
|
|
72
78
|
|
|
73
79
|
## Forms testing
|
|
74
80
|
|
|
@@ -83,7 +89,10 @@ Never test forms with raw `useState` — mirror production `FormRoot` + `FormFie
|
|
|
83
89
|
|
|
84
90
|
## CI gate
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
The full suite must pass with zero failures before any UI PR merges — **and that run belongs
|
|
93
|
+
to CI, not to you.** Locally you run only the tests for the files you changed; CI runs
|
|
94
|
+
everything on the PR. `tal --help` states the same rule for the agent loop: *FULL SUITE KHÔNG
|
|
95
|
+
THUỘC VỀ VÒNG LẶP.*
|
|
87
96
|
|
|
88
97
|
## When tests fail
|
|
89
98
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
2
|
+
import { Flex, PageContainer } from "@godxjp/ui/layout";
|
|
3
|
+
import { BarChart } from "@godxjp/ui/charts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 横棒グラフの分類軸と、カードの見出しとの重複 — gh#409 · 1 と 2。
|
|
7
|
+
*
|
|
8
|
+
* 監理団体の実習実施者(受入企業)名は「株式会社」を含む 8〜10 文字の全角で、幅は 100〜125px に
|
|
9
|
+
* なります。分類軸の幅を固定にすると、名前の**先頭**、つまり識別に必要な部分から切り落とされます。
|
|
10
|
+
* BarChart は実際に描画される字形で各目盛りを実測し、いちばん広い目盛りに合わせて軸を確保します。
|
|
11
|
+
* 上限はトークン `--chart-category-axis-max-fraction` が持ち、これを超える名前だけが**末尾**で
|
|
12
|
+
* 省略され、全文は目盛りの `<title>` と図の代替テキストに残ります。
|
|
13
|
+
*
|
|
14
|
+
* 下段のカードは `showCaption={false}`。`CardTitle` が見出しを担うので、キャプションは
|
|
15
|
+
* `sr-only` として DOM に残り、`role="img"` の名前は失われません。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 実習実施者ごとの受入人数。企業名は 8〜10 文字の全角で、固定幅の軸では先頭が消えていました。 */
|
|
19
|
+
const acceptances = [
|
|
20
|
+
{ company: "株式会社山田製作所", trainees: 42 },
|
|
21
|
+
{ company: "佐藤食品株式会社", trainees: 35 },
|
|
22
|
+
{ company: "株式会社中村建設", trainees: 28 },
|
|
23
|
+
{ company: "東海精密工業株式会社", trainees: 24 },
|
|
24
|
+
{ company: "みどり農産株式会社", trainees: 18 },
|
|
25
|
+
{ company: "大和金属加工株式会社", trainees: 12 },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export default function Demo() {
|
|
29
|
+
return (
|
|
30
|
+
<PageContainer
|
|
31
|
+
title="CJK category axis"
|
|
32
|
+
subtitle="横棒グラフの分類軸を実測で確保し、見出しの重複をなくす"
|
|
33
|
+
>
|
|
34
|
+
<Flex direction="col" gap="lg">
|
|
35
|
+
<Card>
|
|
36
|
+
<CardHeader>
|
|
37
|
+
<CardTitle level={2}>横棒 BarChart · 分類軸は目盛りの実測で決まる</CardTitle>
|
|
38
|
+
<CardDescription>
|
|
39
|
+
いちばん広い目盛りに合わせて軸幅を確保します。キャプションは既定どおり表示。
|
|
40
|
+
</CardDescription>
|
|
41
|
+
</CardHeader>
|
|
42
|
+
<CardContent>
|
|
43
|
+
<BarChart
|
|
44
|
+
id="cjk-axis-chart"
|
|
45
|
+
label="実習実施者別 受入人数"
|
|
46
|
+
data={acceptances}
|
|
47
|
+
categoryKey="company"
|
|
48
|
+
series={[{ dataKey: "trainees", label: "受入人数" }]}
|
|
49
|
+
showLegend={false}
|
|
50
|
+
size="md"
|
|
51
|
+
horizontal
|
|
52
|
+
/>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
|
+
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader>
|
|
58
|
+
<CardTitle level={2}>実習実施者別 受入人数</CardTitle>
|
|
59
|
+
<CardDescription>
|
|
60
|
+
`showCaption={"{false}"}` — 見出しは CardTitle が担い、キャプションは sr-only
|
|
61
|
+
として残るのでアクセシブルな名前は保たれます。
|
|
62
|
+
</CardDescription>
|
|
63
|
+
</CardHeader>
|
|
64
|
+
<CardContent>
|
|
65
|
+
<BarChart
|
|
66
|
+
id="cjk-axis-chart-no-caption"
|
|
67
|
+
label="実習実施者別 受入人数"
|
|
68
|
+
data={acceptances}
|
|
69
|
+
categoryKey="company"
|
|
70
|
+
series={[{ dataKey: "trainees", label: "受入人数" }]}
|
|
71
|
+
showLegend={false}
|
|
72
|
+
showCaption={false}
|
|
73
|
+
size="md"
|
|
74
|
+
horizontal
|
|
75
|
+
/>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
</Flex>
|
|
79
|
+
</PageContainer>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -281,6 +281,28 @@ export default function Demo() {
|
|
|
281
281
|
</Card>
|
|
282
282
|
</ResponsiveGrid>
|
|
283
283
|
</Flex>
|
|
284
|
+
|
|
285
|
+
<Card>
|
|
286
|
+
<CardHeader>
|
|
287
|
+
<CardTitle level={2}>連続したツールバー</CardTitle>
|
|
288
|
+
</CardHeader>
|
|
289
|
+
<CardContent>
|
|
290
|
+
<Text>本文</Text>
|
|
291
|
+
</CardContent>
|
|
292
|
+
<CardBar pad={{ block: 1, inline: 3 }} surface="muted" border="block-start" gap="sm">
|
|
293
|
+
<Button variant="ghost" size="sm">
|
|
294
|
+
添付
|
|
295
|
+
</Button>
|
|
296
|
+
</CardBar>
|
|
297
|
+
<CardBar
|
|
298
|
+
pad={{ block: 2, inline: 3 }}
|
|
299
|
+
border="block-start"
|
|
300
|
+
gap="sm"
|
|
301
|
+
extra={<Button>送信</Button>}
|
|
302
|
+
>
|
|
303
|
+
<Text tone="muted">下書きを保存しました</Text>
|
|
304
|
+
</CardBar>
|
|
305
|
+
</Card>
|
|
284
306
|
</Flex>
|
|
285
307
|
</PageContainer>
|
|
286
308
|
);
|