@godxjp/ui 23.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.
- package/dist/app/date-format-labels.d.ts +14 -2
- package/dist/app/date-format-labels.js +1 -1
- package/dist/app/date-formats.d.ts +14 -2
- package/dist/app/date-formats.js +9 -2
- package/dist/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/form-field.js +2 -2
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/number-input.js +27 -6
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/switch.js +1 -1
- package/dist/components/feedback/dialog.js +2 -2
- package/dist/components/feedback/sheet.js +2 -22
- package/dist/components/navigation/filter-bar.js +2 -1
- package/dist/components/navigation/tabs.js +16 -4
- package/dist/i18n/messages/en.json +5 -1
- package/dist/i18n/messages/ja.json +5 -1
- package/dist/i18n/messages/vi.json +5 -1
- package/dist/lib/breakpoint-token.d.ts +8 -0
- package/dist/lib/breakpoint-token.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +8 -1
- package/dist/props/components/navigation.prop.d.ts +8 -1
- package/dist/props/registry.d.ts +6 -1
- package/dist/props/registry.js +6 -0
- package/dist/props/vocabulary/data.prop.d.ts +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +41 -5
- package/dist/styles/focus-ring.css +28 -0
- package/dist/styles/form-layout.css +4 -0
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +13 -2
- package/dist/styles/shell-layout.css +4 -0
- package/dist/styles/table-layout.css +5 -2
- package/dist/tokens/components/control.css +8 -3
- package/dist/tokens/components/navigation.css +2 -0
- package/docs/CONSUMER-RULES.md +7 -2
- package/docs/CUSTOMER-THEMING.md +18 -8
- package/docs/DATETIME.md +11 -2
- package/docs/SPACING.md +35 -5
- package/docs/TOKENS.md +1 -1
- package/docs/data-display/data-table/index.tsx +17 -2
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
- package/docs/navigation/dropdown-menu.tsx +4 -4
- package/docs/navigation/tabs.tsx +72 -0
- package/docs/roadmap/ai-chat-components.md +1 -2
- package/docs/roadmap/tree-components.md +0 -1
- package/package.json +11 -17
- package/scripts/_agent-setup.mjs +18 -0
- package/scripts/guinea-pig-skill.md +12 -6
- package/scripts/ui-audit.mjs +184 -6
|
@@ -4,4 +4,4 @@ export type { TitleProp, SubtitleProp, StatusProp, DescriptionProp, ExtraProp, F
|
|
|
4
4
|
export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TableDensityProp, DensityProp, GapNameProp, PadProp, PadRawProp, PadSides, GapProp, GapStepProp, } from "./layout.prop.js";
|
|
5
5
|
export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, TextWhitespaceProp, SizeProp, ControlStatusProp, ControlVariantProp, FormLayoutProp, DescriptionsLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, ActivityVariantProp, ActivityAnnounceProp, } from "./interaction.prop.js";
|
|
6
6
|
export type { BreadcrumbItemProp, BreadcrumbProp, BreadcrumbItemMenuProp, BreadcrumbItemMenuEntryProp, BreadcrumbSeparatorProp, BreadcrumbItemRenderProp, } from "./navigation.prop.js";
|
|
7
|
-
export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, TableRowToneProp, RowToneProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
|
|
7
|
+
export type { GetRowIdProp, GetRowLabelProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, TableRowToneProp, RowToneProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
|
package/dist/styles/control.css
CHANGED
|
@@ -177,8 +177,8 @@
|
|
|
177
177
|
content: "";
|
|
178
178
|
position: absolute;
|
|
179
179
|
inset: 0;
|
|
180
|
-
min-inline-size: var(--button-bare-target-size);
|
|
181
|
-
min-block-size: var(--button-bare-target-size);
|
|
180
|
+
min-inline-size: var(--button-bare-target-size, var(--control-height-xs));
|
|
181
|
+
min-block-size: var(--button-bare-target-size, var(--control-height-xs));
|
|
182
182
|
margin: auto;
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -1338,6 +1338,20 @@
|
|
|
1338
1338
|
inline-size: 0.75rem;
|
|
1339
1339
|
block-size: 0.75rem;
|
|
1340
1340
|
}
|
|
1341
|
+
|
|
1342
|
+
@media (pointer: coarse) {
|
|
1343
|
+
.ui-number-input:not([data-size="xs"]):not([data-size="sm"]) {
|
|
1344
|
+
--control-height: var(--number-input-touch-height);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.ui-number-input-steppers {
|
|
1348
|
+
gap: 0;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.ui-number-input-step {
|
|
1352
|
+
block-size: calc(var(--control-height) / 2);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1341
1355
|
}
|
|
1342
1356
|
|
|
1343
1357
|
@layer components {
|
|
@@ -1887,6 +1901,22 @@
|
|
|
1887
1901
|
transition: opacity 150ms ease;
|
|
1888
1902
|
}
|
|
1889
1903
|
|
|
1904
|
+
.ui-control-inline-affix-action {
|
|
1905
|
+
position: relative;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.ui-control-inline-affix-action::after {
|
|
1909
|
+
content: "";
|
|
1910
|
+
position: absolute;
|
|
1911
|
+
inset-block-start: 50%;
|
|
1912
|
+
inset-inline-start: 50%;
|
|
1913
|
+
translate: -50% -50%;
|
|
1914
|
+
min-inline-size: var(--touch-target-min);
|
|
1915
|
+
min-block-size: var(--touch-target-min);
|
|
1916
|
+
inline-size: 100%;
|
|
1917
|
+
block-size: 100%;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1890
1920
|
.ui-control-inline-affix-action:hover,
|
|
1891
1921
|
.ui-control-inline-affix-action:focus-visible {
|
|
1892
1922
|
color: hsl(var(--foreground));
|
|
@@ -2108,6 +2138,8 @@
|
|
|
2108
2138
|
background-color: hsl(var(--muted));
|
|
2109
2139
|
|
|
2110
2140
|
border-radius: 0;
|
|
2141
|
+
|
|
2142
|
+
padding-block: var(--calendar-bordered-weekday-padding-block);
|
|
2111
2143
|
}
|
|
2112
2144
|
|
|
2113
2145
|
.ui-calendar[data-bordered="true"] .ui-calendar-grid {
|
|
@@ -2536,7 +2568,7 @@
|
|
|
2536
2568
|
}
|
|
2537
2569
|
|
|
2538
2570
|
.ui-button--xs {
|
|
2539
|
-
height: var(--button-xs-height);
|
|
2571
|
+
height: var(--button-xs-height, var(--control-height-xs));
|
|
2540
2572
|
gap: var(--button-xs-space-gap);
|
|
2541
2573
|
padding-inline: var(--button-xs-space-inline);
|
|
2542
2574
|
font-size: var(--button-xs-font-size);
|
|
@@ -2642,8 +2674,8 @@
|
|
|
2642
2674
|
|
|
2643
2675
|
.ui-control[data-status="warning"],
|
|
2644
2676
|
.ui-control-multiline[data-status="warning"] {
|
|
2645
|
-
--focus-ring-color: var(--control-status-warning-
|
|
2646
|
-
--focus-outline-color: var(--control-status-warning-
|
|
2677
|
+
--focus-ring-color: var(--control-status-warning-outline-color);
|
|
2678
|
+
--focus-outline-color: var(--control-status-warning-outline-color);
|
|
2647
2679
|
--focus-ring-glow-color: var(--control-status-warning-glow-color);
|
|
2648
2680
|
--focus-ring-glow-alpha: var(--control-status-warning-glow-alpha);
|
|
2649
2681
|
}
|
|
@@ -2988,6 +3020,10 @@
|
|
|
2988
3020
|
justify-content: center;
|
|
2989
3021
|
}
|
|
2990
3022
|
|
|
3023
|
+
.ui-segmented:not([data-orientation="vertical"]) {
|
|
3024
|
+
row-gap: var(--segmented-track-padding);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
2991
3027
|
.ui-segmented[data-orientation="vertical"] {
|
|
2992
3028
|
flex-direction: column;
|
|
2993
3029
|
}
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
.ui-toggle,
|
|
14
14
|
.ui-checkbox,
|
|
15
15
|
.ui-radio,
|
|
16
|
+
.ui-slider-thumb,
|
|
17
|
+
.ui-control-affix-action,
|
|
18
|
+
.ui-control-inline-affix-action,
|
|
19
|
+
.ui-search-input-clear,
|
|
20
|
+
.ui-tag-input-remove,
|
|
21
|
+
.ui-color-picker-input,
|
|
22
|
+
.ui-upload-tile-add,
|
|
23
|
+
.ui-upload-picture-empty,
|
|
24
|
+
.ui-upload-dropzone,
|
|
25
|
+
.ui-tabs-add,
|
|
26
|
+
.ui-steps-control,
|
|
27
|
+
.ui-carousel-previous,
|
|
28
|
+
.ui-carousel-next,
|
|
16
29
|
.ui-steps-inline-control,
|
|
17
30
|
.ui-pagination-link,
|
|
18
31
|
.ui-legal-document-toc-link,
|
|
@@ -28,6 +41,7 @@
|
|
|
28
41
|
):focus-visible,
|
|
29
42
|
|
|
30
43
|
.ui-checkbox[data-focus-visible],
|
|
44
|
+
.ui-radio[data-focus-visible],
|
|
31
45
|
.ui-switch[data-focus-visible],
|
|
32
46
|
.ui-slider-thumb[data-focus-visible],
|
|
33
47
|
.ui-focus-ring[data-focus-visible],
|
|
@@ -59,6 +73,19 @@
|
|
|
59
73
|
.ui-toggle,
|
|
60
74
|
.ui-checkbox,
|
|
61
75
|
.ui-radio,
|
|
76
|
+
.ui-slider-thumb,
|
|
77
|
+
.ui-control-affix-action,
|
|
78
|
+
.ui-control-inline-affix-action,
|
|
79
|
+
.ui-search-input-clear,
|
|
80
|
+
.ui-tag-input-remove,
|
|
81
|
+
.ui-color-picker-input,
|
|
82
|
+
.ui-upload-tile-add,
|
|
83
|
+
.ui-upload-picture-empty,
|
|
84
|
+
.ui-upload-dropzone,
|
|
85
|
+
.ui-tabs-add,
|
|
86
|
+
.ui-steps-control,
|
|
87
|
+
.ui-carousel-previous,
|
|
88
|
+
.ui-carousel-next,
|
|
62
89
|
.ui-steps-inline-control,
|
|
63
90
|
.ui-pagination-link,
|
|
64
91
|
.ui-legal-document-toc-link,
|
|
@@ -74,6 +101,7 @@
|
|
|
74
101
|
):focus-visible,
|
|
75
102
|
|
|
76
103
|
:root[data-focus-outline="on"] .ui-checkbox[data-focus-visible],
|
|
104
|
+
:root[data-focus-outline="on"] .ui-radio[data-focus-visible],
|
|
77
105
|
:root[data-focus-outline="on"] .ui-switch[data-focus-visible],
|
|
78
106
|
:root[data-focus-outline="on"] .ui-slider-thumb[data-focus-visible],
|
|
79
107
|
:root[data-focus-outline="on"] .ui-focus-ring[data-focus-visible],
|
package/dist/styles/layout.css
CHANGED
|
@@ -103,6 +103,11 @@
|
|
|
103
103
|
flex: 1 1 auto;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
[data-slot="tabs"][data-orientation="vertical"] > [data-slot="tabs-list"],
|
|
107
|
+
[data-slot="tabs"][data-orientation="vertical"] > .ui-tabs-bar {
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
106
111
|
.ui-tabs-extra {
|
|
107
112
|
display: flex;
|
|
108
113
|
flex: 0 0 auto;
|
|
@@ -194,6 +199,12 @@
|
|
|
194
199
|
block-size: var(--tabs-add-icon-size);
|
|
195
200
|
}
|
|
196
201
|
|
|
202
|
+
.ui-pagination {
|
|
203
|
+
max-inline-size: 100%;
|
|
204
|
+
overflow-x: auto;
|
|
205
|
+
scrollbar-width: thin;
|
|
206
|
+
}
|
|
207
|
+
|
|
197
208
|
.ui-pagination .ui-button svg,
|
|
198
209
|
.ui-pagination .ui-pagination-link svg {
|
|
199
210
|
inline-size: var(--pagination-icon-size);
|
|
@@ -205,11 +216,11 @@
|
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
.ui-pagination[data-align="center"] {
|
|
208
|
-
justify-content: center;
|
|
219
|
+
justify-content: safe center;
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
.ui-pagination[data-align="end"] {
|
|
212
|
-
justify-content: flex-end;
|
|
223
|
+
justify-content: safe flex-end;
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
.ui-pagination[data-size="sm"] {
|
|
@@ -2322,6 +2322,10 @@
|
|
|
2322
2322
|
.ui-mobile-shell {
|
|
2323
2323
|
--control-height: var(--mobile-shell-control-height);
|
|
2324
2324
|
|
|
2325
|
+
--control-height-sm: calc(var(--control-height) - calc(0.25rem * var(--scaling)));
|
|
2326
|
+
--control-height-lg: calc(var(--control-height) + calc(0.25rem * var(--scaling)));
|
|
2327
|
+
--control-height-xs: calc(var(--control-height) - calc(0.5rem * var(--scaling)));
|
|
2328
|
+
|
|
2325
2329
|
--mobile-shell-padding-inline: calc(
|
|
2326
2330
|
var(--mobile-shell-inset-inline) + var(--mobile-shell-safe-inset-inline)
|
|
2327
2331
|
);
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
.ui-table-row[data-tone] > [data-slot="table-cell"]:first-child {
|
|
47
|
-
border-inline-start: var(--table-row-tone-rail-width) solid
|
|
48
|
-
hsl(var(--table-row-tone-color));
|
|
47
|
+
border-inline-start: var(--table-row-tone-rail-width) solid hsl(var(--table-row-tone-color));
|
|
49
48
|
padding-inline-start: calc(var(--table-cell-space-x) - var(--table-row-tone-rail-width));
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -206,6 +205,10 @@
|
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
.ui-data-table-empty {
|
|
208
|
+
padding: var(--empty-state-space-y) var(--empty-state-space-x);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.ui-data-table-empty .ui-empty-state {
|
|
209
212
|
padding: 0;
|
|
210
213
|
}
|
|
211
214
|
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
--control-status-warning-glow-color: var(--warning);
|
|
33
33
|
--control-status-warning-glow-alpha: var(--control-outline-error-alpha);
|
|
34
34
|
|
|
35
|
+
--number-input-touch-height: var(--band-height-2xl);
|
|
36
|
+
|
|
35
37
|
--control-variant-filled-background: var(--muted);
|
|
36
38
|
--control-variant-filled-hover-background: var(--accent);
|
|
37
39
|
|
|
@@ -286,6 +288,8 @@
|
|
|
286
288
|
--calendar-footer-space-block-start: var(--space-2);
|
|
287
289
|
--calendar-footer-border-width: var(--stroke-hairline);
|
|
288
290
|
|
|
291
|
+
--calendar-bordered-weekday-padding-block: var(--space-1);
|
|
292
|
+
|
|
289
293
|
--transfer-pane-min-height: 14rem;
|
|
290
294
|
--transfer-pane-radius: var(--radius-md);
|
|
291
295
|
--transfer-panes-space-gap: var(--space-3);
|
|
@@ -317,9 +321,9 @@
|
|
|
317
321
|
--month-picker-icon-size: var(--icon-size-md);
|
|
318
322
|
--month-picker-separator-icon-size: var(--icon-size-sm);
|
|
319
323
|
|
|
320
|
-
--button-xs-height:
|
|
324
|
+
--button-xs-height: initial;
|
|
321
325
|
|
|
322
|
-
--button-bare-target-size:
|
|
326
|
+
--button-bare-target-size: initial;
|
|
323
327
|
--button-xs-space-inline: var(--space-2);
|
|
324
328
|
--button-xs-space-gap: var(--space-1);
|
|
325
329
|
--button-xs-font-size: var(--font-size-xs);
|
|
@@ -369,7 +373,8 @@
|
|
|
369
373
|
|
|
370
374
|
--control-status-error-border-color: hsl(var(--destructive));
|
|
371
375
|
--control-status-warning-border-color: hsl(var(--text-warning));
|
|
372
|
-
|
|
376
|
+
|
|
377
|
+
--control-status-warning-outline-color: var(--text-warning);
|
|
373
378
|
}
|
|
374
379
|
|
|
375
380
|
@media (pointer: coarse) {
|
package/docs/CONSUMER-RULES.md
CHANGED
|
@@ -11,7 +11,12 @@ Read this once; the audit enforces it. Everything else in `docs/` is for contrib
|
|
|
11
11
|
7. Colours are semantic tokens (`tone="destructive"`, `bg-primary`), never palette names, hex, or `bg-black` / `text-white`.
|
|
12
12
|
8. Sizes come from props (`size`, `width`, `columns`), never `w-[240px]` / `max-h-[420px]`.
|
|
13
13
|
9. Logical directions (`ms-`, `me-`, `start-`, `end-`) when a utility is unavoidable; never `ml-` / `left-`.
|
|
14
|
-
10.
|
|
14
|
+
10. A `Card` whose whole body is a table gets `<CardContent flush>` — the table must touch the
|
|
15
|
+
card's inner edge. A default `CardContent` pads 16px while the table draws its own border, so
|
|
16
|
+
the table reads as a second box inside the card, and a wide one runs past the card entirely.
|
|
17
|
+
(`flush` alone is enough: it drops the border, the radius and the inline padding. `tight` is a
|
|
18
|
+
different knob — it governs the header band, not the body's edges.)
|
|
19
|
+
11. Run `node node_modules/@godxjp/ui/scripts/ui-audit.mjs --changed` before every review; a browser sweep with `visual-audit.mjs` is a separate, heavier run — do it when the work is a visual review, not on every change; `visual-audit.mjs <url>` on the running app. Zero errors is the bar.
|
|
15
20
|
|
|
16
21
|
**Opting a deliberate exception out.** Name the rule; the block form also has to say why.
|
|
17
22
|
|
|
@@ -45,6 +50,6 @@ information belongs on the screen:
|
|
|
45
50
|
- `ServiceLauncherCard` already has this shape as a prop — `disabledReason`, rendered above the
|
|
46
51
|
action — and it is the pattern to copy anywhere else.
|
|
47
52
|
|
|
48
|
-
If the control is disabled
|
|
53
|
+
If the control is disabled _pending something the user can fix_, prefer leaving it enabled and
|
|
49
54
|
explaining on submit: an inert control with a paragraph beside it is often a validation message
|
|
50
55
|
wearing a disguise.
|
package/docs/CUSTOMER-THEMING.md
CHANGED
|
@@ -89,7 +89,7 @@ Import the styles, then set anchor tokens in your app's `theme.css` (loaded afte
|
|
|
89
89
|
|
|
90
90
|
/* ── Shadow tint — ONE knob ─────────────────────────────────────── */ --shadow-color: 12 26 49; /* RGB channels (default 0 0 0). Tints the WHOLE shadow ramp. */
|
|
91
91
|
|
|
92
|
-
/* ── Brand depth — all opt-in, all quiet by default ─────────────── */ --shadow-glow: 0 8px 20px hsl(var(--primary) / 0.32); /* glow halo on the primary CTA */ --card-shadow: 0 1px 2px rgb(12 26 49 / 0.06), 0 10px 28px -14px rgb(12 26 49 / 0.2); /* lift every Card */ --focus-ring-color: var(--ring); /* hue of every focus ring */ --focus-ring-
|
|
92
|
+
/* ── Brand depth — all opt-in, all quiet by default ─────────────── */ --shadow-glow: 0 8px 20px hsl(var(--primary) / 0.32); /* glow halo on the primary CTA */ --card-shadow: 0 1px 2px rgb(12 26 49 / 0.06), 0 10px 28px -14px rgb(12 26 49 / 0.2); /* lift every Card */ --focus-ring-color: var(--ring); /* hue of every focus ring */ --focus-ring-weight: var(--stroke-md); /* thickness of every focus ring — 2px; nothing paints until <html data-focus-outline="on"> */ --focus-ring-opacity: 1; /* alpha of every focus ring */ --focus-ring-offset: 0px; /* gap, outline-form rings only (star, dot, anchor) */ --gradient-hero: linear-gradient( 180deg, hsl(var(--accent)), transparent ); /* PageContainer header banner */ --gradient-glow: radial-gradient( 60% 70% at 90% -8%, hsl(var(--primary) / 0.1), transparent 70% ); /* AppShell ambient wash */ --overlay-background: rgb(12 26 49 / 0.55); /* modal scrim (Dialog / Sheet / Drawer) */ } ```
|
|
93
93
|
|
|
94
94
|
### The anchor tokens (single knobs that propagate)
|
|
95
95
|
|
|
@@ -101,23 +101,33 @@ Import the styles, then set anchor tokens in your app's `theme.css` (loaded afte
|
|
|
101
101
|
| `--shadow-color` | `0 0 0` | every shadow step `--shadow-{xs…2xl}` (`rgb(color / α)`) |
|
|
102
102
|
| `--shadow-glow` | invisible | brand glow halo on the primary CTA (set the full shadow value) |
|
|
103
103
|
| `--card-shadow` | `none` | resting elevation of every Card |
|
|
104
|
-
| `--focus-ring-color` / `--focus-ring-
|
|
104
|
+
| `--focus-ring-color` / `--focus-ring-weight` | `var(--ring)` / `1px` | hue & thickness of every keyboard-focus ring — but nothing paints until the switch below is on |
|
|
105
105
|
| `--focus-ring-opacity` / `--focus-ring-offset` | `1` / `0px` | alpha of every ring · gap for outline-form rings (star, carousel dot, heading anchor) |
|
|
106
106
|
| `--gradient-{hero,glow,brand}` | `none` | hero header banner / AppShell ambient wash / spare (set the full gradient) |
|
|
107
107
|
| `--overlay-background` | `rgb(0 0 0 / .5)` | the scrim of every overlay (Dialog / AlertDialog / Sheet / Drawer) |
|
|
108
108
|
|
|
109
|
-
### Focus ring —
|
|
109
|
+
### Focus ring — THE SWITCH FIRST, then three levels of override
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
**The indicator ships OFF.** `--focus-outline` is `0`, and every painted focus length multiplies by it, so by default nothing paints a focus mark. That is a product decision with a stated cost — it forfeits WCAG 2.2 SC 2.4.7 (AA) and a JIS X 8341-3 AA claim — recorded in `docs/DESIGN-AUTHORITY.md`. Turning it on is ONE attribute on the root element, no CSS:
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
```html
|
|
114
|
+
<html data-focus-outline="on"></html>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
(`AppProvider` has the equivalent.) Every `:focus-visible` selector is present either way; only the paint is switched.
|
|
118
|
+
|
|
119
|
+
Every ring is then drawn by a single rule (`src/styles/focus-ring.css`) reading the tokens above. Nothing else paints one; a test fails the build if a stylesheet tries.
|
|
120
|
+
|
|
121
|
+
**Thickness is `--focus-ring-weight`, never `--focus-ring-width`.** The width is DERIVED — `calc(var(--focus-ring-weight) * var(--focus-outline))` — so assigning it directly paints a ring even while the indicator is switched off, and that is exactly what the build-failing test forbids a stylesheet to do.
|
|
114
122
|
|
|
115
123
|
```css
|
|
116
124
|
:root {
|
|
117
125
|
--focus-ring-color: 24 99% 46%; /* every ring, brand orange */
|
|
118
|
-
--focus-ring-
|
|
126
|
+
--focus-ring-weight: var(--stroke-md); /* every ring, 2px */
|
|
119
127
|
}
|
|
120
|
-
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**A slim ring that still satisfies the criteria.** Switched on, the shipped weight is one hairline (1px) in the focus hue, measured in Chromium at **5.05:1** light and **7.07:1** dark on every surface a control sits on — so SC 1.4.11 (3:1, non-text contrast) is met on colour, independent of thickness. SC 2.4.13 Focus Appearance (AAA) additionally wants a perimeter of at least 2 CSS px: set `--focus-ring-weight: var(--stroke-md)` (measured: a 2px ring, same hue, same ratio). `var(--stroke-lg)` is the heavy 3px mark.
|
|
121
131
|
|
|
122
132
|
**Per component.** A component that genuinely needs a different ring publishes its own knob and the rule picks it up locally — Toggle and TimeInput ship a heavier, softer ring because they are filled surfaces where a hard 2px reads as a second border:
|
|
123
133
|
|
|
@@ -136,7 +146,7 @@ That means retuning is a one-liner and it reaches everything:
|
|
|
136
146
|
<div style={{ "--focus-ring-color": "0 84% 60%" } as React.CSSProperties}>…</div>
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
**Turning it off** — `--focus-
|
|
149
|
+
**Turning it back off** — drop the `data-focus-outline` attribute (or set `--focus-outline: 0`), which is the shipped state. Do NOT reach for `--focus-ring-width`: it is derived from the weight and the switch, and pinning it to any value is the same mistake in the other direction. Note the cost either way — with no visible focus indicator the package does not meet WCAG 2.2 SC 2.4.7.
|
|
140
150
|
|
|
141
151
|
**Adding your own component to the system**: put `ui-focus-ring` (or `ui-focus-ring-outline` when the mark needs a gap) on the focusable element.
|
|
142
152
|
|
package/docs/DATETIME.md
CHANGED
|
@@ -23,10 +23,18 @@ Omit `locale`, `timezone`, `timeFormat`, `dateFormat` → synced from AppProvide
|
|
|
23
23
|
|
|
24
24
|
| value | Pattern | Default locale |
|
|
25
25
|
| ----- | ------------ | -------------- |
|
|
26
|
-
| `iso` | `yyyy-MM-dd` |
|
|
26
|
+
| `iso` | `yyyy-MM-dd` | — |
|
|
27
|
+
| `ymd` | `yyyy/MM/dd` | ja |
|
|
27
28
|
| `dmy` | `dd/MM/yyyy` | vi |
|
|
28
29
|
| `mdy` | `MM/dd/yyyy` | en |
|
|
29
30
|
|
|
31
|
+
`ymd` and `iso` are both year-first and are NOT interchangeable: a Japanese business document
|
|
32
|
+
(請求書, 申請書) is written `2026/05/01`, never `2026-05-01`. `ja` defaulted to `iso` until a
|
|
33
|
+
Japanese-first consumer shipped its own date formatter as an explicit stopgap purely to get the
|
|
34
|
+
slashes — rule 1 above broken by this package's own default. Only the DEFAULT moved: a stored
|
|
35
|
+
preference still wins, `defaultDateFormat` still pins any value, and `iso` is one option away in
|
|
36
|
+
`<DateFormatPicker />`.
|
|
37
|
+
|
|
30
38
|
Sent to backend as `x-date-format`. Pick with `<DateFormatPicker />`.
|
|
31
39
|
|
|
32
40
|
## kind presets
|
|
@@ -43,9 +51,10 @@ Sent to backend as `x-date-format`. Pick with `<DateFormatPicker />`.
|
|
|
43
51
|
|
|
44
52
|
## ISO 8601 patterns
|
|
45
53
|
|
|
46
|
-
Date order follows `dateFormat` from AppProvider (`iso` | `dmy` | `mdy`):
|
|
54
|
+
Date order follows `dateFormat` from AppProvider (`iso` | `ymd` | `dmy` | `mdy`):
|
|
47
55
|
|
|
48
56
|
- `iso`: `yyyy-MM-dd` + `yyyy-MM-dd HH:mm`
|
|
57
|
+
- `ymd`: `yyyy/MM/dd` + `yyyy/MM/dd HH:mm`
|
|
49
58
|
- `dmy`: `dd/MM/yyyy` + `dd/MM/yyyy HH:mm`
|
|
50
59
|
- `mdy`: `MM/dd/yyyy` + `MM/dd/yyyy HH:mm`
|
|
51
60
|
|
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
|
|
46
|
-
|
|
|
47
|
-
| `StatCard`
|
|
48
|
-
| `CardContent solo`
|
|
49
|
-
| `CardContent flush
|
|
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-
|
|
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
|
|
|
@@ -290,13 +290,28 @@ export default function Demo() {
|
|
|
290
290
|
</AppProvider>
|
|
291
291
|
</Flex>
|
|
292
292
|
|
|
293
|
-
<Flex direction="col" gap="sm">
|
|
293
|
+
<Flex direction="col" gap="sm" id="empty-builtin">
|
|
294
294
|
<Text as="div" weight="medium">
|
|
295
295
|
空の状態(data が空のとき自動表示)
|
|
296
296
|
</Text>
|
|
297
297
|
<DataTable data={[]} columns={columns} getRowId={(row) => row.id} />
|
|
298
298
|
</Flex>
|
|
299
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
|
+
|
|
300
315
|
{/* — the two failure states, in the same table grid as empty/loading. */}
|
|
301
316
|
<Flex direction="col" gap="sm">
|
|
302
317
|
<Text as="div" weight="medium">
|
|
@@ -318,7 +333,7 @@ export default function Demo() {
|
|
|
318
333
|
<DataTable data={[]} columns={columns} getRowId={(row) => row.id} error />
|
|
319
334
|
</Flex>
|
|
320
335
|
|
|
321
|
-
<Flex direction="col" gap="sm">
|
|
336
|
+
<Flex direction="col" gap="sm" id="error-custom-node">
|
|
322
337
|
<Text as="div" weight="medium">
|
|
323
338
|
エラー(error にノードを渡して文言を差し替え)
|
|
324
339
|
</Text>
|
|
@@ -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>
|
|
@@ -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
|
+
}
|