@godxjp/ui 16.4.0 → 16.5.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 (34) hide show
  1. package/dist/components/data-display/card.d.ts +5 -1
  2. package/dist/components/data-display/card.js +4 -2
  3. package/dist/components/feedback/sheet.js +3 -1
  4. package/dist/components/navigation/steps.js +2 -2
  5. package/dist/lib/control-styles.d.ts +7 -5
  6. package/dist/lib/control-styles.js +4 -4
  7. package/dist/props/registry.d.ts +1 -1
  8. package/dist/props/registry.js +1 -1
  9. package/dist/styles/alert-layout.css +7 -5
  10. package/dist/styles/card-layout.css +33 -1
  11. package/dist/styles/control.css +24 -14
  12. package/dist/styles/data-display-layout.css +15 -12
  13. package/dist/styles/data-entry-layout.css +2 -2
  14. package/dist/styles/dialog-layout.css +6 -2
  15. package/dist/styles/index.css +30 -3
  16. package/dist/styles/layout.css +3 -0
  17. package/dist/styles/navigation-layout.css +10 -10
  18. package/dist/styles/shell-layout.css +17 -6
  19. package/dist/styles/table-layout.css +5 -5
  20. package/dist/tokens/foundation.css +64 -0
  21. package/package.json +3 -2
  22. package/dist/tokens/components/badge.css +0 -10
  23. package/dist/tokens/components/card.css +0 -51
  24. package/dist/tokens/components/control.css +0 -82
  25. package/dist/tokens/components/data-display.css +0 -8
  26. package/dist/tokens/components/data-entry.css +0 -6
  27. package/dist/tokens/components/descriptions.css +0 -9
  28. package/dist/tokens/components/feedback.css +0 -25
  29. package/dist/tokens/components/form.css +0 -11
  30. package/dist/tokens/components/list-row.css +0 -9
  31. package/dist/tokens/components/navigation.css +0 -15
  32. package/dist/tokens/components/shell.css +0 -17
  33. package/dist/tokens/components/table.css +0 -13
  34. package/dist/tokens/semantic/layout.css +0 -49
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
+ import type { LucideIcon } from "lucide-react";
3
4
  type CardSize = "md" | "compact";
4
5
  /** Semantic leading-edge accent stripe (border-inline-start; width via the
5
6
  * --card-accent-rail-width token, default 6px). */
@@ -86,6 +87,9 @@ export type StatCardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<
86
87
  label: React.ReactNode;
87
88
  value: React.ReactNode;
88
89
  hint?: React.ReactNode;
90
+ /** Optional leading icon, rendered as a tinted medallion above the metric. Decorative
91
+ * (aria-hidden) — the label carries the meaning. Tint via --stat-card-icon-* tokens. */
92
+ icon?: LucideIcon;
89
93
  /** Optional compact trend text beside the value. Avoid badge-like deltas. */
90
94
  delta?: React.ReactNode;
91
95
  /** KPI layout: stacked = design default, inline = label left / value right. */
@@ -98,7 +102,7 @@ export type StatCardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<
98
102
  accent?: CardAccent;
99
103
  };
100
104
  /** KPI / stat tile — token-driven layout aligned to dashboard KPI cards. */
101
- export declare function StatCard({ label, value, hint, delta, layout, align, inverse, accent, className, size, ...props }: StatCardProps): import("react/jsx-runtime").JSX.Element;
105
+ export declare function StatCard({ label, value, hint, icon: Icon, delta, layout, align, inverse, accent, className, size, ...props }: StatCardProps): import("react/jsx-runtime").JSX.Element;
102
106
  /** Header actions slot — pair with `CardHeader className="flex flex-row …"`. */
103
107
  export declare const CardAction: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
104
108
  export {};
@@ -96,6 +96,7 @@ function StatCard({
96
96
  label,
97
97
  value,
98
98
  hint,
99
+ icon: Icon,
99
100
  delta,
100
101
  layout = "stacked",
101
102
  align = "start",
@@ -117,6 +118,7 @@ function StatCard({
117
118
  "data-stat-align": align,
118
119
  ...props,
119
120
  children: [
121
+ Icon ? /* @__PURE__ */ jsx("span", { "data-slot": "stat-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, {}) }) : null,
120
122
  /* @__PURE__ */ jsxs("div", { "data-slot": "stat-card-body", children: [
121
123
  /* @__PURE__ */ jsx("div", { "data-slot": "stat-card-label", children: label }),
122
124
  hint && layout === "inline" ? /* @__PURE__ */ jsx("div", { "data-slot": "stat-card-hint", children: hint }) : null
@@ -130,8 +132,8 @@ function StatCard({
130
132
  "data-slot": "stat-card-delta",
131
133
  "data-delta-tone": deltaTone,
132
134
  className: cn(
133
- deltaTone === "positive" && "text-success",
134
- deltaTone === "negative" && "text-destructive"
135
+ deltaTone === "positive" && "text-success-strong",
136
+ deltaTone === "negative" && "text-error-strong"
135
137
  ),
136
138
  children: delta
137
139
  }
@@ -24,7 +24,9 @@ const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PU
24
24
  ref,
25
25
  "data-slot": "sheet-overlay",
26
26
  className: cn(
27
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
27
+ // Scrim colour comes from the shared --overlay-background token (see dialog-layout.css),
28
+ // so a service tints every overlay's backdrop from one knob instead of a baked bg-black/50.
29
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50",
28
30
  className
29
31
  ),
30
32
  ...props
@@ -17,7 +17,7 @@ function StepIcon({
17
17
  if (icon)
18
18
  return /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", controlIconClass), children: icon });
19
19
  if (type === "dot") {
20
- return /* @__PURE__ */ jsx(
20
+ return /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", controlIconClass), children: /* @__PURE__ */ jsx(
21
21
  "span",
22
22
  {
23
23
  className: cn(
@@ -28,7 +28,7 @@ function StepIcon({
28
28
  status === "wait" && "bg-muted-foreground/30"
29
29
  )
30
30
  }
31
- );
31
+ ) });
32
32
  }
33
33
  return /* @__PURE__ */ jsxs(
34
34
  "span",
@@ -17,10 +17,12 @@ export declare const controlIconLeadingClass = "size-[length:var(--control-icon-
17
17
  export declare const tableRowHeightClass = "h-[length:var(--table-row-height)]";
18
18
  export declare const tableHeadHeightClass = "h-[length:var(--table-row-height)]";
19
19
  export declare const tableCellPaddingClass = "py-[length:var(--table-cell-padding-y)]";
20
- /** Semantic status / badge tones — always use tokens, never raw Tailwind palette. */
21
- export declare const toneSuccessClass = "border-success/30 bg-success/10 text-success";
22
- export declare const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-foreground";
23
- export declare const toneInfoClass = "border-info/30 bg-info/10 text-info";
24
- export declare const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-destructive";
20
+ /** Semantic status / badge tones — always use tokens, never raw Tailwind palette. The TEXT uses the
21
+ * AA-strong status colours (text-*-strong, darker than the fill) so a small status label clears
22
+ * WCAG AA on the soft tint; the border/fill keep the brighter wa-iro role. */
23
+ export declare const toneSuccessClass = "border-success/30 bg-success/10 text-success-strong";
24
+ export declare const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-strong";
25
+ export declare const toneInfoClass = "border-info/30 bg-info/10 text-info-strong";
26
+ export declare const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-error-strong";
25
27
  export declare const toneMutedClass = "border-border bg-muted text-muted-foreground";
26
28
  export declare const toneNeutralClass = "border-border bg-muted text-muted-foreground";
@@ -8,10 +8,10 @@ const controlIconLeadingClass = "size-[length:var(--control-icon-size)] shrink-0
8
8
  const tableRowHeightClass = "h-[length:var(--table-row-height)]";
9
9
  const tableHeadHeightClass = "h-[length:var(--table-row-height)]";
10
10
  const tableCellPaddingClass = "py-[length:var(--table-cell-padding-y)]";
11
- const toneSuccessClass = "border-success/30 bg-success/10 text-success";
12
- const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-foreground";
13
- const toneInfoClass = "border-info/30 bg-info/10 text-info";
14
- const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-destructive";
11
+ const toneSuccessClass = "border-success/30 bg-success/10 text-success-strong";
12
+ const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-strong";
13
+ const toneInfoClass = "border-info/30 bg-info/10 text-info-strong";
14
+ const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-error-strong";
15
15
  const toneMutedClass = "border-border bg-muted text-muted-foreground";
16
16
  const toneNeutralClass = "border-border bg-muted text-muted-foreground";
17
17
  export {
@@ -1086,7 +1086,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
1086
1086
  readonly StatCardProp: {
1087
1087
  readonly group: "data-display";
1088
1088
  readonly file: "components/data-display/card.tsx";
1089
- readonly vocabulary: readonly ["TitleProp", "ToneProp", "ClassNameProp"];
1089
+ readonly vocabulary: readonly ["TitleProp", "ToneProp", "IconProp", "ClassNameProp"];
1090
1090
  };
1091
1091
  readonly ResponsiveGridProp: {
1092
1092
  readonly group: "layout";
@@ -1080,7 +1080,7 @@ const COMPONENT_PROP_REGISTRY = {
1080
1080
  StatCardProp: {
1081
1081
  group: "data-display",
1082
1082
  file: "components/data-display/card.tsx",
1083
- vocabulary: ["TitleProp", "ToneProp", "ClassNameProp"]
1083
+ vocabulary: ["TitleProp", "ToneProp", "IconProp", "ClassNameProp"]
1084
1084
  },
1085
1085
  ResponsiveGridProp: {
1086
1086
  group: "layout",
@@ -120,20 +120,22 @@
120
120
  line-height: 1;
121
121
  }
122
122
 
123
+ /* Title text reads the AA-strong status colour (darker than the fill) so the coloured heading
124
+ * clears WCAG AA on the soft alert tint; the icon keeps the brighter role colour. */
123
125
  [data-slot="alert-title"][data-tone="destructive"] {
124
- color: hsl(var(--destructive));
126
+ color: hsl(var(--text-error));
125
127
  }
126
128
 
127
129
  [data-slot="alert-title"][data-tone="warning"] {
128
- color: hsl(var(--warning));
130
+ color: hsl(var(--text-warning));
129
131
  }
130
132
 
131
133
  [data-slot="alert-title"][data-tone="success"] {
132
- color: hsl(var(--success));
134
+ color: hsl(var(--text-success));
133
135
  }
134
136
 
135
137
  [data-slot="alert-title"][data-tone="info"] {
136
- color: hsl(var(--info));
138
+ color: hsl(var(--text-info));
137
139
  }
138
140
 
139
141
  [data-slot="alert-description"] {
@@ -168,7 +170,7 @@
168
170
 
169
171
  .ui-skeleton-block {
170
172
  border-radius: var(--skeleton-radius);
171
- background: hsl(var(--muted));
173
+ background: var(--skeleton-background);
172
174
  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
173
175
  }
174
176
 
@@ -29,8 +29,15 @@
29
29
  border-style: solid;
30
30
  border-color: hsl(var(--card-border));
31
31
  border-radius: var(--card-radius);
32
- background: hsl(var(--card-background));
32
+ /* Fill = base card colour with an opt-in role tint washed over it (--card-tint default
33
+ * transparent → byte-identical at rest). Tint layer sits ABOVE the base colour. */
34
+ background:
35
+ linear-gradient(var(--card-tint), var(--card-tint)),
36
+ hsl(var(--card-background));
33
37
  color: hsl(var(--card-foreground));
38
+ /* Resting elevation + opt-in brand glow — both quiet no-ops by default so a service theme can
39
+ * lift (--card-shadow) and/or glow (--card-glow) every card with no markup change. */
40
+ box-shadow: var(--card-shadow), var(--card-glow);
34
41
  overflow: hidden;
35
42
  }
36
43
 
@@ -384,6 +391,31 @@
384
391
  min-width: 0;
385
392
  }
386
393
 
394
+ /* Optional leading icon medallion — tinted box above the metric. Tone via tokens (default a
395
+ * soft brand-primary tint), so a service rethemes it with --primary or the icon tokens. */
396
+ [data-slot="stat-card-icon"] {
397
+ display: inline-flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ width: var(--stat-card-icon-size);
401
+ height: var(--stat-card-icon-size);
402
+ margin-bottom: var(--stat-card-gap);
403
+ border-radius: var(--stat-card-icon-radius);
404
+ background: var(--stat-card-icon-background);
405
+ color: var(--stat-card-icon-foreground);
406
+ }
407
+
408
+ [data-slot="stat-card-icon"] svg {
409
+ width: var(--stat-card-icon-glyph-size);
410
+ height: var(--stat-card-icon-glyph-size);
411
+ }
412
+
413
+ /* Inline layout: medallion floats to the trailing edge so label/value keep the start column. */
414
+ [data-slot="card"][data-stat-layout="inline"] [data-slot="stat-card-icon"] {
415
+ margin-bottom: 0;
416
+ order: 1;
417
+ }
418
+
387
419
  [data-slot="stat-card-label"] {
388
420
  display: flex;
389
421
  align-items: center;
@@ -50,13 +50,13 @@
50
50
  }
51
51
 
52
52
  .ui-toggle[data-state="on"] {
53
- background: hsl(var(--primary));
53
+ background: var(--toggle-on-background);
54
54
  color: hsl(var(--primary-foreground));
55
55
  }
56
56
 
57
57
  .ui-toggle:focus-visible {
58
58
  outline: none;
59
- box-shadow: 0 0 0 3px hsl(var(--ring) / 0.35);
59
+ box-shadow: 0 0 0 3px hsl(var(--focus-ring-color) / 0.35);
60
60
  }
61
61
 
62
62
  .ui-toggle:disabled,
@@ -85,7 +85,7 @@
85
85
 
86
86
  .ui-button:focus-visible {
87
87
  outline: none;
88
- box-shadow: 0 0 0 var(--control-focus-ring-width) hsl(var(--ring));
88
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color));
89
89
  }
90
90
 
91
91
  .ui-button:disabled {
@@ -102,7 +102,9 @@
102
102
  .ui-button--default {
103
103
  background: hsl(var(--primary));
104
104
  color: hsl(var(--primary-foreground));
105
- box-shadow: var(--shadow-sm);
105
+ /* Brand glow layers on top of the resting shadow; --shadow-glow is invisible by default
106
+ * (quiet) so a service opts the CTA halo in by setting the token alone. */
107
+ box-shadow: var(--shadow-sm), var(--shadow-glow);
106
108
  }
107
109
 
108
110
  .ui-button--default:hover {
@@ -119,9 +121,17 @@
119
121
  background: hsl(var(--destructive) / 0.9);
120
122
  }
121
123
 
124
+ /* Outline + ghost set their text colour EXPLICITLY (read --foreground) — never rely on inherited
125
+ * body colour. Inheriting breaks in a scoped dark region (e.g. an on-navy hero): the body's
126
+ * computed dark colour would carry in and the label goes near-invisible on the dark fill. */
122
127
  .ui-button--outline {
123
128
  border: 1px solid hsl(var(--input));
124
129
  background: hsl(var(--background));
130
+ color: hsl(var(--foreground));
131
+ }
132
+
133
+ .ui-button--ghost {
134
+ color: hsl(var(--foreground));
125
135
  }
126
136
 
127
137
  .ui-button--outline:hover,
@@ -277,7 +287,7 @@
277
287
  .ui-slider-thumb:focus-visible,
278
288
  .ui-switch:focus-visible {
279
289
  outline: none;
280
- box-shadow: 0 0 0 var(--control-focus-ring-width) hsl(var(--ring));
290
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color));
281
291
  }
282
292
 
283
293
  .ui-checkbox:disabled,
@@ -289,7 +299,7 @@
289
299
  }
290
300
 
291
301
  .ui-checkbox[data-state="checked"] {
292
- background: hsl(var(--primary));
302
+ background: var(--checkbox-checked-background);
293
303
  color: hsl(var(--primary-foreground));
294
304
  }
295
305
 
@@ -326,7 +336,7 @@
326
336
  }
327
337
 
328
338
  .ui-switch[data-state="checked"] {
329
- background: hsl(var(--primary));
339
+ background: var(--switch-checked-background);
330
340
  }
331
341
 
332
342
  .ui-switch[data-state="unchecked"] {
@@ -364,13 +374,13 @@
364
374
  flex-grow: 1;
365
375
  overflow: hidden;
366
376
  border-radius: var(--radius-pill);
367
- background: hsl(var(--primary) / 0.2);
377
+ background: var(--slider-track-background);
368
378
  }
369
379
 
370
380
  .ui-slider-range {
371
381
  position: absolute;
372
382
  height: 100%;
373
- background: hsl(var(--primary));
383
+ background: var(--slider-range-background);
374
384
  }
375
385
 
376
386
  .ui-slider-thumb {
@@ -646,13 +656,13 @@
646
656
  }
647
657
  .ui-otp-slot[data-active="true"] {
648
658
  z-index: 1;
649
- box-shadow: 0 0 0 2px hsl(var(--ring));
659
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color));
650
660
  }
651
661
  .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot {
652
662
  border-color: hsl(var(--destructive));
653
663
  }
654
664
  .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot[data-active="true"] {
655
- box-shadow: 0 0 0 2px hsl(var(--destructive));
665
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
656
666
  }
657
667
  .ui-otp-container:has(.ui-otp-input:disabled) .ui-otp-slot {
658
668
  cursor: not-allowed;
@@ -717,7 +727,7 @@
717
727
  fill: currentColor;
718
728
  }
719
729
  .ui-rating-star:focus-visible {
720
- outline: 2px solid hsl(var(--ring));
730
+ outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color));
721
731
  outline-offset: 2px;
722
732
  border-radius: var(--radius-sm);
723
733
  }
@@ -735,8 +745,8 @@
735
745
  background: hsl(var(--background));
736
746
  }
737
747
  .ui-tag-input:focus-within {
738
- box-shadow: 0 0 0 2px hsl(var(--ring));
739
- border-color: hsl(var(--ring));
748
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color));
749
+ border-color: hsl(var(--focus-ring-color));
740
750
  }
741
751
  .ui-tag-input-disabled {
742
752
  opacity: 0.6;
@@ -9,7 +9,10 @@
9
9
  justify-content: center;
10
10
  overflow: hidden;
11
11
  border-radius: var(--radius-pill);
12
- background: hsl(var(--muted));
12
+ /* Tint washes over the role background (default transparent = no-op, rule #44). */
13
+ background:
14
+ linear-gradient(var(--avatar-tint), var(--avatar-tint)),
15
+ var(--avatar-background);
13
16
  color: hsl(var(--muted-foreground));
14
17
  font-size: var(--font-size-base);
15
18
  font-weight: var(--font-weight-medium);
@@ -27,7 +30,7 @@
27
30
  height: 100%;
28
31
  align-items: center;
29
32
  justify-content: center;
30
- background: hsl(var(--muted));
33
+ background: var(--avatar-background);
31
34
  }
32
35
 
33
36
  .ui-progress {
@@ -40,13 +43,13 @@
40
43
  height: 0.5rem;
41
44
  overflow: hidden;
42
45
  border-radius: var(--radius-pill);
43
- background: hsl(var(--secondary));
46
+ background: var(--progress-track-background);
44
47
  }
45
48
 
46
49
  .ui-progress-bar {
47
50
  height: 100%;
48
51
  border-radius: inherit;
49
- background: hsl(var(--success));
52
+ background: var(--progress-fill-background);
50
53
  }
51
54
 
52
55
  .ui-progress[data-tone="warning"] .ui-progress-bar {
@@ -75,8 +78,8 @@
75
78
  }
76
79
 
77
80
  .ui-tree-item[data-active="true"] {
78
- border-color: hsl(var(--primary) / 0.3);
79
- background: hsl(var(--primary) / 0.05);
81
+ border-color: var(--tree-item-active-border);
82
+ background: var(--tree-item-active-background);
80
83
  }
81
84
 
82
85
  .ui-tree-item[data-depth="1"] {
@@ -151,7 +154,7 @@
151
154
  place-items: center;
152
155
  border: 1px solid transparent;
153
156
  border-radius: var(--radius-pill);
154
- background: hsl(var(--success));
157
+ background: var(--timeline-dot-done-background);
155
158
  color: hsl(var(--success-foreground));
156
159
  font-size: var(--timeline-note-font-size);
157
160
  font-weight: var(--font-weight-medium);
@@ -160,14 +163,14 @@
160
163
 
161
164
  /* done → success fill. */
162
165
  .ui-timeline-dot[data-status="done"] {
163
- background: hsl(var(--success));
166
+ background: var(--timeline-dot-done-background);
164
167
  color: hsl(var(--success-foreground));
165
168
  }
166
169
 
167
170
  /* current → primary fill + ring (also the legacy data-current shorthand). */
168
171
  .ui-timeline-dot[data-status="current"],
169
172
  .ui-timeline-dot[data-current="true"] {
170
- background: hsl(var(--primary));
173
+ background: var(--timeline-dot-current-background);
171
174
  color: hsl(var(--primary-foreground));
172
175
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.18);
173
176
  }
@@ -202,7 +205,7 @@
202
205
 
203
206
  /* The segment below a done/current step reads as travelled (completed). */
204
207
  .ui-timeline-line[data-completed="true"] {
205
- background: hsl(var(--primary));
208
+ background: var(--timeline-line-completed-background);
206
209
  }
207
210
 
208
211
  .ui-timeline-head {
@@ -256,7 +259,7 @@
256
259
  text-decoration: underline;
257
260
  }
258
261
  .ui-accordion-trigger:focus-visible {
259
- outline: 2px solid hsl(var(--ring));
262
+ outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color));
260
263
  outline-offset: 2px;
261
264
  border-radius: var(--radius-sm);
262
265
  }
@@ -383,7 +386,7 @@
383
386
  }
384
387
 
385
388
  .ui-carousel-dot:focus-visible {
386
- outline: 2px solid hsl(var(--ring));
389
+ outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color));
387
390
  outline-offset: 2px;
388
391
  }
389
392
 
@@ -83,8 +83,8 @@
83
83
  }
84
84
 
85
85
  .ui-time-input:focus-visible {
86
- border-color: hsl(var(--ring));
87
- box-shadow: 0 0 0 3px hsl(var(--ring) / 0.3);
86
+ border-color: hsl(var(--focus-ring-color));
87
+ box-shadow: 0 0 0 3px hsl(var(--focus-ring-color) / 0.3);
88
88
  }
89
89
 
90
90
  .ui-time-input[aria-invalid="true"] {
@@ -21,11 +21,15 @@
21
21
  border-top: 1px solid hsl(var(--border));
22
22
  }
23
23
 
24
+ .ui-dialog-overlay,
25
+ [data-slot="sheet-overlay"] {
26
+ background-color: var(--overlay-background);
27
+ }
28
+
24
29
  .ui-dialog-overlay {
25
30
  position: fixed;
26
31
  inset: 0;
27
32
  z-index: 50;
28
- background-color: rgb(0 0 0 / 0.5);
29
33
  }
30
34
 
31
35
  [data-slot="dialog-content"] {
@@ -43,7 +47,7 @@
43
47
  padding: var(--dialog-space-inset);
44
48
  /* clip so the full-bleed header/footer bands round with the dialog corners (Ant Modal). */
45
49
  overflow: hidden;
46
- box-shadow: var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1));
50
+ box-shadow: var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1)), var(--dialog-content-glow);
47
51
  }
48
52
 
49
53
  @media (min-width: 640px) {
@@ -41,6 +41,8 @@
41
41
  * `var(--radius-2xl)` directly; without a generated utility Tailwind drops the unused var). */
42
42
  @source inline("rounded-{xs,xl,2xl}");
43
43
  @source inline("text-2xs");
44
+ @source inline("text-{3xl,4xl,5xl}");
45
+ @source inline("text-{success,warning,info,error}-strong");
44
46
  /* `.js` matters: the published package ships only compiled JS under dist (no
45
47
  * .tsx sources), and this file is copied verbatim into dist/styles — without
46
48
  * it Tailwind finds no component classes in consumers (unstyled popovers and
@@ -64,7 +66,14 @@
64
66
  @import "./navigation-layout.css";
65
67
  @import "./chart-layout.css";
66
68
 
67
- @theme {
69
+ /* `inline` (NOT a bare @theme): Tailwind inlines each expression — e.g. `hsl(var(--primary))` —
70
+ * directly into every utility (`bg-primary` becomes `background-color: hsl(var(--primary))`) instead
71
+ * of emitting `var(--color-primary)` computed once at :root. This is THE enabler for scoped /
72
+ * multi-tenant theming: a `[data-tenant]{ --primary: … }` override now re-resolves at the element,
73
+ * so colors and radius propagate into a subtree (a plain @theme froze them at :root, so scoped
74
+ * overrides never reached the color utilities). Components read raw tokens like `hsl(var(--primary))`
75
+ * directly in their own CSS, so they re-theme the same way. */
76
+ @theme inline {
68
77
  --color-background: hsl(var(--background));
69
78
  --color-foreground: hsl(var(--foreground));
70
79
  --color-card: hsl(var(--card));
@@ -92,6 +101,12 @@
92
101
  --color-info-foreground: hsl(var(--info-foreground));
93
102
  --color-attention: hsl(var(--attention));
94
103
  --color-attention-foreground: hsl(var(--attention-foreground));
104
+ /* AA-strong status TEXT colours (text-{success,warning,info,error}-strong) — darker than the
105
+ * fills so small coloured labels clear WCAG AA on white; flip light on the dark theme. */
106
+ --color-success-strong: hsl(var(--text-success));
107
+ --color-warning-strong: hsl(var(--text-warning));
108
+ --color-info-strong: hsl(var(--text-info));
109
+ --color-error-strong: hsl(var(--text-error));
95
110
  --color-chart-1: var(--chart-1);
96
111
  --color-chart-2: var(--chart-2);
97
112
  --color-chart-3: var(--chart-3);
@@ -145,6 +160,10 @@
145
160
  --text-lg: var(--font-size-lg);
146
161
  --text-xl: var(--font-size-xl);
147
162
  --text-2xl: var(--font-size-2xl);
163
+ /* Opt-in DISPLAY ramp (marketing/hero) — wired to the display tokens, unused by the admin scale. */
164
+ --text-3xl: var(--font-size-3xl);
165
+ --text-4xl: var(--font-size-4xl);
166
+ --text-5xl: var(--font-size-5xl);
148
167
  }
149
168
 
150
169
  @layer base {
@@ -172,7 +191,9 @@
172
191
  body {
173
192
  background-color: hsl(var(--background));
174
193
  color: hsl(var(--foreground));
175
- font-family: var(--font-family-sans);
194
+ /* Body reads --font-family-body (defaults to --font-family-sans → unchanged); a service that
195
+ * wants a dual-font brand (display + body face) overrides --font-family-body alone. */
196
+ font-family: var(--font-family-body);
176
197
  font-size: var(--font-size-base);
177
198
  line-height: var(--line-height-body);
178
199
  font-feature-settings:
@@ -186,7 +207,13 @@
186
207
 
187
208
  /* Semantic heading scale — SEO + accessibility: real <h1>–<h6> elements carry
188
209
  * the document outline (one <h1> per page, levels never skipped). Visual size
189
- * follows the dxs-kintai scale; component titles override via their own class. */
210
+ * follows the dxs-kintai scale; component titles override via their own class.
211
+ * Headings read --font-family-display (defaults to --font-family-sans → unchanged) for dual-font
212
+ * brands (display face on headings, body face on prose). */
213
+ :where(h1, h2, h3, h4, h5, h6) {
214
+ font-family: var(--font-family-display);
215
+ }
216
+
190
217
  h1 {
191
218
  font-size: var(--heading-h1);
192
219
  font-weight: var(--font-weight-medium);
@@ -345,6 +345,9 @@
345
345
  gap: var(--space-stack-sm);
346
346
  padding-bottom: var(--page-header-pad-bottom);
347
347
  border-bottom: var(--page-header-divider);
348
+ /* Hero banner hook — invisible by default (--gradient-hero: none); a service paints a hero
349
+ * gradient across the page header (full-bleed) by configuring the token alone. */
350
+ background-image: var(--gradient-hero);
348
351
  }
349
352
 
350
353
  .ui-page-header-row {
@@ -8,7 +8,7 @@
8
8
  background: hsl(var(--popover));
9
9
  color: hsl(var(--popover-foreground));
10
10
  padding: var(--space-1);
11
- box-shadow: var(--shadow-md);
11
+ box-shadow: var(--shadow-md), var(--shadow-glow);
12
12
  }
13
13
 
14
14
  .ui-context-menu-item,
@@ -55,8 +55,8 @@
55
55
  .ui-menubar-sub-trigger:hover,
56
56
  .ui-navigation-menu-link:hover,
57
57
  .ui-navigation-menu-item:hover {
58
- background: hsl(var(--accent));
59
- color: hsl(var(--accent-foreground));
58
+ background: var(--menubar-item-hover-background);
59
+ color: var(--menubar-item-hover-foreground);
60
60
  }
61
61
 
62
62
  .ui-context-menu-item[data-state="open"],
@@ -68,8 +68,8 @@
68
68
  .ui-menubar-sub-trigger[data-state="open"],
69
69
  .ui-navigation-menu-link[data-highlighted],
70
70
  .ui-navigation-menu-item[data-focus-visible] {
71
- background: hsl(var(--accent));
72
- color: hsl(var(--accent-foreground));
71
+ background: var(--menubar-item-hover-background);
72
+ color: var(--menubar-item-hover-foreground);
73
73
  }
74
74
 
75
75
  .ui-context-menu-item[data-variant="destructive"],
@@ -146,7 +146,7 @@
146
146
  background: hsl(var(--popover));
147
147
  color: hsl(var(--popover-foreground));
148
148
  padding: var(--space-1);
149
- box-shadow: var(--shadow-md);
149
+ box-shadow: var(--shadow-md), var(--shadow-glow);
150
150
  }
151
151
 
152
152
  .ui-context-menu-sub-trigger-icon,
@@ -179,13 +179,13 @@
179
179
  background: hsl(var(--popover));
180
180
  color: hsl(var(--popover-foreground));
181
181
  padding: var(--space-1);
182
- box-shadow: var(--shadow-md);
182
+ box-shadow: var(--shadow-md), var(--shadow-glow);
183
183
  }
184
184
 
185
185
  .ui-menubar-trigger[data-state="open"],
186
186
  .ui-menubar-sub-trigger[data-state="open"] {
187
- background: hsl(var(--accent));
188
- color: hsl(var(--accent-foreground));
187
+ background: var(--menubar-item-hover-background);
188
+ color: var(--menubar-item-hover-foreground);
189
189
  }
190
190
 
191
191
  .ui-navigation-menu {
@@ -233,7 +233,7 @@
233
233
  background: hsl(var(--popover));
234
234
  color: hsl(var(--popover-foreground));
235
235
  padding: var(--space-1);
236
- box-shadow: var(--shadow-md);
236
+ box-shadow: var(--shadow-md), var(--shadow-glow);
237
237
  }
238
238
 
239
239
  .ui-navigation-menu-link {
@@ -30,6 +30,8 @@
30
30
  overflow: hidden;
31
31
  border-inline-end: 1px solid hsl(var(--border));
32
32
  background: hsl(var(--card));
33
+ /* Opt-in brand-chrome wash — invisible by default (--sidebar-gradient: none). */
34
+ background-image: var(--sidebar-gradient);
33
35
  color: hsl(var(--foreground));
34
36
  }
35
37
 
@@ -45,6 +47,8 @@
45
47
  padding-inline: var(--space-4);
46
48
  border-bottom: 1px solid hsl(var(--border));
47
49
  background: hsl(var(--background) / 0.92);
50
+ /* Opt-in brand-chrome wash — invisible by default (--topbar-gradient: none). */
51
+ background-image: var(--topbar-gradient);
48
52
  backdrop-filter: blur(12px);
49
53
  }
50
54
 
@@ -52,6 +56,9 @@
52
56
  grid-area: main;
53
57
  min-width: 0;
54
58
  overflow: auto;
59
+ /* Ambient brand glow hook — invisible by default (--gradient-glow: none); a service paints a
60
+ * radial/linear brand wash behind the whole content area by configuring the token alone. */
61
+ background-image: var(--gradient-glow);
55
62
  /* Sole scroll/clip boundary for the page area. `overflow:auto` alone does NOT reliably
56
63
  * establish a paint-containment boundary in Chrome: tall nested scrollers (a two-pane
57
64
  * `grid` of long lists, a nested `overflow-y:auto` region) — or any absolutely-positioned
@@ -254,13 +261,17 @@
254
261
  text-align: start;
255
262
  }
256
263
 
257
- .sb-nav-item:hover,
258
- .sb-nav-item[data-active="true"] {
264
+ .sb-nav-item:hover {
259
265
  background: hsl(var(--accent));
260
266
  color: hsl(var(--foreground));
261
267
  }
262
268
 
269
+ /* Active item reads its own bg/fg tokens (defaults = the hover look) so a service can give the
270
+ * selected nav row a brand treatment — e.g. a gold tint + gold text on a navy sidebar — without
271
+ * forking CSS. Defaults are byte-identical to the hover state. */
263
272
  .sb-nav-item[data-active="true"] {
273
+ background: var(--sidebar-item-active-background);
274
+ color: var(--sidebar-item-active-foreground);
264
275
  font-weight: var(--font-weight-medium);
265
276
  }
266
277
 
@@ -351,12 +362,12 @@
351
362
 
352
363
  .sb-nav-item--sub[data-active="true"] {
353
364
  background: transparent;
354
- color: hsl(var(--primary));
365
+ color: var(--sidebar-item-active-color);
355
366
  font-weight: 500;
356
367
  }
357
368
 
358
369
  .sb-nav-item--sub[data-active="true"]::before {
359
- background: hsl(var(--primary));
370
+ background: var(--sidebar-item-active-tint);
360
371
  opacity: 1;
361
372
  }
362
373
 
@@ -406,7 +417,7 @@
406
417
  }
407
418
 
408
419
  .sb-user:focus-visible {
409
- box-shadow: 0 0 0 var(--control-focus-ring-width) hsl(var(--ring) / 0.45);
420
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color) / 0.45);
410
421
  }
411
422
 
412
423
  .sb-user-avatar {
@@ -649,7 +660,7 @@
649
660
  }
650
661
 
651
662
  .tb-icon-btn:focus-visible {
652
- box-shadow: 0 0 0 var(--control-focus-ring-width) hsl(var(--ring) / 0.45);
663
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color) / 0.45);
653
664
  }
654
665
 
655
666
  .tb-bell {
@@ -8,10 +8,10 @@
8
8
  padding: var(--table-cell-padding-y) var(--table-cell-space-x);
9
9
  text-align: start;
10
10
  vertical-align: middle;
11
- background: hsl(var(--secondary));
11
+ background: var(--table-header-background);
12
12
  font-size: var(--table-head-font-size);
13
13
  font-weight: var(--font-weight-medium);
14
- color: hsl(var(--muted-foreground));
14
+ color: var(--table-header-foreground);
15
15
  white-space: nowrap;
16
16
  }
17
17
 
@@ -97,7 +97,7 @@
97
97
  /* Zebra striping (opt-in via the `striped` prop). Selected rows keep their
98
98
  * own emphasis, so they are excluded from the stripe. */
99
99
  .ui-data-table-surface[data-striped] tbody tr:nth-child(even):not([data-state="selected"]) {
100
- background-color: hsl(var(--muted) / 0.4);
100
+ background-color: var(--table-row-striped-background);
101
101
  }
102
102
 
103
103
  /* Empty-state cell hosts the built-in EmptyState (which brings its own padding). */
@@ -195,10 +195,10 @@
195
195
  /* Mirror the row's hover / selected tint — a translucent muted wash layered
196
196
  * over the opaque base, so the pinned cell matches the rest of its row. */
197
197
  .ui-data-table-surface tbody tr:hover > .ui-data-table-pin-end {
198
- background-image: linear-gradient(hsl(var(--muted) / 0.5), hsl(var(--muted) / 0.5));
198
+ background-image: linear-gradient(var(--table-row-hover-background), var(--table-row-hover-background));
199
199
  }
200
200
 
201
201
  .ui-data-table-surface tbody tr[data-state="selected"] > .ui-data-table-pin-end {
202
- background-image: linear-gradient(hsl(var(--muted) / 0.3), hsl(var(--muted) / 0.3));
202
+ background-image: linear-gradient(var(--table-row-selected-background), var(--table-row-selected-background));
203
203
  }
204
204
  }
@@ -37,6 +37,13 @@
37
37
  --text-disabled: 40 6% 68%; /* disabled control text */
38
38
  --text-link: 204 100% 39%; /* inline link — defaults to brand blue */
39
39
  --text-brand: 204 100% 39%; /* brand-colored text / numerals */
40
+ /* Status TEXT colours — darker than the fill roles so small coloured text (a StatCard delta, an
41
+ * outline status badge label) clears WCAG AA 4.5:1 on white. The fill roles (--success/--warning/
42
+ * --info) stay the brighter wa-iro for badges/bars; only TEXT reads these via text-*-strong. */
43
+ --text-success: 152 84% 22%; /* deep 若竹 — AA green text on white */
44
+ --text-warning: 36 100% 28%; /* dark 山吹 amber — yellow can never pass as text */
45
+ --text-info: 221 70% 38%; /* deep 群青 — AA blue text on white */
46
+ --text-error: 357 75% 38%; /* deep 茜 — AA red text on white */
40
47
 
41
48
  /* Interaction ramp — hover / active steps (services override to their own brand ramp). */
42
49
  --primary-hover: 204 100% 33%;
@@ -93,6 +100,35 @@
93
100
  0 20px 25px -5px rgb(var(--shadow-color) / 0.1), 0 8px 10px -6px rgb(var(--shadow-color) / 0.1);
94
101
  --shadow-2xl: 0 25px 50px -12px rgb(var(--shadow-color) / 0.25);
95
102
 
103
+ /* Brand GLOW — an opt-in coloured halo for the primary CTA (and any surface that opts in).
104
+ * Quiet by default (cardinal rule #44): a zero-size transparent shadow, which is invalid as a
105
+ * bare `none` inside a comma shadow list but valid as `0 0 0 0 transparent`. A service turns it
106
+ * on by overriding the whole value once, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)`
107
+ * — the CTA then carries a brand glow with no component change. */
108
+ --shadow-glow: 0 0 0 0 transparent;
109
+
110
+ /* FOCUS RING — the two global knobs for the keyboard-focus affordance (WCAG 2.4.7 / 2.4.11).
111
+ * `--focus-ring-color` is the themeable hue (HSL components, defaults to --ring) and
112
+ * `--focus-ring-width` the standard thickness; override either ONCE — even scoped under a
113
+ * `[data-tenant]` — to retint or resize EVERY focus ring. Both are leaf tokens (a colour and a
114
+ * length, no nested var) so they re-resolve at the element that paints the ring; every
115
+ * :focus-visible rule reads them DIRECTLY as `0 0 0 var(--focus-ring-width)
116
+ * hsl(var(--focus-ring-color))`, never via an intermediate composite (a `:root`-declared
117
+ * composite that wraps these would freeze at :root and ignore a scoped override). */
118
+ --focus-ring-color: var(--ring);
119
+ --focus-ring-width: 2px;
120
+
121
+ /* GRADIENTS — opt-in decorative fills exposed at the token layer so a service can paint a hero
122
+ * banner, a brand wash, or a radial brand glow purely by configuring tokens (cardinal rule #45).
123
+ * All default to a transparent no-op so nothing renders until a theme opts in; a surface that
124
+ * supports a gradient reads one of these as its background-image. Examples a service might set:
125
+ * --gradient-brand: linear-gradient(135deg, hsl(var(--primary)), hsl(var(--accent)));
126
+ * --gradient-hero: linear-gradient(180deg, hsl(var(--secondary)), hsl(var(--background)));
127
+ * --gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent); */
128
+ --gradient-brand: none;
129
+ --gradient-hero: none;
130
+ --gradient-glow: none;
131
+
96
132
  /* Sans stack — script-aware per the international convention for mixed
97
133
  * Latin/CJK UI: native Latin system fonts FIRST (clean, familiar English &
98
134
  * DEFAULT face is the bundled "Noto Sans JP" (Japanese + clean Latin), with system sans as
@@ -103,6 +139,12 @@
103
139
  "Noto Sans JP", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
104
140
  system-ui, "Hiragino Sans", sans-serif;
105
141
  --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
142
+ /* Dual-font split (opt-in) — many brand designs pair a DISPLAY face for headings with a separate
143
+ * BODY face (e.g. Source Sans 3 display + Inter body). Both default to --font-family-sans, so a
144
+ * single-font system is unchanged; a service overrides one or both. Headings read
145
+ * --font-family-display, body reads --font-family-body (wired in styles/index.css @layer base). */
146
+ --font-family-display: var(--font-family-sans);
147
+ --font-family-body: var(--font-family-sans);
106
148
  /* ── Type scale — GOLDEN-RATIO modular scale (Ant/Tailwind model). ONE base + ONE ratio; every
107
149
  * step is base × ratioⁿ, so the whole scale stays in golden proportion. `--font-size-ratio` is
108
150
  * φ^¼ (the golden ratio's quarter-step ≈ 1.1227) — a TRUE golden ratio yet dense enough that
@@ -122,6 +164,19 @@
122
164
  --font-size-xl: calc(var(--font-size-lg) * var(--font-size-ratio)); /* ratio³ ≈ 19.8px */
123
165
  --font-size-2xl: calc(var(--font-size-xl) * var(--font-size-ratio)); /* ratio⁴ ≈ 22.3px */
124
166
 
167
+ /* DISPLAY type — OPT-IN large sizes for MARKETING / hero surfaces (landing pages, CTA banners).
168
+ * The enterprise heading scale stays small by design (h1 = 20px, 渋み restraint); these are a
169
+ * SEPARATE bolder ramp a marketing surface opts into via the text-3xl/-4xl/-5xl utilities (wired
170
+ * in styles/index.css @theme). A service rescales the whole display ramp from --font-size-display
171
+ * (its own base). Nothing in the admin system uses them, so defaults are unchanged. */
172
+ --font-size-display: 3.375rem; /* 54px — hero display (the single display knob) */
173
+ --font-size-display-ratio: 1.28; /* bolder step than the body φ^¼ — marketing wants more contrast */
174
+ --font-size-3xl: calc(
175
+ var(--font-size-display) / var(--font-size-display-ratio) / var(--font-size-display-ratio)
176
+ ); /* ≈ 33 → 28px feature heading */
177
+ --font-size-4xl: calc(var(--font-size-display) / var(--font-size-display-ratio)); /* ≈ 36px section */
178
+ --font-size-5xl: var(--font-size-display); /* 54px hero */
179
+
125
180
  /* Headings — also golden-derived. h1=ratio³, h2=ratio²; h3/h4 reuse body sizes (single source). */
126
181
  --heading-h1: calc(
127
182
  var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio)
@@ -139,6 +194,10 @@
139
194
  * so any remaining reference renders at 500. `bold` is the 700 emphasis weight. */
140
195
  --font-weight-semibold: 500;
141
196
  --font-weight-bold: 700;
197
+ /* Display/black weight — OPT-IN for marketing display type (hero/CTA). The enterprise system caps
198
+ * at 700 (簡素 restraint); this is only reached via the .font-display-weight utility / a service
199
+ * setting it on a display surface, so admin text is unchanged. */
200
+ --font-weight-black: 800;
142
201
  --line-height-tight: 1.25;
143
202
  --line-height-normal: 1.5;
144
203
  --line-height-body: 1.7;
@@ -285,6 +344,11 @@
285
344
  --text-disabled: 45 6% 40%;
286
345
  --text-link: 204 90% 66%;
287
346
  --text-brand: 204 90% 66%;
347
+ /* Status text on the dark spine reads LIGHT (AA on dark surfaces). */
348
+ --text-success: 146 55% 64%;
349
+ --text-warning: 44 95% 62%;
350
+ --text-info: 221 70% 72%;
351
+ --text-error: 357 75% 68%;
288
352
  --primary-hover: 204 90% 66%;
289
353
  --primary-active: 204 90% 72%;
290
354
  --secondary-hover: 45 6% 22%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "16.4.0",
3
+ "version": "16.5.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.29.1",
6
6
  "sideEffects": false,
@@ -275,7 +275,7 @@
275
275
  "test:coverage": "vitest run --coverage",
276
276
  "check:example-imports": "node scripts/check-example-imports.mjs",
277
277
  "verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:audit-sync && pnpm test",
278
- "verify:release": "pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm test",
278
+ "verify:release": "pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm test",
279
279
  "check:mcp-sync": "node scripts/check-mcp-sync.mjs",
280
280
  "check:mcp-orphans": "node scripts/check-mcp-orphans.mjs",
281
281
  "check:mcp-prop-sync": "node scripts/check-mcp-prop-sync.mjs",
@@ -297,6 +297,7 @@
297
297
  "dev": "node scripts/dev.mjs",
298
298
  "prepublishOnly": "pnpm run build",
299
299
  "check:audit-sync": "node scripts/check-audit-sync.mjs",
300
+ "check:contrast": "node scripts/check-contrast.mjs",
300
301
  "visual-audit": "node scripts/visual-audit.mjs",
301
302
  "postinstall": "node scripts/postinstall.mjs",
302
303
  "init-agent": "node scripts/init-agent-kit.mjs"
@@ -1,10 +0,0 @@
1
- /* Badge component tokens. */
2
-
3
- :root {
4
- --badge-space-gap: var(--space-inline-xs);
5
- --badge-space-x: var(--space-2);
6
- --badge-space-y: var(--space-1);
7
- /* Small-by-design (badge/pill/counter). A knob (rule #45) so a service can
8
- * re-tune badge text without touching the global --font-size-xs step. */
9
- --badge-font-size: var(--font-size-xs);
10
- }
@@ -1,51 +0,0 @@
1
- /* Card component tokens: card chrome derives from semantic layout tokens. */
2
-
3
- :root {
4
- /* Horizontal inset of every slot (header / content / footer) + the resting top/bottom
5
- * shell padding. This is the column the title, body and footer all align to. */
6
- --card-space-inset: var(--space-section-active);
7
- /* Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y. */
8
- --card-space-header-y: var(--space-stack-sm);
9
- /* Gap between the header and the body, and the body's own top padding — the breathing
10
- * room under a title before content begins. */
11
- --card-space-body-y: var(--space-section-active);
12
- /* Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y. */
13
- --card-space-footer-y: var(--space-stack-sm);
14
- /* DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider
15
- * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY
16
- * top+bottom — distinct from a plain header that flows into the body (top inset, no bottom).
17
- * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes
18
- * the band density here instead of forking per-slot CSS. */
19
- --card-space-divided-y: var(--card-space-header-y);
20
- /* Vertical gap between stacked items WITHIN a slot (e.g. title ↕ description in the header). */
21
- --card-space-gap: var(--space-stack-xs);
22
- --card-title-font-size: var(--font-size-base);
23
- --card-title-line-height: var(--line-height-tight);
24
- --card-title-font-weight: var(--font-weight-semibold);
25
- --card-description-font-size: var(--font-size-sm);
26
- --card-description-line-height: var(--line-height-normal);
27
- --card-background: var(--card);
28
- --card-border: var(--border);
29
- --card-header-background: var(--muted);
30
- --card-header-background-alpha: 0.55;
31
- /* Banded-header divider — tokenised (rule #44) so a service theme can make it
32
- * dashed / heavier / none without forking CSS. Pair with
33
- * --card-header-background-alpha: 0 for a quiet borderless-band header. */
34
- --card-header-border-bottom: 1px solid hsl(var(--card-border));
35
- --card-radius: var(--radius);
36
- /* Accent edge — width of the semantic leading-edge stripe (data-accent).
37
- * Tokenised (rule #44) so a service theme can re-tune it without forking CSS.
38
- * The slot padding compensation in card-layout.css subtracts the same token,
39
- * so content stays aligned on the shell whatever the rail width. */
40
- --card-accent-rail-width: 6px;
41
- --stat-card-label-font-size: var(--font-size-xs);
42
- --stat-card-label-font-weight: var(--font-weight-medium);
43
- --stat-card-label-letter-spacing: 0.04em;
44
- --stat-card-value-font-size: var(--font-size-2xl);
45
- --stat-card-value-line-height: 1.1;
46
- --stat-card-value-font-weight: var(--font-weight-semibold);
47
- --stat-card-hint-font-size: var(--font-size-xs);
48
- --stat-card-gap: var(--space-stack-xs);
49
- --stat-card-icon-size: 2.25rem;
50
- --stat-card-delta-font-size: var(--font-size-xs);
51
- }
@@ -1,82 +0,0 @@
1
- /* Control primitive tokens: heights, horizontal padding, adjacent control sizes. */
2
-
3
- :root {
4
- --control-height-compact: 1.75rem;
5
- --control-height-default: 2rem;
6
- --control-height-comfortable: 2.75rem;
7
- --control-padding-x-compact: var(--space-2);
8
- --control-padding-x-default: var(--space-3);
9
- --control-padding-x-comfortable: var(--space-4);
10
-
11
- --control-height: calc(var(--control-height-default) * var(--scaling));
12
- /* Adjacent control sizes, derived from the active --control-height. The ±step
13
- * is scaled too so the whole control ladder stays proportional under --scaling. */
14
- --control-height-sm: calc(var(--control-height) - calc(0.25rem * var(--scaling)));
15
- --control-height-lg: calc(var(--control-height) + calc(0.25rem * var(--scaling)));
16
- --control-height-xs: calc(var(--control-height) - calc(0.5rem * var(--scaling)));
17
- --control-padding-x: var(--control-padding-x-default);
18
- --control-gap: var(--space-inline-sm);
19
- --control-gap-sm: var(--space-inline-xs);
20
- --control-radius: var(--radius);
21
- --control-icon-size: calc(1rem * var(--scaling));
22
- --control-icon-size-sm: calc(0.875rem * var(--scaling));
23
- --control-focus-ring-width: 2px;
24
-
25
- --checkbox-size: calc(1rem * var(--scaling));
26
- --checkbox-size-compact: 0.875rem;
27
- --checkbox-size-comfortable: 1.125rem;
28
- --choice-gap: var(--space-inline-sm);
29
- --choice-group-gap-x: var(--space-6);
30
- --choice-group-gap-y: var(--space-3);
31
- --choice-description-gap: 0.125rem;
32
- --choice-control-offset: 0.125rem;
33
-
34
- --switch-width: calc(2.25rem * var(--scaling));
35
- --switch-width-compact: 2rem;
36
- --switch-width-comfortable: 2.5rem;
37
- --switch-height: calc(1.25rem * var(--scaling));
38
- --switch-height-compact: 1.125rem;
39
- --switch-height-comfortable: 1.375rem;
40
- --switch-thumb-size: calc(1rem * var(--scaling));
41
- --switch-thumb-size-compact: 0.875rem;
42
- --switch-thumb-size-comfortable: 1.125rem;
43
- --switch-thumb-translate: calc(1rem * var(--scaling));
44
- --switch-thumb-translate-compact: 0.875rem;
45
- --switch-thumb-translate-comfortable: 1.125rem;
46
-
47
- --slider-track-height: 0.375rem;
48
- --slider-thumb-size: 1rem;
49
-
50
- --color-picker-input-width: 6.5rem;
51
-
52
- --command-list-max-height: min(300px, 50vh);
53
- --command-input-padding-x: var(--space-3);
54
- --command-group-padding: var(--space-1);
55
- --command-item-padding-y: var(--space-2);
56
- --command-item-padding-x: var(--space-2);
57
- --search-input-edge-inset: var(--space-3);
58
- --search-input-start-padding: calc(
59
- var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap)
60
- );
61
- --search-input-end-padding: calc(
62
- var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap)
63
- );
64
-
65
- --choice-description-font-size: var(--font-size-xs);
66
- --color-picker-hex-font-size: var(--font-size-xs);
67
- --command-group-heading-font-size: var(--font-size-xs);
68
- --search-input-label-font-size: var(--font-size-xs);
69
- --tag-input-chip-font-size: var(--font-size-xs);
70
- --toggle-sm-font-size: var(--font-size-xs);
71
- --button-sm-font-size: var(--font-size-xs);
72
- }
73
-
74
- /* Rule #24 — on touch devices (coarse pointer) interactive controls keep a ≥44px tap target
75
- * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height
76
- * resolves through these via var(), so inputs/buttons/selects/table rows all bump together. */
77
- @media (pointer: coarse) {
78
- :root {
79
- --control-height-compact: 2.75rem;
80
- --control-height-default: 2.75rem;
81
- }
82
- }
@@ -1,8 +0,0 @@
1
- /* Data-display component tokens — small-by-design text knobs (rule #45/#46). */
2
-
3
- :root {
4
- --progress-label-font-size: var(--font-size-xs);
5
- --tree-item-title-font-size: var(--font-size-xs);
6
- --tree-item-description-font-size: var(--font-size-xs);
7
- --timeline-note-font-size: var(--font-size-xs);
8
- }
@@ -1,6 +0,0 @@
1
- /* Data-entry component tokens — small-by-design text knobs (rule #45/#46). */
2
-
3
- :root {
4
- --password-strength-score-font-size: var(--font-size-xs);
5
- --password-strength-checklist-font-size: var(--font-size-xs);
6
- }
@@ -1,9 +0,0 @@
1
- /* Descriptions component tokens. */
2
-
3
- :root {
4
- /* Width of the label column when <Descriptions layout="horizontal">. Labels align to this
5
- * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>).
6
- * A rem value gives a fixed aligned column; set `max-content` to size each label to its text.
7
- * (rule #44/#45 — a service theme tunes it here instead of forking CSS.) */
8
- --descriptions-label-width: 8rem;
9
- }
@@ -1,25 +0,0 @@
1
- /* Feedback primitive tokens: dialog, alert, empty state. */
2
-
3
- :root {
4
- /* Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the
5
- * whole system, or --dialog-space-x/-y for dialogs only). */
6
- --dialog-space-x: var(--space-chrome-x);
7
- --dialog-space-y: var(--space-chrome-y);
8
- --dialog-space-inset: var(--dialog-space-y) var(--dialog-space-x);
9
- --dialog-space-gap: var(--space-stack-md);
10
- --dialog-close-space-offset: var(--space-4);
11
- --alert-space-inset: var(--space-section-active);
12
- --alert-space-gap: var(--space-inline-md);
13
- --alert-inner-space-gap: var(--space-stack-sm);
14
- --alert-dismiss-space-offset: var(--space-3);
15
- /* Soft (subtle) semantic tint ratios — themeable so a service can hit its exact spec
16
- * (a brand's success-bg/-border are often more present than the faint 5%/30% default). */
17
- --alert-bg-alpha: 0.05;
18
- --alert-border-alpha: 0.3;
19
- --empty-state-space-y: var(--space-10);
20
- --empty-state-space-x: var(--space-6);
21
- --skeleton-row-gap: var(--space-stack-sm);
22
- --skeleton-cell-gap: var(--space-inline-lg);
23
- --skeleton-card-inset: var(--space-section-active);
24
- --skeleton-radius: var(--radius);
25
- }
@@ -1,11 +0,0 @@
1
- /* Form layout tokens: horizontal-layout label column geometry. */
2
-
3
- :root {
4
- /* Width of the label column in horizontal/inline layout. A service theme sets
5
- * this once (e.g. 110px) to align every form to its design grid; the Form/
6
- * FormField `labelWidth` prop overrides per form/field. */
7
- --form-label-width: max-content;
8
-
9
- /* Column gap between the label and its control in horizontal/inline layout. */
10
- --form-label-gap: var(--space-4); /* 16px */
11
- }
@@ -1,9 +0,0 @@
1
- /* ListRow component tokens — a single-line entity row for short lists inside a Card
2
- * (sessions / API tokens / linked accounts / passkeys …). Sits in a flush CardContent;
3
- * rows separate with a quiet divider (#44 — chrome defaults to the calm semantic border). */
4
- :root {
5
- --list-row-padding-y: var(--space-3);
6
- --list-row-padding-x: var(--space-4);
7
- --list-row-gap: var(--space-3);
8
- --list-row-border: 1px solid hsl(var(--border));
9
- }
@@ -1,15 +0,0 @@
1
- /* Navigation primitive tokens: pagination, filters, compact pickers. */
2
-
3
- :root {
4
- --pagination-gap: var(--space-inline-sm);
5
- --pagination-item-gap: var(--space-inline-xs);
6
- --pagination-size-width: 5.5rem;
7
- --pagination-total-font-size: var(--font-size-sm);
8
- --filter-bar-gap: var(--space-3);
9
- --filter-bar-padding-y: var(--space-2);
10
- --filter-label-font-size: var(--font-size-xs);
11
- --filter-picker-width-sm: 11rem;
12
- --filter-picker-width-md: 14rem;
13
- --breadcrumb-font-size: var(--font-size-xs);
14
- --menubar-shortcut-font-size: var(--font-size-xs);
15
- }
@@ -1,17 +0,0 @@
1
- /* Shell (sidebar / topbar / kbd) component tokens — small-by-design text
2
- * knobs (rule #45/#46). A service re-tunes chrome text without moving the
3
- * global scale. */
4
-
5
- :root {
6
- --sidebar-section-label-font-size: var(--font-size-2xs);
7
- --sidebar-product-tenant-font-size: var(--font-size-2xs);
8
- --sidebar-badge-font-size: var(--font-size-2xs);
9
- --sidebar-user-role-font-size: var(--font-size-2xs);
10
- --sidebar-nav-sub-font-size: var(--font-size-xs);
11
- --sidebar-flyout-title-font-size: var(--font-size-xs);
12
- --topbar-chip-icon-font-size: var(--font-size-2xs);
13
- --kbd-font-size: var(--font-size-2xs);
14
- --sidebar-logo-mark-font-size: var(--font-size-xs);
15
- --sidebar-avatar-font-size: var(--font-size-2xs);
16
- --sidebar-user-name-font-size: var(--font-size-xs);
17
- }
@@ -1,13 +0,0 @@
1
- /* Table component tokens: row height, cell padding. */
2
-
3
- :root {
4
- --table-row-height-compact: 1.75rem;
5
- --table-row-height-default: 2rem;
6
- --table-row-height-comfortable: 2.75rem;
7
- --table-row-height: calc(var(--table-row-height-default) * var(--scaling));
8
- --table-cell-padding-y: var(--space-2);
9
- --table-cell-space-x: var(--control-padding-x);
10
- --table-head-font-size: var(--font-size-xs);
11
- /* Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over. */
12
- --table-pin-shadow: -6px 0 6px -5px hsl(var(--foreground) / 0.12);
13
- }
@@ -1,49 +0,0 @@
1
- /* Layout primitive tokens: page, section, stack, inline. */
2
-
3
- :root {
4
- /* Semantic spacing — ONE coherent 4px grid (Tailwind/Material/Ant standard). All steps reference
5
- * the linear `--space-*` scale (NOT the golden φ scale — golden ratio is for modular SIZE scales
6
- * like type/radius, not the spacing grid; mixing the two left an incoherent density rhythm). The
7
- * φ tokens remain available as an optional golden-layout utility, just not the spacing baseline. */
8
- --space-page-x: var(--space-6); /* 24px — page gutter */
9
- --space-page-y: var(--space-6); /* 24px */
10
- --space-section: var(--space-4); /* 16px */
11
- --space-stack-xs: var(--space-1); /* 4px */
12
- --space-stack-sm: var(--space-2); /* 8px */
13
- --space-stack-md: var(--space-4); /* 16px */
14
- --space-stack-lg: var(--space-6); /* 24px */
15
- --space-stack-xl: var(--space-10); /* 40px */
16
- --space-inline-xs: var(--space-1); /* 4px */
17
- --space-inline-sm: var(--space-2); /* 8px */
18
- --space-inline-md: var(--space-3); /* 12px */
19
- --space-inline-lg: var(--space-4); /* 16px */
20
-
21
- --space-page-active-x: var(--space-page-x);
22
- --space-page-active-y: var(--space-page-y);
23
- --space-section-active: var(--space-section);
24
-
25
- /* Full-bleed rule under the PageContainer header. OFF by default — the title/body
26
- * gap already separates them; a divider is a service-theme opt-in:
27
- * `--page-header-divider: 1px solid hsl(var(--border));` */
28
- --page-header-divider: none;
29
-
30
- /* Bottom inset of the page header, sized so title→body distance (this pad + the
31
- * --space-section-active container gap) EQUALS the page's top padding — the title
32
- * band reads vertically balanced (24 above / 24 below by default), instead of the
33
- * old fixed 16px pad that left 24 above vs 32 below. */
34
- --page-header-pad-bottom: calc(var(--space-page-active-y) - var(--space-section-active));
35
-
36
- /* Section "chrome" padding — the SHARED inset for header / body / footer of every overlay-ish
37
- * surface (Dialog · Sheet · Drawer · Card). One global knob: a service sets these once instead of
38
- * per-component. Vertical is tightened (16 vs the old 24) and density-aware (see density.css);
39
- * horizontal stays 24 (Ant standard). Per-component tokens (--sheet-pad-*, --dialog-space-inset…)
40
- * default to these and only override when a surface genuinely needs to differ. */
41
- --space-chrome-x: var(--space-6); /* 24px — horizontal inset */
42
- --space-chrome-y: var(--space-4); /* 16px — vertical inset (header/footer/body) */
43
- --space-chrome-gap: var(--space-stack-sm); /* gap between header ↔ body ↔ footer */
44
-
45
- /* The ONE vertical gap between a field's LABEL and its CONTENT — same everywhere a label sits above
46
- * a control or a value (FormField, Descriptions, Form, any label-above stack). Single source so the
47
- * whole system stays in sync (gh feedback: Descriptions was ~0px while FormField was 8px). */
48
- --field-label-gap: var(--space-2); /* 8px */
49
- }