@sableui/react 0.15.0 → 0.16.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.
- package/CHANGELOG.md +73 -0
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- package/dist/blocks/wallet-block.js +2 -2
- package/dist/components/app-shell/app-shell-frame.js +16 -0
- package/dist/components/app-shell/app-shell-main.js +12 -0
- package/dist/components/app-shell/app-shell.js +28 -4
- package/dist/components/app-shell/app-shell.meta.js +88 -4
- package/dist/components/area-chart/area-chart.meta.js +6 -1
- package/dist/components/attachment/attachment.meta.js +2 -1
- package/dist/components/attachment/attachment.variants.js +1 -1
- package/dist/components/badge/badge.meta.js +8 -1
- package/dist/components/badge/badge.variants.js +7 -7
- package/dist/components/banner/banner.meta.js +6 -1
- package/dist/components/banner/banner.variants.js +5 -5
- package/dist/components/bar-chart/bar-chart.meta.js +6 -1
- package/dist/components/caret-menu/caret-menu.js +5 -6
- package/dist/components/chat-composer/chat-composer.meta.js +2 -2
- package/dist/components/chat-composer/chat-composer.variants.js +1 -1
- package/dist/components/color-field/color-field.js +6 -8
- package/dist/components/combobox/combobox.js +27 -5
- package/dist/components/combobox/combobox.meta.js +10 -2
- package/dist/components/command-menu/command-menu.js +14 -3
- package/dist/components/command-menu/command-menu.meta.js +9 -1
- package/dist/components/date-picker/date-picker.meta.js +2 -2
- package/dist/components/feedback/feedback.js +1 -1
- package/dist/components/feedback/feedback.meta.js +2 -1
- package/dist/components/gradient-art/gradient-art.js +8 -5
- package/dist/components/gradient-art/gradient-art.meta.js +35 -2
- package/dist/components/input/input.meta.js +11 -3
- package/dist/components/input-group/input-group.js +1 -1
- package/dist/components/input-group/input-group.meta.js +1 -1
- package/dist/components/line-chart/line-chart.meta.js +6 -1
- package/dist/components/menu/menu.js +1 -1
- package/dist/components/meter/meter.js +53 -7
- package/dist/components/meter/meter.meta.js +51 -4
- package/dist/components/meter/meter.variants.js +43 -7
- package/dist/components/note/note.meta.js +5 -1
- package/dist/components/note/note.variants.js +4 -4
- package/dist/components/number-field/number-field.meta.js +2 -2
- package/dist/components/otp-field/otp-field.meta.js +2 -2
- package/dist/components/panel-group/panel-group.js +5 -8
- package/dist/components/pie-chart/pie-chart.meta.js +6 -1
- package/dist/components/radar-chart/radar-chart.meta.js +6 -1
- package/dist/components/radial-chart/radial-chart.meta.js +6 -1
- package/dist/components/search-input/search-input.meta.js +11 -3
- package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
- package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +5 -4
- package/dist/components/sidebar/sidebar.js +16 -1
- package/dist/components/sidebar/sidebar.meta.js +7 -2
- package/dist/components/sparkline/sparkline.meta.js +8 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -1
- package/dist/components/status-dot/status-dot.variants.js +7 -7
- package/dist/components/stepper/stepper.meta.js +2 -1
- package/dist/components/stepper/stepper.variants.js +1 -1
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/textarea/textarea.js +5 -0
- package/dist/components/textarea/textarea.meta.js +3 -3
- package/dist/file-hashes.json +91 -88
- package/dist/foundations/accessibility.js +5 -0
- package/dist/index.d.ts +132 -24
- package/dist/index.js +9 -1
- package/dist/lib/chart/types.js +8 -8
- package/dist/lib/chart/use-arrival.js +5 -4
- package/dist/lib/field-shell.js +1 -1
- package/dist/lib/highlight-glide.js +6 -6
- package/dist/lib/scrub-gesture.js +6 -6
- package/dist/lib/use-caret-trigger.js +11 -18
- package/dist/lib/use-match-highlight.js +69 -0
- package/dist/lib/use-smooth-wheel.js +10 -8
- package/dist/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +100 -0
- package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
- package/dist/themes/_contract.js +14 -0
- package/dist/themes/atelier/atelier.css +18 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/contrast.js +48 -1
- package/dist/themes/ember/ember.css +18 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +18 -0
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +18 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -0
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +18 -0
- package/llms/blocks/wallet-block.md +5 -4
- package/llms/components/app-shell.md +72 -3
- package/llms/components/area-chart.md +1 -1
- package/llms/components/attachment.md +1 -1
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +1 -1
- package/llms/components/bar-chart.md +1 -1
- package/llms/components/chat-composer.md +1 -1
- package/llms/components/combobox.md +2 -1
- package/llms/components/command-menu.md +3 -2
- package/llms/components/date-picker.md +1 -1
- package/llms/components/feedback.md +1 -1
- package/llms/components/gradient-art.md +28 -1
- package/llms/components/input-group.md +1 -1
- package/llms/components/input.md +4 -2
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +33 -4
- package/llms/components/note.md +1 -1
- package/llms/components/number-field.md +1 -1
- package/llms/components/otp-field.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/search-input.md +20 -2
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/sidebar.md +3 -2
- package/llms/components/sparkline.md +1 -1
- package/llms/components/status-dot.md +1 -1
- package/llms/components/stepper.md +1 -1
- package/llms/components/table.md +2 -2
- package/llms/components/textarea.md +2 -2
- package/llms/foundations/accessibility.md +1 -0
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +5 -2
- package/llms-full.txt +222 -49
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/registry.json +396 -57
- package/scripts/lint-rules.mjs +6 -3
|
@@ -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-
|
|
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
|
-
"
|
|
192
|
-
"
|
|
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:
|
|
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 (
|
|
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",
|
|
@@ -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
|
),
|
|
@@ -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
|
|
54
|
-
const
|
|
55
|
-
const
|
|
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),
|
|
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
|
-
"
|
|
108
|
-
"
|
|
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-
|
|
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: ["
|
|
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();
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
// src/components/meter/meter.tsx
|
|
2
2
|
import { Meter as Base } from "@base-ui/react/meter";
|
|
3
|
-
import { createContext, useContext } from "react";
|
|
3
|
+
import { createContext, useContext, useState } from "react";
|
|
4
4
|
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import {
|
|
6
6
|
meterIndicatorVariants,
|
|
7
7
|
meterRingValueVariants,
|
|
8
8
|
meterRingVariants,
|
|
9
|
+
meterSegmentedTrackVariants,
|
|
10
|
+
meterTickVariants,
|
|
9
11
|
meterTrackVariants
|
|
10
12
|
} from "./meter.variants.js";
|
|
11
13
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
14
|
var ToneContext = createContext("primary");
|
|
13
15
|
var SizeContext = createContext(void 0);
|
|
16
|
+
var SegmentsContext = createContext(null);
|
|
17
|
+
function filledTicks({ count, value, min, max }) {
|
|
18
|
+
if (value == null || value <= min) return 0;
|
|
19
|
+
const ratio = max === min ? 1 : Math.min(1, (value - min) / (max - min));
|
|
20
|
+
return Math.min(count, Math.max(1, Math.round(ratio * count)));
|
|
21
|
+
}
|
|
14
22
|
var RING_TONE_STROKE = {
|
|
15
23
|
primary: "var(--primary)",
|
|
16
|
-
"3": "var(--tone-3)",
|
|
17
|
-
success: "var(--success)",
|
|
18
|
-
warning: "var(--tone-warning)",
|
|
19
|
-
destructive: "var(--destructive)"
|
|
24
|
+
"3": "var(--tone-3-mark)",
|
|
25
|
+
success: "var(--success-mark)",
|
|
26
|
+
warning: "var(--tone-warning-mark)",
|
|
27
|
+
destructive: "var(--destructive-mark)"
|
|
20
28
|
};
|
|
21
29
|
function MeterRing({
|
|
22
30
|
value,
|
|
@@ -53,15 +61,30 @@ function MeterRing({
|
|
|
53
61
|
showValue ? /* @__PURE__ */ jsx(MeterValue, { className: meterRingValueVariants({ size }) }) : null
|
|
54
62
|
] });
|
|
55
63
|
}
|
|
56
|
-
function Meter({ className, children, tone = "primary", shape = "bar", size, strokeWidth = 6, showValue, ...props }) {
|
|
64
|
+
function Meter({ className, children, tone = "primary", shape = "bar", size, strokeWidth = 6, showValue, segments, ...props }) {
|
|
57
65
|
if (shape === "ring") {
|
|
58
66
|
return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(Base.Root, { className: cn(meterRingVariants({ size }), className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterRing, { value: props.value ?? null, min: props.min, max: props.max, strokeWidth, showValue, tone, size }) }) });
|
|
59
67
|
}
|
|
60
|
-
|
|
68
|
+
const count = segments != null && Number.isFinite(segments) && segments >= 1 ? Math.floor(segments) : 0;
|
|
69
|
+
const segmented = count ? { count, value: props.value ?? null, min: props.min ?? 0, max: props.max ?? 100 } : null;
|
|
70
|
+
return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(SizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(SegmentsContext.Provider, { value: segmented, children: /* @__PURE__ */ jsx(Base.Root, { className: cn("w-full", className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterTrack, {}) }) }) }) });
|
|
61
71
|
}
|
|
62
72
|
Meter.displayName = "Meter";
|
|
63
73
|
function MeterTrack({ className, children, size, ...props }) {
|
|
64
74
|
const inherited = useContext(SizeContext);
|
|
75
|
+
const tone = useContext(ToneContext);
|
|
76
|
+
const segments = useContext(SegmentsContext);
|
|
77
|
+
if (segments) {
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
Base.Track,
|
|
80
|
+
{
|
|
81
|
+
"data-segmented": "",
|
|
82
|
+
className: cn(meterSegmentedTrackVariants({ size: size ?? inherited }), className),
|
|
83
|
+
...props,
|
|
84
|
+
children: /* @__PURE__ */ jsx(MeterTicks, { count: segments.count, filled: filledTicks(segments), tone })
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
65
88
|
return /* @__PURE__ */ jsx(
|
|
66
89
|
Base.Track,
|
|
67
90
|
{
|
|
@@ -72,6 +95,29 @@ function MeterTrack({ className, children, size, ...props }) {
|
|
|
72
95
|
);
|
|
73
96
|
}
|
|
74
97
|
MeterTrack.displayName = "MeterTrack";
|
|
98
|
+
function MeterTicks({ count, filled, tone }) {
|
|
99
|
+
const [shown, setShown] = useState(filled);
|
|
100
|
+
const [from, setFrom] = useState(filled);
|
|
101
|
+
if (filled !== shown) {
|
|
102
|
+
setFrom(shown);
|
|
103
|
+
setShown(filled);
|
|
104
|
+
}
|
|
105
|
+
return Array.from({ length: count }, (_, i) => {
|
|
106
|
+
const isFilled = i < filled;
|
|
107
|
+
const step = filled > from && i >= from && i < filled ? i - from : filled < from && i >= filled && i < from ? from - 1 - i : 0;
|
|
108
|
+
return /* @__PURE__ */ jsx(
|
|
109
|
+
"span",
|
|
110
|
+
{
|
|
111
|
+
"aria-hidden": "true",
|
|
112
|
+
"data-slot": "meter-tick",
|
|
113
|
+
"data-filled": isFilled ? "" : void 0,
|
|
114
|
+
className: meterTickVariants({ tone, filled: isFilled }),
|
|
115
|
+
style: step ? { transitionDelay: `calc(var(--motion-duration-slow) * ${Number((step / count).toFixed(4))} * var(--motion-expressive, 1))` } : void 0
|
|
116
|
+
},
|
|
117
|
+
i
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
75
121
|
function MeterIndicator({ className, tone, ...props }) {
|
|
76
122
|
const inherited = useContext(ToneContext);
|
|
77
123
|
return /* @__PURE__ */ jsx(
|