@godxjp/ui 33.19.0 → 33.21.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 (121) hide show
  1. package/README.md +4 -4
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/AppSettingPicker.json +0 -1
  4. package/agent/components/AppShell.json +11 -1
  5. package/agent/components/CardShowcase.json +44 -0
  6. package/agent/components/CatalogCard.json +83 -0
  7. package/agent/components/CatalogDetail.json +76 -0
  8. package/agent/components/CatalogDetailSection.json +52 -0
  9. package/agent/components/CatalogDetailTag.json +27 -0
  10. package/agent/components/Descriptions.json +6 -0
  11. package/agent/components/EditorialArticle.json +53 -0
  12. package/agent/components/EditorialAuth.json +53 -0
  13. package/agent/components/EditorialAuthCard.json +52 -0
  14. package/agent/components/EditorialBanner.json +53 -0
  15. package/agent/components/EditorialBreadcrumb.json +38 -0
  16. package/agent/components/EditorialCard.json +70 -0
  17. package/agent/components/EditorialEmptyPanel.json +57 -0
  18. package/agent/components/EditorialFilterBar.json +51 -0
  19. package/agent/components/EditorialFilterField.json +39 -0
  20. package/agent/components/EditorialFilterPanel.json +46 -0
  21. package/agent/components/EditorialFooter.json +54 -0
  22. package/agent/components/EditorialFootnote.json +36 -0
  23. package/agent/components/EditorialHero.json +52 -0
  24. package/agent/components/EditorialInlineSearch.json +54 -0
  25. package/agent/components/EditorialMasthead.json +52 -0
  26. package/agent/components/EditorialMetaBar.json +37 -0
  27. package/agent/components/EditorialPageHeader.json +73 -0
  28. package/agent/components/EditorialPanel.json +48 -0
  29. package/agent/components/EditorialPillNav.json +41 -0
  30. package/agent/components/EditorialPrompt.json +54 -0
  31. package/agent/components/EditorialSearch.json +112 -0
  32. package/agent/components/EditorialSection.json +68 -0
  33. package/agent/components/EditorialSectionHead.json +62 -0
  34. package/agent/components/EditorialStats.json +32 -0
  35. package/agent/components/EditorialTextButton.json +47 -0
  36. package/agent/components/EditorialToggleGroup.json +52 -0
  37. package/agent/components/EditorialToolbar.json +45 -0
  38. package/agent/components/Eyebrow.json +39 -0
  39. package/agent/components/HoldingCard.json +69 -0
  40. package/agent/components/HoldingCardAction.json +51 -0
  41. package/agent/components/HoldingGrid.json +37 -0
  42. package/agent/components/NumberInput.json +5 -0
  43. package/agent/components/PriceSummary.json +32 -0
  44. package/agent/components/SettingsLayout.json +31 -0
  45. package/agent/components/SettingsPanel.json +53 -0
  46. package/agent/components/Tabs.json +2 -2
  47. package/agent/components/ThemePicker.json +54 -0
  48. package/agent/components/TopbarItem.json +5 -0
  49. package/agent/components-index.json +292 -98
  50. package/agent/components.json +2054 -4
  51. package/agent/index.json +7 -7
  52. package/agent/llms.txt +8 -8
  53. package/agent/tokens.json +5646 -126
  54. package/dist/components/data-display/badge.js +4 -2
  55. package/dist/components/data-display/descriptions.d.ts +5 -1
  56. package/dist/components/data-display/descriptions.js +21 -9
  57. package/dist/components/data-entry/form-field.js +2 -2
  58. package/dist/components/data-entry/number-input.d.ts +1 -0
  59. package/dist/components/data-entry/number-input.js +8 -0
  60. package/dist/components/data-entry/select.js +24 -2
  61. package/dist/components/data-entry/toggle.d.ts +2 -1
  62. package/dist/components/data-entry/toggle.js +12 -2
  63. package/dist/components/general/button.js +7 -0
  64. package/dist/components/general/typography.d.ts +1 -1
  65. package/dist/components/layout/app-shell.d.ts +1 -1
  66. package/dist/components/layout/app-shell.js +28 -2
  67. package/dist/components/layout/centered-shell.js +4 -0
  68. package/dist/components/layout/editorial-account.d.ts +51 -0
  69. package/dist/components/layout/editorial-account.js +466 -0
  70. package/dist/components/layout/editorial-catalog.d.ts +18 -0
  71. package/dist/components/layout/editorial-catalog.js +164 -0
  72. package/dist/components/layout/editorial.d.ts +56 -0
  73. package/dist/components/layout/editorial.js +867 -0
  74. package/dist/components/layout/index.d.ts +6 -0
  75. package/dist/components/layout/index.js +98 -0
  76. package/dist/components/layout/mobile-shell.d.ts +2 -0
  77. package/dist/components/layout/mobile-shell.js +18 -11
  78. package/dist/components/layout/responsive-grid.js +9 -1
  79. package/dist/components/layout/topbar-item.d.ts +1 -0
  80. package/dist/components/layout/topbar-item.js +2 -0
  81. package/dist/components/navigation/tabs.d.ts +1 -1
  82. package/dist/components/navigation/tabs.js +17 -5
  83. package/dist/contracts/measurement.json +1 -1
  84. package/dist/lib/toast-clearance.js +1 -1
  85. package/dist/props/components/data-display.prop.d.ts +3 -1
  86. package/dist/props/components/data-entry.prop.d.ts +9 -0
  87. package/dist/props/components/editorial-account.prop.d.ts +249 -0
  88. package/dist/props/components/editorial-account.prop.js +0 -0
  89. package/dist/props/components/general.prop.d.ts +6 -2
  90. package/dist/props/components/layout.prop.d.ts +602 -2
  91. package/dist/props/components/navigation.prop.d.ts +2 -0
  92. package/dist/props/registry.d.ts +1140 -8
  93. package/dist/props/registry.js +805 -1
  94. package/dist/styles/badge-layout.css +2 -0
  95. package/dist/styles/control.css +58 -12
  96. package/dist/styles/core.css +1 -0
  97. package/dist/styles/data-display-layout.css +39 -3
  98. package/dist/styles/editorial-account-layout.css +1130 -0
  99. package/dist/styles/focus-ring.css +33 -10
  100. package/dist/styles/form-layout.css +8 -1
  101. package/dist/styles/index.css +1 -0
  102. package/dist/styles/layers.json +248 -1
  103. package/dist/styles/layout.css +2022 -0
  104. package/dist/styles/navigation-layout.css +5 -0
  105. package/dist/styles/shell-layout.css +100 -2
  106. package/dist/styles/table-layout.css +25 -13
  107. package/dist/styles/text-layout.css +6 -0
  108. package/dist/styles.css +1 -1
  109. package/dist/tokens/base.css +2 -0
  110. package/dist/tokens/components/badge.css +3 -0
  111. package/dist/tokens/components/control.css +9 -0
  112. package/dist/tokens/components/descriptions.css +2 -0
  113. package/dist/tokens/components/editorial-account.css +393 -0
  114. package/dist/tokens/components/editorial.css +721 -0
  115. package/dist/tokens/components/form.css +4 -0
  116. package/dist/tokens/components/navigation.css +2 -0
  117. package/dist/tokens/components/shell.css +23 -0
  118. package/dist/tokens/components/table.css +1 -1
  119. package/docs/TOKENS.md +26 -0
  120. package/package.json +2 -2
  121. package/scripts/ui-audit.mjs +3 -2
@@ -62,8 +62,10 @@ const badgeVariants = cva(
62
62
  variant: {
63
63
  default: "border-transparent bg-primary text-primary-foreground",
64
64
  secondary: "border-transparent bg-secondary text-secondary-foreground",
65
- outline: "text-foreground",
66
- dashed: "border-dashed text-foreground",
65
+ // gh#1439 — the outline / dashed ink is a role-mirror knob (default --foreground), so a
66
+ // brand whose plain chip reads muted sets it once instead of wrapping every label.
67
+ outline: "text-[color:var(--badge-outline-foreground,hsl(var(--foreground)))]",
68
+ dashed: "border-dashed text-[color:var(--badge-outline-foreground,hsl(var(--foreground)))]",
67
69
  /* `color` mode. Deliberately empty and deliberately NOT in the public
68
70
  * `BadgeVariant` union: every fill, border and text utility has to stay
69
71
  * off the element so badge-layout.css's `[data-tinted]` rule is
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import type { WidthProp } from "../../props/vocabulary/shared.prop.js";
2
3
  import type { DescriptionsLayoutProp } from "../../props/vocabulary/interaction.prop.js";
3
4
  import type { DescriptionsColumnProp, DescriptionsItemsProp, DescriptionsSpanProp } from "../../props/vocabulary/data.prop.js";
4
5
  export type { DescriptionsLayoutProp };
@@ -16,6 +17,9 @@ export interface DescriptionsProps {
16
17
  * it there would read as a mistake, exactly like `Form`'s own contract.
17
18
  */
18
19
  labelAlign?: "start" | "end";
20
+ /** Horizontal label width. Omitted/auto keeps the theme width; content shares the longest
21
+ * label track per visual column. CSS lengths (numbers are px) override the theme per list. */
22
+ labelWidth?: WidthProp | "content";
19
23
  /**
20
24
  * Draw the grid as a bordered table (antd `bordered`): an outer frame, a rule between every
21
25
  * cell, and a shaded label cell. Colour and rhythm come from the `--descriptions-*` tokens.
@@ -29,7 +33,7 @@ export interface DescriptionsProps {
29
33
  className?: string;
30
34
  children?: React.ReactNode;
31
35
  }
32
- export declare function Descriptions({ columns, layout, labelAlign, bordered, items, className, children, }: DescriptionsProps): React.JSX.Element;
36
+ export declare function Descriptions({ columns, layout, labelAlign, labelWidth, bordered, items, className, children, }: DescriptionsProps): React.JSX.Element;
33
37
  export declare namespace Descriptions {
34
38
  export { DescriptionsItem as Item };
35
39
  }
@@ -5,7 +5,8 @@ import { cn } from "../../lib/utils.js";
5
5
  const DescriptionsLayoutContext = React.createContext({
6
6
  layout: "vertical",
7
7
  labelAlign: "start",
8
- bordered: false
8
+ bordered: false,
9
+ contentLabels: false
9
10
  });
10
11
  const BREAKPOINT_LADDER = ["sm", "md", "lg", "xl"];
11
12
  function responsiveVars(name, base, steps) {
@@ -25,6 +26,7 @@ function Descriptions({
25
26
  columns = 2,
26
27
  layout = "vertical",
27
28
  labelAlign = "start",
29
+ labelWidth,
28
30
  bordered = false,
29
31
  items,
30
32
  className,
@@ -32,21 +34,31 @@ function Descriptions({
32
34
  }) {
33
35
  const legacyColumns = typeof columns === "number" ? LEGACY_COLUMN_CLASS[columns] : void 0;
34
36
  const responsiveColumns = legacyColumns === void 0 ? typeof columns === "number" ? { sm: Math.min(columns, 2), lg: columns } : columns : void 0;
37
+ const intrinsicWidth = labelWidth === "content" || labelWidth === "max-content" || labelWidth === "min-content" || labelWidth === "fit-content";
38
+ const contentLabels = layout === "horizontal" && intrinsicWidth;
39
+ const labelStyle = layout === "horizontal" && labelWidth !== void 0 && labelWidth !== "auto" && !intrinsicWidth ? {
40
+ "--descriptions-label-width": typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth
41
+ } : {};
42
+ const contentColumns = contentLabels && !responsiveColumns ? typeof columns === "number" ? columns === 3 ? { sm: 2, lg: 3 } : columns === 2 ? { sm: 2 } : {} : columns : responsiveColumns;
35
43
  const context = React.useMemo(
36
- () => ({ layout, labelAlign, bordered }),
37
- [layout, labelAlign, bordered]
44
+ () => ({ layout, labelAlign, bordered, contentLabels }),
45
+ [layout, labelAlign, bordered, contentLabels]
38
46
  );
39
47
  return /* @__PURE__ */ jsx(DescriptionsLayoutContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
40
48
  "dl",
41
49
  {
42
50
  "data-slot": "descriptions",
43
51
  "data-columns": typeof columns === "number" ? columns : void 0,
44
- "data-columns-responsive": responsiveColumns ? "" : void 0,
52
+ "data-columns-responsive": responsiveColumns || contentLabels ? "" : void 0,
53
+ "data-label-width": contentLabels ? "content" : void 0,
45
54
  "data-bordered": bordered ? "" : void 0,
46
- style: responsiveColumns ? responsiveVars("--descriptions-column-count", 1, responsiveColumns) : void 0,
55
+ style: {
56
+ ...contentColumns ? responsiveVars("--descriptions-column-count", 1, contentColumns) : {},
57
+ ...labelStyle
58
+ },
47
59
  className: cn(
48
60
  "grid gap-x-[var(--descriptions-column-gap)] gap-y-[var(--descriptions-row-gap)]",
49
- legacyColumns,
61
+ !contentLabels && legacyColumns,
50
62
  className
51
63
  ),
52
64
  children: [
@@ -71,9 +83,9 @@ const LEGACY_SPAN_CLASS = {
71
83
  3: "sm:col-span-2 lg:col-span-3"
72
84
  };
73
85
  function DescriptionsItem({ label, mono, span, className, children }) {
74
- const { layout, labelAlign } = React.useContext(DescriptionsLayoutContext);
75
- const legacySpan = typeof span === "number" ? LEGACY_SPAN_CLASS[span] : void 0;
76
- const responsiveSpan = span !== void 0 && span !== "filled" && legacySpan === void 0 ? typeof span === "number" ? { sm: span } : span : void 0;
86
+ const { layout, labelAlign, contentLabels } = React.useContext(DescriptionsLayoutContext);
87
+ const legacySpan = !contentLabels && typeof span === "number" ? LEGACY_SPAN_CLASS[span] : void 0;
88
+ const responsiveSpan = span !== void 0 && span !== "filled" && legacySpan === void 0 ? typeof span === "number" ? span === 3 ? { sm: 2, lg: 3 } : { sm: span } : span : void 0;
77
89
  const endAlignedLabel = layout === "horizontal" && labelAlign === "end";
78
90
  return /* @__PURE__ */ jsxs(
79
91
  "div",
@@ -54,7 +54,7 @@ function FormField({
54
54
  const labelId = `${resolvedId}-label`;
55
55
  const hasLabel = !(label === void 0 || label === null || label === false || label === "");
56
56
  const helperId = helper ? `${resolvedId}-helper` : void 0;
57
- const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "ui-form-field-note text-muted-foreground text-xs", children: helper }) : null;
57
+ const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "ui-form-field-note text-muted-foreground", children: helper }) : null;
58
58
  const validationStatus = error ? "error" : validateStatus;
59
59
  const feedbackId = hasFeedback && validationStatus ? `${resolvedId}-feedback` : void 0;
60
60
  const FeedbackIcon = validationStatus === "validating" ? LoaderCircle : validationStatus === "error" ? CircleAlert : validationStatus === "warning" ? TriangleAlert : CheckCircle;
@@ -200,7 +200,7 @@ function FormField({
200
200
  }
201
201
  ) : null,
202
202
  helperPlacement === "after" ? helperNode : null,
203
- error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-error-strong text-xs", children: error }) : null
203
+ error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-error-strong", children: error }) : null
204
204
  ] })
205
205
  ]
206
206
  }
@@ -25,6 +25,7 @@ export declare const NumberInput: React.ForwardRefExoticComponent<import(".").Fi
25
25
  placeholder?: import("../../props/index.js").PlaceholderProp;
26
26
  prefix?: React.ReactNode;
27
27
  suffix?: React.ReactNode;
28
+ width?: import("../../props/index.js").ControlWidthProp;
28
29
  name?: import("../../props/index.js").NameProp;
29
30
  id?: import("../../props/index.js").IdProp;
30
31
  className?: import("../../props/index.js").ClassNameProp;
@@ -15,6 +15,7 @@ function decimalsOf(n) {
15
15
  const dot = s.indexOf(".");
16
16
  return dot === -1 ? 0 : s.length - dot - 1;
17
17
  }
18
+ const AUTO_CHARS_UNBOUNDED = 6;
18
19
  function clamp(n, min, max) {
19
20
  let out = n;
20
21
  if (min != null && out < min) out = min;
@@ -51,6 +52,7 @@ const NumberInput = React.forwardRef(
51
52
  placeholder,
52
53
  prefix,
53
54
  suffix,
55
+ width = "full",
54
56
  name,
55
57
  id,
56
58
  className,
@@ -182,6 +184,10 @@ const NumberInput = React.forwardRef(
182
184
  if (!changeOnWheel || !interactive || !focused || event.deltaY === 0) return;
183
185
  stepBy(event.deltaY < 0 ? 1 : -1);
184
186
  };
187
+ const autoChars = width !== "auto" ? null : min == null && max == null ? AUTO_CHARS_UNBOUNDED : Math.max(
188
+ ...[min, max].filter((bound) => bound != null && Number.isFinite(bound)).map((bound) => formatAtRest(bound).length),
189
+ 1
190
+ );
185
191
  return /* @__PURE__ */ jsxs(
186
192
  "div",
187
193
  {
@@ -190,7 +196,9 @@ const NumberInput = React.forwardRef(
190
196
  "data-status": status,
191
197
  "data-variant": variant,
192
198
  "data-controls": controls ? void 0 : "off",
199
+ "data-width": width === "full" ? void 0 : width,
193
200
  className: cn("ui-number-input", className),
201
+ style: autoChars == null ? void 0 : { "--number-input-chars": autoChars },
194
202
  children: [
195
203
  prefix != null ? /* @__PURE__ */ jsx(
196
204
  "span",
@@ -319,6 +319,7 @@ const SelectTrigger = React.forwardRef(
319
319
  },
320
320
  [ref]
321
321
  );
322
+ const openedWithPointer = React.useRef(false);
322
323
  const wasOpen = React.useRef(false);
323
324
  React.useEffect(() => {
324
325
  if (!wasOpen.current || isOpen) {
@@ -354,6 +355,7 @@ const SelectTrigger = React.forwardRef(
354
355
  ref: composedRef,
355
356
  id,
356
357
  onPointerDown: (event) => {
358
+ openedWithPointer.current = true;
357
359
  event.currentTarget.focus();
358
360
  },
359
361
  ...events,
@@ -384,6 +386,10 @@ const SelectTrigger = React.forwardRef(
384
386
  "data-status": status,
385
387
  "data-width": width,
386
388
  "data-state": isOpen ? "open" : "closed",
389
+ "data-open-modality": isOpen ? openedWithPointer.current ? "pointer" : "keyboard" : void 0,
390
+ onKeyDownCapture: () => {
391
+ openedWithPointer.current = false;
392
+ },
387
393
  "data-placeholder": hasValue ? void 0 : "",
388
394
  "data-value": dataValue
389
395
  }
@@ -600,6 +606,13 @@ function groupDataOptions(options) {
600
606
  }
601
607
  return order.map((key) => ({ heading: key || void 0, items: buckets.get(key) ?? [] }));
602
608
  }
609
+ function withOptionIcon(option, label) {
610
+ if (!option.icon) return label;
611
+ return /* @__PURE__ */ jsxs("span", { className: "ui-select-option-body", children: [
612
+ /* @__PURE__ */ jsx("span", { className: "ui-select-option-icon", "aria-hidden": "true", children: option.icon }),
613
+ /* @__PURE__ */ jsx("span", { className: "ui-select-option-label", children: label })
614
+ ] });
615
+ }
603
616
  function DataSelectLabel({
604
617
  labelRender,
605
618
  options
@@ -716,7 +729,7 @@ function DataSelect(props) {
716
729
  disabled: option.disabled,
717
730
  "data-testid": optionTestId(option.value),
718
731
  selectedIcon: menuItemSelectedIcon,
719
- children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) : option.label
732
+ children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) : withOptionIcon(option, option.label)
720
733
  },
721
734
  option.value
722
735
  );
@@ -771,7 +784,16 @@ function DataSelect(props) {
771
784
  ...ariaProps,
772
785
  children: [
773
786
  prefix === void 0 || prefix === null ? null : /* @__PURE__ */ jsx("span", { "data-slot": "select-prefix", className: "ui-select-prefix", children: prefix }),
774
- /* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : void 0 }),
787
+ /* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : options.some((option) => option.icon) ? (
788
+ // gh#1444 — the selected option's icon leads the trigger too, as in the search panel.
789
+ /* @__PURE__ */ jsx(
790
+ DataSelectLabel,
791
+ {
792
+ labelRender: ({ label, option }) => option ? withOptionIcon(option, label) : label,
793
+ options
794
+ }
795
+ )
796
+ ) : void 0 }),
775
797
  suffixIcon === void 0 || suffixIcon === null || showClear || loading ? null : /* @__PURE__ */ jsx("span", { "data-slot": "select-suffix", className: "ui-select-suffix", "aria-hidden": "true", children: suffixIcon })
776
798
  ]
777
799
  }
@@ -55,9 +55,10 @@ type TogglePressedFields = {
55
55
  * accessible name (gh#734) — without a second counting API growing beside this one (gh#762).
56
56
  * It defaults to `toggle`, so every existing call emits the identical markup it always has.
57
57
  */
58
- declare function useCounterPill({ count, overflowCount, showZero, countLabel, ariaLabel, slot, }: ToggleCountFields & {
58
+ declare function useCounterPill({ count, overflowCount, showZero, countLabel, ariaLabel, slot, countTone, }: ToggleCountFields & {
59
59
  ariaLabel?: string;
60
60
  slot?: string;
61
+ countTone?: "neutral" | "accent";
61
62
  }): {
62
63
  pill: null;
63
64
  resolvedAriaLabel: string | undefined;
@@ -58,7 +58,8 @@ function useCounterPill({
58
58
  showZero = true,
59
59
  countLabel,
60
60
  ariaLabel,
61
- slot = "toggle"
61
+ slot = "toggle",
62
+ countTone
62
63
  }) {
63
64
  const { locale } = useTranslation();
64
65
  const visible = count != null && (count !== 0 || showZero);
@@ -73,7 +74,16 @@ function useCounterPill({
73
74
  const spoken = countLabel ? `${formatted} ${countLabel}` : formatted;
74
75
  return {
75
76
  pill: /* @__PURE__ */ jsxs(Fragment, { children: [
76
- /* @__PURE__ */ jsx("span", { "data-slot": `${slot}-count`, className: `ui-${slot}-count`, "aria-hidden": "true", children: formatted }),
77
+ /* @__PURE__ */ jsx(
78
+ "span",
79
+ {
80
+ "data-slot": `${slot}-count`,
81
+ "data-count-tone": countTone,
82
+ className: `ui-${slot}-count`,
83
+ "aria-hidden": "true",
84
+ children: formatted
85
+ }
86
+ ),
77
87
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${spoken}` })
78
88
  ] }),
79
89
  resolvedAriaLabel: ariaLabel == null ? ariaLabel : `${ariaLabel}, ${spoken}`
@@ -74,6 +74,11 @@ const buttonVariants = cva("ui-button", {
74
74
  },
75
75
  defaultVariants: { variant: "default", size: "default", shape: "default" }
76
76
  });
77
+ function hasDirectText(children) {
78
+ return React.Children.toArray(children).some(
79
+ (child) => typeof child === "string" && child.trim() !== "" || typeof child === "number"
80
+ );
81
+ }
77
82
  const Button = React.forwardRef(
78
83
  ({
79
84
  className,
@@ -108,6 +113,7 @@ const Button = React.forwardRef(
108
113
  const spokenCount = countLabel ? `${countText} ${countLabel}` : countText;
109
114
  const ariaLabel = props["aria-label"];
110
115
  const foldedAriaLabel = showCount && ariaLabel != null ? { "aria-label": `${ariaLabel}, ${spokenCount}` } : null;
116
+ const labelled = asChild ? React.isValidElement(children) && hasDirectText(children.props.children) : hasDirectText(loadingText ?? children);
111
117
  const countNode = showCount ? /* @__PURE__ */ jsxs(Fragment, { children: [
112
118
  /* @__PURE__ */ jsx("span", { "data-slot": "button-count", className: "ui-button-count", "aria-hidden": "true", children: countText }),
113
119
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${spokenCount}` })
@@ -124,6 +130,7 @@ const Button = React.forwardRef(
124
130
  "data-wrap": wrap ? "" : void 0,
125
131
  "data-align": align === "center" ? void 0 : align,
126
132
  "data-loading": isLoading ? "" : void 0,
133
+ "data-label": labelled ? "" : void 0,
127
134
  "aria-busy": isLoading || void 0,
128
135
  disabled: isLoading || disabled,
129
136
  type: asChild ? void 0 : type ?? "button",
@@ -44,7 +44,7 @@ export declare const Text: React.ForwardRefExoticComponent<Omit<React.HTMLAttrib
44
44
  as?: "span" | "p" | "div" | "a" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr";
45
45
  asChild?: import("../../props/index.js").AsChildProp;
46
46
  link?: boolean;
47
- size?: import("../../props/index.js").TextSizeProp;
47
+ size?: import("../../props/index.js").TextSizeProp | "inherit";
48
48
  tone?: import("../../props/index.js").TextToneProp;
49
49
  weight?: import("../../props/index.js").FontWeightProp;
50
50
  align?: import("../../props/index.js").TextAlignProp;
@@ -3,4 +3,4 @@ import type { AppShellProp } from "../../props/components/layout.prop.js";
3
3
  export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
4
4
  /** Same rail boundary as shell-layout.css; SSR starts in docked mode. */
5
5
  export declare function useAppShellNavigationMode(): "drawer" | "docked";
6
- export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
6
+ export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileTabBar, mobileTabBarLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
@@ -6,6 +6,8 @@ import { Menu } from "lucide-react";
6
6
  import { useMediaQuery } from "../../lib/hooks.js";
7
7
  import { useTranslation } from "../../i18n/use-translation.js";
8
8
  import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
9
+ import { ShellTabBar } from "./mobile-shell.js";
10
+ import { useToastStickyBarClearance } from "../../lib/toast-clearance.js";
9
11
  import { NavSurfaceProvider } from "./nav-surface.js";
10
12
  import { TopbarItem } from "./topbar-item.js";
11
13
  import { isDevelopment } from "../../lib/dev.js";
@@ -30,6 +32,8 @@ function AppShell({
30
32
  navRailPosition = "start",
31
33
  navRailEnd,
32
34
  navRailLabel,
35
+ mobileTabBar,
36
+ mobileTabBarLabel,
33
37
  mobileNav,
34
38
  mobileNavLabel,
35
39
  mobileNavTriggerLabel,
@@ -39,6 +43,23 @@ function AppShell({
39
43
  }) {
40
44
  const { t } = useTranslation();
41
45
  const navigationMode = useAppShellNavigationMode();
46
+ const rootRef = React.useRef(null);
47
+ const tabBarRef = React.useRef(null);
48
+ const showTabBar = responsiveNavigation === "drawer" && navigationMode === "drawer" && mobileTabBar != null && mobileTabBar !== false;
49
+ useToastStickyBarClearance(showTabBar ? [tabBarRef] : [], showTabBar);
50
+ React.useEffect(() => {
51
+ const root = rootRef.current;
52
+ const bar2 = tabBarRef.current;
53
+ if (!root || !bar2 || !showTabBar || typeof ResizeObserver === "undefined") return;
54
+ const publish = () => root.style.setProperty("--app-shell-mobile-tab-bar-block-size", `${bar2.offsetHeight}px`);
55
+ publish();
56
+ const observer = new ResizeObserver(publish);
57
+ observer.observe(bar2, { box: "border-box" });
58
+ return () => {
59
+ observer.disconnect();
60
+ root.style.removeProperty("--app-shell-mobile-tab-bar-block-size");
61
+ };
62
+ }, [showTabBar]);
42
63
  const hasSidebar = sidebar !== void 0 && sidebar !== null && sidebar !== false;
43
64
  const railContent = navRail === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
44
65
  navRail,
@@ -146,10 +167,13 @@ function AppShell({
146
167
  ] }),
147
168
  hasTopbarContent && resolvedTopbar
148
169
  ] });
170
+ const footerRegion = footer !== void 0 ? /* @__PURE__ */ jsx("footer", { className: "app-footer", "aria-label": t("layout.appShell.footerLabel"), children: footer }) : null;
149
171
  return /* @__PURE__ */ jsxs(
150
172
  "div",
151
173
  {
174
+ ref: rootRef,
152
175
  className: "app-root",
176
+ "data-mobile-tab-bar": showTabBar ? "" : void 0,
153
177
  "data-collapsed": hasSidebar && sidebarCollapsed ? "true" : void 0,
154
178
  "data-sidebar": hasSidebar ? void 0 : "none",
155
179
  "data-responsive-navigation": responsiveNavigation,
@@ -177,11 +201,13 @@ function AppShell({
177
201
  tabIndex: 0,
178
202
  children: [
179
203
  breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
180
- children
204
+ children,
205
+ showTabBar && footerRegion
181
206
  ]
182
207
  }
183
208
  ),
184
- footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "app-footer", "aria-label": t("layout.appShell.footerLabel"), children: footer })
209
+ !showTabBar && footerRegion,
210
+ showTabBar && /* @__PURE__ */ jsx(ShellTabBar, { ref: tabBarRef, className: "app-mobile-tab-bar", "aria-label": mobileTabBarLabel, children: mobileTabBar })
185
211
  ]
186
212
  }
187
213
  );
@@ -15,6 +15,8 @@ function PageLayout({
15
15
  width = "md",
16
16
  align = "start",
17
17
  preset = "default",
18
+ footerTone = "default",
19
+ flow = "column",
18
20
  className
19
21
  }) {
20
22
  const { t } = useTranslation();
@@ -23,6 +25,7 @@ function PageLayout({
23
25
  {
24
26
  "data-slot": "centered-shell",
25
27
  "data-preset": preset === "default" ? void 0 : preset,
28
+ "data-flow": flow === "sections" ? "sections" : void 0,
26
29
  className: cn("ui-centered-shell", className),
27
30
  children: [
28
31
  topbar !== void 0 && /* @__PURE__ */ jsx(
@@ -39,6 +42,7 @@ function PageLayout({
39
42
  {
40
43
  className: "ui-centered-shell-footer",
41
44
  "aria-label": t("layout.centeredShell.footerLabel"),
45
+ "data-tone": footerTone === "inverse" ? "inverse" : void 0,
42
46
  children: footer
43
47
  }
44
48
  )
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import type { EditorialAuthCardProp, EditorialAuthProp, EditorialEmptyPanelProp, EditorialFootnoteProp, EditorialMetaBarProp, EditorialStatsProp, EditorialTextButtonProp, EditorialToggleGroupProp, EditorialToolbarProp, HoldingCardActionProp, HoldingCardProp, HoldingGridProp, SettingsLayoutProp, SettingsPanelProp, ThemePickerProp } from "../../props/components/editorial-account.prop.js";
3
+ export type { EditorialAuthCardProp, EditorialAuthCardProp as EditorialAuthCardProps, EditorialAuthProp, EditorialAuthProp as EditorialAuthProps, EditorialEmptyPanelProp, EditorialEmptyPanelProp as EditorialEmptyPanelProps, EditorialFootnoteProp, EditorialFootnoteProp as EditorialFootnoteProps, EditorialMetaBarProp, EditorialMetaBarProp as EditorialMetaBarProps, EditorialStatsItemProp, EditorialStatsProp, EditorialStatsProp as EditorialStatsProps, EditorialTextButtonProp, EditorialTextButtonProp as EditorialTextButtonProps, EditorialToggleGroupItemProp, EditorialToggleGroupProp, EditorialToggleGroupProp as EditorialToggleGroupProps, EditorialToolbarProp, EditorialToolbarProp as EditorialToolbarProps, HoldingCardActionProp, HoldingCardProp, HoldingCardProp as HoldingCardProps, HoldingGridProp, HoldingGridProp as HoldingGridProps, HoldingViewProp, SettingsLayoutProp, SettingsLayoutProp as SettingsLayoutProps, SettingsPanelProp, SettingsPanelProp as SettingsPanelProps, ThemePickerOptionProp, ThemePickerPreviewProp, ThemePickerProp, ThemePickerProp as ThemePickerProps, } from "../../props/components/editorial-account.prop.js";
4
+ /** A named group of `aria-pressed` buttons, one pressed at a time: text filter or icon switch. */
5
+ export declare function EditorialToggleGroup({ items, value, onValueChange, variant, className, ...rest }: EditorialToggleGroupProp): React.JSX.Element;
6
+ /** A hairline-ruled strip: a glyph and a line at the start, a badge at the end. */
7
+ export declare function EditorialMetaBar({ icon, start, end, className }: EditorialMetaBarProp): React.JSX.Element;
8
+ /** A centred signed-out (`guest`, with a card-stack illustration) or empty (`empty`) panel. */
9
+ export declare function EditorialEmptyPanel({ variant, eyebrow, title, level, description, actions, icon, className, }: EditorialEmptyPanelProp): React.JSX.Element;
10
+ /** A strip of figure tiles on unequal tracks; the first tile is the lead. */
11
+ export declare function EditorialStats({ items, className, ...rest }: EditorialStatsProp): React.JSX.Element;
12
+ /**
13
+ * The row above a collection: a summary (count + note) and / or a start cluster, an end cluster
14
+ * (order, view switch, primary action), optionally over a hairline. With a `summary` it is a
15
+ * listing row that keeps one line; without one it is a bar whose clusters may stack.
16
+ */
17
+ export declare function EditorialToolbar({ summary, start, end, divider, className, }: EditorialToolbarProp): React.JSX.Element;
18
+ /** The track of HoldingCards; `view="list"` turns every card into a wide row. */
19
+ export declare function HoldingGrid({ view, children, className, ...rest }: HoldingGridProp): React.JSX.Element;
20
+ /** One owned item: picture ground, overline, title, meta, tags, purchase line, date, actions. */
21
+ export declare function HoldingCard({ media, overline, title, level, meta, tags, purchase, date, actions, className, }: HoldingCardProp): React.JSX.Element;
22
+ /** A quiet glyph + label action of a HoldingCard; `asChild` turns a router link into it. */
23
+ export declare function HoldingCardAction({ icon, children, asChild, onClick, disabled, className, ...rest }: HoldingCardActionProp): React.JSX.Element;
24
+ /** A settings page: a sticky rail beside the panels. */
25
+ export declare function SettingsLayout({ aside, children, className }: SettingsLayoutProp): React.JSX.Element;
26
+ /** One bordered settings section: number, title, description, then its fields. */
27
+ export declare function SettingsPanel({ number, title, level, description, children, titleId, className, }: SettingsPanelProp): React.JSX.Element;
28
+ /** An illustrated radio group for the colour theme (painted light / dark / system previews). */
29
+ export declare function ThemePicker({ legend, options, value, defaultValue, onValueChange, name, className, }: ThemePickerProp): React.JSX.Element;
30
+ /** A sign-in / sign-up page: the story column beside the form card. */
31
+ export declare function EditorialAuth({ eyebrow, title, description, art, benefits, children, className, }: EditorialAuthProp): React.JSX.Element;
32
+ /** The form card of EditorialAuth: badge, title, copy, the form and a ruled footer line. */
33
+ export declare function EditorialAuthCard({ badge, title, level, description, children, footer, className, }: EditorialAuthCardProp): React.JSX.Element;
34
+ /** An underlined text action in the primary tone; `asChild` turns a router link into it. */
35
+ export declare function EditorialTextButton({ children, icon, asChild, onClick, disabled, className, }: EditorialTextButtonProp): React.JSX.Element;
36
+ /**
37
+ * `EditorialSearch variant="inline"` (gh#1439) — internal: a collection's own search row, visible
38
+ * label, framed field with a glyph and a live count. Rendered by EditorialSearch; not exported.
39
+ */
40
+ export declare function EditorialSearchInline({ label, id, placeholder, defaultValue, onSearch, debounce, count, className, }: {
41
+ label: string;
42
+ id?: string;
43
+ placeholder?: string;
44
+ defaultValue?: string;
45
+ onSearch?: (query: string) => void;
46
+ debounce?: number;
47
+ count?: React.ReactNode;
48
+ className?: string;
49
+ }): React.JSX.Element;
50
+ /** A quiet closing line: a note at the start, an optional action at the end. */
51
+ export declare function EditorialFootnote({ children, action, align, className, }: EditorialFootnoteProp): React.JSX.Element;