@sableui/react 0.15.0 → 0.17.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 (145) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/bin/sable-lint.mjs +5 -1
  3. package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
  4. package/dist/blocks/approval-block.js +30 -48
  5. package/dist/blocks/approval-block.meta.js +7 -8
  6. package/dist/blocks/index.d.ts +1 -1
  7. package/dist/blocks/wallet-block.js +2 -2
  8. package/dist/components/app-shell/app-shell-frame.js +16 -0
  9. package/dist/components/app-shell/app-shell-main.js +12 -0
  10. package/dist/components/app-shell/app-shell.js +28 -4
  11. package/dist/components/app-shell/app-shell.meta.js +88 -4
  12. package/dist/components/area-chart/area-chart.meta.js +6 -1
  13. package/dist/components/attachment/attachment.meta.js +2 -1
  14. package/dist/components/attachment/attachment.variants.js +1 -1
  15. package/dist/components/badge/badge.meta.js +8 -1
  16. package/dist/components/badge/badge.variants.js +7 -7
  17. package/dist/components/banner/banner.meta.js +6 -1
  18. package/dist/components/banner/banner.variants.js +5 -5
  19. package/dist/components/bar-chart/bar-chart.meta.js +6 -1
  20. package/dist/components/caret-menu/caret-menu.js +5 -6
  21. package/dist/components/chat-composer/chat-composer.meta.js +2 -2
  22. package/dist/components/chat-composer/chat-composer.variants.js +1 -1
  23. package/dist/components/color-field/color-field.js +6 -8
  24. package/dist/components/combobox/combobox.js +27 -5
  25. package/dist/components/combobox/combobox.meta.js +10 -2
  26. package/dist/components/command-menu/command-menu.js +14 -3
  27. package/dist/components/command-menu/command-menu.meta.js +9 -1
  28. package/dist/components/date-picker/date-picker.meta.js +2 -2
  29. package/dist/components/feedback/feedback.js +1 -1
  30. package/dist/components/feedback/feedback.meta.js +2 -1
  31. package/dist/components/gradient-art/gradient-art.js +8 -5
  32. package/dist/components/gradient-art/gradient-art.meta.js +35 -2
  33. package/dist/components/input/input.meta.js +11 -3
  34. package/dist/components/input-group/input-group.js +1 -1
  35. package/dist/components/input-group/input-group.meta.js +1 -1
  36. package/dist/components/line-chart/line-chart.meta.js +6 -1
  37. package/dist/components/menu/menu.js +1 -1
  38. package/dist/components/meter/meter.js +53 -7
  39. package/dist/components/meter/meter.meta.js +51 -4
  40. package/dist/components/meter/meter.variants.js +43 -7
  41. package/dist/components/note/note.meta.js +5 -1
  42. package/dist/components/note/note.variants.js +4 -4
  43. package/dist/components/number-field/number-field.meta.js +2 -2
  44. package/dist/components/otp-field/otp-field.meta.js +2 -2
  45. package/dist/components/panel-group/panel-group.js +5 -8
  46. package/dist/components/pie-chart/pie-chart.meta.js +6 -1
  47. package/dist/components/radar-chart/radar-chart.meta.js +6 -1
  48. package/dist/components/radial-chart/radial-chart.meta.js +6 -1
  49. package/dist/components/search-input/search-input.meta.js +11 -3
  50. package/dist/components/section-rail/index.js +7 -0
  51. package/dist/components/section-rail/section-rail.js +153 -0
  52. package/dist/components/section-rail/section-rail.meta.js +189 -0
  53. package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
  54. package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
  55. package/dist/components/select/select.js +1 -1
  56. package/dist/components/select/select.meta.js +2 -2
  57. package/dist/components/sheet/sheet.js +5 -4
  58. package/dist/components/sidebar/sidebar.js +16 -1
  59. package/dist/components/sidebar/sidebar.meta.js +7 -2
  60. package/dist/components/sparkline/sparkline.meta.js +8 -1
  61. package/dist/components/status-dot/status-dot.meta.js +8 -1
  62. package/dist/components/status-dot/status-dot.variants.js +7 -7
  63. package/dist/components/stepper/stepper.meta.js +2 -1
  64. package/dist/components/stepper/stepper.variants.js +1 -1
  65. package/dist/components/table/table.meta.js +4 -4
  66. package/dist/components/textarea/textarea.js +5 -0
  67. package/dist/components/textarea/textarea.meta.js +3 -3
  68. package/dist/file-hashes.json +99 -92
  69. package/dist/foundations/accessibility.js +5 -0
  70. package/dist/index.d.ts +227 -25
  71. package/dist/index.js +12 -1
  72. package/dist/lib/chart/types.js +8 -8
  73. package/dist/lib/chart/use-arrival.js +5 -4
  74. package/dist/lib/field-shell.js +1 -1
  75. package/dist/lib/highlight-glide.js +6 -6
  76. package/dist/lib/scrub-gesture.js +6 -6
  77. package/dist/lib/taxonomy.js +1 -0
  78. package/dist/lib/use-active-section.js +59 -0
  79. package/dist/lib/use-caret-trigger.js +11 -18
  80. package/dist/lib/use-match-highlight.js +69 -0
  81. package/dist/lib/use-smooth-wheel.js +10 -8
  82. package/dist/presets/index.d.ts +13 -2
  83. package/dist/presets/index.js +17 -1
  84. package/dist/styles.css +100 -0
  85. package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
  86. package/dist/themes/_contract.js +14 -0
  87. package/dist/themes/atelier/atelier.css +18 -0
  88. package/dist/themes/atelier/index.d.ts +1 -1
  89. package/dist/themes/contrast.js +48 -1
  90. package/dist/themes/ember/ember.css +18 -0
  91. package/dist/themes/ember/index.d.ts +1 -1
  92. package/dist/themes/index.d.ts +2 -2
  93. package/dist/themes/meridian/index.d.ts +1 -1
  94. package/dist/themes/meridian/meridian.css +18 -0
  95. package/dist/themes/poppy/index.d.ts +1 -1
  96. package/dist/themes/poppy/poppy.css +18 -0
  97. package/dist/themes/prism/index.d.ts +1 -1
  98. package/dist/themes/prism/prism.css +18 -0
  99. package/dist/themes/sable/index.d.ts +1 -1
  100. package/dist/themes/sable/sable.css +18 -0
  101. package/dist/themes/verdant/index.d.ts +1 -1
  102. package/dist/themes/verdant/verdant.css +18 -0
  103. package/llms/blocks/approval-block.md +40 -55
  104. package/llms/blocks/wallet-block.md +5 -4
  105. package/llms/components/app-shell.md +72 -3
  106. package/llms/components/area-chart.md +1 -1
  107. package/llms/components/attachment.md +1 -1
  108. package/llms/components/badge.md +1 -1
  109. package/llms/components/banner.md +1 -1
  110. package/llms/components/bar-chart.md +1 -1
  111. package/llms/components/chat-composer.md +1 -1
  112. package/llms/components/combobox.md +2 -1
  113. package/llms/components/command-menu.md +3 -2
  114. package/llms/components/date-picker.md +1 -1
  115. package/llms/components/feedback.md +1 -1
  116. package/llms/components/gradient-art.md +28 -1
  117. package/llms/components/input-group.md +1 -1
  118. package/llms/components/input.md +4 -2
  119. package/llms/components/line-chart.md +1 -1
  120. package/llms/components/meter.md +33 -4
  121. package/llms/components/note.md +1 -1
  122. package/llms/components/number-field.md +1 -1
  123. package/llms/components/otp-field.md +1 -1
  124. package/llms/components/pie-chart.md +1 -1
  125. package/llms/components/radar-chart.md +1 -1
  126. package/llms/components/radial-chart.md +1 -1
  127. package/llms/components/search-input.md +20 -2
  128. package/llms/components/section-rail.md +101 -0
  129. package/llms/components/segmented-bar.md +1 -1
  130. package/llms/components/select.md +1 -1
  131. package/llms/components/sidebar.md +3 -2
  132. package/llms/components/sparkline.md +1 -1
  133. package/llms/components/status-dot.md +1 -1
  134. package/llms/components/stepper.md +1 -1
  135. package/llms/components/table.md +2 -2
  136. package/llms/components/textarea.md +2 -2
  137. package/llms/foundations/accessibility.md +1 -0
  138. package/llms/reference/token-intent.md +22 -2
  139. package/llms/reference/utilities.md +5 -2
  140. package/llms-full.txt +367 -105
  141. package/llms.txt +6 -5
  142. package/package.json +1 -1
  143. package/registry.json +609 -67
  144. package/scripts/lint-rules.mjs +6 -3
  145. package/targets/ios.json +9 -0
@@ -137,7 +137,14 @@ var badgeMeta = {
137
137
  "tone-info-foreground",
138
138
  "success-foreground",
139
139
  "tone-warning-foreground",
140
- "destructive-foreground"
140
+ "destructive-foreground",
141
+ "destructive-mark",
142
+ "success-mark",
143
+ "tone-warning-mark",
144
+ "tone-info-mark",
145
+ "tone-1-mark",
146
+ "tone-2-mark",
147
+ "tone-3-mark"
141
148
  ],
142
149
  a11y: [
143
150
  "Badges are static text by default; do not rely on color alone for meaning.",
@@ -21,13 +21,13 @@ var MATRIX = {
21
21
  // mixed from them fell under 4.5:1 (Meridian dark destructive, 3.8:1).
22
22
  soft: {
23
23
  neutral: "bg-secondary text-secondary-foreground",
24
- "3": "bg-tone-3/15 text-[color-mix(in_oklch,var(--tone-3)_75%,var(--foreground))]",
25
- "2": "bg-tone-2/15 text-[color-mix(in_oklch,var(--tone-2)_75%,var(--foreground))]",
26
- "1": "bg-tone-1/15 text-[color-mix(in_oklch,var(--tone-1)_75%,var(--foreground))]",
27
- info: "bg-tone-info/15 text-[color-mix(in_oklch,var(--tone-info-text)_75%,var(--foreground))]",
28
- success: "bg-success/15 text-[color-mix(in_oklch,var(--success-text)_75%,var(--foreground))]",
29
- warning: "bg-tone-warning/15 text-[color-mix(in_oklch,var(--tone-warning-text)_75%,var(--foreground))]",
30
- destructive: "bg-destructive/15 text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]",
24
+ "3": "bg-tone-3-mark/15 text-[color-mix(in_oklch,var(--tone-3)_75%,var(--foreground))]",
25
+ "2": "bg-tone-2-mark/15 text-[color-mix(in_oklch,var(--tone-2)_75%,var(--foreground))]",
26
+ "1": "bg-tone-1-mark/15 text-[color-mix(in_oklch,var(--tone-1)_75%,var(--foreground))]",
27
+ info: "bg-tone-info-mark/15 text-[color-mix(in_oklch,var(--tone-info-text)_75%,var(--foreground))]",
28
+ success: "bg-success-mark/15 text-[color-mix(in_oklch,var(--success-text)_75%,var(--foreground))]",
29
+ warning: "bg-tone-warning-mark/15 text-[color-mix(in_oklch,var(--tone-warning-text)_75%,var(--foreground))]",
30
+ destructive: "bg-destructive-mark/15 text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]",
31
31
  ...families("soft")
32
32
  },
33
33
  outline: {
@@ -131,7 +131,12 @@ var bannerMeta = {
131
131
  "tone-1",
132
132
  "tone-1-foreground",
133
133
  "destructive",
134
- "destructive-foreground"
134
+ "destructive-foreground",
135
+ "destructive-mark",
136
+ "success-mark",
137
+ "tone-warning-mark",
138
+ "tone-info-mark",
139
+ "tone-1-mark"
135
140
  ],
136
141
  a11y: [
137
142
  "Static banners render as a labelled region (role=region); set `live` to announce dynamic status bars (role=status / role=alert) when they appear or change.",
@@ -20,11 +20,11 @@ var bannerVariants = cva(
20
20
  compoundVariants: [
21
21
  // Soft — a quiet tint of the tone (the default announcement look).
22
22
  { tone: "neutral", appearance: "soft", class: "bg-secondary text-secondary-foreground" },
23
- { tone: "info", appearance: "soft", class: "bg-tone-info/15 text-foreground" },
24
- { tone: "success", appearance: "soft", class: "bg-success/15 text-foreground" },
25
- { tone: "warning", appearance: "soft", class: "bg-tone-warning/15 text-foreground" },
26
- { tone: "1", appearance: "soft", class: "bg-tone-1/15 text-foreground" },
27
- { tone: "destructive", appearance: "soft", class: "bg-destructive/15 text-foreground" },
23
+ { tone: "info", appearance: "soft", class: "bg-tone-info-mark/15 text-foreground" },
24
+ { tone: "success", appearance: "soft", class: "bg-success-mark/15 text-foreground" },
25
+ { tone: "warning", appearance: "soft", class: "bg-tone-warning-mark/15 text-foreground" },
26
+ { tone: "1", appearance: "soft", class: "bg-tone-1-mark/15 text-foreground" },
27
+ { tone: "destructive", appearance: "soft", class: "bg-destructive-mark/15 text-foreground" },
28
28
  // Solid — the full tone fill with its paired ink, for loud states.
29
29
  { tone: "neutral", appearance: "solid", class: "bg-foreground text-background" },
30
30
  { tone: "info", appearance: "solid", class: "bg-tone-info text-tone-info-foreground" },
@@ -199,7 +199,12 @@ var barChartMeta = {
199
199
  "popover",
200
200
  "popover-foreground",
201
201
  "foreground",
202
- "background"
202
+ "background",
203
+ "tone-warning-mark",
204
+ "tone-info-mark",
205
+ "tone-1-mark",
206
+ "tone-2-mark",
207
+ "tone-3-mark"
203
208
  ],
204
209
  motion: [
205
210
  {
@@ -124,12 +124,11 @@ function CaretMenu({ trigger, label, status, children, className }) {
124
124
  if (popupRef.current?.contains(event.target)) return;
125
125
  place();
126
126
  };
127
- view.addEventListener("resize", place);
128
- view.addEventListener("scroll", onScroll, true);
129
- return () => {
130
- view.removeEventListener("resize", place);
131
- view.removeEventListener("scroll", onScroll, true);
132
- };
127
+ const listeners = new AbortController();
128
+ const { signal } = listeners;
129
+ view.addEventListener("resize", place, { signal });
130
+ view.addEventListener("scroll", onScroll, { capture: true, signal });
131
+ return () => listeners.abort();
133
132
  }, [open, field, place]);
134
133
  const [entered, setEntered] = useState(false);
135
134
  useEffect(() => {
@@ -223,8 +223,8 @@ var chatComposerMeta = {
223
223
  ],
224
224
  variants: { invalid: ["true", "false"] },
225
225
  tokensUsed: [
226
- "card",
227
- "input",
226
+ "field",
227
+ "field-border",
228
228
  "ring",
229
229
  "destructive",
230
230
  "foreground",
@@ -1,7 +1,7 @@
1
1
  // src/components/chat-composer/chat-composer.variants.ts
2
2
  import { cva } from "class-variance-authority";
3
3
  var chatComposerVariants = cva(
4
- "flex w-full flex-col gap-2 border border-input bg-card p-2 motion-interactive focus-within:ring-2 focus-within:ring-ring [--chat-composer-r:var(--chat-composer-radius,var(--card-inner-radius,calc(min(var(--button-radius,9999px),var(--control-sm)/2)+(--spacing(2))+1px)))] rounded-[var(--chat-composer-r)]",
4
+ "flex w-full flex-col gap-2 border border-field-border bg-field p-2 motion-interactive focus-within:ring-2 focus-within:ring-ring [--chat-composer-r:var(--chat-composer-radius,var(--card-inner-radius,calc(min(var(--button-radius,9999px),var(--control-sm)/2)+(--spacing(2))+1px)))] rounded-[var(--chat-composer-r)]",
5
5
  {
6
6
  variants: {
7
7
  invalid: {
@@ -27,15 +27,13 @@ function useAreaDrag(onChange) {
27
27
  );
28
28
  };
29
29
  emit(event.clientX, event.clientY);
30
+ const drag = new AbortController();
31
+ const { signal } = drag;
30
32
  const onMove = (e) => emit(e.clientX, e.clientY);
31
- const onUp = () => {
32
- window.removeEventListener("pointermove", onMove);
33
- window.removeEventListener("pointerup", onUp);
34
- window.removeEventListener("pointercancel", onUp);
35
- };
36
- window.addEventListener("pointermove", onMove);
37
- window.addEventListener("pointerup", onUp);
38
- window.addEventListener("pointercancel", onUp);
33
+ const onUp = () => drag.abort();
34
+ window.addEventListener("pointermove", onMove, { signal });
35
+ window.addEventListener("pointerup", onUp, { signal });
36
+ window.addEventListener("pointercancel", onUp, { signal });
39
37
  },
40
38
  [onChange]
41
39
  )
@@ -20,13 +20,18 @@ import { useAnchoredDensity, useDensity, useTypeTier } from "../../lib/density.j
20
20
  import { useResizeObserver } from "../../lib/use-resize-observer.js";
21
21
  import { HighlightGlide } from "../../lib/highlight-glide.js";
22
22
  import { hitTargetMin } from "../../lib/hit-target.js";
23
+ import { mergeRefs } from "../../lib/refs.js";
24
+ import { useMatchHighlight } from "../../lib/use-match-highlight.js";
23
25
  import { Badge } from "../badge/index.js";
24
26
  import { Button } from "../button/index.js";
25
27
  import { jsx, jsxs } from "react/jsx-runtime";
26
28
  var useComboboxFilteredItems = Base.useFilteredItems;
27
29
  var ComboboxVirtualContext = createContext(null);
30
+ var ComboboxMatchContext = createContext("");
28
31
  function Combobox({
29
32
  onItemHighlighted,
33
+ onInputValueChange,
34
+ highlightMatches = true,
30
35
  ...props
31
36
  }) {
32
37
  const revealRef = useRef(null);
@@ -45,6 +50,16 @@ function Combobox({
45
50
  },
46
51
  []
47
52
  );
53
+ const [typed, setTyped] = useState("");
54
+ const inputChangeRef = useRef(onInputValueChange);
55
+ inputChangeRef.current = onInputValueChange;
56
+ const handleInputValueChange = useCallback(
57
+ (value, details) => {
58
+ inputChangeRef.current?.(value, details);
59
+ setTyped(details.reason === "input-change" ? value : "");
60
+ },
61
+ []
62
+ );
48
63
  const bridge = useMemo(
49
64
  () => ({
50
65
  register(reveal) {
@@ -55,13 +70,14 @@ function Combobox({
55
70
  }),
56
71
  [props.value, props.isItemEqualToValue]
57
72
  );
58
- return /* @__PURE__ */ jsx(ComboboxVirtualContext.Provider, { value: bridge, children: /* @__PURE__ */ jsx(
73
+ return /* @__PURE__ */ jsx(ComboboxVirtualContext.Provider, { value: bridge, children: /* @__PURE__ */ jsx(ComboboxMatchContext.Provider, { value: highlightMatches ? typed : "", children: /* @__PURE__ */ jsx(
59
74
  Base.Root,
60
75
  {
61
76
  ...props,
62
- onItemHighlighted: handleHighlight
77
+ onItemHighlighted: handleHighlight,
78
+ onInputValueChange: handleInputValueChange
63
79
  }
64
- ) });
80
+ ) }) });
65
81
  }
66
82
  var ComboboxValue = Base.Value;
67
83
  var ComboboxClear = Base.Clear;
@@ -71,7 +87,7 @@ var ComboboxItemIndicator = Base.ItemIndicator;
71
87
  var ComboboxGroup = Base.Group;
72
88
  var FIELD_SHELL = {
73
89
  xs: cn(FIELD_SHELL_XS, "pe-7 text-ui-md"),
74
- md: "rounded-sm border border-input bg-card pe-9"
90
+ md: "rounded-sm border border-field-border bg-field pe-9"
75
91
  };
76
92
  function useFieldType(xs) {
77
93
  const tier = useTypeTier();
@@ -203,12 +219,18 @@ function ComboboxContent({
203
219
  align = "start",
204
220
  sideOffset = 6,
205
221
  scroll = true,
222
+ ref,
206
223
  ...props
207
224
  }) {
208
225
  const anchoredDensity = useAnchoredDensity();
226
+ const paintMatches = useMatchHighlight(useContext(ComboboxMatchContext), {
227
+ selector: "[data-sable-match]"
228
+ });
229
+ const popupRef = useMemo(() => mergeRefs(ref, paintMatches), [ref, paintMatches]);
209
230
  return /* @__PURE__ */ jsx(ComboboxPortal, { children: /* @__PURE__ */ jsx(Base.Positioner, { side, align, sideOffset, className: "z-(--z-overlay)", children: /* @__PURE__ */ jsxs(
210
231
  Base.Popup,
211
232
  {
233
+ ref: popupRef,
212
234
  ...anchoredDensity,
213
235
  className: cn(
214
236
  "w-[var(--anchor-width)] origin-(--transform-origin) rounded-md border border-border-floating bg-popover text-popover-foreground shadow-md outline-none",
@@ -240,7 +262,7 @@ function ComboboxItem({ className, children, ...props }) {
240
262
  ),
241
263
  ...props,
242
264
  children: [
243
- /* @__PURE__ */ jsx("span", { className: "truncate", children }),
265
+ /* @__PURE__ */ jsx("span", { "data-sable-match": "", className: "truncate", children }),
244
266
  /* @__PURE__ */ jsx(Base.ItemIndicator, { className: "flex items-center text-(--primary)", children: /* @__PURE__ */ jsx(Icon, { name: "check", className: "text-sm" }) })
245
267
  ]
246
268
  }
@@ -121,6 +121,12 @@ var comboboxMeta = {
121
121
  default: "Base UI's locale-aware contains",
122
122
  description: "How items match the query. `null` turns matching off, for items a server already filtered: pair it with `onInputValueChange` to fetch."
123
123
  },
124
+ {
125
+ name: "highlightMatches",
126
+ type: "boolean",
127
+ default: "true",
128
+ description: "Paints the typed text wherever it appears in a row's label, on the `highlight` / `highlight-foreground` pair, through the CSS Custom Highlight API \u2014 no `<mark>` in the row, and the accessible name is unchanged. Only typed text counts: a field still holding the selected item's label lists everything and lights nothing. A literal, case-insensitive match within the label; a `filter` that matches differently leaves rows it kept unpainted. `false` turns it off."
129
+ },
124
130
  {
125
131
  name: "onInputValueChange",
126
132
  type: "(inputValue: string, eventDetails) => void",
@@ -188,8 +194,10 @@ var comboboxMeta = {
188
194
  }
189
195
  ],
190
196
  tokensUsed: [
191
- "input",
192
- "card",
197
+ "highlight",
198
+ "highlight-foreground",
199
+ "field-border",
200
+ "field",
193
201
  "foreground",
194
202
  "muted-foreground",
195
203
  "ring",
@@ -21,6 +21,7 @@ import {
21
21
  DialogTitle
22
22
  } from "../dialog/dialog.js";
23
23
  import { mergeRefs } from "../../lib/refs.js";
24
+ import { useMatchHighlight } from "../../lib/use-match-highlight.js";
24
25
  import { Button } from "../button/index.js";
25
26
  import { Scroller } from "../scroller/scroller.js";
26
27
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -44,6 +45,7 @@ function CommandMenuBody({
44
45
  setQuery,
45
46
  items,
46
47
  filter,
48
+ highlightMatches = true,
47
49
  listClassName,
48
50
  placeholder,
49
51
  emptyMessage,
@@ -99,6 +101,11 @@ function CommandMenuBody({
99
101
  },
100
102
  [seedActive]
101
103
  );
104
+ const paintMatches = useMatchHighlight(query, {
105
+ selector: "[data-sable-match]",
106
+ enabled: highlightMatches
107
+ });
108
+ const listRefs = useMemo(() => mergeRefs(attachList, paintMatches), [attachList, paintMatches]);
102
109
  useEffect(() => {
103
110
  if (open) seedActive();
104
111
  }, [open, query]);
@@ -215,7 +222,7 @@ function CommandMenuBody({
215
222
  /* @__PURE__ */ jsxs(
216
223
  Scroller,
217
224
  {
218
- ref: attachList,
225
+ ref: listRefs,
219
226
  role: "listbox",
220
227
  id: listboxId,
221
228
  "aria-label": title,
@@ -258,6 +265,7 @@ function CommandMenu({
258
265
  query: queryProp,
259
266
  onQueryChange,
260
267
  filter,
268
+ highlightMatches,
261
269
  listClassName,
262
270
  placeholder = "Type a command or search\u2026",
263
271
  emptyMessage = "No results found.",
@@ -302,6 +310,7 @@ function CommandMenu({
302
310
  setQuery,
303
311
  items,
304
312
  filter,
313
+ highlightMatches,
305
314
  listClassName,
306
315
  placeholder,
307
316
  emptyMessage,
@@ -325,6 +334,7 @@ function CommandMenuPanel({
325
334
  query: queryProp,
326
335
  onQueryChange,
327
336
  filter,
337
+ highlightMatches,
328
338
  listClassName,
329
339
  placeholder = "Type a command or search\u2026",
330
340
  emptyMessage = "No results found.",
@@ -352,6 +362,7 @@ function CommandMenuPanel({
352
362
  setQuery,
353
363
  items,
354
364
  filter,
365
+ highlightMatches,
355
366
  listClassName,
356
367
  placeholder,
357
368
  emptyMessage,
@@ -443,10 +454,10 @@ function CommandMenuItem({
443
454
  children: [
444
455
  leading != null ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex shrink-0 items-center", children: leading }) : icon && /* @__PURE__ */ jsx(Icon, { name: icon, className: "text-base text-muted-foreground" }),
445
456
  stacked ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
446
- /* @__PURE__ */ jsx("span", { className: "truncate", children }),
457
+ /* @__PURE__ */ jsx("span", { "data-sable-match": "", className: "truncate", children }),
447
458
  detail && /* @__PURE__ */ jsx("span", { className: "truncate text-body-sm text-muted-foreground", children: detail })
448
459
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
449
- /* @__PURE__ */ jsx("span", { className: "truncate", children }),
460
+ /* @__PURE__ */ jsx("span", { "data-sable-match": "", className: "truncate", children }),
450
461
  detail && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-body-sm text-muted-foreground", children: detail })
451
462
  ] }),
452
463
  hint && /* @__PURE__ */ jsx("span", { className: "ms-auto flex min-w-0 max-w-1/2 items-center ps-4 text-body-sm text-muted-foreground", children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: hint }) })
@@ -153,6 +153,12 @@ var commandMenuMeta = {
153
153
  default: "case-insensitive substring match on label",
154
154
  description: "Replaces or disables the built-in match. A function ranks and filters items itself and its order is kept verbatim; null renders items exactly as given. Either also stops declarative CommandMenuItem children self-filtering. The query arrives raw \u2014 trimming and case are policy, and this is the prop that hands policy over."
155
155
  },
156
+ {
157
+ name: "highlightMatches",
158
+ type: "boolean",
159
+ default: "true",
160
+ description: "Paints the query wherever it appears in a row's label (not its detail or hint), on the `highlight` / `highlight-foreground` pair, through the CSS Custom Highlight API \u2014 no `<mark>` in the row, and the accessible name is unchanged. A literal, case-insensitive match, so under a fuzzy `filter` a row that matched as a subsequence keeps an unpainted label. `false` turns it off. Applies to CommandMenuPanel too."
161
+ },
156
162
  {
157
163
  name: "listClassName",
158
164
  type: "string",
@@ -197,7 +203,7 @@ var commandMenuMeta = {
197
203
  {
198
204
  name: "CommandMenuTrigger",
199
205
  type: '{ onOpen?: () => void; placeholder?: string; shortcut?: string | false; size?: "sm" | "md"; disabled?: boolean }',
200
- description: `The search field in a header that is really a button. Wears SearchInput's shell (border-input, card surface, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: \u2318K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close.`
206
+ description: `The search field in a header that is really a button. Wears SearchInput's shell (the field fill and outline, leading magnifier, muted placeholder) on the control ladder, with a trailing Kbd: \u2318K on macOS, Ctrl K elsewhere. onOpen runs on click and on the shortcut, which it listens for page-wide (useCommandMenuShortcut). shortcut is the key (default "k"); false drops both the hint and the listener. size is sm in a header beside small buttons, md (default) beside Input and SearchInput. It sits outside CommandMenu, so the Dialog returns focus to it on close.`
201
207
  },
202
208
  {
203
209
  name: "useCommandMenuShortcut",
@@ -221,6 +227,8 @@ var commandMenuMeta = {
221
227
  }
222
228
  ],
223
229
  tokensUsed: [
230
+ "highlight",
231
+ "highlight-foreground",
224
232
  "card",
225
233
  "card-foreground",
226
234
  "border",
@@ -125,8 +125,8 @@ var datePickerMeta = {
125
125
  "popover",
126
126
  "popover-foreground",
127
127
  "border",
128
- "input",
129
- "card",
128
+ "field-border",
129
+ "field",
130
130
  "ring"
131
131
  ],
132
132
  a11y: [
@@ -147,7 +147,7 @@ function Feedback({
147
147
  "span",
148
148
  {
149
149
  "aria-hidden": "true",
150
- className: "inline-flex size-10 items-center justify-center rounded-full bg-success/15 text-2xl text-success",
150
+ className: "inline-flex size-10 items-center justify-center rounded-full bg-success-mark/15 text-2xl text-success",
151
151
  children: /* @__PURE__ */ jsx("span", { "data-starting-style": drawn ? void 0 : "", className: "inline-flex motion-tick", children: /* @__PURE__ */ jsx(Icon, { name: "check" }) })
152
152
  }
153
153
  ),
@@ -98,7 +98,8 @@ var feedbackMeta = {
98
98
  "accent",
99
99
  "accent-foreground",
100
100
  "ring",
101
- "success"
101
+ "success",
102
+ "success-mark"
102
103
  ],
103
104
  motion: [
104
105
  {
@@ -40,19 +40,21 @@ function nudge(color, seed) {
40
40
  const dl = ((frac(seed * 13.7) * 2 - 1) * LIGHT_STEP).toFixed(3);
41
41
  return `oklch(from ${color} clamp(0, calc(l + ${dl}), 1) c calc(h + ${dh}))`;
42
42
  }
43
+ var pastel = (color) => `oklch(from ${color} clamp(0.84, calc(0.8 + l * 0.15), 0.95) min(calc(c * 0.5), 0.09) h)`;
43
44
  function param(style, name, fallbackValue) {
44
45
  const n = Number.parseFloat(style.getPropertyValue(name));
45
46
  return Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallbackValue;
46
47
  }
47
48
  var MAX_PIXELS = 15e5;
48
- function GradientArt({ variant = "mesh", tones, seed, className, style, children, ...props }) {
49
+ function GradientArt({ variant = "mesh", tones, seed, intensity = "vivid", className, style, children, ...props }) {
49
50
  const root = useRef(null);
50
51
  const canvas = useRef(null);
51
52
  const probes = useRef(null);
52
53
  const value = seedValue(seed);
53
- const palette = (tones?.length ? tones.map(lifted) : ART_SLOTS).map((color) => nudge(color, value));
54
- const groundColor = nudge(tones?.length ? ground(tones[0]) : ART_GROUND, value);
55
- const paletteKey = palette.join(" ");
54
+ const finish = (color) => intensity === "soft" ? pastel(nudge(color, value)) : nudge(color, value);
55
+ const palette = (tones?.length ? tones.map(lifted) : ART_SLOTS).map(finish);
56
+ const groundColor = finish(tones?.length ? ground(tones[0]) : ART_GROUND);
57
+ const paletteKey = [groundColor, ...palette].join(" ");
56
58
  const background = fallback(rotate(palette, value), groundColor, value);
57
59
  useEffect(() => {
58
60
  const element = root.current;
@@ -133,13 +135,14 @@ function GradientArt({ variant = "mesh", tones, seed, className, style, children
133
135
  offInvalidate();
134
136
  };
135
137
  }
136
- }, [variant, value, paletteKey]);
138
+ }, [variant, value, intensity, paletteKey]);
137
139
  return /* @__PURE__ */ jsxs(
138
140
  "div",
139
141
  {
140
142
  ref: root,
141
143
  "data-slot": "gradient-art",
142
144
  "data-variant": variant,
145
+ "data-intensity": intensity,
143
146
  className: cn("relative isolate overflow-hidden", className),
144
147
  style: { background, ...style },
145
148
  ...props,
@@ -5,7 +5,7 @@ var gradientArtMeta = {
5
5
  status: "beta",
6
6
  keywords: ["gradient", "mesh gradient", "grain", "shader", "artwork", "placeholder image", "cover", "banner", "avatar fallback", "generative"],
7
7
  usage: {
8
- description: "Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable.",
8
+ description: "Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable. `intensity=\"soft\"` paints the same art as a pastel wash \u2014 the iridescent aura AI products use as a card or modal header band, and as a stage for floating chips and logo tiles.",
9
9
  bestPractices: [
10
10
  {
11
11
  guidance: "do",
@@ -19,6 +19,14 @@ var gradientArtMeta = {
19
19
  guidance: "do",
20
20
  description: "Keep the default `mesh` for covers, tiles and avatars. Reach for `grain` where a printed, tactile surface suits \u2014 a banner, a hero card \u2014 and keep one variant per surface type so art reads as a system."
21
21
  },
22
+ {
23
+ guidance: "do",
24
+ description: 'Use `intensity="soft"` as a stage: a card or modal header band, the art on an integration or pricing card, a field behind floating prompt chips or rounded logo tiles. Make the tiles and chips floating surfaces (`bg-popover` with `border-border-floating` and a shadow, or a `floating` Button) so they lift off the wash; soft stays light in dark mode too, so the same composition holds in both.'
25
+ },
26
+ {
27
+ guidance: "dont",
28
+ description: "Do not put text straight on soft art with `text-overlay-foreground` ink. Soft is a light ground in every mode, so overlay ink washes out on it; put the content on a Card or a chip over the art, or use the default vivid intensity for a short overlay label."
29
+ },
22
30
  {
23
31
  guidance: "dont",
24
32
  description: "Do not put body text over it. The art spans the palette's lightest and most saturated tones, so no single ink is guaranteed to clear contrast across it; reserve overlays for a short label on a scrim or an avatar's initials."
@@ -42,10 +50,16 @@ var gradientArtMeta = {
42
50
  description: "Paint these `--tone-*` colours instead of the preset's art palette. Leave unset to follow the preset's art direction."
43
51
  },
44
52
  { name: "seed", type: "string | number", description: "Fixes the composition: the same seed paints the same art in every theme. Pass a stable id." },
53
+ {
54
+ name: "intensity",
55
+ type: '"vivid" | "soft"',
56
+ default: '"vivid"',
57
+ description: "vivid: the preset's art at full strength. soft: every colour and the ground lifted to a light pastel of itself (high lightness, half chroma) \u2014 a wash for header bands and for staging chips and logo tiles, light in both modes."
58
+ },
45
59
  { name: "children", type: "ReactNode", description: "Rendered above the art \u2014 initials, a glyph, a short label." },
46
60
  { name: "className", type: "string", description: "Size and shape: aspect, height, rounding." }
47
61
  ],
48
- variants: { variant: ["mesh", "grain"] },
62
+ variants: { variant: ["mesh", "grain"], intensity: ["vivid", "soft"] },
49
63
  tokensUsed: ["art-1", "art-2", "art-3", "art-4", "art-5", "art-6", "art-ground", "art-warp", "art-softness", "art-grain", "primary", "tone-info", "tone-3", "tone-2", "tone-1", "tone-warning"],
50
64
  motion: [
51
65
  {
@@ -73,6 +87,25 @@ var gradientArtMeta = {
73
87
  {
74
88
  label: "Avatar with initials",
75
89
  code: '<GradientArt seed="Mara Okafor" className="grid size-10 place-items-center rounded-full text-label-md text-overlay-foreground">MO</GradientArt>'
90
+ },
91
+ {
92
+ label: "Soft header band with logo tiles",
93
+ code: `<Card className="w-full max-w-sm">
94
+ <GradientArt intensity="soft" seed="Integrations" className="flex h-32 items-center justify-center gap-3 rounded-t-lg">
95
+ {["mail", "calendar", "chart"].map((name) => (
96
+ <span key={name} className="grid size-12 place-items-center rounded-lg border border-border-floating bg-popover text-popover-foreground shadow-md">
97
+ <Icon name={name} className="text-xl" />
98
+ </span>
99
+ ))}
100
+ </GradientArt>
101
+ <CardHeader>
102
+ <CardTitle>Connect your tools</CardTitle>
103
+ <CardDescription>Bring mail, calendar and analytics into one place.</CardDescription>
104
+ </CardHeader>
105
+ <CardFooter>
106
+ <Button>Connect</Button>
107
+ </CardFooter>
108
+ </Card>`
76
109
  }
77
110
  ]
78
111
  };
@@ -1,7 +1,7 @@
1
1
  // src/components/input/input.meta.ts
2
2
  var inputMeta = {
3
3
  name: "Input",
4
- summary: "Styled native text input. Soft-square (rounded-sm), card surface, 2px focus ring; aria-invalid raises the destructive error treatment.",
4
+ summary: "Styled native text input. Soft-square (rounded-sm), the theme's field surface (outlined or filled), 2px focus ring; aria-invalid raises the destructive error treatment.",
5
5
  keywords: [
6
6
  "input",
7
7
  "text input",
@@ -32,6 +32,14 @@ var inputMeta = {
32
32
  guidance: "do",
33
33
  description: "Keep a focusable field at 16px or larger on mobile. iOS Safari zooms the whole page when a focused input computes under 16px and never zooms back out. Input handles this itself \u2014 below the sm breakpoint it renders text-body-lg (17px) and returns to text-body-md at sm and up. Build a custom field the same way: a second paired tier under a max-sm: variant, never a hand-set font-size."
34
34
  },
35
+ {
36
+ guidance: "do",
37
+ description: "Choose outlined or filled fields once, in the theme: `--theme-field: var(--muted)` with `--theme-field-border: transparent` turns every Input, Textarea, Select, Combobox, SearchInput, InputGroup and ChatComposer into a borderless filled well; unset, they are outlined on the card. Every field reads the `field` / `field-border` pair, so they change together."
38
+ },
39
+ {
40
+ guidance: "dont",
41
+ description: "Restyle one Input as a filled chip with `bg-muted border-transparent`. It drifts from every other field on the page, and the theme can no longer move them together; set the field tokens instead."
42
+ },
35
43
  {
36
44
  guidance: "dont",
37
45
  description: "Override an Input's type with a page tier below 16px (text-body-md, text-body-sm, text-label-*) without a >=16px mobile arm \u2014 that reintroduces the iOS focus zoom."
@@ -104,8 +112,8 @@ var inputMeta = {
104
112
  }
105
113
  ],
106
114
  tokensUsed: [
107
- "input",
108
- "card",
115
+ "field-border",
116
+ "field",
109
117
  "foreground",
110
118
  "muted-foreground",
111
119
  "ring",
@@ -42,7 +42,7 @@ function InputGroup({
42
42
  "flex h-(--control-md) w-full items-center",
43
43
  // The field shell, moved from the input to the group: focus is the
44
44
  // ring on the whole field, and so are the error and disabled states.
45
- "rounded-sm border border-input bg-card motion-interactive focus-within:ring-2 focus-within:ring-ring",
45
+ "rounded-sm border border-field-border bg-field motion-interactive focus-within:ring-2 focus-within:ring-ring",
46
46
  "has-[>input[aria-invalid=true]]:border-destructive has-[>input[aria-invalid=true]]:ring-destructive/30",
47
47
  "has-[>input:disabled]:cursor-not-allowed has-[>input:disabled]:opacity-50",
48
48
  // The Input keeps its type, padding and behaviour; the box is the group's.
@@ -75,7 +75,7 @@ var inputGroupMeta = {
75
75
  description: "Layout classes for the group (width, margins)."
76
76
  }
77
77
  ],
78
- tokensUsed: ["input", "card", "ring", "destructive", "muted-foreground", "foreground"],
78
+ tokensUsed: ["field-border", "field", "ring", "destructive", "muted-foreground", "foreground"],
79
79
  motion: [
80
80
  {
81
81
  slot: "arrival",
@@ -180,7 +180,12 @@ var lineChartMeta = {
180
180
  "popover",
181
181
  "popover-foreground",
182
182
  "foreground",
183
- "background"
183
+ "background",
184
+ "tone-warning-mark",
185
+ "tone-info-mark",
186
+ "tone-1-mark",
187
+ "tone-2-mark",
188
+ "tone-3-mark"
184
189
  ],
185
190
  motion: [
186
191
  {
@@ -68,7 +68,7 @@ var MENU_ITEM_DESTRUCTIVE = (
68
68
  // enough for a white label, and ink mixed from them measured 3.8:1 on
69
69
  // Meridian's popover. From the text token every theme clears 4.5:1 (worst
70
70
  // 5.4:1, Prism dark, on the 10% red wash).
71
- "text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))] data-[highlighted]:bg-destructive/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
71
+ "text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))] data-[highlighted]:bg-destructive-mark/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
72
72
  );
73
73
  function MenuItem({ className, variant = "default", icon, description, children, ...props }) {
74
74
  const tier = useTypeTier();