@godxjp/ui 16.9.4 → 17.0.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 (65) hide show
  1. package/README.md +30 -5
  2. package/dist/components/charts/chart-cartesian.js +1 -6
  3. package/dist/components/charts/pie-chart.js +1 -8
  4. package/dist/components/data-display/empty-state.d.ts +1 -1
  5. package/dist/components/data-display/empty-state.js +25 -7
  6. package/dist/components/data-display/progress.d.ts +9 -2
  7. package/dist/components/data-display/progress.js +6 -3
  8. package/dist/components/data-entry/form-field.js +13 -4
  9. package/dist/components/data-entry/search-select.d.ts +1 -1
  10. package/dist/components/data-entry/search-select.js +97 -19
  11. package/dist/components/data-entry/select.js +15 -4
  12. package/dist/components/feedback/alert.d.ts +11 -2
  13. package/dist/components/feedback/alert.js +51 -9
  14. package/dist/components/general/button.js +2 -0
  15. package/dist/components/general/index.d.ts +4 -0
  16. package/dist/components/general/index.js +4 -0
  17. package/dist/components/general/logo.d.ts +27 -0
  18. package/dist/components/general/logo.js +23 -0
  19. package/dist/components/general/reveal.d.ts +14 -0
  20. package/dist/components/general/reveal.js +21 -0
  21. package/dist/components/layout/auth-shell.d.ts +11 -0
  22. package/dist/components/layout/auth-shell.js +15 -0
  23. package/dist/components/layout/flex.js +1 -1
  24. package/dist/components/layout/index.d.ts +2 -0
  25. package/dist/components/layout/index.js +2 -0
  26. package/dist/components/layout/page-container.js +4 -4
  27. package/dist/components/navigation/app-setting-picker.js +19 -4
  28. package/dist/components/navigation/pagination.d.ts +1 -1
  29. package/dist/components/navigation/pagination.js +2 -1
  30. package/dist/components/query/data-state.d.ts +1 -1
  31. package/dist/components/query/data-state.js +27 -4
  32. package/dist/components/query/index.d.ts +2 -0
  33. package/dist/components/query/index.js +4 -1
  34. package/dist/i18n/messages/en.json +30 -2
  35. package/dist/i18n/messages/ja.json +30 -2
  36. package/dist/i18n/messages/vi.json +30 -2
  37. package/dist/lib/query-error.d.ts +17 -0
  38. package/dist/lib/query-error.js +58 -0
  39. package/dist/props/components/app.prop.d.ts +9 -1
  40. package/dist/props/components/data-display.prop.d.ts +15 -0
  41. package/dist/props/components/data-entry.prop.d.ts +8 -0
  42. package/dist/props/components/feedback.prop.d.ts +6 -0
  43. package/dist/props/components/general.prop.d.ts +23 -1
  44. package/dist/props/components/layout.prop.d.ts +17 -0
  45. package/dist/props/components/query.prop.d.ts +7 -1
  46. package/dist/props/registry.d.ts +31 -2
  47. package/dist/props/registry.js +44 -2
  48. package/dist/props/vocabulary/index.d.ts +1 -1
  49. package/dist/props/vocabulary/interaction.prop.d.ts +16 -0
  50. package/dist/styles/alert-layout.css +1 -2
  51. package/dist/styles/card-layout.css +6 -6
  52. package/dist/styles/data-display-layout.css +18 -0
  53. package/dist/styles/index.css +2 -0
  54. package/dist/styles/layout.css +28 -0
  55. package/dist/styles/logo-layout.css +41 -0
  56. package/dist/styles/motion.css +52 -0
  57. package/dist/styles/shell-layout.css +42 -0
  58. package/dist/theme/famgia.service.css +44 -0
  59. package/dist/tokens/base.css +1 -0
  60. package/dist/tokens/components/feedback.css +4 -0
  61. package/dist/tokens/components/logo.css +15 -0
  62. package/dist/tokens/components/shell.css +10 -0
  63. package/dist/tokens/foundation.css +8 -2
  64. package/package.json +7 -3
  65. package/scripts/visual-audit.mjs +293 -126
@@ -320,6 +320,8 @@ export type SearchSelectProp = {
320
320
  searchPlaceholder?: PlaceholderProp;
321
321
  emptyMessage?: EmptyMessageProp;
322
322
  loadingMessage?: string;
323
+ /** Message shown when an async `loadOptions` rejects — a distinct state from empty/loading. */
324
+ errorMessage?: string;
323
325
  clearLabel?: string;
324
326
  /** Show a "clear" row when a value is selected (default true). */
325
327
  clearable?: boolean;
@@ -328,6 +330,12 @@ export type SearchSelectProp = {
328
330
  name?: NameProp;
329
331
  id?: IdProp;
330
332
  className?: ClassNameProp;
333
+ "aria-label"?: string;
334
+ "aria-labelledby"?: string;
335
+ "aria-describedby"?: string;
336
+ "aria-errormessage"?: string;
337
+ "aria-invalid"?: boolean | "true" | "false";
338
+ "aria-required"?: boolean | "true" | "false";
331
339
  "data-testid"?: string;
332
340
  };
333
341
  /**
@@ -1,5 +1,6 @@
1
1
  /** Feedback component prop types — @see docs/COMPONENTS.md#feedback */
2
2
  import type * as React from "react";
3
+ import type { QueryErrorCategory } from "../../lib/query-error.js";
3
4
  import type { AlertVariantProp, CancelLabelProp, ChildrenProp, ClassNameProp, ConfirmLabelProp, ConfirmVariantProp, DescriptionProp, HandlerProp, IconProp, OpenProp, OnOpenChangeProp, PendingProp, ToneProp, TitleProp } from "../vocabulary/index.js";
4
5
  /** @see AlertDialog */
5
6
  export type AlertDialogProp = {
@@ -19,7 +20,12 @@ export type AlertDialogProp = {
19
20
  /** @see Alert */
20
21
  export type AlertQueryErrorProp = {
21
22
  error: unknown;
23
+ /** Override the auto-classified cause. When omitted, the error is classified via `classifyQueryError`. */
24
+ category?: QueryErrorCategory;
25
+ /** Retry affordance — only shown for causes where retrying is meaningful (transient/network/5xx). */
22
26
  onRetry?: HandlerProp;
27
+ /** Recovery for `auth` (401/expired token): renew the session or sign in again. Replaces Retry. */
28
+ onAuthAction?: HandlerProp;
23
29
  className?: ClassNameProp;
24
30
  };
25
31
  /** @see Alert */
@@ -1,6 +1,6 @@
1
1
  /** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
2
2
  import type * as React from "react";
3
- import type { AsChildProp, ButtonSizeProp, ButtonVariantProp, DisabledProp, FontWeightProp, HeadingLevelProp, OnClickProp, PendingProp, ShapeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
3
+ import type { AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
4
4
  /** @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`. */
5
5
  export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
6
6
  /** Render element. Default `span`. Covers inline/block text + description-list + code/caption. */
@@ -71,3 +71,25 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
71
71
  */
72
72
  showZero?: boolean;
73
73
  };
74
+ /**
75
+ * @see Reveal — entrance-motion primitive (staggered fade-up). Wraps content in a real element
76
+ * that animates in on mount reading the DS motion tokens (`--duration-slow`, `--ease-emphasized`,
77
+ * `--reveal-distance`), replacing hand-rolled `@keyframes` + `.app-reveal`/`.d1..d6` classes.
78
+ * Honours `prefers-reduced-motion` — the animation is dropped and content stays fully visible with
79
+ * no layout shift.
80
+ */
81
+ export type RevealProp = React.HTMLAttributes<HTMLDivElement> & {
82
+ /** Child content to reveal on enter. */
83
+ children?: ChildrenProp;
84
+ /**
85
+ * Stagger ordinal — an INDEX into the motion ladder (`0..6`), never a raw ms. Each step adds one
86
+ * `--reveal-stagger-step` of delay so sibling reveals cascade. Default `0` (enter immediately).
87
+ */
88
+ delay?: RevealDelayProp;
89
+ /**
90
+ * Merge the reveal behaviour onto the single child element (Radix `Slot`) instead of rendering a
91
+ * wrapper `<div>` — use when an extra box would break a grid/flex layout. Default `false`.
92
+ */
93
+ asChild?: AsChildProp;
94
+ className?: ClassNameProp;
95
+ };
@@ -67,6 +67,23 @@ export type AppShellProp = {
67
67
  footer?: ReactNode;
68
68
  sidebarCollapsed?: boolean;
69
69
  };
70
+ /**
71
+ * @see AuthShell — centred auth/login page shell (login · mfa · passkey · device · reset). A
72
+ * top brand bar, a centred `main` that holds the auth `Card`, and an optional footer, over a
73
+ * `min-h-dvh` surface. The shell scopes `--control-height` to the comfortable tier (44px, the WCAG
74
+ * touch floor) and bumps the auth heading size so forms read at the right density — replacing
75
+ * consumers' hand-rolled `.auth-shell-*` / `.ui-auth-scope` classes. Motion is delegated to
76
+ * `Reveal` (wrap the card) so `prefers-reduced-motion` is honoured at one place.
77
+ */
78
+ export type AuthShellProp = {
79
+ /** Centred content — typically a single auth `<Card>` with the form. */
80
+ children: ReactNode;
81
+ /** Brand bar slot pinned to the top (e.g. a `<Logo>` / product mark). */
82
+ brand?: ReactNode;
83
+ /** Footer slot pinned to the bottom (legal links, locale switch, support). */
84
+ footer?: ReactNode;
85
+ className?: ClassNameProp;
86
+ };
70
87
  /** @see Sidebar */
71
88
  export type SidebarProductProp = {
72
89
  name: string;
@@ -8,13 +8,19 @@ import type { ClassNameProp, HandlerProp } from "../vocabulary/index.js";
8
8
  export type DataStateProp<T> = {
9
9
  query: UseQueryResult<T>;
10
10
  skeleton: React.ReactNode;
11
+ /** Rendered when a query is disabled/unstarted (`fetchStatus: "idle"` with no data). */
12
+ prerequisite?: React.ReactNode;
11
13
  empty?: React.ReactNode;
12
14
  isEmpty?: (data: NonNullable<T>) => boolean;
13
15
  errorRenderer?: (error: unknown, retry: () => void) => React.ReactNode;
14
- /** Default error UI retry button. Default `true`. */
16
+ /** Force the Retry affordance even for non-transient causes. Retry is offered automatically for
17
+ * transient/network/5xx errors regardless of this flag; default `false` for all other causes. */
15
18
  showRetry?: boolean;
16
19
  /** Default `() => query.refetch()`. */
17
20
  onRetry?: HandlerProp;
21
+ /** Recovery for authentication errors (401 / expired token): renew the session or sign in again.
22
+ * When provided, a 401 renders this action instead of Retry. */
23
+ onAuthError?: HandlerProp;
18
24
  children: (data: NonNullable<T>) => React.ReactNode;
19
25
  };
20
26
  type MutationLike = Pick<UseMutationResult<unknown, unknown, unknown, unknown>, "isError" | "error" | "isPending">;
@@ -209,6 +209,11 @@ export declare const VOCABULARY_REGISTRY: {
209
209
  readonly category: "interaction";
210
210
  readonly description: "Badge visual variant (default | secondary | outline | dashed)";
211
211
  };
212
+ readonly AppSettingPickerAppearanceProp: {
213
+ readonly file: "vocabulary/interaction.prop.ts";
214
+ readonly category: "interaction";
215
+ readonly description: "AppSettingPicker trigger presentation: labeled (icon + value) | icon (square icon-only topbar trigger)";
216
+ };
212
217
  readonly ShapeProp: {
213
218
  readonly file: "vocabulary/interaction.prop.ts";
214
219
  readonly category: "interaction";
@@ -289,6 +294,11 @@ export declare const VOCABULARY_REGISTRY: {
289
294
  readonly category: "interaction";
290
295
  readonly description: "Active table sort";
291
296
  };
297
+ readonly RevealDelayProp: {
298
+ readonly file: "vocabulary/interaction.prop.ts";
299
+ readonly category: "interaction";
300
+ readonly description: "Reveal entrance-stagger ordinal (0..6) — an index into the motion ladder, never a raw ms";
301
+ };
292
302
  readonly BreadcrumbItemProp: {
293
303
  readonly file: "vocabulary/navigation.prop.ts";
294
304
  readonly category: "navigation";
@@ -380,7 +390,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
380
390
  readonly AppSettingPickerProp: {
381
391
  readonly group: "app";
382
392
  readonly file: "components/app.prop.ts";
383
- readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "DisabledProp", "IdProp", "ClassNameProp"];
393
+ readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "DisabledProp", "IdProp", "ClassNameProp", "AppSettingPickerAppearanceProp"];
384
394
  };
385
395
  readonly PageContainerProp: {
386
396
  readonly group: "layout";
@@ -438,6 +448,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
438
448
  readonly file: "components/layout.prop.ts";
439
449
  readonly vocabulary: readonly ["ChildrenProp"];
440
450
  };
451
+ readonly AuthShellProp: {
452
+ readonly group: "layout";
453
+ readonly file: "components/layout.prop.ts";
454
+ readonly vocabulary: readonly ["ChildrenProp", "ClassNameProp"];
455
+ };
441
456
  readonly SidebarProductProp: {
442
457
  readonly group: "layout";
443
458
  readonly file: "components/layout.prop.ts";
@@ -468,6 +483,15 @@ export declare const COMPONENT_PROP_REGISTRY: {
468
483
  readonly file: "components/general.prop.ts";
469
484
  readonly vocabulary: readonly ["ButtonVariantProp", "SizeProp", "ShapeProp", "AsChildProp", "DisabledProp", "OnClickProp", "PendingProp"];
470
485
  };
486
+ readonly RevealProp: {
487
+ readonly group: "general";
488
+ readonly file: "components/general.prop.ts";
489
+ readonly vocabulary: readonly ["ChildrenProp", "RevealDelayProp", "AsChildProp", "ClassNameProp", {
490
+ readonly field: "delay";
491
+ readonly local: true;
492
+ readonly reason: "Reveal stagger ordinal — RevealDelayProp vocabulary.";
493
+ }];
494
+ };
471
495
  readonly TextProp: {
472
496
  readonly group: "general";
473
497
  readonly file: "components/general.prop.ts";
@@ -663,7 +687,12 @@ export declare const COMPONENT_PROP_REGISTRY: {
663
687
  readonly EmptyStateProp: {
664
688
  readonly group: "data-display";
665
689
  readonly file: "components/data-display.prop.ts";
666
- readonly vocabulary: readonly ["IconProp", "TitleProp", "DescriptionProp", "ActionProp"];
690
+ readonly vocabulary: readonly ["IconProp", "TitleProp", "DescriptionProp", "ActionProp", "ToneProp"];
691
+ };
692
+ readonly EmptyStateToneProp: {
693
+ readonly group: "data-display";
694
+ readonly file: "components/data-display.prop.ts";
695
+ readonly vocabulary: readonly [];
667
696
  };
668
697
  readonly DescriptionsProp: {
669
698
  readonly group: "data-display";
@@ -208,6 +208,11 @@ const VOCABULARY_REGISTRY = {
208
208
  category: "interaction",
209
209
  description: "Badge visual variant (default | secondary | outline | dashed)"
210
210
  },
211
+ AppSettingPickerAppearanceProp: {
212
+ file: "vocabulary/interaction.prop.ts",
213
+ category: "interaction",
214
+ description: "AppSettingPicker trigger presentation: labeled (icon + value) | icon (square icon-only topbar trigger)"
215
+ },
211
216
  ShapeProp: {
212
217
  file: "vocabulary/interaction.prop.ts",
213
218
  category: "interaction",
@@ -288,6 +293,11 @@ const VOCABULARY_REGISTRY = {
288
293
  category: "interaction",
289
294
  description: "Active table sort"
290
295
  },
296
+ RevealDelayProp: {
297
+ file: "vocabulary/interaction.prop.ts",
298
+ category: "interaction",
299
+ description: "Reveal entrance-stagger ordinal (0..6) \u2014 an index into the motion ladder, never a raw ms"
300
+ },
291
301
  // navigation.prop.ts
292
302
  BreadcrumbItemProp: {
293
303
  file: "vocabulary/navigation.prop.ts",
@@ -376,7 +386,14 @@ const COMPONENT_PROP_REGISTRY = {
376
386
  AppSettingPickerProp: {
377
387
  group: "app",
378
388
  file: "components/app.prop.ts",
379
- vocabulary: ["ValueProp", "OnValueChangeProp", "DisabledProp", "IdProp", "ClassNameProp"]
389
+ vocabulary: [
390
+ "ValueProp",
391
+ "OnValueChangeProp",
392
+ "DisabledProp",
393
+ "IdProp",
394
+ "ClassNameProp",
395
+ "AppSettingPickerAppearanceProp"
396
+ ]
380
397
  },
381
398
  PageContainerProp: {
382
399
  group: "layout",
@@ -416,6 +433,11 @@ const COMPONENT_PROP_REGISTRY = {
416
433
  file: "components/layout.prop.ts",
417
434
  vocabulary: ["ChildrenProp"]
418
435
  },
436
+ AuthShellProp: {
437
+ group: "layout",
438
+ file: "components/layout.prop.ts",
439
+ vocabulary: ["ChildrenProp", "ClassNameProp"]
440
+ },
419
441
  SidebarProductProp: {
420
442
  group: "layout",
421
443
  file: "components/layout.prop.ts",
@@ -454,6 +476,21 @@ const COMPONENT_PROP_REGISTRY = {
454
476
  "PendingProp"
455
477
  ]
456
478
  },
479
+ RevealProp: {
480
+ group: "general",
481
+ file: "components/general.prop.ts",
482
+ vocabulary: [
483
+ "ChildrenProp",
484
+ "RevealDelayProp",
485
+ "AsChildProp",
486
+ "ClassNameProp",
487
+ {
488
+ field: "delay",
489
+ local: true,
490
+ reason: "Reveal stagger ordinal \u2014 RevealDelayProp vocabulary."
491
+ }
492
+ ]
493
+ },
457
494
  TextProp: {
458
495
  group: "general",
459
496
  file: "components/general.prop.ts",
@@ -705,7 +742,12 @@ const COMPONENT_PROP_REGISTRY = {
705
742
  EmptyStateProp: {
706
743
  group: "data-display",
707
744
  file: "components/data-display.prop.ts",
708
- vocabulary: ["IconProp", "TitleProp", "DescriptionProp", "ActionProp"]
745
+ vocabulary: ["IconProp", "TitleProp", "DescriptionProp", "ActionProp", "ToneProp"]
746
+ },
747
+ EmptyStateToneProp: {
748
+ group: "data-display",
749
+ file: "components/data-display.prop.ts",
750
+ vocabulary: []
709
751
  },
710
752
  DescriptionsProp: {
711
753
  group: "data-display",
@@ -2,6 +2,6 @@
2
2
  export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, OnOpenChangeProp, HandlerProp, PendingProp, RequiredProp, DisabledProp, LabelProp, HelperProp, ErrorProp, PlaceholderProp, NameProp, ValueProp, DefaultValueProp, OnValueChangeProp, OnChangeProp, OnClickProp, AsChildProp, WidthProp, } from "./shared.prop.js";
3
3
  export type { TitleProp, SubtitleProp, DescriptionProp, ExtraProp, FooterProp, ActionProp, IconProp, ConfirmLabelProp, CancelLabelProp, ActionsProp, EmptyMessageProp, } from "./content.prop.js";
4
4
  export type { PageDensityProp, PageContainerVariantProp, TableDensityProp, DensityProp, GapProp, } from "./layout.prop.js";
5
- export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, ShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, SizeProp, FormLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, } from "./interaction.prop.js";
5
+ export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, ShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, SizeProp, FormLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, } from "./interaction.prop.js";
6
6
  export type { BreadcrumbItemProp, BreadcrumbProp } from "./navigation.prop.js";
7
7
  export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, } from "./data.prop.js";
@@ -20,6 +20,16 @@ export type HeadingLevelProp = 1 | 2 | 3 | 4;
20
20
  export type TextAlignProp = "start" | "center" | "end";
21
21
  /** Badge visual style. */
22
22
  export type BadgeVariantProp = "default" | "secondary" | "outline" | "dashed";
23
+ /**
24
+ * AppSettingPicker trigger presentation.
25
+ * - `labeled` (default) — the leading icon + the selected value inside a full-width control
26
+ * (settings forms, preference panels).
27
+ * - `icon` — a square, icon-only utility trigger (e.g. a topbar globe locale switcher). It
28
+ * STRUCTURALLY drops the value text and the picker's owned trigger width, keeping the localized
29
+ * `aria-label`, focus ring, keyboard behaviour and a `--control-height` tap target (which is
30
+ * ≥44px on coarse/touch pointers per Rule #24) — so consumers never hide internal nodes via CSS.
31
+ */
32
+ export type AppSettingPickerAppearanceProp = "labeled" | "icon";
23
33
  /** Button size preset. */
24
34
  export type SizeProp = "xs" | "sm" | "md" | "lg";
25
35
  /** Button size preset; icon-only sizes are a documented Button subset. */
@@ -46,3 +56,9 @@ export type SortStateProp = {
46
56
  key: string;
47
57
  direction: SortDirectionProp;
48
58
  };
59
+ /**
60
+ * Entrance-stagger ordinal for `Reveal` — an INDEX into the motion ladder, never a raw ms.
61
+ * `0` = enter immediately; `1..6` each add one `--reveal-stagger-step` of delay so a column of
62
+ * revealed rows cascades in. Reduced-motion collapses every step to 0 (content stays visible).
63
+ */
64
+ export type RevealDelayProp = 0 | 1 | 2 | 3 | 4 | 5 | 6;
@@ -107,8 +107,7 @@
107
107
  grid-column: 1;
108
108
  }
109
109
 
110
- [data-slot="alert-body"]:has(> [data-slot="alert-actions"])
111
- > [data-slot="alert-actions"] {
110
+ [data-slot="alert-body"]:has(> [data-slot="alert-actions"]) > [data-slot="alert-actions"] {
112
111
  grid-column: 2;
113
112
  grid-row: 1;
114
113
  }
@@ -34,8 +34,7 @@
34
34
  * --card-background defaults to the LIVE --card role at the call site (re-resolves under a
35
35
  * scoped theme); a service overrides --card-background to win. */
36
36
  background:
37
- linear-gradient(var(--card-tint), var(--card-tint)),
38
- hsl(var(--card-background, var(--card)));
37
+ linear-gradient(var(--card-tint), var(--card-tint)), hsl(var(--card-background, var(--card)));
39
38
  color: hsl(var(--card-foreground));
40
39
  /* Resting elevation + opt-in brand glow — both quiet no-ops by default so a service theme can
41
40
  * lift (--card-shadow) and/or glow (--card-glow) every card with no markup change. */
@@ -100,9 +99,7 @@
100
99
  [data-slot="card"][data-accent] > [data-slot="card-header"],
101
100
  [data-slot="card"][data-accent] > [data-slot="card-content"]:not([data-flush]),
102
101
  [data-slot="card"][data-accent] > [data-slot="card-footer"]:not([data-flush]) {
103
- padding-inline-start: calc(
104
- var(--card-space-inset) - var(--card-accent-rail-width)
105
- );
102
+ padding-inline-start: calc(var(--card-space-inset) - var(--card-accent-rail-width));
106
103
  }
107
104
 
108
105
  /* ── Header ──────────────────────────────────────────────────────────── */
@@ -156,7 +153,10 @@
156
153
  }
157
154
 
158
155
  .ui-card-header--banded {
159
- border-bottom: var(--card-header-border-bottom, 1px solid hsl(var(--card-border, var(--border))));
156
+ border-bottom: var(
157
+ --card-header-border-bottom,
158
+ 1px solid hsl(var(--card-border, var(--border)))
159
+ );
160
160
  background-color: hsl(
161
161
  var(--card-header-background, var(--muted)) / var(--card-header-background-alpha)
162
162
  );
@@ -56,6 +56,24 @@
56
56
  background: hsl(var(--warning));
57
57
  }
58
58
 
59
+ .ui-progress[data-tone="destructive"] .ui-progress-bar {
60
+ background: hsl(var(--destructive));
61
+ }
62
+
63
+ /* Over-capacity: keep the destructive fill but lay a diagonal hatch over it so an over-limit bar
64
+ * (value > 100, width still capped at 100%) is visually distinct from a full one. Placed after the
65
+ * tone rules so background-color wins; background-image carries the stripes. */
66
+ .ui-progress[data-over] .ui-progress-bar {
67
+ background-color: hsl(var(--destructive));
68
+ background-image: repeating-linear-gradient(
69
+ 45deg,
70
+ hsl(var(--destructive-foreground) / 0.4) 0,
71
+ hsl(var(--destructive-foreground) / 0.4) 0.3rem,
72
+ transparent 0.3rem,
73
+ transparent 0.6rem
74
+ );
75
+ }
76
+
59
77
  .ui-progress-label {
60
78
  color: hsl(var(--muted-foreground));
61
79
  font-size: var(--progress-label-font-size);
@@ -15,8 +15,10 @@
15
15
  @import "./fonts.css";
16
16
  @import "./base.css";
17
17
  @import "./shell-layout.css";
18
+ @import "./motion.css";
18
19
  @import "./layout.css";
19
20
  @import "./control.css";
21
+ @import "./logo-layout.css";
20
22
  @import "./card-layout.css";
21
23
  @import "./text-layout.css";
22
24
  @import "./table-layout.css";
@@ -46,6 +46,15 @@
46
46
  flex-direction: column;
47
47
  }
48
48
 
49
+ .ui-empty-state[data-variant="section"] {
50
+ padding: var(--empty-state-section-space-y) var(--empty-state-section-space-x);
51
+ }
52
+
53
+ .ui-empty-state[data-variant="compact"] {
54
+ padding: var(--empty-state-compact-space-y) var(--empty-state-compact-space-x);
55
+ gap: var(--space-stack-xs);
56
+ }
57
+
49
58
  .ui-flex[data-wrap="true"] {
50
59
  flex-wrap: wrap;
51
60
  }
@@ -512,6 +521,25 @@
512
521
  background-color: var(--empty-state-icon-tint, hsl(var(--muted)));
513
522
  color: var(--empty-state-icon-foreground, hsl(var(--muted-foreground)));
514
523
  }
524
+
525
+ /* tone — set the medallion foreground + tint from the matching role token, so a consumer never
526
+ * hand-rolls a `.ui-success-state` class. `muted` (default) keeps the neutral role fallback. */
527
+ .ui-empty-state[data-tone="success"] {
528
+ --empty-state-icon-foreground: hsl(var(--success));
529
+ --empty-state-icon-tint: hsl(var(--success) / 0.12);
530
+ }
531
+ .ui-empty-state[data-tone="warning"] {
532
+ --empty-state-icon-foreground: hsl(var(--warning));
533
+ --empty-state-icon-tint: hsl(var(--warning) / 0.12);
534
+ }
535
+ .ui-empty-state[data-tone="destructive"] {
536
+ --empty-state-icon-foreground: hsl(var(--destructive));
537
+ --empty-state-icon-tint: hsl(var(--destructive) / 0.12);
538
+ }
539
+ .ui-empty-state[data-tone="info"] {
540
+ --empty-state-icon-foreground: hsl(var(--info));
541
+ --empty-state-icon-tint: hsl(var(--info) / 0.12);
542
+ }
515
543
  }
516
544
 
517
545
  @layer components {
@@ -0,0 +1,41 @@
1
+ /*
2
+ * LOGO — the product brand-mark box (a glyph on the primary fill). Replaces the hand-rolled
3
+ * `<span aria-hidden className="grid size-7 place-items-center rounded-md bg-primary …">g</span>`
4
+ * repeated across app shells and topbars (issue #116): size/radius/font-size are tokens (rule #45),
5
+ * colours read the primary role directly, and the type comes from a semantic class (not utilities
6
+ * on a bare span, rule #46).
7
+ */
8
+ @layer components {
9
+ .ui-logo {
10
+ display: inline-grid;
11
+ place-items: center;
12
+ flex: 0 0 auto;
13
+ width: var(--logo-size-md);
14
+ height: var(--logo-size-md);
15
+ border-radius: var(--logo-radius);
16
+ background: hsl(var(--primary));
17
+ color: hsl(var(--primary-foreground));
18
+ font-weight: 700;
19
+ font-size: var(--logo-font-size-md);
20
+ line-height: 1;
21
+ user-select: none;
22
+ }
23
+
24
+ .ui-logo[data-size="xs"] {
25
+ width: var(--logo-size-xs);
26
+ height: var(--logo-size-xs);
27
+ font-size: var(--logo-font-size-xs);
28
+ }
29
+
30
+ .ui-logo[data-size="sm"] {
31
+ width: var(--logo-size-sm);
32
+ height: var(--logo-size-sm);
33
+ font-size: var(--logo-font-size-sm);
34
+ }
35
+
36
+ .ui-logo[data-size="lg"] {
37
+ width: var(--logo-size-lg);
38
+ height: var(--logo-size-lg);
39
+ font-size: var(--logo-font-size-lg);
40
+ }
41
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * MOTION — entrance/reveal animation for the `Reveal` primitive.
3
+ * Reads the DS motion tokens (--duration-slow / --ease-emphasized / --reveal-distance) instead of
4
+ * a hand-rolled @keyframes, and staggers via the --reveal-stagger-step ladder. Under
5
+ * prefers-reduced-motion the animation is dropped: content renders in its final, fully-visible
6
+ * position with no transform and no layout shift.
7
+ */
8
+
9
+ @layer components {
10
+ .ui-reveal {
11
+ animation: ui-reveal-in var(--duration-slow) var(--ease-emphasized) both;
12
+ will-change: transform, opacity;
13
+ }
14
+
15
+ .ui-reveal[data-reveal-delay="1"] {
16
+ animation-delay: calc(var(--reveal-stagger-step) * 1);
17
+ }
18
+ .ui-reveal[data-reveal-delay="2"] {
19
+ animation-delay: calc(var(--reveal-stagger-step) * 2);
20
+ }
21
+ .ui-reveal[data-reveal-delay="3"] {
22
+ animation-delay: calc(var(--reveal-stagger-step) * 3);
23
+ }
24
+ .ui-reveal[data-reveal-delay="4"] {
25
+ animation-delay: calc(var(--reveal-stagger-step) * 4);
26
+ }
27
+ .ui-reveal[data-reveal-delay="5"] {
28
+ animation-delay: calc(var(--reveal-stagger-step) * 5);
29
+ }
30
+ .ui-reveal[data-reveal-delay="6"] {
31
+ animation-delay: calc(var(--reveal-stagger-step) * 6);
32
+ }
33
+
34
+ @keyframes ui-reveal-in {
35
+ from {
36
+ opacity: 0;
37
+ transform: translateY(var(--reveal-distance));
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: none;
42
+ }
43
+ }
44
+
45
+ /* Accessibility: no motion → no animation, content stays visible (WCAG 2.3.3 / 2.2.2). */
46
+ @media (prefers-reduced-motion: reduce) {
47
+ .ui-reveal,
48
+ .ui-reveal[data-reveal-delay] {
49
+ animation: none;
50
+ }
51
+ }
52
+ }
@@ -81,6 +81,48 @@
81
81
  max-width: none;
82
82
  }
83
83
 
84
+ /*
85
+ * AUTH SHELL — centred auth/login page shell (brand bar + centred card + footer).
86
+ * Comfortable control density + a larger auth heading are scoped to the shell so login/mfa/reset
87
+ * forms read at the right size without a consumer scope class. Everything reads a token knob.
88
+ */
89
+ .ui-auth-shell {
90
+ --control-height: var(--auth-shell-control-height);
91
+ --heading-h1: var(--auth-shell-heading-size);
92
+ display: flex;
93
+ min-height: 100dvh;
94
+ flex-direction: column;
95
+ background:
96
+ linear-gradient(180deg, hsl(var(--primary) / 0.035), transparent 14rem),
97
+ hsl(var(--background));
98
+ }
99
+
100
+ .ui-auth-shell-bar {
101
+ display: flex;
102
+ align-items: center;
103
+ padding: var(--auth-shell-bar-padding);
104
+ }
105
+
106
+ .ui-auth-shell-main {
107
+ display: flex;
108
+ flex: 1 1 auto;
109
+ flex-direction: column;
110
+ align-items: center;
111
+ justify-content: center;
112
+ padding: var(--auth-shell-main-padding);
113
+ }
114
+
115
+ .ui-auth-shell-card {
116
+ width: 100%;
117
+ max-width: var(--auth-shell-card-max-width);
118
+ }
119
+
120
+ .ui-auth-shell-footer {
121
+ padding: var(--auth-shell-footer-padding);
122
+ text-align: center;
123
+ color: hsl(var(--muted-foreground));
124
+ }
125
+
84
126
  .app-topbar-rail {
85
127
  display: flex;
86
128
  min-width: 0;
@@ -0,0 +1,44 @@
1
+ /* ───────────────────────────────────────────────────────────────
2
+ * Famgia service theme — ファムジア株式会社
3
+ * Re-themes @godxjp/ui via SEMANTIC TOKENS only (no component CSS).
4
+ * Brand: electric blue #2563EB (primary) + violet #7C3AED (accent gradient).
5
+ * Import this instead of "@godxjp/ui/styles".
6
+ * ─────────────────────────────────────────────────────────────── */
7
+
8
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+JP:wght@400;500;700&display=swap");
9
+ @import "@godxjp/ui/styles";
10
+
11
+ /* ── Light (default) ── */
12
+ :root {
13
+ /* Brand primary ramp — electric blue */
14
+ --primary: 221 83% 53%; /* #2563EB */
15
+ --primary-foreground: 0 0% 100%;
16
+ --primary-hover: 224 71% 48%; /* #1D4ED8 */
17
+ --primary-active: 226 71% 40%; /* #1E40AF */
18
+ --ring: 221 83% 53%;
19
+
20
+ /* Brand-colored text (AA on tint/white) */
21
+ --text-link: 224 76% 45%;
22
+ --text-brand: 221 83% 53%;
23
+ --text-primary: 224 76% 42%;
24
+
25
+ /* Signature brand gradient: blue → violet */
26
+ --gradient-brand: linear-gradient(135deg, hsl(221 83% 53%), hsl(262 83% 58%));
27
+
28
+ /* Typography — Inter (Latin/VI) + Noto Sans JP (kana/kanji) */
29
+ --font-family-sans:
30
+ "Inter", "Noto Sans JP", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
31
+ "Helvetica Neue", Arial, sans-serif;
32
+ }
33
+
34
+ /* ── Dark ── lifted blue for contrast */
35
+ .dark {
36
+ --primary: 221 90% 66%;
37
+ --primary-foreground: 222 47% 11%;
38
+ --primary-hover: 221 90% 72%;
39
+ --primary-active: 221 90% 60%;
40
+ --ring: 221 90% 66%;
41
+ --text-link: 221 90% 72%;
42
+ --text-brand: 221 90% 70%;
43
+ --text-primary: 221 90% 68%;
44
+ }
@@ -16,5 +16,6 @@
16
16
  @import "./components/navigation.css";
17
17
  @import "./components/data-display.css";
18
18
  @import "./components/list-row.css";
19
+ @import "./components/logo.css";
19
20
  @import "./components/data-entry.css";
20
21
  @import "./components/shell.css";
@@ -22,6 +22,10 @@
22
22
  --dialog-content-glow: 0 0 0 0 transparent;
23
23
  --empty-state-space-y: var(--space-10);
24
24
  --empty-state-space-x: var(--space-6);
25
+ --empty-state-section-space-y: var(--space-6);
26
+ --empty-state-section-space-x: var(--space-4);
27
+ --empty-state-compact-space-y: var(--space-3);
28
+ --empty-state-compact-space-x: var(--space-2);
25
29
  /* EmptyState icon medallion colour — `initial` so the role defaults re-resolve at the call site
26
30
  * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground)
27
31
  * or washes the medallion fill (--empty-state-icon-tint) without forking.