@godxjp/ui 22.0.0 → 23.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.
Files changed (115) hide show
  1. package/README.md +4 -4
  2. package/dist/app/date-format-labels.d.ts +14 -2
  3. package/dist/app/date-format-labels.js +1 -1
  4. package/dist/app/date-formats.d.ts +14 -2
  5. package/dist/app/date-formats.js +9 -2
  6. package/dist/components/data-display/data-table.d.ts +8 -1
  7. package/dist/components/data-display/data-table.js +9 -2
  8. package/dist/components/data-display/scroll-area.d.ts +24 -4
  9. package/dist/components/data-display/scroll-area.js +34 -72
  10. package/dist/components/data-entry/cascader.js +8 -6
  11. package/dist/components/data-entry/checkbox.js +3 -1
  12. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  13. package/dist/components/data-entry/choice-hit-target.js +12 -0
  14. package/dist/components/data-entry/control-surface.d.ts +8 -0
  15. package/dist/components/data-entry/control-surface.js +10 -1
  16. package/dist/components/data-entry/form-field.js +5 -4
  17. package/dist/components/data-entry/input.js +1 -1
  18. package/dist/components/data-entry/number-input.js +27 -6
  19. package/dist/components/data-entry/radio.js +4 -3
  20. package/dist/components/data-entry/search-select.js +225 -95
  21. package/dist/components/data-entry/select.d.ts +76 -14
  22. package/dist/components/data-entry/select.js +495 -163
  23. package/dist/components/data-entry/slider.d.ts +3 -11
  24. package/dist/components/data-entry/slider.js +582 -93
  25. package/dist/components/data-entry/switch.js +3 -1
  26. package/dist/components/feedback/dialog.js +2 -2
  27. package/dist/components/feedback/sheet.js +2 -22
  28. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  29. package/dist/components/navigation/dropdown-menu.js +190 -15
  30. package/dist/components/navigation/filter-bar.js +2 -1
  31. package/dist/components/navigation/index.d.ts +0 -3
  32. package/dist/components/navigation/index.js +0 -78
  33. package/dist/components/navigation/tabs.js +16 -4
  34. package/dist/components/ui/index.d.ts +0 -3
  35. package/dist/components/ui/index.js +0 -3
  36. package/dist/components/ui/input-otp.d.ts +1 -1
  37. package/dist/components/ui/segmented.js +3 -1
  38. package/dist/components/ui/tag-input.js +3 -5
  39. package/dist/i18n/messages/en.json +5 -1
  40. package/dist/i18n/messages/ja.json +5 -1
  41. package/dist/i18n/messages/vi.json +5 -1
  42. package/dist/lib/breakpoint-token.d.ts +8 -0
  43. package/dist/lib/breakpoint-token.js +29 -0
  44. package/dist/lib/select-options.d.ts +21 -0
  45. package/dist/lib/select-options.js +46 -0
  46. package/dist/props/components/data-display.prop.d.ts +17 -7
  47. package/dist/props/components/data-entry.prop.d.ts +323 -30
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/navigation.prop.d.ts +25 -1
  50. package/dist/props/registry.d.ts +69 -1
  51. package/dist/props/registry.js +75 -0
  52. package/dist/props/vocabulary/data.prop.d.ts +5 -0
  53. package/dist/props/vocabulary/index.d.ts +1 -1
  54. package/dist/styles/control.css +246 -28
  55. package/dist/styles/data-display-layout.css +18 -10
  56. package/dist/styles/focus-ring.css +28 -0
  57. package/dist/styles/fonts.css +88 -4
  58. package/dist/styles/form-layout.css +4 -0
  59. package/dist/styles/layout.css +2 -1
  60. package/dist/styles/navigation-layout.css +24 -238
  61. package/dist/styles/shell-layout.css +148 -139
  62. package/dist/styles/table-layout.css +12 -5
  63. package/dist/tokens/components/control.css +12 -3
  64. package/dist/tokens/components/data-display.css +0 -5
  65. package/dist/tokens/components/navigation.css +5 -5
  66. package/dist/tokens/components/scroll-area.css +7 -0
  67. package/docs/CONSUMER-RULES.md +7 -2
  68. package/docs/CUSTOMER-THEMING.md +25 -9
  69. package/docs/DATETIME.md +11 -2
  70. package/docs/FRAME-COVERAGE-REPORT.md +4 -7
  71. package/docs/SPACING.md +35 -5
  72. package/docs/TOKENS.md +1 -1
  73. package/docs/data-display/data-table/index.tsx +44 -3
  74. package/docs/data-display/popover.tsx +4 -6
  75. package/docs/data-display/scroll-area.tsx +51 -45
  76. package/docs/data-display/table.tsx +1 -1
  77. package/docs/data-entry/date-picker.tsx +2 -3
  78. package/docs/data-entry/input-otp.tsx +5 -2
  79. package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
  80. package/docs/data-entry/segmented.tsx +40 -1
  81. package/docs/data-entry/select-matrix.tsx +1 -2
  82. package/docs/data-entry/select.tsx +108 -3
  83. package/docs/data-entry/slider.tsx +125 -47
  84. package/docs/feedback/dialog.tsx +1 -2
  85. package/docs/feedback/sheet.tsx +1 -2
  86. package/docs/layout/app-shell-states.tsx +143 -0
  87. package/docs/navigation/dropdown-menu.tsx +77 -6
  88. package/docs/navigation/tabs.tsx +72 -0
  89. package/docs/roadmap/ai-chat-components.md +1 -2
  90. package/docs/roadmap/parity-audit-data-entry.md +44 -33
  91. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  92. package/docs/roadmap/parity-backlog.md +2 -3
  93. package/docs/roadmap/tree-components.md +0 -1
  94. package/package.json +25 -55
  95. package/scripts/_agent-setup.mjs +18 -0
  96. package/scripts/guinea-pig-skill.md +12 -6
  97. package/scripts/ui-audit.mjs +185 -7
  98. package/scripts/visual-audit-rules.mjs +0 -7
  99. package/scripts/visual-audit.mjs +6 -27
  100. package/dist/components/navigation/context-menu.d.ts +0 -21
  101. package/dist/components/navigation/context-menu.js +0 -149
  102. package/dist/components/navigation/menubar.d.ts +0 -28
  103. package/dist/components/navigation/menubar.js +0 -136
  104. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  105. package/dist/components/navigation/navigation-menu.js +0 -91
  106. package/dist/components/ui/context-menu.d.ts +0 -1
  107. package/dist/components/ui/context-menu.js +0 -2
  108. package/dist/components/ui/menubar.d.ts +0 -1
  109. package/dist/components/ui/menubar.js +0 -2
  110. package/dist/components/ui/navigation-menu.d.ts +0 -1
  111. package/dist/components/ui/navigation-menu.js +0 -2
  112. package/docs/FRAME-A11Y-CI.md +0 -349
  113. package/docs/navigation/context-menu.tsx +0 -128
  114. package/docs/navigation/menubar.tsx +0 -141
  115. package/docs/navigation/navigation-menu.tsx +0 -158
@@ -3,8 +3,8 @@
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: **138**
7
- - With ≥1 frame: **133** · zero-frame: **5**
6
+ - Public components: **135**
7
+ - With ≥1 frame: **130** · zero-frame: **5**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -74,7 +74,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
74
74
  | Input | ✓ | ✓ | · | · | · | · | · | · | · | · |
75
75
  | NumberInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
76
76
  | SearchInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
77
- | Select | ✓ | N/A | · | · | · | · | · | · | · | · |
77
+ | Select | ✓ | · | · | · | · | · | · | · | · | · |
78
78
  | Switch | ✓ | ✓ | · | · | · | · | · | · | · | · |
79
79
  | Textarea | ✓ | ✓ | · | · | · | · | · | · | · | · |
80
80
  | Label | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -91,7 +91,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
91
91
  | Toaster | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
92
92
  | Tabs | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
93
93
  | Pagination | ✓ | · | · | · | · | · | · | · | · | · |
94
- | DropdownMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
94
+ | DropdownMenu | ✓ | N/A | · | · | · | · | · | · | · | · |
95
95
  | Steps | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
96
96
  | Toolbar | ✓ | N/A | · | N/A | · | · | · | · | · | · |
97
97
  | AppProvider | ✓ | · | · | · | · | · | · | · | · | · |
@@ -128,9 +128,6 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
128
128
  | Rating | ✓ | N/A | · | · | · | · | · | · | · | · |
129
129
  | Segmented | ✓ | ✓ | · | · | · | · | · | · | · | · |
130
130
  | TagInput | ✓ | ✓ | · | · | · | · | · | · | · | · |
131
- | ContextMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
132
- | Menubar | ✓ | N/A | N/A | · | · | · | · | · | · | · |
133
- | NavigationMenu | ✓ | N/A | N/A | · | · | · | · | · | · | · |
134
131
  | ResizablePanel | ✓ | N/A | · | N/A | · | · | · | · | · | · |
135
132
  | Carousel | ✓ | N/A | N/A | · | · | · | · | · | · | · |
136
133
  | AppSettingPicker | ✓ | N/A | · | · | · | · | · | · | · | · |
package/docs/SPACING.md CHANGED
@@ -42,11 +42,11 @@ Implementation: `src/tokens/base.css` (values) · layout owners: `src/styles/*-l
42
42
  | `--card-space-body-y` | `--space-section-active` | Header↔body gap |
43
43
  | `--card-space-header-y` / `--card-space-footer-y` | `--space-stack-sm` | Banded header + separated footer band |
44
44
 
45
- | Component | Use |
46
- | ------------------------- | ------------------------------------------- |
47
- | `StatCard` | KPI / stat tile (`solo` path) |
48
- | `CardContent solo` | Body-only card (same padding as `StatCard`) |
49
- | `CardContent flush tight` | Edge-to-edge table/tabs in card |
45
+ | Component | Use |
46
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | `StatCard` | KPI / stat tile (`solo` path) |
48
+ | `CardContent solo` | Body-only card (same padding as `StatCard`) |
49
+ | `CardContent flush` | Edge-to-edge table/tabs in card — REQUIRED when the table IS the body (rule 10). `flush` alone drops the border, radius and inline padding; add `tight` only to change the HEADER band. |
50
50
 
51
51
  See preview **Data Display → Card** for live examples.
52
52
 
@@ -64,6 +64,36 @@ Pick by AXIS, and note that the three do **not** share one value: each reads a d
64
64
 
65
65
  Because `ui-card-inset-x` reads `--card-space-inset`, it follows the card it sits in: a `Card density="tight"` or `"cozy"` moves those cells with the shell. The other two read fixed steps (band and body) and do not.
66
66
 
67
+ ## Control interiors are NOT on this scale
68
+
69
+ Everything above is the space **between** things — page sections, siblings in a stack, a card's
70
+ shell against its content. A control's **inside** is a different measurement and it is derived from
71
+ the control band (`--control-height`, `--control-padding-x`), never from `--space-*`. The two do not
72
+ meet, and reading a step off the table above as a minimum for a control's interior gives the wrong
73
+ answer.
74
+
75
+ The case that keeps coming up is `Segmented`'s track (gh#503). Its padding is **2px**, and the
76
+ label inside it therefore sits ~5px from the track's outer edge — under `--space-2` (8px), which is
77
+ NOT a floor this document sets for anything.
78
+
79
+ 2px is the whole geometry of the control:
80
+
81
+ ```
82
+ label height = --control-height − track padding × 2 → 32 − 4 = 28
83
+ ```
84
+
85
+ so the track measures **exactly** `--control-height` and a `Segmented` sits level with the `Input`
86
+ and the `Button` beside it on the same row. Raise the padding to 8px and one of two things has to
87
+ give: either the label band drops to 16px — a 14px type size in a 16px box, under every hit target
88
+ this library holds — or the track grows to 44px (28 + 8 × 2) and stops lining up with every other
89
+ control. The same derivation is why a `Button`'s own label sits ~5px inside its border. A control
90
+ is sized by its band; the band is what a dense enterprise UI is for.
91
+
92
+ **The knob, if a service wants a roomier control.** `--segmented-track-padding` is a published
93
+ component token (`src/tokens/components/segmented.css`); raising it re-derives the item height from
94
+ the same formula, so the track stays exactly one control tall. Do not reach for a Tailwind `p-*` on
95
+ the control — the audit rejects it, and it would break the identity above.
96
+
67
97
  ## MCP
68
98
 
69
99
  `get_pattern page-sections` (a page of Cards, spaced by the page) · `get_rule 40` (mobile-first spacing) · `list_audit_rules` (the spacing rules the CLI enforces).
package/docs/TOKENS.md CHANGED
@@ -220,7 +220,7 @@ The census confirmed the number and rejected the diagnosis: **`width` is not one
220
220
 
221
221
  **Px, never rem.** A stroke is a _device_ line: its job is to read as one crisp rule at any type size, so it must not grow when the root font-size does. Every token that moved onto this scale was already `px` for that reason; a `rem` thickness (`--slider-track-height: 0.375rem`) is a different decision and stayed where it was. **Not `--scaling`-multiplied** either — `--scaling` is a density knob, and a 1px divider at 0.92px would blur rather than tighten.
222
222
 
223
- `--focus-ring-width` is now a member (`var(--stroke-md)`) rather than a parallel authority, so a high-contrast theme that wants every emphasis stroke at 3px sets one token.
223
+ The focus mark reads this scale too, through `--focus-ring-weight` `var(--focus-outline-weight)`, which is `var(--stroke-hairline)` (the light 1px mark the ON state ships). `--focus-ring-width` is DERIVED from it (`weight × --focus-outline`, the on/off switch) and must never be set directly; a theme that wants a 2px focus perimeter (WCAG 2.2 SC 2.4.13) sets `--focus-ring-weight: var(--stroke-md)`. See docs/CUSTOMER-THEMING.md.
224
224
 
225
225
  #### `--band-height-*` — the vertical extent of a horizontal band
226
226
 
@@ -1,7 +1,7 @@
1
1
  import { useMemo, useState } from "react";
2
2
 
3
3
  import { AppProvider } from "@godxjp/ui/app";
4
- import { Badge, DataTable, type ColumnDef } from "@godxjp/ui/data-display";
4
+ import { Badge, Card, CardContent, DataTable, type ColumnDef } from "@godxjp/ui/data-display";
5
5
  import { Button, Text } from "@godxjp/ui/general";
6
6
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
7
  import {
@@ -93,6 +93,16 @@ const columns: ColumnDef<Invoice>[] = [
93
93
  },
94
94
  ];
95
95
 
96
+ /* Five 240px columns: wider than the frame at every viewport the gates sweep, and wide for a
97
+ * reason no font can change — the width is declared, so the overflow is deterministic in CI. */
98
+ const wideColumns: ColumnDef<Invoice>[] = [
99
+ { key: "id", header: "請求書番号", width: "240px" },
100
+ { key: "partner", header: "取引先", width: "240px" },
101
+ { key: "amount", header: "金額", align: "right", width: "240px" },
102
+ { key: "status", header: "状態", width: "240px" },
103
+ { key: "date", header: "期限日", width: "240px" },
104
+ ];
105
+
96
106
  export default function Demo() {
97
107
  const [selected, setSelected] = useState<Set<string>>(new Set(["INV-0311"]));
98
108
  const [sort, setSort] = useState<SortStateProp | undefined>({
@@ -117,6 +127,22 @@ export default function Demo() {
117
127
  subtitle="sortable · selectable · bulk actions · row actions · loading · clickable rows · empty state"
118
128
  >
119
129
  <Flex direction="col" gap="lg">
130
+ {/* A table WIDER than its frame, inside a flush Card — the shape a list page uses. It must
131
+ scroll inside its own box with the fade at the inline end, never cut its last columns
132
+ (WCAG 1.4.10). `check:data-table-overflow` measures this section. */}
133
+ <Flex direction="col" gap="sm" id="wide-overflow" className="max-w-3xl">
134
+ <Text weight="medium">枠より広い表(横スクロール · 列を切らない)</Text>
135
+ <Card>
136
+ <CardContent flush>
137
+ <DataTable
138
+ data={invoices}
139
+ columns={wideColumns}
140
+ getRowId={(row) => row.id}
141
+ selectable
142
+ />
143
+ </CardContent>
144
+ </Card>
145
+ </Flex>
120
146
  <Flex direction="col" gap="sm" id="text-action-width">
121
147
  <Text weight="medium">日本語の行アクション</Text>
122
148
  <DataTable
@@ -264,13 +290,28 @@ export default function Demo() {
264
290
  </AppProvider>
265
291
  </Flex>
266
292
 
267
- <Flex direction="col" gap="sm">
293
+ <Flex direction="col" gap="sm" id="empty-builtin">
268
294
  <Text as="div" weight="medium">
269
295
  空の状態(data が空のとき自動表示)
270
296
  </Text>
271
297
  <DataTable data={[]} columns={columns} getRowId={(row) => row.id} />
272
298
  </Flex>
273
299
 
300
+ {/* The SAME cell with consumer content in it. `empty` takes a ReactNode, so a plain string
301
+ is a legal call — and the lifecycle cell used to be `padding: 0`, which put that string
302
+ flush against the table's edge. Measured here; see check:data-table-empty-inset. */}
303
+ <Flex direction="col" gap="sm" id="empty-plain-string">
304
+ <Text as="div" weight="medium">
305
+ 空の状態(empty に文字列を渡す)
306
+ </Text>
307
+ <DataTable
308
+ data={[]}
309
+ columns={columns}
310
+ getRowId={(row) => row.id}
311
+ empty="まだ登録がありません"
312
+ />
313
+ </Flex>
314
+
274
315
  {/* — the two failure states, in the same table grid as empty/loading. */}
275
316
  <Flex direction="col" gap="sm">
276
317
  <Text as="div" weight="medium">
@@ -292,7 +333,7 @@ export default function Demo() {
292
333
  <DataTable data={[]} columns={columns} getRowId={(row) => row.id} error />
293
334
  </Flex>
294
335
 
295
- <Flex direction="col" gap="sm">
336
+ <Flex direction="col" gap="sm" id="error-custom-node">
296
337
  <Text as="div" weight="medium">
297
338
  エラー(error にノードを渡して文言を差し替え)
298
339
  </Text>
@@ -135,10 +135,7 @@ export default function Demo() {
135
135
  <Flex direction="row" wrap gap="sm">
136
136
  <Popover>
137
137
  <PopoverTrigger asChild>
138
- {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
139
- <Button data-axe-open variant="outline">
140
- side=top
141
- </Button>
138
+ <Button variant="outline">side=top</Button>
142
139
  </PopoverTrigger>
143
140
  <PopoverContent side="top" aria-label="上側の配置例">
144
141
  <PopoverDescription>上に開きます。</PopoverDescription>
@@ -215,8 +212,9 @@ export default function Demo() {
215
212
  <CardDescription>
216
213
  既定の panel は --popover-width(18rem)。auto は中身に測らせます — 自前の幅を持つもの
217
214
  (Calendar、チャート、固定幅のプレビュー)はこれ以外に正解がありません。trigger
218
- はアンカーに合わせます。className に w-* を書くのは呼び出し側の定数で、どのテーマからも
219
- retune できません。flush が padding 側で避けているのと同じ話です。
215
+ はアンカーに合わせます。className に w-*
216
+ を書くのは呼び出し側の定数で、どのテーマからも retune できません。flush が padding
217
+ 側で避けているのと同じ話です。
220
218
  </CardDescription>
221
219
  </CardHeader>
222
220
  <CardContent>
@@ -14,10 +14,10 @@ import { Button, Text } from "@godxjp/ui/general";
14
14
  import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
15
15
 
16
16
  /**
17
- * ScrollArea — custom scrollbar container. ALWAYS set an explicit height/max-height
18
- * (vertical) or width (horizontal) on the wrapper or the scrollbar never appears.
19
- * `type` controls when the bar is visible; `ScrollArea orientation="horizontal"`
20
- * adds a horizontal bar. Composed only from real @godxjp/ui components.
17
+ * ScrollArea — a native scrolling box. ALWAYS give it an explicit height/max-height
18
+ * (vertical) or width (horizontal), or nothing ever overflows and no scrollbar appears.
19
+ * `orientation` decides which axes may scroll; the browser draws the bar, styled from
20
+ * the --scroll-area-* tokens. Composed only from real @godxjp/ui components.
21
21
  */
22
22
  const entries = Array.from(
23
23
  { length: 18 },
@@ -97,7 +97,7 @@ export default function Demo() {
97
97
  return (
98
98
  <PageContainer
99
99
  title="ScrollArea"
100
- subtitle="Custom scrollbar container · needs an explicit height"
100
+ subtitle="Native scrolling, token-styled scrollbar · needs an explicit height"
101
101
  >
102
102
  <Flex direction="col" gap="lg">
103
103
  <Card>
@@ -115,7 +115,6 @@ export default function Demo() {
115
115
  anchor="bottom"
116
116
  viewportRef={streamViewport}
117
117
  onAnchoredChange={setAnchored}
118
- type="always"
119
118
  >
120
119
  <CardContent>
121
120
  <Flex direction="col">
@@ -173,7 +172,7 @@ export default function Demo() {
173
172
  <CardContent>
174
173
  <Card variant="outline" className="h-40 w-full">
175
174
  <CardContent flush>
176
- <ScrollArea anchor="bottom" anchorOffset={0} type="always">
175
+ <ScrollArea anchor="bottom" anchorOffset={0}>
177
176
  <CardContent>
178
177
  <Flex direction="col" gap="xs">
179
178
  {entries.map((e) => (
@@ -215,40 +214,13 @@ export default function Demo() {
215
214
  </CardContent>
216
215
  </Card>
217
216
 
218
- <Card>
219
- <CardHeader>
220
- <CardTitle level={2}>type=&quot;always&quot;(バーを常時表示)</CardTitle>
221
- <CardDescription>
222
- 既定の hover はホバー時のみバーを表示します。always
223
- は内容が溢れる限りバーを常に表示します。
224
- </CardDescription>
225
- </CardHeader>
226
- <CardContent>
227
- <Card variant="outline" className="h-56 w-full">
228
- <CardContent flush>
229
- <ScrollArea type="always">
230
- <CardContent>
231
- <Flex direction="col" gap="xs">
232
- {entries.map((e) => (
233
- <div key={e} className="text-sm tabular-nums">
234
- {e}
235
- </div>
236
- ))}
237
- </Flex>
238
- </CardContent>
239
- </ScrollArea>
240
- </CardContent>
241
- </Card>
242
- </CardContent>
243
- </Card>
244
-
245
217
  <Card>
246
218
  <CardHeader>
247
219
  <CardTitle level={2}>ScrollArea</CardTitle>
248
220
  <CardDescription>orientation="horizontal"</CardDescription>
249
221
  </CardHeader>
250
222
  <CardContent>
251
- <ScrollArea orientation="horizontal" type="always">
223
+ <ScrollArea orientation="horizontal">
252
224
  <ResponsiveGrid flow="columns">
253
225
  {columns.map((column) => (
254
226
  <Card key={column}>
@@ -264,17 +236,51 @@ export default function Demo() {
264
236
 
265
237
  <Card>
266
238
  <CardHeader>
267
- <CardTitle level={2}>個別の ScrollBar を追加する</CardTitle>
239
+ <CardTitle level={2}>ScrollBar は何も描画しない(非推奨)</CardTitle>
240
+ <CardDescription>
241
+ v22 まではこの要素を置くことが軸を開く方法だったが、スクロールがブラウザ本来のものに
242
+ なったので ScrollBar は何も描画しない。下のカードには ScrollBar
243
+ が置いてあるが、横に動くのは orientation=&quot;both&quot; のおかげで、ScrollBar
244
+ のおかげではない。残っている &lt;ScrollBar&gt; は消して、軸は orientation で宣言する。
245
+ </CardDescription>
246
+ </CardHeader>
247
+ <CardContent>
248
+ <Card variant="outline" className="h-24 w-full">
249
+ <CardContent flush>
250
+ <ScrollArea orientation="both">
251
+ <ResponsiveGrid flow="columns">
252
+ {columns.map((column) => (
253
+ <Text key={column}>{column}</Text>
254
+ ))}
255
+ </ResponsiveGrid>
256
+ <ScrollBar orientation="horizontal" />
257
+ </ScrollArea>
258
+ </CardContent>
259
+ </Card>
260
+ </CardContent>
261
+ </Card>
262
+
263
+ <Card>
264
+ <CardHeader>
265
+ <CardTitle level={2}>両方向スクロール(orientation=&quot;both&quot;)</CardTitle>
266
+ <CardDescription>
267
+ 縦にも横にも溢れうる面は orientation=&quot;both&quot;。以前は縦の ScrollArea に
268
+ ScrollBar を足して横軸を開けていたが、スクロールはブラウザ本来のものになったので、
269
+ 軸を決めるのは orientation だけになった。
270
+ </CardDescription>
268
271
  </CardHeader>
269
272
  <CardContent>
270
- <ScrollArea type="always">
271
- <ResponsiveGrid flow="columns">
272
- {columns.map((column) => (
273
- <Text key={column}>{column}</Text>
274
- ))}
275
- </ResponsiveGrid>
276
- <ScrollBar orientation="horizontal" />
277
- </ScrollArea>
273
+ <Card variant="outline" className="h-32 w-full">
274
+ <CardContent flush>
275
+ <ScrollArea orientation="both">
276
+ <ResponsiveGrid flow="columns">
277
+ {columns.map((column) => (
278
+ <Text key={column}>{column}</Text>
279
+ ))}
280
+ </ResponsiveGrid>
281
+ </ScrollArea>
282
+ </CardContent>
283
+ </Card>
278
284
  </CardContent>
279
285
  </Card>
280
286
 
@@ -288,7 +294,7 @@ export default function Demo() {
288
294
  <CardContent>
289
295
  <Card variant="outline" className="h-56 w-full">
290
296
  <CardContent flush>
291
- <ScrollArea type="always">
297
+ <ScrollArea>
292
298
  <CardContent>
293
299
  <Flex direction="col" gap="xs">
294
300
  {shortEntries.map((e) => (
@@ -181,7 +181,7 @@ export default function Demo() {
181
181
  full frame. The suppression above is scoped to a flush body, never to `bordered`.
182
182
  </CardDescription>
183
183
  </CardHeader>
184
- <CardContent>
184
+ <CardContent flush>
185
185
  <Table bordered>
186
186
  <TableHeader>
187
187
  <TableRow>
@@ -41,8 +41,7 @@ export default function Demo() {
41
41
  形式に整形され、 解釈できない入力は元の値へ復帰する。
42
42
  </CardDescription>
43
43
  </CardHeader>
44
- {/* data-axe-open: check-frame-axe opens the calendar before scanning. */}
45
- <CardContent data-axe-open>
44
+ <CardContent>
46
45
  <FormField id="issue-date" label="発行日" required>
47
46
  <DatePicker
48
47
  id="issue-date"
@@ -288,7 +287,7 @@ export default function Demo() {
288
287
  ISO 送信される。 開始 &gt; 終了で入力しても order (既定 true) が昇順に正規化する。
289
288
  </CardDescription>
290
289
  </CardHeader>
291
- <CardContent data-axe-open>
290
+ <CardContent>
292
291
  <FormField id="period" label="会計期間" required>
293
292
  <DatePicker
294
293
  range
@@ -38,8 +38,11 @@ export default function Demo() {
38
38
  <CardDescription>6スロット 1グループ · ペースト・矢印キー操作に対応。</CardDescription>
39
39
  </CardHeader>
40
40
  <CardContent>
41
- <FormField id="email-otp" label="認証コード" required>
42
- <InputOTP maxLength={6} value={emailCode} onChange={setEmailCode}>
41
+ {/* この行の id は FIELD ではなく CONTROL 側にある。FormField はその id をそのまま
42
+ 使い、ラベルのクリックもその id を見る(gh#477:以前は FormField 自身の id を
43
+ 見ていたので、両者が食い違った瞬間にラベルがどこも指さなくなった)。 */}
44
+ <FormField label="認証コード" required>
45
+ <InputOTP id="email-otp" maxLength={6} value={emailCode} onChange={setEmailCode}>
43
46
  <InputOTPGroup>
44
47
  <InputOTPSlot index={0} />
45
48
  <InputOTPSlot index={1} />
@@ -0,0 +1,77 @@
1
+ import {
2
+ Badge,
3
+ Card,
4
+ CardContent,
5
+ CardDescription,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from "@godxjp/ui/data-display";
9
+ import { Segmented } from "@godxjp/ui/data-entry";
10
+ import { Button, Text } from "@godxjp/ui/general";
11
+ import { Flex, PageContainer } from "@godxjp/ui/layout";
12
+
13
+ /**
14
+ * The composition a consumer actually filters a list with: a Segmented that carries a count badge
15
+ * per option, sharing a WRAPPING flex row with the other filter controls, inside a Card.
16
+ *
17
+ * It is a separate frame from `segmented.tsx` because the row is the variable. `#four-with-counts`
18
+ * over there puts the track directly in a CardContent, where it is a block box and shrinks to the
19
+ * column; here it is a FLEX ITEM, and a flex item's `min-inline-size` defaults to `auto` — its
20
+ * max-content width — so nothing makes it give ground to the row.
21
+ */
22
+ const OPTIONS = [
23
+ { value: "all", label: "すべて", count: 133 },
24
+ { value: "ginou", label: "技能実習", count: 82 },
25
+ { value: "tokutei", label: "特定技能", count: 51 },
26
+ { value: "ikusei", label: "育成就労", count: 0 },
27
+ ].map(({ value, label, count }) => ({
28
+ value,
29
+ label: (
30
+ <>
31
+ {label}
32
+ <Badge as="span" variant="secondary">
33
+ {count}
34
+ </Badge>
35
+ </>
36
+ ),
37
+ }));
38
+
39
+ export default function SegmentedInFilterRow() {
40
+ return (
41
+ <PageContainer title="Segmented in a filter row" subtitle="4 択 + 件数バッジ · Flex wrap の中">
42
+ <Card id="four-in-wrap-row">
43
+ <CardHeader>
44
+ <CardTitle level={2}>制度の絞り込み</CardTitle>
45
+ <CardDescription>
46
+ `check:segmented-wrap` がこのカードも測ります。トラックは行の中でも縮み、393px
47
+ でラベルも件数も切り詰めません。
48
+ </CardDescription>
49
+ </CardHeader>
50
+ <CardContent>
51
+ <Flex gap="sm" align="center" wrap>
52
+ <Segmented aria-label="制度" defaultValue="all" options={OPTIONS} />
53
+ <Button variant="outline" size="sm">
54
+ 絞り込みを解除
55
+ </Button>
56
+ </Flex>
57
+ </CardContent>
58
+ </Card>
59
+
60
+ <Card id="four-in-wrap-row-alone">
61
+ <CardHeader>
62
+ <CardTitle level={2}>同じトラック、行の中に一つだけ</CardTitle>
63
+ <CardDescription>
64
+ <Text size="sm" tone="muted">
65
+ 兄弟が無くても行は行。フレックス項目としての縮み方を切り分けるための対照。
66
+ </Text>
67
+ </CardDescription>
68
+ </CardHeader>
69
+ <CardContent>
70
+ <Flex gap="sm" align="center" wrap>
71
+ <Segmented aria-label="制度(単独)" defaultValue="all" options={OPTIONS} />
72
+ </Flex>
73
+ </CardContent>
74
+ </Card>
75
+ </PageContainer>
76
+ );
77
+ }
@@ -1,6 +1,13 @@
1
1
  import { useState } from "react";
2
2
 
3
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
3
+ import {
4
+ Badge,
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@godxjp/ui/data-display";
4
11
  import { FormField, Segmented } from "@godxjp/ui/data-entry";
5
12
  import { Text, VisuallyHidden } from "@godxjp/ui/general";
6
13
  import { Flex, PageContainer } from "@godxjp/ui/layout";
@@ -84,6 +91,38 @@ export default function Demo() {
84
91
  </CardContent>
85
92
  </Card>
86
93
 
94
+ <Card id="four-with-counts">
95
+ <CardHeader>
96
+ <CardTitle level={2}>4 択 + 件数(スマホ幅では折り返す)</CardTitle>
97
+ <CardDescription>
98
+ 一覧の上の状態フィルター。4 つが 1 行に収まらない幅では、トラックが 2 行目に折り返し
99
+ ます — ラベルも件数も切り詰めません。`check:segmented-wrap` がこのカードを測ります。
100
+ </CardDescription>
101
+ </CardHeader>
102
+ <CardContent>
103
+ <Segmented
104
+ aria-label="状態"
105
+ defaultValue="all"
106
+ options={[
107
+ { value: "all", label: "すべて", count: 128 },
108
+ { value: "active", label: "実習中", count: 96 },
109
+ { value: "pending", label: "申請中", count: 12 },
110
+ { value: "gone", label: "失踪・帰国", count: 0 },
111
+ ].map(({ value, label, count }) => ({
112
+ value,
113
+ label: (
114
+ <>
115
+ {label}
116
+ <Badge as="span" variant="secondary">
117
+ {count}
118
+ </Badge>
119
+ </>
120
+ ),
121
+ }))}
122
+ />
123
+ </CardContent>
124
+ </Card>
125
+
87
126
  <Card>
88
127
  <CardHeader>
89
128
  <CardTitle level={2}>size · vertical · block</CardTitle>
@@ -142,8 +142,7 @@ export default function Demo() {
142
142
  references. The internal search textbox has its own visible-purpose accessible name.
143
143
  </CardDescription>
144
144
  </CardHeader>
145
- {/* data-axe-open: check-frame-axe opens the searchable panel before scanning. */}
146
- <CardContent data-axe-open>
145
+ <CardContent>
147
146
  <FormField
148
147
  id="searchable-invalid"
149
148
  label="担当拠点"