@xcv-ui/ui 0.0.0-stage → 1.0.1
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/LICENSE +17 -0
- package/README.md +72 -2
- package/dist/index.d.ts +424 -0
- package/dist/index.js +1559 -0
- package/package.json +39 -4
- package/styles/index.css +1 -0
- package/styles/primitives.css +1201 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1559 @@
|
|
|
1
|
+
// src/layout/Box.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var XcvBox = forwardRef(function XcvBox2({
|
|
5
|
+
display = "block",
|
|
6
|
+
direction,
|
|
7
|
+
align,
|
|
8
|
+
justify,
|
|
9
|
+
gap,
|
|
10
|
+
wrap,
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}, ref) {
|
|
14
|
+
const isFlex = display === "flex" || display === "inline-flex";
|
|
15
|
+
const supportsLayout = isFlex || display === "grid";
|
|
16
|
+
const classes = [
|
|
17
|
+
"xcv-box",
|
|
18
|
+
`xcv-box--${display}`,
|
|
19
|
+
supportsLayout && align && `xcv-box--align-${align}`,
|
|
20
|
+
supportsLayout && justify && `xcv-box--justify-${justify}`,
|
|
21
|
+
supportsLayout && gap && `xcv-box--gap-${gap}`,
|
|
22
|
+
isFlex && direction && `xcv-box--direction-${direction}`,
|
|
23
|
+
isFlex && wrap !== void 0 && (wrap ? "xcv-box--wrap" : "xcv-box--nowrap"),
|
|
24
|
+
className
|
|
25
|
+
].filter(Boolean).join(" ");
|
|
26
|
+
return /* @__PURE__ */ jsx("div", { ref, className: classes, ...props });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/layout/Stack.tsx
|
|
30
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
31
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
32
|
+
var XcvStack = forwardRef2(function XcvStack2({ gap = "md", align = "stretch", justify = "start", className, ...props }, ref) {
|
|
33
|
+
const classes = ["xcv-stack", `xcv-stack--gap-${gap}`, `xcv-stack--align-${align}`, `xcv-stack--justify-${justify}`, className].filter(Boolean).join(" ");
|
|
34
|
+
return /* @__PURE__ */ jsx2("div", { ref, className: classes, ...props });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/layout/Inline.tsx
|
|
38
|
+
import { forwardRef as forwardRef3 } from "react";
|
|
39
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
40
|
+
var XcvInline = forwardRef3(function XcvInline2({ gap = "sm", align = "center", justify = "start", wrap = true, className, ...props }, ref) {
|
|
41
|
+
const classes = ["xcv-inline", `xcv-inline--gap-${gap}`, `xcv-inline--align-${align}`, `xcv-inline--justify-${justify}`, wrap ? "xcv-inline--wrap" : "xcv-inline--nowrap", className].filter(Boolean).join(" ");
|
|
42
|
+
return /* @__PURE__ */ jsx3("div", { ref, className: classes, ...props });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/layout/Container.tsx
|
|
46
|
+
import { forwardRef as forwardRef4 } from "react";
|
|
47
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
48
|
+
var XcvContainer = forwardRef4(function XcvContainer2({ size = "lg", centered = true, className, ...props }, ref) {
|
|
49
|
+
const classes = ["xcv-container", `xcv-container--${size}`, centered && "xcv-container--centered", className].filter(Boolean).join(" ");
|
|
50
|
+
return /* @__PURE__ */ jsx4("div", { ref, className: classes, ...props });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/layout/Divider.tsx
|
|
54
|
+
import { forwardRef as forwardRef5 } from "react";
|
|
55
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
56
|
+
var XcvDivider = forwardRef5(function XcvDivider2({ orientation = "horizontal", spacing = "md", className, ...props }, ref) {
|
|
57
|
+
const classes = ["xcv-divider", `xcv-divider--${orientation}`, `xcv-divider--spacing-${spacing}`, className].filter(Boolean).join(" ");
|
|
58
|
+
if (orientation === "vertical") {
|
|
59
|
+
return /* @__PURE__ */ jsx5("span", { ...props, ref, role: "separator", "aria-orientation": "vertical", className: classes });
|
|
60
|
+
}
|
|
61
|
+
return /* @__PURE__ */ jsx5("hr", { ...props, ref, role: "separator", "aria-orientation": "horizontal", className: classes });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// src/layout/Surface.tsx
|
|
65
|
+
import { forwardRef as forwardRef6 } from "react";
|
|
66
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
67
|
+
var XcvSurface = forwardRef6(function XcvSurface2({ variant = "default", padding = "md", radius = "subtle", className, children, ...props }, ref) {
|
|
68
|
+
const classes = ["xcv-surface", `xcv-surface--${variant}`, `xcv-surface--padding-${padding}`, `xcv-surface--radius-${radius}`, className].filter(Boolean).join(" ");
|
|
69
|
+
return /* @__PURE__ */ jsx6("div", { ref, className: classes, "data-xcv-radius": radius, ...props, children });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/layout/Card.tsx
|
|
73
|
+
import { forwardRef as forwardRef7 } from "react";
|
|
74
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
75
|
+
function getCardClasses(variant, padding, radius, className) {
|
|
76
|
+
return [
|
|
77
|
+
"xcv-card",
|
|
78
|
+
`xcv-card--${variant}`,
|
|
79
|
+
`xcv-card--padding-${padding}`,
|
|
80
|
+
`xcv-card--radius-${radius}`,
|
|
81
|
+
className
|
|
82
|
+
].filter(Boolean).join(" ");
|
|
83
|
+
}
|
|
84
|
+
var CardRoot = forwardRef7(function CardRoot2({
|
|
85
|
+
variant = "default",
|
|
86
|
+
padding = "md",
|
|
87
|
+
radius = "subtle",
|
|
88
|
+
interactive = false,
|
|
89
|
+
accented = false,
|
|
90
|
+
className,
|
|
91
|
+
children,
|
|
92
|
+
...props
|
|
93
|
+
}, ref) {
|
|
94
|
+
return /* @__PURE__ */ jsx7(
|
|
95
|
+
"div",
|
|
96
|
+
{
|
|
97
|
+
ref,
|
|
98
|
+
className: getCardClasses(variant, padding, radius, className),
|
|
99
|
+
"data-xcv-radius": radius,
|
|
100
|
+
"data-xcv-interactive": interactive ? "on" : "off",
|
|
101
|
+
"data-xcv-accented": accented ? "on" : "off",
|
|
102
|
+
...props,
|
|
103
|
+
children
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
var CardContent = forwardRef7(function CardContent2({ className, ...props }, ref) {
|
|
108
|
+
return /* @__PURE__ */ jsx7("div", { ref, className: ["xcv-card__content", className].filter(Boolean).join(" "), ...props });
|
|
109
|
+
});
|
|
110
|
+
var CardFooter = forwardRef7(function CardFooter2({ className, ...props }, ref) {
|
|
111
|
+
return /* @__PURE__ */ jsx7("div", { ref, className: ["xcv-card__footer", className].filter(Boolean).join(" "), ...props });
|
|
112
|
+
});
|
|
113
|
+
var XcvCard = Object.assign(CardRoot, { Content: CardContent, Footer: CardFooter });
|
|
114
|
+
|
|
115
|
+
// src/layout/Media.tsx
|
|
116
|
+
import { forwardRef as forwardRef8 } from "react";
|
|
117
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
118
|
+
var ratios = { square: "1 / 1", video: "16 / 9", portrait: "3 / 4", landscape: "4 / 3", wide: "21 / 9" };
|
|
119
|
+
var XcvMedia = forwardRef8(function XcvMedia2({
|
|
120
|
+
aspectRatio,
|
|
121
|
+
radius = "subtle",
|
|
122
|
+
fit = "cover",
|
|
123
|
+
position = "center",
|
|
124
|
+
className,
|
|
125
|
+
style,
|
|
126
|
+
children,
|
|
127
|
+
...props
|
|
128
|
+
}, ref) {
|
|
129
|
+
const ratio = aspectRatio ? ratios[aspectRatio] ?? aspectRatio : void 0;
|
|
130
|
+
return /* @__PURE__ */ jsx8(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
ref,
|
|
134
|
+
className: ["xcv-media", ratio ? "xcv-media--ratio" : "xcv-media--intrinsic", className].filter(Boolean).join(" "),
|
|
135
|
+
"data-xcv-radius": radius,
|
|
136
|
+
style: { ...style, ...ratio !== void 0 ? { aspectRatio: ratio } : {}, "--xcv-media-fit": fit, "--xcv-media-position": position },
|
|
137
|
+
...props,
|
|
138
|
+
children
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/layout/List.tsx
|
|
144
|
+
import { forwardRef as forwardRef9 } from "react";
|
|
145
|
+
import { jsx as jsx9, jsxs } from "react/jsx-runtime";
|
|
146
|
+
var XcvList = forwardRef9(function XcvList2({ as = "ul", variant = "plain", gap = "md", marker, className, ...props }, ref) {
|
|
147
|
+
const Component = as;
|
|
148
|
+
const resolvedMarker = marker ?? (as === "ol" ? "decimal" : "none");
|
|
149
|
+
const classes = [
|
|
150
|
+
"xcv-list",
|
|
151
|
+
`xcv-list--${variant}`,
|
|
152
|
+
`xcv-list--gap-${gap}`,
|
|
153
|
+
`xcv-list--marker-${resolvedMarker}`,
|
|
154
|
+
className
|
|
155
|
+
].filter(Boolean).join(" ");
|
|
156
|
+
return /* @__PURE__ */ jsx9(Component, { ref, className: classes, ...props });
|
|
157
|
+
});
|
|
158
|
+
var XcvListItem = forwardRef9(function XcvListItem2({ leading, trailing, align = "center", className, children, ...props }, ref) {
|
|
159
|
+
const classes = ["xcv-list-item", `xcv-list-item--align-${align}`, className].filter(Boolean).join(" ");
|
|
160
|
+
return /* @__PURE__ */ jsx9("li", { ref, className: classes, ...props, children: /* @__PURE__ */ jsxs("div", { className: "xcv-list-item__layout", children: [
|
|
161
|
+
leading != null ? /* @__PURE__ */ jsx9("div", { className: "xcv-list-item__leading", children: leading }) : null,
|
|
162
|
+
/* @__PURE__ */ jsx9("div", { className: "xcv-list-item__content", children }),
|
|
163
|
+
trailing != null ? /* @__PURE__ */ jsx9("div", { className: "xcv-list-item__trailing", children: trailing }) : null
|
|
164
|
+
] }) });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// src/typography/Text.tsx
|
|
168
|
+
import { forwardRef as forwardRef10 } from "react";
|
|
169
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
170
|
+
var XcvText = forwardRef10(function XcvText2({ as: Component = "p", variant = "body", weight = "normal", color = "primary", className, ...props }, ref) {
|
|
171
|
+
const classes = ["xcv-text", `xcv-text--${variant}`, `xcv-text--weight-${weight}`, `xcv-text--color-${color}`, className].filter(Boolean).join(" ");
|
|
172
|
+
return /* @__PURE__ */ jsx10(Component, { ref, className: classes, ...props });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// src/typography/Title.tsx
|
|
176
|
+
import { forwardRef as forwardRef11 } from "react";
|
|
177
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
178
|
+
var XcvTitle = forwardRef11(function XcvTitle2({ as: Component = "h2", size = "xl", weight = "semibold", className, ...props }, ref) {
|
|
179
|
+
const classes = ["xcv-title", `xcv-title--${size}`, `xcv-title--weight-${weight}`, className].filter(Boolean).join(" ");
|
|
180
|
+
return /* @__PURE__ */ jsx11(Component, { ref, className: classes, ...props });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// src/typography/Label.tsx
|
|
184
|
+
import { forwardRef as forwardRef12 } from "react";
|
|
185
|
+
import { jsx as jsx12, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
186
|
+
var XcvLabel = forwardRef12(function XcvLabel2({ required, children, className, ...props }, ref) {
|
|
187
|
+
return /* @__PURE__ */ jsxs2("label", { ref, className: ["xcv-label", className].filter(Boolean).join(" "), ...props, children: [
|
|
188
|
+
children,
|
|
189
|
+
required && /* @__PURE__ */ jsx12("span", { className: "xcv-label__required", "aria-hidden": "true", children: " *" })
|
|
190
|
+
] });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// src/actions/Button.tsx
|
|
194
|
+
import { forwardRef as forwardRef13 } from "react";
|
|
195
|
+
import { jsx as jsx13, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
196
|
+
var XcvButton = forwardRef13(function XcvButton2({
|
|
197
|
+
variant = "primary",
|
|
198
|
+
size = "md",
|
|
199
|
+
radius = "subtle",
|
|
200
|
+
loading = false,
|
|
201
|
+
fullWidth = false,
|
|
202
|
+
iconOnly = false,
|
|
203
|
+
disabled,
|
|
204
|
+
className,
|
|
205
|
+
children,
|
|
206
|
+
type = "button",
|
|
207
|
+
"aria-busy": ariaBusy,
|
|
208
|
+
...props
|
|
209
|
+
}, ref) {
|
|
210
|
+
const classes = [
|
|
211
|
+
"xcv-button",
|
|
212
|
+
`xcv-button--${variant}`,
|
|
213
|
+
`xcv-button--${size}`,
|
|
214
|
+
fullWidth && "xcv-button--full-width",
|
|
215
|
+
iconOnly && "xcv-button--icon-only",
|
|
216
|
+
loading && "xcv-button--loading",
|
|
217
|
+
className
|
|
218
|
+
].filter(Boolean).join(" ");
|
|
219
|
+
return /* @__PURE__ */ jsxs3(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
ref,
|
|
223
|
+
type,
|
|
224
|
+
className: classes,
|
|
225
|
+
disabled: disabled || loading,
|
|
226
|
+
"data-xcv-radius": radius,
|
|
227
|
+
"aria-busy": loading ? true : ariaBusy,
|
|
228
|
+
...props,
|
|
229
|
+
children: [
|
|
230
|
+
loading && /* @__PURE__ */ jsx13("span", { className: "xcv-button__spinner", "aria-hidden": "true" }),
|
|
231
|
+
/* @__PURE__ */ jsx13("span", { className: "xcv-button__content", children })
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// src/actions/Link.tsx
|
|
238
|
+
import { forwardRef as forwardRef14 } from "react";
|
|
239
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
240
|
+
var XcvLink = forwardRef14(function XcvLink2({ variant = "default", className, ...props }, ref) {
|
|
241
|
+
const classes = ["xcv-link", `xcv-link--${variant}`, className].filter(Boolean).join(" ");
|
|
242
|
+
return /* @__PURE__ */ jsx14("a", { ref, className: classes, ...props });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// src/forms/TextInput.tsx
|
|
246
|
+
import { forwardRef as forwardRef15 } from "react";
|
|
247
|
+
|
|
248
|
+
// src/forms/FieldContext.tsx
|
|
249
|
+
import { createContext, useContext } from "react";
|
|
250
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
251
|
+
var XcvFieldContext = createContext(null);
|
|
252
|
+
function XcvFieldProvider({ value, children }) {
|
|
253
|
+
return /* @__PURE__ */ jsx15(XcvFieldContext.Provider, { value, children });
|
|
254
|
+
}
|
|
255
|
+
function useXcvFieldControlProps(props) {
|
|
256
|
+
const context = useContext(XcvFieldContext);
|
|
257
|
+
const describedBy = [context?.describedBy, props["aria-describedby"]].filter(Boolean).join(" ") || void 0;
|
|
258
|
+
const result = {};
|
|
259
|
+
const id = context?.controlId ?? props.id;
|
|
260
|
+
const required = context?.required || props.required;
|
|
261
|
+
const invalid = context?.invalid ? true : props["aria-invalid"];
|
|
262
|
+
if (id !== void 0) result.id = id;
|
|
263
|
+
if (required !== void 0) result.required = required;
|
|
264
|
+
if (describedBy !== void 0) result["aria-describedby"] = describedBy;
|
|
265
|
+
if (invalid !== void 0) result["aria-invalid"] = invalid;
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/forms/TextInput.tsx
|
|
270
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
271
|
+
var XcvTextInput = forwardRef15(function XcvTextInput2({ fieldState = "default", radius = "subtle", inputSize = "md", className, id, required, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }, ref) {
|
|
272
|
+
const fieldProps = useXcvFieldControlProps({ id, required, "aria-describedby": describedBy, "aria-invalid": fieldState === "error" ? true : invalid });
|
|
273
|
+
const classes = ["xcv-field-control", "xcv-text-input", `xcv-field-control--${fieldState}`, `xcv-field-control--${inputSize}`, className].filter(Boolean).join(" ");
|
|
274
|
+
return /* @__PURE__ */ jsx16("input", { ref, className: classes, ...fieldProps, ...props, "data-xcv-radius": radius });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// src/forms/TextArea.tsx
|
|
278
|
+
import { forwardRef as forwardRef16 } from "react";
|
|
279
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
280
|
+
var XcvTextArea = forwardRef16(function XcvTextArea2({ fieldState = "default", radius = "subtle", className, id, required, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }, ref) {
|
|
281
|
+
const fieldProps = useXcvFieldControlProps({ id, required, "aria-describedby": describedBy, "aria-invalid": fieldState === "error" ? true : invalid });
|
|
282
|
+
const classes = ["xcv-field-control", "xcv-text-area", `xcv-field-control--${fieldState}`, className].filter(Boolean).join(" ");
|
|
283
|
+
return /* @__PURE__ */ jsx17("textarea", { ref, className: classes, ...fieldProps, ...props, "data-xcv-radius": radius });
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// src/forms/Select.tsx
|
|
287
|
+
import {
|
|
288
|
+
forwardRef as forwardRef17,
|
|
289
|
+
useEffect,
|
|
290
|
+
useId,
|
|
291
|
+
useRef,
|
|
292
|
+
useState
|
|
293
|
+
} from "react";
|
|
294
|
+
import { createPortal } from "react-dom";
|
|
295
|
+
import { jsx as jsx18, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
296
|
+
var XcvSelect = forwardRef17(function XcvSelect2({
|
|
297
|
+
options,
|
|
298
|
+
fieldState = "default",
|
|
299
|
+
radius = "subtle",
|
|
300
|
+
placeholder,
|
|
301
|
+
selectSize = "md",
|
|
302
|
+
className,
|
|
303
|
+
id,
|
|
304
|
+
required,
|
|
305
|
+
disabled = false,
|
|
306
|
+
value,
|
|
307
|
+
defaultValue,
|
|
308
|
+
name,
|
|
309
|
+
onChange,
|
|
310
|
+
onBlur,
|
|
311
|
+
onFocus,
|
|
312
|
+
onKeyDown,
|
|
313
|
+
onClick,
|
|
314
|
+
autoFocus,
|
|
315
|
+
"aria-label": ariaLabel,
|
|
316
|
+
"aria-labelledby": ariaLabelledBy,
|
|
317
|
+
"aria-describedby": describedBy,
|
|
318
|
+
"aria-invalid": invalid,
|
|
319
|
+
...nativeProps
|
|
320
|
+
}, forwardedRef) {
|
|
321
|
+
const fieldProps = useXcvFieldControlProps({
|
|
322
|
+
id,
|
|
323
|
+
required,
|
|
324
|
+
"aria-describedby": describedBy,
|
|
325
|
+
"aria-invalid": fieldState === "error" ? true : invalid
|
|
326
|
+
});
|
|
327
|
+
const generatedId = useId();
|
|
328
|
+
const controlId = fieldProps.id ?? id ?? `xcv-select-${generatedId}`;
|
|
329
|
+
const isRequired = fieldProps.required ?? required;
|
|
330
|
+
const listboxId = `${controlId}-listbox`;
|
|
331
|
+
const selectedId = `${controlId}-option`;
|
|
332
|
+
const isControlled = value !== void 0;
|
|
333
|
+
const initialValue = defaultValue !== void 0 ? String(defaultValue) : placeholder !== void 0 ? "" : options[0]?.value ?? "";
|
|
334
|
+
const [internalValue, setInternalValue] = useState(initialValue);
|
|
335
|
+
const selectedValue = isControlled ? String(value) : internalValue;
|
|
336
|
+
const selectedValueRef = useRef(selectedValue);
|
|
337
|
+
selectedValueRef.current = selectedValue;
|
|
338
|
+
const selectedOption = options.find((option) => option.value === selectedValue);
|
|
339
|
+
const [open, setOpen] = useState(false);
|
|
340
|
+
const [activeValue, setActiveValue] = useState(selectedValue);
|
|
341
|
+
const [listboxPosition, setListboxPosition] = useState(null);
|
|
342
|
+
const [portalTheme, setPortalTheme] = useState({});
|
|
343
|
+
const listboxRef = useRef(null);
|
|
344
|
+
const selectRef = useRef(null);
|
|
345
|
+
const wrapperRef = useRef(null);
|
|
346
|
+
const triggerRef = useRef(null);
|
|
347
|
+
const typeaheadRef = useRef("");
|
|
348
|
+
const typeaheadTimerRef = useRef(null);
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (disabled) setOpen(false);
|
|
351
|
+
}, [disabled]);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (!open) {
|
|
354
|
+
setPortalTheme({});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const syncTheme = () => {
|
|
358
|
+
const wrapper = wrapperRef.current;
|
|
359
|
+
const readScope = (attribute) => wrapper?.closest(`[${attribute}]`)?.getAttribute(attribute) ?? void 0;
|
|
360
|
+
const brand = readScope("data-xcv-brand");
|
|
361
|
+
const mode = readScope("data-xcv-mode");
|
|
362
|
+
const theme = readScope("data-xcv-theme");
|
|
363
|
+
setPortalTheme({
|
|
364
|
+
...brand === void 0 ? {} : { brand },
|
|
365
|
+
...mode === void 0 ? {} : { mode },
|
|
366
|
+
...theme === void 0 ? {} : { theme }
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
syncTheme();
|
|
370
|
+
const observer = new MutationObserver(syncTheme);
|
|
371
|
+
observer.observe(document.documentElement, {
|
|
372
|
+
attributes: true,
|
|
373
|
+
subtree: true,
|
|
374
|
+
attributeFilter: ["data-xcv-brand", "data-xcv-mode", "data-xcv-theme"]
|
|
375
|
+
});
|
|
376
|
+
return () => observer.disconnect();
|
|
377
|
+
}, [open]);
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
if (!open) {
|
|
380
|
+
setListboxPosition(null);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const updatePosition = () => {
|
|
384
|
+
const trigger = triggerRef.current;
|
|
385
|
+
const panel = listboxRef.current;
|
|
386
|
+
if (!trigger || !panel) return;
|
|
387
|
+
const rect = trigger.getBoundingClientRect();
|
|
388
|
+
const viewport = window.visualViewport;
|
|
389
|
+
const viewportTop = viewport?.offsetTop ?? 0;
|
|
390
|
+
const viewportLeft = viewport?.offsetLeft ?? 0;
|
|
391
|
+
const viewportHeight = viewport?.height ?? window.innerHeight;
|
|
392
|
+
const viewportWidth = viewport?.width ?? window.innerWidth;
|
|
393
|
+
const gap = 8;
|
|
394
|
+
const panelHeight = Math.min(panel.scrollHeight, 288);
|
|
395
|
+
const viewportBottom = viewportTop + viewportHeight;
|
|
396
|
+
const viewportRight = viewportLeft + viewportWidth;
|
|
397
|
+
const below = viewportBottom - rect.bottom - gap;
|
|
398
|
+
const above = rect.top - viewportTop - gap;
|
|
399
|
+
const placeAbove = below < Math.min(panelHeight, 180) && above > below;
|
|
400
|
+
const available = Math.max(0, placeAbove ? above : below);
|
|
401
|
+
const width = Math.min(rect.width, viewportWidth - 16);
|
|
402
|
+
const left = Math.min(Math.max(viewportLeft + 8, rect.left), viewportRight - width - 8);
|
|
403
|
+
setListboxPosition({ position: "fixed", left, width, top: placeAbove ? void 0 : rect.bottom + gap, bottom: placeAbove ? viewportBottom - rect.top + gap : void 0, maxHeight: Math.min(288, available), visibility: "visible" });
|
|
404
|
+
};
|
|
405
|
+
setListboxPosition({ position: "fixed", visibility: "hidden" });
|
|
406
|
+
updatePosition();
|
|
407
|
+
const visualViewport = window.visualViewport;
|
|
408
|
+
window.addEventListener("resize", updatePosition);
|
|
409
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
410
|
+
visualViewport?.addEventListener("resize", updatePosition);
|
|
411
|
+
visualViewport?.addEventListener("scroll", updatePosition);
|
|
412
|
+
return () => {
|
|
413
|
+
window.removeEventListener("resize", updatePosition);
|
|
414
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
415
|
+
visualViewport?.removeEventListener("resize", updatePosition);
|
|
416
|
+
visualViewport?.removeEventListener("scroll", updatePosition);
|
|
417
|
+
};
|
|
418
|
+
}, [open, options.length]);
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
if (!open) return;
|
|
421
|
+
function closeOnOutsidePointerDown(event) {
|
|
422
|
+
if (event.target instanceof Node && !wrapperRef.current?.contains(event.target) && !listboxRef.current?.contains(event.target)) {
|
|
423
|
+
setOpen(false);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
document.addEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
427
|
+
return () => document.removeEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
428
|
+
}, [open]);
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
if (selectRef.current && isControlled) selectRef.current.value = selectedValue;
|
|
431
|
+
}, [isControlled, selectedValue]);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
const form = selectRef.current?.form;
|
|
434
|
+
if (!form || isControlled) return;
|
|
435
|
+
function resetValue() {
|
|
436
|
+
setInternalValue(initialValue);
|
|
437
|
+
}
|
|
438
|
+
form.addEventListener("reset", resetValue);
|
|
439
|
+
return () => form.removeEventListener("reset", resetValue);
|
|
440
|
+
}, [initialValue, isControlled]);
|
|
441
|
+
useEffect(() => () => {
|
|
442
|
+
if (typeaheadTimerRef.current !== null) clearTimeout(typeaheadTimerRef.current);
|
|
443
|
+
}, []);
|
|
444
|
+
function enabledOptions() {
|
|
445
|
+
return options.filter((option) => !option.disabled);
|
|
446
|
+
}
|
|
447
|
+
function openAt(valueToActivate) {
|
|
448
|
+
const enabled = enabledOptions();
|
|
449
|
+
const current = enabled.find((option) => option.value === (valueToActivate ?? selectedValue));
|
|
450
|
+
setActiveValue(current?.value ?? enabled[0]?.value ?? "");
|
|
451
|
+
setOpen(true);
|
|
452
|
+
}
|
|
453
|
+
function choose(option) {
|
|
454
|
+
if (disabled || option.disabled) return;
|
|
455
|
+
const nativeSelect = selectRef.current;
|
|
456
|
+
if (!nativeSelect) return;
|
|
457
|
+
const previousValue = selectedValue;
|
|
458
|
+
if (option.value !== previousValue) {
|
|
459
|
+
if (!isControlled) setInternalValue(option.value);
|
|
460
|
+
nativeSelect.value = option.value;
|
|
461
|
+
const EventConstructor = nativeSelect.ownerDocument.defaultView?.Event;
|
|
462
|
+
if (EventConstructor) nativeSelect.dispatchEvent(new EventConstructor("change", { bubbles: true }));
|
|
463
|
+
if (isControlled) {
|
|
464
|
+
queueMicrotask(() => {
|
|
465
|
+
if (selectRef.current && selectRef.current.value !== selectedValueRef.current) {
|
|
466
|
+
selectRef.current.value = selectedValueRef.current;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
setOpen(false);
|
|
472
|
+
triggerRef.current?.focus();
|
|
473
|
+
}
|
|
474
|
+
function moveActive(direction) {
|
|
475
|
+
const enabled = enabledOptions();
|
|
476
|
+
if (!enabled.length) return;
|
|
477
|
+
const index = enabled.findIndex((option) => option.value === activeValue);
|
|
478
|
+
const next = index < 0 ? direction > 0 ? 0 : enabled.length - 1 : (index + direction + enabled.length) % enabled.length;
|
|
479
|
+
setActiveValue(enabled[next].value);
|
|
480
|
+
}
|
|
481
|
+
function handleKeyDown(event) {
|
|
482
|
+
onKeyDown?.(event);
|
|
483
|
+
if (event.defaultPrevented) return;
|
|
484
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
485
|
+
event.preventDefault();
|
|
486
|
+
if (!open) openAt(event.key === "ArrowDown" ? selectedValue : enabledOptions()[enabledOptions().length - 1]?.value);
|
|
487
|
+
else moveActive(event.key === "ArrowDown" ? 1 : -1);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (event.key === "Home" || event.key === "End") {
|
|
491
|
+
if (!open) return;
|
|
492
|
+
event.preventDefault();
|
|
493
|
+
const enabled = enabledOptions();
|
|
494
|
+
setActiveValue(event.key === "Home" ? enabled[0]?.value ?? "" : enabled[enabled.length - 1]?.value ?? "");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
498
|
+
event.preventDefault();
|
|
499
|
+
if (!open) openAt();
|
|
500
|
+
else {
|
|
501
|
+
const option = options.find((item) => item.value === activeValue);
|
|
502
|
+
if (option) choose(option);
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (event.key === "Escape" && open) {
|
|
507
|
+
event.preventDefault();
|
|
508
|
+
setOpen(false);
|
|
509
|
+
triggerRef.current?.focus();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
513
|
+
const nextQuery = typeaheadRef.current + event.key.toLocaleLowerCase();
|
|
514
|
+
typeaheadRef.current = nextQuery;
|
|
515
|
+
if (typeaheadTimerRef.current !== null) clearTimeout(typeaheadTimerRef.current);
|
|
516
|
+
typeaheadTimerRef.current = setTimeout(() => {
|
|
517
|
+
typeaheadRef.current = "";
|
|
518
|
+
}, 600);
|
|
519
|
+
const start = enabledOptions();
|
|
520
|
+
const match = start.find((option) => option.label.toLocaleLowerCase().startsWith(nextQuery));
|
|
521
|
+
if (match) {
|
|
522
|
+
event.preventDefault();
|
|
523
|
+
if (open) setActiveValue(match.value);
|
|
524
|
+
else choose(match);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return /* @__PURE__ */ jsxs4("span", { ref: wrapperRef, id: `${controlId}-root`, className: "xcv-select-wrapper", "data-xcv-radius": radius, "data-open": open ? "true" : void 0, children: [
|
|
529
|
+
/* @__PURE__ */ jsxs4(
|
|
530
|
+
"select",
|
|
531
|
+
{
|
|
532
|
+
...nativeProps,
|
|
533
|
+
ref: (node) => {
|
|
534
|
+
selectRef.current = node;
|
|
535
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
536
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
537
|
+
},
|
|
538
|
+
className: "xcv-select__native",
|
|
539
|
+
name,
|
|
540
|
+
value: selectedValue,
|
|
541
|
+
disabled,
|
|
542
|
+
required: isRequired,
|
|
543
|
+
tabIndex: -1,
|
|
544
|
+
"aria-hidden": "true",
|
|
545
|
+
onChange,
|
|
546
|
+
children: [
|
|
547
|
+
placeholder !== void 0 && /* @__PURE__ */ jsx18("option", { value: "", disabled: true, children: placeholder }),
|
|
548
|
+
options.map((option) => /* @__PURE__ */ jsx18("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
),
|
|
552
|
+
/* @__PURE__ */ jsxs4(
|
|
553
|
+
"button",
|
|
554
|
+
{
|
|
555
|
+
...fieldProps,
|
|
556
|
+
ref: triggerRef,
|
|
557
|
+
id: controlId,
|
|
558
|
+
type: "button",
|
|
559
|
+
role: "combobox",
|
|
560
|
+
className: ["xcv-field-control", "xcv-select__trigger", `xcv-field-control--${fieldState}`, `xcv-field-control--${selectSize}`, className].filter(Boolean).join(" "),
|
|
561
|
+
"data-xcv-radius": radius,
|
|
562
|
+
disabled,
|
|
563
|
+
"aria-label": ariaLabel,
|
|
564
|
+
"aria-labelledby": ariaLabelledBy,
|
|
565
|
+
"aria-describedby": fieldProps["aria-describedby"],
|
|
566
|
+
"aria-invalid": fieldProps["aria-invalid"],
|
|
567
|
+
"aria-required": isRequired || void 0,
|
|
568
|
+
autoFocus,
|
|
569
|
+
"aria-haspopup": "listbox",
|
|
570
|
+
"aria-autocomplete": "none",
|
|
571
|
+
"aria-expanded": open,
|
|
572
|
+
"aria-controls": open ? listboxId : void 0,
|
|
573
|
+
"aria-activedescendant": open && activeValue ? `${selectedId}-${options.findIndex((option) => option.value === activeValue)}` : void 0,
|
|
574
|
+
onFocus,
|
|
575
|
+
onBlur: (event) => {
|
|
576
|
+
onBlur?.(event);
|
|
577
|
+
if (!event.currentTarget.parentElement?.contains(event.relatedTarget) && !listboxRef.current?.contains(event.relatedTarget)) setOpen(false);
|
|
578
|
+
},
|
|
579
|
+
onKeyDown: handleKeyDown,
|
|
580
|
+
onClick: (event) => {
|
|
581
|
+
onClick?.(event);
|
|
582
|
+
if (event.defaultPrevented) return;
|
|
583
|
+
if (open) setOpen(false);
|
|
584
|
+
else openAt();
|
|
585
|
+
},
|
|
586
|
+
children: [
|
|
587
|
+
/* @__PURE__ */ jsx18("span", { className: selectedOption ? "xcv-select__value" : "xcv-select__value xcv-select__value--placeholder", children: selectedOption?.label ?? placeholder ?? "" }),
|
|
588
|
+
/* @__PURE__ */ jsx18("span", { className: "xcv-select__chevron", "aria-hidden": "true" })
|
|
589
|
+
]
|
|
590
|
+
}
|
|
591
|
+
),
|
|
592
|
+
open && typeof document !== "undefined" && createPortal(
|
|
593
|
+
/* @__PURE__ */ jsx18("div", { ref: listboxRef, id: listboxId, role: "listbox", "aria-labelledby": ariaLabelledBy ?? controlId, "aria-describedby": fieldProps["aria-describedby"], className: "xcv-select__listbox", "data-xcv-radius": radius, "data-xcv-brand": portalTheme.brand, "data-xcv-mode": portalTheme.mode, "data-xcv-theme": portalTheme.theme, style: listboxPosition ?? { position: "fixed", visibility: "hidden" }, children: options.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "xcv-select__empty", role: "presentation", children: "No options available" }) : options.map((option, index) => /* @__PURE__ */ jsxs4("div", { id: selectedId + "-" + index, role: "option", "aria-selected": option.value === selectedValue, "aria-disabled": option.disabled || void 0, "data-active": option.value === activeValue ? "true" : void 0, className: "xcv-select__option", onPointerMove: () => {
|
|
594
|
+
if (!option.disabled) setActiveValue(option.value);
|
|
595
|
+
}, onClick: () => choose(option), children: [
|
|
596
|
+
/* @__PURE__ */ jsx18("span", { children: option.label }),
|
|
597
|
+
option.value === selectedValue && /* @__PURE__ */ jsx18("span", { className: "xcv-select__check", "aria-hidden": "true", children: "\u2713" })
|
|
598
|
+
] }, option.value)) }),
|
|
599
|
+
document.body
|
|
600
|
+
)
|
|
601
|
+
] });
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// src/forms/Checkbox.tsx
|
|
605
|
+
import { forwardRef as forwardRef18 } from "react";
|
|
606
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
607
|
+
var XcvCheckbox = forwardRef18(function XcvCheckbox2({ fieldState = "default", className, id, required, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }, ref) {
|
|
608
|
+
const fieldProps = useXcvFieldControlProps({ id, required, "aria-describedby": describedBy, "aria-invalid": fieldState === "error" ? true : invalid });
|
|
609
|
+
const classes = ["xcv-checkable", "xcv-checkbox", `xcv-field-control--${fieldState}`, className].filter(Boolean).join(" ");
|
|
610
|
+
return /* @__PURE__ */ jsx19("input", { ref, type: "checkbox", className: classes, ...fieldProps, ...props });
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// src/forms/Radio.tsx
|
|
614
|
+
import { forwardRef as forwardRef19 } from "react";
|
|
615
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
616
|
+
var XcvRadio = forwardRef19(function XcvRadio2({ fieldState = "default", className, id, required, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }, ref) {
|
|
617
|
+
const fieldProps = useXcvFieldControlProps({ id, required, "aria-describedby": describedBy, "aria-invalid": fieldState === "error" ? true : invalid });
|
|
618
|
+
const classes = ["xcv-checkable", "xcv-radio", `xcv-field-control--${fieldState}`, className].filter(Boolean).join(" ");
|
|
619
|
+
return /* @__PURE__ */ jsx20("input", { ref, type: "radio", className: classes, ...fieldProps, ...props });
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// src/forms/Toggle.tsx
|
|
623
|
+
import { forwardRef as forwardRef20 } from "react";
|
|
624
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
625
|
+
var XcvToggle = forwardRef20(function XcvToggle2({ fieldState = "default", className, id, required, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }, ref) {
|
|
626
|
+
const fieldProps = useXcvFieldControlProps({ id, required, "aria-describedby": describedBy, "aria-invalid": fieldState === "error" ? true : invalid });
|
|
627
|
+
const classes = ["xcv-checkable", "xcv-toggle", `xcv-field-control--${fieldState}`, className].filter(Boolean).join(" ");
|
|
628
|
+
return /* @__PURE__ */ jsx21("input", { ref, type: "checkbox", role: "switch", className: classes, ...fieldProps, ...props });
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// src/forms/FormField.tsx
|
|
632
|
+
import { forwardRef as forwardRef21, useId as useId2 } from "react";
|
|
633
|
+
import { jsx as jsx22, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
634
|
+
var XcvFormField = forwardRef21(function XcvFormField2({
|
|
635
|
+
label,
|
|
636
|
+
children,
|
|
637
|
+
id,
|
|
638
|
+
description,
|
|
639
|
+
error,
|
|
640
|
+
required = false,
|
|
641
|
+
className,
|
|
642
|
+
...props
|
|
643
|
+
}, ref) {
|
|
644
|
+
const generatedId = useId2();
|
|
645
|
+
const controlId = id ?? `xcv-field-${generatedId}`;
|
|
646
|
+
const hasDescription = description !== void 0 && description !== null && description !== false && description !== true;
|
|
647
|
+
const hasError = error !== void 0 && error !== null && error !== false && error !== true;
|
|
648
|
+
const descriptionId = hasDescription ? `${controlId}-description` : void 0;
|
|
649
|
+
const errorId = hasError ? `${controlId}-error` : void 0;
|
|
650
|
+
const describedBy = [descriptionId, errorId].filter(Boolean).join(" ") || void 0;
|
|
651
|
+
const value = { controlId, invalid: hasError, required };
|
|
652
|
+
if (describedBy) value.describedBy = describedBy;
|
|
653
|
+
return /* @__PURE__ */ jsxs5("div", { ref, className: ["xcv-form-field", className].filter(Boolean).join(" "), ...props, children: [
|
|
654
|
+
/* @__PURE__ */ jsxs5("label", { className: "xcv-form-field__label", htmlFor: controlId, children: [
|
|
655
|
+
label,
|
|
656
|
+
required && /* @__PURE__ */ jsx22("span", { className: "xcv-label__required", "aria-hidden": "true", children: " *" })
|
|
657
|
+
] }),
|
|
658
|
+
/* @__PURE__ */ jsx22(XcvFieldProvider, { value, children }),
|
|
659
|
+
hasDescription && /* @__PURE__ */ jsx22("div", { className: "xcv-form-field__description", id: descriptionId, children: description }),
|
|
660
|
+
hasError && /* @__PURE__ */ jsx22("div", { className: "xcv-form-field__error", id: errorId, "aria-live": "polite", children: error })
|
|
661
|
+
] });
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// src/feedback/Notice.tsx
|
|
665
|
+
import { forwardRef as forwardRef22 } from "react";
|
|
666
|
+
import { jsx as jsx23, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
667
|
+
var XcvNotice = forwardRef22(function XcvNotice2({
|
|
668
|
+
tone = "info",
|
|
669
|
+
radius = "subtle",
|
|
670
|
+
title,
|
|
671
|
+
icon,
|
|
672
|
+
announce = false,
|
|
673
|
+
className,
|
|
674
|
+
children,
|
|
675
|
+
...props
|
|
676
|
+
}, ref) {
|
|
677
|
+
const classes = ["xcv-notice", `xcv-notice--${tone}`, className].filter(Boolean).join(" ");
|
|
678
|
+
return /* @__PURE__ */ jsxs6("div", { ref, ...props, className: classes, "data-xcv-radius": radius, "aria-live": announce || void 0, children: [
|
|
679
|
+
icon != null && /* @__PURE__ */ jsx23("span", { className: "xcv-notice__icon", "aria-hidden": "true", children: icon }),
|
|
680
|
+
/* @__PURE__ */ jsxs6("div", { className: "xcv-notice__body", children: [
|
|
681
|
+
title != null && /* @__PURE__ */ jsx23("div", { className: "xcv-notice__title", children: title }),
|
|
682
|
+
children != null && /* @__PURE__ */ jsx23("div", { className: "xcv-notice__content", children })
|
|
683
|
+
] })
|
|
684
|
+
] });
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// src/feedback/Badge.tsx
|
|
688
|
+
import { forwardRef as forwardRef23 } from "react";
|
|
689
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
690
|
+
var XcvBadge = forwardRef23(function XcvBadge2({ tone = "neutral", radius = "subtle", className, ...props }, ref) {
|
|
691
|
+
const classes = ["xcv-badge", `xcv-badge--${tone}`, className].filter(Boolean).join(" ");
|
|
692
|
+
return /* @__PURE__ */ jsx24("span", { ref, className: classes, "data-xcv-radius": radius, ...props });
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// src/feedback/Loader.tsx
|
|
696
|
+
import { forwardRef as forwardRef24 } from "react";
|
|
697
|
+
import { jsx as jsx25, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
698
|
+
var XcvLoader = forwardRef24(function XcvLoader2({ variant = "orbit", size = "md", label = "Loading", showLabel = false, className, ...props }, ref) {
|
|
699
|
+
const classes = ["xcv-loader", `xcv-loader--${size}`, className].filter(Boolean).join(" ");
|
|
700
|
+
return /* @__PURE__ */ jsxs7("span", { ref, ...props, className: classes, "data-variant": variant, role: "status", "aria-live": "polite", "aria-label": label, children: [
|
|
701
|
+
/* @__PURE__ */ jsx25("span", { className: "xcv-loader__visual", "aria-hidden": "true", children: Array.from({ length: 5 }, (_, index) => /* @__PURE__ */ jsx25("span", { className: "xcv-loader__shape" }, index)) }),
|
|
702
|
+
showLabel && /* @__PURE__ */ jsx25("span", { className: "xcv-loader__label", "aria-hidden": "true", children: label })
|
|
703
|
+
] });
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// src/navigation/Accordion.tsx
|
|
707
|
+
import {
|
|
708
|
+
Children,
|
|
709
|
+
Fragment,
|
|
710
|
+
createContext as createContext2,
|
|
711
|
+
forwardRef as forwardRef25,
|
|
712
|
+
isValidElement,
|
|
713
|
+
useContext as useContext2,
|
|
714
|
+
useId as useId3,
|
|
715
|
+
useState as useState2
|
|
716
|
+
} from "react";
|
|
717
|
+
import { jsx as jsx26, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
718
|
+
var AccordionContext = createContext2(null);
|
|
719
|
+
var AccordionItemContext = createContext2(null);
|
|
720
|
+
function normalizeValue(value, mode) {
|
|
721
|
+
if (value === void 0) return [];
|
|
722
|
+
const values = typeof value === "string" ? [value] : [...new Set(value)];
|
|
723
|
+
return mode === "single" ? values.slice(0, 1) : values;
|
|
724
|
+
}
|
|
725
|
+
function useAccordionContext() {
|
|
726
|
+
const context = useContext2(AccordionContext);
|
|
727
|
+
if (!context) throw new Error("XcvAccordion compound components must be used inside XcvAccordion.");
|
|
728
|
+
return context;
|
|
729
|
+
}
|
|
730
|
+
function useAccordionItemContext() {
|
|
731
|
+
const context = useContext2(AccordionItemContext);
|
|
732
|
+
if (!context) throw new Error("XcvAccordion.Trigger and XcvAccordion.Content must be used inside XcvAccordion.Item.");
|
|
733
|
+
return context;
|
|
734
|
+
}
|
|
735
|
+
var XcvAccordionRoot = forwardRef25(function XcvAccordionRoot2({
|
|
736
|
+
mode = "single",
|
|
737
|
+
value,
|
|
738
|
+
defaultValue,
|
|
739
|
+
onValueChange,
|
|
740
|
+
radius = "subtle",
|
|
741
|
+
className,
|
|
742
|
+
children,
|
|
743
|
+
...props
|
|
744
|
+
}, ref) {
|
|
745
|
+
const [internalValue, setInternalValue] = useState2(() => normalizeValue(defaultValue, mode));
|
|
746
|
+
const openValues = normalizeValue(value === void 0 ? internalValue : value, mode);
|
|
747
|
+
const itemValues = collectAccordionItemValues(children);
|
|
748
|
+
if (new Set(itemValues).size !== itemValues.length) throw new Error("XcvAccordion item values must be unique.");
|
|
749
|
+
function toggle(itemValue) {
|
|
750
|
+
let next;
|
|
751
|
+
if (mode === "multiple") next = openValues.includes(itemValue) ? openValues.filter((current) => current !== itemValue) : [...openValues, itemValue];
|
|
752
|
+
else next = openValues.includes(itemValue) ? [] : [itemValue];
|
|
753
|
+
if (value === void 0) setInternalValue(next);
|
|
754
|
+
onValueChange?.(mode === "multiple" ? next : next[0]);
|
|
755
|
+
}
|
|
756
|
+
return /* @__PURE__ */ jsx26(AccordionContext.Provider, { value: { openValues, toggle }, children: /* @__PURE__ */ jsx26("div", { ref, "data-xcv-radius": radius, className: ["xcv-accordion", className].filter(Boolean).join(" "), ...props, children }) });
|
|
757
|
+
});
|
|
758
|
+
function XcvAccordionItem({ value, disabled = false, className, children, ...props }) {
|
|
759
|
+
const { openValues } = useAccordionContext();
|
|
760
|
+
const triggerCount = countAccordionSlots(children, XcvAccordionTrigger);
|
|
761
|
+
const contentCount = countAccordionSlots(children, XcvAccordionContent);
|
|
762
|
+
if (triggerCount !== 1 || contentCount !== 1) {
|
|
763
|
+
throw new Error("Each XcvAccordion.Item must contain exactly one XcvAccordion.Trigger and one XcvAccordion.Content.");
|
|
764
|
+
}
|
|
765
|
+
const id = useId3();
|
|
766
|
+
const context = { itemValue: value, triggerId: `xcv-accordion-trigger-${id}`, panelId: `xcv-accordion-panel-${id}`, open: openValues.includes(value), disabled };
|
|
767
|
+
return /* @__PURE__ */ jsx26(AccordionItemContext.Provider, { value: context, children: /* @__PURE__ */ jsx26("div", { className: ["xcv-accordion__item", context.open && "xcv-accordion__item--open", disabled && "xcv-accordion__item--disabled", className].filter(Boolean).join(" "), ...props, children }) });
|
|
768
|
+
}
|
|
769
|
+
function XcvAccordionTrigger({ className, children, type = "button", disabled, ...props }) {
|
|
770
|
+
const { toggle } = useAccordionContext();
|
|
771
|
+
const item = useAccordionItemContext();
|
|
772
|
+
return /* @__PURE__ */ jsxs8(
|
|
773
|
+
"button",
|
|
774
|
+
{
|
|
775
|
+
...props,
|
|
776
|
+
id: item.triggerId,
|
|
777
|
+
type,
|
|
778
|
+
role: "button",
|
|
779
|
+
className: ["xcv-accordion__trigger", className].filter(Boolean).join(" "),
|
|
780
|
+
"aria-expanded": item.open,
|
|
781
|
+
"aria-controls": item.panelId,
|
|
782
|
+
disabled: disabled || item.disabled,
|
|
783
|
+
onClick: (event) => {
|
|
784
|
+
props.onClick?.(event);
|
|
785
|
+
if (!event.defaultPrevented && !disabled && !item.disabled) toggle(item.itemValue);
|
|
786
|
+
},
|
|
787
|
+
children: [
|
|
788
|
+
/* @__PURE__ */ jsx26("span", { className: "xcv-accordion__trigger-content", children }),
|
|
789
|
+
/* @__PURE__ */ jsx26("span", { className: "xcv-accordion__indicator xcv-chevron", "aria-hidden": "true" })
|
|
790
|
+
]
|
|
791
|
+
}
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function XcvAccordionContent({ className, children, ...props }) {
|
|
795
|
+
const item = useAccordionItemContext();
|
|
796
|
+
return /* @__PURE__ */ jsx26(
|
|
797
|
+
"div",
|
|
798
|
+
{
|
|
799
|
+
...props,
|
|
800
|
+
id: item.panelId,
|
|
801
|
+
role: "region",
|
|
802
|
+
"aria-labelledby": item.triggerId,
|
|
803
|
+
hidden: !item.open,
|
|
804
|
+
className: ["xcv-accordion__content", className].filter(Boolean).join(" "),
|
|
805
|
+
children
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
function countAccordionSlots(children, slotType) {
|
|
810
|
+
let count = 0;
|
|
811
|
+
Children.forEach(children, (child) => {
|
|
812
|
+
if (!isValidElement(child)) return;
|
|
813
|
+
if (child.type === slotType) {
|
|
814
|
+
count += 1;
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (child.type === Fragment) count += countAccordionSlots(child.props.children, slotType);
|
|
818
|
+
});
|
|
819
|
+
return count;
|
|
820
|
+
}
|
|
821
|
+
function collectAccordionItemValues(children) {
|
|
822
|
+
const values = [];
|
|
823
|
+
Children.forEach(children, (child) => {
|
|
824
|
+
if (!isValidElement(child)) return;
|
|
825
|
+
if (child.type === XcvAccordionItem) {
|
|
826
|
+
if (typeof child.props.value === "string") values.push(child.props.value);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (child.type === Fragment) values.push(...collectAccordionItemValues(child.props.children));
|
|
830
|
+
});
|
|
831
|
+
return values;
|
|
832
|
+
}
|
|
833
|
+
var XcvAccordion = Object.assign(XcvAccordionRoot, { Item: XcvAccordionItem, Trigger: XcvAccordionTrigger, Content: XcvAccordionContent });
|
|
834
|
+
|
|
835
|
+
// src/navigation/ActionsMenu.tsx
|
|
836
|
+
import { Children as Children2, Fragment as Fragment2, createContext as createContext3, forwardRef as forwardRef26, isValidElement as isValidElement2, useContext as useContext3, useId as useId4, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
|
|
837
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
838
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
839
|
+
var MenuContext = createContext3(null);
|
|
840
|
+
function useMenu() {
|
|
841
|
+
const value = useContext3(MenuContext);
|
|
842
|
+
if (!value) throw new Error("XcvActionsMenu parts must be used inside XcvActionsMenu.");
|
|
843
|
+
return value;
|
|
844
|
+
}
|
|
845
|
+
var XcvActionsMenuRoot = forwardRef26(function XcvActionsMenuRoot2({ radius = "subtle", defaultOpen = false, open, onOpenChange, className, children, onBlur, ...props }, ref) {
|
|
846
|
+
const [internalOpen, setInternalOpen] = useState3(defaultOpen);
|
|
847
|
+
const isOpen = open ?? internalOpen;
|
|
848
|
+
const id = useId4();
|
|
849
|
+
const triggerRef = useRef2(null);
|
|
850
|
+
const contentRef = useRef2(null);
|
|
851
|
+
const rootRef = useRef2(null);
|
|
852
|
+
const focusLast = useRef2(false);
|
|
853
|
+
const restoreFocus = useRef2(false);
|
|
854
|
+
useEffect2(() => {
|
|
855
|
+
if (!isOpen) return;
|
|
856
|
+
function closeOnOutsidePointerDown(event) {
|
|
857
|
+
if (event.target instanceof Node && !rootRef.current?.contains(event.target) && !contentRef.current?.contains(event.target)) {
|
|
858
|
+
restoreFocus.current = false;
|
|
859
|
+
if (open === void 0) setInternalOpen(false);
|
|
860
|
+
onOpenChange?.(false);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
document.addEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
864
|
+
return () => document.removeEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
865
|
+
}, [isOpen, open, onOpenChange]);
|
|
866
|
+
const triggerCount = countMenuSlots(children, XcvActionsMenuTrigger);
|
|
867
|
+
const contentCount = countMenuSlots(children, XcvActionsMenuContent);
|
|
868
|
+
if (triggerCount !== 1 || contentCount !== 1) {
|
|
869
|
+
throw new Error("XcvActionsMenu requires exactly one Trigger and one Content.");
|
|
870
|
+
}
|
|
871
|
+
if (hasMenuPartsOutsideContent(children)) {
|
|
872
|
+
throw new Error("XcvActionsMenu Item and Separator slots must be inside Content.");
|
|
873
|
+
}
|
|
874
|
+
const setOpen = (next) => {
|
|
875
|
+
if (open === void 0) setInternalOpen(next);
|
|
876
|
+
onOpenChange?.(next);
|
|
877
|
+
};
|
|
878
|
+
return /* @__PURE__ */ jsx27(MenuContext.Provider, { value: { open: isOpen, setOpen, menuId: `xcv-actions-menu-${id}`, radius, triggerRef, contentRef, focusLast, restoreFocus }, children: /* @__PURE__ */ jsx27("div", { ref: (node) => {
|
|
879
|
+
rootRef.current = node;
|
|
880
|
+
if (typeof ref === "function") ref(node);
|
|
881
|
+
else if (ref) ref.current = node;
|
|
882
|
+
}, ...props, "data-xcv-radius": radius, className: ["xcv-actions-menu", className].filter(Boolean).join(" "), onBlur: (event) => {
|
|
883
|
+
onBlur?.(event);
|
|
884
|
+
if (!event.defaultPrevented && !event.currentTarget.contains(event.relatedTarget) && !contentRef.current?.contains(event.relatedTarget)) {
|
|
885
|
+
restoreFocus.current = false;
|
|
886
|
+
setOpen(false);
|
|
887
|
+
}
|
|
888
|
+
}, children }) });
|
|
889
|
+
});
|
|
890
|
+
function XcvActionsMenuTrigger({ className, children, type = "button", onClick, onKeyDown, ...props }) {
|
|
891
|
+
const menu = useMenu();
|
|
892
|
+
return /* @__PURE__ */ jsx27(
|
|
893
|
+
"button",
|
|
894
|
+
{
|
|
895
|
+
...props,
|
|
896
|
+
ref: menu.triggerRef,
|
|
897
|
+
type,
|
|
898
|
+
className: ["xcv-actions-menu__trigger", className].filter(Boolean).join(" "),
|
|
899
|
+
"aria-haspopup": "menu",
|
|
900
|
+
"aria-expanded": menu.open,
|
|
901
|
+
"aria-controls": menu.open ? menu.menuId : void 0,
|
|
902
|
+
onClick: (event) => {
|
|
903
|
+
onClick?.(event);
|
|
904
|
+
if (!event.defaultPrevented) {
|
|
905
|
+
if (!menu.open) {
|
|
906
|
+
menu.focusLast.current = false;
|
|
907
|
+
menu.restoreFocus.current = false;
|
|
908
|
+
}
|
|
909
|
+
menu.setOpen(!menu.open);
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
onKeyDown: (event) => {
|
|
913
|
+
onKeyDown?.(event);
|
|
914
|
+
if (event.defaultPrevented) return;
|
|
915
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
916
|
+
event.preventDefault();
|
|
917
|
+
menu.focusLast.current = event.key === "ArrowUp";
|
|
918
|
+
menu.restoreFocus.current = false;
|
|
919
|
+
menu.setOpen(true);
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
children
|
|
923
|
+
}
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
function XcvActionsMenuContent({ label, className, children, onKeyDown, ...props }) {
|
|
927
|
+
const menu = useMenu();
|
|
928
|
+
const [position, setPosition] = useState3(null);
|
|
929
|
+
const wasOpen = useRef2(menu.open);
|
|
930
|
+
const typeahead = useRef2("");
|
|
931
|
+
const typeaheadTimer = useRef2(null);
|
|
932
|
+
useEffect2(() => () => {
|
|
933
|
+
if (typeaheadTimer.current !== null) clearTimeout(typeaheadTimer.current);
|
|
934
|
+
}, []);
|
|
935
|
+
useEffect2(() => {
|
|
936
|
+
if (!menu.open) {
|
|
937
|
+
setPosition(null);
|
|
938
|
+
menu.focusLast.current = false;
|
|
939
|
+
if (wasOpen.current && menu.restoreFocus.current) {
|
|
940
|
+
menu.restoreFocus.current = false;
|
|
941
|
+
menu.triggerRef.current?.focus();
|
|
942
|
+
}
|
|
943
|
+
wasOpen.current = false;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
wasOpen.current = true;
|
|
947
|
+
const updatePosition = () => {
|
|
948
|
+
const trigger = menu.triggerRef.current;
|
|
949
|
+
const panel = menu.contentRef.current;
|
|
950
|
+
if (!trigger || !panel) return;
|
|
951
|
+
const rect = trigger.getBoundingClientRect();
|
|
952
|
+
const panelRect = panel.getBoundingClientRect();
|
|
953
|
+
const viewport = window.visualViewport;
|
|
954
|
+
const viewportTop = viewport?.offsetTop ?? 0;
|
|
955
|
+
const viewportLeft = viewport?.offsetLeft ?? 0;
|
|
956
|
+
const viewportHeight = viewport?.height ?? window.innerHeight;
|
|
957
|
+
const viewportWidth = viewport?.width ?? window.innerWidth;
|
|
958
|
+
const viewportBottom = viewportTop + viewportHeight;
|
|
959
|
+
const viewportRight = viewportLeft + viewportWidth;
|
|
960
|
+
const gap = 8;
|
|
961
|
+
const below = viewportBottom - rect.bottom - gap;
|
|
962
|
+
const above = rect.top - viewportTop - gap;
|
|
963
|
+
const placeAbove = below < Math.min(panelRect.height, 180) && above > below;
|
|
964
|
+
const width = Math.min(Math.max(panelRect.width, rect.width), viewportWidth - 16);
|
|
965
|
+
const left = Math.min(Math.max(viewportLeft + 8, rect.left), viewportRight - width - 8);
|
|
966
|
+
setPosition({ position: "fixed", left, width, minWidth: Math.min(rect.width, viewportWidth - 16), maxWidth: "calc(100vw - 16px)", top: placeAbove ? void 0 : rect.bottom + gap, bottom: placeAbove ? viewportBottom - rect.top + gap : void 0, maxHeight: Math.max(0, placeAbove ? above : below), visibility: "visible" });
|
|
967
|
+
};
|
|
968
|
+
setPosition({ position: "fixed", visibility: "hidden" });
|
|
969
|
+
updatePosition();
|
|
970
|
+
const visualViewport = window.visualViewport;
|
|
971
|
+
window.addEventListener("resize", updatePosition);
|
|
972
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
973
|
+
visualViewport?.addEventListener("resize", updatePosition);
|
|
974
|
+
visualViewport?.addEventListener("scroll", updatePosition);
|
|
975
|
+
const items = menu.contentRef.current?.querySelectorAll('[role="menuitem"]');
|
|
976
|
+
const target = menu.focusLast.current ? items?.[items.length - 1] : items?.[0];
|
|
977
|
+
target?.focus();
|
|
978
|
+
return () => {
|
|
979
|
+
window.removeEventListener("resize", updatePosition);
|
|
980
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
981
|
+
visualViewport?.removeEventListener("resize", updatePosition);
|
|
982
|
+
visualViewport?.removeEventListener("scroll", updatePosition);
|
|
983
|
+
};
|
|
984
|
+
}, [menu.open]);
|
|
985
|
+
function moveFocus(current, direction) {
|
|
986
|
+
const items = Array.from(menu.contentRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
987
|
+
if (!items.length) return;
|
|
988
|
+
const currentItem = current.closest('[role="menuitem"]');
|
|
989
|
+
const index = currentItem ? items.indexOf(currentItem) : -1;
|
|
990
|
+
if (index < 0) return;
|
|
991
|
+
items[(index + direction + items.length) % items.length]?.focus();
|
|
992
|
+
}
|
|
993
|
+
return menu.open && typeof document !== "undefined" ? createPortal2(
|
|
994
|
+
/* @__PURE__ */ jsx27(
|
|
995
|
+
"div",
|
|
996
|
+
{
|
|
997
|
+
...props,
|
|
998
|
+
ref: menu.contentRef,
|
|
999
|
+
id: menu.menuId,
|
|
1000
|
+
role: "menu",
|
|
1001
|
+
"aria-label": label,
|
|
1002
|
+
"data-xcv-radius": menu.radius,
|
|
1003
|
+
style: position ?? { position: "fixed", visibility: "hidden" },
|
|
1004
|
+
className: ["xcv-actions-menu__content", className].filter(Boolean).join(" "),
|
|
1005
|
+
onKeyDown: (event) => {
|
|
1006
|
+
onKeyDown?.(event);
|
|
1007
|
+
if (event.defaultPrevented) return;
|
|
1008
|
+
const target = event.target;
|
|
1009
|
+
if (event.key === "ArrowDown") {
|
|
1010
|
+
event.preventDefault();
|
|
1011
|
+
moveFocus(target, 1);
|
|
1012
|
+
} else if (event.key === "ArrowUp") {
|
|
1013
|
+
event.preventDefault();
|
|
1014
|
+
moveFocus(target, -1);
|
|
1015
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
1016
|
+
event.preventDefault();
|
|
1017
|
+
const items = menu.contentRef.current?.querySelectorAll('[role="menuitem"]');
|
|
1018
|
+
(event.key === "Home" ? items?.[0] : items?.[items.length - 1])?.focus();
|
|
1019
|
+
} else if (event.key === "Escape") {
|
|
1020
|
+
event.preventDefault();
|
|
1021
|
+
menu.restoreFocus.current = true;
|
|
1022
|
+
menu.setOpen(false);
|
|
1023
|
+
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
1024
|
+
const character = event.key.toLocaleLowerCase();
|
|
1025
|
+
const previous = typeahead.current;
|
|
1026
|
+
const query = previous && [...previous].every((letter) => letter === character) ? character : previous + character;
|
|
1027
|
+
typeahead.current = query;
|
|
1028
|
+
if (typeaheadTimer.current !== null) clearTimeout(typeaheadTimer.current);
|
|
1029
|
+
typeaheadTimer.current = setTimeout(() => {
|
|
1030
|
+
typeahead.current = "";
|
|
1031
|
+
}, 600);
|
|
1032
|
+
const items = Array.from(menu.contentRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
1033
|
+
const active = items.indexOf(document.activeElement);
|
|
1034
|
+
for (let offset = 1; offset <= items.length; offset += 1) {
|
|
1035
|
+
const candidate = items[(active + offset + items.length) % items.length];
|
|
1036
|
+
if (candidate?.textContent?.trim().toLocaleLowerCase().startsWith(query)) {
|
|
1037
|
+
event.preventDefault();
|
|
1038
|
+
candidate.focus();
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
children
|
|
1045
|
+
}
|
|
1046
|
+
),
|
|
1047
|
+
document.body
|
|
1048
|
+
) : null;
|
|
1049
|
+
}
|
|
1050
|
+
function XcvActionsMenuItem({ className, disabled = false, onSelect, onClick, children, ...props }) {
|
|
1051
|
+
const menu = useMenu();
|
|
1052
|
+
return /* @__PURE__ */ jsx27(
|
|
1053
|
+
"button",
|
|
1054
|
+
{
|
|
1055
|
+
...props,
|
|
1056
|
+
type: "button",
|
|
1057
|
+
role: "menuitem",
|
|
1058
|
+
tabIndex: -1,
|
|
1059
|
+
"aria-disabled": disabled || void 0,
|
|
1060
|
+
className: ["xcv-actions-menu__item", className].filter(Boolean).join(" "),
|
|
1061
|
+
onClick: (event) => {
|
|
1062
|
+
if (disabled) {
|
|
1063
|
+
event.preventDefault();
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
onClick?.(event);
|
|
1067
|
+
if (event.defaultPrevented) return;
|
|
1068
|
+
onSelect?.();
|
|
1069
|
+
menu.restoreFocus.current = true;
|
|
1070
|
+
menu.setOpen(false);
|
|
1071
|
+
},
|
|
1072
|
+
children
|
|
1073
|
+
}
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
function XcvActionsMenuSeparator() {
|
|
1077
|
+
return /* @__PURE__ */ jsx27("div", { className: "xcv-actions-menu__separator", role: "separator" });
|
|
1078
|
+
}
|
|
1079
|
+
function hasMenuPartsOutsideContent(children, insideContent = false) {
|
|
1080
|
+
let invalid = false;
|
|
1081
|
+
Children2.forEach(children, (child) => {
|
|
1082
|
+
if (invalid || !isValidElement2(child)) return;
|
|
1083
|
+
if (child.type === Fragment2) {
|
|
1084
|
+
invalid = hasMenuPartsOutsideContent(child.props.children, insideContent);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (child.type === XcvActionsMenuContent) {
|
|
1088
|
+
invalid = insideContent || hasMenuPartsOutsideContent(child.props.children, true);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (child.type === XcvActionsMenuItem || child.type === XcvActionsMenuSeparator) {
|
|
1092
|
+
invalid = !insideContent;
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
return invalid;
|
|
1096
|
+
}
|
|
1097
|
+
function countMenuSlots(children, slotType) {
|
|
1098
|
+
let count = 0;
|
|
1099
|
+
Children2.forEach(children, (child) => {
|
|
1100
|
+
if (!isValidElement2(child)) return;
|
|
1101
|
+
if (child.type === slotType) {
|
|
1102
|
+
count += 1;
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (child.type === Fragment2) count += countMenuSlots(child.props.children, slotType);
|
|
1106
|
+
});
|
|
1107
|
+
return count;
|
|
1108
|
+
}
|
|
1109
|
+
var XcvActionsMenu = Object.assign(XcvActionsMenuRoot, { Trigger: XcvActionsMenuTrigger, Content: XcvActionsMenuContent, Item: XcvActionsMenuItem, Separator: XcvActionsMenuSeparator });
|
|
1110
|
+
|
|
1111
|
+
// src/navigation/Tabs.tsx
|
|
1112
|
+
import { Children as Children3, Fragment as Fragment3, createContext as createContext4, forwardRef as forwardRef27, isValidElement as isValidElement3, useContext as useContext4, useId as useId5, useState as useState4 } from "react";
|
|
1113
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
1114
|
+
function getTabId(baseId, kind, value) {
|
|
1115
|
+
return `${baseId}-${kind}-${encodeURIComponent(value)}`;
|
|
1116
|
+
}
|
|
1117
|
+
var TabsContext = createContext4(null);
|
|
1118
|
+
function useTabs() {
|
|
1119
|
+
const value = useContext4(TabsContext);
|
|
1120
|
+
if (!value) throw new Error("XcvTabs parts must be used inside XcvTabs.");
|
|
1121
|
+
return value;
|
|
1122
|
+
}
|
|
1123
|
+
var XcvTabsRoot = forwardRef27(function XcvTabsRoot2({ value, defaultValue, onValueChange, radius = "subtle", orientation = "horizontal", className, children, ...props }, ref) {
|
|
1124
|
+
const [internalValue, setInternalValue] = useState4(defaultValue);
|
|
1125
|
+
const selected = value ?? internalValue;
|
|
1126
|
+
if (selected === void 0) throw new Error("XcvTabs requires a valid value or defaultValue so one tab is selected initially.");
|
|
1127
|
+
const tabListCount = countCompoundComponents(children, XcvTabsList);
|
|
1128
|
+
if (tabListCount !== 1) throw new Error("XcvTabs requires exactly one XcvTabs.List.");
|
|
1129
|
+
const tabValues = collectCompoundValues(children, XcvTabsTab, [XcvTabsList]);
|
|
1130
|
+
const panelValues = collectCompoundValues(children, XcvTabsPanel, []);
|
|
1131
|
+
if (new Set(tabValues).size !== tabValues.length) throw new Error("XcvTabs tab values must be unique.");
|
|
1132
|
+
if (new Set(panelValues).size !== panelValues.length) throw new Error("XcvTabs panel values must be unique.");
|
|
1133
|
+
if (tabValues.length !== panelValues.length || tabValues.some((tabValue) => !panelValues.includes(tabValue))) {
|
|
1134
|
+
throw new Error("Every XcvTabs.Tab must have exactly one XcvTabs.Panel with the same value.");
|
|
1135
|
+
}
|
|
1136
|
+
if (!tabValues.includes(selected)) throw new Error("XcvTabs selected value must match an available tab.");
|
|
1137
|
+
if (isCompoundTabDisabled(children, selected, [XcvTabsList])) throw new Error("XcvTabs selected tab must not be disabled.");
|
|
1138
|
+
const baseId = useId5();
|
|
1139
|
+
const select = (next) => {
|
|
1140
|
+
if (value === void 0) setInternalValue(next);
|
|
1141
|
+
onValueChange?.(next);
|
|
1142
|
+
};
|
|
1143
|
+
return /* @__PURE__ */ jsx28(TabsContext.Provider, { value: { value: selected, select, baseId, orientation }, children: /* @__PURE__ */ jsx28("div", { ref, ...props, "data-xcv-radius": radius, className: ["xcv-tabs", `xcv-tabs--${orientation}`, className].filter(Boolean).join(" "), children }) });
|
|
1144
|
+
});
|
|
1145
|
+
function XcvTabsList({ label, className, children, onKeyDown, ...props }) {
|
|
1146
|
+
const tabs = useTabs();
|
|
1147
|
+
function handleKeyDown(event) {
|
|
1148
|
+
onKeyDown?.(event);
|
|
1149
|
+
if (event.defaultPrevented) return;
|
|
1150
|
+
const directionElement = event.currentTarget.closest("[dir]");
|
|
1151
|
+
const inheritedDirection = directionElement?.getAttribute("dir")?.toLowerCase();
|
|
1152
|
+
const computedDirection = getComputedStyle(event.currentTarget).direction;
|
|
1153
|
+
const isRtl = tabs.orientation === "horizontal" && (inheritedDirection === "rtl" || inheritedDirection !== "ltr" && computedDirection === "rtl");
|
|
1154
|
+
const forward = tabs.orientation === "horizontal" ? isRtl ? "ArrowLeft" : "ArrowRight" : "ArrowDown";
|
|
1155
|
+
const backward = tabs.orientation === "horizontal" ? isRtl ? "ArrowRight" : "ArrowLeft" : "ArrowUp";
|
|
1156
|
+
if (![forward, backward, "Home", "End"].includes(event.key)) return;
|
|
1157
|
+
const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]:not(:disabled)'));
|
|
1158
|
+
if (!buttons.length) return;
|
|
1159
|
+
event.preventDefault();
|
|
1160
|
+
const currentTab = event.target.closest('[role="tab"]');
|
|
1161
|
+
const index = currentTab ? buttons.indexOf(currentTab) : -1;
|
|
1162
|
+
if (index < 0) return;
|
|
1163
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === forward ? 1 : -1) + buttons.length) % buttons.length;
|
|
1164
|
+
const nextButton = buttons[next];
|
|
1165
|
+
if (!nextButton) return;
|
|
1166
|
+
nextButton.focus();
|
|
1167
|
+
const nextValue = nextButton.dataset.value;
|
|
1168
|
+
if (nextValue !== void 0) tabs.select(nextValue);
|
|
1169
|
+
}
|
|
1170
|
+
return /* @__PURE__ */ jsx28("div", { ...props, role: "tablist", "aria-label": label, "aria-orientation": tabs.orientation, className: ["xcv-tabs__list", className].filter(Boolean).join(" "), onKeyDown: handleKeyDown, children });
|
|
1171
|
+
}
|
|
1172
|
+
function XcvTabsTab({ value, className, children, disabled, ...props }) {
|
|
1173
|
+
const tabs = useTabs();
|
|
1174
|
+
const selected = tabs.value === value;
|
|
1175
|
+
const id = getTabId(tabs.baseId, "tab", value);
|
|
1176
|
+
const panelId = getTabId(tabs.baseId, "panel", value);
|
|
1177
|
+
return /* @__PURE__ */ jsx28(
|
|
1178
|
+
"button",
|
|
1179
|
+
{
|
|
1180
|
+
...props,
|
|
1181
|
+
type: "button",
|
|
1182
|
+
role: "tab",
|
|
1183
|
+
id,
|
|
1184
|
+
"data-value": value,
|
|
1185
|
+
"aria-selected": selected,
|
|
1186
|
+
"aria-controls": panelId,
|
|
1187
|
+
tabIndex: selected ? 0 : -1,
|
|
1188
|
+
disabled,
|
|
1189
|
+
className: ["xcv-tabs__tab", selected && "xcv-tabs__tab--selected", className].filter(Boolean).join(" "),
|
|
1190
|
+
onClick: (event) => {
|
|
1191
|
+
props.onClick?.(event);
|
|
1192
|
+
if (!event.defaultPrevented && !disabled) tabs.select(value);
|
|
1193
|
+
},
|
|
1194
|
+
children
|
|
1195
|
+
}
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
function XcvTabsPanel({ value, className, children, ...props }) {
|
|
1199
|
+
const tabs = useTabs();
|
|
1200
|
+
const selected = tabs.value === value;
|
|
1201
|
+
return /* @__PURE__ */ jsx28(
|
|
1202
|
+
"div",
|
|
1203
|
+
{
|
|
1204
|
+
...props,
|
|
1205
|
+
id: getTabId(tabs.baseId, "panel", value),
|
|
1206
|
+
role: "tabpanel",
|
|
1207
|
+
"aria-labelledby": getTabId(tabs.baseId, "tab", value),
|
|
1208
|
+
hidden: !selected,
|
|
1209
|
+
tabIndex: 0,
|
|
1210
|
+
className: ["xcv-tabs__panel", className].filter(Boolean).join(" "),
|
|
1211
|
+
children
|
|
1212
|
+
}
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
function countCompoundComponents(children, targetType) {
|
|
1216
|
+
let count = 0;
|
|
1217
|
+
Children3.forEach(children, (child) => {
|
|
1218
|
+
if (!isValidElement3(child)) return;
|
|
1219
|
+
if (child.type === targetType) {
|
|
1220
|
+
count += 1;
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (child.type === Fragment3) count += countCompoundComponents(child.props.children, targetType);
|
|
1224
|
+
});
|
|
1225
|
+
return count;
|
|
1226
|
+
}
|
|
1227
|
+
function collectCompoundValues(children, targetType, traversableTypes) {
|
|
1228
|
+
const values = [];
|
|
1229
|
+
Children3.forEach(children, (child) => {
|
|
1230
|
+
if (!isValidElement3(child)) return;
|
|
1231
|
+
if (child.type === targetType) {
|
|
1232
|
+
if (typeof child.props.value === "string") values.push(child.props.value);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (child.type === Fragment3 || traversableTypes.includes(child.type)) {
|
|
1236
|
+
values.push(...collectCompoundValues(child.props.children, targetType, traversableTypes));
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
return values;
|
|
1240
|
+
}
|
|
1241
|
+
function isCompoundTabDisabled(children, selectedValue, traversableTypes) {
|
|
1242
|
+
let disabled = false;
|
|
1243
|
+
Children3.forEach(children, (child) => {
|
|
1244
|
+
if (!isValidElement3(child)) return;
|
|
1245
|
+
if (child.type === XcvTabsTab) {
|
|
1246
|
+
if (child.props.value === selectedValue && child.props.disabled === true) disabled = true;
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (child.type === Fragment3 || traversableTypes.includes(child.type)) {
|
|
1250
|
+
if (isCompoundTabDisabled(child.props.children, selectedValue, traversableTypes)) disabled = true;
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
return disabled;
|
|
1254
|
+
}
|
|
1255
|
+
var XcvTabs = Object.assign(XcvTabsRoot, { List: XcvTabsList, Tab: XcvTabsTab, Panel: XcvTabsPanel });
|
|
1256
|
+
|
|
1257
|
+
// src/navigation/Stepper.tsx
|
|
1258
|
+
import { forwardRef as forwardRef28 } from "react";
|
|
1259
|
+
import { jsx as jsx29, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1260
|
+
var XcvStepper = forwardRef28(function XcvStepper2({ steps, columns = 4, label = "Workflow steps", radius = "subtle", className, ...props }, ref) {
|
|
1261
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
1262
|
+
for (const step of steps) {
|
|
1263
|
+
if (stepIds.has(step.id)) {
|
|
1264
|
+
throw new Error(`XcvStepper step ids must be unique. Duplicate id: "${step.id}".`);
|
|
1265
|
+
}
|
|
1266
|
+
stepIds.add(step.id);
|
|
1267
|
+
}
|
|
1268
|
+
return /* @__PURE__ */ jsx29(
|
|
1269
|
+
"nav",
|
|
1270
|
+
{
|
|
1271
|
+
ref,
|
|
1272
|
+
...props,
|
|
1273
|
+
"aria-label": label,
|
|
1274
|
+
"data-xcv-radius": radius,
|
|
1275
|
+
className: ["xcv-stepper", `xcv-stepper--columns-${columns}`, className].filter(Boolean).join(" "),
|
|
1276
|
+
children: /* @__PURE__ */ jsx29("ol", { className: "xcv-stepper__list", children: steps.map((step, index) => /* @__PURE__ */ jsx29("li", { className: "xcv-stepper__item", children: /* @__PURE__ */ jsxs9("a", { className: "xcv-stepper__link", href: step.href, children: [
|
|
1277
|
+
/* @__PURE__ */ jsx29("span", { className: "xcv-stepper__number", "aria-hidden": "true", children: String(index + 1).padStart(2, "0") }),
|
|
1278
|
+
/* @__PURE__ */ jsx29("span", { className: "xcv-stepper__label", children: step.label })
|
|
1279
|
+
] }) }, step.id)) })
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
// src/navigation/Pagination.tsx
|
|
1285
|
+
import { forwardRef as forwardRef29 } from "react";
|
|
1286
|
+
import { jsx as jsx30, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1287
|
+
var XcvPagination = forwardRef29(function XcvPagination2({
|
|
1288
|
+
page,
|
|
1289
|
+
pageCount,
|
|
1290
|
+
onPageChange,
|
|
1291
|
+
siblingCount: _siblingCount,
|
|
1292
|
+
label = "Pagination",
|
|
1293
|
+
firstLabel = "First page",
|
|
1294
|
+
previousLabel = "Previous page",
|
|
1295
|
+
nextLabel = "Next page",
|
|
1296
|
+
lastLabel = "Last page",
|
|
1297
|
+
pageLabel = (number) => `Page ${number}`,
|
|
1298
|
+
radius = "subtle",
|
|
1299
|
+
className,
|
|
1300
|
+
...props
|
|
1301
|
+
}, ref) {
|
|
1302
|
+
if (!Number.isSafeInteger(page) || !Number.isSafeInteger(pageCount) || pageCount < 0 || page < 1 || (pageCount === 0 ? page !== 1 : page > pageCount)) {
|
|
1303
|
+
throw new RangeError("XcvPagination expects a valid 1-based page within pageCount.");
|
|
1304
|
+
}
|
|
1305
|
+
void _siblingCount;
|
|
1306
|
+
if (pageCount <= 1) return null;
|
|
1307
|
+
const firstVisiblePage = pageCount >= 3 ? Math.min(Math.max(1, page - 1), pageCount - 2) : 1;
|
|
1308
|
+
const visiblePages = Array.from({ length: 3 }, (_, index) => {
|
|
1309
|
+
const number = firstVisiblePage + index;
|
|
1310
|
+
return number <= pageCount ? number : null;
|
|
1311
|
+
});
|
|
1312
|
+
return /* @__PURE__ */ jsxs10(
|
|
1313
|
+
"nav",
|
|
1314
|
+
{
|
|
1315
|
+
ref,
|
|
1316
|
+
...props,
|
|
1317
|
+
"data-xcv-radius": radius,
|
|
1318
|
+
"aria-label": label,
|
|
1319
|
+
className: ["xcv-pagination", className].filter(Boolean).join(" "),
|
|
1320
|
+
children: [
|
|
1321
|
+
/* @__PURE__ */ jsx30(
|
|
1322
|
+
"button",
|
|
1323
|
+
{
|
|
1324
|
+
type: "button",
|
|
1325
|
+
className: "xcv-pagination__control",
|
|
1326
|
+
"aria-label": firstLabel,
|
|
1327
|
+
disabled: page <= 1,
|
|
1328
|
+
onClick: () => onPageChange(1),
|
|
1329
|
+
children: "\xAB"
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
/* @__PURE__ */ jsx30(
|
|
1333
|
+
"button",
|
|
1334
|
+
{
|
|
1335
|
+
type: "button",
|
|
1336
|
+
className: "xcv-pagination__control",
|
|
1337
|
+
"aria-label": previousLabel,
|
|
1338
|
+
disabled: page <= 1,
|
|
1339
|
+
onClick: () => onPageChange(page - 1),
|
|
1340
|
+
children: "\u2039"
|
|
1341
|
+
}
|
|
1342
|
+
),
|
|
1343
|
+
visiblePages.map((number, index) => number === null ? /* @__PURE__ */ jsx30("span", { className: "xcv-pagination__page-slot xcv-pagination__page-slot--empty", "aria-hidden": "true" }, `placeholder-${index}`) : /* @__PURE__ */ jsx30(
|
|
1344
|
+
"button",
|
|
1345
|
+
{
|
|
1346
|
+
type: "button",
|
|
1347
|
+
className: ["xcv-pagination__page-slot", "xcv-pagination__page", number === page && "xcv-pagination__page--current"].filter(Boolean).join(" "),
|
|
1348
|
+
"aria-label": pageLabel(number),
|
|
1349
|
+
"aria-current": number === page ? "page" : void 0,
|
|
1350
|
+
onClick: () => onPageChange(number),
|
|
1351
|
+
children: number
|
|
1352
|
+
},
|
|
1353
|
+
number
|
|
1354
|
+
)),
|
|
1355
|
+
/* @__PURE__ */ jsx30(
|
|
1356
|
+
"button",
|
|
1357
|
+
{
|
|
1358
|
+
type: "button",
|
|
1359
|
+
className: "xcv-pagination__control",
|
|
1360
|
+
"aria-label": nextLabel,
|
|
1361
|
+
disabled: page >= pageCount,
|
|
1362
|
+
onClick: () => onPageChange(page + 1),
|
|
1363
|
+
children: "\u203A"
|
|
1364
|
+
}
|
|
1365
|
+
),
|
|
1366
|
+
/* @__PURE__ */ jsx30(
|
|
1367
|
+
"button",
|
|
1368
|
+
{
|
|
1369
|
+
type: "button",
|
|
1370
|
+
className: "xcv-pagination__control",
|
|
1371
|
+
"aria-label": lastLabel,
|
|
1372
|
+
disabled: page >= pageCount,
|
|
1373
|
+
onClick: () => onPageChange(pageCount),
|
|
1374
|
+
children: "\xBB"
|
|
1375
|
+
}
|
|
1376
|
+
)
|
|
1377
|
+
]
|
|
1378
|
+
}
|
|
1379
|
+
);
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
// src/navigation/FilterGroup.tsx
|
|
1383
|
+
import { forwardRef as forwardRef30, useState as useState5 } from "react";
|
|
1384
|
+
import { jsx as jsx31, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1385
|
+
var XcvFilterGroup = forwardRef30(function XcvFilterGroup2({
|
|
1386
|
+
label,
|
|
1387
|
+
name,
|
|
1388
|
+
options,
|
|
1389
|
+
values,
|
|
1390
|
+
defaultValues = [],
|
|
1391
|
+
onValuesChange,
|
|
1392
|
+
orientation = "vertical",
|
|
1393
|
+
className,
|
|
1394
|
+
...props
|
|
1395
|
+
}, ref) {
|
|
1396
|
+
const [internalValues, setInternalValues] = useState5([...defaultValues]);
|
|
1397
|
+
const selected = [...new Set(values ?? internalValues)];
|
|
1398
|
+
const optionValues = /* @__PURE__ */ new Set();
|
|
1399
|
+
for (const option of options) {
|
|
1400
|
+
if (optionValues.has(option.value)) throw new Error(`XcvFilterGroup option values must be unique. Duplicate value: "${option.value}".`);
|
|
1401
|
+
optionValues.add(option.value);
|
|
1402
|
+
}
|
|
1403
|
+
function toggle(value, checked) {
|
|
1404
|
+
const option = options.find((item) => item.value === value);
|
|
1405
|
+
if (!option || option.disabled) return;
|
|
1406
|
+
const next = checked ? [...selected, value] : selected.filter((item) => item !== value);
|
|
1407
|
+
if (values === void 0) setInternalValues(next);
|
|
1408
|
+
onValuesChange?.(next);
|
|
1409
|
+
}
|
|
1410
|
+
return /* @__PURE__ */ jsxs11("fieldset", { ref, ...props, className: ["xcv-filter-group", `xcv-filter-group--${orientation}`, className].filter(Boolean).join(" "), children: [
|
|
1411
|
+
/* @__PURE__ */ jsx31("legend", { className: "xcv-filter-group__legend", children: label }),
|
|
1412
|
+
/* @__PURE__ */ jsx31("div", { className: "xcv-filter-group__options", children: options.map((option) => /* @__PURE__ */ jsxs11("label", { className: `xcv-filter-group__option${option.disabled ? " xcv-filter-group__option--disabled" : ""}`, children: [
|
|
1413
|
+
/* @__PURE__ */ jsx31("input", { type: "checkbox", className: "xcv-checkable xcv-checkbox", name, value: option.value, checked: selected.includes(option.value), disabled: option.disabled, onChange: (event) => toggle(option.value, event.currentTarget.checked) }),
|
|
1414
|
+
/* @__PURE__ */ jsx31("span", { children: option.label })
|
|
1415
|
+
] }, option.value)) })
|
|
1416
|
+
] });
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
// src/data/StatCard.tsx
|
|
1420
|
+
import { forwardRef as forwardRef31 } from "react";
|
|
1421
|
+
import { jsx as jsx32, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1422
|
+
var XcvStatCard = forwardRef31(function XcvStatCard2({ label, value, description, detail, detailTone = "neutral", icon, radius = "subtle", className, ...props }, ref) {
|
|
1423
|
+
return /* @__PURE__ */ jsxs12("div", { ref, ...props, className: ["xcv-stat-card", "xcv-card", className].filter(Boolean).join(" "), "data-xcv-radius": radius, children: [
|
|
1424
|
+
/* @__PURE__ */ jsxs12("div", { className: "xcv-stat-card__header", children: [
|
|
1425
|
+
/* @__PURE__ */ jsx32("div", { className: "xcv-stat-card__label", children: label }),
|
|
1426
|
+
icon != null && /* @__PURE__ */ jsx32("span", { className: "xcv-stat-card__icon", "aria-hidden": "true", children: icon })
|
|
1427
|
+
] }),
|
|
1428
|
+
/* @__PURE__ */ jsx32("div", { className: "xcv-stat-card__value", children: value }),
|
|
1429
|
+
description != null && /* @__PURE__ */ jsx32("div", { className: "xcv-stat-card__description", children: description }),
|
|
1430
|
+
detail != null && /* @__PURE__ */ jsx32("div", { className: ["xcv-stat-card__detail", `xcv-stat-card__detail--${detailTone}`].join(" "), children: detail })
|
|
1431
|
+
] });
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
// src/data/Table.tsx
|
|
1435
|
+
import { forwardRef as forwardRef32 } from "react";
|
|
1436
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
1437
|
+
var XcvTableRoot = forwardRef32(function XcvTable({ density = "comfortable", radius = "large", wrapperClassName, wrapperLabel = "Scrollable table", className, children, ...props }, ref) {
|
|
1438
|
+
return /* @__PURE__ */ jsx33("div", { className: ["xcv-table__viewport", wrapperClassName].filter(Boolean).join(" "), "data-xcv-radius": radius, role: "region", "aria-label": wrapperLabel, tabIndex: 0, children: /* @__PURE__ */ jsx33("table", { ...props, ref, className: ["xcv-table", `xcv-table--${density}`, className].filter(Boolean).join(" "), children }) });
|
|
1439
|
+
});
|
|
1440
|
+
function XcvTableCaption({ className, ...props }) {
|
|
1441
|
+
return /* @__PURE__ */ jsx33("caption", { ...props, className: ["xcv-table__caption", className].filter(Boolean).join(" ") });
|
|
1442
|
+
}
|
|
1443
|
+
function XcvTableHead({ className, ...props }) {
|
|
1444
|
+
return /* @__PURE__ */ jsx33("thead", { ...props, className: ["xcv-table__head", className].filter(Boolean).join(" ") });
|
|
1445
|
+
}
|
|
1446
|
+
function XcvTableBody({ className, ...props }) {
|
|
1447
|
+
return /* @__PURE__ */ jsx33("tbody", { ...props, className: ["xcv-table__body", className].filter(Boolean).join(" ") });
|
|
1448
|
+
}
|
|
1449
|
+
function XcvTableRow({ className, ...props }) {
|
|
1450
|
+
return /* @__PURE__ */ jsx33("tr", { ...props, className: ["xcv-table__row", className].filter(Boolean).join(" ") });
|
|
1451
|
+
}
|
|
1452
|
+
function XcvTableHeaderCell({ scope = "col", className, ...props }) {
|
|
1453
|
+
return /* @__PURE__ */ jsx33("th", { ...props, scope, className: ["xcv-table__header-cell", className].filter(Boolean).join(" ") });
|
|
1454
|
+
}
|
|
1455
|
+
function XcvTableCell({ className, ...props }) {
|
|
1456
|
+
return /* @__PURE__ */ jsx33("td", { ...props, className: ["xcv-table__cell", className].filter(Boolean).join(" ") });
|
|
1457
|
+
}
|
|
1458
|
+
var XcvTable2 = Object.assign(XcvTableRoot, {
|
|
1459
|
+
Caption: XcvTableCaption,
|
|
1460
|
+
Head: XcvTableHead,
|
|
1461
|
+
Body: XcvTableBody,
|
|
1462
|
+
Row: XcvTableRow,
|
|
1463
|
+
HeaderCell: XcvTableHeaderCell,
|
|
1464
|
+
Cell: XcvTableCell
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
// src/data/SearchBox.tsx
|
|
1468
|
+
import { forwardRef as forwardRef33, useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
|
|
1469
|
+
import { jsx as jsx34, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1470
|
+
var XcvSearchBox = forwardRef33(function XcvSearchBox2({ label, radius = "subtle", value, defaultValue = "", onValueChange, onSearch, debounceMs, loading = false, clearable = true, clearLabel = "Clear search", submitLabel = "Search", disabled, className, ...inputProps }, ref) {
|
|
1471
|
+
const [internalValue, setInternalValue] = useState6(defaultValue);
|
|
1472
|
+
const currentValue = value ?? internalValue;
|
|
1473
|
+
const onSearchRef = useRef3(onSearch);
|
|
1474
|
+
const debounceTimer = useRef3(void 0);
|
|
1475
|
+
const previousValue = useRef3(currentValue);
|
|
1476
|
+
onSearchRef.current = onSearch;
|
|
1477
|
+
useEffect3(() => {
|
|
1478
|
+
if (previousValue.current === currentValue) return;
|
|
1479
|
+
previousValue.current = currentValue;
|
|
1480
|
+
if (disabled || loading || debounceMs === void 0 || !Number.isFinite(debounceMs) || debounceMs < 0) return;
|
|
1481
|
+
debounceTimer.current = setTimeout(() => {
|
|
1482
|
+
debounceTimer.current = void 0;
|
|
1483
|
+
onSearchRef.current(currentValue);
|
|
1484
|
+
}, debounceMs);
|
|
1485
|
+
return () => {
|
|
1486
|
+
if (debounceTimer.current !== void 0) clearTimeout(debounceTimer.current);
|
|
1487
|
+
debounceTimer.current = void 0;
|
|
1488
|
+
};
|
|
1489
|
+
}, [currentValue, debounceMs, disabled, loading]);
|
|
1490
|
+
function updateValue(next) {
|
|
1491
|
+
if (value === void 0) setInternalValue(next);
|
|
1492
|
+
onValueChange?.(next);
|
|
1493
|
+
}
|
|
1494
|
+
function submit(event) {
|
|
1495
|
+
event.preventDefault();
|
|
1496
|
+
if (disabled || loading || currentValue.trim().length === 0) return;
|
|
1497
|
+
if (debounceTimer.current !== void 0) clearTimeout(debounceTimer.current);
|
|
1498
|
+
debounceTimer.current = void 0;
|
|
1499
|
+
onSearch(currentValue);
|
|
1500
|
+
}
|
|
1501
|
+
return /* @__PURE__ */ jsxs13("form", { "data-xcv-radius": radius, className: ["xcv-search-box", className].filter(Boolean).join(" "), role: "search", "aria-label": label, "aria-busy": loading || void 0, onSubmit: submit, children: [
|
|
1502
|
+
/* @__PURE__ */ jsxs13("label", { className: "xcv-search-box__label", children: [
|
|
1503
|
+
label,
|
|
1504
|
+
/* @__PURE__ */ jsx34(XcvTextInput, { ...inputProps, ref, type: "search", className: "xcv-search-box__input", inputSize: "md", radius, value: currentValue, disabled: disabled || loading, onChange: (event) => updateValue(event.currentTarget.value) })
|
|
1505
|
+
] }),
|
|
1506
|
+
clearable && currentValue.length > 0 && /* @__PURE__ */ jsx34(
|
|
1507
|
+
XcvButton,
|
|
1508
|
+
{
|
|
1509
|
+
type: "button",
|
|
1510
|
+
variant: "secondary",
|
|
1511
|
+
size: "md",
|
|
1512
|
+
radius,
|
|
1513
|
+
iconOnly: true,
|
|
1514
|
+
"aria-label": clearLabel,
|
|
1515
|
+
className: "xcv-search-box__clear",
|
|
1516
|
+
disabled: disabled || loading,
|
|
1517
|
+
onClick: () => updateValue(""),
|
|
1518
|
+
children: /* @__PURE__ */ jsx34("span", { "aria-hidden": "true", children: "\xD7" })
|
|
1519
|
+
}
|
|
1520
|
+
),
|
|
1521
|
+
/* @__PURE__ */ jsx34(XcvButton, { type: "submit", size: "md", radius, loading, disabled: disabled || currentValue.trim().length === 0, children: submitLabel })
|
|
1522
|
+
] });
|
|
1523
|
+
});
|
|
1524
|
+
export {
|
|
1525
|
+
XcvAccordion,
|
|
1526
|
+
XcvActionsMenu,
|
|
1527
|
+
XcvBadge,
|
|
1528
|
+
XcvBox,
|
|
1529
|
+
XcvButton,
|
|
1530
|
+
XcvCard,
|
|
1531
|
+
XcvCheckbox,
|
|
1532
|
+
XcvContainer,
|
|
1533
|
+
XcvDivider,
|
|
1534
|
+
XcvFilterGroup,
|
|
1535
|
+
XcvFormField,
|
|
1536
|
+
XcvInline,
|
|
1537
|
+
XcvLabel,
|
|
1538
|
+
XcvLink,
|
|
1539
|
+
XcvList,
|
|
1540
|
+
XcvListItem,
|
|
1541
|
+
XcvLoader,
|
|
1542
|
+
XcvMedia,
|
|
1543
|
+
XcvNotice,
|
|
1544
|
+
XcvPagination,
|
|
1545
|
+
XcvRadio,
|
|
1546
|
+
XcvSearchBox,
|
|
1547
|
+
XcvSelect,
|
|
1548
|
+
XcvStack,
|
|
1549
|
+
XcvStatCard,
|
|
1550
|
+
XcvStepper,
|
|
1551
|
+
XcvSurface,
|
|
1552
|
+
XcvTable2 as XcvTable,
|
|
1553
|
+
XcvTabs,
|
|
1554
|
+
XcvText,
|
|
1555
|
+
XcvTextArea,
|
|
1556
|
+
XcvTextInput,
|
|
1557
|
+
XcvTitle,
|
|
1558
|
+
XcvToggle
|
|
1559
|
+
};
|