@godxjp/ui 16.3.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 (35) hide show
  1. package/dist/app/app-provider.js +1 -0
  2. package/dist/components/data-display/card.d.ts +5 -1
  3. package/dist/components/data-display/card.js +4 -2
  4. package/dist/components/feedback/sheet.js +3 -1
  5. package/dist/components/navigation/steps.js +2 -2
  6. package/dist/lib/control-styles.d.ts +7 -5
  7. package/dist/lib/control-styles.js +4 -4
  8. package/dist/props/registry.d.ts +1 -1
  9. package/dist/props/registry.js +1 -1
  10. package/dist/styles/alert-layout.css +7 -5
  11. package/dist/styles/card-layout.css +33 -1
  12. package/dist/styles/control.css +24 -14
  13. package/dist/styles/data-display-layout.css +15 -12
  14. package/dist/styles/data-entry-layout.css +2 -2
  15. package/dist/styles/dialog-layout.css +6 -2
  16. package/dist/styles/index.css +52 -7
  17. package/dist/styles/layout.css +3 -0
  18. package/dist/styles/navigation-layout.css +10 -10
  19. package/dist/styles/shell-layout.css +17 -6
  20. package/dist/styles/table-layout.css +5 -5
  21. package/dist/tokens/foundation.css +70 -8
  22. package/package.json +10 -8
  23. package/dist/tokens/components/badge.css +0 -10
  24. package/dist/tokens/components/card.css +0 -51
  25. package/dist/tokens/components/control.css +0 -82
  26. package/dist/tokens/components/data-display.css +0 -8
  27. package/dist/tokens/components/data-entry.css +0 -6
  28. package/dist/tokens/components/descriptions.css +0 -9
  29. package/dist/tokens/components/feedback.css +0 -25
  30. package/dist/tokens/components/form.css +0 -11
  31. package/dist/tokens/components/list-row.css +0 -9
  32. package/dist/tokens/components/navigation.css +0 -15
  33. package/dist/tokens/components/shell.css +0 -17
  34. package/dist/tokens/components/table.css +0 -13
  35. package/dist/tokens/semantic/layout.css +0 -49
@@ -275,6 +275,7 @@ function AppProvider({
275
275
  React.useEffect(() => {
276
276
  if (typeof document !== "undefined") {
277
277
  document.documentElement.dir = localeDirection(locale);
278
+ document.documentElement.lang = locale;
278
279
  }
279
280
  }, [locale]);
280
281
  React.useEffect(() => {
@@ -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) {
@@ -1,11 +1,29 @@
1
1
  @import "sonner/dist/styles.css";
2
2
  @import "react-day-picker/style.css";
3
- /* Default sans font — self-contained so consumers need no font config (subsetted: latin/vi/jp). */
4
- @import "@fontsource/m-plus-2/400.css";
5
- @import "@fontsource/m-plus-2/500.css";
6
- @import "@fontsource/m-plus-2/700.css";
3
+ /* Bundled sans fonts — self-contained so consumers need no font config. The browser only
4
+ * downloads the subset files matching the rendered text:
5
+ * · Noto Sans JP — the DEFAULT face (Japanese + Latin), used for all locales except `vi`.
6
+ * · Montserrat — swapped in for the Vietnamese locale (`<html lang="vi">`), incl. its
7
+ * `vietnamese` subset for full diacritics. See --font-family-sans + :lang(vi). */
8
+ @import "@fontsource/noto-sans-jp/400.css";
9
+ @import "@fontsource/noto-sans-jp/500.css";
10
+ @import "@fontsource/noto-sans-jp/700.css";
11
+ @import "@fontsource/montserrat/400.css";
12
+ @import "@fontsource/montserrat/500.css";
13
+ @import "@fontsource/montserrat/700.css";
7
14
  @import "tailwindcss";
8
15
 
16
+ /* Vietnamese locale → Montserrat (set by AppProvider via <html lang="vi">). Overrides the
17
+ * --font-family-sans token so every body/heading rule that reads it switches automatically. */
18
+ :root:lang(vi),
19
+ [lang="vi"] {
20
+ /* Montserrat for Latin/Vietnamese; Noto Sans JP kept as the tail so any 日本語 in a vi-locale
21
+ * screen still has the bundled JP face (Montserrat has no CJK glyphs). */
22
+ --font-family-sans:
23
+ "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
24
+ system-ui, "Noto Sans JP", sans-serif;
25
+ }
26
+
9
27
  /* Guarantee the full set of semantic color utilities ships in the compiled CSS
10
28
  * even when the library itself only references a few — consumers (and the token
11
29
  * docs) must be able to use bg/text/border for EVERY semantic role. Without this
@@ -23,6 +41,8 @@
23
41
  * `var(--radius-2xl)` directly; without a generated utility Tailwind drops the unused var). */
24
42
  @source inline("rounded-{xs,xl,2xl}");
25
43
  @source inline("text-2xs");
44
+ @source inline("text-{3xl,4xl,5xl}");
45
+ @source inline("text-{success,warning,info,error}-strong");
26
46
  /* `.js` matters: the published package ships only compiled JS under dist (no
27
47
  * .tsx sources), and this file is copied verbatim into dist/styles — without
28
48
  * it Tailwind finds no component classes in consumers (unstyled popovers and
@@ -46,7 +66,14 @@
46
66
  @import "./navigation-layout.css";
47
67
  @import "./chart-layout.css";
48
68
 
49
- @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 {
50
77
  --color-background: hsl(var(--background));
51
78
  --color-foreground: hsl(var(--foreground));
52
79
  --color-card: hsl(var(--card));
@@ -74,6 +101,12 @@
74
101
  --color-info-foreground: hsl(var(--info-foreground));
75
102
  --color-attention: hsl(var(--attention));
76
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));
77
110
  --color-chart-1: var(--chart-1);
78
111
  --color-chart-2: var(--chart-2);
79
112
  --color-chart-3: var(--chart-3);
@@ -127,6 +160,10 @@
127
160
  --text-lg: var(--font-size-lg);
128
161
  --text-xl: var(--font-size-xl);
129
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);
130
167
  }
131
168
 
132
169
  @layer base {
@@ -154,7 +191,9 @@
154
191
  body {
155
192
  background-color: hsl(var(--background));
156
193
  color: hsl(var(--foreground));
157
- 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);
158
197
  font-size: var(--font-size-base);
159
198
  line-height: var(--line-height-body);
160
199
  font-feature-settings:
@@ -168,7 +207,13 @@
168
207
 
169
208
  /* Semantic heading scale — SEO + accessibility: real <h1>–<h6> elements carry
170
209
  * the document outline (one <h1> per page, levels never skipped). Visual size
171
- * 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
+
172
217
  h1 {
173
218
  font-size: var(--heading-h1);
174
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 {