@godxjp/ui 28.13.0 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/agent/START-HERE.md +29 -10
  2. package/agent/components/Anchor.json +6 -1
  3. package/agent/components/AppShell.json +1 -1
  4. package/agent/components/AreaChart.json +19 -1
  5. package/agent/components/BarChart.json +11 -1
  6. package/agent/components/Cascader.json +1 -1
  7. package/agent/components/CompactBarTrend.json +1 -1
  8. package/agent/components/DataState.json +1 -1
  9. package/agent/components/DataTable.json +4 -4
  10. package/agent/components/FormField.json +1 -1
  11. package/agent/components/InfiniteQueryState.json +1 -1
  12. package/agent/components/Input.json +1 -1
  13. package/agent/components/LineChart.json +20 -2
  14. package/agent/components/ListRow.json +1 -1
  15. package/agent/components/Masonry.json +1 -1
  16. package/agent/components/MasterDetail.json +1 -1
  17. package/agent/components/PasswordStrength.json +1 -1
  18. package/agent/components/PermissionMatrix.json +1 -1
  19. package/agent/components/Select.json +1 -0
  20. package/agent/components/Sidebar.json +1 -1
  21. package/agent/components/Table.json +8 -3
  22. package/agent/components/ThemeScope.json +49 -0
  23. package/agent/components/Topbar.json +1 -0
  24. package/agent/components/TopbarItem.json +2 -1
  25. package/agent/components/Transfer.json +1 -1
  26. package/agent/components/TreeSelect.json +1 -1
  27. package/agent/components/UploadCropDialog.json +1 -1
  28. package/agent/components/formatDate.json +1 -1
  29. package/agent/components-index.json +5 -0
  30. package/agent/components.json +138 -30
  31. package/agent/index.json +19 -9
  32. package/agent/llms.txt +10 -10
  33. package/agent/patterns/tenant-brand-color.json +28 -0
  34. package/agent/patterns-index.json +27 -0
  35. package/agent/patterns.json +28 -0
  36. package/agent/rules.json +15 -0
  37. package/agent/tokens.json +4965 -970
  38. package/dist/app/index.d.ts +3 -0
  39. package/dist/app/index.js +3 -0
  40. package/dist/app/tenant-theme.d.ts +80 -0
  41. package/dist/app/tenant-theme.js +154 -0
  42. package/dist/app/theme-axes.d.ts +14 -1
  43. package/dist/app/theme-axes.js +24 -31
  44. package/dist/components/charts/chart-cartesian.d.ts +5 -1
  45. package/dist/components/charts/chart-cartesian.js +15 -8
  46. package/dist/components/data-display/badge.d.ts +1 -1
  47. package/dist/components/data-display/badge.js +20 -2
  48. package/dist/components/data-display/carousel.js +4 -4
  49. package/dist/components/data-display/data-table.js +13 -2
  50. package/dist/components/data-display/permission-matrix.js +1 -1
  51. package/dist/components/data-display/table.d.ts +11 -2
  52. package/dist/components/data-display/table.js +18 -2
  53. package/dist/components/data-entry/control-appearance.d.ts +12 -6
  54. package/dist/components/data-entry/control-appearance.js +1 -1
  55. package/dist/components/data-entry/select.js +4 -3
  56. package/dist/components/feedback/dialog.js +6 -3
  57. package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
  58. package/dist/components/feedback/overlay-header-tone.js +4 -4
  59. package/dist/components/feedback/sheet.d.ts +1 -1
  60. package/dist/components/feedback/sheet.js +6 -9
  61. package/dist/components/feedback/sonner.js +16 -3
  62. package/dist/components/general/button.js +22 -5
  63. package/dist/components/layout/affix.js +15 -1
  64. package/dist/components/layout/sidebar.js +7 -1
  65. package/dist/components/navigation/anchor.d.ts +1 -1
  66. package/dist/components/navigation/anchor.js +5 -4
  67. package/dist/components/navigation/app-setting-picker.js +1 -1
  68. package/dist/components/navigation/pagination.js +1 -1
  69. package/dist/components/navigation/tabs.js +15 -2
  70. package/dist/components/query/infinite-query-state.d.ts +22 -6
  71. package/dist/lib/control-styles.d.ts +31 -11
  72. package/dist/lib/control-styles.js +6 -6
  73. package/dist/lib/overlay-portal.d.ts +20 -0
  74. package/dist/lib/overlay-portal.js +93 -0
  75. package/dist/props/components/app.prop.d.ts +12 -0
  76. package/dist/props/components/charts.prop.d.ts +30 -0
  77. package/dist/props/components/index.d.ts +1 -1
  78. package/dist/props/components/navigation.prop.d.ts +21 -2
  79. package/dist/props/components/query.prop.d.ts +36 -2
  80. package/dist/props/registry.d.ts +46 -1
  81. package/dist/props/registry.js +38 -3
  82. package/dist/styles/alert-layout.css +34 -14
  83. package/dist/styles/badge-layout.css +10 -6
  84. package/dist/styles/base.css +14 -5
  85. package/dist/styles/card-layout.css +19 -8
  86. package/dist/styles/chart-layout.css +22 -3
  87. package/dist/styles/control.css +165 -59
  88. package/dist/styles/data-display-layout.css +129 -36
  89. package/dist/styles/data-entry-layout.css +23 -87
  90. package/dist/styles/dialog-layout.css +49 -19
  91. package/dist/styles/float-button-layout.css +5 -5
  92. package/dist/styles/focus-ring.css +9 -5
  93. package/dist/styles/layout.css +42 -15
  94. package/dist/styles/logo-layout.css +1 -1
  95. package/dist/styles/motion.css +1 -1
  96. package/dist/styles/navigation-layout.css +90 -29
  97. package/dist/styles/shell-layout.css +63 -40
  98. package/dist/styles/table-layout.css +56 -17
  99. package/dist/styles/text-layout.css +13 -4
  100. package/dist/styles/toggle.css +8 -2
  101. package/dist/tokens/components/actions.css +1 -1
  102. package/dist/tokens/components/attachments.css +4 -4
  103. package/dist/tokens/components/badge.css +4 -4
  104. package/dist/tokens/components/callout.css +1 -1
  105. package/dist/tokens/components/card.css +9 -4
  106. package/dist/tokens/components/chart.css +10 -1
  107. package/dist/tokens/components/chat-bubble.css +1 -1
  108. package/dist/tokens/components/control.css +28 -10
  109. package/dist/tokens/components/conversations.css +2 -1
  110. package/dist/tokens/components/data-display.css +12 -7
  111. package/dist/tokens/components/descriptions.css +1 -1
  112. package/dist/tokens/components/draggable-panel.css +1 -1
  113. package/dist/tokens/components/feedback.css +28 -8
  114. package/dist/tokens/components/float-button.css +1 -1
  115. package/dist/tokens/components/legal-document.css +1 -1
  116. package/dist/tokens/components/logo.css +1 -1
  117. package/dist/tokens/components/mega-menu.css +5 -3
  118. package/dist/tokens/components/navigation.css +21 -7
  119. package/dist/tokens/components/segmented.css +8 -3
  120. package/dist/tokens/components/shell.css +19 -5
  121. package/dist/tokens/components/table.css +9 -1
  122. package/dist/tokens/components/thought-chain.css +1 -1
  123. package/dist/tokens/components/toggle.css +2 -0
  124. package/dist/tokens/components/tree.css +3 -1
  125. package/dist/tokens/components/upload.css +6 -6
  126. package/dist/tokens/components/welcome.css +1 -1
  127. package/dist/tokens/foundation.css +28 -1
  128. package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
  129. package/docs/CUSTOMER-THEMING.md +637 -1
  130. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  131. package/docs/GLASSMORPHISM-STANDARD.md +196 -0
  132. package/docs/THEME-API-COVERAGE.md +538 -0
  133. package/docs/TOKEN-RESOLUTION.md +195 -0
  134. package/docs/TOKENS.md +63 -24
  135. package/docs/asset-modules.d.ts +7 -0
  136. package/docs/data-display/charts.tsx +80 -0
  137. package/docs/data-display/data-table/index.tsx +30 -0
  138. package/docs/data-display/popover.tsx +1 -1
  139. package/docs/data-display/table.tsx +52 -0
  140. package/docs/feedback/sheet.tsx +10 -10
  141. package/docs/foundation/density.tsx +4 -4
  142. package/docs/i18n/messages/en.json +502 -0
  143. package/docs/i18n/messages/ja.json +502 -0
  144. package/docs/i18n/messages/vi.json +502 -0
  145. package/docs/layout/account-chip.tsx +2 -2
  146. package/docs/layout/responsive-grid.tsx +1 -1
  147. package/docs/navigation/toolbar.tsx +20 -12
  148. package/docs/providers/theme-scope.tsx +186 -0
  149. package/docs/showcase/caimono-price-comparison.tsx +911 -0
  150. package/docs/showcase/case4-login.tsx +2 -2
  151. package/docs/showcase/permission-matrix.tsx +13 -5
  152. package/docs/showcase/table-pagination.tsx +2 -1
  153. package/docs/showcase/tenant-brand-color.tsx +338 -0
  154. package/docs/showcase/theme-lab.tsx +2125 -0
  155. package/docs/themes/flat.css +462 -0
  156. package/docs/themes/glassmorphism.css +958 -0
  157. package/docs/themes/index.ts +200 -0
  158. package/package.json +4 -3
  159. package/scripts/explain-token.mjs +382 -0
@@ -78,14 +78,30 @@ const TableBody = React.forwardRef(({ className, ...props }, ref) => (
78
78
  /* @__PURE__ */ jsx("tbody", { ref, "data-slot": "table-body", className: cn(className), ...props })
79
79
  ));
80
80
  TableBody.displayName = "TableBody";
81
- const TableRow = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
81
+ const TableRow = React.forwardRef(({ className, tone, ...props }, ref) => /* @__PURE__ */ jsx(
82
82
  "tr",
83
83
  {
84
84
  ref,
85
+ "data-tone": tone,
85
86
  className: cn(
86
87
  // The row rule itself is `.ui-table-row` in table-layout.css (--table-row-border-width),
87
88
  // NOT a `border-b` utility — a utility sits in `@layer utilities` and would outrank the
88
- "ui-table-row hover:bg-accent/70 data-[state=selected]:bg-primary/[0.06] transition-colors",
89
+ //
90
+ // THE WASH NOW READS THE SAME TOKEN THE PINNED-COLUMN MIRROR ALREADY DID (gh#894). A bare
91
+ // `hover:bg-accent/70` could not be retuned by any theme — the pin-column mirror right
92
+ // below this file's own selectors (table-layout.css, data-display-layout.css) already
93
+ // reads `--table-row-hover-background-alpha`, so the row that actually owns the fill was
94
+ // the one piece a theme could not reach. The shared default stays 0.7 (unchanged; gh#700
95
+ // needs it >= ~0.68 or a striped row's hover stops being a visible step) — a tone Badge
96
+ // composed over a hovered GLASS row still measures 4.02–4.34:1 on four of five seeds, and
97
+ // clearing that needs glass's OWN lower value for this token (its theme file, not this
98
+ // one). What this line fixes is that a theme now CAN set that value at all.
99
+ //
100
+ // `color-mix(…)`, not `hsl(var(--accent) / var(--x, 70%))`: Tailwind's arbitrary-property
101
+ // parser treats a bare `/` inside the bracket as ITS OWN opacity-modifier boundary — that
102
+ // spelling measured 0.1 alpha in Chromium (not 0.7), silently. `color-mix` has no `/` for
103
+ // it to misread, matching the same idiom `toneSuccessClass` already uses (control-styles.ts).
104
+ "ui-table-row data-[state=selected]:bg-primary/[0.06] transition-colors hover:[background-color:color-mix(in_oklab,hsl(var(--accent))_var(--table-row-hover-background-alpha,70%),transparent)]",
89
105
  className
90
106
  ),
91
107
  ...props
@@ -28,13 +28,19 @@ export type ControlAppearanceAttributes = {
28
28
  */
29
29
  export declare function controlAppearanceAttributes({ status, variant, "aria-invalid": ariaInvalid, }: ControlAppearanceInput): ControlAppearanceAttributes;
30
30
  /**
31
- * The chrome utilities a variant carries, kept in the COMPONENT rather than in `control.css`.
31
+ * The chrome a variant carries, kept in the COMPONENT rather than in `control.css`.
32
32
  *
33
- * A Tailwind utility lives in `@layer utilities`, which outranks `@layer components` no matter
34
- * how specific the component rule is — so `bg-background` baked unconditionally into the class
35
- * list would silently defeat `filled` and `borderless` and no gate would notice. The outlined
36
- * field therefore keeps exactly the utilities it always had, and the other two simply do not
37
- * receive them; their surface comes from `.ui-control--filled` / `.ui-control--borderless`.
33
+ * A Tailwind utility lives in `@layer utilities`, which outranks `@layer components` no matter how
34
+ * specific the component rule is — so a chrome class baked unconditionally into the base list
35
+ * would silently defeat `filled` and `borderless` and no gate would notice. Each variant therefore
36
+ * receives exactly its own, and the other two never see it.
37
+ *
38
+ * `outlined` USED TO BE THE TWO UTILITIES `border-input bg-background` (gh#880). That is why
39
+ * `--control-surface-background` / `--control-surface-border-color` — documented, published, and
40
+ * reaching the whole select family through `.ui-control-surface` — reached neither Input nor
41
+ * DatePicker: the utilities outranked the tokens by construction. It is a component CLASS now, so
42
+ * the same two tokens paint every field in the library. No token was added; the existing pair just
43
+ * has no hole left in it. See `.ui-control-outlined-surface` in control.css.
38
44
  */
39
45
  export declare const CONTROL_VARIANT_CHROME_CLASS: Record<ControlVariantProp, string>;
40
46
  /**
@@ -10,7 +10,7 @@ function controlAppearanceAttributes({
10
10
  };
11
11
  }
12
12
  const CONTROL_VARIANT_CHROME_CLASS = {
13
- outlined: "border-input bg-background",
13
+ outlined: "ui-control-outlined-surface",
14
14
  filled: "ui-control--filled",
15
15
  borderless: "ui-control--borderless"
16
16
  };
@@ -213,6 +213,7 @@ function LabelInValueSelect({
213
213
  }
214
214
  function CompoundSelect({ id, name, ...props }) {
215
215
  const fieldA11y = pickFieldA11y(props);
216
+ const nameFallback = useFieldNameFallback(fieldA11y);
216
217
  const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
217
218
  return /* @__PURE__ */ jsx(
218
219
  SelectFieldA11yContext.Provider,
@@ -229,7 +230,7 @@ function CompoundSelect({ id, name, ...props }) {
229
230
  id,
230
231
  name: name ?? identity.name,
231
232
  "aria-label": fieldA11y["aria-label"],
232
- "aria-labelledby": fieldA11y["aria-labelledby"]
233
+ "aria-labelledby": fieldA11y["aria-labelledby"] ?? nameFallback["aria-labelledby"]
233
234
  }
234
235
  )
235
236
  }
@@ -731,8 +732,8 @@ function DataSelect(props) {
731
732
  },
732
733
  disabled: disabled || !hasOptions && !showEmptyPopup,
733
734
  allowsEmptyCollection: showEmptyPopup,
734
- "aria-label": ariaProps["aria-label"],
735
- "aria-labelledby": ariaProps["aria-labelledby"],
735
+ "aria-label": ariaProps["aria-label"] ?? nameFromPlaceholder,
736
+ "aria-labelledby": ariaProps["aria-labelledby"] ?? inheritedName["aria-labelledby"],
736
737
  name: resolvedName,
737
738
  open,
738
739
  defaultOpen,
@@ -93,7 +93,10 @@ function DialogShell({
93
93
  ...props,
94
94
  style,
95
95
  ref: mergeRefs(ref, racRef, contentRef),
96
- onKeyDown: chain(props.onKeyDown, closeOnEscape(() => state.setOpen(false)))
96
+ onKeyDown: chain(
97
+ props.onKeyDown,
98
+ closeOnEscape(() => state.setOpen(false))
99
+ )
97
100
  }
98
101
  );
99
102
  },
@@ -195,7 +198,7 @@ function DialogContent({
195
198
  showCloseButton ? /* @__PURE__ */ jsxs(
196
199
  DialogClose,
197
200
  {
198
- className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
201
+ className: "ui-dialog-close ui-focus-ring transition-opacity disabled:pointer-events-none",
199
202
  children: [
200
203
  /* @__PURE__ */ jsx(X, { className: "ui-dialog-close-icon", "aria-hidden": "true" }),
201
204
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("feedback.alert.dismiss") })
@@ -313,7 +316,7 @@ function AlertDialogContent({
313
316
  {
314
317
  type: "button",
315
318
  "data-slot": "dialog-close",
316
- className: "ui-focus-ring transition-opacity disabled:pointer-events-none",
319
+ className: "ui-dialog-close ui-focus-ring transition-opacity disabled:pointer-events-none",
317
320
  "aria-label": t("feedback.alert.dismiss"),
318
321
  onClick: () => {
319
322
  state.setOpen(false);
@@ -3,5 +3,12 @@ import type { ToneProp } from "../../props/vocabulary/index.js";
3
3
  * Soft semantic background band for an overlay header (Sheet / Dialog / Drawer).
4
4
  * Tints ONLY the background — text stays `--foreground` (渋み: never invert the band into a loud
5
5
  * solid fill).
6
+ *
7
+ * The four STATUS bands read `--surface-*` with today's 10% wash as the call-site fallback
8
+ * (gh#866), so a brand whose kit pairs each status ink with an independently chosen pale ground
9
+ * paints that ground here too — the band is one of eight surfaces that used to derive its own and
10
+ * therefore could not agree with the Badge sitting in it. `muted`/`neutral` are not statuses and
11
+ * keep `bg-muted`. Arbitrary property rather than `bg-success/10`: same utility layer, same
12
+ * specificity, nothing about the cascade moves.
6
13
  */
7
14
  export declare const overlayHeaderToneClass: Record<ToneProp, string>;
@@ -1,9 +1,9 @@
1
1
  const overlayHeaderToneClass = {
2
2
  default: "",
3
- success: "bg-success/10",
4
- warning: "bg-warning/10",
5
- destructive: "bg-destructive/10",
6
- info: "bg-info/10",
3
+ success: "[background-color:var(--surface-success,color-mix(in_oklab,hsl(var(--success))_10%,transparent))]",
4
+ warning: "[background-color:var(--surface-warning,color-mix(in_oklab,hsl(var(--warning))_10%,transparent))]",
5
+ destructive: "[background-color:var(--surface-destructive,color-mix(in_oklab,hsl(var(--destructive))_10%,transparent))]",
6
+ info: "[background-color:var(--surface-info,color-mix(in_oklab,hsl(var(--info))_10%,transparent))]",
7
7
  muted: "bg-muted",
8
8
  neutral: "bg-muted"
9
9
  };
@@ -28,7 +28,7 @@ export interface SheetProps {
28
28
  modal?: boolean;
29
29
  children?: React.ReactNode;
30
30
  }
31
- export declare function Sheet({ open, defaultOpen, onOpenChange, modal, children, }: SheetProps): React.JSX.Element;
31
+ export declare function Sheet({ open, defaultOpen, onOpenChange, modal, children }: SheetProps): React.JSX.Element;
32
32
  export interface SheetTriggerProps extends React.ComponentPropsWithRef<"button"> {
33
33
  /** Mượn thẻ của con thay vì dựng `<button>` riêng. */
34
34
  asChild?: boolean;
@@ -31,13 +31,7 @@ function useSheetOpenState(component) {
31
31
  }
32
32
  const SheetModalContext = React.createContext(true);
33
33
  const SheetLabelContext = React.createContext(null);
34
- function Sheet({
35
- open,
36
- defaultOpen,
37
- onOpenChange,
38
- modal = true,
39
- children
40
- }) {
34
+ function Sheet({ open, defaultOpen, onOpenChange, modal = true, children }) {
41
35
  const [uncontrolled, setUncontrolled] = React.useState(defaultOpen ?? false);
42
36
  const isOpen = open ?? uncontrolled;
43
37
  const setOpen = React.useCallback(
@@ -97,7 +91,7 @@ function SheetOverlay(_props) {
97
91
  SheetOverlay.displayName = "SheetOverlay";
98
92
  const SHEET_OVERLAY_CLASS = "ui-sheet-overlay data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed";
99
93
  const sheetVariants = cva(
100
- "ui-sheet-panel fixed flex flex-col gap-[var(--space-chrome-gap)] bg-background px-[var(--sheet-pad-x)] py-[var(--sheet-pad-y)] transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
94
+ "ui-sheet-panel fixed flex flex-col gap-[var(--space-chrome-gap)] text-foreground px-[var(--sheet-pad-x)] py-[var(--sheet-pad-y)] transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
101
95
  {
102
96
  variants: {
103
97
  // `side` is a deliberately PHYSICAL API (left/right/top/bottom) — a sheet
@@ -171,7 +165,10 @@ function SheetContent({
171
165
  ...props,
172
166
  style: mergedStyle,
173
167
  ref: mergeRefs(ref, racRef, contentRef),
174
- onKeyDown: chain(props.onKeyDown, closeOnEscape(() => state.setOpen(false)))
168
+ onKeyDown: chain(
169
+ props.onKeyDown,
170
+ closeOnEscape(() => state.setOpen(false))
171
+ )
175
172
  }
176
173
  );
177
174
  },
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { CheckCircle2, Info, Loader2, OctagonX, TriangleAlert } from "lucide-react";
5
6
  import { Toaster as Sonner } from "sonner";
7
+ import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
6
8
  function useDocumentTheme() {
7
9
  return React.useSyncExternalStore(
8
10
  (onStoreChange) => {
@@ -33,7 +35,11 @@ const toneVars = Object.fromEntries(
33
35
  TOAST_TONES.flatMap(({ type, hue, ink }) => [
34
36
  [
35
37
  `--${type}-bg`,
36
- `color-mix(in srgb, hsl(var(--${hue})) calc(var(--alert-bg-alpha) * 100%), hsl(var(--popover)))`
38
+ // gh#866 — the surface reads its `--surface-*` role first, with the composite above as the
39
+ // call-site fallback. Without it the toast was the fourth of eight status surfaces that
40
+ // could only ever be a derivation of its own ink, so a brand's chosen pale ground reached
41
+ // the Alert and not the toast that says the same sentence.
42
+ `var(--surface-${hue}, color-mix(in srgb, hsl(var(--${hue})) calc(var(--alert-bg-alpha) * 100%), hsl(var(--popover) / var(--popover-alpha, 100%))))`
37
43
  ],
38
44
  [`--${type}-border`, `hsl(var(--${hue}) / var(--alert-border-alpha))`],
39
45
  [`--${type}-text`, `hsl(var(--${ink}))`]
@@ -41,7 +47,8 @@ const toneVars = Object.fromEntries(
41
47
  );
42
48
  function Toaster({ style, richColors = true, ...props }) {
43
49
  const theme = useDocumentTheme();
44
- return /* @__PURE__ */ jsx(
50
+ const overlayPortalContainer = useOverlayPortalContainer();
51
+ const toaster = /* @__PURE__ */ jsx(
45
52
  Sonner,
46
53
  {
47
54
  theme,
@@ -59,7 +66,12 @@ function Toaster({ style, richColors = true, ...props }) {
59
66
  // sonner uses these vars verbatim as CSS colors, so wrap with hsl()
60
67
  // here — unwrapped they are invalid values and the toast renders
61
68
  // transparent.
62
- "--normal-bg": "hsl(var(--popover))",
69
+ // `--normal-bg` is SONNER's private name, so the package's own knob is threaded through
70
+ // it (gh#880): a theme sets `--toast-background` like every other surface's fill and
71
+ // never has to know that this one surface is painted by a vendor stylesheet. The default
72
+ // is the same `hsl(var(--popover))` and resolves in the browser, on the toaster, under
73
+ // whatever scope it sits in — so a themed region's toast follows the region (gh#877).
74
+ "--normal-bg": "var(--toast-background, hsl(var(--popover) / var(--popover-alpha, 100%)))",
63
75
  "--normal-text": "hsl(var(--popover-foreground))",
64
76
  "--normal-border": "hsl(var(--border))",
65
77
  "--border-radius": "var(--radius)",
@@ -77,6 +89,7 @@ function Toaster({ style, richColors = true, ...props }) {
77
89
  ...props
78
90
  }
79
91
  );
92
+ return overlayPortalContainer ? createPortal(toaster, overlayPortalContainer) : toaster;
80
93
  }
81
94
  export {
82
95
  Toaster
@@ -16,17 +16,34 @@ const buttonVariants = cva("ui-button", {
16
16
  // utilities, out-ranked it: the hover step this library takes from the derived tier
17
17
  // (`--primary-hover`) could never take effect while `hover:bg-primary/90` was emitted here.
18
18
  default: "ui-button--default",
19
- destructive: "ui-button--destructive bg-destructive text-destructive-foreground",
19
+ // `bg-destructive` LEFT for the reason spelled out on `default` above, and gh#880 is
20
+ // where it finally cost something: a utility out-ranks `@layer components`, so
21
+ // `--button-destructive-background` could not have reached this variant while it stood.
22
+ // The ink stays a utility — nothing competes for `color` here.
23
+ destructive: "ui-button--destructive text-destructive-foreground",
20
24
  // `shadow-xs` từng nằm ở đây và mắc ĐÚNG lỗi đã nêu ngay trên cho `default`:
21
25
  // một utility thì out-rank tầng components, nên bóng của nút outline đi vòng
22
26
  // qua tầng token — consumer đặt `--shadow-sm: none` thì `default` sạch bóng còn
23
27
  // `outline` trơ ra. Nay bóng khai ở `.ui-button--outline` và đọc `var(--shadow-sm)`
24
28
  // như mọi biến thể khác, nên nó tắt/đổi được qua theme. (gh#662)
25
- outline: "ui-button--outline border bg-background hover:bg-accent hover:text-accent-foreground",
26
- dashed: "ui-button--dashed border border-dashed bg-background hover:bg-accent hover:text-accent-foreground",
27
- secondary: "ui-button--secondary bg-secondary text-secondary-foreground hover:bg-secondary/80",
29
+ // `bg-background` gone for the same reason (gh#880). The HOVER fill stays a utility: it
30
+ // is the same value `.ui-button--outline:hover` declares, and moving it is a separate
31
+ // decision from making the resting surface reachable.
32
+ outline: "ui-button--outline border hover:bg-accent hover:text-accent-foreground",
33
+ // `dashed` is `outline` with a dashed edge, so it shares `--button-outline-background`
34
+ // rather than growing a knob of its own (gh#880).
35
+ dashed: "ui-button--dashed border border-dashed hover:bg-accent hover:text-accent-foreground",
36
+ secondary: "ui-button--secondary text-secondary-foreground hover:bg-secondary/80",
28
37
  ghost: "ui-button--ghost hover:bg-accent hover:text-accent-foreground",
29
- link: "ui-button--link text-primary underline-offset-4 hover:underline",
38
+ // `text-primary` gone for the reason `bg-background` went in gh#880 and the checkbox's
39
+ // `data-[state=checked]:bg-primary` went before it: a utility is layered AFTER components in
40
+ // Tailwind v4, so it silently out-ranked `.ui-button--link`'s own `color` and NO token could
41
+ // reach this ink. gh#884 measured the consequence — the link is the one Button variant whose
42
+ // brand colour is TEXT on a page surface rather than a fill, and it fails WCAG 2.2 AA at
43
+ // 9 of 15 theme x seed cells. The components-layer rule now resolves
44
+ // `var(--button-link-foreground, hsl(var(--primary)))`, and `text-primary` compiled to
45
+ // exactly that fallback, so the default is byte-identical.
46
+ link: "ui-button--link underline-offset-4 hover:underline",
30
47
  // NO utilities, deliberately: `bare` is the absence of geometry, and every property it has
31
48
  // to unset (the size tier's height and inline inset) is declared in the components layer.
32
49
  // A utility here would be the only thing that could out-rank it. See `.ui-button--bare`.
@@ -13,6 +13,16 @@ function resolveTarget(target) {
13
13
  if (!node || typeof window === "undefined" || node === window) return null;
14
14
  return node;
15
15
  }
16
+ function fixedContainingBlock(node) {
17
+ if (typeof window === "undefined") return null;
18
+ for (let n = node?.parentElement ?? null; n && n !== document.documentElement; n = n.parentElement) {
19
+ const cs = window.getComputedStyle(n);
20
+ if (cs.filter !== "none" || cs.backdropFilter !== "none" || cs.transform !== "none" || cs.perspective !== "none" || /transform|filter|perspective/.test(cs.willChange) || /paint|layout|strict|content/.test(cs.contain)) {
21
+ return n;
22
+ }
23
+ }
24
+ return null;
25
+ }
16
26
  const Affix = React.forwardRef(function Affix2({
17
27
  children,
18
28
  offsetBlockStart,
@@ -63,7 +73,11 @@ const Affix = React.forwardRef(function Affix2({
63
73
  (previous) => previous && previous.inline === rootRect.width && previous.block === contentRect.height ? previous : { inline: rootRect.width, block: contentRect.height }
64
74
  );
65
75
  const rect = targetElement?.getBoundingClientRect();
66
- const next = rect ? pinToEnd ? window.innerHeight - rect.bottom : rect.top : 0;
76
+ const blockEl = fixedContainingBlock(content);
77
+ const block = blockEl?.getBoundingClientRect();
78
+ const viewportInset = rect ? pinToEnd ? window.innerHeight - rect.bottom : rect.top : 0;
79
+ const blockOffset = block ? pinToEnd ? window.innerHeight - (block.bottom + ((blockEl?.scrollHeight ?? 0) - (blockEl?.clientHeight ?? 0) - (blockEl?.scrollTop ?? 0))) : block.top - (blockEl?.scrollTop ?? 0) : 0;
80
+ const next = viewportInset - blockOffset;
67
81
  setTargetInset((previous) => previous === next ? previous : next);
68
82
  }, [pinToEnd, targetElement]);
69
83
  React.useLayoutEffect(() => {
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { CONTRAST_PIVOT, relativeLuminance } from "../../app/tenant-theme.js";
4
5
  import { Slot } from "../../lib/slot.js";
5
6
  import { ChevronDown } from "lucide-react";
6
7
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../data-display/collapsible.js";
@@ -315,11 +316,16 @@ function Sidebar({
315
316
  return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
316
317
  brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brandNode }) : product ? (() => {
317
318
  const interactive = onProductClick != null;
319
+ const markLuminance = product.color != null ? relativeLuminance(product.color) : null;
320
+ const markInk = product.color == null ? "hsl(var(--attention-foreground))" : markLuminance == null ? "white" : markLuminance > CONTRAST_PIVOT ? "black" : "white";
318
321
  const mark = /* @__PURE__ */ jsx(
319
322
  "span",
320
323
  {
321
324
  className: "sb-logo-mark",
322
- style: { background: product.color ?? "hsl(var(--attention))" },
325
+ style: {
326
+ background: product.color ?? "hsl(var(--attention))",
327
+ color: `var(--sidebar-logo-mark-color, ${markInk})`
328
+ },
323
329
  children: product.name[0]?.toUpperCase() ?? "?"
324
330
  }
325
331
  );
@@ -58,7 +58,7 @@ export type { AnchorContainerProp, AnchorDirectionProp, AnchorItemProp, AnchorPr
58
58
  * the target section, not just the scroll position, so the next Tab continues from where the
59
59
  * reader was sent instead of from the nav.
60
60
  */
61
- export declare function Anchor({ items, direction, affix, bounds, getContainer, getCurrentAnchor, offsetBlockStart, targetOffsetBlockStart, showInkInFixed, replace, value, defaultValue, onValueChange, onChange, offsetTop, targetOffset, onClick, label, id, className, ...rest }: AnchorProp): React.JSX.Element;
61
+ export declare function Anchor({ items, direction, affix, bounds, target, getContainer, getCurrentAnchor, offsetBlockStart, targetOffsetBlockStart, showInkInFixed, replace, value, defaultValue, onValueChange, onChange, offsetTop, targetOffset, onClick, label, id, className, ...rest }: AnchorProp): React.JSX.Element;
62
62
  export declare namespace Anchor {
63
63
  var displayName: string;
64
64
  }
@@ -36,6 +36,7 @@ function Anchor({
36
36
  direction = "vertical",
37
37
  affix = true,
38
38
  bounds = DEFAULT_BOUNDS,
39
+ target,
39
40
  getContainer,
40
41
  getCurrentAnchor,
41
42
  offsetBlockStart,
@@ -117,10 +118,10 @@ function Anchor({
117
118
  if (!controlledRef.current) setInternal(href);
118
119
  onValueChangeRef.current?.(href);
119
120
  }, []);
120
- const container = React.useCallback(
121
- () => getContainer?.() ?? window,
122
- [getContainer]
123
- );
121
+ const container = React.useCallback(() => {
122
+ if (target) return target() ?? window;
123
+ return getContainer?.() ?? window;
124
+ }, [target, getContainer]);
124
125
  const line = targetOffsetBlockStart ?? offsetBlockStart ?? 0;
125
126
  const resolveFromScroll = React.useCallback(() => {
126
127
  if (typeof document === "undefined") return "";
@@ -122,6 +122,7 @@ const AppSettingPicker = React.forwardRef(
122
122
  }),
123
123
  disabled: disabled || unbound,
124
124
  name,
125
+ "aria-label": t(ARIA_KEY[kind]),
125
126
  children: [
126
127
  /* @__PURE__ */ jsxs(
127
128
  SelectTrigger,
@@ -216,7 +217,6 @@ const AppSettingPicker = React.forwardRef(
216
217
  ),
217
218
  "data-kind": kind,
218
219
  "data-appearance": resolvedAppearance,
219
- "aria-label": t(ARIA_KEY[kind]),
220
220
  children: [
221
221
  inline ? null : /* @__PURE__ */ jsx(
222
222
  Icon,
@@ -241,12 +241,12 @@ function Pagination({
241
241
  value: String(pageSize),
242
242
  onValueChange: (v) => go(1, Number(v)),
243
243
  disabled,
244
+ "aria-label": t("navigation.pagination.pageSize"),
244
245
  children: [
245
246
  /* @__PURE__ */ jsx(
246
247
  SelectTrigger,
247
248
  {
248
249
  className: "ui-pagination-size-trigger w-max min-w-[var(--pagination-size-width)]",
249
- "aria-label": t("navigation.pagination.pageSize"),
250
250
  children: /* @__PURE__ */ jsx(SelectValue, { children: t("navigation.pagination.pageSizeTrigger", { size: pageSize }) })
251
251
  }
252
252
  ),
@@ -494,7 +494,14 @@ const TabsList = React.forwardRef(
494
494
  // — so the leading tab sat permanently outside the scrollport (an unreachable control at
495
495
  // 320px, WCAG 2.2 SC 2.1.1). `safe` falls back to start alignment exactly when it
496
496
  // overflows, and still centres whenever the tabs fit.
497
- "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-[var(--tabs-list-line-space-gap)] data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
497
+ // `data-[variant=default]:bg-muted` USED TO BE HERE AND IS NOW `--tabs-list-background`
498
+ // in navigation-layout.css (gh#880). A Tailwind utility lives in `@layer utilities`, so
499
+ // while the track was painted from here NO rule this package writes could reach it and a
500
+ // theme had no knob at all — the visible defect being a translucent Card whose Tabs strip
501
+ // stayed opaque `--muted`. Nothing about the card/line strips changes: both clear the
502
+ // track with utilities of their own (`data-[variant=default]:bg-transparent`,
503
+ // `data-[variant=line]:bg-transparent`), and a utility still outranks the new rule.
504
+ "group/tabs-list text-muted-foreground 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-[var(--tabs-list-line-space-gap)] data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
498
505
  className
499
506
  ),
500
507
  render: (domProps) => withDomProps("div", "tabs-list", props, domProps, { "data-variant": variant }),
@@ -541,7 +548,13 @@ const TabsTrigger = React.forwardRef(
541
548
  // labels needing 107 / 95 / 53px, i.e. the SAME defect one API away. antd puts no
542
549
  // `flex-grow` on a tab of any `type`, so the rule belongs to the line STRIP rather than
543
550
  // to one construction path; the pill strip keeps `flex-1`, which is what fills its box.
544
- "text-muted-foreground ring-offset-background hover:text-foreground ui-focus-ring data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs:group-data-[variant=default]/tabs-list:data-[state=active]:border-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:flex-none group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
551
+ // `data-[state=active]:bg-background` USED TO BE HERE and is now
552
+ // `--tabs-trigger-active-background` in navigation-layout.css, for the same reason as the
553
+ // list's track (gh#880): the selected slab of this library's other track-and-slab control
554
+ // — Segmented — has been `--segmented-item-selected-background` since gh#848, and these
555
+ // two are documented as the same pair. The card face and the line strip both restate the
556
+ // fill as utilities, which still outrank the new rule, so neither moves.
557
+ "text-muted-foreground ring-offset-background hover:text-foreground ui-focus-ring data-[state=active]:text-foreground group-data-[variant=default]/tabs:group-data-[variant=default]/tabs-list:data-[state=active]:border-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:flex-none group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
545
558
  // The tier the root was given. Without this the compound form ignored `size` outright.
546
559
  triggerSizeClassName(size),
547
560
  className
@@ -1,14 +1,30 @@
1
1
  import * as React from "react";
2
2
  import type { InfiniteQueryStateProp } from "../../props/components/query.prop.js";
3
3
  export type { InfiniteQueryStateProp, InfiniteQueryStateProp as InfiniteQueryStateProps, } from "../../props/components/query.prop.js";
4
- /** Flatten `{ pages: [{ items }] }` — default GODX paginated API shape. */
5
- export declare function flattenItemPages<TItem, TPage extends {
6
- items: TItem[];
7
- }>(data: {
8
- pages: TPage[];
4
+ /**
5
+ * Flatten `{ pages: [{ items }] }` — default GODX paginated API shape.
6
+ *
7
+ * TItem USED TO BE ITS OWN TYPE PARAMETER, constrained by `TPage extends { items: TItem[] }`, and
8
+ * that made it uninferable in the one way this function is documented to be used. TypeScript infers
9
+ * a type parameter from ARGUMENT POSITIONS, never from another parameter's constraint, so passing
10
+ * the function by reference — `flatten={flattenItemPages}`, the whole API — left TItem at its
11
+ * fallback and collapsed InfiniteQueryState's TFlat to `unknown`. The catalog example was written
12
+ * with `flattenItemPages as any` and `(items: any)` to silence exactly that, which is how the
13
+ * signature survived (gh#889).
14
+ *
15
+ * Taking the page shape INLINE — `{ pages: { items: TItem[] }[] }` — puts TItem in an argument
16
+ * position, so it is inferred from the real page type and `items` arrives as `Activity[]`. TPage
17
+ * itself was never used for anything but that constraint, so naming it bought nothing.
18
+ */
19
+ export declare function flattenItemPages<TItem>(data: {
20
+ pages: {
21
+ items: TItem[];
22
+ }[];
9
23
  } | undefined): TItem[];
10
24
  /**
11
25
  * `useInfiniteQuery` lifecycle widget — flatten pages, load-more footer.
12
26
  * Cursor / activity feeds where user accumulates pages (vs DataTable cursor buttons).
13
27
  */
14
- export declare function InfiniteQueryState<TPage, TFlat>({ query, skeleton, empty, flatten, isEmpty, errorRenderer, showRetry, onRetry, onAuthError, loadingMore, loadMore, showLoadMore, children, }: InfiniteQueryStateProp<TPage, TFlat>): React.JSX.Element;
28
+ export declare function InfiniteQueryState<TPage, TFlat = TPage extends {
29
+ items: (infer TItem)[];
30
+ } ? TItem[] : unknown>({ query, skeleton, empty, flatten, isEmpty, errorRenderer, showRetry, onRetry, onAuthError, loadingMore, loadMore, showLoadMore, children, }: InfiniteQueryStateProp<TPage, TFlat>): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  /** Shared control sizing — reads `--control-height`, `--font-size-*` from density / theme. */
2
- export declare const controlMultilineClass = "ui-control-multiline aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] border-input bg-background ring-offset-background placeholder:text-muted-foreground";
2
+ export declare const controlMultilineClass = "ui-control-multiline ui-control-outlined-surface aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
3
3
  /**
4
4
  * Multiline control with its own chrome removed, for a textarea EMBEDDED in a surface that already
5
5
  * draws the box — a chat composer inside a Card, an inline edit cell, a comment box in a panel.
@@ -9,10 +9,10 @@ export declare const controlMultilineGhostClass = "ui-control-multiline data-[st
9
9
  /**
10
10
  * Multiline control drawn as antd's `filled` variant — a tinted surface instead of a boundary.
11
11
  *
12
- * It is a SEPARATE string rather than `controlMultilineClass` plus a modifier because
13
- * `bg-background` lives in `@layer utilities`, which outranks every component-layer rule: a
14
- * `.ui-control--filled` appended to the outlined list would be silently overpainted and no gate
15
- * would catch it. The two lists differ only in which chrome utilities they carry.
12
+ * It is a SEPARATE string rather than `controlMultilineClass` plus a modifier because the two
13
+ * surfaces are alternatives, not a base plus a modifier: appending `.ui-control--filled` to the
14
+ * outlined list would put two same-layer rules on one element and leave the winner to source order.
15
+ * The two lists differ only in which chrome class they carry.
16
16
  */
17
17
  export declare const controlMultilineFilledClass = "ui-control-multiline ui-control--filled aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
18
18
  /**
@@ -47,7 +47,15 @@ export declare const controlOpenRingClass = "ui-control-trigger";
47
47
  * The clamp keeps it to one line, so a wrappable value cannot grow the control.
48
48
  */
49
49
  export declare const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
50
- export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis border-input bg-background";
50
+ /**
51
+ * The trigger WITH the resting surface — `.ui-control-outlined-surface`, which paints the identical
52
+ * border and fill from `--control-surface-border-color` / `--control-surface-background`.
53
+ *
54
+ * It used to be the two Tailwind utilities `border-input bg-background`, and `@layer utilities`
55
+ * outranks `@layer components`, so those two tokens could not reach it (gh#880). The painted result
56
+ * is unchanged; what changed is that a theme can now reach it.
57
+ */
58
+ export declare const controlTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-outlined-surface";
51
59
  /** `controlTriggerBaseClass` + the token-driven surface — the select-family trigger. */
52
60
  export declare const controlSurfaceTriggerClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis ui-control-surface";
53
61
  export declare const controlIconClass = "size-[length:var(--control-height)] shrink-0";
@@ -59,15 +67,27 @@ export declare const tableHeadHeightClass = "h-[length:var(--table-row-height)]"
59
67
  export declare const tableCellPaddingClass = "py-[length:var(--table-cell-padding-y)]";
60
68
  /** Semantic status / badge tones — always use tokens, never raw Tailwind palette. The TEXT uses the
61
69
  * AA-strong status colours (text-*-strong, darker than the fill) so a small status label clears
62
- * WCAG AA on the soft tint; the border/fill keep the brighter wa-iro role. */
63
- export declare const toneSuccessClass = "border-success/30 bg-success/10 text-success-strong";
64
- export declare const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-strong";
65
- export declare const toneInfoClass = "border-info/30 bg-info/10 text-info-strong";
70
+ * WCAG AA on the soft tint; the border keeps the brighter wa-iro role.
71
+ *
72
+ * THE FILL IS AN ARBITRARY PROPERTY, NOT `bg-success/10`, AND THAT IS THE WHOLE OF gh#866 HERE.
73
+ * `bg-<role>/10` can only ever be the role at 10% — a derivation — so a brand that ships an
74
+ * independently chosen pale surface (`--success #126342` with `--success-soft #E8F5EF`, a mint
75
+ * that is not that green at any alpha) could not reach the chip. `[background-color:…]` is the
76
+ * same utility layer and the same specificity as the class it replaces, so nothing about the
77
+ * cascade moves; it just reads `--surface-<role>` first and falls back to the wash Tailwind itself emits,
78
+ * `color-mix(in oklab, … 10%, transparent)` — the verbose spelling is deliberate, because
79
+ * `hsl(var(--success) / 0.1)` rasterises one 8-bit step off it on `warning` (252,245,225 vs
80
+ * 252,246,225 over the page ground) and "near enough" is not a default this repo ships.
81
+ * Written at the CALL SITE rather than bound at `:root`, per docs/TOKENS.md · "Role-mirror knobs
82
+ * MUST be `initial`", so a scoped `[data-tenant]` reaches it instead of freezing on `<html>`. */
83
+ export declare const toneSuccessClass = "border-success/30 [background-color:var(--surface-success,color-mix(in_oklab,hsl(var(--success))_10%,transparent))] text-success-strong";
84
+ export declare const toneWarningClass = "border-warning/30 [background-color:var(--surface-warning,color-mix(in_oklab,hsl(var(--warning))_10%,transparent))] text-warning-strong";
85
+ export declare const toneInfoClass = "border-info/30 [background-color:var(--surface-info,color-mix(in_oklab,hsl(var(--info))_10%,transparent))] text-info-strong";
66
86
  /** Soft BRAND pill — a tinted primary tone (border/fill keep the brand role; the TEXT uses the
67
87
  * AA-strong brand colour `text-primary-strong`, darker than the fill, so a small brand label clears
68
88
  * WCAG AA on the soft tint — `text-primary` alone is only 4.04:1 in light). For a SOLID brand fill
69
89
  * use the Badge `default` variant instead. */
70
90
  export declare const tonePrimaryClass = "border-primary/30 bg-primary/10 text-primary-strong";
71
- export declare const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-error-strong";
91
+ export declare const toneDestructiveClass = "border-destructive/30 [background-color:var(--surface-destructive,color-mix(in_oklab,hsl(var(--destructive))_10%,transparent))] text-error-strong";
72
92
  export declare const toneMutedClass = "border-border bg-muted text-muted-foreground";
73
93
  export declare const toneNeutralClass = "border-border bg-muted text-muted-foreground";
@@ -1,9 +1,9 @@
1
- const controlMultilineClass = "ui-control-multiline aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] border-input bg-background ring-offset-background placeholder:text-muted-foreground";
1
+ const controlMultilineClass = "ui-control-multiline ui-control-outlined-surface aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
2
2
  const controlMultilineGhostClass = "ui-control-multiline data-[status=error]:border-destructive data-[status=warning]:border-warning w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:ring-0";
3
3
  const controlMultilineFilledClass = "ui-control-multiline ui-control--filled aria-invalid:border-destructive data-[status=error]:border-destructive data-[status=warning]:border-warning w-full rounded-[var(--control-radius)] ring-offset-background placeholder:text-muted-foreground";
4
4
  const controlOpenRingClass = "ui-control-trigger";
5
5
  const controlTriggerBaseClass = "ui-control ui-control-trigger flex items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] transition-[color,box-shadow] [&>[data-slot=select-value]]:line-clamp-1 [&>[data-slot=select-value]]:whitespace-normal [&>[data-slot=select-value]]:text-ellipsis";
6
- const controlTriggerClass = `${controlTriggerBaseClass} border-input bg-background`;
6
+ const controlTriggerClass = `${controlTriggerBaseClass} ui-control-outlined-surface`;
7
7
  const controlSurfaceTriggerClass = `${controlTriggerBaseClass} ui-control-surface`;
8
8
  const controlIconClass = "size-[length:var(--control-height)] shrink-0";
9
9
  const controlIconSmClass = "size-[calc(var(--control-height)-0.5rem)] shrink-0";
@@ -11,11 +11,11 @@ const controlIconLeadingClass = "size-[length:var(--control-icon-size)] shrink-0
11
11
  const tableRowHeightClass = "h-[length:var(--table-row-height)]";
12
12
  const tableHeadHeightClass = "h-[length:var(--table-row-height)]";
13
13
  const tableCellPaddingClass = "py-[length:var(--table-cell-padding-y)]";
14
- const toneSuccessClass = "border-success/30 bg-success/10 text-success-strong";
15
- const toneWarningClass = "border-warning/30 bg-warning/10 text-warning-strong";
16
- const toneInfoClass = "border-info/30 bg-info/10 text-info-strong";
14
+ const toneSuccessClass = "border-success/30 [background-color:var(--surface-success,color-mix(in_oklab,hsl(var(--success))_10%,transparent))] text-success-strong";
15
+ const toneWarningClass = "border-warning/30 [background-color:var(--surface-warning,color-mix(in_oklab,hsl(var(--warning))_10%,transparent))] text-warning-strong";
16
+ const toneInfoClass = "border-info/30 [background-color:var(--surface-info,color-mix(in_oklab,hsl(var(--info))_10%,transparent))] text-info-strong";
17
17
  const tonePrimaryClass = "border-primary/30 bg-primary/10 text-primary-strong";
18
- const toneDestructiveClass = "border-destructive/30 bg-destructive/10 text-error-strong";
18
+ const toneDestructiveClass = "border-destructive/30 [background-color:var(--surface-destructive,color-mix(in_oklab,hsl(var(--destructive))_10%,transparent))] text-error-strong";
19
19
  const toneMutedClass = "border-border bg-muted text-muted-foreground";
20
20
  const toneNeutralClass = "border-border bg-muted text-muted-foreground";
21
21
  export {