@godxjp/ui 27.0.0 → 27.3.1

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 (68) hide show
  1. package/dist/components/data-display/badge.d.ts +10 -3
  2. package/dist/components/data-display/badge.js +14 -3
  3. package/dist/components/data-display/data-table.d.ts +20 -12
  4. package/dist/components/data-display/data-table.js +53 -5
  5. package/dist/components/data-display/range-timeline.d.ts +16 -0
  6. package/dist/components/data-display/range-timeline.js +115 -80
  7. package/dist/components/data-display/table.d.ts +16 -0
  8. package/dist/components/data-display/table.js +2 -0
  9. package/dist/components/data-entry/checkbox.js +23 -5
  10. package/dist/components/data-entry/command.d.ts +3 -1
  11. package/dist/components/data-entry/command.js +10 -2
  12. package/dist/components/data-entry/form-errors.d.ts +5 -0
  13. package/dist/components/data-entry/form-errors.js +6 -0
  14. package/dist/components/data-entry/form-field.js +9 -8
  15. package/dist/components/data-entry/index.d.ts +1 -0
  16. package/dist/components/data-entry/search-select.js +145 -136
  17. package/dist/components/data-entry/select.js +38 -3
  18. package/dist/components/feedback/dialog.d.ts +4 -3
  19. package/dist/components/feedback/dialog.js +46 -19
  20. package/dist/components/feedback/non-modal-layer.d.ts +16 -0
  21. package/dist/components/feedback/non-modal-layer.js +37 -0
  22. package/dist/components/feedback/sheet.d.ts +7 -4
  23. package/dist/components/feedback/sheet.js +63 -40
  24. package/dist/components/layout/org-switcher.js +1 -1
  25. package/dist/components/layout/responsive-grid.d.ts +8 -2
  26. package/dist/components/layout/responsive-grid.js +2 -0
  27. package/dist/components/query/mutation-feedback.js +2 -4
  28. package/dist/contracts/measurement.json +1 -1
  29. package/dist/form/form-field-control.d.ts +1 -1
  30. package/dist/form/form-field-control.js +32 -23
  31. package/dist/form/form-root.d.ts +1 -1
  32. package/dist/form/form-root.js +25 -6
  33. package/dist/i18n/messages/en.json +2 -1
  34. package/dist/i18n/messages/ja.json +2 -1
  35. package/dist/i18n/messages/vi.json +2 -1
  36. package/dist/props/components/data-display.prop.d.ts +12 -0
  37. package/dist/props/components/data-entry.prop.d.ts +10 -0
  38. package/dist/props/components/feedback.prop.d.ts +7 -1
  39. package/dist/props/components/form.prop.d.ts +52 -3
  40. package/dist/props/components/index.d.ts +1 -1
  41. package/dist/props/components/layout.prop.d.ts +7 -0
  42. package/dist/props/registry.d.ts +26 -3
  43. package/dist/props/registry.js +25 -2
  44. package/dist/props/vocabulary/data.prop.d.ts +27 -1
  45. package/dist/props/vocabulary/index.d.ts +1 -1
  46. package/dist/styles/control.css +44 -1
  47. package/dist/styles/data-display-layout.css +58 -1
  48. package/dist/styles/form-layout.css +21 -0
  49. package/dist/styles/layout.css +7 -0
  50. package/dist/styles/navigation-layout.css +5 -1
  51. package/dist/styles/shell-layout.css +1 -14
  52. package/dist/styles/table-layout.css +80 -8
  53. package/dist/tokens/components/control.css +5 -0
  54. package/dist/tokens/components/data-display.css +6 -0
  55. package/dist/tokens/components/navigation.css +1 -1
  56. package/dist/tokens/components/table.css +2 -0
  57. package/docs/DESIGN-AUTHORITY.md +32 -1
  58. package/docs/FORMS.md +11 -10
  59. package/docs/data-display/data-table/examples/server-paged.tsx +156 -0
  60. package/docs/data-display/data-table/examples/striped-list.tsx +173 -0
  61. package/docs/data-display/popover.tsx +43 -1
  62. package/docs/data-display/timeline.tsx +73 -9
  63. package/docs/data-entry/command.tsx +80 -1
  64. package/docs/data-entry/form-root.tsx +12 -2
  65. package/docs/feedback/dialog.tsx +82 -0
  66. package/docs/feedback/sheet.tsx +84 -0
  67. package/docs/layout/responsive-grid.tsx +100 -0
  68. package/package.json +4 -2
@@ -40,7 +40,47 @@
40
40
  }
41
41
 
42
42
  .ui-table-row[data-tone] {
43
- background: hsl(var(--table-row-tone-color) / var(--table-row-tone-wash-alpha));
43
+ background-image: linear-gradient(
44
+ hsl(var(--table-row-tone-color) / var(--table-row-tone-wash-alpha)),
45
+ hsl(var(--table-row-tone-color) / var(--table-row-tone-wash-alpha))
46
+ );
47
+ }
48
+
49
+ :where([data-slot="table"] > tbody > tr:nth-child(even of :not([data-expanded-row]))) {
50
+ background-color: color-mix(
51
+ in srgb,
52
+ var(--table-row-striped-background, hsl(var(--muted) / 0.8)) var(--table-row-striped-alpha),
53
+ transparent
54
+ );
55
+ }
56
+
57
+ :where(
58
+ [data-slot="table"]
59
+ > tbody
60
+ > tr:nth-child(even of :not([data-expanded-row]))
61
+ + tr[data-expanded-row]
62
+ > td
63
+ ) {
64
+ background-image: linear-gradient(
65
+ color-mix(
66
+ in srgb,
67
+ var(--table-row-striped-background, hsl(var(--muted) / 0.8)) var(--table-row-striped-alpha),
68
+ transparent
69
+ ),
70
+ color-mix(
71
+ in srgb,
72
+ var(--table-row-striped-background, hsl(var(--muted) / 0.8)) var(--table-row-striped-alpha),
73
+ transparent
74
+ )
75
+ );
76
+ }
77
+
78
+ [data-slot="table"][data-striped=""] {
79
+ --table-row-striped-alpha: 100%;
80
+ }
81
+
82
+ [data-slot="table"][data-striped="false"] {
83
+ --table-row-striped-alpha: 0%;
44
84
  }
45
85
 
46
86
  .ui-table-row[data-tone] > [data-slot="table-cell"]:first-child {
@@ -172,6 +212,14 @@
172
212
  min-width: 0;
173
213
  }
174
214
 
215
+ .ui-data-table-pagination--footer > .ui-pagination {
216
+ flex: 1 1 auto;
217
+ }
218
+
219
+ .ui-data-table-pagination--footer:empty {
220
+ display: none;
221
+ }
222
+
175
223
  .ui-data-table-page-size {
176
224
  flex: 0 0 auto;
177
225
  flex-wrap: nowrap;
@@ -213,10 +261,6 @@
213
261
  min-inline-size: var(--table-surface-min-inline-size);
214
262
  }
215
263
 
216
- .ui-data-table-surface[data-striped] tbody tr:nth-child(even):not([data-state="selected"]) {
217
- background-color: var(--table-row-striped-background, hsl(var(--muted) / 0.4));
218
- }
219
-
220
264
  .ui-data-table-empty {
221
265
  padding: var(--empty-state-space-y) var(--empty-state-space-x);
222
266
  }
@@ -332,6 +376,13 @@
332
376
  box-shadow: var(--table-fixed-shadow-start);
333
377
  }
334
378
 
379
+ [data-slot="card"]
380
+ .ui-data-table-surface
381
+ tbody
382
+ :is(.ui-data-table-pin-end, .ui-data-table-pin-start) {
383
+ background-color: hsl(var(--card-background, var(--card)));
384
+ }
385
+
335
386
  th.ui-data-table-pin-end,
336
387
  th.ui-data-table-pin-start {
337
388
  background-color: hsl(var(--secondary));
@@ -345,11 +396,27 @@
345
396
  box-shadow: 6px 0 6px -5px hsl(var(--foreground) / 0.12);
346
397
  }
347
398
 
399
+ :where([data-slot="table"] > tbody > tr:nth-child(even of :not([data-expanded-row])))
400
+ > :is(.ui-data-table-pin-end, .ui-data-table-pin-start) {
401
+ background-image: linear-gradient(
402
+ color-mix(
403
+ in srgb,
404
+ var(--table-row-striped-background, hsl(var(--muted) / 0.8)) var(--table-row-striped-alpha),
405
+ transparent
406
+ ),
407
+ color-mix(
408
+ in srgb,
409
+ var(--table-row-striped-background, hsl(var(--muted) / 0.8)) var(--table-row-striped-alpha),
410
+ transparent
411
+ )
412
+ );
413
+ }
414
+
348
415
  .ui-data-table-surface tbody tr:hover > .ui-data-table-pin-end,
349
416
  .ui-data-table-surface tbody tr:hover > .ui-data-table-pin-start {
350
417
  background-image: linear-gradient(
351
- var(--table-row-hover-background, hsl(var(--muted) / 0.5)),
352
- var(--table-row-hover-background, hsl(var(--muted) / 0.5))
418
+ var(--table-row-hover-background, hsl(var(--accent) / 0.7)),
419
+ var(--table-row-hover-background, hsl(var(--accent) / 0.7))
353
420
  );
354
421
  }
355
422
 
@@ -385,7 +452,12 @@
385
452
  }
386
453
 
387
454
  .ui-data-table-expanded-row > [data-slot="table-cell"] {
388
- background-color: var(--table-row-expanded-background, hsl(var(--muted) / 0.3));
455
+ background-color: color-mix(
456
+ in srgb,
457
+ var(--table-row-expanded-background, hsl(var(--muted) / 0.3))
458
+ calc(100% - var(--table-row-striped-alpha)),
459
+ transparent
460
+ );
389
461
  }
390
462
 
391
463
  .ui-data-table-summary {
@@ -140,6 +140,11 @@
140
140
  --command-item-padding-x: var(--space-2);
141
141
 
142
142
  --command-item-gap: var(--control-gap);
143
+
144
+ --command-list-split-padding: 0;
145
+ --command-list-split-inset: 0px;
146
+ --command-item-divider-width: var(--stroke-hairline);
147
+ --command-item-divider-color: initial;
143
148
  --command-palette-width: 35rem;
144
149
  --command-palette-inset-top: calc(var(--space-6) * 4);
145
150
  --command-palette-viewport-inset: var(--space-3);
@@ -151,4 +151,10 @@
151
151
  --range-timeline-row-height: var(--control-height-lg);
152
152
  --range-timeline-bar-height: var(--control-height-sm);
153
153
  --range-timeline-border-color: var(--border);
154
+
155
+ --range-timeline-grid-color: initial;
156
+
157
+ --range-timeline-grid-width: var(--stroke-hairline);
158
+
159
+ --range-timeline-muted-column-background: initial;
154
160
  }
@@ -11,7 +11,7 @@
11
11
 
12
12
  --pagination-icon-size: var(--control-icon-size);
13
13
 
14
- --pagination-control-height-sm: var(--band-height-sm);
14
+ --pagination-control-height-sm: initial;
15
15
 
16
16
  --pagination-jumper-gap: var(--space-inline-xs);
17
17
  --pagination-jumper-width: 4rem;
@@ -37,6 +37,8 @@
37
37
  --table-selection-menu-gap: var(--space-1);
38
38
 
39
39
  --table-row-striped-background: initial;
40
+
41
+ --table-row-striped-alpha: 0%;
40
42
  --table-row-hover-background: initial;
41
43
  --table-row-selected-background: initial;
42
44
  }
@@ -234,7 +234,12 @@ in page CSS.
234
234
  **Three things override antd's spelling, each for a stated reason:**
235
235
 
236
236
  - **Logical over physical.** antd's `fixed: 'left' | 'right'` cannot mirror for an RTL locale, so
237
- only `start` / `end` are published. Same rule that makes `check:rtl` a gate.
237
+ only `start` / `end` are published. Same rule that makes `check:rtl` a gate. Table
238
+ `pagination.position` follows it (gh#705): antd `topLeft | topCenter | topRight | bottomLeft |
239
+ bottomCenter | bottomRight | none` is `topStart | topCenter | topEnd | bottomStart | bottomCenter |
240
+ bottomEnd | none` (`TablePaginationPositionProp`, default `['bottomEnd']`), the camelCase shape
241
+ `DropdownMenuPlacementProp` already uses. antd's `size="small"` Table → small pager is
242
+ `density="compact"` → `Pagination size="sm"`.
238
243
  - **This library's controlled vocabulary wins on values.** antd's `SortOrder` is
239
244
  `'ascend' | 'descend'`; here it stays `SortDirectionProp` (`asc` / `desc`), because that type
240
245
  already exists and a second spelling of the same axis is exactly what `check:prop-vocabulary`
@@ -266,6 +271,32 @@ in page CSS.
266
271
  `dataEntry.chatComposer.hintModEnter` with `{modifier}` from `isApplePlatform()`
267
272
  (`@godxjp/ui/lib/utils`). `allowEmptySubmit` (default `false`, antd's behaviour) sends `""`,
268
273
  still never while `loading` / `disabled` / `readOnly`.
274
+ - **`Table.striped` / `DataTable.striped` (gh#700).** antd names no zebra capability — its docs
275
+ stripe through `rowClassName` — so there is no antd spelling to port. Element Plus calls it
276
+ `stripe`, Bootstrap `.table-striped`, MUI documents `:nth-of-type(odd)`. This library takes
277
+ `striped`: a positive boolean adjective, the shape `bordered` and `hoverable` already have on the
278
+ same component, and the spelling `DataTable` had shipped since 17f4a067. It is TRI-STATE on
279
+ purpose: omitted inherits the service default `--table-row-striped-alpha` (`0%` at `:root`, so
280
+ one theme line — `:root { --table-row-striped-alpha: 100%; }` — stripes every list table),
281
+ `true` / `false` override it per table. `rowClassName` is not the answer here: it counts DOM
282
+ rows, so an expanded detail row shifts every stripe after it, and `ui-audit` blocks the
283
+ utilities it would need.
284
+
285
+ - **`Command.split` is antd `List.split`, with this library's default.** Same name, same meaning:
286
+ a divider between rows and none after the last (gh#699). antd's `List` defaults it to `true`;
287
+ `Command` defaults it to `false`, because its resting home is a command palette, where inset
288
+ pill rows are right. The option lists the library renders itself as a checklist
289
+ (`Select mode="multiple" | "tags"`) set it to `true`, and a consumer composing a filter facet
290
+ (`Popover` + `Command`) opts in with `split`. It also zeroes the list padding and bleeds rows to
291
+ the panel edge, which antd's `split` does not need to say because a `List` has no inset rows.
292
+
293
+ - **`ResponsiveGrid.align` is antd `Row align`, with Flex's values (gh#708).** antd spells the
294
+ cross-axis `top | middle | bottom | stretch`; this library already names that axis
295
+ `FlexAlignProp` (`start | center | end | stretch | baseline`), so `ResponsiveGridAlignProp` is
296
+ `Extract<FlexAlignProp, "start" | "stretch">` — antd `top` → `start`, `stretch` unchanged.
297
+ `middle` / `bottom` are not offered until a screen needs them. The default differs by flow on
298
+ purpose: omitted, `flow="columns"` keeps `start` and `flow="rows"` keeps the grid's stretch, so
299
+ no existing grid moves; antd's `Row` default is `top` in every case.
269
300
 
270
301
  **A knob that only a fork could reach is not parity either.** antd's `components`,
271
302
  `filterDropdown`, `classNames`/`styles` semantic maps and `prefixCls` all exist to let a consumer
package/docs/FORMS.md CHANGED
@@ -59,16 +59,17 @@ export function CreateCustomerForm({ onSubmit }: { onSubmit: (v: Values) => void
59
59
 
60
60
  `onSubmit` runs only after validation passes (antd `onFinish`). Everything else is optional:
61
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 |
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
+ | `submitFailedMessage` | — | Rejection banner text; `false` never shows it. Skipped for a 400/422 once `errors` holds a message |
67
+ | `onReset` | — | A native `<Button type="reset">` restores `defaultValues`; this runs after |
68
+ | `scrollToFirstError` | `scrollToFirstError` | Default `true`; centres the field row, honours `prefers-reduced-motion` |
69
+ | `disabled` | Form `disabled` | Disables fields and submission; values remain in state. Buttons read `useFormDisabled()` |
70
+ | `layout` `labelWidth` `controlWidth` `labelAlign` `collapseBelow` | `layout` `labelCol` … | Renders the `Form` layout shell (see the spacing note below) |
71
+ | `density` / `requiredMark` | `size` / `requiredMark` | Forwarded to the same shell |
72
+ | `errors` | — | Server error bag; fields claim their keys, `<FormErrors />` shows the rest |
72
73
 
73
74
  > **Spacing.** A bare `FormRoot` stacks its children with `ui-stack-md`. Passing ANY layout prop
74
75
  > switches it to the `Form` shell, whose `--form-block-gap` owns the spacing instead — the two are
@@ -0,0 +1,156 @@
1
+ import { useMemo, useState } from "react";
2
+
3
+ import {
4
+ Avatar,
5
+ AvatarFallback,
6
+ Badge,
7
+ Card,
8
+ CardContent,
9
+ DataTable,
10
+ type ColumnDef,
11
+ } from "@godxjp/ui/data-display";
12
+ import { Button } from "@godxjp/ui/general";
13
+ import {
14
+ AppShell,
15
+ Flex,
16
+ PageContainer,
17
+ Sidebar,
18
+ type SidebarSectionProp,
19
+ Topbar,
20
+ } from "@godxjp/ui/layout";
21
+ import { Download, FileText, LayoutDashboard, Plus, ReceiptText } from "lucide-react";
22
+
23
+ /**
24
+ * Server-paged list (gh#705) — antd's `Table pagination`: the server returns ONE page plus the
25
+ * total, and the table renders its own footer from `pagination={{ total, current, pageSize,
26
+ * showTotal, onChange }}` — the real Pagination, total beside the page numbers, bottom-end. The
27
+ * table is compact, so the pager is `size="sm"` and sits on the same control step as the toolbar
28
+ * buttons above it. `data` is only the current page; the footer offers ceil(total / pageSize).
29
+ */
30
+ const sections: SidebarSectionProp[] = [
31
+ {
32
+ label: "会計",
33
+ items: [
34
+ { id: "dashboard", label: "ダッシュボード", icon: LayoutDashboard },
35
+ { id: "journal", label: "仕訳", icon: FileText },
36
+ { id: "invoices", label: "請求書", icon: ReceiptText },
37
+ ],
38
+ },
39
+ ];
40
+
41
+ type Invoice = {
42
+ id: string;
43
+ partner: string;
44
+ amount: number;
45
+ status: "active" | "pending" | "draft" | "failed";
46
+ };
47
+
48
+ const partners = ["株式会社ベトヤ", "ハノイ物流", "GMO決済", "東京ロジ", "大阪商事"];
49
+ const statuses: Invoice["status"][] = ["active", "pending", "draft", "failed"];
50
+
51
+ // Stands in for the server: 137 rows exist, but only one page ever reaches the table.
52
+ const SERVER_TOTAL = 137;
53
+ function fetchPage(page: number, pageSize: number): Invoice[] {
54
+ const first = (page - 1) * pageSize;
55
+ const count = Math.max(0, Math.min(pageSize, SERVER_TOTAL - first));
56
+ return Array.from({ length: count }, (_, i) => {
57
+ const n = first + i + 1;
58
+ return {
59
+ id: `INV-2024-${String(n).padStart(4, "0")}`,
60
+ partner: partners[n % partners.length],
61
+ amount: 12000 + ((n * 7919) % 480000),
62
+ status: statuses[n % statuses.length],
63
+ };
64
+ });
65
+ }
66
+
67
+ const yen = new Intl.NumberFormat("ja-JP", {
68
+ style: "currency",
69
+ currency: "JPY",
70
+ maximumFractionDigits: 0,
71
+ });
72
+
73
+ const columns: ColumnDef<Invoice>[] = [
74
+ { key: "id", header: "請求書番号", width: "w-40" },
75
+ { key: "partner", header: "取引先" },
76
+ {
77
+ key: "amount",
78
+ header: "金額",
79
+ align: "right",
80
+ render: (row) => <span className="tabular-nums">{yen.format(row.amount)}</span>,
81
+ },
82
+ { key: "status", header: "状態", render: (row) => <Badge status={row.status} /> },
83
+ ];
84
+
85
+ export default function Demo() {
86
+ const [page, setPage] = useState(1);
87
+ const [pageSize, setPageSize] = useState(10);
88
+ const rows = useMemo(() => fetchPage(page, pageSize), [page, pageSize]);
89
+
90
+ return (
91
+ <AppShell
92
+ sidebar={
93
+ <Sidebar
94
+ activeId="invoices"
95
+ sections={sections}
96
+ onSelect={() => {}}
97
+ product={{ name: "CoreBooks", role: "管理コンソール", color: "hsl(var(--primary))" }}
98
+ />
99
+ }
100
+ topbar={
101
+ <Topbar
102
+ start={
103
+ <Avatar className="rounded-md">
104
+ <AvatarFallback className="bg-primary text-primary-foreground font-bold">
105
+ C
106
+ </AvatarFallback>
107
+ </Avatar>
108
+ }
109
+ />
110
+ }
111
+ >
112
+ <PageContainer
113
+ title="請求書 一覧"
114
+ subtitle="サーバー側でページングされた一覧"
115
+ breadcrumb={[{ label: "請求書" }]}
116
+ extra={
117
+ <Button size="sm">
118
+ <Plus />
119
+ 新規請求書
120
+ </Button>
121
+ }
122
+ >
123
+ <Card>
124
+ <CardContent flush>
125
+ <DataTable
126
+ data={rows}
127
+ columns={columns}
128
+ getRowId={(row) => row.id}
129
+ pagination={{
130
+ total: SERVER_TOTAL,
131
+ current: page,
132
+ pageSize,
133
+ pageSizeOptions: [10, 20, 50],
134
+ showTotal: (total, [from, to]) => `${from}〜${to} 件 / 全 ${total} 件`,
135
+ onChange: (nextPage, nextPageSize) => {
136
+ setPage(nextPage);
137
+ setPageSize(nextPageSize);
138
+ },
139
+ }}
140
+ >
141
+ <DataTable.Toolbar>
142
+ <Flex direction="row" align="center" gap="sm">
143
+ <Button size="sm" variant="outline">
144
+ <Download />
145
+ エクスポート
146
+ </Button>
147
+ </Flex>
148
+ <DataTable.ViewOptions />
149
+ </DataTable.Toolbar>
150
+ </DataTable>
151
+ </CardContent>
152
+ </Card>
153
+ </PageContainer>
154
+ </AppShell>
155
+ );
156
+ }
@@ -0,0 +1,173 @@
1
+ import { useState } from "react";
2
+
3
+ import {
4
+ Badge,
5
+ Card,
6
+ CardContent,
7
+ CardHeader,
8
+ CardTitle,
9
+ DataTable,
10
+ Descriptions,
11
+ type ColumnDef,
12
+ } from "@godxjp/ui/data-display";
13
+ import { Text } from "@godxjp/ui/general";
14
+ import { Flex, PageContainer } from "@godxjp/ui/layout";
15
+
16
+ /**
17
+ * DataTable `striped` (gh#700) — a wide issue list striped by LOGICAL record. On one screen: an
18
+ * expanded detail row (it wears its own record's stripe and does not shift the rows after it), a
19
+ * frozen inline-end column (its opaque cells wear the stripe too), a pre-selected row (selection
20
+ * replaces the stripe) and a toned row (the rail and wash stay visible on the stripe).
21
+ *
22
+ * To stripe EVERY list table in a service, the theme sets `--table-row-striped-alpha: 100%` on
23
+ * :root once; `striped` here is the per-table form of the same switch.
24
+ */
25
+ type Issue = {
26
+ id: string;
27
+ key: string;
28
+ subject: string;
29
+ type: "bug" | "task" | "story";
30
+ assignee: string;
31
+ status: "active" | "pending" | "draft" | "failed";
32
+ priority: "高" | "中" | "低";
33
+ due: string;
34
+ milestone: string;
35
+ };
36
+
37
+ const TYPE_LABEL = { bug: "バグ", task: "タスク", story: "ストーリー" } as const;
38
+
39
+ const ROWS: Issue[] = [
40
+ {
41
+ id: "1",
42
+ key: "OPS-128",
43
+ subject: "請求書 PDF の和暦表記が一部崩れる",
44
+ type: "bug",
45
+ assignee: "田中",
46
+ status: "active",
47
+ priority: "高",
48
+ due: "2024-04-12",
49
+ milestone: "4月リリース",
50
+ },
51
+ {
52
+ id: "2",
53
+ key: "OPS-127",
54
+ subject: "取引先マスタの一括取込に重複チェックを追加",
55
+ type: "story",
56
+ assignee: "佐藤",
57
+ status: "pending",
58
+ priority: "中",
59
+ due: "2024-04-15",
60
+ milestone: "4月リリース",
61
+ },
62
+ {
63
+ id: "3",
64
+ key: "OPS-126",
65
+ subject: "承認フローの差し戻し理由を必須にする",
66
+ type: "task",
67
+ assignee: "鈴木",
68
+ status: "draft",
69
+ priority: "中",
70
+ due: "2024-04-18",
71
+ milestone: "5月リリース",
72
+ },
73
+ {
74
+ id: "4",
75
+ key: "OPS-125",
76
+ subject: "夜間バッチが月末に二重起動する",
77
+ type: "bug",
78
+ assignee: "高橋",
79
+ status: "failed",
80
+ priority: "高",
81
+ due: "2024-04-10",
82
+ milestone: "4月リリース",
83
+ },
84
+ {
85
+ id: "5",
86
+ key: "OPS-124",
87
+ subject: "権限マトリクスに監査ログ閲覧を追加",
88
+ type: "story",
89
+ assignee: "伊藤",
90
+ status: "active",
91
+ priority: "低",
92
+ due: "2024-04-22",
93
+ milestone: "5月リリース",
94
+ },
95
+ {
96
+ id: "6",
97
+ key: "OPS-123",
98
+ subject: "CSV 出力の文字コードを選べるようにする",
99
+ type: "task",
100
+ assignee: "渡辺",
101
+ status: "pending",
102
+ priority: "低",
103
+ due: "2024-04-25",
104
+ milestone: "5月リリース",
105
+ },
106
+ ];
107
+
108
+ const columns: ColumnDef<Issue>[] = [
109
+ { key: "key", header: "キー", width: "110px" },
110
+ { key: "type", header: "種別", width: "110px", render: (row) => TYPE_LABEL[row.type] },
111
+ { key: "subject", header: "件名", width: "320px" },
112
+ { key: "assignee", header: "担当者", width: "110px" },
113
+ { key: "status", header: "状態", width: "110px", render: (row) => <Badge status={row.status} /> },
114
+ { key: "priority", header: "優先度", width: "90px" },
115
+ { key: "milestone", header: "マイルストーン", width: "150px" },
116
+ // Frozen to the inline end: its cells are opaque, so they must wear the stripe themselves.
117
+ { key: "due", header: "期日", width: "130px", fixed: "end" },
118
+ ];
119
+
120
+ export default function StripedListDemo() {
121
+ const [selected, setSelected] = useState<string[]>(["3"]);
122
+ const [expanded, setExpanded] = useState<string[]>(["2"]);
123
+
124
+ return (
125
+ <PageContainer title="課題一覧" subtitle="DataTable striped · 論理行ごとの縞模様">
126
+ <Card>
127
+ <CardHeader>
128
+ <CardTitle>未完了の課題</CardTitle>
129
+ </CardHeader>
130
+ <CardContent flush>
131
+ <DataTable
132
+ data={ROWS}
133
+ columns={columns}
134
+ getRowId={(row) => row.id}
135
+ striped
136
+ hoverable
137
+ scroll={{ x: 1240 }}
138
+ rowTone={(row) => (row.status === "failed" ? "destructive" : undefined)}
139
+ rowSelection={{
140
+ selectedRowKeys: selected,
141
+ onChange: (keys) => {
142
+ setSelected(keys);
143
+ },
144
+ }}
145
+ expandable={{
146
+ expandedRowKeys: expanded,
147
+ onExpandedRowsChange: (keys) => {
148
+ setExpanded(keys);
149
+ },
150
+ expandedRowRender: (row) => (
151
+ <Descriptions bordered columns={{ sm: 2, lg: 3 }} layout="horizontal">
152
+ <Descriptions.Item label="キー" mono>
153
+ {row.key}
154
+ </Descriptions.Item>
155
+ <Descriptions.Item label="担当者">{row.assignee}</Descriptions.Item>
156
+ <Descriptions.Item label="マイルストーン">{row.milestone}</Descriptions.Item>
157
+ </Descriptions>
158
+ ),
159
+ }}
160
+ >
161
+ <DataTable.Content />
162
+ </DataTable>
163
+ </CardContent>
164
+ </Card>
165
+
166
+ <Flex direction="col" gap="xs">
167
+ <Text size="sm" tone="muted">
168
+ 選択中: {selected.length} 件 · 展開中: {expanded.length} 件
169
+ </Text>
170
+ </Flex>
171
+ </PageContainer>
172
+ );
173
+ }
@@ -14,7 +14,17 @@ import {
14
14
  PopoverTitle,
15
15
  PopoverTrigger,
16
16
  } from "@godxjp/ui/data-display";
17
- import { Checkbox, FormField, Input, Label } from "@godxjp/ui/data-entry";
17
+ import {
18
+ Checkbox,
19
+ FormField,
20
+ Input,
21
+ Label,
22
+ Select,
23
+ SelectContent,
24
+ SelectItem,
25
+ SelectTrigger,
26
+ SelectValue,
27
+ } from "@godxjp/ui/data-entry";
18
28
  import { Button, Text } from "@godxjp/ui/general";
19
29
  import { Flex, PageContainer } from "@godxjp/ui/layout";
20
30
  import { Info, SlidersHorizontal } from "lucide-react";
@@ -50,6 +60,38 @@ export default function Demo() {
50
60
  </CardContent>
51
61
  </Card>
52
62
 
63
+ <Card>
64
+ <CardHeader>
65
+ <CardTitle level={2}>Picker in a popover · Select defaultOpen</CardTitle>
66
+ <CardDescription>
67
+ Opening the popover opens the workspace list with it. The Select waits for the popover
68
+ to finish sliding in, so the list sits below its trigger exactly as a click-open does
69
+ and the first option is never covered.
70
+ </CardDescription>
71
+ </CardHeader>
72
+ <CardContent>
73
+ <Popover>
74
+ <PopoverTrigger asChild>
75
+ <Button variant="outline" data-picker-popover="">
76
+ ワークスペースを切り替え
77
+ </Button>
78
+ </PopoverTrigger>
79
+ <PopoverContent aria-label="ワークスペース">
80
+ <Select aria-label="ワークスペース" defaultOpen defaultValue="tokyo">
81
+ <SelectTrigger aria-label="ワークスペース" data-picker-trigger="">
82
+ <SelectValue />
83
+ </SelectTrigger>
84
+ <SelectContent>
85
+ <SelectItem value="tokyo">東京本社</SelectItem>
86
+ <SelectItem value="osaka">大阪支社</SelectItem>
87
+ <SelectItem value="fukuoka">福岡拠点</SelectItem>
88
+ </SelectContent>
89
+ </Select>
90
+ </PopoverContent>
91
+ </Popover>
92
+ </CardContent>
93
+ </Card>
94
+
53
95
  <Card>
54
96
  <CardHeader>
55
97
  <CardTitle level={2}>Controlled trigger + content</CardTitle>