@godxjp/ui 31.28.4 → 31.30.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 (72) hide show
  1. package/agent/START-HERE.md +5 -5
  2. package/agent/components/AppLauncher.json +5 -0
  3. package/agent/components/Avatar.json +1 -0
  4. package/agent/components/Dialog.json +3 -1
  5. package/agent/components/Legend.json +1 -1
  6. package/agent/components/PasswordStrength.json +2 -2
  7. package/agent/components/RangeTimeline.json +28 -1
  8. package/agent/components/ResponsiveGrid.json +7 -0
  9. package/agent/components/Sidebar.json +5 -0
  10. package/agent/components/SortableList.json +78 -0
  11. package/agent/components/TopbarItem.json +1 -1
  12. package/agent/components-index.json +5 -0
  13. package/agent/components.json +131 -6
  14. package/agent/index.json +6 -6
  15. package/agent/llms.txt +6 -6
  16. package/agent/tokens.json +122 -2
  17. package/dist/components/data-display/legend.d.ts +3 -2
  18. package/dist/components/data-display/legend.js +10 -1
  19. package/dist/components/data-display/range-timeline.d.ts +93 -2
  20. package/dist/components/data-display/range-timeline.js +220 -58
  21. package/dist/components/data-entry/index.d.ts +2 -0
  22. package/dist/components/data-entry/index.js +2 -0
  23. package/dist/components/data-entry/password-strength.d.ts +15 -3
  24. package/dist/components/data-entry/password-strength.js +16 -34
  25. package/dist/components/data-entry/sortable-list.d.ts +10 -0
  26. package/dist/components/data-entry/sortable-list.js +237 -0
  27. package/dist/components/feedback/dialog.d.ts +8 -2
  28. package/dist/components/feedback/dialog.js +8 -1
  29. package/dist/components/feedback/sheet.js +1 -1
  30. package/dist/components/layout/app-launcher.d.ts +1 -1
  31. package/dist/components/layout/app-launcher.js +18 -14
  32. package/dist/components/layout/responsive-grid.d.ts +17 -2
  33. package/dist/components/layout/responsive-grid.js +17 -4
  34. package/dist/components/layout/sidebar.d.ts +1 -1
  35. package/dist/components/layout/sidebar.js +3 -0
  36. package/dist/components/layout/topbar-item.js +8 -2
  37. package/dist/components/ui/avatar.d.ts +3 -1
  38. package/dist/components/ui/avatar.js +2 -1
  39. package/dist/contracts/measurement.json +1 -1
  40. package/dist/i18n/messages/en.json +31 -1
  41. package/dist/i18n/messages/ja.json +31 -1
  42. package/dist/i18n/messages/vi.json +31 -1
  43. package/dist/props/components/data-display.prop.d.ts +18 -0
  44. package/dist/props/components/data-entry.prop.d.ts +34 -0
  45. package/dist/props/components/feedback.prop.d.ts +6 -1
  46. package/dist/props/components/layout.prop.d.ts +22 -0
  47. package/dist/props/registry.d.ts +33 -2
  48. package/dist/props/registry.js +44 -1
  49. package/dist/styles/control.css +50 -0
  50. package/dist/styles/data-display-layout.css +139 -2
  51. package/dist/styles/dialog-layout.css +14 -1
  52. package/dist/styles/layers.json +6 -1
  53. package/dist/styles/layout.css +29 -7
  54. package/dist/styles/shell-layout.css +26 -0
  55. package/dist/tokens/components/control.css +5 -0
  56. package/dist/tokens/components/data-display.css +12 -0
  57. package/dist/tokens/components/feedback.css +6 -0
  58. package/dist/tokens/components/shell.css +6 -0
  59. package/dist/tokens/semantic/layout.css +1 -4
  60. package/docs/FRAME-COVERAGE-REPORT.md +4 -3
  61. package/docs/data-display/avatar.tsx +32 -0
  62. package/docs/data-display/timeline.tsx +84 -1
  63. package/docs/data-entry/sortable-list.tsx +76 -0
  64. package/docs/feedback/dialog.tsx +37 -1
  65. package/docs/i18n/messages/en.json +62 -0
  66. package/docs/i18n/messages/ja.json +62 -0
  67. package/docs/i18n/messages/vi.json +62 -0
  68. package/docs/layout/app-launcher.tsx +17 -1
  69. package/docs/layout/responsive-grid.tsx +23 -0
  70. package/docs/layout/sidebar.tsx +24 -0
  71. package/docs/layout/topbar-item.tsx +11 -0
  72. package/package.json +2 -2
@@ -138,7 +138,10 @@
138
138
  min-inline-size: 0;
139
139
  }
140
140
 
141
- .ui-space-compact[data-orientation="horizontal"][data-full-width="true"] > [data-slot="space-compact-item"]:not(:has(.ui-control, .ui-control-multiline, .ui-tag-input)) {
141
+ .ui-space-compact[data-orientation="horizontal"][data-full-width="true"]
142
+ > [data-slot="space-compact-item"]:not(
143
+ :has(.ui-control, .ui-control-multiline, .ui-tag-input)
144
+ ) {
142
145
  flex: none;
143
146
  }
144
147
 
@@ -146,16 +149,21 @@
146
149
  flex-direction: column;
147
150
  }
148
151
 
149
- .ui-space-compact[data-orientation="vertical"][data-full-width="true"] > [data-slot="space-compact-item"] {
152
+ .ui-space-compact[data-orientation="vertical"][data-full-width="true"]
153
+ > [data-slot="space-compact-item"] {
150
154
  min-inline-size: auto;
151
155
  min-block-size: 0;
152
156
  }
153
157
 
154
- .ui-space-compact[data-orientation="horizontal"] > [data-slot="space-compact-item"] + [data-slot="space-compact-item"] {
158
+ .ui-space-compact[data-orientation="horizontal"]
159
+ > [data-slot="space-compact-item"]
160
+ + [data-slot="space-compact-item"] {
155
161
  margin-inline-start: calc(var(--control-border-width, var(--stroke-hairline)) * -1);
156
162
  }
157
163
 
158
- .ui-space-compact[data-orientation="vertical"] > [data-slot="space-compact-item"] + [data-slot="space-compact-item"] {
164
+ .ui-space-compact[data-orientation="vertical"]
165
+ > [data-slot="space-compact-item"]
166
+ + [data-slot="space-compact-item"] {
159
167
  margin-block-start: calc(var(--control-border-width, var(--stroke-hairline)) * -1);
160
168
  }
161
169
 
@@ -175,12 +183,14 @@
175
183
  z-index: 1;
176
184
  }
177
185
 
178
- .ui-space-compact[data-orientation="horizontal"] > [data-slot="space-compact-item"]:not(:first-child) {
186
+ .ui-space-compact[data-orientation="horizontal"]
187
+ > [data-slot="space-compact-item"]:not(:first-child) {
179
188
  --input-radius-start: 0;
180
189
  --control-trigger-radius-start: 0;
181
190
  }
182
191
 
183
- .ui-space-compact[data-orientation="horizontal"] > [data-slot="space-compact-item"]:not(:last-child) {
192
+ .ui-space-compact[data-orientation="horizontal"]
193
+ > [data-slot="space-compact-item"]:not(:last-child) {
184
194
  --input-radius-end: 0;
185
195
  --control-trigger-radius-end: 0;
186
196
  }
@@ -410,6 +420,10 @@
410
420
  align-items: start;
411
421
  }
412
422
 
423
+ .ui-responsive-grid[data-dense] {
424
+ grid-auto-flow: row dense;
425
+ }
426
+
413
427
  .ui-responsive-grid[data-align="start"] {
414
428
  align-items: start;
415
429
  }
@@ -926,7 +940,10 @@
926
940
 
927
941
  .ui-page-container[data-header-scale="display"] .ui-page-title,
928
942
  .ui-page-container[data-header-scale="display"] .ui-page-header .ui-page-title {
929
- font-size: var(--page-title-font-size-display);
943
+ font-size: var(
944
+ --page-title-font-size-display,
945
+ var(--font-size-4xl, calc(var(--font-size-display) / var(--font-size-display-ratio)))
946
+ );
930
947
  font-weight: var(--font-weight-semibold);
931
948
  line-height: var(--page-title-line-height-display);
932
949
  text-wrap: balance;
@@ -1966,22 +1983,27 @@
1966
1983
  .ui-master-detail[data-rail-width="wide"] {
1967
1984
  --master-detail-rail-size: var(--master-detail-rail-wide);
1968
1985
  }
1986
+
1969
1987
  .ui-responsive-grid-item {
1970
1988
  grid-column: span min(var(--responsive-grid-base, 1), var(--responsive-grid-item-base, 1));
1989
+ grid-row: span var(--responsive-grid-item-row-base, 1);
1971
1990
  }
1972
1991
  @container responsive-grid (min-width: 40rem) {
1973
1992
  .ui-responsive-grid-item {
1974
1993
  grid-column: span min(var(--responsive-grid-sm, 1), var(--responsive-grid-item-sm, 1));
1994
+ grid-row: span var(--responsive-grid-item-row-sm, 1);
1975
1995
  }
1976
1996
  }
1977
1997
  @container responsive-grid (min-width: 48rem) {
1978
1998
  .ui-responsive-grid-item {
1979
1999
  grid-column: span min(var(--responsive-grid-md, 1), var(--responsive-grid-item-md, 1));
2000
+ grid-row: span var(--responsive-grid-item-row-md, 1);
1980
2001
  }
1981
2002
  }
1982
2003
  @container responsive-grid (min-width: 64rem) {
1983
2004
  .ui-responsive-grid-item {
1984
2005
  grid-column: span min(var(--responsive-grid-lg, 1), var(--responsive-grid-item-lg, 1));
2006
+ grid-row: span var(--responsive-grid-item-row-lg, 1);
1985
2007
  }
1986
2008
  }
1987
2009
  .ui-separator[data-label-size="2xs"] .ui-separator-label {
@@ -1504,6 +1504,21 @@
1504
1504
  font-weight: var(--font-weight-medium);
1505
1505
  }
1506
1506
 
1507
+ .ui-app-launcher-footer {
1508
+ display: flex;
1509
+ flex-wrap: wrap;
1510
+ align-items: center;
1511
+ gap: var(--app-launcher-footer-gap);
1512
+ margin-block-start: var(--app-launcher-footer-space-block);
1513
+ padding-block-start: var(--app-launcher-footer-space-block);
1514
+ border-block-start: var(--stroke-hairline) solid hsl(var(--border));
1515
+ font-size: var(--app-launcher-footer-font-size);
1516
+ }
1517
+
1518
+ .ui-app-launcher-launchpad .ui-app-launcher-footer {
1519
+ justify-content: center;
1520
+ }
1521
+
1507
1522
  .ui-app-launcher-state {
1508
1523
  display: flex;
1509
1524
  min-height: var(--app-launcher-state-min-height);
@@ -1606,6 +1621,17 @@
1606
1621
  height: var(--sidebar-product-caret-icon-size);
1607
1622
  }
1608
1623
 
1624
+ .sb-action {
1625
+ display: flex;
1626
+ flex-shrink: 0;
1627
+ flex-direction: column;
1628
+ padding: var(--sidebar-action-padding);
1629
+ }
1630
+
1631
+ .sb-root[data-collapsed="true"] .sb-action {
1632
+ align-items: center;
1633
+ }
1634
+
1609
1635
  .sb-nav-scroll {
1610
1636
  flex: 1;
1611
1637
  min-height: 0;
@@ -470,6 +470,11 @@
470
470
  );
471
471
  --emoji-picker-glyph-size: var(--icon-size-lg);
472
472
  --emoji-picker-grid-max-block-size: 16rem;
473
+
474
+ --sortable-list-gap: var(--space-2);
475
+ --sortable-list-item-padding: var(--space-2);
476
+ --sortable-list-item-radius: var(--control-radius);
477
+ --sortable-list-grid-item-min-inline-size: 12rem;
473
478
  }
474
479
 
475
480
  @media (pointer: coarse) {
@@ -250,6 +250,12 @@
250
250
  :root {
251
251
  --range-timeline-label-width: var(--app-shell-sidebar-width);
252
252
 
253
+ --range-timeline-resizer-inline-size: var(--space-6);
254
+ --range-timeline-resizer-active-width: var(--stroke-md, 2px);
255
+
256
+ --range-timeline-busy-body-alpha: 0.55;
257
+ --range-timeline-busy-line-block-size: var(--stroke-md, 2px);
258
+
253
259
  --range-timeline-unit-width: var(--range-timeline-unit-width-default);
254
260
 
255
261
  --range-timeline-unit-width-compact: calc(var(--control-height-sm) * 1.5);
@@ -258,6 +264,12 @@
258
264
  --range-timeline-row-height: var(--control-height-lg);
259
265
  --range-timeline-bar-height: var(--control-height-sm);
260
266
 
267
+ --range-timeline-bar-muted-alpha: 0.45;
268
+ --range-timeline-bar-overdue-width: var(--stroke-md);
269
+
270
+ --range-timeline-plan-border-width: var(--stroke-hairline);
271
+ --range-timeline-early-marker-inline-size: var(--stroke-md);
272
+
261
273
  --range-timeline-border-color: var(--border);
262
274
 
263
275
  --range-timeline-grid-color: initial;
@@ -29,6 +29,12 @@
29
29
  --sheet-close-rest-alpha: 0.7;
30
30
  --sheet-header-close-space-inline-end: var(--space-8);
31
31
  --dialog-width-default: 32rem;
32
+
33
+ --dialog-width: initial;
34
+
35
+ --dialog-motion-duration: initial;
36
+ --sheet-enter-duration: initial;
37
+ --sheet-exit-duration: initial;
32
38
  --dialog-viewport-inset: 1.5rem;
33
39
  --dialog-overlay-background: initial;
34
40
 
@@ -139,6 +139,8 @@
139
139
 
140
140
  --sidebar-nav-item-radius: initial;
141
141
  --sidebar-nav-scroll-padding: var(--space-3) var(--space-2);
142
+
143
+ --sidebar-action-padding: var(--space-3) var(--space-2) 0;
142
144
  --sidebar-section-gap: var(--space-4);
143
145
  --sidebar-section-label-padding-x: var(--space-2);
144
146
  --sidebar-section-label-padding-bottom: var(--space-1);
@@ -240,6 +242,10 @@
240
242
  --app-launcher-group-label-color: initial;
241
243
  --app-launcher-state-min-height: 8rem;
242
244
 
245
+ --app-launcher-footer-space-block: var(--space-2);
246
+ --app-launcher-footer-gap: var(--space-3);
247
+ --app-launcher-footer-font-size: var(--font-size-sm, var(--font-size-base));
248
+
243
249
  --app-launcher-launchpad-backdrop-background: initial;
244
250
  --app-launcher-launchpad-backdrop-blur-size: var(--blur-lg);
245
251
  --app-launcher-launchpad-backdrop-alpha: 55%;
@@ -91,10 +91,7 @@
91
91
 
92
92
  --page-title-font-size-chrome: var(--heading-h3, var(--font-size-base));
93
93
 
94
- --page-title-font-size-display: var(
95
- --font-size-4xl,
96
- calc(var(--font-size-display) / var(--font-size-display-ratio))
97
- );
94
+ --page-title-font-size-display: initial;
98
95
  --page-title-line-height-display: 1.4;
99
96
 
100
97
  --page-icon-size: var(--icon-size-4xl);
@@ -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: **184**
7
- - With ≥1 frame: **160** · zero-frame: **24**
6
+ - Public components: **185**
7
+ - With ≥1 frame: **161** · zero-frame: **24**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -48,7 +48,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
48
48
  | CardBar | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
49
49
  | TimeRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
50
50
  | VisuallyHidden | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
51
- | RangeTimeline | **✗** | · | N/A | N/A | · | · | · | · | · | · |
51
+ | RangeTimeline | **✗** | · | · | N/A | · | · | · | · | · | · |
52
52
  | PageContainer | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
53
53
  | PageCover | ✓ | · | N/A | N/A | · | · | · | · | · | · |
54
54
  | Flex | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -195,6 +195,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
195
195
  | PieChart | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
196
196
  | CommandPalette | ✓ | N/A | · | · | · | · | · | · | · | · |
197
197
  | EmojiPicker | ✓ | · | · | · | · | · | · | · | · | · |
198
+ | SortableList | ✓ | N/A | · | · | · | · | · | · | · | · |
198
199
  | TwoFactorSetup | ✓ | N/A | · | · | · | · | · | · | · | · |
199
200
  | AuthDivider | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
200
201
  | AuthFooter | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -11,9 +11,11 @@ import {
11
11
  } from "@godxjp/ui/data-display";
12
12
  import { Button, Text } from "@godxjp/ui/general";
13
13
  import { Flex, PageContainer } from "@godxjp/ui/layout";
14
+ import { useTranslation } from "@godxjp/ui/i18n";
14
15
  import { Building2, KeyRound, ShieldCheck, Sparkles } from "lucide-react";
15
16
 
16
17
  import portraitAmber from "../assets/portrait-amber.svg";
18
+ import shotLandscape from "../assets/shot-landscape.svg";
17
19
  import portraitClay from "../assets/portrait-clay.svg";
18
20
  import portraitIndigo from "../assets/portrait-indigo.svg";
19
21
  import portraitIris from "../assets/portrait-iris.svg";
@@ -39,9 +41,39 @@ import portraitSlate from "../assets/portrait-slate.svg";
39
41
  * Composed only from real @godxjp/ui components.
40
42
  */
41
43
  export default function Demo() {
44
+ const { t } = useTranslation();
42
45
  return (
43
46
  <PageContainer title="Avatar" subtitle="Identity image with a readable fallback">
44
47
  <Flex direction="col" gap="lg">
48
+ <Card>
49
+ <CardHeader>
50
+ <CardTitle level={2}>{t("avatarDocs.fitTitle")}</CardTitle>
51
+ <CardDescription>{t("avatarDocs.fitDescription")}</CardDescription>
52
+ </CardHeader>
53
+ <CardContent>
54
+ {/* gh#1176: a wide logo — cover crops it, contain shows it whole. */}
55
+ <Flex direction="row" wrap align="center" gap="md">
56
+ <Flex direction="col" align="center" gap="xs">
57
+ <Avatar shape="square" size="lg">
58
+ <AvatarImage src={shotLandscape} alt={t("avatarDocs.logo")} />
59
+ <AvatarFallback>AC</AvatarFallback>
60
+ </Avatar>
61
+ <Text size="sm" tone="muted">
62
+ cover
63
+ </Text>
64
+ </Flex>
65
+ <Flex direction="col" align="center" gap="xs">
66
+ <Avatar shape="square" size="lg">
67
+ <AvatarImage src={shotLandscape} alt={t("avatarDocs.logo")} fit="contain" />
68
+ <AvatarFallback>AC</AvatarFallback>
69
+ </Avatar>
70
+ <Text size="sm" tone="muted">
71
+ contain
72
+ </Text>
73
+ </Flex>
74
+ </Flex>
75
+ </CardContent>
76
+ </Card>
45
77
  <Card>
46
78
  <CardHeader>
47
79
  <CardTitle level={2}>Image + fallback</CardTitle>
@@ -1,4 +1,4 @@
1
- import type { CSSProperties } from "react";
1
+ import { useState, type CSSProperties } from "react";
2
2
 
3
3
  import { useTranslation } from "@godxjp/ui/i18n";
4
4
  import {
@@ -11,6 +11,7 @@ import {
11
11
  RangeTimeline,
12
12
  type TimelineItem,
13
13
  } from "@godxjp/ui/data-display";
14
+ import { Switch } from "@godxjp/ui/data-entry";
14
15
  import { Text } from "@godxjp/ui/general";
15
16
  import { Flex, PageContainer } from "@godxjp/ui/layout";
16
17
  import { ArrowRightLeft, MessageSquare } from "lucide-react";
@@ -90,6 +91,7 @@ const richItems: TimelineItem[] = [
90
91
 
91
92
  export default function Demo() {
92
93
  const { t, locale } = useTranslation();
94
+ const [gantt, setGantt] = useState({ width: 280, busy: false });
93
95
  // An activity log (gh#1092): every row already happened, so none is "done" or "upcoming" —
94
96
  // `status: "log"` keeps the neutral dot and adds no screen-reader status prefix.
95
97
  const logItems: TimelineItem[] = [
@@ -270,6 +272,87 @@ export default function Demo() {
270
272
  />
271
273
  </CardContent>
272
274
  </Card>
275
+ <Card>
276
+ <CardHeader>
277
+ <CardTitle level={2}>{t("rangeTimelineDocs.resizeTitle")}</CardTitle>
278
+ <CardDescription>{t("rangeTimelineDocs.resizeDescription")}</CardDescription>
279
+ </CardHeader>
280
+ <CardContent>
281
+ <Flex direction="col" gap="sm">
282
+ <Switch
283
+ checked={gantt.busy}
284
+ onCheckedChange={(busy) => setGantt((state) => ({ ...state, busy }))}
285
+ aria-label={t("rangeTimelineDocs.busyToggle")}
286
+ />
287
+ <RangeTimeline
288
+ label={t("rangeTimeline.schedule")}
289
+ columns={Array.from({ length: 14 }, (_, index) => ({
290
+ label: new Intl.NumberFormat(locale).format(index + 1),
291
+ units: 1,
292
+ }))}
293
+ rows={[
294
+ {
295
+ id: "r1",
296
+ label: t("rangeTimelineDocs.longTask"),
297
+ start: 1,
298
+ end: 6,
299
+ startLabel: "",
300
+ endLabel: "",
301
+ color: "#2563eb",
302
+ tooltip: t("rangeTimelineDocs.longTask"),
303
+ },
304
+ {
305
+ id: "r3",
306
+ label: t("rangeTimelineDocs.doneTask"),
307
+ start: 0,
308
+ end: 2,
309
+ startLabel: "",
310
+ endLabel: "",
311
+ color: "#16a34a",
312
+ muted: true,
313
+ },
314
+ {
315
+ id: "r4",
316
+ label: t("rangeTimelineDocs.lateTask"),
317
+ start: 3,
318
+ end: 8,
319
+ startLabel: "",
320
+ endLabel: "",
321
+ color: "#f59e0b",
322
+ overdue: true,
323
+ },
324
+ {
325
+ id: "r5",
326
+ label: t("rangeTimelineDocs.plannedTask"),
327
+ start: 2,
328
+ end: 9,
329
+ startLabel: "",
330
+ endLabel: "",
331
+ color: "#7c3aed",
332
+ plan: { start: 2, end: 6 },
333
+ },
334
+ {
335
+ id: "r2",
336
+ label: t("rangeTimelineDocs.undatedTask"),
337
+ start: null,
338
+ end: null,
339
+ startLabel: "",
340
+ endLabel: "",
341
+ },
342
+ ]}
343
+ resizableLabel={{ min: 180, max: 520 }}
344
+ labelWidth={gantt.width}
345
+ onLabelWidthChange={(width) => setGantt((state) => ({ ...state, width }))}
346
+ busy={gantt.busy}
347
+ />
348
+ <Text size="sm" tone="muted">
349
+ {t("rangeTimelineDocs.width", {
350
+ width: new Intl.NumberFormat(locale).format(gantt.width),
351
+ })}
352
+ </Text>
353
+ </Flex>
354
+ </CardContent>
355
+ </Card>
273
356
 
274
357
  {/* sticky sits OUTSIDE a Card on purpose: Card clips (`overflow: hidden`), and a clipping
275
358
  ancestor becomes the box the header sticks to — the same limit as antd Table sticky. */}
@@ -0,0 +1,76 @@
1
+ import * as React from "react";
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
3
+ import { SortableList } from "@godxjp/ui/data-entry";
4
+ import { Text } from "@godxjp/ui/general";
5
+ import { useTranslation } from "@godxjp/ui/i18n";
6
+ import { Flex, PageContainer } from "@godxjp/ui/layout";
7
+
8
+ /**
9
+ * SortableList (gh#1173) — a desktop editor's widget order (tiles) and a settings list of tabs,
10
+ * reordered by dragging a grip or by the keyboard, every step announced.
11
+ */
12
+ export default function Demo() {
13
+ const { t, locale } = useTranslation();
14
+ const list = new Intl.ListFormat(locale, { style: "narrow", type: "unit" });
15
+ const ordinal = new Intl.NumberFormat(locale);
16
+ const widgets = [
17
+ { value: "calendar", label: t("sortableListDocs.calendar") },
18
+ { value: "mail", label: t("sortableListDocs.mail") },
19
+ { value: "tasks", label: t("sortableListDocs.tasks") },
20
+ { value: "notes", label: t("sortableListDocs.notes") },
21
+ ];
22
+ const tabs = [
23
+ { value: "overview", label: t("sortableListDocs.overview") },
24
+ { value: "members", label: t("sortableListDocs.members") },
25
+ { value: "billing", label: t("sortableListDocs.billing"), disabled: true },
26
+ { value: "audit", label: t("sortableListDocs.audit") },
27
+ ];
28
+ const [order, setOrder] = React.useState(widgets.map((w) => w.value));
29
+ return (
30
+ <PageContainer title="SortableList" subtitle={t("sortableListDocs.subtitle")}>
31
+ <Flex direction="col" gap="lg">
32
+ <Card>
33
+ <CardHeader>
34
+ <CardTitle level={2}>{t("sortableListDocs.widgetsTitle")}</CardTitle>
35
+ <CardDescription>{t("sortableListDocs.widgetsDescription")}</CardDescription>
36
+ </CardHeader>
37
+ <CardContent>
38
+ <Flex direction="col" gap="sm">
39
+ <SortableList
40
+ aria-label={t("sortableListDocs.widgetsTitle")}
41
+ layout="grid"
42
+ items={widgets}
43
+ value={order}
44
+ onValueChange={setOrder}
45
+ renderItem={(item, { index }) => (
46
+ <Flex direction="col" gap="xs">
47
+ <Text weight="medium">{item.label}</Text>
48
+ <Text size="sm" tone="muted">
49
+ {t("sortableListDocs.slot", { position: ordinal.format(index + 1) })}
50
+ </Text>
51
+ </Flex>
52
+ )}
53
+ />
54
+ <Text size="sm" tone="muted">
55
+ {t("sortableListDocs.saved", {
56
+ order: list.format(
57
+ order.map((key) => widgets.find((w) => w.value === key)?.label ?? key),
58
+ ),
59
+ })}
60
+ </Text>
61
+ </Flex>
62
+ </CardContent>
63
+ </Card>
64
+ <Card>
65
+ <CardHeader>
66
+ <CardTitle level={2}>{t("sortableListDocs.tabsTitle")}</CardTitle>
67
+ <CardDescription>{t("sortableListDocs.tabsDescription")}</CardDescription>
68
+ </CardHeader>
69
+ <CardContent>
70
+ <SortableList aria-label={t("sortableListDocs.tabsTitle")} items={tabs} />
71
+ </CardContent>
72
+ </Card>
73
+ </Flex>
74
+ </PageContainer>
75
+ );
76
+ }
@@ -21,7 +21,8 @@ import {
21
21
  CardTitle,
22
22
  } from "@godxjp/ui/data-display";
23
23
  import { Button, Text } from "@godxjp/ui/general";
24
- import { Flex, PageContainer } from "@godxjp/ui/layout";
24
+ import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
25
+ import { useTranslation } from "@godxjp/ui/i18n";
25
26
 
26
27
  /** Every branch of DialogHeader's `tone`, in the order the vocabulary declares them. */
27
28
  const headerTones = [
@@ -49,6 +50,8 @@ const yen = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY"
49
50
  * Use DialogFooter for primary/cancel actions.
50
51
  */
51
52
  export default function Demo() {
53
+ const { t } = useTranslation();
54
+ const [wideOpen, setWideOpen] = useState(false);
52
55
  const [createOpen, setCreateOpen] = useState(true);
53
56
  const [detailOpen, setDetailOpen] = useState(false);
54
57
  const [toneOpen, setToneOpen] = useState(false);
@@ -128,6 +131,39 @@ export default function Demo() {
128
131
  </CardContent>
129
132
  </Card>
130
133
 
134
+ <Card>
135
+ <CardHeader>
136
+ <CardTitle level={2}>{t("dialogWidthDocs.title")}</CardTitle>
137
+ <CardDescription>{t("dialogWidthDocs.description")}</CardDescription>
138
+ </CardHeader>
139
+ <CardContent>
140
+ <Dialog open={wideOpen} onOpenChange={setWideOpen}>
141
+ <DialogTrigger asChild>
142
+ <Button variant="outline" size="sm">
143
+ {t("dialogWidthDocs.open")}
144
+ </Button>
145
+ </DialogTrigger>
146
+ <DialogContent width="60rem">
147
+ <DialogHeader>
148
+ <DialogTitle>{t("dialogWidthDocs.dialogTitle")}</DialogTitle>
149
+ <DialogDescription>{t("dialogWidthDocs.dialogDescription")}</DialogDescription>
150
+ </DialogHeader>
151
+ <DialogBody>
152
+ <ResponsiveGrid columns={{ base: 1, sm: 3 }}>
153
+ {[1, 2, 3, 4, 5, 6].map((n) => (
154
+ <Card key={n}>
155
+ <CardContent>
156
+ <Text>{t("dialogWidthDocs.desktop", { n: String(n) })}</Text>
157
+ </CardContent>
158
+ </Card>
159
+ ))}
160
+ </ResponsiveGrid>
161
+ </DialogBody>
162
+ </DialogContent>
163
+ </Dialog>
164
+ </CardContent>
165
+ </Card>
166
+
131
167
  <Card>
132
168
  <CardHeader>
133
169
  <CardTitle level={2}>Read-only detail pop-up</CardTitle>
@@ -1638,5 +1638,67 @@
1638
1638
  "label": "Icon",
1639
1639
  "chosen": "Chosen: {icon}",
1640
1640
  "none": "No icon"
1641
+ },
1642
+ "dialogWidthDocs": {
1643
+ "title": "Wide dialog (width)",
1644
+ "description": "DialogContent width sizes the panel for an overview grid; it is capped at the viewport minus the inset, so it still fits a phone.",
1645
+ "open": "Open the overview",
1646
+ "dialogTitle": "All desktops",
1647
+ "dialogDescription": "A 60rem dialog holding a three-column grid.",
1648
+ "desktop": "Desktop {n}"
1649
+ },
1650
+ "sortableListDocs": {
1651
+ "subtitle": "Drag a grip, or Space · arrows · Space · Esc — every move is announced",
1652
+ "widgetsTitle": "Home widgets",
1653
+ "widgetsDescription": "Order the tiles on your desktop. The order is saved once, when you drop.",
1654
+ "calendar": "Calendar",
1655
+ "mail": "Mail",
1656
+ "tasks": "Tasks",
1657
+ "notes": "Notes",
1658
+ "slot": "Slot {position}",
1659
+ "saved": "Saved order: {order}",
1660
+ "tabsTitle": "Project tabs",
1661
+ "tabsDescription": "A list layout. Billing is locked by your plan, so it cannot be picked up — the others still move past it.",
1662
+ "overview": "Overview",
1663
+ "members": "Members",
1664
+ "billing": "Billing",
1665
+ "audit": "Audit log"
1666
+ },
1667
+ "topbarItemDocs": {
1668
+ "notifications": "Notifications",
1669
+ "notificationsUnread": "Notifications, {count} unread"
1670
+ },
1671
+ "appLauncherDocs": {
1672
+ "requestAccess": "Request access",
1673
+ "allApps": "All apps"
1674
+ },
1675
+ "responsiveGridDocs": {
1676
+ "boardTitle": "Widget board (rowSpan + dense)",
1677
+ "boardDescription": "A 2×1 cell, a tall 1×2 cell (ResponsiveGrid.Item rowSpan={2}) and 1×1 cells. dense lets the small cells fill the space beside the wide and tall ones.",
1678
+ "revenue": "Monthly revenue",
1679
+ "tasks": "Open tasks",
1680
+ "members": "Members",
1681
+ "invoices": "Invoices",
1682
+ "services": "Services",
1683
+ "invitations": "Invitations"
1684
+ },
1685
+ "avatarDocs": {
1686
+ "fitTitle": "Logos: fit=\"contain\"",
1687
+ "fitDescription": "cover (the default) crops to fill — right for a face. contain shows a wide or transparent logo whole.",
1688
+ "logo": "Acme logo"
1689
+ },
1690
+ "sidebarDocs": {
1691
+ "newEntry": "New journal entry"
1692
+ },
1693
+ "rangeTimelineDocs": {
1694
+ "resizeTitle": "Resizable label column, loading, undated rows",
1695
+ "resizeDescription": "Drag the divider in the header (or focus it and use the arrow keys, Shift for bigger steps, Home/End) to widen the label column. The width is controlled, so an app can remember it per user. The switch marks a range as loading; a row without dates shows a hint instead of a bar.",
1696
+ "busyToggle": "Loading a new range",
1697
+ "longTask": "EXSELI-81 Rebuild the contact form and its confirmation email",
1698
+ "undatedTask": "EXSELI-82 Not scheduled yet",
1699
+ "width": "Label column: {width}px",
1700
+ "doneTask": "EXSELI-79 Release notes (done)",
1701
+ "lateTask": "EXSELI-80 Data migration (overdue)",
1702
+ "plannedTask": "EXSELI-84 Payment API (plan 3–7, actual 3–10)"
1641
1703
  }
1642
1704
  }