@weichenlin/swell-ui 0.2.0 → 0.4.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/README.md +172 -36
- package/dist/index.d.ts +422 -43
- package/dist/index.js +2941 -545
- package/dist/theme.css +185 -77
- package/package.json +1 -2
- package/dist/themes/warm.css +0 -63
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Check, ChevronDown, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
|
|
1
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
|
|
2
|
+
import { Children, Fragment, cloneElement, createContext, isValidElement, use, useCallback, useContext, useEffect, useEffectEvent, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
3
|
import { clsx } from "clsx";
|
|
3
4
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
4
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { cva } from "class-variance-authority";
|
|
6
|
-
import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { createPortal } from "react-dom";
|
|
8
8
|
//#region src/lib/cn.ts
|
|
9
9
|
const tone = (name) => [
|
|
@@ -45,6 +45,8 @@ const twMerge = extendTailwindMerge({ extend: { theme: {
|
|
|
45
45
|
"input-halo-invalid-focus",
|
|
46
46
|
"control-checked",
|
|
47
47
|
"control-checked-fg",
|
|
48
|
+
"switch-handle",
|
|
49
|
+
"switch-handle-checked",
|
|
48
50
|
"popover-bg",
|
|
49
51
|
"popover-border",
|
|
50
52
|
"popover-highlight",
|
|
@@ -76,64 +78,592 @@ function cn(...inputs) {
|
|
|
76
78
|
return twMerge(clsx(inputs));
|
|
77
79
|
}
|
|
78
80
|
//#endregion
|
|
79
|
-
//#region src/components/
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
|
|
85
|
-
},
|
|
86
|
-
info: {
|
|
87
|
-
subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
|
|
88
|
-
outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
|
|
89
|
-
solid: ["border-info bg-info text-info-fg", "text-info-fg"]
|
|
90
|
-
},
|
|
91
|
-
success: {
|
|
92
|
-
subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
|
|
93
|
-
outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
|
|
94
|
-
solid: ["border-success bg-success text-success-fg", "text-success-fg"]
|
|
95
|
-
},
|
|
96
|
-
warning: {
|
|
97
|
-
subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
|
|
98
|
-
outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
|
|
99
|
-
solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
|
|
100
|
-
},
|
|
101
|
-
danger: {
|
|
102
|
-
subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
|
|
103
|
-
outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
|
|
104
|
-
solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
|
|
105
|
-
}
|
|
81
|
+
//#region src/components/input/field.tsx
|
|
82
|
+
const controlHeight = {
|
|
83
|
+
sm: "h-[34px]",
|
|
84
|
+
md: "h-[38px]",
|
|
85
|
+
lg: "h-[42px]"
|
|
106
86
|
};
|
|
107
|
-
const
|
|
108
|
-
sm:
|
|
109
|
-
|
|
110
|
-
|
|
87
|
+
const textSize = {
|
|
88
|
+
sm: "text-sm",
|
|
89
|
+
md: "text-sm",
|
|
90
|
+
lg: "text-base"
|
|
91
|
+
};
|
|
92
|
+
const iconSize$1 = {
|
|
93
|
+
sm: "[&_svg]:size-4",
|
|
94
|
+
md: "[&_svg]:size-4",
|
|
95
|
+
lg: "[&_svg]:size-[18px]"
|
|
96
|
+
};
|
|
97
|
+
const inputPadding = {
|
|
98
|
+
sm: "px-3",
|
|
99
|
+
md: "px-3",
|
|
100
|
+
lg: "px-4"
|
|
101
|
+
};
|
|
102
|
+
const startSlotPadding = {
|
|
103
|
+
sm: "ps-2",
|
|
104
|
+
md: "ps-2.5",
|
|
105
|
+
lg: "ps-3"
|
|
106
|
+
};
|
|
107
|
+
const endSlotPadding = {
|
|
108
|
+
sm: "pe-2",
|
|
109
|
+
md: "pe-2.5",
|
|
110
|
+
lg: "pe-3"
|
|
111
|
+
};
|
|
112
|
+
const actionsPadding = {
|
|
113
|
+
sm: "pe-[3px]",
|
|
114
|
+
md: "pe-1",
|
|
115
|
+
lg: "pe-1"
|
|
116
|
+
};
|
|
117
|
+
/** Compact Button square that fits inside the field's inner height with an even inset. */
|
|
118
|
+
const compactButton = {
|
|
119
|
+
sm: "size-[26px]",
|
|
120
|
+
md: "size-7",
|
|
121
|
+
lg: "size-8"
|
|
122
|
+
};
|
|
123
|
+
const labelStart = {
|
|
124
|
+
sm: "start-2",
|
|
125
|
+
md: "start-2",
|
|
126
|
+
lg: "start-3"
|
|
127
|
+
};
|
|
128
|
+
const floatShift = {
|
|
129
|
+
sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,17px))]",
|
|
130
|
+
md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,19px))]",
|
|
131
|
+
lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,21px))]"
|
|
132
|
+
};
|
|
133
|
+
const isText = (node) => typeof node === "string" || typeof node === "number";
|
|
134
|
+
const isPresent = (node) => node != null && node !== false && node !== "";
|
|
135
|
+
function Adornment({ node, side, size }) {
|
|
136
|
+
const text = isText(node);
|
|
137
|
+
return /* @__PURE__ */ jsx("span", {
|
|
138
|
+
"aria-hidden": text ? void 0 : true,
|
|
139
|
+
className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize$1[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
|
|
140
|
+
children: node
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const notchPadding = {
|
|
144
|
+
border: {
|
|
145
|
+
sm: "ps-2 pe-2",
|
|
146
|
+
md: "ps-2 pe-2",
|
|
147
|
+
lg: "ps-3 pe-1"
|
|
111
148
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
149
|
+
halo: {
|
|
150
|
+
sm: "ps-[9px] pe-[9px]",
|
|
151
|
+
md: "ps-[9px] pe-[9px]",
|
|
152
|
+
lg: "ps-[13px] pe-[5px]"
|
|
115
153
|
}
|
|
116
154
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
155
|
+
/**
|
|
156
|
+
* MUI-style notched outline: a fieldset draws the ring and an invisible legend holding the label
|
|
157
|
+
* text at its floated size cuts the gap. The legend collapses while the label rests in the field.
|
|
158
|
+
* A legend as tall as the border keeps the border at the fieldset's top edge.
|
|
159
|
+
*/
|
|
160
|
+
function Notch({ ring, size, disabled, children }) {
|
|
161
|
+
const halo = ring === "halo";
|
|
162
|
+
return /* @__PURE__ */ jsx("fieldset", {
|
|
163
|
+
"aria-hidden": true,
|
|
164
|
+
className: cn("pointer-events-none absolute m-0 min-w-0 p-0", "transition-[border-color] duration-200 ease-in-out motion-reduce:transition-none", notchPadding[ring][size], halo ? ["-inset-1 rounded-[calc(var(--radius-lg)+3px)] border-[3px] border-(--field-halo)", "forced-colors:invisible forced-colors:group-has-[:is(input,select,textarea):is(:focus,:open)]/control:visible forced-colors:border-[Highlight]"] : ["-inset-px rounded-[inherit] border border-(--field-border)", disabled && "forced-colors:border-[GrayText]"]),
|
|
165
|
+
children: /* @__PURE__ */ jsx("legend", {
|
|
166
|
+
className: cn("invisible float-none block w-auto max-w-[0.01px] overflow-hidden p-0 text-xs whitespace-nowrap", halo ? "h-0.75" : "h-px", "transition-[max-width] duration-50 ease-out motion-reduce:transition-none", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-full", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:delay-50", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:duration-100"),
|
|
167
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
168
|
+
className: "px-1",
|
|
169
|
+
children
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Keeps an internal ref (focus forwarding, clear, reset) alongside the caller's ref. */
|
|
175
|
+
function useMergedRef(ownRef, ref) {
|
|
176
|
+
return useCallback((node) => {
|
|
177
|
+
ownRef.current = node;
|
|
178
|
+
if (typeof ref === "function") return ref(node);
|
|
179
|
+
if (ref) ref.current = node;
|
|
180
|
+
}, [ownRef, ref]);
|
|
181
|
+
}
|
|
182
|
+
const focusControl = (control) => control.focus();
|
|
183
|
+
/**
|
|
184
|
+
* The frame every field shares (input family, Select): label above or floating with a notched
|
|
185
|
+
* outline, the bordered control with its border/halo/hover states, description and live region.
|
|
186
|
+
* State selectors match `input`, `textarea` or `select`, never the inner buttons, so a focused clear or copy
|
|
187
|
+
* button shows only its own ring. `:open` too: an open base-select picker moves focus onto its
|
|
188
|
+
* options, and the select then matches neither `:focus` nor `:focus-within`. Inside `:is()` an
|
|
189
|
+
* unsupported `:open` is dropped alone instead of invalidating the rule.
|
|
190
|
+
*/
|
|
191
|
+
function FieldShell({ label, floating = false, helperText, error, size, id: idProp, required, disabled, readOnly, "aria-describedby": describedByProp, hasStart = false, multiline = false, footer, className, status = "", controlRef, activate = focusControl, children }) {
|
|
192
|
+
const autoId = useId();
|
|
193
|
+
const id = idProp ?? `${autoId}-control`;
|
|
194
|
+
const descriptionId = `${autoId}-description`;
|
|
195
|
+
const invalid = Boolean(error);
|
|
196
|
+
const description = invalid ? error === true ? null : error : helperText;
|
|
197
|
+
const hasDescription = isPresent(description);
|
|
198
|
+
const describedBy = [describedByProp, hasDescription ? descriptionId : null].filter(Boolean).join(" ") || void 0;
|
|
199
|
+
const hasLabel = Boolean(label);
|
|
200
|
+
const floatingLabel = floating && hasLabel;
|
|
201
|
+
const asterisk = required ? /* @__PURE__ */ jsx("span", {
|
|
202
|
+
"aria-hidden": true,
|
|
203
|
+
className: "text-danger",
|
|
204
|
+
children: " *"
|
|
205
|
+
}) : null;
|
|
206
|
+
const forwardClick = (e) => {
|
|
207
|
+
const control = controlRef.current;
|
|
208
|
+
const target = e.target;
|
|
209
|
+
if (!control || control.contains(target) || target.closest("button")) return;
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
activate(control);
|
|
212
|
+
};
|
|
213
|
+
const renderFloatingLabel = (restingWidth) => floatingLabel ? /* @__PURE__ */ jsxs("label", {
|
|
214
|
+
htmlFor: id,
|
|
215
|
+
className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2 truncate px-1 leading-normal", "text-fg-muted pointer-coarse:text-base", "transition-[translate,font-size,color] duration-150 ease-out motion-reduce:transition-none", multiline && {
|
|
216
|
+
sm: "top-[18px]",
|
|
217
|
+
md: "top-5",
|
|
218
|
+
lg: "top-6"
|
|
219
|
+
}[size], labelStart[size], textSize[size], restingWidth, floatShift[size], "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-[calc(100%-1rem)]", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:text-xs", invalid ? "text-danger" : ["[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:group-has-[:is(input,select,textarea):not(:is(:focus,:open))]/control:text-fg-strong", "group-has-[:is(input,select,textarea):is(:focus,:open)]/control:text-input-focus"]),
|
|
220
|
+
children: [label, asterisk]
|
|
221
|
+
}) : null;
|
|
222
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
223
|
+
"data-slot": "input-field",
|
|
224
|
+
className: cn("flex w-full flex-col gap-1.5", disabled && "opacity-50", className),
|
|
225
|
+
children: [
|
|
226
|
+
hasLabel && !floating && /* @__PURE__ */ jsxs("label", {
|
|
227
|
+
htmlFor: id,
|
|
228
|
+
className: "self-start text-sm font-medium text-fg",
|
|
229
|
+
children: [label, asterisk]
|
|
230
|
+
}),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", {
|
|
232
|
+
"data-slot": "input-control",
|
|
233
|
+
"data-start": hasStart ? "" : void 0,
|
|
234
|
+
onMouseDown: forwardClick,
|
|
235
|
+
className: cn("group/control relative isolate flex items-center rounded-lg border", floatingLabel ? "bg-transparent" : "[--field-bg:var(--color-input-bg)] bg-(--field-bg)", "transition-[border-color,box-shadow,background-color] duration-200 ease-in-out motion-reduce:transition-none", multiline ? {
|
|
236
|
+
sm: "[--field-label-shift:19px]",
|
|
237
|
+
md: "[--field-label-shift:21px]",
|
|
238
|
+
lg: "[--field-label-shift:25px]"
|
|
239
|
+
}[size] : controlHeight[size], invalid ? ["[--field-border:var(--color-danger)] [--field-halo:var(--color-input-halo-invalid)]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo-invalid-focus)]"] : ["[--field-border:var(--color-input-border)] [--field-halo:transparent]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-focus)] has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo)]"], "[--field-lift:0_0_#0000]", !invalid && !disabled && !readOnly && [
|
|
240
|
+
"hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-border-hover)]",
|
|
241
|
+
!floatingLabel && "hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-bg:var(--color-input-bg-hover)]",
|
|
242
|
+
"hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-lift:var(--shadow-input-lift)]"
|
|
243
|
+
], floatingLabel ? ["border-transparent forced-colors:border-[Canvas]", !readOnly && "[box-shadow:var(--field-lift)]"] : [
|
|
244
|
+
"border-(--field-border)",
|
|
245
|
+
readOnly ? "[box-shadow:0_0_0_3px_var(--field-halo)]" : "[box-shadow:var(--shadow-input),var(--field-lift),0_0_0_3px_var(--field-halo)]",
|
|
246
|
+
"has-[:is(input,select,textarea):is(:focus,:open)]:outline-hidden",
|
|
247
|
+
disabled && "forced-colors:border-[GrayText]"
|
|
248
|
+
], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
|
|
249
|
+
children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Notch, {
|
|
250
|
+
size,
|
|
251
|
+
ring: "halo",
|
|
252
|
+
disabled,
|
|
253
|
+
children: [label, asterisk]
|
|
254
|
+
}), /* @__PURE__ */ jsxs(Notch, {
|
|
255
|
+
size,
|
|
256
|
+
ring: "border",
|
|
257
|
+
disabled,
|
|
258
|
+
children: [label, asterisk]
|
|
259
|
+
})] }), children({
|
|
260
|
+
id,
|
|
261
|
+
invalid,
|
|
262
|
+
describedBy,
|
|
263
|
+
floating: floatingLabel,
|
|
264
|
+
floatingLabel: renderFloatingLabel
|
|
265
|
+
})]
|
|
266
|
+
}),
|
|
267
|
+
(hasDescription || isPresent(footer)) && /* @__PURE__ */ jsxs("div", {
|
|
268
|
+
className: "flex items-start justify-between gap-3",
|
|
269
|
+
children: [hasDescription && /* @__PURE__ */ jsxs("p", {
|
|
270
|
+
id: descriptionId,
|
|
271
|
+
className: cn("m-0 flex min-w-0 flex-1 items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
|
|
272
|
+
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
273
|
+
"aria-hidden": true,
|
|
274
|
+
className: "mt-px size-3.5 shrink-0"
|
|
275
|
+
}), /* @__PURE__ */ jsx("span", { children: description })]
|
|
276
|
+
}), footer]
|
|
277
|
+
}),
|
|
278
|
+
/* @__PURE__ */ jsx("span", {
|
|
279
|
+
"aria-live": "polite",
|
|
280
|
+
"aria-atomic": true,
|
|
281
|
+
className: "sr-only",
|
|
282
|
+
children: status
|
|
283
|
+
})
|
|
284
|
+
]
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/** The input family's frame: adornments, actions and the input itself inside FieldShell. */
|
|
288
|
+
function FieldFrame({ label, floating = false, helperText, error, size = "md", start, end, mono = false, validating = false, validatingText = "Checking…", className, inputClassName, inputRef: inputRefProp, actions, status, ref, id, required, disabled, readOnly, placeholder, "aria-describedby": describedByProp, "aria-invalid": ariaInvalid, ...props }) {
|
|
289
|
+
const ownRef = useRef(null);
|
|
290
|
+
const inputRef = inputRefProp ?? ownRef;
|
|
291
|
+
const setRef = useMergedRef(inputRef, ref);
|
|
292
|
+
const hasStart = isPresent(start);
|
|
293
|
+
const hasEnd = isPresent(end);
|
|
294
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
295
|
+
label,
|
|
296
|
+
floating,
|
|
297
|
+
helperText,
|
|
298
|
+
error,
|
|
299
|
+
size,
|
|
300
|
+
id,
|
|
301
|
+
required,
|
|
302
|
+
disabled,
|
|
303
|
+
readOnly,
|
|
304
|
+
"aria-describedby": describedByProp,
|
|
305
|
+
hasStart,
|
|
306
|
+
className,
|
|
307
|
+
status: validating ? validatingText : status ?? "",
|
|
308
|
+
controlRef: inputRef,
|
|
309
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
310
|
+
hasStart ? /* @__PURE__ */ jsx(Adornment, {
|
|
311
|
+
node: start,
|
|
312
|
+
side: "start",
|
|
313
|
+
size
|
|
314
|
+
}) : null,
|
|
315
|
+
/* @__PURE__ */ jsxs("span", {
|
|
316
|
+
className: "@container flex min-w-0 flex-1 self-stretch rounded-[inherit]",
|
|
317
|
+
children: [field.floatingLabel("max-w-[calc(100cqw-1rem)]"), /* @__PURE__ */ jsx("input", {
|
|
318
|
+
...props,
|
|
319
|
+
ref: setRef,
|
|
320
|
+
id: field.id,
|
|
321
|
+
required,
|
|
322
|
+
disabled,
|
|
323
|
+
readOnly,
|
|
324
|
+
placeholder: field.floating ? placeholder || " " : placeholder,
|
|
325
|
+
"aria-invalid": field.invalid ? true : ariaInvalid,
|
|
326
|
+
"aria-describedby": field.describedBy,
|
|
327
|
+
spellCheck: mono ? false : props.spellCheck,
|
|
328
|
+
autoCapitalize: mono ? "off" : props.autoCapitalize,
|
|
329
|
+
autoCorrect: mono ? "off" : props.autoCorrect,
|
|
330
|
+
className: cn("h-full min-w-0 flex-1 rounded-[inherit] bg-transparent text-fg outline-none", "placeholder:text-input-placeholder pointer-coarse:text-base", "disabled:cursor-not-allowed", "autofill:[transition:background-color_0s_600000s] autofill:[-webkit-text-fill-color:var(--color-fg)]", textSize[size], inputPadding[size], hasStart && (isText(start) ? "ps-1" : "ps-2"), !validating && hasEnd && isText(end) ? "pe-1" : (validating || hasEnd || actions) && "pe-2", mono && "font-mono", field.floating && ["placeholder:opacity-0", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:placeholder:opacity-100"], inputClassName)
|
|
331
|
+
})]
|
|
332
|
+
}),
|
|
333
|
+
validating ? /* @__PURE__ */ jsx("span", {
|
|
334
|
+
className: cn("flex shrink-0 items-center self-stretch text-fg-muted", iconSize$1[size], endSlotPadding[size]),
|
|
335
|
+
children: /* @__PURE__ */ jsx(LoaderCircle, {
|
|
336
|
+
"aria-hidden": true,
|
|
337
|
+
className: "animate-spin motion-reduce:[animation-duration:2.5s]"
|
|
338
|
+
})
|
|
339
|
+
}) : hasEnd ? /* @__PURE__ */ jsx(Adornment, {
|
|
340
|
+
node: end,
|
|
341
|
+
side: "end",
|
|
342
|
+
size
|
|
343
|
+
}) : null,
|
|
344
|
+
actions ? /* @__PURE__ */ jsx("span", {
|
|
345
|
+
className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
|
|
346
|
+
children: actions
|
|
347
|
+
}) : null
|
|
348
|
+
] })
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/components/accordion/use-accordion-motion.ts
|
|
353
|
+
/** Animate measured heights; never cap content with an arbitrary max-height. */
|
|
354
|
+
function useAccordionMotion(open, itemRef, restoreFocus) {
|
|
355
|
+
const panelRef = useRef(null);
|
|
356
|
+
const innerRef = useRef(null);
|
|
357
|
+
const previousOpen = useRef(open);
|
|
358
|
+
const [present, setPresent] = useState(open);
|
|
359
|
+
useLayoutEffect(() => {
|
|
360
|
+
const panel = panelRef.current;
|
|
361
|
+
const inner = innerRef.current;
|
|
362
|
+
if (!panel || !inner) return;
|
|
363
|
+
const changed = previousOpen.current !== open;
|
|
364
|
+
previousOpen.current = open;
|
|
365
|
+
let active = true;
|
|
366
|
+
let animation;
|
|
367
|
+
let observer;
|
|
368
|
+
let target = -1;
|
|
369
|
+
let settled = false;
|
|
370
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
371
|
+
if (!open && panel.contains(panel.ownerDocument.activeElement) && itemRef.current) restoreFocus(itemRef.current);
|
|
372
|
+
panel.inert = !open;
|
|
373
|
+
panel.toggleAttribute("inert", !open);
|
|
374
|
+
if (open) panel.removeAttribute("aria-hidden");
|
|
375
|
+
else panel.setAttribute("aria-hidden", "true");
|
|
376
|
+
const finish = () => {
|
|
377
|
+
if (!active) return;
|
|
378
|
+
settled = true;
|
|
379
|
+
observer?.disconnect();
|
|
380
|
+
animation?.cancel();
|
|
381
|
+
animation = void 0;
|
|
382
|
+
panel.style.removeProperty("height");
|
|
383
|
+
panel.style.removeProperty("overflow");
|
|
384
|
+
panel.hidden = !open;
|
|
385
|
+
setPresent(open);
|
|
386
|
+
};
|
|
387
|
+
const run = () => {
|
|
388
|
+
if (!active || settled) return;
|
|
389
|
+
const nextTarget = open ? inner.getBoundingClientRect().height : 0;
|
|
390
|
+
if (nextTarget === target && animation) return;
|
|
391
|
+
target = nextTarget;
|
|
392
|
+
const from = panel.getBoundingClientRect().height;
|
|
393
|
+
animation?.cancel();
|
|
394
|
+
panel.style.height = `${from}px`;
|
|
395
|
+
const current = panel.animate([{ height: `${from}px` }, { height: `${target}px` }], {
|
|
396
|
+
duration: open ? 200 : 160,
|
|
397
|
+
easing: "cubic-bezier(.16,1,.3,1)",
|
|
398
|
+
fill: "both"
|
|
399
|
+
});
|
|
400
|
+
animation = current;
|
|
401
|
+
current.finished.then(() => {
|
|
402
|
+
if (active && animation === current) finish();
|
|
403
|
+
}, () => {});
|
|
404
|
+
};
|
|
405
|
+
if (!changed || reduced?.matches || typeof panel.animate !== "function") finish();
|
|
406
|
+
else {
|
|
407
|
+
if (open && !panel.style.height) panel.style.height = "0px";
|
|
408
|
+
panel.hidden = false;
|
|
409
|
+
panel.style.overflow = "hidden";
|
|
410
|
+
setPresent(true);
|
|
411
|
+
run();
|
|
412
|
+
if (open && typeof ResizeObserver !== "undefined") {
|
|
413
|
+
observer = new ResizeObserver(run);
|
|
414
|
+
observer.observe(inner);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const onReducedMotion = () => {
|
|
418
|
+
if (reduced?.matches) finish();
|
|
419
|
+
};
|
|
420
|
+
reduced?.addEventListener("change", onReducedMotion);
|
|
421
|
+
return () => {
|
|
422
|
+
active = false;
|
|
423
|
+
observer?.disconnect();
|
|
424
|
+
reduced?.removeEventListener("change", onReducedMotion);
|
|
425
|
+
if (animation) {
|
|
426
|
+
panel.style.height = `${panel.getBoundingClientRect().height}px`;
|
|
427
|
+
animation.cancel();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}, [
|
|
431
|
+
open,
|
|
432
|
+
itemRef,
|
|
433
|
+
restoreFocus
|
|
434
|
+
]);
|
|
435
|
+
return {
|
|
436
|
+
panelRef,
|
|
437
|
+
innerRef,
|
|
438
|
+
present
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/components/accordion/accordion.tsx
|
|
443
|
+
const AccordionContext = createContext(null);
|
|
444
|
+
const ItemContext = createContext(null);
|
|
445
|
+
function useAccordionContext(part) {
|
|
446
|
+
const context = useContext(AccordionContext);
|
|
447
|
+
if (!context) throw new Error(`${part} must be rendered inside <Accordion>`);
|
|
448
|
+
return context;
|
|
449
|
+
}
|
|
450
|
+
function useItemContext(part) {
|
|
451
|
+
const context = useContext(ItemContext);
|
|
452
|
+
if (!context) throw new Error(`${part} must be rendered inside <AccordionItem>`);
|
|
453
|
+
return context;
|
|
454
|
+
}
|
|
455
|
+
const valuesOf = (value) => typeof value === "string" ? [value] : [...new Set(value ?? [])];
|
|
456
|
+
const ownTriggers = (root) => [...root.querySelectorAll("[data-slot=\"accordion-trigger\"]")].filter((node) => node.closest("[data-slot=\"accordion\"]") === root);
|
|
457
|
+
/** Vertical disclosure group. Changing type requires a new React key. */
|
|
458
|
+
function Accordion({ type = "single", value, defaultValue, onValueChange, collapsible = true, variant = "line", headingLevel = 3, disabled = false, className, children, ref, onKeyDown, ...props }) {
|
|
459
|
+
const [initialType] = useState(type);
|
|
460
|
+
if (initialType !== type) throw new Error("Changing Accordion type requires a new key");
|
|
461
|
+
const controlled = value !== void 0;
|
|
462
|
+
const [internal, setInternal] = useState(() => valuesOf(defaultValue));
|
|
463
|
+
const selected = controlled ? valuesOf(value) : internal;
|
|
464
|
+
const rootRef = useRef(null);
|
|
465
|
+
const mergedRef = useMergedRef(rootRef, ref);
|
|
466
|
+
const items = useRef(/* @__PURE__ */ new Map());
|
|
467
|
+
const internalRef = useRef(internal);
|
|
468
|
+
useLayoutEffect(() => {
|
|
469
|
+
internalRef.current = internal;
|
|
470
|
+
}, [internal]);
|
|
471
|
+
const restoreFocus = useCallback((item, removing = false) => {
|
|
472
|
+
const root = rootRef.current;
|
|
473
|
+
if (!root) return;
|
|
474
|
+
const triggers = ownTriggers(root);
|
|
475
|
+
const own = triggers.find((node) => node.closest("[data-slot=\"accordion-item\"]") === item);
|
|
476
|
+
const index = own ? triggers.indexOf(own) : -1;
|
|
477
|
+
const next = (!removing && own && !own.disabled ? own : void 0) ?? triggers.slice(index + 1).find((node) => !node.disabled) ?? triggers.slice(0, Math.max(index, 0)).reverse().find((node) => !node.disabled);
|
|
478
|
+
if (next) next.focus({ preventScroll: true });
|
|
479
|
+
else {
|
|
480
|
+
const previous = root.getAttribute("tabindex");
|
|
481
|
+
root.tabIndex = -1;
|
|
482
|
+
root.focus({ preventScroll: true });
|
|
483
|
+
if (previous === null) root.removeAttribute("tabindex");
|
|
484
|
+
else root.setAttribute("tabindex", previous);
|
|
485
|
+
}
|
|
486
|
+
}, []);
|
|
487
|
+
const register = useCallback((itemValue, node) => {
|
|
488
|
+
const members = items.current;
|
|
489
|
+
if (members.has(itemValue)) throw new Error(`Accordion item values must be unique: "${itemValue}"`);
|
|
490
|
+
members.set(itemValue, node);
|
|
491
|
+
const reconcile = () => {
|
|
492
|
+
queueMicrotask(() => {
|
|
493
|
+
if (controlled || !rootRef.current) return;
|
|
494
|
+
if (internalRef.current.every((entry) => members.has(entry))) return;
|
|
495
|
+
setInternal((current) => {
|
|
496
|
+
const next = current.filter((entry) => members.has(entry));
|
|
497
|
+
return next.length === current.length ? current : next;
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
reconcile();
|
|
502
|
+
return () => {
|
|
503
|
+
members.delete(itemValue);
|
|
504
|
+
const focused = node.ownerDocument.activeElement;
|
|
505
|
+
if (focused && node.contains(focused)) {
|
|
506
|
+
const root = rootRef.current;
|
|
507
|
+
const triggers = root ? ownTriggers(root) : [];
|
|
508
|
+
const index = triggers.findIndex((trigger) => node.contains(trigger));
|
|
509
|
+
const candidates = [...triggers.slice(index + 1), ...triggers.slice(0, index).reverse()];
|
|
510
|
+
queueMicrotask(() => {
|
|
511
|
+
const active = node.ownerDocument.activeElement;
|
|
512
|
+
if (node.isConnected || !rootRef.current || active !== node.ownerDocument.body && active !== focused) return;
|
|
513
|
+
const next = candidates.find((trigger) => trigger.isConnected && !trigger.disabled);
|
|
514
|
+
if (next) next.focus({ preventScroll: true });
|
|
515
|
+
else restoreFocus(node, true);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
reconcile();
|
|
519
|
+
};
|
|
520
|
+
}, [controlled, restoreFocus]);
|
|
521
|
+
const toggle = (itemValue) => {
|
|
522
|
+
const open = selected.includes(itemValue);
|
|
523
|
+
if (disabled || type === "single" && open && !collapsible) return;
|
|
524
|
+
const next = open ? selected.filter((entry) => entry !== itemValue) : type === "single" ? [itemValue] : [...selected, itemValue];
|
|
525
|
+
if (!controlled) setInternal(next);
|
|
526
|
+
if (type === "multiple") onValueChange?.(next);
|
|
527
|
+
else onValueChange?.(next[0] ?? null);
|
|
528
|
+
};
|
|
529
|
+
return /* @__PURE__ */ jsx(AccordionContext, {
|
|
530
|
+
value: {
|
|
531
|
+
selected,
|
|
532
|
+
variant,
|
|
533
|
+
headingLevel,
|
|
534
|
+
disabled,
|
|
535
|
+
collapsible: type === "multiple" || collapsible,
|
|
536
|
+
toggle,
|
|
537
|
+
register,
|
|
538
|
+
restoreFocus
|
|
539
|
+
},
|
|
540
|
+
children: /* @__PURE__ */ jsx(ItemContext, {
|
|
541
|
+
value: null,
|
|
542
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
543
|
+
...props,
|
|
544
|
+
ref: mergedRef,
|
|
545
|
+
"data-slot": "accordion",
|
|
546
|
+
"data-variant": variant,
|
|
547
|
+
"data-disabled": disabled || void 0,
|
|
548
|
+
className: cn("min-w-0", variant === "outline" && "flex flex-col gap-2", className),
|
|
549
|
+
onKeyDown: (event) => {
|
|
550
|
+
onKeyDown?.(event);
|
|
551
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
552
|
+
if (![
|
|
553
|
+
"ArrowDown",
|
|
554
|
+
"ArrowUp",
|
|
555
|
+
"Home",
|
|
556
|
+
"End"
|
|
557
|
+
].includes(event.key)) return;
|
|
558
|
+
const triggers = ownTriggers(event.currentTarget).filter((node) => !node.disabled);
|
|
559
|
+
const index = triggers.indexOf(event.target);
|
|
560
|
+
if (index === -1) return;
|
|
561
|
+
event.preventDefault();
|
|
562
|
+
triggers[event.key === "Home" ? 0 : event.key === "End" ? triggers.length - 1 : Math.max(0, Math.min(triggers.length - 1, index + (event.key === "ArrowDown" ? 1 : -1)))]?.focus();
|
|
563
|
+
},
|
|
564
|
+
children
|
|
565
|
+
})
|
|
566
|
+
})
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function AccordionItem({ value, disabled = false, className, children, ref, ...props }) {
|
|
570
|
+
const group = useAccordionContext("AccordionItem");
|
|
571
|
+
if (!value.trim()) throw new Error("AccordionItem value must be a non-empty string");
|
|
572
|
+
const id = useId();
|
|
573
|
+
const itemRef = useRef(null);
|
|
574
|
+
const mergedRef = useMergedRef(itemRef, ref);
|
|
575
|
+
const register = group.register;
|
|
576
|
+
useLayoutEffect(() => {
|
|
577
|
+
if (itemRef.current) return register(value, itemRef.current);
|
|
578
|
+
}, [register, value]);
|
|
579
|
+
const open = group.selected.includes(value);
|
|
580
|
+
const isDisabled = group.disabled || disabled;
|
|
581
|
+
return /* @__PURE__ */ jsx(ItemContext, {
|
|
582
|
+
value: {
|
|
583
|
+
id,
|
|
584
|
+
value,
|
|
585
|
+
open,
|
|
586
|
+
disabled: isDisabled,
|
|
587
|
+
itemRef
|
|
588
|
+
},
|
|
589
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
590
|
+
...props,
|
|
591
|
+
ref: mergedRef,
|
|
592
|
+
"data-slot": "accordion-item",
|
|
593
|
+
"data-state": open ? "open" : "closed",
|
|
594
|
+
"data-disabled": isDisabled || void 0,
|
|
595
|
+
className: cn("min-w-0 border-input-border", group.variant === "outline" ? "rounded-xl border bg-surface" : "border-b", className),
|
|
596
|
+
children
|
|
597
|
+
})
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
function AccordionTrigger({ children, description, className, onClick, "aria-describedby": describedBy, ...props }) {
|
|
601
|
+
const group = useAccordionContext("AccordionTrigger");
|
|
602
|
+
const item = useItemContext("AccordionTrigger");
|
|
603
|
+
const Heading = `h${group.headingLevel}`;
|
|
604
|
+
const hasDescription = isPresent(description);
|
|
605
|
+
const locked = item.open && !group.collapsible;
|
|
606
|
+
return /* @__PURE__ */ jsx(Heading, {
|
|
607
|
+
"data-slot": "accordion-heading",
|
|
608
|
+
className: "m-0 text-inherit",
|
|
609
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
610
|
+
...props,
|
|
611
|
+
id: `${item.id}-trigger`,
|
|
612
|
+
type: "button",
|
|
613
|
+
disabled: item.disabled,
|
|
614
|
+
"aria-expanded": item.open,
|
|
615
|
+
"aria-controls": `${item.id}-content`,
|
|
616
|
+
"aria-disabled": locked || void 0,
|
|
617
|
+
"aria-labelledby": props["aria-label"] ? void 0 : `${item.id}-title`,
|
|
618
|
+
"aria-describedby": [describedBy, hasDescription && `${item.id}-description`].filter(Boolean).join(" ") || void 0,
|
|
619
|
+
"data-slot": "accordion-trigger",
|
|
620
|
+
"data-state": item.open ? "open" : "closed",
|
|
621
|
+
className: cn("group/accordion-trigger flex min-h-12 w-full cursor-pointer items-start gap-3 rounded-sm py-3 text-start text-sm/6 font-medium text-fg outline-none", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", "disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-default", "forced-colors:disabled:text-[GrayText]", group.variant === "outline" && "rounded-xl px-4", className),
|
|
622
|
+
onClick: (event) => {
|
|
623
|
+
onClick?.(event);
|
|
624
|
+
if (!event.defaultPrevented && !item.disabled && !locked) group.toggle(item.value);
|
|
625
|
+
},
|
|
626
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
627
|
+
className: "min-w-0 flex-1 wrap-anywhere",
|
|
628
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
629
|
+
id: `${item.id}-title`,
|
|
630
|
+
className: "underline-offset-4 group-enabled/accordion-trigger:group-hover/accordion-trigger:underline",
|
|
631
|
+
children
|
|
632
|
+
}), hasDescription && /* @__PURE__ */ jsx("span", {
|
|
633
|
+
id: `${item.id}-description`,
|
|
634
|
+
className: "mt-0.5 block text-xs/4.5 font-normal text-fg-muted",
|
|
635
|
+
children: description
|
|
636
|
+
})]
|
|
637
|
+
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
638
|
+
"aria-hidden": true,
|
|
639
|
+
className: cn("mt-1 size-4 shrink-0 text-fg-muted transition-transform duration-200 ease-out motion-reduce:transition-none", item.open && "rotate-180")
|
|
640
|
+
})]
|
|
641
|
+
})
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function AccordionContent({ keepMounted = true, region = false, className, contentClassName, children, ref, ...props }) {
|
|
645
|
+
const group = useAccordionContext("AccordionContent");
|
|
646
|
+
const item = useItemContext("AccordionContent");
|
|
647
|
+
const { panelRef, innerRef, present } = useAccordionMotion(item.open, item.itemRef, group.restoreFocus);
|
|
648
|
+
const mergedRef = useMergedRef(panelRef, ref);
|
|
649
|
+
return /* @__PURE__ */ jsx("div", {
|
|
121
650
|
...props,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
651
|
+
ref: mergedRef,
|
|
652
|
+
id: `${item.id}-content`,
|
|
653
|
+
role: region ? "region" : void 0,
|
|
654
|
+
"aria-labelledby": region ? `${item.id}-title` : void 0,
|
|
655
|
+
hidden: !item.open && !present,
|
|
656
|
+
inert: !item.open && !present,
|
|
657
|
+
"aria-hidden": !item.open && !present ? true : void 0,
|
|
658
|
+
"data-slot": "accordion-content",
|
|
659
|
+
"data-state": item.open ? "open" : "closed",
|
|
660
|
+
className: cn("min-w-0", contentClassName),
|
|
661
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
662
|
+
ref: innerRef,
|
|
663
|
+
"data-slot": "accordion-content-inner",
|
|
664
|
+
className: cn("flow-root min-w-0 pb-4 text-sm/5.5 wrap-anywhere text-fg-muted", group.variant === "outline" ? "ps-4 pe-11" : "pe-7", className),
|
|
665
|
+
children: (keepMounted || item.open || present) && children
|
|
666
|
+
})
|
|
137
667
|
});
|
|
138
668
|
}
|
|
139
669
|
//#endregion
|
|
@@ -221,11 +751,66 @@ function usePressFeedback(ripple = true) {
|
|
|
221
751
|
return ref;
|
|
222
752
|
}
|
|
223
753
|
//#endregion
|
|
754
|
+
//#region src/lib/slot.tsx
|
|
755
|
+
function setRef(ref, node) {
|
|
756
|
+
if (typeof ref === "function") return ref(node);
|
|
757
|
+
if (ref) ref.current = node;
|
|
758
|
+
}
|
|
759
|
+
function composeRefs(...refs) {
|
|
760
|
+
return (node) => {
|
|
761
|
+
const cleanups = refs.map((ref) => setRef(ref, node));
|
|
762
|
+
return () => {
|
|
763
|
+
for (const [i, ref] of refs.entries()) {
|
|
764
|
+
const cleanup = cleanups[i];
|
|
765
|
+
if (typeof cleanup === "function") cleanup();
|
|
766
|
+
else setRef(ref, null);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function mergeProps(slot, child) {
|
|
772
|
+
const merged = {
|
|
773
|
+
...slot,
|
|
774
|
+
...child
|
|
775
|
+
};
|
|
776
|
+
for (const key of Object.keys(slot)) {
|
|
777
|
+
const own = slot[key];
|
|
778
|
+
const theirs = child[key];
|
|
779
|
+
if (/^on[A-Z]/.test(key) && typeof own === "function" && typeof theirs === "function") merged[key] = (...args) => {
|
|
780
|
+
theirs(...args);
|
|
781
|
+
own(...args);
|
|
782
|
+
};
|
|
783
|
+
else if (key === "className") merged.className = cn(own, theirs);
|
|
784
|
+
else if (key === "style") merged.style = {
|
|
785
|
+
...own,
|
|
786
|
+
...theirs
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return merged;
|
|
790
|
+
}
|
|
791
|
+
/** Applies `wrap` to an element's children, or to what a render-prop child returns. */
|
|
792
|
+
function wrapChildren(children, wrap) {
|
|
793
|
+
if (typeof children !== "function") return wrap(children);
|
|
794
|
+
return (...args) => wrap(children(...args));
|
|
795
|
+
}
|
|
796
|
+
/** Renders its only child with these props merged in (Radix Slot style). */
|
|
797
|
+
function Slot({ children, ref, ...props }) {
|
|
798
|
+
const child = Children.only(children);
|
|
799
|
+
const childProps = isValidElement(child) ? child.props : {};
|
|
800
|
+
const childRef = childProps.ref;
|
|
801
|
+
const composedRef = useMemo(() => composeRefs(ref, childRef), [ref, childRef]);
|
|
802
|
+
if (!isValidElement(child)) return null;
|
|
803
|
+
return cloneElement(child, {
|
|
804
|
+
...mergeProps(props, childProps),
|
|
805
|
+
ref: composedRef
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
//#endregion
|
|
224
809
|
//#region src/components/tooltip.tsx
|
|
225
810
|
const OPEN_DELAY_MS = 300;
|
|
226
811
|
const GAP_PX = 6;
|
|
227
812
|
const VIEWPORT_PADDING_PX = 8;
|
|
228
|
-
const opposite = {
|
|
813
|
+
const opposite$1 = {
|
|
229
814
|
top: "bottom",
|
|
230
815
|
bottom: "top",
|
|
231
816
|
left: "right",
|
|
@@ -238,7 +823,7 @@ function isFocusVisible$1(el) {
|
|
|
238
823
|
return true;
|
|
239
824
|
}
|
|
240
825
|
}
|
|
241
|
-
function clamp(value, min, max) {
|
|
826
|
+
function clamp$2(value, min, max) {
|
|
242
827
|
return Math.max(min, Math.min(value, Math.max(min, max)));
|
|
243
828
|
}
|
|
244
829
|
/**
|
|
@@ -255,15 +840,15 @@ function place(anchor, width, height, preferred) {
|
|
|
255
840
|
right: vw - anchor.right
|
|
256
841
|
};
|
|
257
842
|
const need = (s) => (s === "top" || s === "bottom" ? height : width) + GAP_PX + VIEWPORT_PADDING_PX;
|
|
258
|
-
const side = room[preferred] < need(preferred) && room[opposite[preferred]] > room[preferred] ? opposite[preferred] : preferred;
|
|
843
|
+
const side = room[preferred] < need(preferred) && room[opposite$1[preferred]] > room[preferred] ? opposite$1[preferred] : preferred;
|
|
259
844
|
if (side === "top" || side === "bottom") return {
|
|
260
845
|
side,
|
|
261
|
-
left: clamp(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
|
|
846
|
+
left: clamp$2(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
|
|
262
847
|
top: side === "top" ? anchor.top - height - GAP_PX : anchor.bottom
|
|
263
848
|
};
|
|
264
849
|
return {
|
|
265
850
|
side,
|
|
266
|
-
top: clamp(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
|
|
851
|
+
top: clamp$2(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
|
|
267
852
|
left: side === "left" ? anchor.left - width - GAP_PX : anchor.right
|
|
268
853
|
};
|
|
269
854
|
}
|
|
@@ -456,7 +1041,7 @@ const spinnerSize = {
|
|
|
456
1041
|
md: 18,
|
|
457
1042
|
lg: 20
|
|
458
1043
|
};
|
|
459
|
-
const iconSize
|
|
1044
|
+
const iconSize = {
|
|
460
1045
|
sm: "[&_svg]:size-4",
|
|
461
1046
|
md: "[&_svg]:size-4",
|
|
462
1047
|
lg: "[&_svg]:size-[18px]"
|
|
@@ -474,7 +1059,7 @@ const iconEndPadding = {
|
|
|
474
1059
|
/** Press layer + content + spinner; shared by every button-styled host. */
|
|
475
1060
|
function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, children }) {
|
|
476
1061
|
const layerRef = usePressFeedback(ripple);
|
|
477
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1062
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
478
1063
|
/* @__PURE__ */ jsx("span", {
|
|
479
1064
|
ref: layerRef,
|
|
480
1065
|
"data-ripple": ripple ? "" : void 0,
|
|
@@ -482,7 +1067,7 @@ function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, chi
|
|
|
482
1067
|
className: "pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit] forced-colors:hidden"
|
|
483
1068
|
}),
|
|
484
1069
|
/* @__PURE__ */ jsxs("span", {
|
|
485
|
-
className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize
|
|
1070
|
+
className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize[size], loading && "opacity-0"),
|
|
486
1071
|
children: [
|
|
487
1072
|
iconLeft && /* @__PURE__ */ jsx("span", {
|
|
488
1073
|
"aria-hidden": true,
|
|
@@ -514,395 +1099,292 @@ function stateClass(disabled, loading) {
|
|
|
514
1099
|
if (loading) return "cursor-progress opacity-70";
|
|
515
1100
|
if (disabled) return "cursor-not-allowed opacity-50 forced-colors:opacity-100 forced-colors:text-[GrayText] forced-colors:after:border-[GrayText]";
|
|
516
1101
|
}
|
|
517
|
-
function
|
|
1102
|
+
function blockActivation(e) {
|
|
1103
|
+
e.preventDefault();
|
|
1104
|
+
e.stopPropagation();
|
|
1105
|
+
}
|
|
1106
|
+
function ButtonImpl({ asChild = false, className, intent, size, tone, fullWidth, square, loading = false, loadingText = "Loading", disabled = false, iconLeft, iconRight, ripple, tooltip, type = "button", onClick, onClickCapture, onAuxClickCapture, children, ...props }) {
|
|
518
1107
|
const s = size ?? "md";
|
|
519
1108
|
const inert = disabled || loading;
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
type,
|
|
523
|
-
"data-slot": "button",
|
|
524
|
-
"aria-disabled": inert || void 0,
|
|
525
|
-
"aria-busy": loading || void 0,
|
|
526
|
-
className: cn(buttonVariants({
|
|
527
|
-
intent,
|
|
528
|
-
size,
|
|
529
|
-
tone,
|
|
530
|
-
fullWidth,
|
|
531
|
-
square
|
|
532
|
-
}), iconPadding(s, iconLeft, iconRight), stateClass(disabled, loading), className),
|
|
533
|
-
onClick: (e) => {
|
|
534
|
-
if (inert) {
|
|
535
|
-
e.preventDefault();
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
onClick?.(e);
|
|
539
|
-
},
|
|
540
|
-
children: /* @__PURE__ */ jsx(ButtonContent, {
|
|
541
|
-
size: s,
|
|
542
|
-
ripple: ripple ?? true,
|
|
543
|
-
loading,
|
|
544
|
-
iconLeft,
|
|
545
|
-
iconRight,
|
|
546
|
-
children
|
|
547
|
-
})
|
|
548
|
-
});
|
|
549
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
550
|
-
...tooltip,
|
|
551
|
-
className: fullWidth ? "flex w-full" : void 0,
|
|
552
|
-
children: button
|
|
553
|
-
}) : button, /* @__PURE__ */ jsx("span", {
|
|
554
|
-
"aria-live": "polite",
|
|
555
|
-
"aria-atomic": true,
|
|
556
|
-
className: "sr-only",
|
|
557
|
-
children: loading ? loadingText : ""
|
|
558
|
-
})] });
|
|
559
|
-
}
|
|
560
|
-
function IconOnlyButton({ icon, tooltip = true, tooltipSide, disabledReason, className, size, ...props }) {
|
|
561
|
-
const reason = props.disabled ? disabledReason : void 0;
|
|
562
|
-
const tip = reason ? {
|
|
563
|
-
content: reason,
|
|
564
|
-
side: tooltipSide
|
|
565
|
-
} : tooltip ? {
|
|
566
|
-
content: props["aria-label"],
|
|
567
|
-
side: tooltipSide,
|
|
568
|
-
describe: false
|
|
569
|
-
} : disabledReason !== void 0 ? { content: void 0 } : void 0;
|
|
570
|
-
return /* @__PURE__ */ jsx(ButtonImpl, {
|
|
571
|
-
...props,
|
|
1109
|
+
const classes = cn(buttonVariants({
|
|
1110
|
+
intent,
|
|
572
1111
|
size,
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
...rest,
|
|
585
|
-
tooltip
|
|
1112
|
+
tone,
|
|
1113
|
+
fullWidth,
|
|
1114
|
+
square
|
|
1115
|
+
}), iconPadding(s, iconLeft, iconRight), stateClass(disabled, loading), className);
|
|
1116
|
+
const content = (inner) => /* @__PURE__ */ jsx(ButtonContent, {
|
|
1117
|
+
size: s,
|
|
1118
|
+
ripple: ripple ?? true,
|
|
1119
|
+
loading,
|
|
1120
|
+
iconLeft,
|
|
1121
|
+
iconRight,
|
|
1122
|
+
children: inner
|
|
586
1123
|
});
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
titleId: "",
|
|
592
|
-
setTitled: () => {}
|
|
593
|
-
});
|
|
594
|
-
/**
|
|
595
|
-
* Bordered surface for a group of related content. No padding of its own: CardHeader and
|
|
596
|
-
* CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
|
|
597
|
-
*/
|
|
598
|
-
function Card({ as: Comp = "div", className, ...props }) {
|
|
599
|
-
const titleId = useId();
|
|
600
|
-
const [titled, setTitled] = useState(false);
|
|
601
|
-
return /* @__PURE__ */ jsx(CardContext, {
|
|
602
|
-
value: {
|
|
603
|
-
titleId,
|
|
604
|
-
setTitled
|
|
605
|
-
},
|
|
606
|
-
children: /* @__PURE__ */ jsx(Comp, {
|
|
607
|
-
"aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
|
|
1124
|
+
if (asChild) {
|
|
1125
|
+
const child = Children.only(children);
|
|
1126
|
+
if (!isValidElement(child)) return null;
|
|
1127
|
+
const link = /* @__PURE__ */ jsx(Slot, {
|
|
608
1128
|
...props,
|
|
609
|
-
"data-slot": "
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
1129
|
+
"data-slot": "button",
|
|
1130
|
+
"aria-disabled": disabled || void 0,
|
|
1131
|
+
className: classes,
|
|
1132
|
+
onClick,
|
|
1133
|
+
onClickCapture: disabled ? blockActivation : onClickCapture,
|
|
1134
|
+
onAuxClickCapture: disabled ? blockActivation : onAuxClickCapture,
|
|
1135
|
+
children: cloneElement(child, { children: wrapChildren(child.props.children, content) })
|
|
1136
|
+
});
|
|
1137
|
+
return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
1138
|
+
...tooltip,
|
|
1139
|
+
className: fullWidth ? "flex w-full" : void 0,
|
|
1140
|
+
children: link
|
|
1141
|
+
}) : link;
|
|
1142
|
+
}
|
|
1143
|
+
const button = /* @__PURE__ */ jsx("button", {
|
|
1144
|
+
...props,
|
|
1145
|
+
type,
|
|
1146
|
+
"data-slot": "button",
|
|
1147
|
+
"aria-disabled": inert || void 0,
|
|
1148
|
+
"aria-busy": loading || void 0,
|
|
1149
|
+
className: classes,
|
|
1150
|
+
onClickCapture,
|
|
1151
|
+
onAuxClickCapture,
|
|
1152
|
+
onClick: (e) => {
|
|
1153
|
+
if (inert) {
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
onClick?.(e);
|
|
1158
|
+
},
|
|
1159
|
+
children: content(children)
|
|
639
1160
|
});
|
|
1161
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
1162
|
+
...tooltip,
|
|
1163
|
+
className: fullWidth ? "flex w-full" : void 0,
|
|
1164
|
+
children: button
|
|
1165
|
+
}) : button, /* @__PURE__ */ jsx("span", {
|
|
1166
|
+
"aria-live": "polite",
|
|
1167
|
+
"aria-atomic": true,
|
|
1168
|
+
className: "sr-only",
|
|
1169
|
+
children: loading ? loadingText : ""
|
|
1170
|
+
})] });
|
|
640
1171
|
}
|
|
641
|
-
function
|
|
642
|
-
|
|
1172
|
+
function IconOnlyButton({ icon, tooltip = true, tooltipSide, disabledReason, className, size, children, ...props }) {
|
|
1173
|
+
const reason = props.disabled ? disabledReason : void 0;
|
|
1174
|
+
const tip = reason ? {
|
|
1175
|
+
content: reason,
|
|
1176
|
+
side: tooltipSide
|
|
1177
|
+
} : tooltip ? {
|
|
1178
|
+
content: props["aria-label"],
|
|
1179
|
+
side: tooltipSide,
|
|
1180
|
+
describe: false
|
|
1181
|
+
} : disabledReason !== void 0 ? { content: void 0 } : void 0;
|
|
1182
|
+
const child = Children.toArray(children)[0];
|
|
1183
|
+
return /* @__PURE__ */ jsx(ButtonImpl, {
|
|
643
1184
|
...props,
|
|
644
|
-
|
|
645
|
-
|
|
1185
|
+
size,
|
|
1186
|
+
tooltip: tip,
|
|
1187
|
+
square: true,
|
|
1188
|
+
className,
|
|
1189
|
+
children: props.asChild && isValidElement(child) ? cloneElement(child, void 0, icon) : icon
|
|
646
1190
|
});
|
|
647
1191
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const textSize = {
|
|
656
|
-
sm: "text-sm",
|
|
657
|
-
md: "text-sm",
|
|
658
|
-
lg: "text-base"
|
|
659
|
-
};
|
|
660
|
-
const iconSize = {
|
|
661
|
-
sm: "[&_svg]:size-4",
|
|
662
|
-
md: "[&_svg]:size-4",
|
|
663
|
-
lg: "[&_svg]:size-[18px]"
|
|
664
|
-
};
|
|
665
|
-
const inputPadding = {
|
|
666
|
-
sm: "px-3",
|
|
667
|
-
md: "px-3",
|
|
668
|
-
lg: "px-4"
|
|
669
|
-
};
|
|
670
|
-
const startSlotPadding = {
|
|
671
|
-
sm: "ps-2",
|
|
672
|
-
md: "ps-2.5",
|
|
673
|
-
lg: "ps-3"
|
|
674
|
-
};
|
|
675
|
-
const endSlotPadding = {
|
|
676
|
-
sm: "pe-2",
|
|
677
|
-
md: "pe-2.5",
|
|
678
|
-
lg: "pe-3"
|
|
679
|
-
};
|
|
680
|
-
const actionsPadding = {
|
|
681
|
-
sm: "pe-[3px]",
|
|
682
|
-
md: "pe-1",
|
|
683
|
-
lg: "pe-1"
|
|
684
|
-
};
|
|
685
|
-
/** Compact Button square that fits inside the field's inner height with an even inset. */
|
|
686
|
-
const compactButton = {
|
|
687
|
-
sm: "size-[26px]",
|
|
688
|
-
md: "size-7",
|
|
689
|
-
lg: "size-8"
|
|
690
|
-
};
|
|
691
|
-
const labelStart = {
|
|
692
|
-
sm: "start-2",
|
|
693
|
-
md: "start-2",
|
|
694
|
-
lg: "start-3"
|
|
695
|
-
};
|
|
696
|
-
const floatShift = {
|
|
697
|
-
sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+17px)]",
|
|
698
|
-
md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+19px)]",
|
|
699
|
-
lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+21px)]"
|
|
700
|
-
};
|
|
701
|
-
const isText = (node) => typeof node === "string" || typeof node === "number";
|
|
702
|
-
const isPresent = (node) => node != null && node !== false && node !== "";
|
|
703
|
-
function Adornment({ node, side, size }) {
|
|
704
|
-
const text = isText(node);
|
|
705
|
-
return /* @__PURE__ */ jsx("span", {
|
|
706
|
-
"aria-hidden": text ? void 0 : true,
|
|
707
|
-
className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
|
|
708
|
-
children: node
|
|
1192
|
+
function Button(props) {
|
|
1193
|
+
if (props.icon !== void 0) return /* @__PURE__ */ jsx(IconOnlyButton, { ...props });
|
|
1194
|
+
const { disabledReason, ...rest } = props;
|
|
1195
|
+
const tooltip = disabledReason === void 0 ? void 0 : { content: rest.disabled ? disabledReason : void 0 };
|
|
1196
|
+
return /* @__PURE__ */ jsx(ButtonImpl, {
|
|
1197
|
+
...rest,
|
|
1198
|
+
tooltip
|
|
709
1199
|
});
|
|
710
1200
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1201
|
+
//#endregion
|
|
1202
|
+
//#region src/components/alert/alert.tsx
|
|
1203
|
+
const tones = {
|
|
1204
|
+
neutral: {
|
|
1205
|
+
Icon: Info,
|
|
1206
|
+
iconClass: "text-neutral"
|
|
716
1207
|
},
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1208
|
+
info: {
|
|
1209
|
+
Icon: Info,
|
|
1210
|
+
iconClass: "text-info"
|
|
1211
|
+
},
|
|
1212
|
+
success: {
|
|
1213
|
+
Icon: CircleCheck,
|
|
1214
|
+
iconClass: "text-success"
|
|
1215
|
+
},
|
|
1216
|
+
warning: {
|
|
1217
|
+
Icon: TriangleAlert,
|
|
1218
|
+
iconClass: "text-warning"
|
|
1219
|
+
},
|
|
1220
|
+
danger: {
|
|
1221
|
+
Icon: CircleAlert,
|
|
1222
|
+
iconClass: "text-danger"
|
|
721
1223
|
}
|
|
722
1224
|
};
|
|
723
|
-
/**
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
"
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1225
|
+
/** A persistent message shown next to the content it describes. Use a live region for updates that need announcing. */
|
|
1226
|
+
function Alert({ tone = "neutral", title, action, onDismiss, dismissLabel = "Dismiss alert", className, children, ...props }) {
|
|
1227
|
+
const { Icon, iconClass } = tones[tone];
|
|
1228
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1229
|
+
...props,
|
|
1230
|
+
"data-slot": "alert",
|
|
1231
|
+
"data-tone": tone,
|
|
1232
|
+
className: cn("@container/alert flex items-start gap-3 rounded-xl border border-ring-deep px-4 py-3", "text-sm text-fg forced-colors:border-[CanvasText]", className),
|
|
1233
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1234
|
+
className: cn("grid min-w-0 flex-1 grid-cols-[1rem_minmax(0,1fr)] items-start gap-x-2.5 gap-y-3", action && "@md/alert:grid-cols-[1rem_minmax(0,1fr)_auto]"),
|
|
1235
|
+
children: [
|
|
1236
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1237
|
+
"aria-hidden": "true",
|
|
1238
|
+
"data-slot": "alert-icon",
|
|
1239
|
+
className: cn("mt-0.5 size-4", iconClass)
|
|
1240
|
+
}),
|
|
1241
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1242
|
+
className: "min-w-0 space-y-0.5 wrap-anywhere",
|
|
1243
|
+
children: [title != null && /* @__PURE__ */ jsx("div", {
|
|
1244
|
+
"data-slot": "alert-title",
|
|
1245
|
+
className: "font-medium",
|
|
1246
|
+
children: title
|
|
1247
|
+
}), children != null && /* @__PURE__ */ jsx("div", {
|
|
1248
|
+
"data-slot": "alert-description",
|
|
1249
|
+
className: title != null ? "text-fg-muted" : void 0,
|
|
1250
|
+
children
|
|
1251
|
+
})]
|
|
1252
|
+
}),
|
|
1253
|
+
action && /* @__PURE__ */ jsx("div", {
|
|
1254
|
+
"data-slot": "alert-action",
|
|
1255
|
+
className: "col-start-2 justify-self-start @md/alert:col-start-3 @md/alert:self-center",
|
|
1256
|
+
children: action
|
|
1257
|
+
})
|
|
1258
|
+
]
|
|
1259
|
+
}), onDismiss && /* @__PURE__ */ jsx(Button, {
|
|
1260
|
+
"aria-label": dismissLabel,
|
|
1261
|
+
className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
|
|
1262
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1263
|
+
intent: "ghost",
|
|
1264
|
+
size: "sm",
|
|
1265
|
+
onClick: onDismiss
|
|
1266
|
+
})]
|
|
740
1267
|
});
|
|
741
1268
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
"
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
"data-start": hasStart ? "" : void 0,
|
|
798
|
-
onMouseDown: forwardClick,
|
|
799
|
-
className: cn("group/control relative isolate flex items-center rounded-lg border", floatingLabel ? "bg-transparent" : "[--field-bg:var(--color-input-bg)] bg-(--field-bg)", "transition-[border-color,box-shadow,background-color] duration-200 ease-in-out motion-reduce:transition-none", controlHeight[size], invalid ? ["[--field-border:var(--color-danger)] [--field-halo:var(--color-input-halo-invalid)]", "has-[:is(input,select):is(:focus,:open)]:[--field-halo:var(--color-input-halo-invalid-focus)]"] : ["[--field-border:var(--color-input-border)] [--field-halo:transparent]", "has-[:is(input,select):is(:focus,:open)]:[--field-border:var(--color-input-focus)] has-[:is(input,select):is(:focus,:open)]:[--field-halo:var(--color-input-halo)]"], "[--field-lift:0_0_#0000]", !invalid && !disabled && !readOnly && [
|
|
800
|
-
"hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-border:var(--color-input-border-hover)]",
|
|
801
|
-
!floatingLabel && "hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-bg:var(--color-input-bg-hover)]",
|
|
802
|
-
"hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-lift:var(--shadow-input-lift)]"
|
|
803
|
-
], floatingLabel ? ["border-transparent forced-colors:border-[Canvas]", !readOnly && "[box-shadow:var(--field-lift)]"] : [
|
|
804
|
-
"border-(--field-border)",
|
|
805
|
-
readOnly ? "[box-shadow:0_0_0_3px_var(--field-halo)]" : "[box-shadow:var(--shadow-input),var(--field-lift),0_0_0_3px_var(--field-halo)]",
|
|
806
|
-
"has-[:is(input,select):is(:focus,:open)]:outline-hidden",
|
|
807
|
-
disabled && "forced-colors:border-[GrayText]"
|
|
808
|
-
], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
|
|
809
|
-
children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Notch, {
|
|
810
|
-
size,
|
|
811
|
-
ring: "halo",
|
|
812
|
-
disabled,
|
|
813
|
-
children: [label, asterisk]
|
|
814
|
-
}), /* @__PURE__ */ jsxs(Notch, {
|
|
815
|
-
size,
|
|
816
|
-
ring: "border",
|
|
817
|
-
disabled,
|
|
818
|
-
children: [label, asterisk]
|
|
819
|
-
})] }), children({
|
|
820
|
-
id,
|
|
821
|
-
invalid,
|
|
822
|
-
describedBy,
|
|
823
|
-
floating: floatingLabel,
|
|
824
|
-
floatingLabel: renderFloatingLabel
|
|
825
|
-
})]
|
|
826
|
-
}),
|
|
827
|
-
hasDescription && /* @__PURE__ */ jsxs("p", {
|
|
828
|
-
id: descriptionId,
|
|
829
|
-
className: cn("m-0 flex items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
|
|
830
|
-
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
831
|
-
"aria-hidden": true,
|
|
832
|
-
className: "mt-px size-3.5 shrink-0"
|
|
833
|
-
}), /* @__PURE__ */ jsx("span", { children: description })]
|
|
834
|
-
}),
|
|
835
|
-
/* @__PURE__ */ jsx("span", {
|
|
836
|
-
"aria-live": "polite",
|
|
837
|
-
"aria-atomic": true,
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/components/badge/badge.tsx
|
|
1271
|
+
const toneClasses$1 = {
|
|
1272
|
+
neutral: {
|
|
1273
|
+
subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
|
|
1274
|
+
outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
|
|
1275
|
+
solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
|
|
1276
|
+
},
|
|
1277
|
+
info: {
|
|
1278
|
+
subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
|
|
1279
|
+
outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
|
|
1280
|
+
solid: ["border-info bg-info text-info-fg", "text-info-fg"]
|
|
1281
|
+
},
|
|
1282
|
+
success: {
|
|
1283
|
+
subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
|
|
1284
|
+
outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
|
|
1285
|
+
solid: ["border-success bg-success text-success-fg", "text-success-fg"]
|
|
1286
|
+
},
|
|
1287
|
+
warning: {
|
|
1288
|
+
subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
|
|
1289
|
+
outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
|
|
1290
|
+
solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
|
|
1291
|
+
},
|
|
1292
|
+
danger: {
|
|
1293
|
+
subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
|
|
1294
|
+
outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
|
|
1295
|
+
solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
const sizeClasses$3 = {
|
|
1299
|
+
sm: {
|
|
1300
|
+
base: "h-5 px-1.5",
|
|
1301
|
+
withIcon: "ps-1"
|
|
1302
|
+
},
|
|
1303
|
+
md: {
|
|
1304
|
+
base: "h-6 px-2",
|
|
1305
|
+
withIcon: "ps-1.5"
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
|
|
1309
|
+
const [toneClass, iconClass] = toneClasses$1[tone][variant];
|
|
1310
|
+
const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
|
|
1311
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1312
|
+
...props,
|
|
1313
|
+
"data-slot": "badge",
|
|
1314
|
+
className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
|
|
1315
|
+
children: [hasIcon && /* @__PURE__ */ jsx("span", {
|
|
1316
|
+
"aria-hidden": "true",
|
|
1317
|
+
"data-slot": "badge-icon",
|
|
1318
|
+
className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
|
|
1319
|
+
children: busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
|
|
1320
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1321
|
+
"data-slot": "badge-label",
|
|
1322
|
+
className: "min-w-0 truncate leading-4",
|
|
1323
|
+
children: [context && /* @__PURE__ */ jsxs("span", {
|
|
838
1324
|
className: "sr-only",
|
|
839
|
-
children:
|
|
840
|
-
})
|
|
841
|
-
]
|
|
1325
|
+
children: [context, ": "]
|
|
1326
|
+
}), children]
|
|
1327
|
+
})]
|
|
842
1328
|
});
|
|
843
1329
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
|
|
903
|
-
children: actions
|
|
904
|
-
}) : null
|
|
905
|
-
] })
|
|
1330
|
+
//#endregion
|
|
1331
|
+
//#region src/components/card/card.tsx
|
|
1332
|
+
const CardContext = createContext({
|
|
1333
|
+
titleId: "",
|
|
1334
|
+
setTitled: () => {}
|
|
1335
|
+
});
|
|
1336
|
+
/**
|
|
1337
|
+
* Bordered surface for a group of related content. No padding of its own: CardHeader and
|
|
1338
|
+
* CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
|
|
1339
|
+
*/
|
|
1340
|
+
function Card({ as: Comp = "div", className, ...props }) {
|
|
1341
|
+
const titleId = useId();
|
|
1342
|
+
const [titled, setTitled] = useState(false);
|
|
1343
|
+
return /* @__PURE__ */ jsx(CardContext, {
|
|
1344
|
+
value: {
|
|
1345
|
+
titleId,
|
|
1346
|
+
setTitled
|
|
1347
|
+
},
|
|
1348
|
+
children: /* @__PURE__ */ jsx(Comp, {
|
|
1349
|
+
"aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
|
|
1350
|
+
...props,
|
|
1351
|
+
"data-slot": "card",
|
|
1352
|
+
className: cn("rounded-xl border border-border-strong bg-surface", className)
|
|
1353
|
+
})
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function CardHeader({ title, description, action, headingLevel = 2, className, ...props }) {
|
|
1357
|
+
const { titleId, setTitled } = useContext(CardContext);
|
|
1358
|
+
useLayoutEffect(() => {
|
|
1359
|
+
setTitled(true);
|
|
1360
|
+
return () => setTitled(false);
|
|
1361
|
+
}, [setTitled]);
|
|
1362
|
+
const Heading = headingLevel === 2 ? "h2" : "h3";
|
|
1363
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1364
|
+
...props,
|
|
1365
|
+
"data-slot": "card-header",
|
|
1366
|
+
className: cn("flex items-start justify-between gap-4 px-5 pt-5", className),
|
|
1367
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1368
|
+
className: "min-w-0",
|
|
1369
|
+
children: [/* @__PURE__ */ jsx(Heading, {
|
|
1370
|
+
id: titleId,
|
|
1371
|
+
className: "font-sans text-sm font-medium",
|
|
1372
|
+
children: title
|
|
1373
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
1374
|
+
className: "text-xs text-fg-muted",
|
|
1375
|
+
children: description
|
|
1376
|
+
})]
|
|
1377
|
+
}), action && /* @__PURE__ */ jsx("div", {
|
|
1378
|
+
className: "shrink-0",
|
|
1379
|
+
children: action
|
|
1380
|
+
})]
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
function CardContent({ className, ...props }) {
|
|
1384
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1385
|
+
...props,
|
|
1386
|
+
"data-slot": "card-content",
|
|
1387
|
+
className: cn("p-5 [[data-slot=card-header]+&]:pt-4", className)
|
|
906
1388
|
});
|
|
907
1389
|
}
|
|
908
1390
|
//#endregion
|
|
@@ -960,7 +1442,7 @@ function ChoiceRow({ slot, label, description, error, labelPosition = "end", gap
|
|
|
960
1442
|
return /* @__PURE__ */ jsx("label", {
|
|
961
1443
|
"data-slot": slot,
|
|
962
1444
|
className: cn("relative isolate flex min-h-6 min-w-6 items-start text-sm/6 text-fg", "cursor-pointer has-[input:disabled]:cursor-not-allowed has-[input[aria-busy=true]]:cursor-progress", "has-[input:disabled]:opacity-50", "forced-colors:has-[input:disabled]:text-[GrayText] forced-colors:has-[input:disabled]:opacity-100", "pointer-coarse:before:absolute pointer-coarse:before:top-1/2 pointer-coarse:before:left-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:size-full pointer-coarse:before:min-h-11", "pointer-coarse:before:min-w-11 pointer-coarse:before:-translate-1/2", gap, labelPosition === "start" ? "w-full justify-between" : "w-fit max-w-full", className),
|
|
963
|
-
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment, { children: [controlSlot, text] })
|
|
1445
|
+
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [controlSlot, text] })
|
|
964
1446
|
});
|
|
965
1447
|
}
|
|
966
1448
|
//#endregion
|
|
@@ -1034,52 +1516,561 @@ function Checkbox({ label, description, error, indeterminate = false, className,
|
|
|
1034
1516
|
]
|
|
1035
1517
|
})
|
|
1036
1518
|
});
|
|
1037
|
-
}
|
|
1038
|
-
//#endregion
|
|
1039
|
-
//#region src/components/checkbox/checkbox-group.tsx
|
|
1040
|
-
/** A `<fieldset>` + `<legend>` around related checkboxes; holds no value of its own. */
|
|
1041
|
-
function CheckboxGroup({ legend, description, error, orientation = "vertical", className, "aria-describedby": describedByProp, children, ...props }) {
|
|
1042
|
-
const id = useId();
|
|
1043
|
-
const descriptionId = `${id}-description`;
|
|
1044
|
-
const errorId = `${id}-error`;
|
|
1045
|
-
const hasDescription = isPresent(description);
|
|
1046
|
-
const hasError = Boolean(error) && error !== true && isPresent(error);
|
|
1047
|
-
const describedBy = [
|
|
1048
|
-
describedByProp,
|
|
1049
|
-
hasDescription && descriptionId,
|
|
1050
|
-
hasError && errorId
|
|
1051
|
-
].filter(Boolean).join(" ") || void 0;
|
|
1052
|
-
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1053
|
-
...props,
|
|
1054
|
-
"data-slot": "checkbox-group",
|
|
1055
|
-
"aria-describedby": describedBy,
|
|
1056
|
-
className: cn("group/checkbox-group m-0 min-w-0 border-0 p-0", className),
|
|
1057
|
-
children: [/* @__PURE__ */ jsx("legend", {
|
|
1058
|
-
className: "float-start mb-1.5 w-full p-0 text-sm font-medium text-fg group-disabled/checkbox-group:opacity-50",
|
|
1059
|
-
children: legend
|
|
1060
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1061
|
-
className: "clear-both flex flex-col",
|
|
1062
|
-
children: [
|
|
1063
|
-
hasDescription && /* @__PURE__ */ jsx("p", {
|
|
1064
|
-
id: descriptionId,
|
|
1065
|
-
className: "m-0 -mt-1 mb-2.5 text-xs/snug text-fg-muted group-disabled/checkbox-group:opacity-50",
|
|
1066
|
-
children: description
|
|
1067
|
-
}),
|
|
1068
|
-
/* @__PURE__ */ jsx("div", {
|
|
1069
|
-
"data-orientation": orientation,
|
|
1070
|
-
className: cn("flex", orientation === "vertical" ? "flex-col gap-2" : "flex-row flex-wrap gap-x-6 gap-y-2"),
|
|
1071
|
-
children
|
|
1072
|
-
}),
|
|
1073
|
-
hasError && /* @__PURE__ */ jsxs("p", {
|
|
1074
|
-
id: errorId,
|
|
1075
|
-
className: "m-0 mt-2 flex items-start gap-1 text-xs/snug text-danger",
|
|
1076
|
-
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
1519
|
+
}
|
|
1520
|
+
//#endregion
|
|
1521
|
+
//#region src/components/checkbox/checkbox-group.tsx
|
|
1522
|
+
/** A `<fieldset>` + `<legend>` around related checkboxes; holds no value of its own. */
|
|
1523
|
+
function CheckboxGroup({ legend, description, error, orientation = "vertical", className, "aria-describedby": describedByProp, children, ...props }) {
|
|
1524
|
+
const id = useId();
|
|
1525
|
+
const descriptionId = `${id}-description`;
|
|
1526
|
+
const errorId = `${id}-error`;
|
|
1527
|
+
const hasDescription = isPresent(description);
|
|
1528
|
+
const hasError = Boolean(error) && error !== true && isPresent(error);
|
|
1529
|
+
const describedBy = [
|
|
1530
|
+
describedByProp,
|
|
1531
|
+
hasDescription && descriptionId,
|
|
1532
|
+
hasError && errorId
|
|
1533
|
+
].filter(Boolean).join(" ") || void 0;
|
|
1534
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1535
|
+
...props,
|
|
1536
|
+
"data-slot": "checkbox-group",
|
|
1537
|
+
"aria-describedby": describedBy,
|
|
1538
|
+
className: cn("group/checkbox-group m-0 min-w-0 border-0 p-0", className),
|
|
1539
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
1540
|
+
className: "float-start mb-1.5 w-full p-0 text-sm font-medium text-fg group-disabled/checkbox-group:opacity-50",
|
|
1541
|
+
children: legend
|
|
1542
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1543
|
+
className: "clear-both flex flex-col",
|
|
1544
|
+
children: [
|
|
1545
|
+
hasDescription && /* @__PURE__ */ jsx("p", {
|
|
1546
|
+
id: descriptionId,
|
|
1547
|
+
className: "m-0 -mt-1 mb-2.5 text-xs/snug text-fg-muted group-disabled/checkbox-group:opacity-50",
|
|
1548
|
+
children: description
|
|
1549
|
+
}),
|
|
1550
|
+
/* @__PURE__ */ jsx("div", {
|
|
1551
|
+
"data-orientation": orientation,
|
|
1552
|
+
className: cn("flex", orientation === "vertical" ? "flex-col gap-2" : "flex-row flex-wrap gap-x-6 gap-y-2"),
|
|
1553
|
+
children
|
|
1554
|
+
}),
|
|
1555
|
+
hasError && /* @__PURE__ */ jsxs("p", {
|
|
1556
|
+
id: errorId,
|
|
1557
|
+
className: "m-0 mt-2 flex items-start gap-1 text-xs/snug text-danger",
|
|
1558
|
+
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
1559
|
+
"aria-hidden": true,
|
|
1560
|
+
className: "mt-px size-3.5 shrink-0"
|
|
1561
|
+
}), /* @__PURE__ */ jsx("span", { children: error })]
|
|
1562
|
+
})
|
|
1563
|
+
]
|
|
1564
|
+
})]
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
//#endregion
|
|
1568
|
+
//#region src/components/combobox/types.ts
|
|
1569
|
+
const isGroup$1 = (item) => "options" in item;
|
|
1570
|
+
const normalize = (text) => text.trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase();
|
|
1571
|
+
const defaultFilter = (option, query) => normalize([option.label, ...option.keywords ?? []].join(" ")).includes(normalize(query));
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region src/components/combobox/use-combobox-position.ts
|
|
1574
|
+
const clamp$1 = (n, min, max) => Math.max(min, Math.min(n, Math.max(min, max)));
|
|
1575
|
+
/** Top-layer positioning only: unlike Menu, a combobox never transfers focus into the list. */
|
|
1576
|
+
function useComboboxPosition(open, inputRef, contentRef) {
|
|
1577
|
+
useLayoutEffect(() => {
|
|
1578
|
+
const content = contentRef.current;
|
|
1579
|
+
const anchor = inputRef.current?.closest("[data-slot=\"input-control\"]");
|
|
1580
|
+
if (!open || !content || !anchor) return;
|
|
1581
|
+
content.showPopover?.();
|
|
1582
|
+
const update = () => {
|
|
1583
|
+
const rect = anchor.getBoundingClientRect();
|
|
1584
|
+
const viewport = window.visualViewport;
|
|
1585
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
1586
|
+
const top = viewport?.offsetTop ?? 0;
|
|
1587
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
1588
|
+
const height = viewport?.height ?? document.documentElement.clientHeight;
|
|
1589
|
+
content.style.width = `${Math.min(rect.width, Math.max(0, width - 16))}px`;
|
|
1590
|
+
const below = top + height - rect.bottom - 12;
|
|
1591
|
+
const above = rect.top - top - 12;
|
|
1592
|
+
const side = below < Math.min(content.scrollHeight, 280) && above > below ? "top" : "bottom";
|
|
1593
|
+
content.style.maxHeight = `${Math.max(0, Math.min(280, side === "top" ? above : below))}px`;
|
|
1594
|
+
const w = content.offsetWidth;
|
|
1595
|
+
const h = content.offsetHeight;
|
|
1596
|
+
const rtl = getComputedStyle(anchor).direction === "rtl";
|
|
1597
|
+
content.style.left = `${Math.round(clamp$1(rtl ? rect.right - w : rect.left, left + 8, left + width - w - 8))}px`;
|
|
1598
|
+
content.style.top = `${Math.round(clamp$1(side === "top" ? rect.top - h - 4 : rect.bottom + 4, top + 8, top + height - h - 8))}px`;
|
|
1599
|
+
content.dataset.side = side;
|
|
1600
|
+
};
|
|
1601
|
+
update();
|
|
1602
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(update);
|
|
1603
|
+
observer?.observe(anchor);
|
|
1604
|
+
observer?.observe(content);
|
|
1605
|
+
const mutation = new MutationObserver(update);
|
|
1606
|
+
mutation.observe(content, {
|
|
1607
|
+
childList: true,
|
|
1608
|
+
subtree: true,
|
|
1609
|
+
characterData: true
|
|
1610
|
+
});
|
|
1611
|
+
window.addEventListener("resize", update);
|
|
1612
|
+
window.addEventListener("scroll", update, true);
|
|
1613
|
+
window.visualViewport?.addEventListener("resize", update);
|
|
1614
|
+
window.visualViewport?.addEventListener("scroll", update);
|
|
1615
|
+
return () => {
|
|
1616
|
+
observer?.disconnect();
|
|
1617
|
+
mutation.disconnect();
|
|
1618
|
+
window.removeEventListener("resize", update);
|
|
1619
|
+
window.removeEventListener("scroll", update, true);
|
|
1620
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
1621
|
+
window.visualViewport?.removeEventListener("scroll", update);
|
|
1622
|
+
content.hidePopover?.();
|
|
1623
|
+
};
|
|
1624
|
+
}, [
|
|
1625
|
+
open,
|
|
1626
|
+
inputRef,
|
|
1627
|
+
contentRef
|
|
1628
|
+
]);
|
|
1629
|
+
}
|
|
1630
|
+
//#endregion
|
|
1631
|
+
//#region src/components/combobox/combobox.tsx
|
|
1632
|
+
const resultsText = (count) => `${count} ${count === 1 ? "option" : "options"} available.`;
|
|
1633
|
+
function Combobox({ options, value, defaultValue = null, onValueChange, inputValue, onInputValueChange, open: openProp, defaultOpen = false, onOpenChange, filter = defaultFilter, selectedOption, loading = false, loadingText = "Loading…", searchError, onRetry, retryText = "Retry", emptyText = "No matching options.", clearable = true, openOnFocus = false, clearLabel = "Clear selection", openLabel = "Open options", closeLabel = "Close options", requiredText = "Choose an option from the list.", unresolvedText = "The selected option is unavailable.", getResultsText = resultsText, label, floating, helperText, error, size = "md", start, mono, inputClassName, contentClassName, className, name, form, disabled, readOnly, required, ref, id: idProp, onClick, onFocus, onBlur, onKeyDown, onCompositionStart, onCompositionEnd, onInvalid, ...props }) {
|
|
1634
|
+
const autoId = useId();
|
|
1635
|
+
const id = idProp ?? `${autoId}-input`;
|
|
1636
|
+
const listId = `${autoId}-list`;
|
|
1637
|
+
const rootRef = useRef(null);
|
|
1638
|
+
const inputRef = useRef(null);
|
|
1639
|
+
const contentRef = useRef(null);
|
|
1640
|
+
const hiddenRef = useRef(null);
|
|
1641
|
+
const composing = useRef(false);
|
|
1642
|
+
const skipFocusOpen = useRef(false);
|
|
1643
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1644
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
1645
|
+
const [draft, setDraft] = useState(null);
|
|
1646
|
+
const [dirty, setDirty] = useState(false);
|
|
1647
|
+
const [compositionText, setCompositionText] = useState(null);
|
|
1648
|
+
const [active, setActive] = useState(null);
|
|
1649
|
+
const [nativeInvalid, setNativeInvalid] = useState(false);
|
|
1650
|
+
const selectedValue = value === void 0 ? internalValue : value;
|
|
1651
|
+
const open = !disabled && !readOnly && (openProp ?? internalOpen);
|
|
1652
|
+
const flat = useMemo(() => {
|
|
1653
|
+
const items = options.flatMap((item) => isGroup$1(item) ? [...item.options] : [item]);
|
|
1654
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1655
|
+
for (const item of items) {
|
|
1656
|
+
if (!item.value || seen.has(item.value)) throw new Error("Combobox option values must be unique, non-empty strings");
|
|
1657
|
+
seen.add(item.value);
|
|
1658
|
+
}
|
|
1659
|
+
return items;
|
|
1660
|
+
}, [options]);
|
|
1661
|
+
const resolved = flat.find((item) => item.value === selectedValue) ?? (selectedOption?.value === selectedValue ? selectedOption : null);
|
|
1662
|
+
const [cached, setCached] = useState(resolved);
|
|
1663
|
+
const selected = resolved ?? (cached?.value === selectedValue ? cached : null);
|
|
1664
|
+
const [initialOption, setInitialOption] = useState(resolved?.value === defaultValue ? resolved : null);
|
|
1665
|
+
if (resolved?.value === defaultValue && resolved !== initialOption) setInitialOption(resolved);
|
|
1666
|
+
const text = compositionText !== null ? draft ?? inputValue ?? selected?.label ?? "" : inputValue ?? draft ?? selected?.label ?? "";
|
|
1667
|
+
const query = dirty ? compositionText ?? text : "";
|
|
1668
|
+
const blocked = loading || Boolean(searchError);
|
|
1669
|
+
const [previousResults, setPreviousResults] = useState({
|
|
1670
|
+
options,
|
|
1671
|
+
blocked,
|
|
1672
|
+
filter
|
|
1673
|
+
});
|
|
1674
|
+
if (previousResults.options !== options || previousResults.blocked !== blocked || previousResults.filter !== filter) {
|
|
1675
|
+
setPreviousResults({
|
|
1676
|
+
options,
|
|
1677
|
+
blocked,
|
|
1678
|
+
filter
|
|
1679
|
+
});
|
|
1680
|
+
setActive(null);
|
|
1681
|
+
}
|
|
1682
|
+
const visible = flat.filter((item) => filter === false || !query || filter(item, query));
|
|
1683
|
+
const activeItem = !blocked && active?.options === options && active.query === query ? visible.find((item) => item.value === active.value && !item.disabled) : void 0;
|
|
1684
|
+
const optionId = (item) => `${autoId}-option-${encodeURIComponent(item.value)}`;
|
|
1685
|
+
const activeId = open && activeItem ? optionId(activeItem) : void 0;
|
|
1686
|
+
const invalidMessage = selectedValue !== null && !selected ? unresolvedText : required && selectedValue === null ? requiredText : "";
|
|
1687
|
+
const fieldError = error || (nativeInvalid && invalidMessage ? invalidMessage : void 0);
|
|
1688
|
+
const count = visible.filter((item) => !item.disabled).length;
|
|
1689
|
+
const status = open ? searchError ? typeof searchError === "string" ? searchError : "Unable to load options." : loading ? loadingText : getResultsText(count) : "";
|
|
1690
|
+
const [previousSelection, setPreviousSelection] = useState({
|
|
1691
|
+
value: selectedValue,
|
|
1692
|
+
label: selected?.label
|
|
1693
|
+
});
|
|
1694
|
+
if (resolved && resolved !== cached) setCached(resolved);
|
|
1695
|
+
if (previousSelection.value !== selectedValue || previousSelection.label !== selected?.label) {
|
|
1696
|
+
setPreviousSelection({
|
|
1697
|
+
value: selectedValue,
|
|
1698
|
+
label: selected?.label
|
|
1699
|
+
});
|
|
1700
|
+
setDraft(null);
|
|
1701
|
+
setDirty(false);
|
|
1702
|
+
setActive(null);
|
|
1703
|
+
}
|
|
1704
|
+
if (nativeInvalid && !invalidMessage) setNativeInvalid(false);
|
|
1705
|
+
useLayoutEffect(() => {
|
|
1706
|
+
inputRef.current?.setCustomValidity(disabled || readOnly ? "" : invalidMessage);
|
|
1707
|
+
}, [
|
|
1708
|
+
invalidMessage,
|
|
1709
|
+
disabled,
|
|
1710
|
+
readOnly
|
|
1711
|
+
]);
|
|
1712
|
+
useComboboxPosition(open, inputRef, contentRef);
|
|
1713
|
+
useLayoutEffect(() => {
|
|
1714
|
+
if (activeId) document.getElementById(activeId)?.scrollIntoView?.({ block: "nearest" });
|
|
1715
|
+
}, [activeId]);
|
|
1716
|
+
const updateText = (next, reason, fallback = false) => {
|
|
1717
|
+
setDraft(fallback ? null : next);
|
|
1718
|
+
if (next !== text || reason === "input") onInputValueChange?.(next, { reason });
|
|
1719
|
+
};
|
|
1720
|
+
const changeOpen = (next, reason) => {
|
|
1721
|
+
if (openProp === void 0) setInternalOpen(next);
|
|
1722
|
+
if (next !== open) onOpenChange?.(next, { reason });
|
|
1723
|
+
};
|
|
1724
|
+
const close = (reason) => {
|
|
1725
|
+
changeOpen(false, reason);
|
|
1726
|
+
updateText(selected?.label ?? "", "revert", true);
|
|
1727
|
+
setDirty(false);
|
|
1728
|
+
setActive(null);
|
|
1729
|
+
};
|
|
1730
|
+
const show = (reason, direction) => {
|
|
1731
|
+
if (disabled || readOnly) return;
|
|
1732
|
+
changeOpen(true, reason);
|
|
1733
|
+
setDirty(false);
|
|
1734
|
+
const items = flat.filter((item) => !item.disabled);
|
|
1735
|
+
const initial = items.find((item) => item.value === selectedValue) ?? (direction === 1 ? items[0] : direction === -1 ? items.at(-1) : void 0);
|
|
1736
|
+
setActive(initial ? {
|
|
1737
|
+
value: initial.value,
|
|
1738
|
+
options,
|
|
1739
|
+
query: ""
|
|
1740
|
+
} : null);
|
|
1741
|
+
};
|
|
1742
|
+
const focusInput = () => {
|
|
1743
|
+
skipFocusOpen.current = true;
|
|
1744
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
1745
|
+
skipFocusOpen.current = false;
|
|
1746
|
+
};
|
|
1747
|
+
const clear = (reason) => {
|
|
1748
|
+
if (value === void 0) setInternalValue(null);
|
|
1749
|
+
if (selectedValue !== null) onValueChange?.(null, {
|
|
1750
|
+
reason: "clear",
|
|
1751
|
+
option: null
|
|
1752
|
+
});
|
|
1753
|
+
updateText("", reason);
|
|
1754
|
+
setDirty(true);
|
|
1755
|
+
setActive(null);
|
|
1756
|
+
changeOpen(true, "clear");
|
|
1757
|
+
};
|
|
1758
|
+
const edit = (next) => {
|
|
1759
|
+
if (disabled || readOnly) return;
|
|
1760
|
+
if (next === "") clear("input");
|
|
1761
|
+
else {
|
|
1762
|
+
updateText(next, "input");
|
|
1763
|
+
setDirty(true);
|
|
1764
|
+
setActive(null);
|
|
1765
|
+
changeOpen(true, "input");
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
const choose = (item) => {
|
|
1769
|
+
if (blocked || disabled || readOnly || item.disabled) return;
|
|
1770
|
+
setCached(item);
|
|
1771
|
+
if (value === void 0) setInternalValue(item.value);
|
|
1772
|
+
if (selectedValue !== item.value) onValueChange?.(item.value, {
|
|
1773
|
+
reason: "select",
|
|
1774
|
+
option: item
|
|
1775
|
+
});
|
|
1776
|
+
updateText(item.label, "selection", true);
|
|
1777
|
+
setDirty(false);
|
|
1778
|
+
setActive(null);
|
|
1779
|
+
changeOpen(false, "select");
|
|
1780
|
+
focusInput();
|
|
1781
|
+
};
|
|
1782
|
+
const dismiss = useEffectEvent(close);
|
|
1783
|
+
useLayoutEffect(() => {
|
|
1784
|
+
if (!open) return;
|
|
1785
|
+
const outside = (event) => {
|
|
1786
|
+
if (!event.composedPath().includes(rootRef.current)) dismiss("outside");
|
|
1787
|
+
};
|
|
1788
|
+
document.addEventListener("pointerdown", outside, true);
|
|
1789
|
+
return () => {
|
|
1790
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
1791
|
+
};
|
|
1792
|
+
}, [open]);
|
|
1793
|
+
const wasOpen = useRef(open);
|
|
1794
|
+
const settle = useEffectEvent(() => {
|
|
1795
|
+
updateText(selected?.label ?? "", "revert", true);
|
|
1796
|
+
setDirty(false);
|
|
1797
|
+
setActive(null);
|
|
1798
|
+
});
|
|
1799
|
+
useLayoutEffect(() => {
|
|
1800
|
+
if (wasOpen.current && !open) settle();
|
|
1801
|
+
wasOpen.current = open;
|
|
1802
|
+
}, [open]);
|
|
1803
|
+
const reset = useEffectEvent(() => {
|
|
1804
|
+
const nextValue = value === void 0 ? defaultValue : value;
|
|
1805
|
+
const nextOption = flat.find((item) => item.value === nextValue) ?? (selectedOption?.value === nextValue ? selectedOption : null) ?? (cached?.value === nextValue ? cached : null) ?? (initialOption?.value === nextValue ? initialOption : null);
|
|
1806
|
+
if (nextOption) setCached(nextOption);
|
|
1807
|
+
const nextText = inputValue ?? nextOption?.label ?? "";
|
|
1808
|
+
if (value === void 0) setInternalValue(nextValue);
|
|
1809
|
+
if (openProp === void 0) setInternalOpen(false);
|
|
1810
|
+
updateText(nextText, "reset", true);
|
|
1811
|
+
setDirty(false);
|
|
1812
|
+
setCompositionText(null);
|
|
1813
|
+
composing.current = false;
|
|
1814
|
+
setActive(null);
|
|
1815
|
+
setNativeInvalid(false);
|
|
1816
|
+
if (inputRef.current) inputRef.current.value = nextText;
|
|
1817
|
+
if (hiddenRef.current) hiddenRef.current.value = nextValue ?? "";
|
|
1818
|
+
});
|
|
1819
|
+
useLayoutEffect(() => {
|
|
1820
|
+
const owner = inputRef.current?.form;
|
|
1821
|
+
if (!owner) return;
|
|
1822
|
+
let timer;
|
|
1823
|
+
const handleReset = (event) => {
|
|
1824
|
+
clearTimeout(timer);
|
|
1825
|
+
timer = setTimeout(() => {
|
|
1826
|
+
if (!event.defaultPrevented) reset();
|
|
1827
|
+
}, 0);
|
|
1828
|
+
};
|
|
1829
|
+
owner.addEventListener("reset", handleReset);
|
|
1830
|
+
return () => {
|
|
1831
|
+
clearTimeout(timer);
|
|
1832
|
+
owner.removeEventListener("reset", handleReset);
|
|
1833
|
+
};
|
|
1834
|
+
}, [form]);
|
|
1835
|
+
const renderOption = (item) => /* @__PURE__ */ jsxs("div", {
|
|
1836
|
+
id: optionId(item),
|
|
1837
|
+
role: "option",
|
|
1838
|
+
"aria-selected": item.value === selectedValue,
|
|
1839
|
+
"aria-disabled": item.disabled || void 0,
|
|
1840
|
+
"aria-labelledby": `${optionId(item)}-label`,
|
|
1841
|
+
"aria-describedby": item.description ? `${optionId(item)}-description` : void 0,
|
|
1842
|
+
"data-slot": "combobox-option",
|
|
1843
|
+
"data-active": activeItem?.value === item.value ? "" : void 0,
|
|
1844
|
+
className: cn("flex min-h-8.5 cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm/5 text-fg outline-none select-none pointer-coarse:min-h-11", "data-active:bg-popover-highlight data-active:outline-1 data-active:-outline-offset-1 data-active:outline-input-border", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 forced-colors:data-active:outline-[Highlight]"),
|
|
1845
|
+
onPointerMove: (event) => {
|
|
1846
|
+
if (event.pointerType !== "touch" && !item.disabled) setActive({
|
|
1847
|
+
value: item.value,
|
|
1848
|
+
options,
|
|
1849
|
+
query
|
|
1850
|
+
});
|
|
1851
|
+
},
|
|
1852
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1853
|
+
onClick: () => choose(item),
|
|
1854
|
+
children: [
|
|
1855
|
+
item.icon && /* @__PURE__ */ jsx("span", {
|
|
1856
|
+
"aria-hidden": true,
|
|
1857
|
+
className: "shrink-0 text-fg-muted [&_svg]:size-4",
|
|
1858
|
+
children: item.icon
|
|
1859
|
+
}),
|
|
1860
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1861
|
+
className: "min-w-0 flex-1 wrap-anywhere",
|
|
1862
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1863
|
+
id: `${optionId(item)}-label`,
|
|
1864
|
+
children: item.label
|
|
1865
|
+
}), item.description && /* @__PURE__ */ jsx("span", {
|
|
1866
|
+
id: `${optionId(item)}-description`,
|
|
1867
|
+
className: "mt-0.5 block text-xs/snug text-fg-muted",
|
|
1868
|
+
children: item.description
|
|
1869
|
+
})]
|
|
1870
|
+
}),
|
|
1871
|
+
/* @__PURE__ */ jsx(Check, {
|
|
1872
|
+
"aria-hidden": true,
|
|
1873
|
+
className: cn("size-4 shrink-0", item.value !== selectedValue && "invisible")
|
|
1874
|
+
})
|
|
1875
|
+
]
|
|
1876
|
+
}, item.value);
|
|
1877
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1878
|
+
ref: rootRef,
|
|
1879
|
+
onBlur: (event) => {
|
|
1880
|
+
if ((open || dirty) && !rootRef.current?.contains(event.relatedTarget)) close("blur");
|
|
1881
|
+
},
|
|
1882
|
+
"data-slot": "combobox",
|
|
1883
|
+
"data-state": open ? "open" : "closed",
|
|
1884
|
+
className: cn("w-full", className),
|
|
1885
|
+
children: [
|
|
1886
|
+
/* @__PURE__ */ jsx(FieldFrame, {
|
|
1887
|
+
...props,
|
|
1888
|
+
ref,
|
|
1889
|
+
inputRef,
|
|
1890
|
+
id,
|
|
1891
|
+
type: "text",
|
|
1892
|
+
form,
|
|
1893
|
+
label,
|
|
1894
|
+
floating,
|
|
1895
|
+
helperText,
|
|
1896
|
+
error: fieldError,
|
|
1897
|
+
required,
|
|
1898
|
+
disabled,
|
|
1899
|
+
readOnly,
|
|
1900
|
+
size,
|
|
1901
|
+
start,
|
|
1902
|
+
mono,
|
|
1903
|
+
value: text,
|
|
1904
|
+
role: "combobox",
|
|
1905
|
+
"aria-autocomplete": "list",
|
|
1906
|
+
"aria-expanded": open,
|
|
1907
|
+
"aria-controls": open ? listId : void 0,
|
|
1908
|
+
"aria-activedescendant": activeId,
|
|
1909
|
+
autoComplete: props.autoComplete ?? "off",
|
|
1910
|
+
status,
|
|
1911
|
+
inputClassName,
|
|
1912
|
+
className: "pointer-coarse:**:data-[slot=input-control]:min-h-11",
|
|
1913
|
+
onChange: (event) => {
|
|
1914
|
+
if (composing.current) setDraft(event.currentTarget.value);
|
|
1915
|
+
else edit(event.currentTarget.value);
|
|
1916
|
+
},
|
|
1917
|
+
onCompositionStart: (event) => {
|
|
1918
|
+
onCompositionStart?.(event);
|
|
1919
|
+
composing.current = true;
|
|
1920
|
+
setCompositionText(query);
|
|
1921
|
+
},
|
|
1922
|
+
onCompositionEnd: (event) => {
|
|
1923
|
+
onCompositionEnd?.(event);
|
|
1924
|
+
composing.current = false;
|
|
1925
|
+
setCompositionText(null);
|
|
1926
|
+
edit(event.currentTarget.value);
|
|
1927
|
+
},
|
|
1928
|
+
onClick: (event) => {
|
|
1929
|
+
onClick?.(event);
|
|
1930
|
+
if (!event.defaultPrevented && !open) show("trigger");
|
|
1931
|
+
},
|
|
1932
|
+
onFocus: (event) => {
|
|
1933
|
+
onFocus?.(event);
|
|
1934
|
+
if (!event.defaultPrevented && openOnFocus && !open && !skipFocusOpen.current) show("input");
|
|
1935
|
+
},
|
|
1936
|
+
onBlur,
|
|
1937
|
+
onInvalid: (event) => {
|
|
1938
|
+
setNativeInvalid(true);
|
|
1939
|
+
onInvalid?.(event);
|
|
1940
|
+
},
|
|
1941
|
+
onKeyDown: (event) => {
|
|
1942
|
+
onKeyDown?.(event);
|
|
1943
|
+
if (event.defaultPrevented || disabled || readOnly || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
|
|
1944
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1945
|
+
event.preventDefault();
|
|
1946
|
+
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
1947
|
+
if (!open) {
|
|
1948
|
+
show("keyboard", direction);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
if (blocked) return;
|
|
1952
|
+
const enabled = visible.filter((item) => !item.disabled);
|
|
1953
|
+
const index = enabled.findIndex((item) => item.value === activeItem?.value);
|
|
1954
|
+
const next = index < 0 ? direction === 1 ? enabled[0] : enabled.at(-1) : enabled[index + direction];
|
|
1955
|
+
if (next) setActive({
|
|
1956
|
+
value: next.value,
|
|
1957
|
+
options,
|
|
1958
|
+
query
|
|
1959
|
+
});
|
|
1960
|
+
} else if (event.key === "Enter" && open) {
|
|
1961
|
+
event.preventDefault();
|
|
1962
|
+
if (activeItem) choose(activeItem);
|
|
1963
|
+
} else if (event.key === "Escape" && open) {
|
|
1964
|
+
event.preventDefault();
|
|
1965
|
+
event.stopPropagation();
|
|
1966
|
+
close("escape");
|
|
1967
|
+
} else if (event.key === "Tab" && !(searchError && onRetry)) close("blur");
|
|
1968
|
+
},
|
|
1969
|
+
actions: !readOnly && /* @__PURE__ */ jsxs(Fragment$1, { children: [clearable && (text !== "" || selectedValue !== null) && /* @__PURE__ */ jsx(Button, {
|
|
1970
|
+
type: "button",
|
|
1971
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1972
|
+
"aria-label": clearLabel,
|
|
1973
|
+
intent: "ghost",
|
|
1974
|
+
size,
|
|
1975
|
+
disabled,
|
|
1976
|
+
className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
|
|
1977
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1978
|
+
onClick: () => {
|
|
1979
|
+
clear("clear");
|
|
1980
|
+
focusInput();
|
|
1981
|
+
}
|
|
1982
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1983
|
+
type: "button",
|
|
1984
|
+
icon: /* @__PURE__ */ jsx(ChevronDown, {}),
|
|
1985
|
+
tabIndex: -1,
|
|
1986
|
+
"aria-label": open ? closeLabel : openLabel,
|
|
1987
|
+
"aria-expanded": open,
|
|
1988
|
+
"aria-controls": open ? listId : void 0,
|
|
1989
|
+
disabled,
|
|
1990
|
+
intent: "ghost",
|
|
1991
|
+
size,
|
|
1992
|
+
className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
|
|
1993
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1994
|
+
onClick: () => {
|
|
1995
|
+
focusInput();
|
|
1996
|
+
if (open) close("trigger");
|
|
1997
|
+
else show("trigger");
|
|
1998
|
+
}
|
|
1999
|
+
})] })
|
|
2000
|
+
}),
|
|
2001
|
+
name && /* @__PURE__ */ jsx("input", {
|
|
2002
|
+
ref: hiddenRef,
|
|
2003
|
+
type: "hidden",
|
|
2004
|
+
name,
|
|
2005
|
+
form,
|
|
2006
|
+
disabled,
|
|
2007
|
+
value: selectedValue ?? ""
|
|
2008
|
+
}),
|
|
2009
|
+
open && /* @__PURE__ */ jsxs("div", {
|
|
2010
|
+
ref: contentRef,
|
|
2011
|
+
popover: "manual",
|
|
2012
|
+
"data-slot": "combobox-content",
|
|
2013
|
+
className: cn("fixed inset-auto z-50 m-0 box-border max-h-70 overflow-y-auto rounded-xl border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none scrollbar-subtle", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", contentClassName),
|
|
2014
|
+
onKeyDown: (event) => {
|
|
2015
|
+
if (event.key === "Escape") {
|
|
2016
|
+
event.preventDefault();
|
|
2017
|
+
event.stopPropagation();
|
|
2018
|
+
close("escape");
|
|
2019
|
+
focusInput();
|
|
2020
|
+
}
|
|
2021
|
+
},
|
|
2022
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2023
|
+
id: listId,
|
|
2024
|
+
role: "listbox",
|
|
2025
|
+
"aria-label": label ?? props["aria-label"],
|
|
2026
|
+
"aria-busy": loading || void 0,
|
|
2027
|
+
children: !blocked && options.map((item, index) => {
|
|
2028
|
+
if (!isGroup$1(item)) return visible.includes(item) ? renderOption(item) : null;
|
|
2029
|
+
const members = item.options.filter((option) => visible.includes(option));
|
|
2030
|
+
if (!members.length) return null;
|
|
2031
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2032
|
+
role: "group",
|
|
2033
|
+
"aria-labelledby": `${autoId}-group-${index}`,
|
|
2034
|
+
"data-slot": "combobox-group",
|
|
2035
|
+
className: "not-first:mt-1 not-first:border-t not-first:border-popover-border not-first:pt-1",
|
|
2036
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2037
|
+
id: `${autoId}-group-${index}`,
|
|
2038
|
+
className: "px-2 pt-1.5 pb-1 text-xs font-medium text-fg-muted",
|
|
2039
|
+
children: item.label
|
|
2040
|
+
}), members.map(renderOption)]
|
|
2041
|
+
}, `${item.label}-${index}`);
|
|
2042
|
+
})
|
|
2043
|
+
}), searchError ? /* @__PURE__ */ jsxs("div", {
|
|
2044
|
+
className: "flex items-start gap-2 p-3 text-xs text-danger",
|
|
2045
|
+
children: [
|
|
2046
|
+
/* @__PURE__ */ jsx(CircleAlert, {
|
|
2047
|
+
"aria-hidden": true,
|
|
2048
|
+
className: "mt-px size-3.5 shrink-0"
|
|
2049
|
+
}),
|
|
2050
|
+
/* @__PURE__ */ jsx("span", {
|
|
2051
|
+
className: "flex-1",
|
|
2052
|
+
children: searchError
|
|
2053
|
+
}),
|
|
2054
|
+
onRetry && /* @__PURE__ */ jsx(Button, {
|
|
2055
|
+
type: "button",
|
|
2056
|
+
size: "sm",
|
|
2057
|
+
intent: "secondary",
|
|
2058
|
+
onClick: onRetry,
|
|
2059
|
+
children: retryText
|
|
2060
|
+
})
|
|
2061
|
+
]
|
|
2062
|
+
}) : loading ? /* @__PURE__ */ jsxs("div", {
|
|
2063
|
+
className: "flex items-center justify-center gap-2 p-4 text-sm text-fg-muted",
|
|
2064
|
+
children: [/* @__PURE__ */ jsx(LoaderCircle, {
|
|
1077
2065
|
"aria-hidden": true,
|
|
1078
|
-
className: "
|
|
1079
|
-
}),
|
|
1080
|
-
})
|
|
1081
|
-
|
|
1082
|
-
|
|
2066
|
+
className: "size-4 animate-spin motion-reduce:[animation-duration:2.5s]"
|
|
2067
|
+
}), loadingText]
|
|
2068
|
+
}) : visible.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
2069
|
+
className: "p-4 text-center text-sm text-fg-muted",
|
|
2070
|
+
children: emptyText
|
|
2071
|
+
}) : null]
|
|
2072
|
+
})
|
|
2073
|
+
]
|
|
1083
2074
|
});
|
|
1084
2075
|
}
|
|
1085
2076
|
//#endregion
|
|
@@ -1257,7 +2248,7 @@ function ModalShell({ kind, open, onOpenChange, alert = false, dismissible = tru
|
|
|
1257
2248
|
onMouseDown: handleMouseDown,
|
|
1258
2249
|
onClick: handleClick,
|
|
1259
2250
|
className,
|
|
1260
|
-
children: rendered && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2251
|
+
children: rendered && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1261
2252
|
hidden: true,
|
|
1262
2253
|
ref: showOnAttach
|
|
1263
2254
|
}, closeCount), children] })
|
|
@@ -1493,6 +2484,326 @@ function DrawerClose({ size = "sm", icon, ...props }) {
|
|
|
1493
2484
|
});
|
|
1494
2485
|
}
|
|
1495
2486
|
//#endregion
|
|
2487
|
+
//#region src/components/dropdown-menu/dropdown-menu.tsx
|
|
2488
|
+
const MenuContext = createContext(null);
|
|
2489
|
+
function useMenu() {
|
|
2490
|
+
const value = use(MenuContext);
|
|
2491
|
+
if (!value) throw new Error("DropdownMenu parts must be inside DropdownMenu");
|
|
2492
|
+
return value;
|
|
2493
|
+
}
|
|
2494
|
+
function DropdownMenu({ children, open: controlled, defaultOpen = false, onOpenChange }) {
|
|
2495
|
+
const [internal, setInternal] = useState(defaultOpen);
|
|
2496
|
+
const id = useId();
|
|
2497
|
+
const trigger = useRef(null);
|
|
2498
|
+
const content = useRef(null);
|
|
2499
|
+
const initialFocus = useRef("first");
|
|
2500
|
+
const reason = useRef("trigger");
|
|
2501
|
+
return /* @__PURE__ */ jsx(MenuContext, {
|
|
2502
|
+
value: {
|
|
2503
|
+
open: controlled ?? internal,
|
|
2504
|
+
triggerId: `${id}-trigger`,
|
|
2505
|
+
contentId: `${id}-menu`,
|
|
2506
|
+
trigger,
|
|
2507
|
+
content,
|
|
2508
|
+
initialFocus,
|
|
2509
|
+
reason,
|
|
2510
|
+
change(next, why, focus = "first") {
|
|
2511
|
+
if (next) initialFocus.current = focus;
|
|
2512
|
+
reason.current = why;
|
|
2513
|
+
if (controlled === void 0) setInternal(next);
|
|
2514
|
+
onOpenChange?.(next, why);
|
|
2515
|
+
}
|
|
2516
|
+
},
|
|
2517
|
+
children
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
function DropdownMenuTrigger({ asChild = false, ref, onClick, onClickCapture, onKeyDown, disabled, ...props }) {
|
|
2521
|
+
const menu = useMenu();
|
|
2522
|
+
const mergedRef = useMergedRef(menu.trigger, ref);
|
|
2523
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
2524
|
+
...props,
|
|
2525
|
+
ref: mergedRef,
|
|
2526
|
+
id: menu.triggerId,
|
|
2527
|
+
type: "button",
|
|
2528
|
+
"data-slot": "dropdown-menu-trigger",
|
|
2529
|
+
"data-state": menu.open ? "open" : "closed",
|
|
2530
|
+
disabled: asChild ? disabled : void 0,
|
|
2531
|
+
"aria-haspopup": "menu",
|
|
2532
|
+
"aria-expanded": menu.open,
|
|
2533
|
+
"aria-controls": menu.open ? menu.contentId : void 0,
|
|
2534
|
+
"aria-disabled": disabled || props["aria-disabled"] || void 0,
|
|
2535
|
+
onClickCapture: (event) => {
|
|
2536
|
+
if (disabled) {
|
|
2537
|
+
event.preventDefault();
|
|
2538
|
+
event.stopPropagation();
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
onClickCapture?.(event);
|
|
2542
|
+
},
|
|
2543
|
+
onClick: (event) => {
|
|
2544
|
+
onClick?.(event);
|
|
2545
|
+
if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
|
|
2546
|
+
menu.change(!menu.open, "trigger");
|
|
2547
|
+
},
|
|
2548
|
+
onKeyDown: (event) => {
|
|
2549
|
+
onKeyDown?.(event);
|
|
2550
|
+
if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
|
|
2551
|
+
if (["ArrowDown", "ArrowUp"].includes(event.key)) {
|
|
2552
|
+
event.preventDefault();
|
|
2553
|
+
menu.change(true, "trigger", event.key === "ArrowUp" ? "last" : "first");
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
const opposite = {
|
|
2559
|
+
top: "bottom",
|
|
2560
|
+
bottom: "top",
|
|
2561
|
+
left: "right",
|
|
2562
|
+
right: "left"
|
|
2563
|
+
};
|
|
2564
|
+
const clamp = (value, low, high) => Math.max(low, Math.min(value, Math.max(low, high)));
|
|
2565
|
+
function items(content) {
|
|
2566
|
+
return Array.from(content.querySelectorAll("[role=\"menuitem\"]")).filter((item) => !item.hidden && item.getAttribute("aria-hidden") !== "true");
|
|
2567
|
+
}
|
|
2568
|
+
function DropdownMenuContent(props) {
|
|
2569
|
+
const { open } = useMenu();
|
|
2570
|
+
return open ? /* @__PURE__ */ jsx(OpenContent, { ...props }) : null;
|
|
2571
|
+
}
|
|
2572
|
+
function OpenContent({ side = "bottom", align = "end", sideOffset = 6, className, ref, onKeyDown, style, ...props }) {
|
|
2573
|
+
const menu = useMenu();
|
|
2574
|
+
const { content: contentRef, trigger: triggerRef, initialFocus, reason: closeReason } = menu;
|
|
2575
|
+
const mergedRef = useMergedRef(contentRef, ref);
|
|
2576
|
+
const query = useRef({
|
|
2577
|
+
text: "",
|
|
2578
|
+
time: 0
|
|
2579
|
+
});
|
|
2580
|
+
const dismiss = useEffectEvent((reason) => menu.change(false, reason));
|
|
2581
|
+
useLayoutEffect(() => {
|
|
2582
|
+
const content = contentRef.current;
|
|
2583
|
+
const trigger = triggerRef.current;
|
|
2584
|
+
if (!content || !trigger) return;
|
|
2585
|
+
content.showPopover?.();
|
|
2586
|
+
const entries = items(content);
|
|
2587
|
+
((initialFocus.current === "last" ? entries.at(-1) : entries[0]) ?? content).focus({ preventScroll: true });
|
|
2588
|
+
const outside = (event) => {
|
|
2589
|
+
const path = event.composedPath();
|
|
2590
|
+
if (!path.includes(content) && !path.includes(trigger)) dismiss("outside");
|
|
2591
|
+
};
|
|
2592
|
+
const focusOutside = (event) => {
|
|
2593
|
+
if (closeReason.current === "tab") return;
|
|
2594
|
+
if (!content.contains(event.target) && !trigger.contains(event.target)) dismiss("outside");
|
|
2595
|
+
};
|
|
2596
|
+
const reasonRef = closeReason;
|
|
2597
|
+
document.addEventListener("pointerdown", outside, true);
|
|
2598
|
+
document.addEventListener("focusin", focusOutside);
|
|
2599
|
+
return () => {
|
|
2600
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
2601
|
+
document.removeEventListener("focusin", focusOutside);
|
|
2602
|
+
const restore = content.contains(document.activeElement) && !["outside", "tab"].includes(reasonRef.current);
|
|
2603
|
+
content.hidePopover?.();
|
|
2604
|
+
if (restore && trigger.isConnected) trigger.focus({ preventScroll: true });
|
|
2605
|
+
};
|
|
2606
|
+
}, [
|
|
2607
|
+
contentRef,
|
|
2608
|
+
triggerRef,
|
|
2609
|
+
initialFocus,
|
|
2610
|
+
closeReason
|
|
2611
|
+
]);
|
|
2612
|
+
useLayoutEffect(() => {
|
|
2613
|
+
const content = contentRef.current;
|
|
2614
|
+
const trigger = triggerRef.current;
|
|
2615
|
+
if (!content || !trigger) return;
|
|
2616
|
+
const update = () => {
|
|
2617
|
+
const rect = trigger.getBoundingClientRect();
|
|
2618
|
+
const viewport = window.visualViewport;
|
|
2619
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
2620
|
+
const top = viewport?.offsetTop ?? 0;
|
|
2621
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
2622
|
+
const height = viewport?.height ?? document.documentElement.clientHeight;
|
|
2623
|
+
const gap = Math.max(0, sideOffset);
|
|
2624
|
+
content.style.maxWidth = `${Math.max(0, width - 16)}px`;
|
|
2625
|
+
content.style.maxHeight = `${Math.max(0, height - 16)}px`;
|
|
2626
|
+
const room = {
|
|
2627
|
+
top: rect.top - top,
|
|
2628
|
+
bottom: top + height - rect.bottom,
|
|
2629
|
+
left: rect.left - left,
|
|
2630
|
+
right: left + width - rect.right
|
|
2631
|
+
};
|
|
2632
|
+
const vertical = side === "top" || side === "bottom";
|
|
2633
|
+
const needed = (vertical ? content.scrollHeight : content.offsetWidth) + gap + 8;
|
|
2634
|
+
const actual = room[side] < needed && room[opposite[side]] > room[side] ? opposite[side] : side;
|
|
2635
|
+
if (vertical) content.style.maxHeight = `${Math.max(0, room[actual] - gap - 8)}px`;
|
|
2636
|
+
else content.style.maxWidth = `${Math.max(0, room[actual] - gap - 8)}px`;
|
|
2637
|
+
const w = content.offsetWidth;
|
|
2638
|
+
const h = content.offsetHeight;
|
|
2639
|
+
const rtl = getComputedStyle(trigger).direction === "rtl";
|
|
2640
|
+
const fraction = {
|
|
2641
|
+
start: 0,
|
|
2642
|
+
center: .5,
|
|
2643
|
+
end: 1
|
|
2644
|
+
}[align];
|
|
2645
|
+
const horizontalFraction = rtl ? 1 - fraction : fraction;
|
|
2646
|
+
const x = vertical ? rect.left + (rect.width - w) * horizontalFraction : actual === "left" ? rect.left - w - gap : rect.right + gap;
|
|
2647
|
+
const y = vertical ? actual === "top" ? rect.top - h - gap : rect.bottom + gap : rect.top + (rect.height - h) * fraction;
|
|
2648
|
+
content.style.left = `${Math.round(clamp(x, left + 8, left + width - w - 8))}px`;
|
|
2649
|
+
content.style.top = `${Math.round(clamp(y, top + 8, top + height - h - 8))}px`;
|
|
2650
|
+
content.dataset.side = actual;
|
|
2651
|
+
};
|
|
2652
|
+
update();
|
|
2653
|
+
const observer = new ResizeObserver(update);
|
|
2654
|
+
observer.observe(trigger);
|
|
2655
|
+
observer.observe(content);
|
|
2656
|
+
window.addEventListener("resize", update);
|
|
2657
|
+
window.addEventListener("scroll", update, true);
|
|
2658
|
+
window.visualViewport?.addEventListener("resize", update);
|
|
2659
|
+
window.visualViewport?.addEventListener("scroll", update);
|
|
2660
|
+
return () => {
|
|
2661
|
+
observer.disconnect();
|
|
2662
|
+
window.removeEventListener("resize", update);
|
|
2663
|
+
window.removeEventListener("scroll", update, true);
|
|
2664
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
2665
|
+
window.visualViewport?.removeEventListener("scroll", update);
|
|
2666
|
+
};
|
|
2667
|
+
}, [
|
|
2668
|
+
side,
|
|
2669
|
+
align,
|
|
2670
|
+
sideOffset,
|
|
2671
|
+
contentRef,
|
|
2672
|
+
triggerRef
|
|
2673
|
+
]);
|
|
2674
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2675
|
+
...props,
|
|
2676
|
+
ref: mergedRef,
|
|
2677
|
+
id: menu.contentId,
|
|
2678
|
+
role: "menu",
|
|
2679
|
+
tabIndex: -1,
|
|
2680
|
+
"aria-labelledby": props["aria-label"] ? void 0 : menu.triggerId,
|
|
2681
|
+
popover: "manual",
|
|
2682
|
+
"data-slot": "dropdown-menu-content",
|
|
2683
|
+
"data-state": "open",
|
|
2684
|
+
style,
|
|
2685
|
+
className: cn("fixed inset-auto z-50 m-0 box-border w-max min-w-50 overflow-y-auto rounded-lg border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", className),
|
|
2686
|
+
onKeyDown: (event) => {
|
|
2687
|
+
onKeyDown?.(event);
|
|
2688
|
+
if (event.defaultPrevented) return;
|
|
2689
|
+
const entries = items(event.currentTarget);
|
|
2690
|
+
const index = entries.indexOf(document.activeElement);
|
|
2691
|
+
if (event.key === "Escape") {
|
|
2692
|
+
event.preventDefault();
|
|
2693
|
+
event.stopPropagation();
|
|
2694
|
+
menu.change(false, "escape");
|
|
2695
|
+
} else if (event.key === "Tab") {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
const trigger = menu.trigger.current;
|
|
2698
|
+
if (!trigger) return;
|
|
2699
|
+
const scope = trigger.closest("dialog[open]") ?? document.body;
|
|
2700
|
+
const tabbables = Array.from(scope.querySelectorAll("a[href], button, input, select, textarea, [tabindex]")).filter((el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[inert]") && !event.currentTarget.contains(el) && el.getClientRects().length > 0);
|
|
2701
|
+
const target = tabbables[tabbables.indexOf(trigger) + (event.shiftKey ? -1 : 1)];
|
|
2702
|
+
menu.change(false, "tab");
|
|
2703
|
+
(target ?? trigger).focus();
|
|
2704
|
+
} else if ([
|
|
2705
|
+
"ArrowDown",
|
|
2706
|
+
"ArrowUp",
|
|
2707
|
+
"Home",
|
|
2708
|
+
"End"
|
|
2709
|
+
].includes(event.key)) {
|
|
2710
|
+
event.preventDefault();
|
|
2711
|
+
entries[event.key === "Home" ? 0 : event.key === "End" ? entries.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + entries.length) % entries.length]?.focus();
|
|
2712
|
+
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
|
|
2713
|
+
event.preventDefault();
|
|
2714
|
+
const now = Date.now();
|
|
2715
|
+
query.current.text = (now - query.current.time > 500 ? "" : query.current.text) + event.key.toLocaleLowerCase();
|
|
2716
|
+
query.current.time = now;
|
|
2717
|
+
const text = query.current.text;
|
|
2718
|
+
const term = Array.from(text).every((char) => char === text[0]) ? text[0] : text;
|
|
2719
|
+
[...entries.slice(index + 1), ...entries.slice(0, index + 1)].find((item) => (item.dataset.textValue ?? item.textContent ?? "").trim().toLocaleLowerCase().startsWith(term))?.focus();
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
function DropdownMenuItem({ asChild = false, iconLeft, tone = "default", textValue, disabled, onSelect, onClick, onClickCapture, onAuxClickCapture, onKeyDown, onPointerMove, children, className, ...props }) {
|
|
2725
|
+
const menu = useMenu();
|
|
2726
|
+
const Component = asChild ? Slot : "button";
|
|
2727
|
+
const renderContent = (label) => /* @__PURE__ */ jsxs(Fragment$1, { children: [iconLeft && /* @__PURE__ */ jsx("span", {
|
|
2728
|
+
"aria-hidden": true,
|
|
2729
|
+
children: iconLeft
|
|
2730
|
+
}), label] });
|
|
2731
|
+
const child = asChild ? Children.only(children) : null;
|
|
2732
|
+
const content = isValidElement(child) ? cloneElement(child, { children: wrapChildren(child.props.children, renderContent) }) : renderContent(children);
|
|
2733
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
2734
|
+
...props,
|
|
2735
|
+
type: asChild ? void 0 : "button",
|
|
2736
|
+
role: "menuitem",
|
|
2737
|
+
tabIndex: -1,
|
|
2738
|
+
"aria-disabled": disabled || void 0,
|
|
2739
|
+
"data-slot": "dropdown-menu-item",
|
|
2740
|
+
"data-tone": tone,
|
|
2741
|
+
"data-text-value": textValue,
|
|
2742
|
+
className: cn("flex min-h-8 w-full cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm/snug outline-none select-none pointer-coarse:min-h-10", "focus:bg-popover-highlight focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", tone === "danger" ? "text-danger-subtle-fg focus:bg-danger-subtle-bg" : "text-fg [&_svg]:text-fg-muted", className),
|
|
2743
|
+
onClickCapture: (event) => {
|
|
2744
|
+
if (disabled) {
|
|
2745
|
+
event.preventDefault();
|
|
2746
|
+
event.stopPropagation();
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
onClickCapture?.(event);
|
|
2750
|
+
},
|
|
2751
|
+
onAuxClickCapture: (event) => {
|
|
2752
|
+
if (disabled) {
|
|
2753
|
+
event.preventDefault();
|
|
2754
|
+
event.stopPropagation();
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
onAuxClickCapture?.(event);
|
|
2758
|
+
},
|
|
2759
|
+
onPointerMove: (event) => {
|
|
2760
|
+
onPointerMove?.(event);
|
|
2761
|
+
if (!event.defaultPrevented && event.pointerType !== "touch") event.currentTarget.focus({ preventScroll: true });
|
|
2762
|
+
},
|
|
2763
|
+
onKeyDown: (event) => {
|
|
2764
|
+
onKeyDown?.(event);
|
|
2765
|
+
if (event.defaultPrevented) return;
|
|
2766
|
+
if (event.key === " " || event.key === "Enter") {
|
|
2767
|
+
event.preventDefault();
|
|
2768
|
+
event.currentTarget.click();
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
onClick: (event) => {
|
|
2772
|
+
if (disabled) {
|
|
2773
|
+
event.preventDefault();
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
onClick?.(event);
|
|
2777
|
+
const selection = new Event("select", { cancelable: true });
|
|
2778
|
+
onSelect?.(selection);
|
|
2779
|
+
if (!selection.defaultPrevented) menu.change(false, "item-select");
|
|
2780
|
+
},
|
|
2781
|
+
children: content
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
function DropdownMenuGroup({ className, ...props }) {
|
|
2785
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2786
|
+
...props,
|
|
2787
|
+
role: "group",
|
|
2788
|
+
"data-slot": "dropdown-menu-group",
|
|
2789
|
+
className
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
function DropdownMenuLabel({ className, ...props }) {
|
|
2793
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2794
|
+
...props,
|
|
2795
|
+
"data-slot": "dropdown-menu-label",
|
|
2796
|
+
className: cn("px-2 py-1.5 text-xs text-fg-muted", className)
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
2800
|
+
return /* @__PURE__ */ jsx("hr", {
|
|
2801
|
+
...props,
|
|
2802
|
+
"data-slot": "dropdown-menu-separator",
|
|
2803
|
+
className: cn("my-1 h-px border-0 bg-popover-border", className)
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
//#endregion
|
|
1496
2807
|
//#region src/components/input/copy-input.tsx
|
|
1497
2808
|
const RESET_MS = 1500;
|
|
1498
2809
|
function CopyInput({ value, mono = true, size = "md", onFocus, onClick, ...props }) {
|
|
@@ -1677,6 +2988,197 @@ function Meter({ label, summary, segments, className, ...props }) {
|
|
|
1677
2988
|
});
|
|
1678
2989
|
}
|
|
1679
2990
|
//#endregion
|
|
2991
|
+
//#region src/components/radio-group/radio-group.tsx
|
|
2992
|
+
const RadioContext = createContext(null);
|
|
2993
|
+
function RadioGroup({ name, legend, description, error, value, defaultValue, onValueChange, variant = "default", orientation = "vertical", itemsClassName, children, className, disabled, required, form, ref, "aria-invalid": ariaInvalid, "aria-describedby": describedByProp, ...props }) {
|
|
2994
|
+
const id = useId();
|
|
2995
|
+
const descriptionId = `${id}-description`;
|
|
2996
|
+
const errorId = `${id}-error`;
|
|
2997
|
+
const legendId = `${id}-legend`;
|
|
2998
|
+
const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
|
|
2999
|
+
const hasDescription = isPresent(description);
|
|
3000
|
+
const hasError = Boolean(error) && error !== true && isPresent(error);
|
|
3001
|
+
const describedBy = [
|
|
3002
|
+
describedByProp,
|
|
3003
|
+
hasDescription && descriptionId,
|
|
3004
|
+
hasError && errorId
|
|
3005
|
+
].filter(Boolean).join(" ") || void 0;
|
|
3006
|
+
const controlled = value !== void 0;
|
|
3007
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? null);
|
|
3008
|
+
const selected = controlled ? value : internalValue;
|
|
3009
|
+
const groupRef = useRef(null);
|
|
3010
|
+
const mergedRef = useMergedRef(groupRef, ref);
|
|
3011
|
+
const inputs = useRef(/* @__PURE__ */ new Set());
|
|
3012
|
+
const currentSelection = useRef(selected);
|
|
3013
|
+
useLayoutEffect(() => {
|
|
3014
|
+
currentSelection.current = selected;
|
|
3015
|
+
}, [selected]);
|
|
3016
|
+
const register = useCallback((node) => {
|
|
3017
|
+
const members = inputs.current;
|
|
3018
|
+
if ([...members].some((input) => input.value === node.value)) throw new Error(`Radio values must be unique within a RadioGroup: "${node.value}"`);
|
|
3019
|
+
members.add(node);
|
|
3020
|
+
const reconcile = () => {
|
|
3021
|
+
queueMicrotask(() => {
|
|
3022
|
+
if (controlled || !groupRef.current) return;
|
|
3023
|
+
setInternalValue((current) => [...members].some((input) => input.value === current) ? current : null);
|
|
3024
|
+
});
|
|
3025
|
+
};
|
|
3026
|
+
reconcile();
|
|
3027
|
+
return () => {
|
|
3028
|
+
members.delete(node);
|
|
3029
|
+
reconcile();
|
|
3030
|
+
};
|
|
3031
|
+
}, [controlled]);
|
|
3032
|
+
useLayoutEffect(() => {
|
|
3033
|
+
const owner = groupRef.current?.form;
|
|
3034
|
+
if (!owner) return;
|
|
3035
|
+
let timer;
|
|
3036
|
+
const reset = (event) => {
|
|
3037
|
+
clearTimeout(timer);
|
|
3038
|
+
timer = setTimeout(() => {
|
|
3039
|
+
if (event.defaultPrevented) return;
|
|
3040
|
+
const next = controlled ? currentSelection.current : defaultValue ?? null;
|
|
3041
|
+
const members = [...inputs.current];
|
|
3042
|
+
if (!controlled) setInternalValue(members.some((input) => input.value === next) ? next : null);
|
|
3043
|
+
for (const input of members) input.checked = input.value === next;
|
|
3044
|
+
}, 0);
|
|
3045
|
+
};
|
|
3046
|
+
owner.addEventListener("reset", reset);
|
|
3047
|
+
return () => {
|
|
3048
|
+
clearTimeout(timer);
|
|
3049
|
+
owner.removeEventListener("reset", reset);
|
|
3050
|
+
};
|
|
3051
|
+
}, [
|
|
3052
|
+
controlled,
|
|
3053
|
+
defaultValue,
|
|
3054
|
+
form
|
|
3055
|
+
]);
|
|
3056
|
+
return /* @__PURE__ */ jsx(RadioContext, {
|
|
3057
|
+
value: {
|
|
3058
|
+
name,
|
|
3059
|
+
form,
|
|
3060
|
+
selected,
|
|
3061
|
+
disabled,
|
|
3062
|
+
required,
|
|
3063
|
+
invalid,
|
|
3064
|
+
describedBy,
|
|
3065
|
+
variant,
|
|
3066
|
+
orientation,
|
|
3067
|
+
register,
|
|
3068
|
+
select: (event) => {
|
|
3069
|
+
if (!event.currentTarget.checked) return;
|
|
3070
|
+
const next = event.currentTarget.value;
|
|
3071
|
+
if (!controlled) setInternalValue(next);
|
|
3072
|
+
onValueChange?.(next, event);
|
|
3073
|
+
}
|
|
3074
|
+
},
|
|
3075
|
+
children: /* @__PURE__ */ jsxs("fieldset", {
|
|
3076
|
+
...props,
|
|
3077
|
+
ref: mergedRef,
|
|
3078
|
+
name,
|
|
3079
|
+
form,
|
|
3080
|
+
disabled,
|
|
3081
|
+
"data-slot": "radio-group",
|
|
3082
|
+
"data-variant": variant,
|
|
3083
|
+
role: "radiogroup",
|
|
3084
|
+
"aria-labelledby": props["aria-labelledby"] ?? legendId,
|
|
3085
|
+
"aria-describedby": describedBy,
|
|
3086
|
+
"aria-invalid": invalid ? true : ariaInvalid,
|
|
3087
|
+
"aria-required": required || void 0,
|
|
3088
|
+
"aria-orientation": orientation,
|
|
3089
|
+
className: cn("group/radio-group m-0 min-w-0 border-0 p-0", className),
|
|
3090
|
+
children: [/* @__PURE__ */ jsxs("legend", {
|
|
3091
|
+
id: legendId,
|
|
3092
|
+
className: "float-start mb-1.5 w-full p-0 text-sm font-medium text-fg group-disabled/radio-group:opacity-50",
|
|
3093
|
+
children: [legend, required && /* @__PURE__ */ jsx("span", {
|
|
3094
|
+
"aria-hidden": true,
|
|
3095
|
+
className: "text-danger",
|
|
3096
|
+
children: " *"
|
|
3097
|
+
})]
|
|
3098
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3099
|
+
className: "clear-both flex flex-col",
|
|
3100
|
+
children: [
|
|
3101
|
+
hasDescription && /* @__PURE__ */ jsx("p", {
|
|
3102
|
+
id: descriptionId,
|
|
3103
|
+
className: "m-0 -mt-1 mb-2.5 text-xs/snug text-fg-muted group-disabled/radio-group:opacity-50",
|
|
3104
|
+
children: description
|
|
3105
|
+
}),
|
|
3106
|
+
/* @__PURE__ */ jsx("div", {
|
|
3107
|
+
"data-slot": "radio-group-items",
|
|
3108
|
+
"data-orientation": orientation,
|
|
3109
|
+
className: cn("flex gap-2", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap gap-x-6", variant === "card" && orientation === "horizontal" && "gap-3", itemsClassName),
|
|
3110
|
+
children
|
|
3111
|
+
}),
|
|
3112
|
+
hasError && /* @__PURE__ */ jsxs("p", {
|
|
3113
|
+
id: errorId,
|
|
3114
|
+
className: "m-0 mt-2 flex items-start gap-1 text-xs/snug text-danger",
|
|
3115
|
+
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
3116
|
+
"aria-hidden": true,
|
|
3117
|
+
className: "mt-px size-3.5 shrink-0"
|
|
3118
|
+
}), /* @__PURE__ */ jsx("span", { children: error })]
|
|
3119
|
+
})
|
|
3120
|
+
]
|
|
3121
|
+
})]
|
|
3122
|
+
})
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
function Radio({ value, label, description, disabled, className, inputClassName, ref, onChange, "aria-describedby": describedBy, ...props }) {
|
|
3126
|
+
const group = useContext(RadioContext);
|
|
3127
|
+
if (!group) throw new Error("Radio must be rendered inside <RadioGroup>");
|
|
3128
|
+
if (value === "") throw new Error("Radio value must be a non-empty string");
|
|
3129
|
+
const ownRef = useRef(null);
|
|
3130
|
+
const mergedRef = useMergedRef(ownRef, ref);
|
|
3131
|
+
const register = group.register;
|
|
3132
|
+
useLayoutEffect(() => {
|
|
3133
|
+
if (ownRef.current) return register(ownRef.current);
|
|
3134
|
+
}, [register, value]);
|
|
3135
|
+
const card = group.variant === "card";
|
|
3136
|
+
const invalid = group.invalid;
|
|
3137
|
+
return /* @__PURE__ */ jsx(ChoiceRow, {
|
|
3138
|
+
slot: "radio",
|
|
3139
|
+
label,
|
|
3140
|
+
description,
|
|
3141
|
+
gap: card ? "gap-3" : "gap-2",
|
|
3142
|
+
controlOffset: "mt-1",
|
|
3143
|
+
"aria-describedby": [describedBy, group.describedBy].filter(Boolean).join(" ") || void 0,
|
|
3144
|
+
className: cn("group/radio wrap-anywhere motion-reduce:**:transition-none", "pointer-coarse:min-h-11 pointer-coarse:py-2.5", card && [
|
|
3145
|
+
"w-full rounded-lg border bg-surface p-3 pointer-coarse:p-3",
|
|
3146
|
+
"transition-[border-color,background-color] duration-150 ease-out",
|
|
3147
|
+
"[&>span:last-child>span:first-child]:font-medium",
|
|
3148
|
+
"has-[input:checked]:bg-[color-mix(in_oklab,var(--color-control-checked)_4%,var(--color-surface))]",
|
|
3149
|
+
"has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2",
|
|
3150
|
+
invalid ? "border-danger has-[input:focus-visible]:outline-danger" : "border-input-border has-[input:checked]:border-control-checked has-[input:enabled]:hover:border-input-border-hover has-[input:focus-visible]:outline-control-checked",
|
|
3151
|
+
"forced-colors:has-[input:checked]:border-[Highlight] forced-colors:has-[input:focus-visible]:outline-[Highlight]",
|
|
3152
|
+
group.orientation === "horizontal" && "flex-[1_1_14rem]"
|
|
3153
|
+
], className),
|
|
3154
|
+
control: (row) => /* @__PURE__ */ jsxs("span", {
|
|
3155
|
+
className: "relative inline-grid size-4 place-items-center transition-[scale] duration-100 ease-out has-[input:enabled:active]:scale-[0.92]",
|
|
3156
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3157
|
+
...props,
|
|
3158
|
+
ref: mergedRef,
|
|
3159
|
+
"data-slot": "radio-input",
|
|
3160
|
+
type: "radio",
|
|
3161
|
+
name: group.name,
|
|
3162
|
+
form: group.form,
|
|
3163
|
+
value,
|
|
3164
|
+
checked: group.selected === value,
|
|
3165
|
+
disabled: group.disabled || disabled,
|
|
3166
|
+
required: group.required,
|
|
3167
|
+
"aria-labelledby": props["aria-labelledby"] ?? row.labelledBy,
|
|
3168
|
+
"aria-describedby": row.describedBy,
|
|
3169
|
+
onChange: (event) => {
|
|
3170
|
+
onChange?.(event);
|
|
3171
|
+
group.select(event);
|
|
3172
|
+
},
|
|
3173
|
+
className: cn("peer col-start-1 row-start-1 m-0 size-4 cursor-pointer appearance-none rounded-full border outline-hidden disabled:cursor-not-allowed", "bg-input-bg checked:bg-control-checked", "transition-[border-color,background-color,box-shadow] duration-150 ease-out", invalid ? "border-danger [box-shadow:0_0_0_3px_var(--color-input-halo-invalid)]" : "border-input-border checked:border-control-checked group-hover/radio:enabled:not-checked:border-input-border-hover", !card && [invalid ? "focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo-invalid-focus)] not-forced-colors:focus-visible:outline-danger" : "focus-visible:border-input-focus focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo)] not-forced-colors:focus-visible:outline-control-checked", "checked:focus-visible:shadow-none checked:focus-visible:outline-solid checked:focus-visible:outline-2 checked:focus-visible:outline-offset-2"], card && "focus-visible:outline-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:disabled:checked:bg-[GrayText]", !card && "forced-colors:focus-visible:outline-[Highlight]", inputClassName)
|
|
3174
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3175
|
+
"aria-hidden": true,
|
|
3176
|
+
className: "pointer-events-none col-start-1 row-start-1 size-1.5 scale-0 rounded-full bg-control-checked-fg transition-[scale] duration-150 ease-out peer-checked:scale-100 forced-colors:forced-color-adjust-none forced-colors:bg-[HighlightText] forced-colors:peer-disabled:bg-[Canvas]"
|
|
3177
|
+
})]
|
|
3178
|
+
})
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
//#endregion
|
|
1680
3182
|
//#region src/components/select/select.tsx
|
|
1681
3183
|
const selectPadding = {
|
|
1682
3184
|
sm: "px-3",
|
|
@@ -1761,7 +3263,7 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
|
|
|
1761
3263
|
className,
|
|
1762
3264
|
controlRef: selectRef,
|
|
1763
3265
|
activate: openPicker,
|
|
1764
|
-
children: (field) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3266
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1765
3267
|
field.floatingLabel(labelRestingWidth[size]),
|
|
1766
3268
|
/* @__PURE__ */ jsxs("select", {
|
|
1767
3269
|
...props,
|
|
@@ -1790,73 +3292,18 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
|
|
|
1790
3292
|
}),
|
|
1791
3293
|
hasLeading && /* @__PURE__ */ jsx("span", {
|
|
1792
3294
|
"aria-hidden": true,
|
|
1793
|
-
className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize[size], startSlotPadding[size]),
|
|
3295
|
+
className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize$1[size], startSlotPadding[size]),
|
|
1794
3296
|
children: leading
|
|
1795
3297
|
}),
|
|
1796
3298
|
/* @__PURE__ */ jsx("span", {
|
|
1797
3299
|
"aria-hidden": true,
|
|
1798
|
-
className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize[size], endSlotPadding[size]),
|
|
3300
|
+
className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize$1[size], endSlotPadding[size]),
|
|
1799
3301
|
children: /* @__PURE__ */ jsx(ChevronDown, { className: "transition-transform duration-150 ease-out group-has-[select:open]/control:rotate-180 motion-reduce:transition-none" })
|
|
1800
3302
|
})
|
|
1801
3303
|
] })
|
|
1802
3304
|
});
|
|
1803
3305
|
}
|
|
1804
3306
|
//#endregion
|
|
1805
|
-
//#region src/lib/slot.tsx
|
|
1806
|
-
function setRef(ref, node) {
|
|
1807
|
-
if (typeof ref === "function") return ref(node);
|
|
1808
|
-
if (ref) ref.current = node;
|
|
1809
|
-
}
|
|
1810
|
-
function composeRefs(...refs) {
|
|
1811
|
-
return (node) => {
|
|
1812
|
-
const cleanups = refs.map((ref) => setRef(ref, node));
|
|
1813
|
-
return () => {
|
|
1814
|
-
for (const [i, ref] of refs.entries()) {
|
|
1815
|
-
const cleanup = cleanups[i];
|
|
1816
|
-
if (typeof cleanup === "function") cleanup();
|
|
1817
|
-
else setRef(ref, null);
|
|
1818
|
-
}
|
|
1819
|
-
};
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
function mergeProps(slot, child) {
|
|
1823
|
-
const merged = {
|
|
1824
|
-
...slot,
|
|
1825
|
-
...child
|
|
1826
|
-
};
|
|
1827
|
-
for (const key of Object.keys(slot)) {
|
|
1828
|
-
const own = slot[key];
|
|
1829
|
-
const theirs = child[key];
|
|
1830
|
-
if (/^on[A-Z]/.test(key) && typeof own === "function" && typeof theirs === "function") merged[key] = (...args) => {
|
|
1831
|
-
theirs(...args);
|
|
1832
|
-
own(...args);
|
|
1833
|
-
};
|
|
1834
|
-
else if (key === "className") merged.className = cn(own, theirs);
|
|
1835
|
-
else if (key === "style") merged.style = {
|
|
1836
|
-
...own,
|
|
1837
|
-
...theirs
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
return merged;
|
|
1841
|
-
}
|
|
1842
|
-
/** Applies `wrap` to an element's children, or to what a render-prop child returns. */
|
|
1843
|
-
function wrapChildren(children, wrap) {
|
|
1844
|
-
if (typeof children !== "function") return wrap(children);
|
|
1845
|
-
return (...args) => wrap(children(...args));
|
|
1846
|
-
}
|
|
1847
|
-
/** Renders its only child with these props merged in (Radix Slot style). */
|
|
1848
|
-
function Slot({ children, ref, ...props }) {
|
|
1849
|
-
const child = Children.only(children);
|
|
1850
|
-
const childProps = isValidElement(child) ? child.props : {};
|
|
1851
|
-
const childRef = childProps.ref;
|
|
1852
|
-
const composedRef = useMemo(() => composeRefs(ref, childRef), [ref, childRef]);
|
|
1853
|
-
if (!isValidElement(child)) return null;
|
|
1854
|
-
return cloneElement(child, {
|
|
1855
|
-
...mergeProps(props, childProps),
|
|
1856
|
-
ref: composedRef
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
//#endregion
|
|
1860
3307
|
//#region src/components/tabs/shared.tsx
|
|
1861
3308
|
const listBase = [
|
|
1862
3309
|
"relative isolate max-w-full overflow-x-auto overscroll-x-contain scroll-px-6",
|
|
@@ -1965,7 +3412,7 @@ function TabContent({ icon, badge, children }) {
|
|
|
1965
3412
|
children
|
|
1966
3413
|
})]
|
|
1967
3414
|
}),
|
|
1968
|
-
badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("span", {
|
|
3415
|
+
badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx("span", {
|
|
1969
3416
|
"data-slot": "tab-badge",
|
|
1970
3417
|
className: "inline-flex",
|
|
1971
3418
|
children: badge
|
|
@@ -2153,7 +3600,7 @@ function SidebarProvider({ children }) {
|
|
|
2153
3600
|
function Sidebar({ className, children, ...props }) {
|
|
2154
3601
|
const { open, setOpen, drawerId } = useSidebarContext("Sidebar");
|
|
2155
3602
|
const label = props["aria-label"] ? { "aria-label": props["aria-label"] } : { "aria-labelledby": props["aria-labelledby"] };
|
|
2156
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelContext, {
|
|
3603
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PanelContext, {
|
|
2157
3604
|
value: { drawer: false },
|
|
2158
3605
|
children: /* @__PURE__ */ jsx("nav", {
|
|
2159
3606
|
...props,
|
|
@@ -2373,13 +3820,586 @@ function SidebarIcon({ className, ...props }) {
|
|
|
2373
3820
|
}
|
|
2374
3821
|
/** Count at the end of a SidebarItem, as a small neutral Badge; part of the name ("Projects 8"). */
|
|
2375
3822
|
function SidebarBadge({ className, ...props }) {
|
|
2376
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
3823
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
2377
3824
|
size: "sm",
|
|
2378
3825
|
...props,
|
|
2379
3826
|
className: cn("shrink-0", className)
|
|
2380
3827
|
})] });
|
|
2381
3828
|
}
|
|
2382
3829
|
//#endregion
|
|
3830
|
+
//#region src/components/slider/scale.ts
|
|
3831
|
+
const fail = (message) => {
|
|
3832
|
+
throw new Error(`Slider: ${message}`);
|
|
3833
|
+
};
|
|
3834
|
+
const close = (value, integer) => Math.abs(value - integer) <= 1e-7;
|
|
3835
|
+
const decimals = (value) => {
|
|
3836
|
+
const [digits, exponent = "0"] = String(value).toLowerCase().split("e");
|
|
3837
|
+
return Math.max(0, (digits.split(".")[1]?.length ?? 0) - Number(exponent));
|
|
3838
|
+
};
|
|
3839
|
+
/** Work in integer ticks, not repeated floating point additions. */
|
|
3840
|
+
function createScale(min, max, step, gap) {
|
|
3841
|
+
if (![
|
|
3842
|
+
min,
|
|
3843
|
+
max,
|
|
3844
|
+
step,
|
|
3845
|
+
gap
|
|
3846
|
+
].every(Number.isFinite)) fail("bounds, step and gap must be finite");
|
|
3847
|
+
if (min >= max || step <= 0) fail("min must be less than max and step must be positive");
|
|
3848
|
+
if (!Number.isInteger(gap) || gap < 0) fail("minStepsBetweenThumbs must be a non-negative integer");
|
|
3849
|
+
const factor = 10 ** Math.max(decimals(min), decimals(max), decimals(step));
|
|
3850
|
+
const start = Math.round(min * factor), end = Math.round(max * factor), increment = Math.round(step * factor);
|
|
3851
|
+
if (![
|
|
3852
|
+
start,
|
|
3853
|
+
end,
|
|
3854
|
+
increment,
|
|
3855
|
+
end - start
|
|
3856
|
+
].every(Number.isSafeInteger) || increment < 1) fail("bounds and step exceed safe numeric precision");
|
|
3857
|
+
const count = (end - start) / increment;
|
|
3858
|
+
if (!Number.isSafeInteger(count)) fail("(max - min) must be evenly divisible by step");
|
|
3859
|
+
if (gap > count) fail("minimum thumb distance exceeds the available range");
|
|
3860
|
+
const fromTick = (tick) => (start + tick * increment) / factor;
|
|
3861
|
+
const toTick = (value) => (value * factor - start) / increment;
|
|
3862
|
+
const validate = (values, length) => {
|
|
3863
|
+
if (values.length !== length) fail(`expected ${length} value${length === 1 ? "" : "s"}`);
|
|
3864
|
+
const ticks = values.map((value) => {
|
|
3865
|
+
if (!Number.isFinite(value)) fail("values must be finite");
|
|
3866
|
+
const tick = toTick(value), rounded = Math.round(tick);
|
|
3867
|
+
if (!close(tick, rounded) || rounded < 0 || rounded > count) fail("values must be on a step within min and max");
|
|
3868
|
+
return rounded;
|
|
3869
|
+
});
|
|
3870
|
+
if (ticks.length === 2 && ticks[1] - ticks[0] < gap) fail("range values must be ordered and satisfy the minimum distance");
|
|
3871
|
+
return ticks.map(fromTick);
|
|
3872
|
+
};
|
|
3873
|
+
const normalize = (values) => {
|
|
3874
|
+
const ticks = values.map((value) => Math.max(0, Math.min(count, Math.round(toTick(value)))));
|
|
3875
|
+
if (ticks.length === 2) {
|
|
3876
|
+
ticks[0] = Math.min(ticks[0], count - gap);
|
|
3877
|
+
ticks[1] = Math.max(ticks[1], ticks[0] + gap);
|
|
3878
|
+
}
|
|
3879
|
+
return ticks.map(fromTick);
|
|
3880
|
+
};
|
|
3881
|
+
const limits = (values, index) => values.length === 1 ? [0, count] : index === 0 ? [0, Math.round(toTick(values[1])) - gap] : [Math.round(toTick(values[0])) + gap, count];
|
|
3882
|
+
const move = (values, index, tick) => {
|
|
3883
|
+
const [low, high] = limits(values, index);
|
|
3884
|
+
return values.map((value, i) => i === index ? fromTick(Math.max(low, Math.min(high, Math.round(tick)))) : value);
|
|
3885
|
+
};
|
|
3886
|
+
return {
|
|
3887
|
+
min,
|
|
3888
|
+
max,
|
|
3889
|
+
step,
|
|
3890
|
+
gap,
|
|
3891
|
+
count,
|
|
3892
|
+
fromTick,
|
|
3893
|
+
toTick,
|
|
3894
|
+
validate,
|
|
3895
|
+
normalize,
|
|
3896
|
+
limits,
|
|
3897
|
+
move,
|
|
3898
|
+
key: `${min}/${max}/${step}/${gap}`
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
const sameValues = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3902
|
+
//#endregion
|
|
3903
|
+
//#region src/components/slider/use-slider.ts
|
|
3904
|
+
const keys = [
|
|
3905
|
+
"ArrowRight",
|
|
3906
|
+
"ArrowLeft",
|
|
3907
|
+
"ArrowUp",
|
|
3908
|
+
"ArrowDown",
|
|
3909
|
+
"Home",
|
|
3910
|
+
"End",
|
|
3911
|
+
"PageUp",
|
|
3912
|
+
"PageDown"
|
|
3913
|
+
];
|
|
3914
|
+
function useSlider(options) {
|
|
3915
|
+
const { scale, value, defaultValue, disabled, form } = options;
|
|
3916
|
+
const controlled = value !== void 0;
|
|
3917
|
+
const [internal, setInternal] = useState(() => [...defaultValue]);
|
|
3918
|
+
const values = controlled ? value : scale.normalize(internal);
|
|
3919
|
+
if (!controlled && !sameValues(internal, values)) setInternal([...values]);
|
|
3920
|
+
const [dragging, setDragging] = useState(false);
|
|
3921
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
3922
|
+
const inputs = useRef([]);
|
|
3923
|
+
const controlRef = useRef(null);
|
|
3924
|
+
const railRef = useRef(null);
|
|
3925
|
+
const session = useRef(null);
|
|
3926
|
+
const lastIndex = useRef(0);
|
|
3927
|
+
const mounted = useRef(false);
|
|
3928
|
+
const generation = useRef(0);
|
|
3929
|
+
const latest = useRef({
|
|
3930
|
+
...options,
|
|
3931
|
+
values,
|
|
3932
|
+
controlled
|
|
3933
|
+
});
|
|
3934
|
+
function release() {
|
|
3935
|
+
const current = session.current;
|
|
3936
|
+
session.current = null;
|
|
3937
|
+
if (!current) return null;
|
|
3938
|
+
current.removeListeners?.();
|
|
3939
|
+
if (current.pointerId !== void 0 && current.control?.hasPointerCapture?.(current.pointerId)) current.control.releasePointerCapture(current.pointerId);
|
|
3940
|
+
if (mounted.current) setDragging(false);
|
|
3941
|
+
return current;
|
|
3942
|
+
}
|
|
3943
|
+
function canUse(index) {
|
|
3944
|
+
return !latest.current.disabled && !!inputs.current[index] && !inputs.current[index].matches(":disabled");
|
|
3945
|
+
}
|
|
3946
|
+
function publish(next, details) {
|
|
3947
|
+
const state = latest.current;
|
|
3948
|
+
if (sameValues(state.values, next)) return;
|
|
3949
|
+
if (session.current) session.current.expected = next;
|
|
3950
|
+
if (!state.controlled) {
|
|
3951
|
+
latest.current = {
|
|
3952
|
+
...state,
|
|
3953
|
+
values: next
|
|
3954
|
+
};
|
|
3955
|
+
setInternal(next);
|
|
3956
|
+
}
|
|
3957
|
+
state.onValueChange?.(next, details);
|
|
3958
|
+
}
|
|
3959
|
+
function commit(start, expected, details, key, defer = false) {
|
|
3960
|
+
const token = generation.current;
|
|
3961
|
+
const notify = () => {
|
|
3962
|
+
const state = latest.current;
|
|
3963
|
+
if (!mounted.current || token !== generation.current || state.scale.key !== key || !canUse(details.thumbIndex)) return;
|
|
3964
|
+
if (!sameValues(state.values, expected) || sameValues(start, state.values)) return;
|
|
3965
|
+
state.onValueCommit?.([...state.values], details);
|
|
3966
|
+
};
|
|
3967
|
+
if (defer) queueMicrotask(notify);
|
|
3968
|
+
else notify();
|
|
3969
|
+
}
|
|
3970
|
+
function finish(canceled = false) {
|
|
3971
|
+
const current = release();
|
|
3972
|
+
if (!current) return;
|
|
3973
|
+
if (canceled) {
|
|
3974
|
+
generation.current++;
|
|
3975
|
+
publish(current.start, {
|
|
3976
|
+
thumbIndex: current.index,
|
|
3977
|
+
reason: "cancel"
|
|
3978
|
+
});
|
|
3979
|
+
} else commit(current.start, current.expected, {
|
|
3980
|
+
thumbIndex: current.index,
|
|
3981
|
+
reason: current.reason
|
|
3982
|
+
}, current.scaleKey);
|
|
3983
|
+
}
|
|
3984
|
+
function begin(index, reason) {
|
|
3985
|
+
const state = latest.current;
|
|
3986
|
+
const current = {
|
|
3987
|
+
index,
|
|
3988
|
+
reason,
|
|
3989
|
+
start: [...state.values],
|
|
3990
|
+
expected: [...state.values],
|
|
3991
|
+
scaleKey: state.scale.key,
|
|
3992
|
+
keys: /* @__PURE__ */ new Set(),
|
|
3993
|
+
offset: 0,
|
|
3994
|
+
startX: 0,
|
|
3995
|
+
pending: false
|
|
3996
|
+
};
|
|
3997
|
+
session.current = current;
|
|
3998
|
+
return current;
|
|
3999
|
+
}
|
|
4000
|
+
function move(index, tick, reason) {
|
|
4001
|
+
if (!canUse(index)) {
|
|
4002
|
+
release();
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
const state = latest.current;
|
|
4006
|
+
publish(state.scale.move(state.values, index, tick), {
|
|
4007
|
+
thumbIndex: index,
|
|
4008
|
+
reason
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
function direction() {
|
|
4012
|
+
const node = controlRef.current;
|
|
4013
|
+
return node ? getComputedStyle(node).direction || node.closest("[dir]")?.getAttribute("dir") || "ltr" : "ltr";
|
|
4014
|
+
}
|
|
4015
|
+
function tickAt(clientX) {
|
|
4016
|
+
const rect = railRef.current?.getBoundingClientRect();
|
|
4017
|
+
if (!rect || rect.width <= 0) return null;
|
|
4018
|
+
let ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
4019
|
+
if (direction() === "rtl") ratio = 1 - ratio;
|
|
4020
|
+
return ratio * latest.current.scale.count;
|
|
4021
|
+
}
|
|
4022
|
+
function focus(index) {
|
|
4023
|
+
lastIndex.current = index;
|
|
4024
|
+
setActiveIndex(index);
|
|
4025
|
+
inputs.current[index]?.focus({ preventScroll: true });
|
|
4026
|
+
}
|
|
4027
|
+
function onPointerDown(event) {
|
|
4028
|
+
if (event.defaultPrevented || event.button !== 0 || event.isPrimary === false || session.current?.reason === "pointer") return;
|
|
4029
|
+
if (!canUse(0)) return;
|
|
4030
|
+
const tick = tickAt(event.clientX);
|
|
4031
|
+
if (tick === null) return;
|
|
4032
|
+
if (session.current) finish();
|
|
4033
|
+
const state = latest.current;
|
|
4034
|
+
const distances = state.values.map((v) => Math.abs(state.scale.toTick(v) - tick));
|
|
4035
|
+
const index = distances.length === 1 ? 0 : distances[0] === distances[1] ? lastIndex.current : distances[0] < distances[1] ? 0 : 1;
|
|
4036
|
+
event.preventDefault();
|
|
4037
|
+
focus(index);
|
|
4038
|
+
const current = begin(index, "pointer");
|
|
4039
|
+
current.pointerId = event.pointerId;
|
|
4040
|
+
current.control = event.currentTarget;
|
|
4041
|
+
current.startX = event.clientX;
|
|
4042
|
+
const rect = railRef.current.getBoundingClientRect();
|
|
4043
|
+
const ratio = state.scale.toTick(state.values[index]) / state.scale.count;
|
|
4044
|
+
const center = rect.left + (direction() === "rtl" ? 1 - ratio : ratio) * rect.width;
|
|
4045
|
+
const grabbed = Math.abs(event.clientX - center) <= 22;
|
|
4046
|
+
current.offset = grabbed ? event.clientX - center : 0;
|
|
4047
|
+
current.pending = state.values.length === 2 && state.values[0] === state.values[1] && grabbed;
|
|
4048
|
+
setDragging(true);
|
|
4049
|
+
const owner = event.currentTarget.ownerDocument.defaultView;
|
|
4050
|
+
const pointerMove = (next) => {
|
|
4051
|
+
if (session.current !== current || next.pointerId !== current.pointerId) return;
|
|
4052
|
+
const target = tickAt(next.clientX - current.offset);
|
|
4053
|
+
if (target === null) return;
|
|
4054
|
+
if (current.pending) {
|
|
4055
|
+
if (Math.abs(next.clientX - current.startX) < 2) return;
|
|
4056
|
+
current.index = target < latest.current.scale.toTick(current.start[0]) ? 0 : 1;
|
|
4057
|
+
current.pending = false;
|
|
4058
|
+
focus(current.index);
|
|
4059
|
+
}
|
|
4060
|
+
move(current.index, target, "pointer");
|
|
4061
|
+
};
|
|
4062
|
+
const pointerEnd = (next) => {
|
|
4063
|
+
if (session.current === current && next.pointerId === current.pointerId) finish(next.type !== "pointerup");
|
|
4064
|
+
};
|
|
4065
|
+
owner.addEventListener("pointermove", pointerMove);
|
|
4066
|
+
owner.addEventListener("pointerup", pointerEnd);
|
|
4067
|
+
owner.addEventListener("pointercancel", pointerEnd);
|
|
4068
|
+
current.control.addEventListener("lostpointercapture", pointerEnd);
|
|
4069
|
+
current.removeListeners = () => {
|
|
4070
|
+
owner.removeEventListener("pointermove", pointerMove);
|
|
4071
|
+
owner.removeEventListener("pointerup", pointerEnd);
|
|
4072
|
+
owner.removeEventListener("pointercancel", pointerEnd);
|
|
4073
|
+
current.control?.removeEventListener("lostpointercapture", pointerEnd);
|
|
4074
|
+
};
|
|
4075
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
4076
|
+
if (!grabbed) move(index, tick, "pointer");
|
|
4077
|
+
}
|
|
4078
|
+
function onKeyDown(event, index) {
|
|
4079
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || !keys.includes(event.key) || !canUse(index)) return;
|
|
4080
|
+
event.preventDefault();
|
|
4081
|
+
if (session.current?.reason === "pointer") return;
|
|
4082
|
+
(session.current ?? begin(index, "keyboard")).keys.add(event.key);
|
|
4083
|
+
const state = latest.current;
|
|
4084
|
+
const right = direction() === "rtl" ? -1 : 1;
|
|
4085
|
+
const deltas = {
|
|
4086
|
+
ArrowRight: right,
|
|
4087
|
+
ArrowLeft: -right,
|
|
4088
|
+
ArrowUp: 1,
|
|
4089
|
+
ArrowDown: -1,
|
|
4090
|
+
PageUp: 10,
|
|
4091
|
+
PageDown: -10
|
|
4092
|
+
};
|
|
4093
|
+
const [low, high] = state.scale.limits(state.values, index);
|
|
4094
|
+
move(index, event.key === "Home" ? low : event.key === "End" ? high : state.scale.toTick(state.values[index]) + deltas[event.key], "keyboard");
|
|
4095
|
+
}
|
|
4096
|
+
function onKeyUp(event) {
|
|
4097
|
+
const current = session.current;
|
|
4098
|
+
if (current?.reason !== "keyboard" || !current.keys.has(event.key)) return;
|
|
4099
|
+
current.keys.delete(event.key);
|
|
4100
|
+
if (!current.keys.size) finish();
|
|
4101
|
+
}
|
|
4102
|
+
function onInput(value, index) {
|
|
4103
|
+
if (session.current || !canUse(index) || !Number.isFinite(value)) return;
|
|
4104
|
+
const state = latest.current, start = [...state.values];
|
|
4105
|
+
const next = state.scale.move(start, index, state.scale.toTick(value));
|
|
4106
|
+
publish(next, {
|
|
4107
|
+
thumbIndex: index,
|
|
4108
|
+
reason: "input"
|
|
4109
|
+
});
|
|
4110
|
+
commit(start, next, {
|
|
4111
|
+
thumbIndex: index,
|
|
4112
|
+
reason: "input"
|
|
4113
|
+
}, state.scale.key, true);
|
|
4114
|
+
}
|
|
4115
|
+
useLayoutEffect(() => {
|
|
4116
|
+
latest.current = {
|
|
4117
|
+
...options,
|
|
4118
|
+
values,
|
|
4119
|
+
controlled
|
|
4120
|
+
};
|
|
4121
|
+
const current = session.current;
|
|
4122
|
+
if (current && (current.scaleKey !== scale.key || disabled || !sameValues(values, current.expected))) {
|
|
4123
|
+
generation.current++;
|
|
4124
|
+
release();
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
const cancelRef = useRef(() => {});
|
|
4128
|
+
useLayoutEffect(() => {
|
|
4129
|
+
cancelRef.current = () => finish(true);
|
|
4130
|
+
});
|
|
4131
|
+
useLayoutEffect(() => {
|
|
4132
|
+
mounted.current = true;
|
|
4133
|
+
const owner = controlRef.current?.ownerDocument.defaultView;
|
|
4134
|
+
const blur = () => cancelRef.current();
|
|
4135
|
+
owner?.addEventListener("blur", blur);
|
|
4136
|
+
return () => {
|
|
4137
|
+
mounted.current = false;
|
|
4138
|
+
generation.current++;
|
|
4139
|
+
release();
|
|
4140
|
+
owner?.removeEventListener("blur", blur);
|
|
4141
|
+
};
|
|
4142
|
+
}, []);
|
|
4143
|
+
useLayoutEffect(() => {
|
|
4144
|
+
const owner = inputs.current[0]?.form;
|
|
4145
|
+
if (!owner) return;
|
|
4146
|
+
let timer;
|
|
4147
|
+
const reset = (event) => {
|
|
4148
|
+
clearTimeout(timer);
|
|
4149
|
+
timer = setTimeout(() => {
|
|
4150
|
+
if (event.defaultPrevented) return;
|
|
4151
|
+
generation.current++;
|
|
4152
|
+
release();
|
|
4153
|
+
const state = latest.current;
|
|
4154
|
+
const next = state.controlled ? state.values : state.defaultValue;
|
|
4155
|
+
if (!state.controlled) {
|
|
4156
|
+
latest.current = {
|
|
4157
|
+
...state,
|
|
4158
|
+
values: next
|
|
4159
|
+
};
|
|
4160
|
+
setInternal([...next]);
|
|
4161
|
+
}
|
|
4162
|
+
inputs.current.forEach((input, index) => {
|
|
4163
|
+
if (!input) return;
|
|
4164
|
+
const [low, high] = state.scale.limits(next, index);
|
|
4165
|
+
input.min = String(state.scale.fromTick(low));
|
|
4166
|
+
input.max = String(state.scale.fromTick(high));
|
|
4167
|
+
input.value = String(next[index]);
|
|
4168
|
+
});
|
|
4169
|
+
}, 0);
|
|
4170
|
+
};
|
|
4171
|
+
owner.addEventListener("reset", reset);
|
|
4172
|
+
return () => {
|
|
4173
|
+
clearTimeout(timer);
|
|
4174
|
+
owner.removeEventListener("reset", reset);
|
|
4175
|
+
};
|
|
4176
|
+
}, [form]);
|
|
4177
|
+
return {
|
|
4178
|
+
values,
|
|
4179
|
+
dragging,
|
|
4180
|
+
activeIndex,
|
|
4181
|
+
inputs,
|
|
4182
|
+
controlRef,
|
|
4183
|
+
railRef,
|
|
4184
|
+
onPointerDown,
|
|
4185
|
+
onKeyDown,
|
|
4186
|
+
onKeyUp,
|
|
4187
|
+
onInput,
|
|
4188
|
+
onFocus: (index) => {
|
|
4189
|
+
lastIndex.current = index;
|
|
4190
|
+
setActiveIndex(index);
|
|
4191
|
+
},
|
|
4192
|
+
onBlur: () => {
|
|
4193
|
+
if (session.current?.reason === "keyboard") finish(!controlRef.current?.ownerDocument.hasFocus());
|
|
4194
|
+
}
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
//#endregion
|
|
4198
|
+
//#region src/components/slider/slider.tsx
|
|
4199
|
+
function SliderControl({ values, defaults, names, thumbLabels, range = false, minStepsBetweenThumbs = 0, min = 0, max = 100, step = 1, disabled = false, form, label, helperText, error, showValue = true, formatValue = String, getValueText, marks, change, commit, className, ref, onPointerDown, onKeyDown, onKeyUp, onBlur, onFocus, "aria-label": ariaLabel, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
|
|
4200
|
+
const id = useId();
|
|
4201
|
+
const scale = createScale(min, max, step, minStepsBetweenThumbs);
|
|
4202
|
+
const length = range ? 2 : 1;
|
|
4203
|
+
const initial = scale.validate(defaults ?? (range ? [min, max] : [min]), length);
|
|
4204
|
+
const selected = values === void 0 ? void 0 : scale.validate(values, length);
|
|
4205
|
+
const hasLabel = isPresent(label);
|
|
4206
|
+
if (!hasLabel && !ariaLabel?.trim()) throw new Error("Slider requires a label or aria-label");
|
|
4207
|
+
if (range && (!thumbLabels || thumbLabels.length !== 2 || thumbLabels.some((text) => !text.trim()))) throw new Error("RangeSlider requires two non-empty thumbLabels");
|
|
4208
|
+
if (names && (names.length !== length || names.some((name) => !name.trim()))) throw new Error("Slider requires one non-empty name per thumb");
|
|
4209
|
+
if (marks) {
|
|
4210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4211
|
+
for (const mark of marks) {
|
|
4212
|
+
const normalized = scale.validate([mark.value], 1)[0];
|
|
4213
|
+
if (seen.has(normalized)) throw new Error("Slider mark values must be unique");
|
|
4214
|
+
seen.add(normalized);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
const { inputs, controlRef, railRef, ...slider } = useSlider({
|
|
4218
|
+
scale,
|
|
4219
|
+
value: selected,
|
|
4220
|
+
defaultValue: initial,
|
|
4221
|
+
disabled,
|
|
4222
|
+
form,
|
|
4223
|
+
onValueChange: change,
|
|
4224
|
+
onValueCommit: commit
|
|
4225
|
+
});
|
|
4226
|
+
const mergedRef = useMergedRef(useRef(null), ref);
|
|
4227
|
+
const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
|
|
4228
|
+
const message = error && error !== true ? error : helperText;
|
|
4229
|
+
const description = [describedBy, isPresent(message) && `${id}-description`].filter(Boolean).join(" ") || void 0;
|
|
4230
|
+
const percentages = slider.values.map((value) => scale.toTick(value) / scale.count * 100);
|
|
4231
|
+
const start = range ? percentages[0] : 0;
|
|
4232
|
+
const end = percentages.at(-1);
|
|
4233
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4234
|
+
...props,
|
|
4235
|
+
ref: mergedRef,
|
|
4236
|
+
role: "group",
|
|
4237
|
+
"aria-label": hasLabel ? void 0 : ariaLabel,
|
|
4238
|
+
"aria-labelledby": hasLabel ? `${id}-label` : void 0,
|
|
4239
|
+
"data-slot": "slider",
|
|
4240
|
+
"data-range": range || void 0,
|
|
4241
|
+
"data-disabled": disabled || void 0,
|
|
4242
|
+
"data-invalid": invalid || void 0,
|
|
4243
|
+
"data-dragging": slider.dragging || void 0,
|
|
4244
|
+
className: cn("min-w-0 has-[input:disabled]:opacity-50 forced-colors:has-[input:disabled]:opacity-100", className),
|
|
4245
|
+
onPointerDown: (event) => {
|
|
4246
|
+
onPointerDown?.(event);
|
|
4247
|
+
if (!event.defaultPrevented && controlRef.current?.contains(event.target)) slider.onPointerDown(event);
|
|
4248
|
+
},
|
|
4249
|
+
onKeyDown: (event) => {
|
|
4250
|
+
onKeyDown?.(event);
|
|
4251
|
+
const index = inputs.current.indexOf(event.target);
|
|
4252
|
+
if (!event.defaultPrevented && index !== -1) slider.onKeyDown(event, index);
|
|
4253
|
+
},
|
|
4254
|
+
onKeyUp: (event) => {
|
|
4255
|
+
onKeyUp?.(event);
|
|
4256
|
+
if (inputs.current.includes(event.target)) slider.onKeyUp(event);
|
|
4257
|
+
},
|
|
4258
|
+
onBlur: (event) => {
|
|
4259
|
+
onBlur?.(event);
|
|
4260
|
+
if (inputs.current.includes(event.target)) slider.onBlur();
|
|
4261
|
+
},
|
|
4262
|
+
onFocus: (event) => {
|
|
4263
|
+
onFocus?.(event);
|
|
4264
|
+
const index = inputs.current.indexOf(event.target);
|
|
4265
|
+
if (index !== -1) slider.onFocus(index);
|
|
4266
|
+
},
|
|
4267
|
+
children: [
|
|
4268
|
+
(hasLabel || showValue) && /* @__PURE__ */ jsxs("div", {
|
|
4269
|
+
className: "mb-0.5 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",
|
|
4270
|
+
children: [hasLabel && /* @__PURE__ */ jsx("span", {
|
|
4271
|
+
id: `${id}-label`,
|
|
4272
|
+
className: "min-w-0 text-sm font-medium wrap-anywhere text-fg",
|
|
4273
|
+
children: label
|
|
4274
|
+
}), showValue && /* @__PURE__ */ jsx("span", {
|
|
4275
|
+
"data-slot": "slider-value",
|
|
4276
|
+
"aria-hidden": true,
|
|
4277
|
+
className: "ms-auto text-sm font-medium wrap-anywhere text-fg tabular-nums",
|
|
4278
|
+
children: /* @__PURE__ */ jsx("bdi", {
|
|
4279
|
+
dir: "ltr",
|
|
4280
|
+
children: slider.values.map(formatValue).join(" – ")
|
|
4281
|
+
})
|
|
4282
|
+
})]
|
|
4283
|
+
}),
|
|
4284
|
+
/* @__PURE__ */ jsx("div", {
|
|
4285
|
+
ref: controlRef,
|
|
4286
|
+
"data-slot": "slider-control",
|
|
4287
|
+
className: cn("group/slider-control relative h-11 touch-pan-y touch-pinch-zoom select-none", disabled ? "cursor-not-allowed" : "cursor-pointer"),
|
|
4288
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
4289
|
+
ref: railRef,
|
|
4290
|
+
className: "pointer-events-none absolute inset-x-5.5 top-0 h-full",
|
|
4291
|
+
children: [
|
|
4292
|
+
/* @__PURE__ */ jsx("div", {
|
|
4293
|
+
"aria-hidden": true,
|
|
4294
|
+
"data-slot": "slider-track",
|
|
4295
|
+
className: "absolute inset-x-0 top-5 h-1 rounded-full border border-input-border bg-surface-muted forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]"
|
|
4296
|
+
}),
|
|
4297
|
+
/* @__PURE__ */ jsx("div", {
|
|
4298
|
+
"aria-hidden": true,
|
|
4299
|
+
"data-slot": "slider-range",
|
|
4300
|
+
className: cn("absolute top-5 h-1 rounded-full forced-colors:forced-color-adjust-none forced-colors:bg-[Highlight]", invalid ? "bg-danger" : "bg-control-checked", disabled && "forced-colors:bg-[GrayText]"),
|
|
4301
|
+
style: {
|
|
4302
|
+
insetInlineStart: `${start}%`,
|
|
4303
|
+
width: `${end - start}%`
|
|
4304
|
+
}
|
|
4305
|
+
}),
|
|
4306
|
+
slider.values.map((value, i) => {
|
|
4307
|
+
const index = i;
|
|
4308
|
+
const [low, high] = scale.limits(slider.values, index);
|
|
4309
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [range && /* @__PURE__ */ jsx("span", {
|
|
4310
|
+
id: `${id}-thumb-label-${index}`,
|
|
4311
|
+
className: "sr-only",
|
|
4312
|
+
children: thumbLabels[index]
|
|
4313
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
4314
|
+
className: "absolute top-0 size-11 -translate-x-1/2 rtl:translate-x-1/2",
|
|
4315
|
+
style: {
|
|
4316
|
+
insetInlineStart: `${percentages[index]}%`,
|
|
4317
|
+
zIndex: slider.activeIndex === index ? 2 : 1
|
|
4318
|
+
},
|
|
4319
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
4320
|
+
ref: (node) => {
|
|
4321
|
+
inputs.current[index] = node;
|
|
4322
|
+
},
|
|
4323
|
+
type: "range",
|
|
4324
|
+
"data-slot": "slider-input",
|
|
4325
|
+
name: names?.[index],
|
|
4326
|
+
form,
|
|
4327
|
+
min: scale.fromTick(low),
|
|
4328
|
+
max: scale.fromTick(high),
|
|
4329
|
+
step,
|
|
4330
|
+
value,
|
|
4331
|
+
disabled,
|
|
4332
|
+
"aria-label": hasLabel ? void 0 : [ariaLabel, thumbLabels?.[index]].filter(Boolean).join(" "),
|
|
4333
|
+
"aria-labelledby": hasLabel ? [`${id}-label`, range && `${id}-thumb-label-${index}`].filter(Boolean).join(" ") : void 0,
|
|
4334
|
+
"aria-describedby": description,
|
|
4335
|
+
"aria-invalid": invalid ? true : ariaInvalid,
|
|
4336
|
+
"aria-valuetext": getValueText?.(value, index) ?? formatValue(value),
|
|
4337
|
+
className: "peer pointer-events-none absolute inset-0 m-0 size-full opacity-0",
|
|
4338
|
+
onChange: (event) => slider.onInput(event.currentTarget.valueAsNumber, index)
|
|
4339
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4340
|
+
"aria-hidden": true,
|
|
4341
|
+
"data-slot": "slider-thumb",
|
|
4342
|
+
"data-active": slider.dragging && slider.activeIndex === index || void 0,
|
|
4343
|
+
className: cn("pointer-events-none absolute inset-s-3.25 top-3.25 size-4.5 rounded-full border bg-surface-raised", "peer-focus-visible:outline-solid peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-focus", "transition-[border-color,box-shadow] duration-100 ease-out motion-reduce:transition-none", "data-active:[box-shadow:0_0_0_4px_var(--color-input-halo)]", invalid ? "border-danger" : "border-control-checked peer-enabled:group-hover/slider-control:border-input-border-hover", "forced-colors:forced-color-adjust-none forced-colors:border-[ButtonText] forced-colors:bg-[ButtonFace] forced-colors:shadow-none", "forced-colors:peer-focus-visible:outline-[Highlight] forced-colors:peer-disabled:border-[GrayText]")
|
|
4344
|
+
})]
|
|
4345
|
+
})] }, index);
|
|
4346
|
+
})
|
|
4347
|
+
]
|
|
4348
|
+
})
|
|
4349
|
+
}),
|
|
4350
|
+
marks?.length ? /* @__PURE__ */ jsx("div", {
|
|
4351
|
+
"aria-hidden": true,
|
|
4352
|
+
"data-slot": "slider-marks",
|
|
4353
|
+
className: "relative mx-5.5 min-h-6 text-xs text-fg-muted tabular-nums",
|
|
4354
|
+
children: marks.map((mark) => {
|
|
4355
|
+
const fraction = scale.toTick(mark.value) / scale.count;
|
|
4356
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4357
|
+
className: "absolute top-0 w-0",
|
|
4358
|
+
style: { insetInlineStart: `${fraction * 100}%` },
|
|
4359
|
+
children: [/* @__PURE__ */ jsx("span", { className: "block h-1 w-px bg-input-border forced-colors:bg-[CanvasText]" }), /* @__PURE__ */ jsx("span", {
|
|
4360
|
+
className: cn("mt-0.5 inline-block whitespace-nowrap", fraction === 1 ? "-translate-x-full rtl:translate-x-full" : fraction !== 0 && "-translate-x-1/2 rtl:translate-x-1/2"),
|
|
4361
|
+
children: mark.label
|
|
4362
|
+
})]
|
|
4363
|
+
}, mark.value);
|
|
4364
|
+
})
|
|
4365
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
4366
|
+
"aria-hidden": true,
|
|
4367
|
+
className: "mx-5.5 flex justify-between gap-2 text-xs text-fg-muted tabular-nums",
|
|
4368
|
+
children: [/* @__PURE__ */ jsx("span", { children: formatValue(min) }), /* @__PURE__ */ jsx("span", { children: formatValue(max) })]
|
|
4369
|
+
}),
|
|
4370
|
+
isPresent(message) && /* @__PURE__ */ jsxs("p", {
|
|
4371
|
+
id: `${id}-description`,
|
|
4372
|
+
className: cn("mt-2 mb-0 flex items-start gap-1 text-xs/relaxed", invalid ? "text-danger" : "text-fg-muted"),
|
|
4373
|
+
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
4374
|
+
"aria-hidden": true,
|
|
4375
|
+
className: "mt-0.5 size-3.5 shrink-0"
|
|
4376
|
+
}), /* @__PURE__ */ jsx("span", { children: message })]
|
|
4377
|
+
})
|
|
4378
|
+
]
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
function Slider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
|
|
4382
|
+
return /* @__PURE__ */ jsx(SliderControl, {
|
|
4383
|
+
...props,
|
|
4384
|
+
values: value === void 0 ? void 0 : [value],
|
|
4385
|
+
defaults: defaultValue === void 0 ? void 0 : [defaultValue],
|
|
4386
|
+
names: name === void 0 ? void 0 : [name],
|
|
4387
|
+
change: (next, details) => onValueChange?.(next[0], details),
|
|
4388
|
+
commit: (next, details) => onValueCommit?.(next[0], details)
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
4391
|
+
function RangeSlider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
|
|
4392
|
+
return /* @__PURE__ */ jsx(SliderControl, {
|
|
4393
|
+
...props,
|
|
4394
|
+
range: true,
|
|
4395
|
+
values: value,
|
|
4396
|
+
defaults: defaultValue,
|
|
4397
|
+
names: name,
|
|
4398
|
+
change: (next, details) => onValueChange?.([next[0], next[1]], details),
|
|
4399
|
+
commit: (next, details) => onValueCommit?.([next[0], next[1]], details)
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
//#endregion
|
|
2383
4403
|
//#region src/components/switch/switch.tsx
|
|
2384
4404
|
const sizeClasses = {
|
|
2385
4405
|
sm: {
|
|
@@ -2432,13 +4452,240 @@ function Switch({ label, description, error, size = "md", loading = false, label
|
|
|
2432
4452
|
], "checked:focus-visible:[--switch-ring:transparent] checked:focus-visible:[--switch-halo:transparent]", "checked:focus-visible:outline-solid checked:focus-visible:outline-2 checked:focus-visible:outline-offset-2", row.invalid && "not-forced-colors:checked:focus-visible:outline-danger", "forced-colors:forced-color-adjust-none forced-colors:[box-shadow:none]", "forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:disabled:checked:bg-[GrayText]", "forced-colors:focus-visible:outline-[CanvasText]", inputClassName)
|
|
2433
4453
|
}), /* @__PURE__ */ jsx("span", {
|
|
2434
4454
|
"aria-hidden": true,
|
|
2435
|
-
className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-
|
|
2436
|
-
children: loading && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
|
|
4455
|
+
className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-switch-handle peer-checked:bg-switch-handle-checked text-control-checked shadow-switch-thumb", "[--switch-dir:1] rtl:[--switch-dir:-1] [translate:calc(var(--switch-x)*var(--switch-dir))_0]", "[--switch-x:0px] [--switch-thumb-w:var(--switch-thumb)] peer-checked:[--switch-x:var(--switch-travel)]", "transition-[translate,width] duration-150 ease-in-out", pressable && ["peer-active:[--switch-thumb-w:calc(var(--switch-thumb)+var(--switch-stretch))]", "peer-checked:peer-active:[--switch-x:calc(var(--switch-travel)-var(--switch-stretch))]"], "peer-disabled:shadow-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none", "forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]", "forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-checked:text-[Highlight]", "forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-disabled:peer-checked:bg-[Canvas]", s.icon),
|
|
4456
|
+
children: loading && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
|
|
2437
4457
|
})]
|
|
2438
4458
|
})
|
|
2439
4459
|
});
|
|
2440
4460
|
}
|
|
2441
4461
|
//#endregion
|
|
4462
|
+
//#region src/components/table/table.tsx
|
|
4463
|
+
const labels = {
|
|
4464
|
+
search: "Search rows",
|
|
4465
|
+
clearSelection: "Clear selection",
|
|
4466
|
+
selectPage: "Select all rows on this page",
|
|
4467
|
+
selectRow: (id) => `Select row ${id}`,
|
|
4468
|
+
selectionCount: (count) => `${count} selected`,
|
|
4469
|
+
scrollRegion: "Table",
|
|
4470
|
+
pagination: "Pagination",
|
|
4471
|
+
previousPage: "Previous page",
|
|
4472
|
+
nextPage: "Next page",
|
|
4473
|
+
page: (page, count) => `${page} / ${count}`,
|
|
4474
|
+
range: (first, last, total) => total === 0 ? "0 rows" : `${first}–${last} of ${total}`,
|
|
4475
|
+
emptyState: "No rows yet",
|
|
4476
|
+
noResults: "No matching rows"
|
|
4477
|
+
};
|
|
4478
|
+
const alignClasses = {
|
|
4479
|
+
start: "text-start",
|
|
4480
|
+
center: "text-center",
|
|
4481
|
+
end: "text-end"
|
|
4482
|
+
};
|
|
4483
|
+
const sortIcons = {
|
|
4484
|
+
asc: ArrowUp,
|
|
4485
|
+
desc: ArrowDown
|
|
4486
|
+
};
|
|
4487
|
+
function Table({ data, columns, getRowId, title, emptyState, noResults, filtered = false, search, sorting, pagination, selection, className, tableClassName, labels: labelOverrides, ...props }) {
|
|
4488
|
+
const titleId = useId();
|
|
4489
|
+
const scrollRef = useRef(null);
|
|
4490
|
+
const searchRef = useRef(null);
|
|
4491
|
+
const emptyRef = useRef(null);
|
|
4492
|
+
const text = {
|
|
4493
|
+
...labels,
|
|
4494
|
+
...labelOverrides
|
|
4495
|
+
};
|
|
4496
|
+
const tableName = props["aria-labelledby"] ?? (title != null && !props["aria-label"] ? titleId : void 0);
|
|
4497
|
+
const showEmpty = data.length === 0 && !filtered && !search?.value.trim() && (!pagination || pagination.rowCount === 0);
|
|
4498
|
+
const showSearch = !showEmpty && search != null;
|
|
4499
|
+
const hasControls = showSearch || !!selection?.ids.size;
|
|
4500
|
+
const hasToolbar = title != null || hasControls;
|
|
4501
|
+
const rows = data.map((row) => ({
|
|
4502
|
+
row,
|
|
4503
|
+
id: getRowId(row),
|
|
4504
|
+
canSelect: selection?.canSelectRow?.(row) ?? true
|
|
4505
|
+
}));
|
|
4506
|
+
const selectableIds = selection ? rows.filter((row) => row.canSelect).map((row) => row.id) : [];
|
|
4507
|
+
const selectedOnPage = selectableIds.filter((id) => selection?.ids.has(id)).length;
|
|
4508
|
+
const allOnPageSelected = selectableIds.length > 0 && selectedOnPage === selectableIds.length;
|
|
4509
|
+
const updateSelection = (ids, checked) => {
|
|
4510
|
+
if (!selection) return;
|
|
4511
|
+
const next = new Set(selection.ids);
|
|
4512
|
+
for (const id of ids) if (checked) next.add(id);
|
|
4513
|
+
else next.delete(id);
|
|
4514
|
+
selection.onChange(next);
|
|
4515
|
+
};
|
|
4516
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4517
|
+
"data-slot": "table-root",
|
|
4518
|
+
className: cn("w-full min-w-0 bg-surface text-fg", className),
|
|
4519
|
+
children: [hasToolbar && /* @__PURE__ */ jsxs("div", {
|
|
4520
|
+
"data-slot": "table-toolbar",
|
|
4521
|
+
className: "flex flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3",
|
|
4522
|
+
children: [title != null && /* @__PURE__ */ jsx("span", {
|
|
4523
|
+
id: titleId,
|
|
4524
|
+
className: "text-sm font-semibold",
|
|
4525
|
+
children: title
|
|
4526
|
+
}), hasControls && /* @__PURE__ */ jsxs("div", {
|
|
4527
|
+
className: "ms-auto flex min-w-0 flex-wrap items-center justify-end gap-x-3 gap-y-2",
|
|
4528
|
+
children: [
|
|
4529
|
+
selection && /* @__PURE__ */ jsx("output", {
|
|
4530
|
+
className: "text-xs text-fg-muted",
|
|
4531
|
+
children: selection.ids.size > 0 && text.selectionCount(selection.ids.size)
|
|
4532
|
+
}),
|
|
4533
|
+
!!selection?.ids.size && /* @__PURE__ */ jsx(Button, {
|
|
4534
|
+
intent: "ghost",
|
|
4535
|
+
size: "sm",
|
|
4536
|
+
ripple: false,
|
|
4537
|
+
onClick: () => {
|
|
4538
|
+
selection.onChange(/* @__PURE__ */ new Set());
|
|
4539
|
+
(searchRef.current ?? scrollRef.current ?? emptyRef.current)?.focus();
|
|
4540
|
+
},
|
|
4541
|
+
children: text.clearSelection
|
|
4542
|
+
}),
|
|
4543
|
+
showSearch && /* @__PURE__ */ jsx(SearchInput, {
|
|
4544
|
+
ref: searchRef,
|
|
4545
|
+
"aria-label": text.search,
|
|
4546
|
+
placeholder: `${text.search}…`,
|
|
4547
|
+
size: "sm",
|
|
4548
|
+
className: "w-48 max-w-full",
|
|
4549
|
+
value: search.value,
|
|
4550
|
+
onChange: (event) => search.onChange(event.currentTarget.value)
|
|
4551
|
+
})
|
|
4552
|
+
]
|
|
4553
|
+
})]
|
|
4554
|
+
}), showEmpty ? /* @__PURE__ */ jsx("div", {
|
|
4555
|
+
ref: emptyRef,
|
|
4556
|
+
"data-slot": "table-empty",
|
|
4557
|
+
tabIndex: -1,
|
|
4558
|
+
className: "grid min-h-48 place-items-center px-4 py-8 text-center text-sm text-fg-muted outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
4559
|
+
children: emptyState === void 0 ? text.emptyState : emptyState
|
|
4560
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("section", {
|
|
4561
|
+
ref: scrollRef,
|
|
4562
|
+
"data-slot": "table-scroll",
|
|
4563
|
+
"aria-labelledby": tableName,
|
|
4564
|
+
"aria-label": tableName ? void 0 : props["aria-label"] ?? text.scrollRegion,
|
|
4565
|
+
tabIndex: 0,
|
|
4566
|
+
className: "relative w-full overflow-x-auto outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
4567
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
4568
|
+
...props,
|
|
4569
|
+
"aria-labelledby": tableName,
|
|
4570
|
+
"data-slot": "table",
|
|
4571
|
+
className: cn("w-full border-separate border-spacing-0 text-sm", tableClassName),
|
|
4572
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
4573
|
+
"data-slot": "table-header",
|
|
4574
|
+
children: /* @__PURE__ */ jsxs("tr", { children: [selection && /* @__PURE__ */ jsx("th", {
|
|
4575
|
+
scope: "col",
|
|
4576
|
+
className: "w-12 border-b border-border px-3",
|
|
4577
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
4578
|
+
"aria-label": text.selectPage,
|
|
4579
|
+
checked: allOnPageSelected,
|
|
4580
|
+
indeterminate: selectedOnPage > 0 && !allOnPageSelected,
|
|
4581
|
+
disabled: selectableIds.length === 0,
|
|
4582
|
+
onChange: (event) => updateSelection(selectableIds, event.currentTarget.checked),
|
|
4583
|
+
className: "mx-auto justify-center"
|
|
4584
|
+
})
|
|
4585
|
+
}), columns.map((column) => {
|
|
4586
|
+
const direction = sorting?.value?.id === column.id ? sorting.value.direction : void 0;
|
|
4587
|
+
const SortIcon = direction ? sortIcons[direction] : ArrowUpDown;
|
|
4588
|
+
const align = column.align ?? (column.numeric ? "end" : "start");
|
|
4589
|
+
return /* @__PURE__ */ jsx("th", {
|
|
4590
|
+
scope: "col",
|
|
4591
|
+
"aria-sort": direction ? direction === "asc" ? "ascending" : "descending" : void 0,
|
|
4592
|
+
className: cn("h-10 border-b border-border px-4 text-xs font-medium whitespace-nowrap text-fg-muted", alignClasses[align], column.headerClassName),
|
|
4593
|
+
children: sorting && column.sortable ? /* @__PURE__ */ jsxs("button", {
|
|
4594
|
+
type: "button",
|
|
4595
|
+
className: cn("inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-sm hover:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus pointer-coarse:min-h-11", direction && "text-fg"),
|
|
4596
|
+
onClick: () => sorting.onChange(direction === "desc" ? null : {
|
|
4597
|
+
id: column.id,
|
|
4598
|
+
direction: direction === "asc" ? "desc" : "asc"
|
|
4599
|
+
}),
|
|
4600
|
+
children: [column.header, /* @__PURE__ */ jsx(SortIcon, {
|
|
4601
|
+
"aria-hidden": true,
|
|
4602
|
+
className: "size-3.5 shrink-0"
|
|
4603
|
+
})]
|
|
4604
|
+
}) : column.header
|
|
4605
|
+
}, column.id);
|
|
4606
|
+
})] })
|
|
4607
|
+
}), /* @__PURE__ */ jsxs("tbody", {
|
|
4608
|
+
"data-slot": "table-body",
|
|
4609
|
+
className: "[&>tr:not(:last-child)>*]:border-b [&>tr>*]:border-border",
|
|
4610
|
+
children: [rows.map(({ row, id, canSelect }) => {
|
|
4611
|
+
const selected = selection?.ids.has(id) ?? false;
|
|
4612
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
4613
|
+
"data-slot": "table-row",
|
|
4614
|
+
"data-selected": selected || void 0,
|
|
4615
|
+
className: "hover:bg-surface-muted data-selected:bg-surface-muted",
|
|
4616
|
+
children: [selection && /* @__PURE__ */ jsx("td", {
|
|
4617
|
+
className: "w-12 px-3",
|
|
4618
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
4619
|
+
"aria-label": text.selectRow(id),
|
|
4620
|
+
checked: selected,
|
|
4621
|
+
disabled: !canSelect,
|
|
4622
|
+
onChange: (event) => updateSelection([id], event.currentTarget.checked),
|
|
4623
|
+
className: "mx-auto justify-center"
|
|
4624
|
+
})
|
|
4625
|
+
}), columns.map((column) => {
|
|
4626
|
+
const Cell = column.rowHeader ? "th" : "td";
|
|
4627
|
+
const align = column.align ?? (column.numeric ? "end" : "start");
|
|
4628
|
+
return /* @__PURE__ */ jsx(Cell, {
|
|
4629
|
+
scope: column.rowHeader ? "row" : void 0,
|
|
4630
|
+
className: cn("h-13 px-4 py-2 align-middle", alignClasses[align], column.numeric && "tabular-nums", column.rowHeader && "font-medium", column.className),
|
|
4631
|
+
children: column.cell(row)
|
|
4632
|
+
}, column.id);
|
|
4633
|
+
})]
|
|
4634
|
+
}, id);
|
|
4635
|
+
}), rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
4636
|
+
colSpan: Math.max(1, columns.length + Number(Boolean(selection))),
|
|
4637
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4638
|
+
"data-slot": "table-no-results",
|
|
4639
|
+
className: "sticky inset-s-0 w-max max-w-[min(100%,18rem)] px-4 py-12 text-sm text-fg-muted",
|
|
4640
|
+
children: noResults === void 0 ? /* @__PURE__ */ jsx("output", { children: text.noResults }) : noResults
|
|
4641
|
+
})
|
|
4642
|
+
}) })]
|
|
4643
|
+
})]
|
|
4644
|
+
})
|
|
4645
|
+
}), pagination && /* @__PURE__ */ jsx(TablePagination, {
|
|
4646
|
+
...pagination,
|
|
4647
|
+
visibleCount: rows.length,
|
|
4648
|
+
text
|
|
4649
|
+
})] })]
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
function TablePagination({ pageIndex, pageSize, rowCount, visibleCount, onPageChange, text }) {
|
|
4653
|
+
const count = Math.ceil(rowCount / pageSize);
|
|
4654
|
+
const first = visibleCount ? pageIndex * pageSize + 1 : 0;
|
|
4655
|
+
const last = first ? first + visibleCount - 1 : 0;
|
|
4656
|
+
const canGoBack = pageIndex > 0;
|
|
4657
|
+
const canGoForward = pageIndex + 1 < count;
|
|
4658
|
+
return /* @__PURE__ */ jsxs("nav", {
|
|
4659
|
+
"data-slot": "table-pagination",
|
|
4660
|
+
"aria-label": text.pagination,
|
|
4661
|
+
className: "flex min-h-12 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-t border-border px-4 py-1 text-xs text-fg-muted tabular-nums",
|
|
4662
|
+
children: [/* @__PURE__ */ jsx("output", { children: text.range(first, last, rowCount) }), /* @__PURE__ */ jsxs("span", {
|
|
4663
|
+
className: "flex items-center gap-2",
|
|
4664
|
+
children: [
|
|
4665
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4666
|
+
icon: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:rotate-180" }),
|
|
4667
|
+
"aria-label": text.previousPage,
|
|
4668
|
+
intent: "ghost",
|
|
4669
|
+
size: "sm",
|
|
4670
|
+
ripple: false,
|
|
4671
|
+
disabled: !canGoBack,
|
|
4672
|
+
onClick: () => onPageChange(pageIndex - 1)
|
|
4673
|
+
}),
|
|
4674
|
+
/* @__PURE__ */ jsx("span", { children: count === 0 ? "—" : text.page(pageIndex + 1, count) }),
|
|
4675
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4676
|
+
icon: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:rotate-180" }),
|
|
4677
|
+
"aria-label": text.nextPage,
|
|
4678
|
+
intent: "ghost",
|
|
4679
|
+
size: "sm",
|
|
4680
|
+
ripple: false,
|
|
4681
|
+
disabled: !canGoForward,
|
|
4682
|
+
onClick: () => onPageChange(pageIndex + 1)
|
|
4683
|
+
})
|
|
4684
|
+
]
|
|
4685
|
+
})]
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
//#endregion
|
|
2442
4689
|
//#region src/components/tabs/tab-nav.tsx
|
|
2443
4690
|
const TabNavContext = createContext(null);
|
|
2444
4691
|
/**
|
|
@@ -2645,6 +4892,155 @@ function TabPanel({ value, keepMounted = false, className, ...props }) {
|
|
|
2645
4892
|
});
|
|
2646
4893
|
}
|
|
2647
4894
|
//#endregion
|
|
4895
|
+
//#region src/components/textarea/use-auto-resize.ts
|
|
4896
|
+
/** Measure the native control so wrapping, fonts and IME text keep their browser behavior. */
|
|
4897
|
+
function useAutoResize(ref, enabled, minRows, maxRows) {
|
|
4898
|
+
const resize = useCallback(() => {
|
|
4899
|
+
const node = ref.current;
|
|
4900
|
+
if (!enabled || !node || node.getClientRects().length === 0) return;
|
|
4901
|
+
const style = getComputedStyle(node);
|
|
4902
|
+
const lineHeight = Number.parseFloat(style.lineHeight);
|
|
4903
|
+
if (!Number.isFinite(lineHeight)) return;
|
|
4904
|
+
const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
|
|
4905
|
+
const border = Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth);
|
|
4906
|
+
const min = minRows * lineHeight + padding;
|
|
4907
|
+
const max = maxRows == null ? Infinity : maxRows * lineHeight + padding;
|
|
4908
|
+
const scrollTop = node.scrollTop;
|
|
4909
|
+
node.style.overflowY = "hidden";
|
|
4910
|
+
node.style.height = "0px";
|
|
4911
|
+
const contentHeight = node.scrollHeight;
|
|
4912
|
+
const height = Math.max(min, Math.min(max, contentHeight));
|
|
4913
|
+
node.style.height = `${height + (style.boxSizing === "border-box" ? border : -padding)}px`;
|
|
4914
|
+
node.style.overflowY = contentHeight > max ? "auto" : "hidden";
|
|
4915
|
+
node.scrollTop = scrollTop;
|
|
4916
|
+
}, [
|
|
4917
|
+
enabled,
|
|
4918
|
+
ref,
|
|
4919
|
+
minRows,
|
|
4920
|
+
maxRows
|
|
4921
|
+
]);
|
|
4922
|
+
useLayoutEffect(resize);
|
|
4923
|
+
useLayoutEffect(() => {
|
|
4924
|
+
if (!enabled) return;
|
|
4925
|
+
const node = ref.current;
|
|
4926
|
+
if (!node) return;
|
|
4927
|
+
let width = node.getBoundingClientRect().width;
|
|
4928
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(() => {
|
|
4929
|
+
const nextWidth = node.getBoundingClientRect().width;
|
|
4930
|
+
if (nextWidth !== width) {
|
|
4931
|
+
width = nextWidth;
|
|
4932
|
+
resize();
|
|
4933
|
+
}
|
|
4934
|
+
});
|
|
4935
|
+
observer?.observe(node);
|
|
4936
|
+
window.addEventListener("resize", resize);
|
|
4937
|
+
document.fonts?.addEventListener("loadingdone", resize);
|
|
4938
|
+
let active = true;
|
|
4939
|
+
document.fonts?.ready.then(() => {
|
|
4940
|
+
if (active) resize();
|
|
4941
|
+
});
|
|
4942
|
+
return () => {
|
|
4943
|
+
active = false;
|
|
4944
|
+
observer?.disconnect();
|
|
4945
|
+
window.removeEventListener("resize", resize);
|
|
4946
|
+
document.fonts?.removeEventListener("loadingdone", resize);
|
|
4947
|
+
node.style.removeProperty("height");
|
|
4948
|
+
node.style.removeProperty("overflow-y");
|
|
4949
|
+
};
|
|
4950
|
+
}, [
|
|
4951
|
+
enabled,
|
|
4952
|
+
ref,
|
|
4953
|
+
resize
|
|
4954
|
+
]);
|
|
4955
|
+
return resize;
|
|
4956
|
+
}
|
|
4957
|
+
//#endregion
|
|
4958
|
+
//#region src/components/textarea/textarea.tsx
|
|
4959
|
+
const sizes = {
|
|
4960
|
+
sm: "px-3 py-2 text-sm/5",
|
|
4961
|
+
md: "px-3 py-2.5 text-sm/5",
|
|
4962
|
+
lg: "px-4 py-3 text-base/6"
|
|
4963
|
+
};
|
|
4964
|
+
function positiveRows(value, name) {
|
|
4965
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError(`Textarea ${name} must be a positive integer.`);
|
|
4966
|
+
return value;
|
|
4967
|
+
}
|
|
4968
|
+
const textLength = (value) => String(value ?? "").replace(/\r\n?/g, "\n").length;
|
|
4969
|
+
function Textarea({ label, floating = false, helperText, error, size = "md", autoResize = false, rows = 3, minRows = 3, maxRows, resize = "vertical", showCount = false, mono = false, className, textareaClassName, ref, id, required, disabled, readOnly, placeholder, value, defaultValue, maxLength, onChange, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
|
|
4970
|
+
const nativeRows = positiveRows(autoResize ? minRows : rows, autoResize ? "minRows" : "rows");
|
|
4971
|
+
if (autoResize && maxRows != null && positiveRows(maxRows, "maxRows") < nativeRows) throw new RangeError("Textarea maxRows must be greater than or equal to minRows.");
|
|
4972
|
+
const controlRef = useRef(null);
|
|
4973
|
+
const mergedRef = useMergedRef(controlRef, ref);
|
|
4974
|
+
const [uncontrolledLength, setUncontrolledLength] = useState(() => textLength(defaultValue));
|
|
4975
|
+
const length = value == null ? uncontrolledLength : textLength(value);
|
|
4976
|
+
const overLimit = maxLength != null && maxLength >= 0 && length > maxLength;
|
|
4977
|
+
const countId = `${useId()}-count`;
|
|
4978
|
+
const resizeToContent = useAutoResize(controlRef, autoResize, nativeRows, maxRows);
|
|
4979
|
+
useLayoutEffect(() => {
|
|
4980
|
+
const node = controlRef.current;
|
|
4981
|
+
const form = node?.form;
|
|
4982
|
+
if (!form || !node) return;
|
|
4983
|
+
let resetTimer;
|
|
4984
|
+
const reset = (event) => {
|
|
4985
|
+
clearTimeout(resetTimer);
|
|
4986
|
+
resetTimer = setTimeout(() => {
|
|
4987
|
+
if (event.defaultPrevented) return;
|
|
4988
|
+
setUncontrolledLength(node.value.length);
|
|
4989
|
+
resizeToContent();
|
|
4990
|
+
}, 0);
|
|
4991
|
+
};
|
|
4992
|
+
form.addEventListener("reset", reset);
|
|
4993
|
+
return () => {
|
|
4994
|
+
clearTimeout(resetTimer);
|
|
4995
|
+
form.removeEventListener("reset", reset);
|
|
4996
|
+
};
|
|
4997
|
+
}, [props.form, resizeToContent]);
|
|
4998
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
4999
|
+
multiline: true,
|
|
5000
|
+
label,
|
|
5001
|
+
floating,
|
|
5002
|
+
helperText,
|
|
5003
|
+
error: error || overLimit,
|
|
5004
|
+
size,
|
|
5005
|
+
id,
|
|
5006
|
+
required,
|
|
5007
|
+
disabled,
|
|
5008
|
+
readOnly,
|
|
5009
|
+
"aria-describedby": [describedBy, showCount ? countId : null].filter(Boolean).join(" ") || void 0,
|
|
5010
|
+
className,
|
|
5011
|
+
controlRef,
|
|
5012
|
+
footer: showCount ? /* @__PURE__ */ jsxs("span", {
|
|
5013
|
+
id: countId,
|
|
5014
|
+
className: cn("ms-auto shrink-0 text-xs/snug tabular-nums", overLimit ? "text-danger" : "text-fg-muted"),
|
|
5015
|
+
children: [length, maxLength != null && maxLength >= 0 ? ` / ${maxLength}` : ""]
|
|
5016
|
+
}) : void 0,
|
|
5017
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [field.floatingLabel("max-w-[calc(100%-1rem)]"), /* @__PURE__ */ jsx("textarea", {
|
|
5018
|
+
...props,
|
|
5019
|
+
ref: mergedRef,
|
|
5020
|
+
id: field.id,
|
|
5021
|
+
rows: nativeRows,
|
|
5022
|
+
value,
|
|
5023
|
+
defaultValue,
|
|
5024
|
+
maxLength,
|
|
5025
|
+
required,
|
|
5026
|
+
disabled,
|
|
5027
|
+
readOnly,
|
|
5028
|
+
placeholder: field.floating ? placeholder || " " : placeholder,
|
|
5029
|
+
"aria-invalid": field.invalid ? true : ariaInvalid,
|
|
5030
|
+
"aria-describedby": field.describedBy,
|
|
5031
|
+
spellCheck: mono ? false : props.spellCheck,
|
|
5032
|
+
autoCapitalize: mono ? "off" : props.autoCapitalize,
|
|
5033
|
+
autoCorrect: mono ? "off" : props.autoCorrect,
|
|
5034
|
+
onChange: (event) => {
|
|
5035
|
+
setUncontrolledLength(event.currentTarget.value.length);
|
|
5036
|
+
resizeToContent();
|
|
5037
|
+
onChange?.(event);
|
|
5038
|
+
},
|
|
5039
|
+
className: cn("block w-full min-w-0 flex-1 rounded-[inherit] border-0 bg-transparent text-fg outline-none", "placeholder:text-input-placeholder disabled:cursor-not-allowed pointer-coarse:text-base", sizes[size], autoResize || resize === "none" ? "resize-none" : "resize-y", mono && "font-mono", field.floating && "placeholder:opacity-0 focus:placeholder:opacity-100", textareaClassName)
|
|
5040
|
+
})] })
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
//#endregion
|
|
2648
5044
|
//#region src/components/theme-switcher/theme-switcher.tsx
|
|
2649
5045
|
const systemThemeQuery = "(prefers-color-scheme: dark)";
|
|
2650
5046
|
function subscribeSystemTheme(onChange) {
|
|
@@ -3113,7 +5509,7 @@ function Viewport({ id, position = "bottom-right", visibleToasts = 3, host }) {
|
|
|
3113
5509
|
};
|
|
3114
5510
|
const [y, x] = position.split("-");
|
|
3115
5511
|
const directions = x === "center" ? [y] : [y, x];
|
|
3116
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5512
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3117
5513
|
/* @__PURE__ */ jsx("section", {
|
|
3118
5514
|
ref: viewportRef,
|
|
3119
5515
|
popover: "manual",
|
|
@@ -3450,4 +5846,4 @@ function ToastCard({ record, layout, total, expanded, paused, frontHeight, ownHe
|
|
|
3450
5846
|
});
|
|
3451
5847
|
}
|
|
3452
5848
|
//#endregion
|
|
3453
|
-
export { Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, Input, Meter, PasswordInput, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Tabs, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
|
|
5849
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, Combobox, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, Meter, PasswordInput, Radio, RadioGroup, RangeSlider, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Slider, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, Textarea, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
|