@godxjp/ui 18.10.0 → 18.11.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 (54) hide show
  1. package/dist/components/data-display/card.d.ts +27 -3
  2. package/dist/components/data-display/card.js +4 -1
  3. package/dist/components/data-display/data-table.js +2 -3
  4. package/dist/components/data-entry/command-palette.d.ts +42 -1
  5. package/dist/components/data-entry/command-palette.js +31 -2
  6. package/dist/components/data-entry/input-otp.d.ts +1 -1
  7. package/dist/components/data-entry/select.d.ts +7 -1
  8. package/dist/components/data-entry/select.js +46 -26
  9. package/dist/components/feedback/banner.js +5 -1
  10. package/dist/components/layout/page-container.d.ts +1 -1
  11. package/dist/components/layout/page-container.js +61 -41
  12. package/dist/components/layout/resizable.d.ts +1 -1
  13. package/dist/components/layout/resizable.js +26 -8
  14. package/dist/components/navigation/pagination.js +20 -1
  15. package/dist/components/navigation/steps.d.ts +2 -2
  16. package/dist/components/navigation/steps.js +4 -2
  17. package/dist/components/navigation/tabs.js +6 -1
  18. package/dist/components/ui/avatar.d.ts +1 -1
  19. package/dist/components/ui/avatar.js +2 -1
  20. package/dist/components/ui/input-otp.d.ts +15 -3
  21. package/dist/components/ui/input-otp.js +2 -1
  22. package/dist/email/tokens.generated.d.ts +2 -2
  23. package/dist/email/tokens.generated.js +2 -2
  24. package/dist/i18n/messages/en.json +3 -0
  25. package/dist/i18n/messages/ja.json +3 -0
  26. package/dist/i18n/messages/vi.json +3 -0
  27. package/dist/lib/field-a11y.d.ts +7 -0
  28. package/dist/lib/field-a11y.js +6 -0
  29. package/dist/lib/hooks.d.ts +15 -0
  30. package/dist/lib/hooks.js +37 -0
  31. package/dist/props/components/data-display.prop.d.ts +16 -0
  32. package/dist/props/components/data-entry.prop.d.ts +7 -0
  33. package/dist/props/components/layout.prop.d.ts +8 -0
  34. package/dist/props/components/navigation.prop.d.ts +9 -0
  35. package/dist/props/registry.d.ts +20 -1
  36. package/dist/props/registry.js +21 -1
  37. package/dist/styles/base.css +28 -0
  38. package/dist/styles/card-layout.css +49 -13
  39. package/dist/styles/control.css +17 -2
  40. package/dist/styles/data-display-layout.css +16 -0
  41. package/dist/styles/layout.css +16 -0
  42. package/dist/styles/navigation-layout.css +8 -3
  43. package/dist/styles/shell-layout.css +10 -1
  44. package/dist/styles/table-layout.css +42 -0
  45. package/dist/tokens/components/card.css +12 -0
  46. package/dist/tokens/components/control.css +16 -0
  47. package/dist/tokens/components/data-display.css +12 -0
  48. package/dist/tokens/components/email.css +8 -2
  49. package/dist/tokens/components/feedback.css +20 -0
  50. package/dist/tokens/components/navigation.css +9 -0
  51. package/dist/tokens/components/shell.css +27 -4
  52. package/dist/tokens/components/table.css +21 -0
  53. package/dist/tokens/semantic/layout.css +8 -0
  54. package/package.json +2 -2
@@ -106,7 +106,12 @@ const TabsList = React.forwardRef(({ className, variant = "default", ...props },
106
106
  // active trigger when a resize would otherwise strand it off-strip (gh#204). Vertical
107
107
  // orientation is untouched — it already stacks in a column and is sized by its own
108
108
  // `h-*`/`w-*` overrides.
109
- "group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit max-w-full min-w-0 items-center justify-center rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[orientation=horizontal]:[scrollbar-width:none] data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
109
+ // justify-center-SAFE: the strip is a centred flex box that also scrolls. Plain `center`
110
+ // splits the overflow across BOTH edges, and scrollLeft only ever covers the trailing one
111
+ // — so the leading tab sat permanently outside the scrollport (an unreachable control at
112
+ // 320px, WCAG 2.2 SC 2.1.1). `safe` falls back to start alignment exactly when it
113
+ // overflows, and still centres whenever the tabs fit.
114
+ "group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit max-w-full min-w-0 items-center justify-center-safe rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[orientation=horizontal]:[scrollbar-width:none] data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
110
115
  className
111
116
  ),
112
117
  ...props
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
3
3
  import type { AvatarProp } from "../../props/components/data-display.prop.js";
4
- export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & Pick<AvatarProp, "shape"> & React.RefAttributes<HTMLSpanElement>>;
4
+ export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & Pick<AvatarProp, "shape" | "appearance"> & React.RefAttributes<HTMLSpanElement>>;
5
5
  export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
6
6
  export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
@@ -3,12 +3,13 @@ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
5
  import { cn } from "../../lib/utils.js";
6
- const Avatar = React.forwardRef(({ className, shape = "circle", ...props }, ref) => /* @__PURE__ */ jsx(
6
+ const Avatar = React.forwardRef(({ className, shape = "circle", appearance = "default", ...props }, ref) => /* @__PURE__ */ jsx(
7
7
  AvatarPrimitive.Root,
8
8
  {
9
9
  ref,
10
10
  "data-slot": "avatar",
11
11
  "data-shape": shape === "square" ? "square" : void 0,
12
+ "data-appearance": appearance === "tinted" ? "tinted" : void 0,
12
13
  className: cn("ui-avatar", className),
13
14
  ...props
14
15
  }
@@ -1,5 +1,15 @@
1
1
  import * as React from "react";
2
- export declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value" | "maxLength" | "textAlign" | "onComplete" | "pushPasswordManagerStrategy" | "pasteTransformer" | "containerClassName" | "noScriptCSSFallback"> & {
2
+ import type { InputOTPAlignProp } from "../../props/components/data-entry.prop.js";
3
+ /**
4
+ * InputOTP — the code-challenge field.
5
+ *
6
+ * `align` positions the whole row (groups + separators) on the main axis: `start` (default,
7
+ * unchanged), `center` — the canonical auth challenge — or `end`. The attribute lands on the
8
+ * hidden input because `input-otp` owns the container element; `.ui-otp-container` reads it back
9
+ * through `:has()`, exactly as it already does for the invalid and disabled states. A service that
10
+ * wants every code field centred sets `--otp-container-align` once instead.
11
+ */
12
+ export declare const InputOTP: React.ForwardRefExoticComponent<((Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value" | "maxLength" | "textAlign" | "onComplete" | "pushPasswordManagerStrategy" | "pasteTransformer" | "containerClassName" | "noScriptCSSFallback"> & {
3
13
  value?: string;
4
14
  onChange?: (newValue: string) => unknown;
5
15
  maxLength: number;
@@ -25,8 +35,10 @@ export declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.
25
35
  } & {
26
36
  render?: never;
27
37
  children: React.ReactNode;
28
- } & React.RefAttributes<HTMLInputElement>, "ref">) & React.RefAttributes<HTMLInputElement>>;
29
- export type { InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
38
+ } & React.RefAttributes<HTMLInputElement>, "ref">) & {
39
+ align?: InputOTPAlignProp;
40
+ }) & React.RefAttributes<HTMLInputElement>>;
41
+ export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
30
42
  export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
31
43
  appearance?: import("./input-otp.js").InputOTPGroupAppearanceProp;
32
44
  } & React.RefAttributes<HTMLDivElement>>;
@@ -4,11 +4,12 @@ import * as React from "react";
4
4
  import { OTPInput, OTPInputContext } from "input-otp";
5
5
  import { Minus } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- const InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ jsx(
7
+ const InputOTP = React.forwardRef(({ className, containerClassName, align, ...props }, ref) => /* @__PURE__ */ jsx(
8
8
  OTPInput,
9
9
  {
10
10
  ref,
11
11
  "data-slot": "input-otp",
12
+ "data-align": align && align !== "start" ? align : void 0,
12
13
  containerClassName: cn("ui-otp-container", containerClassName),
13
14
  className: cn("ui-otp-input", className),
14
15
  ...props
@@ -146,8 +146,8 @@ export declare const EMAIL_GEOMETRY_SOURCE: {
146
146
  readonly "--email-heading-font-size": "17px";
147
147
  readonly "--email-heading-line-height": "1.7";
148
148
  readonly "--email-heading-font-weight": "500";
149
- readonly "--email-cta-height": "36px";
150
- readonly "--email-cta-line-height": "36px";
149
+ readonly "--email-cta-height": "44px";
150
+ readonly "--email-cta-line-height": "44px";
151
151
  readonly "--email-cta-padding-x": "16px";
152
152
  readonly "--email-cta-radius": "6px";
153
153
  readonly "--email-cta-font-size": "14px";
@@ -136,8 +136,8 @@ const EMAIL_GEOMETRY_SOURCE = {
136
136
  "--email-heading-font-size": "17px",
137
137
  "--email-heading-line-height": "1.7",
138
138
  "--email-heading-font-weight": "500",
139
- "--email-cta-height": "36px",
140
- "--email-cta-line-height": "36px",
139
+ "--email-cta-height": "44px",
140
+ "--email-cta-line-height": "44px",
141
141
  "--email-cta-padding-x": "16px",
142
142
  "--email-cta-radius": "6px",
143
143
  "--email-cta-font-size": "14px",
@@ -142,6 +142,9 @@
142
142
  }
143
143
  },
144
144
  "layout": {
145
+ "pageHeader": {
146
+ "loading": "Loading page…"
147
+ },
145
148
  "sidebar": {
146
149
  "ariaLabel": "Primary"
147
150
  },
@@ -139,6 +139,9 @@
139
139
  }
140
140
  },
141
141
  "layout": {
142
+ "pageHeader": {
143
+ "loading": "ページを読み込んでいます…"
144
+ },
142
145
  "sidebar": {
143
146
  "ariaLabel": "メインナビゲーション"
144
147
  },
@@ -139,6 +139,9 @@
139
139
  }
140
140
  },
141
141
  "layout": {
142
+ "pageHeader": {
143
+ "loading": "Đang tải trang…"
144
+ },
142
145
  "sidebar": {
143
146
  "ariaLabel": "Điều hướng chính"
144
147
  },
@@ -39,6 +39,13 @@ export declare function mergeAriaIds(...values: Array<string | undefined>): stri
39
39
  * ```
40
40
  */
41
41
  export declare function pickFieldA11y(props: FieldA11yProps): FieldA11yProps;
42
+ /**
43
+ * The inverse of {@link pickFieldA11y}: everything EXCEPT the field-a11y attributes. Use it when a
44
+ * component must route the contract somewhere other than the element the remaining props land on —
45
+ * e.g. `Select`'s compound API, where the props bag belongs to `SelectPrimitive.Root` (a
46
+ * context-only component that renders no DOM) while the accessible name has to reach the trigger.
47
+ */
48
+ export declare function omitFieldA11y<T extends FieldA11yProps>(props: T): Omit<T, keyof FieldA11yProps>;
42
49
  /**
43
50
  * As {@link pickFieldA11y}, but resolves the accessible **name** for a control that also has an
44
51
  * intrinsic `aria-label` (e.g. SearchInput's built-in "Search"): when the FormField supplies an
@@ -19,6 +19,11 @@ function pickFieldA11y(props) {
19
19
  }
20
20
  return out;
21
21
  }
22
+ function omitFieldA11y(props) {
23
+ const out = { ...props };
24
+ for (const key of FIELD_A11Y_KEYS) delete out[key];
25
+ return out;
26
+ }
22
27
  function resolveFieldA11y(props, intrinsicAriaLabel) {
23
28
  const picked = pickFieldA11y(props);
24
29
  if (picked["aria-labelledby"]) {
@@ -38,6 +43,7 @@ function pickGroupFieldA11y(props) {
38
43
  }
39
44
  export {
40
45
  mergeAriaIds,
46
+ omitFieldA11y,
41
47
  pickFieldA11y,
42
48
  pickGroupFieldA11y,
43
49
  resolveFieldA11y
@@ -28,3 +28,18 @@ export declare function useTimeoutFlag(signal: unknown, ms?: number): boolean;
28
28
  export declare function useControlledLatch(valueIsDefined: boolean): boolean;
29
29
  export declare function useMediaQuery(query: string): boolean;
30
30
  export declare function useIsMobile(): boolean;
31
+ /**
32
+ * WCAG 2.1.1 — a region that scrolls must be operable by keyboard. Tabbing to a focusable child
33
+ * scrolls the container, so a region whose content IS focusable needs nothing; one whose content is
34
+ * inert (plain text) strands its overflow for anyone not using a pointer and must therefore take
35
+ * focus itself via `tabindex="0"`. Which case applies depends on the RENDERED size and content —
36
+ * the same pagination strip is fine at 1440px and unreachable at 375px, and a panel full of text
37
+ * only overflows once it is resized — so it is measured at runtime and kept in sync as the element
38
+ * resizes or its content changes.
39
+ *
40
+ * The attribute is written imperatively rather than rendered: `react-resizable-panels` applies our
41
+ * className to a nested div it owns, which no prop can reach.
42
+ *
43
+ * @param element the scroll container itself (state, not a ref, so the effect re-runs when it mounts)
44
+ */
45
+ export declare function useScrollableRegionTabIndex(element: HTMLElement | null): void;
package/dist/lib/hooks.js CHANGED
@@ -68,10 +68,47 @@ function useMediaQuery(query) {
68
68
  function useIsMobile() {
69
69
  return useMediaQuery("(max-width: 767px)");
70
70
  }
71
+ const SCROLLABLE_REGION_FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
72
+ function useScrollableRegionTabIndex(element) {
73
+ useEffect(() => {
74
+ if (!element) return;
75
+ const sync = () => {
76
+ const scrolls = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
77
+ const hasFocusableContent = element.querySelector(SCROLLABLE_REGION_FOCUSABLE_SELECTOR) !== null;
78
+ if (scrolls && !hasFocusableContent) {
79
+ if (element.getAttribute("tabindex") !== "0") element.setAttribute("tabindex", "0");
80
+ } else if (element.getAttribute("tabindex") === "0") {
81
+ element.removeAttribute("tabindex");
82
+ }
83
+ };
84
+ sync();
85
+ const observers = [];
86
+ if (typeof ResizeObserver !== "undefined") {
87
+ const resizeObserver = new ResizeObserver(sync);
88
+ resizeObserver.observe(element);
89
+ for (const child of Array.from(element.children)) resizeObserver.observe(child);
90
+ observers.push(resizeObserver);
91
+ }
92
+ if (typeof MutationObserver !== "undefined") {
93
+ const mutationObserver = new MutationObserver(sync);
94
+ mutationObserver.observe(element, {
95
+ childList: true,
96
+ subtree: true,
97
+ attributes: true,
98
+ attributeFilter: ["disabled", "href", "tabindex"]
99
+ });
100
+ observers.push(mutationObserver);
101
+ }
102
+ return () => {
103
+ for (const observer of observers) observer.disconnect();
104
+ };
105
+ }, [element]);
106
+ }
71
107
  export {
72
108
  useControlledLatch,
73
109
  useDebouncedValue,
74
110
  useIsMobile,
75
111
  useMediaQuery,
112
+ useScrollableRegionTabIndex,
76
113
  useTimeoutFlag
77
114
  };
@@ -62,9 +62,25 @@ export type DescriptionsItemProp = {
62
62
  */
63
63
  export type AvatarProp = React.ComponentPropsWithoutRef<"span"> & {
64
64
  shape?: AvatarShapeProp;
65
+ appearance?: AvatarAppearanceProp;
65
66
  className?: ClassNameProp;
66
67
  children?: ChildrenProp;
67
68
  };
69
+ /**
70
+ * Avatar fill treatment.
71
+ *
72
+ * - `"default"` — the identity fill: `--muted` for a person, the solid brand mark for
73
+ * `shape="square"`.
74
+ * - `"tinted"` — the **capability medallion**: a soft role wash behind a role-coloured glyph.
75
+ * This is the plate a capability/feature icon sits on (`shape="square" appearance="tinted"` for
76
+ * the canonical rounded square). It exists because the medallion is a composition — `Avatar` +
77
+ * a Lucide glyph, per docs/COMPOSITION-VS-COMPONENT.md — but its *tint* was not reachable from
78
+ * a token, so consumers were re-deriving `hsl(var(--primary) / 0.1)` in page CSS or giving up
79
+ * and rendering a bare glyph. Retune with `--avatar-tinted-*`.
80
+ *
81
+ * @see Avatar
82
+ */
83
+ export type AvatarAppearanceProp = "default" | "tinted";
68
84
  /** @see Badge */
69
85
  export type BadgeProp = {
70
86
  variant?: "default" | "secondary" | "outline";
@@ -16,6 +16,13 @@ export type InputOTPGroupAppearanceProp = "slots" | "grouped";
16
16
  export type InputOTPGroupProp = React.HTMLAttributes<HTMLDivElement> & {
17
17
  appearance?: InputOTPGroupAppearanceProp;
18
18
  };
19
+ /**
20
+ * Main-axis alignment of the whole code row (groups + separators) inside its container.
21
+ * `start` is the historical default. A centred challenge is the common auth case and used to
22
+ * force every consumer to wrap `.ui-otp-container` in their own flex-centring div.
23
+ * @see InputOTP
24
+ */
25
+ export type InputOTPAlignProp = "start" | "center" | "end";
19
26
  /** @see Input */
20
27
  export type InputProp = React.InputHTMLAttributes<HTMLInputElement> & {
21
28
  /** Show an inline ✕ that clears the field while it holds text (default false). */
@@ -38,6 +38,14 @@ export type PageContainerProp = {
38
38
  * overflow (`headerLayout` / `measure`) all live on this one renderer.
39
39
  */
40
40
  status?: StatusProp;
41
+ /**
42
+ * Pending state for the title band while the page's own record resolves. Renders the
43
+ * title/subtitle as `ui-skeleton-block` placeholders and marks the header `aria-busy`, keeping
44
+ * the `<h1>` in the document with an sr-only accessible name (an empty heading is an axe
45
+ * violation) so the page's heading outline never disappears mid-load. Breadcrumbs and `extra`
46
+ * are NOT skeletonised — they come from the route, not the record.
47
+ */
48
+ headerLoading?: boolean;
41
49
  extra?: ExtraProp;
42
50
  footer?: FooterProp;
43
51
  breadcrumb?: BreadcrumbProp;
@@ -171,9 +171,18 @@ export type StepsProp = {
171
171
  type?: "default" | "dot" | "inline";
172
172
  size?: "md" | "sm";
173
173
  titlePlacement?: "horizontal" | "vertical";
174
+ /**
175
+ * The glyph between inline steps (`type="inline"` only). `chevron` (default, `›`) is the
176
+ * breadcrumb-flavoured original. `arrow` (`→`) is the canonical hosted-identity progression
177
+ * marker: a chevron reads as "drill into", an arrow reads as "then" — which is what a step row
178
+ * means. Ignored by every other `type`.
179
+ */
180
+ separator?: StepsSeparatorProp;
174
181
  onValueChange?: (value: number) => void;
175
182
  className?: ClassNameProp;
176
183
  };
184
+ /** @see Steps — inline separator glyph. */
185
+ export type StepsSeparatorProp = "chevron" | "arrow";
177
186
  /** Tab pane — Ant Design `items` entry. */
178
187
  export type TabItemProp = {
179
188
  value: string;
@@ -470,6 +470,10 @@ export declare const COMPONENT_PROP_REGISTRY: {
470
470
  readonly group: "layout";
471
471
  readonly file: "components/layout.prop.ts";
472
472
  readonly vocabulary: readonly ["TitleProp", "SubtitleProp", "StatusProp", "ExtraProp", "FooterProp", "BreadcrumbProp", "DensityProp", "PageContainerVariantProp", {
473
+ readonly field: "headerLoading";
474
+ readonly local: true;
475
+ readonly reason: "Pending state of the TITLE BAND only (skeleton title/subtitle + aria-busy on the header). Named for the BAND it skeletonises, so it can never be read as a page-wide loading flag (that is DataState's job); breadcrumbs and `extra` come from the route and stay live while the record resolves.";
476
+ }, {
473
477
  readonly field: "headerLayout";
474
478
  readonly local: true;
475
479
  readonly reason: "Header ARRANGEMENT of the title band vs the extra slot below the 640px step (stack | responsive-inline) — orthogonal to PageContainerVariantProp, which selects the page shell layout.";
@@ -1043,6 +1047,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1043
1047
  readonly file: "components/data-display.prop.ts";
1044
1048
  readonly vocabulary: readonly ["AvatarShapeProp", "ChildrenProp", "ClassNameProp"];
1045
1049
  };
1050
+ readonly AvatarAppearanceProp: {
1051
+ readonly group: "data-display";
1052
+ readonly file: "components/data-display.prop.ts";
1053
+ readonly vocabulary: readonly [];
1054
+ };
1046
1055
  readonly BadgeProp: {
1047
1056
  readonly group: "data-display";
1048
1057
  readonly file: "components/data-display.prop.ts";
@@ -1440,6 +1449,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1440
1449
  readonly file: "components/data-entry.prop.ts";
1441
1450
  readonly vocabulary: readonly [];
1442
1451
  };
1452
+ readonly InputOTPAlignProp: {
1453
+ readonly group: "data-entry";
1454
+ readonly file: "components/data-entry.prop.ts";
1455
+ readonly vocabulary: readonly [];
1456
+ };
1443
1457
  readonly StepItemProp: {
1444
1458
  readonly group: "navigation";
1445
1459
  readonly file: "components/navigation.prop.ts";
@@ -1450,6 +1464,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1450
1464
  readonly file: "components/navigation.prop.ts";
1451
1465
  readonly vocabulary: readonly [];
1452
1466
  };
1467
+ readonly StepsSeparatorProp: {
1468
+ readonly group: "navigation";
1469
+ readonly file: "components/navigation.prop.ts";
1470
+ readonly vocabulary: readonly [];
1471
+ };
1453
1472
  readonly TabsProp: {
1454
1473
  readonly group: "navigation";
1455
1474
  readonly file: "components/navigation.prop.ts";
@@ -1498,7 +1517,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
1498
1517
  readonly CommandPaletteProp: {
1499
1518
  readonly group: "data-entry";
1500
1519
  readonly file: "components/data-entry/command-palette.tsx";
1501
- readonly vocabulary: readonly ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp"];
1520
+ readonly vocabulary: readonly ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp", "OnSearchChangeProp"];
1502
1521
  };
1503
1522
  readonly TwoFactorSetupProp: {
1504
1523
  readonly group: "feedback";
@@ -482,6 +482,11 @@ const COMPONENT_PROP_REGISTRY = {
482
482
  "BreadcrumbProp",
483
483
  "DensityProp",
484
484
  "PageContainerVariantProp",
485
+ {
486
+ field: "headerLoading",
487
+ local: true,
488
+ reason: "Pending state of the TITLE BAND only (skeleton title/subtitle + aria-busy on the header). Named for the BAND it skeletonises, so it can never be read as a page-wide loading flag (that is DataState's job); breadcrumbs and `extra` come from the route and stay live while the record resolves."
489
+ },
485
490
  {
486
491
  field: "headerLayout",
487
492
  local: true,
@@ -1195,6 +1200,11 @@ const COMPONENT_PROP_REGISTRY = {
1195
1200
  file: "components/data-display.prop.ts",
1196
1201
  vocabulary: ["AvatarShapeProp", "ChildrenProp", "ClassNameProp"]
1197
1202
  },
1203
+ AvatarAppearanceProp: {
1204
+ group: "data-display",
1205
+ file: "components/data-display.prop.ts",
1206
+ vocabulary: []
1207
+ },
1198
1208
  BadgeProp: {
1199
1209
  group: "data-display",
1200
1210
  file: "components/data-display.prop.ts",
@@ -1576,12 +1586,22 @@ const COMPONENT_PROP_REGISTRY = {
1576
1586
  file: "components/data-entry.prop.ts",
1577
1587
  vocabulary: []
1578
1588
  },
1589
+ InputOTPAlignProp: {
1590
+ group: "data-entry",
1591
+ file: "components/data-entry.prop.ts",
1592
+ vocabulary: []
1593
+ },
1579
1594
  StepItemProp: {
1580
1595
  group: "navigation",
1581
1596
  file: "components/navigation.prop.ts",
1582
1597
  vocabulary: ["TitleProp", "SubtitleProp", "DescriptionProp", "IconProp", "DisabledProp"]
1583
1598
  },
1584
1599
  StepStatusProp: { group: "navigation", file: "components/navigation.prop.ts", vocabulary: [] },
1600
+ StepsSeparatorProp: {
1601
+ group: "navigation",
1602
+ file: "components/navigation.prop.ts",
1603
+ vocabulary: []
1604
+ },
1585
1605
  TabsProp: {
1586
1606
  group: "navigation",
1587
1607
  file: "components/navigation.prop.ts",
@@ -1632,7 +1652,7 @@ const COMPONENT_PROP_REGISTRY = {
1632
1652
  CommandPaletteProp: {
1633
1653
  group: "data-entry",
1634
1654
  file: "components/data-entry/command-palette.tsx",
1635
- vocabulary: ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp"]
1655
+ vocabulary: ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp", "OnSearchChangeProp"]
1636
1656
  },
1637
1657
  TwoFactorSetupProp: {
1638
1658
  group: "feedback",
@@ -28,6 +28,34 @@
28
28
  * ───────────────────────────────────────────────────────────────────────── */
29
29
  @import "tailwindcss";
30
30
 
31
+ /* ── THE LAYER CONTRACT (gh#412) ───────────────────────────────────────────
32
+ * `@import "tailwindcss"` establishes `theme, base, components, utilities`. Everything this
33
+ * package styles lives in `@layer components`, which is EARLIER than `utilities` — so a Tailwind
34
+ * utility a component itself emits silently outranks the package rule that is supposed to own the
35
+ * same property, no matter how specific that rule is. That is not a specificity bug you can fix
36
+ * with another selector; layer order beats specificity outright.
37
+ *
38
+ * It shipped as a real defect: `<table class="… text-sm">` (utilities) vs the collection preset's
39
+ * `font-size: var(--table-action-collection-font-size-compact)` (components) — the compact type
40
+ * tier never applied at ANY width, so the documented token was dead and a 5–6 character Japanese
41
+ * label could not fit the narrow-frame column measure (WCAG 2.2 SC 1.4.10, and only ever visible
42
+ * in Japanese). Two independent consumers reported it.
43
+ *
44
+ * `godxjp-ui-responsive` is declared HERE, after Tailwind, so it is the LAST layer and outranks
45
+ * `utilities`. It is reserved for one thing: RESPONSIVE RE-POINTS — the container/media blocks
46
+ * that must win over a component's own static utility for the layout contract to hold. Nothing
47
+ * static belongs in it; a rule that does not sit inside a `@container`/`@media` query goes in
48
+ * `@layer components` like everything else.
49
+ *
50
+ * What this layer canNOT do — and the rule consumers must follow: an UNLAYERED consumer rule
51
+ * outranks EVERY layer, including this one. So app CSS must theme this package through TOKENS on
52
+ * a wrapper, never by writing its own selectors against `[data-slot]` / `[data-priority]`
53
+ * elements. A consumer that does write such a rule must put it in a layer
54
+ * (`@layer components { … }`), or it will silently kill the responsive re-points the package
55
+ * makes — a column measured to 0px and wrapped one character per line is what that looks like.
56
+ * See docs/TOKENS.md · "The layer contract". */
57
+ @layer godxjp-ui-responsive;
58
+
31
59
  /* Per-language font wiring — token-only, NO hardcoded faces. Each locale reads
32
60
  * an OPTIONAL per-lang slot token (empty by default) and falls back to the
33
61
  * base sans. Consumers switch a locale's face by setting its slot, e.g.
@@ -51,9 +51,16 @@
51
51
  background: transparent;
52
52
  }
53
53
 
54
+ /* `featured` = the brand-toned perimeter. Its colour is a role-mirror knob (rule #45): declared
55
+ * `initial` in the token layer so the --primary default resolves HERE, at the call site, and a
56
+ * scoped [data-tenant]/.dark override of --primary still reaches it. A service that wants the
57
+ * featured ring in a non-brand tone overrides --card-featured-border-color; a card that wants a
58
+ * SEMANTIC perimeter (attention / destructive / success …) uses
59
+ * `accent="…" accentPlacement="perimeter"` instead — same geometry, tone-owned. */
54
60
  [data-slot="card"][data-variant="featured"] {
55
- border-color: hsl(var(--primary));
56
- box-shadow: 0 0 0 1px hsl(var(--primary));
61
+ border-color: var(--card-featured-border-color, hsl(var(--primary)));
62
+ box-shadow: 0 0 0 var(--card-featured-ring-width)
63
+ var(--card-featured-border-color, hsl(var(--primary)));
57
64
  }
58
65
 
59
66
  /* ── Density — override the shell inset/body tokens ──────────────────────
@@ -72,33 +79,39 @@
72
79
  --card-space-body-y: var(--space-5);
73
80
  }
74
81
 
75
- /* ── Accent edgesemantic left stripe (token width), content on shell ─ */
76
- [data-slot="card"][data-accent] {
77
- border-inline-start-width: var(--card-accent-rail-width);
78
- }
79
-
82
+ /* ── Accent toneONE resolved colour per semantic accent ─────────────
83
+ * `--card-accent-color` is RESOLVED from the card's own `data-accent`, so both placements read
84
+ * the same value and a new tone is one line. It is deliberately NOT a service knob: it is set on
85
+ * the element, so a :root / [data-tenant] override could never reach it. Retint the ROLE
86
+ * (--attention, --success, …) — that is what a role is for. */
80
87
  [data-slot="card"][data-accent="primary"] {
81
- border-inline-start-color: hsl(var(--primary));
88
+ --card-accent-color: hsl(var(--primary));
82
89
  }
83
90
 
84
91
  [data-slot="card"][data-accent="success"] {
85
- border-inline-start-color: hsl(var(--success));
92
+ --card-accent-color: hsl(var(--success));
86
93
  }
87
94
 
88
95
  [data-slot="card"][data-accent="warning"] {
89
- border-inline-start-color: hsl(var(--warning));
96
+ --card-accent-color: hsl(var(--warning));
90
97
  }
91
98
 
92
99
  [data-slot="card"][data-accent="info"] {
93
- border-inline-start-color: hsl(var(--info));
100
+ --card-accent-color: hsl(var(--info));
94
101
  }
95
102
 
96
103
  [data-slot="card"][data-accent="attention"] {
97
- border-inline-start-color: hsl(var(--attention));
104
+ --card-accent-color: hsl(var(--attention));
98
105
  }
99
106
 
100
107
  [data-slot="card"][data-accent="destructive"] {
101
- border-inline-start-color: hsl(var(--destructive));
108
+ --card-accent-color: hsl(var(--destructive));
109
+ }
110
+
111
+ /* ── Accent placement `edge` (DEFAULT) — semantic left stripe (token width), content on shell ─ */
112
+ [data-slot="card"][data-accent] {
113
+ border-inline-start-width: var(--card-accent-rail-width);
114
+ border-inline-start-color: var(--card-accent-color);
102
115
  }
103
116
 
104
117
  [data-slot="card"][data-accent] > [data-slot="card-header"],
@@ -107,6 +120,29 @@
107
120
  padding-inline-start: calc(var(--card-space-inset) - var(--card-accent-rail-width));
108
121
  }
109
122
 
123
+ /* ── Accent placement `perimeter` — the full attention border (gh#12) ───
124
+ * The whole edge carries the semantic tone instead of one rail, so a card can read as
125
+ * "action required" / "failed" at a glance without borrowing `--primary` the way
126
+ * `variant="featured"` does. Same optical weight as `featured` (border + 1px ring), tone-owned.
127
+ * Because the rail is gone, the slot padding compensation above is undone — content returns to
128
+ * the normal shell column, so switching placement never shifts the text. */
129
+ [data-slot="card"][data-accent][data-accent-placement="perimeter"] {
130
+ border-width: var(--card-accent-perimeter-width);
131
+ border-color: var(--card-accent-color);
132
+ box-shadow:
133
+ 0 0 0 var(--card-accent-perimeter-ring-width) var(--card-accent-color),
134
+ var(--card-shadow),
135
+ var(--card-glow);
136
+ }
137
+
138
+ [data-slot="card"][data-accent][data-accent-placement="perimeter"] > [data-slot="card-header"],
139
+ [data-slot="card"][data-accent][data-accent-placement="perimeter"]
140
+ > [data-slot="card-content"]:not([data-flush]),
141
+ [data-slot="card"][data-accent][data-accent-placement="perimeter"]
142
+ > [data-slot="card-footer"]:not([data-flush]) {
143
+ padding-inline-start: var(--card-space-inset);
144
+ }
145
+
110
146
  /* ── Header ──────────────────────────────────────────────────────────── */
111
147
  [data-slot="card-header"] {
112
148
  display: flex;
@@ -714,8 +714,18 @@
714
714
  .ui-otp-container {
715
715
  display: flex;
716
716
  align-items: center;
717
+ justify-content: var(--otp-container-align, flex-start);
717
718
  gap: var(--space-2);
718
719
  }
720
+ /* `align` on InputOTP lands on the hidden <input> (input-otp owns the container element), so the
721
+ * container reads it through :has() — the same mechanism this file already uses for the invalid
722
+ * and disabled states. Specificity puts the per-instance prop above the theme token. */
723
+ .ui-otp-container:has(.ui-otp-input[data-align="center"]) {
724
+ justify-content: center;
725
+ }
726
+ .ui-otp-container:has(.ui-otp-input[data-align="end"]) {
727
+ justify-content: flex-end;
728
+ }
719
729
  .ui-otp-input:disabled {
720
730
  cursor: not-allowed;
721
731
  }
@@ -726,8 +736,8 @@
726
736
  .ui-otp-slot {
727
737
  position: relative;
728
738
  display: flex;
729
- width: var(--otp-slot-size, var(--control-height));
730
- height: var(--otp-slot-size, var(--control-height));
739
+ width: var(--otp-slot-inline-size, var(--otp-slot-size, var(--control-height)));
740
+ height: var(--otp-slot-block-size, var(--otp-slot-size, var(--control-height)));
731
741
  align-items: center;
732
742
  justify-content: center;
733
743
  font-size: var(--font-size-base);
@@ -817,6 +827,11 @@
817
827
  display: inline-flex;
818
828
  align-items: center;
819
829
  gap: var(--space-1);
830
+ /* A 10-star scale needs ~276px of hit area; a 320px viewport offers ~212 inside a card. Without
831
+ * wrapping, the last stars are painted outside the surface with nothing to scroll them into
832
+ * view, so they can be neither seen nor clicked (WCAG 2.1.1 / 2.4.7). Wrapping keeps every star
833
+ * reachable and is a no-op wherever the row already fits. */
834
+ flex-wrap: wrap;
820
835
  }
821
836
  .ui-rating-star {
822
837
  /* ≥24px hit area (WCAG 2.5.8) around the 20px glyph */
@@ -30,6 +30,22 @@
30
30
  color: var(--avatar-square-foreground, hsl(var(--primary-foreground)));
31
31
  }
32
32
 
33
+ /* CAPABILITY MEDALLION — `<Avatar appearance="tinted">` (gh#12). The tinted companion of the
34
+ * solid entity mark: same box, same radius, a soft role wash instead of a solid fill and the
35
+ * glyph in the role itself. Declared AFTER the square rule (equal specificity) so it retints
36
+ * either shape. The glyph rule is scoped to this appearance ON PURPOSE — a global
37
+ * `.ui-avatar svg` would outrank the per-call-site icon classes existing avatars already use. */
38
+ .ui-avatar[data-appearance="tinted"] {
39
+ --avatar-background: var(--avatar-tinted-background, hsl(var(--primary) / 0.1));
40
+ color: var(--avatar-tinted-foreground, hsl(var(--primary)));
41
+ }
42
+
43
+ .ui-avatar[data-appearance="tinted"] svg {
44
+ inline-size: var(--avatar-tinted-glyph-size);
45
+ block-size: var(--avatar-tinted-glyph-size);
46
+ flex: none;
47
+ }
48
+
33
49
  .ui-avatar-image {
34
50
  width: 100%;
35
51
  height: 100%;