@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
@@ -3,9 +3,21 @@ import { type VariantProps } from "class-variance-authority";
3
3
  import type { LucideIcon } from "lucide-react";
4
4
  import type { HeadingLevelProp } from "../../props/vocabulary/index.js";
5
5
  type CardSize = "md" | "compact";
6
- /** Semantic leading-edge accent stripe (border-inline-start; width via the
7
- * --card-accent-rail-width token, default 6px). */
6
+ /** Semantic accent tone. `accentPlacement` decides WHERE it is drawn — a leading-edge
7
+ * stripe (default) or the full perimeter. */
8
8
  type CardAccent = "primary" | "success" | "warning" | "info" | "attention" | "destructive";
9
+ /**
10
+ * Where the semantic `accent` tone is drawn.
11
+ *
12
+ * - `"edge"` (default) — the leading-edge stripe on `border-inline-start` only, at the
13
+ * `--card-accent-rail-width` measure (6px). The classic "this row needs a look" rail.
14
+ * - `"perimeter"` — a full attention border around the WHOLE card in the same semantic
15
+ * tone, measured by `--card-accent-perimeter-width` + `--card-accent-perimeter-ring-width`.
16
+ * This is what `variant="featured"` does, except the tone is yours instead of `--primary`,
17
+ * so a card can shout "action required" (`accent="attention"`) or "this failed"
18
+ * (`accent="destructive"`) without borrowing the brand colour.
19
+ */
20
+ type CardAccentPlacement = "edge" | "perimeter";
9
21
  /** Surface fill — plain card, muted band, borderless outline, or emphasized featured ring. */
10
22
  type CardVariant = "default" | "muted" | "outline" | "featured";
11
23
  /** Padding density — base 16px · tight 12px · cozy 20px. */
@@ -16,6 +28,11 @@ declare const cardVariants: (props?: ({
16
28
  export type CardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof cardVariants> & {
17
29
  size?: CardSize;
18
30
  accent?: CardAccent;
31
+ /**
32
+ * Where `accent` is drawn — `"edge"` (default, the leading-edge stripe) or `"perimeter"`
33
+ * (a full attention border in the accent tone). Inert without `accent`.
34
+ */
35
+ accentPlacement?: CardAccentPlacement;
19
36
  variant?: CardVariant;
20
37
  density?: CardDensity;
21
38
  };
@@ -24,6 +41,11 @@ export declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<
24
41
  } & import("class-variance-authority/types").ClassProp) | undefined) => string> & {
25
42
  size?: CardSize;
26
43
  accent?: CardAccent;
44
+ /**
45
+ * Where `accent` is drawn — `"edge"` (default, the leading-edge stripe) or `"perimeter"`
46
+ * (a full attention border in the accent tone). Inert without `accent`.
47
+ */
48
+ accentPlacement?: CardAccentPlacement;
27
49
  variant?: CardVariant;
28
50
  density?: CardDensity;
29
51
  } & React.RefAttributes<HTMLDivElement>>;
@@ -136,9 +158,11 @@ export type StatCardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<
136
158
  inverse?: boolean;
137
159
  /** Semantic leading-edge rail (Card accent) — flags a KPI needing attention. */
138
160
  accent?: CardAccent;
161
+ /** Where `accent` is drawn — `"edge"` (default rail) or `"perimeter"` (full attention border). */
162
+ accentPlacement?: CardAccentPlacement;
139
163
  };
140
164
  /** KPI / stat tile — token-driven layout aligned to dashboard KPI cards. */
141
- export declare function StatCard({ label, value, hint, icon: Icon, delta, layout, align, inverse, accent, className, size, ...props }: StatCardProps): React.JSX.Element;
165
+ export declare function StatCard({ label, value, hint, icon: Icon, delta, layout, align, inverse, accent, accentPlacement, className, size, ...props }: StatCardProps): React.JSX.Element;
142
166
  /** Header actions slot — pair with `CardHeader className="flex flex-row …"`. */
143
167
  export declare const CardAction: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
144
168
  export {};
@@ -12,7 +12,7 @@ const cardVariants = cva("group/card", {
12
12
  defaultVariants: { size: "md" }
13
13
  });
14
14
  const Card = React.forwardRef(
15
- ({ className, size = "md", accent, variant, density, ...props }, ref) => /* @__PURE__ */ jsx(
15
+ ({ className, size = "md", accent, accentPlacement, variant, density, ...props }, ref) => /* @__PURE__ */ jsx(
16
16
  "div",
17
17
  {
18
18
  ref,
@@ -20,6 +20,7 @@ const Card = React.forwardRef(
20
20
  "data-slot": "card",
21
21
  "data-size": size === "compact" ? "compact" : void 0,
22
22
  "data-accent": accent,
23
+ "data-accent-placement": accentPlacement === "perimeter" ? "perimeter" : void 0,
23
24
  "data-variant": variant && variant !== "default" ? variant : void 0,
24
25
  "data-density": density,
25
26
  ...props
@@ -108,6 +109,7 @@ function StatCard({
108
109
  align = "start",
109
110
  inverse = false,
110
111
  accent,
112
+ accentPlacement,
111
113
  className,
112
114
  size = "compact",
113
115
  ...props
@@ -118,6 +120,7 @@ function StatCard({
118
120
  {
119
121
  size: size ?? "compact",
120
122
  accent,
123
+ accentPlacement,
121
124
  className: cn("ui-stat-card", className),
122
125
  "data-stat-card": "",
123
126
  "data-stat-layout": layout,
@@ -636,9 +636,8 @@ DataTable.Content = function DataTableContent() {
636
636
  ),
637
637
  children: col.render ? col.render(original) : (() => {
638
638
  const v = original[col.key];
639
- if (v == null) return "\u2014";
640
- if (typeof v === "string" || typeof v === "number") return String(v);
641
- return "\u2014";
639
+ const text = v == null || !(typeof v === "string" || typeof v === "number") ? "\u2014" : String(v);
640
+ return /* @__PURE__ */ jsx("span", { "data-slot": "table-cell-text", children: text });
642
641
  })()
643
642
  },
644
643
  col.key
@@ -29,9 +29,50 @@ export type CommandPaletteProps = {
29
29
  open?: boolean;
30
30
  defaultOpen?: boolean;
31
31
  onOpenChange?: (open: boolean) => void;
32
+ /**
33
+ * Controlled search-box query. Pairs with `onSearchChange`; leave it out for the uncontrolled
34
+ * palette (seeded by `defaultSearch`). Same idiom as `SearchSelect`'s `search`.
35
+ */
36
+ search?: string;
37
+ /** Initial uncontrolled query, and the value the palette resets to when it closes. Default `""`. */
38
+ defaultSearch?: string;
39
+ /**
40
+ * Fires on every keystroke with the current query — the seam a server-backed group needs to run
41
+ * search-as-you-type. Also fires with `defaultSearch` when the palette closes.
42
+ */
43
+ onSearchChange?: (query: string) => void;
44
+ /**
45
+ * Whether the palette filters `groups` itself (cmdk's client-side fuzzy match). Set `false` when
46
+ * the QUERY is answered by a server and `groups` already holds the matches — otherwise every row
47
+ * is scored a second time against the same string and late-arriving matches are dropped.
48
+ *
49
+ * It also decides who owns the empty node; see the component doc block. Default `true`.
50
+ */
51
+ shouldFilter?: boolean;
32
52
  loading?: boolean;
33
53
  error?: React.ReactNode;
34
54
  trigger?: React.ReactNode;
35
55
  shortcut?: boolean;
36
56
  };
37
- export declare function CommandPalette({ groups, labels, onSelect, open: controlledOpen, defaultOpen, onOpenChange, loading, error, trigger, shortcut, }: CommandPaletteProps): React.JSX.Element;
57
+ /**
58
+ * CommandPalette — the ⌘K search dialog.
59
+ *
60
+ * ## The empty-state contract (gh#412)
61
+ *
62
+ * `labels.empty` renders under exactly one rule, and which mechanism decides follows `shouldFilter`:
63
+ *
64
+ * - **`shouldFilter` (default, client-side)** — cmdk owns it: the palette holds items, the query
65
+ * matches none of them. Unchanged.
66
+ * - **`shouldFilter={false}` (server-side)** — the PALETTE owns it, derived synchronously from
67
+ * props: the empty node renders when `groups` carries no items, and never while `loading` or
68
+ * `error` is set. cmdk's own empty node cannot be trusted here, because it is driven by a
69
+ * scheduled count of the items that have REGISTERED in the DOM, not by what the consumer knows —
70
+ * so an async group that populates a frame late flips it on and off and any assertion over it
71
+ * races. Reading `groups` instead makes "did we render the empty state?" a pure function of the
72
+ * props at that render, which is what a contract test can assert.
73
+ *
74
+ * The practical consequence for search-as-you-type: hold `loading` true for the whole in-flight
75
+ * window. While it is true the palette shows `labels.loading` and never claims "no results" —
76
+ * a request that has not answered yet is not an empty result.
77
+ */
78
+ export declare function CommandPalette({ groups, labels, onSelect, open: controlledOpen, defaultOpen, onOpenChange, search: controlledSearch, defaultSearch, onSearchChange, shouldFilter, loading, error, trigger, shortcut, }: CommandPaletteProps): React.JSX.Element;
@@ -19,14 +19,20 @@ function CommandPalette({
19
19
  open: controlledOpen,
20
20
  defaultOpen = false,
21
21
  onOpenChange,
22
+ search: controlledSearch,
23
+ defaultSearch = "",
24
+ onSearchChange,
25
+ shouldFilter = true,
22
26
  loading = false,
23
27
  error,
24
28
  trigger,
25
29
  shortcut = true
26
30
  }) {
27
31
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
32
+ const [uncontrolledSearch, setUncontrolledSearch] = React.useState(defaultSearch);
28
33
  const shortcutRestoreFocusRef = React.useRef(null);
29
34
  const open = controlledOpen ?? uncontrolledOpen;
35
+ const search = controlledSearch ?? uncontrolledSearch;
30
36
  const setOpen = React.useCallback(
31
37
  (next) => {
32
38
  if (controlledOpen === void 0) {
@@ -36,6 +42,22 @@ function CommandPalette({
36
42
  },
37
43
  [controlledOpen, onOpenChange]
38
44
  );
45
+ const setSearch = React.useCallback(
46
+ (next) => {
47
+ if (controlledSearch === void 0) {
48
+ setUncontrolledSearch(next);
49
+ }
50
+ onSearchChange?.(next);
51
+ },
52
+ [controlledSearch, onSearchChange]
53
+ );
54
+ const previousOpenRef = React.useRef(open);
55
+ React.useEffect(() => {
56
+ const wasOpen = previousOpenRef.current;
57
+ previousOpenRef.current = open;
58
+ if (!wasOpen || open || search === defaultSearch) return;
59
+ setSearch(defaultSearch);
60
+ }, [open, search, defaultSearch, setSearch]);
39
61
  React.useEffect(() => {
40
62
  if (!shortcut) return;
41
63
  const handleKeyDown = (event) => {
@@ -50,6 +72,7 @@ function CommandPalette({
50
72
  document.addEventListener("keydown", handleKeyDown);
51
73
  return () => document.removeEventListener("keydown", handleKeyDown);
52
74
  }, [open, setOpen, shortcut]);
75
+ const hasItems = React.useMemo(() => groups.some((group) => group.items.length > 0), [groups]);
53
76
  const triggerNode = trigger ?? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ui-command-palette-trigger", children: [
54
77
  /* @__PURE__ */ jsx(Search, { "aria-hidden": "true" }),
55
78
  /* @__PURE__ */ jsx("span", { children: labels.open }),
@@ -79,17 +102,23 @@ function CommandPalette({
79
102
  children: [
80
103
  /* @__PURE__ */ jsx(Dialog.Title, { className: "sr-only", children: labels.title }),
81
104
  /* @__PURE__ */ jsx(Dialog.Description, { id: "ui-command-palette-description", className: "sr-only", children: labels.description }),
82
- /* @__PURE__ */ jsxs(Command, { label: labels.title, children: [
105
+ /* @__PURE__ */ jsxs(Command, { label: labels.title, shouldFilter, children: [
83
106
  /* @__PURE__ */ jsx(
84
107
  CommandInput,
85
108
  {
86
109
  autoFocus: true,
110
+ value: search,
111
+ onValueChange: setSearch,
87
112
  placeholder: labels.placeholder,
88
113
  "aria-label": labels.placeholder
89
114
  }
90
115
  ),
91
116
  /* @__PURE__ */ jsx(CommandList, { "aria-busy": loading, children: loading ? /* @__PURE__ */ jsx("div", { className: "ui-command-palette-state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsx("div", { className: "ui-command-palette-state", role: "alert", children: error }) : /* @__PURE__ */ jsxs(Fragment, { children: [
92
- /* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty }),
117
+ shouldFilter ? /* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty }) : hasItems ? null : (
118
+ // Same node cmdk would have rendered (class + role), decided from `groups`
119
+ // instead of from its scheduled DOM-registration count — see the doc block.
120
+ /* @__PURE__ */ jsx("div", { className: "ui-command-empty", role: "presentation", children: labels.empty })
121
+ ),
93
122
  groups.map((group) => /* @__PURE__ */ jsx(CommandGroup, { heading: group.label, children: group.items.map((item) => /* @__PURE__ */ jsxs(
94
123
  CommandItem,
95
124
  {
@@ -1,2 +1,2 @@
1
1
  export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "../ui/input-otp.js";
2
- export type { InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProps, } from "../ui/input-otp.js";
2
+ export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProps, } from "../ui/input-otp.js";
@@ -1,7 +1,13 @@
1
1
  import * as React from "react";
2
2
  import * as SelectPrimitive from "@radix-ui/react-select";
3
+ import type { FieldA11yProps } from "../../lib/field-a11y.js";
3
4
  import type { SelectDataProp } from "../../props/components/data-entry.prop.js";
4
- export type SelectProp = SelectDataProp | React.ComponentProps<typeof SelectPrimitive.Root>;
5
+ /** Compound-API props: the Radix root's own props PLUS the FormField field-a11y contract, which
6
+ * this component re-routes to the trigger (see {@link SelectFieldA11yContext}). */
7
+ export type SelectCompoundProp = React.ComponentProps<typeof SelectPrimitive.Root> & FieldA11yProps & {
8
+ id?: string;
9
+ };
10
+ export type SelectProp = SelectDataProp | SelectCompoundProp;
5
11
  /**
6
12
  * Select — one component for every single-select. Use the compound API for full control
7
13
  * (`<Select><SelectTrigger/><SelectContent><SelectItem/></SelectContent></Select>`), OR the
@@ -5,15 +5,22 @@ import * as SelectPrimitive from "@radix-ui/react-select";
5
5
  import { ChevronDown, ChevronUp } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { controlTriggerClass } from "../../lib/control-styles.js";
8
+ import { mergeAriaIds, omitFieldA11y, pickFieldA11y } from "../../lib/field-a11y.js";
8
9
  import { SearchSelect } from "./search-select.js";
9
10
  function isDataSelect(props) {
10
11
  return "options" in props || "loadOptions" in props;
11
12
  }
13
+ const SelectFieldA11yContext = React.createContext(null);
12
14
  function Select(props) {
13
15
  if (isDataSelect(props)) {
14
16
  return /* @__PURE__ */ jsx(DataSelect, { ...props });
15
17
  }
16
- return /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
18
+ return /* @__PURE__ */ jsx(CompoundSelect, { ...props });
19
+ }
20
+ function CompoundSelect({ id, ...props }) {
21
+ const fieldA11y = pickFieldA11y(props);
22
+ const rootProps = omitFieldA11y(props);
23
+ return /* @__PURE__ */ jsx(SelectFieldA11yContext.Provider, { value: { ...fieldA11y, id }, children: /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...rootProps }) });
17
24
  }
18
25
  function SelectGroup(props) {
19
26
  return /* @__PURE__ */ jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
@@ -21,31 +28,44 @@ function SelectGroup(props) {
21
28
  function SelectValue(props) {
22
29
  return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
23
30
  }
24
- const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => /* @__PURE__ */ jsxs(
25
- SelectPrimitive.Trigger,
26
- {
27
- ref,
28
- "data-slot": "select-trigger",
29
- "data-size": size,
30
- className: cn(
31
- controlTriggerClass,
32
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-full gap-2 whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
33
- className
34
- ),
35
- ...props,
36
- children: [
37
- children,
38
- showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
39
- ChevronDown,
40
- {
41
- "data-slot": "select-chevron",
42
- className: "size-4 shrink-0 opacity-50",
43
- "aria-hidden": "true"
44
- }
45
- ) }) : null
46
- ]
47
- }
48
- ));
31
+ const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => {
32
+ const field = React.useContext(SelectFieldA11yContext);
33
+ const ownsName = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
34
+ const fieldA11y = field ? {
35
+ id: props.id ?? field.id,
36
+ ...ownsName ? {} : { "aria-label": field["aria-label"], "aria-labelledby": field["aria-labelledby"] },
37
+ "aria-describedby": mergeAriaIds(props["aria-describedby"], field["aria-describedby"]),
38
+ "aria-errormessage": mergeAriaIds(props["aria-errormessage"], field["aria-errormessage"]),
39
+ "aria-required": props["aria-required"] ?? field["aria-required"],
40
+ "aria-invalid": props["aria-invalid"] ?? field["aria-invalid"]
41
+ } : void 0;
42
+ return /* @__PURE__ */ jsxs(
43
+ SelectPrimitive.Trigger,
44
+ {
45
+ ref,
46
+ "data-slot": "select-trigger",
47
+ "data-size": size,
48
+ className: cn(
49
+ controlTriggerClass,
50
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-full gap-2 whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
51
+ className
52
+ ),
53
+ ...props,
54
+ ...fieldA11y,
55
+ children: [
56
+ children,
57
+ showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
58
+ ChevronDown,
59
+ {
60
+ "data-slot": "select-chevron",
61
+ className: "size-4 shrink-0 opacity-50",
62
+ "aria-hidden": "true"
63
+ }
64
+ ) }) : null
65
+ ]
66
+ }
67
+ );
68
+ });
49
69
  SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
50
70
  const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
51
71
  SelectPrimitive.ScrollUpButton,
@@ -2,7 +2,11 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { AlertActions, AlertBase, AlertContent, AlertDescription, AlertTitle } from "./alert.js";
5
- const BannerBase = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(AlertBase, { ref, variant: "banner", ...props }));
5
+ const BannerBase = React.forwardRef((props, ref) => (
6
+ // `variant` sits AFTER the spread: the type already excludes it, and the runtime half of the
7
+ // same guarantee means a spread of leftover Alert props can never silently downgrade the strip.
8
+ /* @__PURE__ */ jsx(AlertBase, { ref, ...props, variant: "banner" })
9
+ ));
6
10
  BannerBase.displayName = "Banner";
7
11
  const Banner = Object.assign(BannerBase, {
8
12
  Title: AlertTitle,
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
2
2
  export type { PageContainerProp, PageContainerProp as PageContainerProps, } from "../../props/components/layout.prop.js";
3
3
  export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
4
4
  export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
5
- declare function PageContainerRoot({ title, subtitle, status, extra, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, density, variant, preset, headerLayout, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
5
+ declare function PageContainerRoot({ title, subtitle, status, extra, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, density, variant, preset, headerLayout, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
6
6
  export declare const PageContainer: typeof PageContainerRoot & {
7
7
  Inset: typeof PageContainerInset;
8
8
  };
@@ -43,6 +43,7 @@ function PageContainerRoot({
43
43
  breadcrumbLabel,
44
44
  breadcrumbAriaLabel,
45
45
  linkComponent: LinkComponent = "a",
46
+ headerLoading = false,
46
47
  density,
47
48
  variant = "default",
48
49
  preset = "default",
@@ -75,47 +76,66 @@ function PageContainerRoot({
75
76
  className
76
77
  ),
77
78
  children: [
78
- /* @__PURE__ */ jsxs("header", { ref: headerRef, className: "ui-page-header", "data-layout": headerLayout, children: [
79
- breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx(
80
- "nav",
81
- {
82
- "aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
83
- className: "ui-breadcrumb",
84
- children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
85
- const isLast = i === breadcrumb.length - 1;
86
- return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
87
- item.to && !isLast ? /* @__PURE__ */ jsx(
88
- LinkComponent,
89
- {
90
- href: item.to,
91
- to: item.to,
92
- className: "hover:text-foreground hover:underline",
93
- children: item.label
94
- }
95
- ) : /* @__PURE__ */ jsx(
96
- "span",
97
- {
98
- className: isLast ? "text-foreground" : "",
99
- "aria-current": isLast ? "page" : void 0,
100
- children: item.label
101
- }
102
- ),
103
- !isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
104
- ] }, i);
105
- }) })
106
- }
107
- ),
108
- /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
109
- /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
110
- status != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-header-title-row", children: [
111
- /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
112
- /* @__PURE__ */ jsx("div", { className: "ui-page-header-status", children: status })
113
- ] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
114
- subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
115
- ] }),
116
- extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
117
- ] })
118
- ] }),
79
+ /* @__PURE__ */ jsxs(
80
+ "header",
81
+ {
82
+ ref: headerRef,
83
+ className: "ui-page-header",
84
+ "data-layout": headerLayout,
85
+ "aria-busy": headerLoading ? "true" : void 0,
86
+ children: [
87
+ breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx(
88
+ "nav",
89
+ {
90
+ "aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
91
+ className: "ui-breadcrumb",
92
+ children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
93
+ const isLast = i === breadcrumb.length - 1;
94
+ return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
95
+ item.to && !isLast ? /* @__PURE__ */ jsx(
96
+ LinkComponent,
97
+ {
98
+ href: item.to,
99
+ to: item.to,
100
+ className: "hover:text-foreground hover:underline",
101
+ children: item.label
102
+ }
103
+ ) : /* @__PURE__ */ jsx(
104
+ "span",
105
+ {
106
+ className: isLast ? "text-foreground" : "",
107
+ "aria-current": isLast ? "page" : void 0,
108
+ children: item.label
109
+ }
110
+ ),
111
+ !isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
112
+ ] }, i);
113
+ }) })
114
+ }
115
+ ),
116
+ /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
117
+ /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
118
+ headerLoading ? /* @__PURE__ */ jsx("h1", { className: "ui-page-title ui-skeleton-block ui-page-title-placeholder", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") }) }) : status != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-header-title-row", children: [
119
+ /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
120
+ /* @__PURE__ */ jsx("div", { className: "ui-page-header-status", children: status })
121
+ ] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
122
+ headerLoading ? (
123
+ // Decorative only — the pending state is already announced once by the heading above,
124
+ // so a second live placeholder here would double-announce it.
125
+ /* @__PURE__ */ jsx(
126
+ "p",
127
+ {
128
+ className: "ui-page-subtitle ui-skeleton-block ui-page-subtitle-placeholder",
129
+ "aria-hidden": "true"
130
+ }
131
+ )
132
+ ) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
133
+ ] }),
134
+ extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
135
+ ] })
136
+ ]
137
+ }
138
+ ),
119
139
  children != null && /* @__PURE__ */ jsx("div", { className: "ui-page-body", children }),
120
140
  footer && /* @__PURE__ */ jsx("footer", { className: "ui-page-footer", children: footer })
121
141
  ]
@@ -5,7 +5,7 @@ export declare const ResizablePanelGroup: {
5
5
  displayName: string;
6
6
  };
7
7
  export declare const ResizablePanel: {
8
- ({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
8
+ ({ className, elementRef, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
9
9
  displayName: string;
10
10
  };
11
11
  export declare const ResizableHandle: {
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
3
4
  import * as ResizablePrimitive from "react-resizable-panels";
4
5
  import { cn } from "../../lib/utils.js";
6
+ import { useScrollableRegionTabIndex } from "../../lib/hooks.js";
5
7
  const ResizablePanelGroup = ({
6
8
  className,
7
9
  ...props
@@ -16,15 +18,31 @@ const ResizablePanelGroup = ({
16
18
  ResizablePanelGroup.displayName = "ResizablePanelGroup";
17
19
  const ResizablePanel = ({
18
20
  className,
21
+ elementRef,
19
22
  ...props
20
- }) => /* @__PURE__ */ jsx(
21
- ResizablePrimitive.Panel,
22
- {
23
- "data-slot": "resizable-panel",
24
- className: cn("ui-resizable-panel", className),
25
- ...props
26
- }
27
- );
23
+ }) => {
24
+ const [scrollRegion, setScrollRegion] = React.useState(null);
25
+ const setRootElement = React.useCallback(
26
+ (node) => {
27
+ setScrollRegion(
28
+ node?.firstElementChild instanceof HTMLElement ? node.firstElementChild : null
29
+ );
30
+ if (typeof elementRef === "function") elementRef(node);
31
+ else if (elementRef) elementRef.current = node;
32
+ },
33
+ [elementRef]
34
+ );
35
+ useScrollableRegionTabIndex(scrollRegion);
36
+ return /* @__PURE__ */ jsx(
37
+ ResizablePrimitive.Panel,
38
+ {
39
+ "data-slot": "resizable-panel",
40
+ className: cn("ui-resizable-panel", className),
41
+ elementRef: setRootElement,
42
+ ...props
43
+ }
44
+ );
45
+ };
28
46
  ResizablePanel.displayName = "ResizablePanel";
29
47
  const ResizableHandle = ({
30
48
  className,
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { useScrollableRegionTabIndex } from "../../lib/hooks.js";
6
7
  import { cn } from "../../lib/utils.js";
7
8
  import { Button } from "../general/button.js";
8
9
  import {
@@ -14,7 +15,25 @@ import {
14
15
  } from "../data-entry/select.js";
15
16
  import { buildPageRange } from "./pagination-utils.js";
16
17
  const PaginationContent = React.forwardRef(({ className, ...props }, ref) => {
17
- return /* @__PURE__ */ jsx("ul", { ref, className: cn("ui-pagination-list", className), role: "list", ...props });
18
+ const [scrollRegion, setScrollRegion] = React.useState(null);
19
+ const setListElement = React.useCallback(
20
+ (node) => {
21
+ setScrollRegion(node);
22
+ if (typeof ref === "function") ref(node);
23
+ else if (ref) ref.current = node;
24
+ },
25
+ [ref]
26
+ );
27
+ useScrollableRegionTabIndex(scrollRegion);
28
+ return /* @__PURE__ */ jsx(
29
+ "ul",
30
+ {
31
+ ref: setListElement,
32
+ className: cn("ui-pagination-list", className),
33
+ role: "list",
34
+ ...props
35
+ }
36
+ );
18
37
  });
19
38
  PaginationContent.displayName = "PaginationContent";
20
39
  const PaginationItem = React.forwardRef(
@@ -1,4 +1,4 @@
1
1
  import * as React from "react";
2
2
  import type { StepsProp } from "../../props/components/navigation.prop.js";
3
- export type { StepsProp, StepsProp as StepsProps, StepItemProp, StepStatusProp, } from "../../props/components/navigation.prop.js";
4
- export declare function Steps({ items, value: current, defaultValue, status: currentStatus, orientation, type, size, titlePlacement, onValueChange, className, }: StepsProp): React.JSX.Element;
3
+ export type { StepsProp, StepsProp as StepsProps, StepItemProp, StepStatusProp, StepsSeparatorProp, } from "../../props/components/navigation.prop.js";
4
+ export declare function Steps({ items, value: current, defaultValue, status: currentStatus, orientation, type, size, titlePlacement, separator, onValueChange, className, }: StepsProp): React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Check, ChevronRight, Circle, Loader2, X } from "lucide-react";
3
+ import { ArrowRight, Check, ChevronRight, Circle, Loader2, X } from "lucide-react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { controlIconClass } from "../../lib/control-styles.js";
@@ -61,6 +61,7 @@ function Steps({
61
61
  type = "default",
62
62
  size = "md",
63
63
  titlePlacement = "horizontal",
64
+ separator = "chevron",
64
65
  onValueChange,
65
66
  className
66
67
  }) {
@@ -69,6 +70,7 @@ function Steps({
69
70
  const isVertical = orientation === "vertical";
70
71
  const compact = size === "sm";
71
72
  const inline = type === "inline";
73
+ const SeparatorIcon = separator === "arrow" ? ArrowRight : ChevronRight;
72
74
  return /* @__PURE__ */ jsx(
73
75
  "ol",
74
76
  {
@@ -151,7 +153,7 @@ function Steps({
151
153
  }
152
154
  ) : /* @__PURE__ */ jsx("span", { className: stepClassName, children: stepInner }),
153
155
  inline && index < items.length - 1 ? /* @__PURE__ */ jsx(
154
- ChevronRight,
156
+ SeparatorIcon,
155
157
  {
156
158
  className: "ui-steps-inline-separator rtl:rotate-180",
157
159
  "aria-hidden": "true"