@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.
- package/dist/components/data-display/badge.d.ts +10 -3
- package/dist/components/data-display/badge.js +14 -3
- package/dist/components/data-display/data-table.d.ts +20 -12
- package/dist/components/data-display/data-table.js +53 -5
- package/dist/components/data-display/range-timeline.d.ts +16 -0
- package/dist/components/data-display/range-timeline.js +115 -80
- package/dist/components/data-display/table.d.ts +16 -0
- package/dist/components/data-display/table.js +2 -0
- package/dist/components/data-entry/checkbox.js +23 -5
- package/dist/components/data-entry/command.d.ts +3 -1
- package/dist/components/data-entry/command.js +10 -2
- package/dist/components/data-entry/form-errors.d.ts +5 -0
- package/dist/components/data-entry/form-errors.js +6 -0
- package/dist/components/data-entry/form-field.js +9 -8
- package/dist/components/data-entry/index.d.ts +1 -0
- package/dist/components/data-entry/search-select.js +145 -136
- package/dist/components/data-entry/select.js +38 -3
- package/dist/components/feedback/dialog.d.ts +4 -3
- package/dist/components/feedback/dialog.js +46 -19
- package/dist/components/feedback/non-modal-layer.d.ts +16 -0
- package/dist/components/feedback/non-modal-layer.js +37 -0
- package/dist/components/feedback/sheet.d.ts +7 -4
- package/dist/components/feedback/sheet.js +63 -40
- package/dist/components/layout/org-switcher.js +1 -1
- package/dist/components/layout/responsive-grid.d.ts +8 -2
- package/dist/components/layout/responsive-grid.js +2 -0
- package/dist/components/query/mutation-feedback.js +2 -4
- package/dist/contracts/measurement.json +1 -1
- package/dist/form/form-field-control.d.ts +1 -1
- package/dist/form/form-field-control.js +32 -23
- package/dist/form/form-root.d.ts +1 -1
- package/dist/form/form-root.js +25 -6
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/components/data-display.prop.d.ts +12 -0
- package/dist/props/components/data-entry.prop.d.ts +10 -0
- package/dist/props/components/feedback.prop.d.ts +7 -1
- package/dist/props/components/form.prop.d.ts +52 -3
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +7 -0
- package/dist/props/registry.d.ts +26 -3
- package/dist/props/registry.js +25 -2
- package/dist/props/vocabulary/data.prop.d.ts +27 -1
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +44 -1
- package/dist/styles/data-display-layout.css +58 -1
- package/dist/styles/form-layout.css +21 -0
- package/dist/styles/layout.css +7 -0
- package/dist/styles/navigation-layout.css +5 -1
- package/dist/styles/shell-layout.css +1 -14
- package/dist/styles/table-layout.css +80 -8
- package/dist/tokens/components/control.css +5 -0
- package/dist/tokens/components/data-display.css +6 -0
- package/dist/tokens/components/navigation.css +1 -1
- package/dist/tokens/components/table.css +2 -0
- package/docs/DESIGN-AUTHORITY.md +32 -1
- package/docs/FORMS.md +11 -10
- package/docs/data-display/data-table/examples/server-paged.tsx +156 -0
- package/docs/data-display/data-table/examples/striped-list.tsx +173 -0
- package/docs/data-display/popover.tsx +43 -1
- package/docs/data-display/timeline.tsx +73 -9
- package/docs/data-entry/command.tsx +80 -1
- package/docs/data-entry/form-root.tsx +12 -2
- package/docs/feedback/dialog.tsx +82 -0
- package/docs/feedback/sheet.tsx +84 -0
- package/docs/layout/responsive-grid.tsx +100 -0
- package/package.json +4 -2
|
@@ -40,7 +40,47 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.ui-table-row[data-tone] {
|
|
43
|
-
background:
|
|
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(--
|
|
352
|
-
var(--table-row-hover-background, hsl(var(--
|
|
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:
|
|
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
|
}
|
package/docs/DESIGN-AUTHORITY.md
CHANGED
|
@@ -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
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
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 {
|
|
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>
|