@classic-homes/theme-react 0.1.52 → 0.1.53

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/dist/index.mjs CHANGED
@@ -39,9 +39,9 @@ var buttonVariants = cva(
39
39
  }
40
40
  );
41
41
  var Button = React.forwardRef(
42
- ({ className, variant, size, asChild = false, ...props }, ref) => {
42
+ ({ className, variant, size, asChild = false, children, ...props }, ref) => {
43
43
  const Comp = asChild ? Slot : "button";
44
- return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
44
+ return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props, children });
45
45
  }
46
46
  );
47
47
  Button.displayName = "Button";
@@ -50,8 +50,8 @@ Button.displayName = "Button";
50
50
  import * as React2 from "react";
51
51
  import { jsx as jsx2 } from "react/jsx-runtime";
52
52
  var Card = React2.forwardRef(
53
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
54
- "div",
53
+ ({ className, as: Component = "div", ...props }, ref) => /* @__PURE__ */ jsx2(
54
+ Component,
55
55
  {
56
56
  ref,
57
57
  className: cn(
@@ -68,8 +68,8 @@ var CardHeader = React2.forwardRef(
68
68
  );
69
69
  CardHeader.displayName = "CardHeader";
70
70
  var CardTitle = React2.forwardRef(
71
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
72
- "h3",
71
+ ({ className, as: Component = "h3", ...props }, ref) => /* @__PURE__ */ jsx2(
72
+ Component,
73
73
  {
74
74
  ref,
75
75
  className: cn("text-2xl font-semibold leading-none tracking-tight", className),
@@ -93,13 +93,32 @@ CardFooter.displayName = "CardFooter";
93
93
  import * as React3 from "react";
94
94
  import { jsx as jsx3 } from "react/jsx-runtime";
95
95
  var Input = React3.forwardRef(
96
- ({ className, type, ...props }, ref) => {
96
+ ({
97
+ className,
98
+ type,
99
+ "aria-invalid": ariaInvalid,
100
+ required,
101
+ "aria-describedby": ariaDescribedBy,
102
+ ...props
103
+ }, ref) => {
104
+ if (process.env.NODE_ENV !== "production") {
105
+ if (!props["aria-label"] && !props["aria-labelledby"] && !props.id) {
106
+ console.warn(
107
+ "Input: Missing accessible label. Provide aria-label, aria-labelledby, or associate with a Label using id/htmlFor for WCAG compliance."
108
+ );
109
+ }
110
+ }
97
111
  return /* @__PURE__ */ jsx3(
98
112
  "input",
99
113
  {
100
114
  type,
115
+ "aria-invalid": ariaInvalid,
116
+ "aria-required": required,
117
+ "aria-describedby": ariaDescribedBy,
118
+ required,
101
119
  className: cn(
102
120
  "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
121
+ ariaInvalid === true || ariaInvalid === "true" ? "border-destructive focus-visible:ring-destructive" : "",
103
122
  className
104
123
  ),
105
124
  ref,
@@ -111,7 +130,57 @@ var Input = React3.forwardRef(
111
130
  Input.displayName = "Input";
112
131
 
113
132
  // src/components/PasswordInput.tsx
133
+ import * as React5 from "react";
134
+
135
+ // src/hooks/useAnnounce.ts
114
136
  import * as React4 from "react";
137
+ function useAnnounce(defaultOptions) {
138
+ const [message, setMessage] = React4.useState("");
139
+ const [politeness, setPoliteness] = React4.useState(
140
+ defaultOptions?.politeness ?? "polite"
141
+ );
142
+ const clearTimeoutRef = React4.useRef(null);
143
+ const clear = React4.useCallback(() => {
144
+ setMessage("");
145
+ if (clearTimeoutRef.current) {
146
+ clearTimeout(clearTimeoutRef.current);
147
+ clearTimeoutRef.current = null;
148
+ }
149
+ }, []);
150
+ const announce = React4.useCallback(
151
+ (newMessage, options) => {
152
+ if (clearTimeoutRef.current) {
153
+ clearTimeout(clearTimeoutRef.current);
154
+ }
155
+ if (options?.politeness) {
156
+ setPoliteness(options.politeness);
157
+ }
158
+ setMessage(newMessage);
159
+ const delay = options?.clearDelay ?? defaultOptions?.clearDelay ?? 1e3;
160
+ if (delay > 0) {
161
+ clearTimeoutRef.current = setTimeout(() => {
162
+ setMessage("");
163
+ }, delay);
164
+ }
165
+ },
166
+ [defaultOptions?.clearDelay]
167
+ );
168
+ React4.useEffect(() => {
169
+ return () => {
170
+ if (clearTimeoutRef.current) {
171
+ clearTimeout(clearTimeoutRef.current);
172
+ }
173
+ };
174
+ }, []);
175
+ return {
176
+ announce,
177
+ message,
178
+ politeness,
179
+ clear
180
+ };
181
+ }
182
+
183
+ // src/components/PasswordInput.tsx
115
184
  import { jsx as jsx4, jsxs } from "react/jsx-runtime";
116
185
  var EyeIcon = () => /* @__PURE__ */ jsxs(
117
186
  "svg",
@@ -124,6 +193,8 @@ var EyeIcon = () => /* @__PURE__ */ jsxs(
124
193
  strokeWidth: 2,
125
194
  strokeLinecap: "round",
126
195
  strokeLinejoin: "round",
196
+ "aria-hidden": "true",
197
+ focusable: "false",
127
198
  children: [
128
199
  /* @__PURE__ */ jsx4("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
129
200
  /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "3" })
@@ -141,20 +212,48 @@ var EyeOffIcon = () => /* @__PURE__ */ jsx4(
141
212
  strokeWidth: 2,
142
213
  strokeLinecap: "round",
143
214
  strokeLinejoin: "round",
215
+ "aria-hidden": "true",
216
+ focusable: "false",
144
217
  children: /* @__PURE__ */ jsx4("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22" })
145
218
  }
146
219
  );
147
- var PasswordInput = React4.forwardRef(
148
- ({ className, disabled, showLabel = "Show password", hideLabel = "Hide password", ...props }, ref) => {
149
- const [showPassword, setShowPassword] = React4.useState(false);
220
+ var PasswordInput = React5.forwardRef(
221
+ ({
222
+ className,
223
+ disabled,
224
+ showLabel = "Show password",
225
+ hideLabel = "Hide password",
226
+ visibleAnnouncement = "Password is now visible",
227
+ hiddenAnnouncement = "Password is now hidden",
228
+ id,
229
+ "aria-label": ariaLabel,
230
+ "aria-labelledby": ariaLabelledBy,
231
+ ...props
232
+ }, ref) => {
233
+ const [showPassword, setShowPassword] = React5.useState(false);
234
+ const { announce, message, politeness } = useAnnounce();
235
+ if (process.env.NODE_ENV !== "production") {
236
+ if (!ariaLabel && !ariaLabelledBy && !id) {
237
+ console.warn(
238
+ "PasswordInput: Missing accessible label. Provide aria-label, aria-labelledby, or an id to associate with a Label element for WCAG compliance."
239
+ );
240
+ }
241
+ }
150
242
  const toggleVisibility = () => {
151
- setShowPassword((prev) => !prev);
243
+ setShowPassword((prev) => {
244
+ const newState = !prev;
245
+ announce(newState ? visibleAnnouncement : hiddenAnnouncement);
246
+ return newState;
247
+ });
152
248
  };
153
249
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
154
250
  /* @__PURE__ */ jsx4(
155
251
  "input",
156
252
  {
157
253
  type: showPassword ? "text" : "password",
254
+ id,
255
+ "aria-label": ariaLabel,
256
+ "aria-labelledby": ariaLabelledBy,
158
257
  className: cn(
159
258
  "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
160
259
  className
@@ -168,35 +267,37 @@ var PasswordInput = React4.forwardRef(
168
267
  "button",
169
268
  {
170
269
  type: "button",
171
- className: "absolute right-0 top-0 h-10 w-10 flex items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-r-md disabled:pointer-events-none disabled:opacity-50",
270
+ className: "absolute right-0 top-0 h-11 w-11 -mr-0.5 -mt-0.5 flex items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-r-md disabled:pointer-events-none disabled:opacity-50",
172
271
  onClick: toggleVisibility,
173
272
  disabled,
174
273
  "aria-label": showPassword ? hideLabel : showLabel,
274
+ "aria-pressed": showPassword,
175
275
  children: showPassword ? /* @__PURE__ */ jsx4(EyeOffIcon, {}) : /* @__PURE__ */ jsx4(EyeIcon, {})
176
276
  }
177
- )
277
+ ),
278
+ /* @__PURE__ */ jsx4("div", { role: "status", "aria-live": politeness, "aria-atomic": "true", className: "sr-only", children: message })
178
279
  ] });
179
280
  }
180
281
  );
181
282
  PasswordInput.displayName = "PasswordInput";
182
283
 
183
284
  // src/components/Label.tsx
184
- import * as React5 from "react";
285
+ import * as React6 from "react";
185
286
  import * as LabelPrimitive from "@radix-ui/react-label";
186
287
  import { cva as cva2 } from "class-variance-authority";
187
288
  import { jsx as jsx5 } from "react/jsx-runtime";
188
289
  var labelVariants = cva2(
189
290
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
190
291
  );
191
- var Label = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props }));
292
+ var Label = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props }));
192
293
  Label.displayName = LabelPrimitive.Root.displayName;
193
294
 
194
295
  // src/components/Badge.tsx
195
- import * as React6 from "react";
296
+ import * as React7 from "react";
196
297
  import { cva as cva3 } from "class-variance-authority";
197
- import { jsx as jsx6 } from "react/jsx-runtime";
298
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
198
299
  var badgeVariants = cva3(
199
- "inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
300
+ "inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors",
200
301
  {
201
302
  variants: {
202
303
  variant: {
@@ -211,15 +312,28 @@ var badgeVariants = cva3(
211
312
  }
212
313
  }
213
314
  );
214
- var Badge = React6.forwardRef(
215
- ({ className, variant, ...props }, ref) => {
216
- return /* @__PURE__ */ jsx6(
315
+ var Badge = React7.forwardRef(
316
+ ({ className, variant, live, label, children, ...props }, ref) => {
317
+ if (process.env.NODE_ENV !== "production") {
318
+ if (live && !label) {
319
+ console.warn(
320
+ 'Badge: When using live={true} for dynamic content, consider providing a label prop for better screen reader context (e.g., label="unread notifications" for a count badge).'
321
+ );
322
+ }
323
+ }
324
+ return /* @__PURE__ */ jsxs2(
217
325
  "div",
218
326
  {
219
327
  ref,
220
- role: "status",
328
+ role: live ? "status" : void 0,
329
+ "aria-live": live ? "polite" : void 0,
330
+ "aria-atomic": live ? "true" : void 0,
221
331
  className: cn(badgeVariants({ variant }), className),
222
- ...props
332
+ ...props,
333
+ children: [
334
+ children,
335
+ label && /* @__PURE__ */ jsx6("span", { className: "sr-only", children: label })
336
+ ]
223
337
  }
224
338
  );
225
339
  }
@@ -227,81 +341,123 @@ var Badge = React6.forwardRef(
227
341
  Badge.displayName = "Badge";
228
342
 
229
343
  // src/components/Separator.tsx
230
- import * as React7 from "react";
344
+ import * as React8 from "react";
231
345
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
232
346
  import { jsx as jsx7 } from "react/jsx-runtime";
233
- var Separator = React7.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx7(
234
- SeparatorPrimitive.Root,
235
- {
236
- ref,
237
- decorative,
238
- orientation,
239
- className: cn(
240
- "shrink-0 bg-border",
241
- orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
242
- className
243
- ),
244
- ...props
347
+ var Separator = React8.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => {
348
+ if (process.env.NODE_ENV !== "production") {
349
+ if (!decorative && !props["aria-label"]) {
350
+ console.warn(
351
+ "Separator: Non-decorative separators (decorative={false}) should have an aria-label to describe the content they separate."
352
+ );
353
+ }
245
354
  }
246
- ));
355
+ return /* @__PURE__ */ jsx7(
356
+ SeparatorPrimitive.Root,
357
+ {
358
+ ref,
359
+ decorative,
360
+ orientation,
361
+ className: cn(
362
+ "shrink-0 bg-border",
363
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
364
+ className
365
+ ),
366
+ ...props
367
+ }
368
+ );
369
+ });
247
370
  Separator.displayName = SeparatorPrimitive.Root.displayName;
248
371
 
249
372
  // src/components/Switch.tsx
250
- import * as React8 from "react";
373
+ import * as React9 from "react";
251
374
  import * as SwitchPrimitives from "@radix-ui/react-switch";
252
375
  import { jsx as jsx8 } from "react/jsx-runtime";
253
376
  var SwitchRoot = SwitchPrimitives.Root;
254
377
  var SwitchThumb = SwitchPrimitives.Thumb;
255
- var Switch = React8.forwardRef(
256
- ({ className, size = "default", ...props }, ref) => /* @__PURE__ */ jsx8(
257
- SwitchRoot,
258
- {
259
- className: cn(
260
- "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
261
- size === "default" && "h-7 w-12",
262
- size === "sm" && "h-5 w-9",
263
- className
264
- ),
265
- ref,
266
- ...props,
267
- children: /* @__PURE__ */ jsx8(
268
- SwitchThumb,
269
- {
270
- className: cn(
271
- "pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform",
272
- size === "default" && "h-5 w-5 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-0.5",
273
- size === "sm" && "h-4 w-4 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
274
- )
275
- }
276
- )
378
+ var Switch = React9.forwardRef(
379
+ ({
380
+ className,
381
+ size = "default",
382
+ "aria-label": ariaLabel,
383
+ "aria-labelledby": ariaLabelledBy,
384
+ "aria-describedby": ariaDescribedBy,
385
+ ...props
386
+ }, ref) => {
387
+ if (process.env.NODE_ENV !== "production") {
388
+ if (!ariaLabel && !ariaLabelledBy && !props.id) {
389
+ console.warn(
390
+ "Switch: Missing accessible label. Provide aria-label, aria-labelledby, or associate with a Label using id/htmlFor for WCAG compliance."
391
+ );
392
+ }
277
393
  }
278
- )
394
+ return /* @__PURE__ */ jsx8(
395
+ SwitchRoot,
396
+ {
397
+ className: cn(
398
+ "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
399
+ size === "default" && "h-7 w-12",
400
+ size === "sm" && "h-5 w-9",
401
+ className
402
+ ),
403
+ ref,
404
+ "aria-label": ariaLabel,
405
+ "aria-labelledby": ariaLabelledBy,
406
+ "aria-describedby": ariaDescribedBy,
407
+ ...props,
408
+ children: /* @__PURE__ */ jsx8(
409
+ SwitchThumb,
410
+ {
411
+ className: cn(
412
+ "pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform",
413
+ size === "default" && "h-5 w-5 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-0.5",
414
+ size === "sm" && "h-4 w-4 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
415
+ )
416
+ }
417
+ )
418
+ }
419
+ );
420
+ }
279
421
  );
280
422
  Switch.displayName = SwitchPrimitives.Root.displayName;
281
423
 
282
424
  // src/components/Avatar.tsx
283
- import * as React9 from "react";
425
+ import * as React10 from "react";
284
426
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
285
427
  import { jsx as jsx9 } from "react/jsx-runtime";
286
- var Avatar = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
287
- AvatarPrimitive.Root,
288
- {
289
- ref,
290
- className: cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className),
291
- ...props
428
+ var Avatar = React10.forwardRef(
429
+ ({ className, name, "aria-label": ariaLabel, ...props }, ref) => {
430
+ if (process.env.NODE_ENV !== "production") {
431
+ if (!ariaLabel && !name) {
432
+ console.warn(
433
+ 'Avatar: Missing accessible text. Provide either a "name" prop or "aria-label" for screen reader users.'
434
+ );
435
+ }
436
+ }
437
+ const computedAriaLabel = ariaLabel ?? (name ? `Avatar for ${name}` : void 0);
438
+ return /* @__PURE__ */ jsx9(
439
+ AvatarPrimitive.Root,
440
+ {
441
+ ref,
442
+ "aria-label": computedAriaLabel,
443
+ className: cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className),
444
+ ...props
445
+ }
446
+ );
292
447
  }
293
- ));
448
+ );
294
449
  Avatar.displayName = AvatarPrimitive.Root.displayName;
295
- var AvatarImage = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
450
+ var AvatarImage = React10.forwardRef(({ className, alt, ...props }, ref) => /* @__PURE__ */ jsx9(
296
451
  AvatarPrimitive.Image,
297
452
  {
298
453
  ref,
454
+ alt,
299
455
  className: cn("aspect-square h-full w-full object-cover", className),
300
456
  ...props
301
457
  }
302
458
  ));
303
459
  AvatarImage.displayName = AvatarPrimitive.Image.displayName;
304
- var AvatarFallback = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
460
+ var AvatarFallback = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx9(
305
461
  AvatarPrimitive.Fallback,
306
462
  {
307
463
  ref,
@@ -315,9 +471,9 @@ var AvatarFallback = React9.forwardRef(({ className, ...props }, ref) => /* @__P
315
471
  AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
316
472
 
317
473
  // src/components/Dialog.tsx
318
- import * as React10 from "react";
474
+ import * as React11 from "react";
319
475
  import * as DialogPrimitive from "@radix-ui/react-dialog";
320
- import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
476
+ import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
321
477
  var PrimitiveOverlay = DialogPrimitive.Overlay;
322
478
  var PrimitiveContent = DialogPrimitive.Content;
323
479
  var PrimitiveTitle = DialogPrimitive.Title;
@@ -326,7 +482,7 @@ var Dialog = DialogPrimitive.Root;
326
482
  var DialogTrigger = DialogPrimitive.Trigger;
327
483
  var DialogPortal = DialogPrimitive.Portal;
328
484
  var DialogClose = DialogPrimitive.Close;
329
- var DialogOverlay = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
485
+ var DialogOverlay = React11.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
330
486
  PrimitiveOverlay,
331
487
  {
332
488
  ref,
@@ -338,7 +494,7 @@ var DialogOverlay = React10.forwardRef(({ className, ...props }, ref) => /* @__P
338
494
  }
339
495
  ));
340
496
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
341
- var DialogContent = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(DialogPortal, { children: [
497
+ var DialogContent = React11.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs3(DialogPortal, { children: [
342
498
  /* @__PURE__ */ jsx10(DialogOverlay, {}),
343
499
  /* @__PURE__ */ jsx10(
344
500
  PrimitiveContent,
@@ -364,7 +520,7 @@ var DialogFooter = ({ className, ...props }) => /* @__PURE__ */ jsx10(
364
520
  }
365
521
  );
366
522
  DialogFooter.displayName = "DialogFooter";
367
- var DialogTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
523
+ var DialogTitle = React11.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
368
524
  PrimitiveTitle,
369
525
  {
370
526
  ref,
@@ -373,7 +529,7 @@ var DialogTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PUR
373
529
  }
374
530
  ));
375
531
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
376
- var DialogDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
532
+ var DialogDescription = React11.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
377
533
  PrimitiveDescription,
378
534
  {
379
535
  ref,
@@ -384,9 +540,9 @@ var DialogDescription = React10.forwardRef(({ className, ...props }, ref) => /*
384
540
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
385
541
 
386
542
  // src/components/PageHeader.tsx
387
- import * as React11 from "react";
543
+ import * as React12 from "react";
388
544
  import { cva as cva4 } from "class-variance-authority";
389
- import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
545
+ import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
390
546
  var pageHeaderContainerVariants = cva4("", {
391
547
  variants: {
392
548
  variant: {
@@ -435,25 +591,34 @@ var pageHeaderActionsVariants = cva4("flex gap-4", {
435
591
  variant: "default"
436
592
  }
437
593
  });
438
- var PageHeader = React11.forwardRef(
439
- ({ className, variant, title, subtitle, actions, ...props }, ref) => /* @__PURE__ */ jsxs3(
440
- "header",
441
- {
442
- ref,
443
- className: cn(pageHeaderContainerVariants({ variant }), className),
444
- ...props,
445
- children: [
446
- /* @__PURE__ */ jsx11("h1", { className: pageHeaderTitleVariants({ variant }), children: title }),
447
- subtitle && /* @__PURE__ */ jsx11("p", { className: pageHeaderSubtitleVariants({ variant }), children: subtitle }),
448
- actions && /* @__PURE__ */ jsx11("div", { className: pageHeaderActionsVariants({ variant }), children: actions })
449
- ]
594
+ var PageHeader = React12.forwardRef(
595
+ ({ className, variant, title, subtitle, actions, as: Heading = "h1", ...props }, ref) => {
596
+ if (process.env.NODE_ENV !== "production") {
597
+ if (Heading === "h1") {
598
+ console.warn(
599
+ 'PageHeader: Using as="h1" (the default). Ensure there is only one h1 per page for proper document structure. Consider as="h2" for secondary headers.'
600
+ );
601
+ }
450
602
  }
451
- )
603
+ return /* @__PURE__ */ jsxs4(
604
+ "header",
605
+ {
606
+ ref,
607
+ className: cn(pageHeaderContainerVariants({ variant }), className),
608
+ ...props,
609
+ children: [
610
+ /* @__PURE__ */ jsx11(Heading, { className: pageHeaderTitleVariants({ variant }), children: title }),
611
+ subtitle && /* @__PURE__ */ jsx11("p", { className: pageHeaderSubtitleVariants({ variant }), children: subtitle }),
612
+ actions && /* @__PURE__ */ jsx11("div", { className: pageHeaderActionsVariants({ variant }), children: actions })
613
+ ]
614
+ }
615
+ );
616
+ }
452
617
  );
453
618
  PageHeader.displayName = "PageHeader";
454
619
 
455
620
  // src/components/DataPanel.tsx
456
- import * as React12 from "react";
621
+ import * as React13 from "react";
457
622
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
458
623
  import {
459
624
  DEFAULT_PANEL_STATE,
@@ -479,7 +644,7 @@ import {
479
644
  calculateDetachedPositionFromPinned,
480
645
  getPinnedPullTransform
481
646
  } from "@classic-homes/data-panel-core";
482
- import { Fragment, jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
647
+ import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
483
648
  var DropdownTrigger = DropdownMenuPrimitive.Trigger;
484
649
  var DropdownContent = DropdownMenuPrimitive.Content;
485
650
  var DropdownLabel = DropdownMenuPrimitive.Label;
@@ -494,28 +659,28 @@ function useDataPanel(options = {}) {
494
659
  constraints = {}
495
660
  } = options;
496
661
  const resolvedConstraints = { ...DEFAULT_CONSTRAINTS, ...constraints };
497
- const [persistedState] = React12.useState(() => {
662
+ const [persistedState] = React13.useState(() => {
498
663
  if (persistKey && typeof window !== "undefined") {
499
664
  return loadPanelState(persistKey);
500
665
  }
501
666
  return null;
502
667
  });
503
- const [mode, setMode] = React12.useState(persistedState?.mode ?? initialMode);
504
- const [edge, setEdge] = React12.useState(persistedState?.edge ?? initialEdge);
505
- const [isExpanded, setIsExpanded] = React12.useState(
668
+ const [mode, setMode] = React13.useState(persistedState?.mode ?? initialMode);
669
+ const [edge, setEdge] = React13.useState(persistedState?.edge ?? initialEdge);
670
+ const [isExpanded, setIsExpanded] = React13.useState(
506
671
  persistedState?.isExpanded ?? initialExpanded
507
672
  );
508
- const [detachedPosition, setDetachedPosition] = React12.useState(
673
+ const [detachedPosition, setDetachedPosition] = React13.useState(
509
674
  persistedState?.detachedPosition ?? DEFAULT_PANEL_STATE.detachedPosition
510
675
  );
511
- const [detachedSize, setDetachedSize] = React12.useState(
676
+ const [detachedSize, setDetachedSize] = React13.useState(
512
677
  persistedState?.detachedSize ?? DEFAULT_PANEL_STATE.detachedSize
513
678
  );
514
- const [pinnedSize, setPinnedSize] = React12.useState(
679
+ const [pinnedSize, setPinnedSize] = React13.useState(
515
680
  persistedState?.pinnedSize ?? DEFAULT_PANEL_STATE.pinnedSize
516
681
  );
517
- const saveTimeoutRef = React12.useRef(null);
518
- const debouncedSave = React12.useCallback(() => {
682
+ const saveTimeoutRef = React13.useRef(null);
683
+ const debouncedSave = React13.useCallback(() => {
519
684
  if (!persistKey) return;
520
685
  if (saveTimeoutRef.current) {
521
686
  clearTimeout(saveTimeoutRef.current);
@@ -533,7 +698,7 @@ function useDataPanel(options = {}) {
533
698
  });
534
699
  }, 300);
535
700
  }, [persistKey, mode, edge, isExpanded, detachedPosition, detachedSize, pinnedSize]);
536
- const handleSetMode = React12.useCallback(
701
+ const handleSetMode = React13.useCallback(
537
702
  (newMode) => {
538
703
  setMode(newMode);
539
704
  if (newMode === "detached" && typeof window !== "undefined" && detachedPosition.x === DEFAULT_PANEL_STATE.detachedPosition.x && detachedPosition.y === DEFAULT_PANEL_STATE.detachedPosition.y) {
@@ -544,7 +709,7 @@ function useDataPanel(options = {}) {
544
709
  },
545
710
  [detachedPosition, detachedSize]
546
711
  );
547
- const handleSetDetachedPosition = React12.useCallback(
712
+ const handleSetDetachedPosition = React13.useCallback(
548
713
  (position) => {
549
714
  if (typeof window === "undefined") {
550
715
  setDetachedPosition(position);
@@ -556,13 +721,13 @@ function useDataPanel(options = {}) {
556
721
  },
557
722
  [detachedSize]
558
723
  );
559
- const handleSetDetachedSize = React12.useCallback(
724
+ const handleSetDetachedSize = React13.useCallback(
560
725
  (size) => {
561
726
  setDetachedSize(constrainSize(size, resolvedConstraints));
562
727
  },
563
728
  [resolvedConstraints]
564
729
  );
565
- const handleSetPinnedSize = React12.useCallback(
730
+ const handleSetPinnedSize = React13.useCallback(
566
731
  (size) => {
567
732
  if (typeof window === "undefined") {
568
733
  setPinnedSize(size);
@@ -576,7 +741,7 @@ function useDataPanel(options = {}) {
576
741
  },
577
742
  [edge, resolvedConstraints]
578
743
  );
579
- React12.useEffect(() => {
744
+ React13.useEffect(() => {
580
745
  debouncedSave();
581
746
  }, [debouncedSave]);
582
747
  return {
@@ -609,7 +774,7 @@ var ChevronUpIcon = () => /* @__PURE__ */ jsx12(
609
774
  children: /* @__PURE__ */ jsx12("path", { d: "m18 15-6-6-6 6" })
610
775
  }
611
776
  );
612
- var MoreVerticalIcon = () => /* @__PURE__ */ jsxs4(
777
+ var MoreVerticalIcon = () => /* @__PURE__ */ jsxs5(
613
778
  "svg",
614
779
  {
615
780
  xmlns: "http://www.w3.org/2000/svg",
@@ -628,7 +793,7 @@ var MoreVerticalIcon = () => /* @__PURE__ */ jsxs4(
628
793
  ]
629
794
  }
630
795
  );
631
- var CloseIcon = () => /* @__PURE__ */ jsxs4(
796
+ var CloseIcon = () => /* @__PURE__ */ jsxs5(
632
797
  "svg",
633
798
  {
634
799
  xmlns: "http://www.w3.org/2000/svg",
@@ -652,10 +817,11 @@ var edgeLabels = {
652
817
  top: "Pin to top",
653
818
  bottom: "Pin to bottom"
654
819
  };
655
- var DataPanelHeader = React12.forwardRef(
820
+ var DataPanelHeader = React13.forwardRef(
656
821
  ({
657
822
  title,
658
823
  subtitle,
824
+ titleId,
659
825
  mode,
660
826
  edge,
661
827
  isExpanded,
@@ -670,7 +836,7 @@ var DataPanelHeader = React12.forwardRef(
670
836
  className,
671
837
  draggable = false
672
838
  }, ref) => {
673
- return /* @__PURE__ */ jsxs4(
839
+ return /* @__PURE__ */ jsxs5(
674
840
  "div",
675
841
  {
676
842
  ref,
@@ -681,11 +847,11 @@ var DataPanelHeader = React12.forwardRef(
681
847
  ),
682
848
  "data-panel-header": true,
683
849
  children: [
684
- /* @__PURE__ */ jsx12("div", { className: "flex flex-col min-w-0 flex-1", children: headerContent ? headerContent : /* @__PURE__ */ jsxs4(Fragment, { children: [
685
- title && /* @__PURE__ */ jsx12("h3", { className: "text-sm font-semibold leading-none truncate", children: title }),
850
+ /* @__PURE__ */ jsx12("div", { className: "flex flex-col min-w-0 flex-1", children: headerContent ? headerContent : /* @__PURE__ */ jsxs5(Fragment, { children: [
851
+ title && /* @__PURE__ */ jsx12("h3", { id: titleId, className: "text-sm font-semibold leading-none truncate", children: title }),
686
852
  subtitle && /* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: subtitle })
687
853
  ] }) }),
688
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1 shrink-0", children: [
854
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 shrink-0", children: [
689
855
  headerActions,
690
856
  /* @__PURE__ */ jsx12(
691
857
  "button",
@@ -698,7 +864,7 @@ var DataPanelHeader = React12.forwardRef(
698
864
  children: /* @__PURE__ */ jsx12("span", { className: cn("transition-transform", !isExpanded && "rotate-180"), children: /* @__PURE__ */ jsx12(ChevronUpIcon, {}) })
699
865
  }
700
866
  ),
701
- !disableModeSwitch && /* @__PURE__ */ jsxs4(DropdownMenuPrimitive.Root, { children: [
867
+ !disableModeSwitch && /* @__PURE__ */ jsxs5(DropdownMenuPrimitive.Root, { children: [
702
868
  /* @__PURE__ */ jsx12(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx12(
703
869
  "button",
704
870
  {
@@ -708,7 +874,7 @@ var DataPanelHeader = React12.forwardRef(
708
874
  children: /* @__PURE__ */ jsx12(MoreVerticalIcon, {})
709
875
  }
710
876
  ) }),
711
- /* @__PURE__ */ jsx12(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsxs4(
877
+ /* @__PURE__ */ jsx12(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsxs5(
712
878
  DropdownContent,
713
879
  {
714
880
  className: "z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
@@ -716,7 +882,7 @@ var DataPanelHeader = React12.forwardRef(
716
882
  align: "end",
717
883
  children: [
718
884
  /* @__PURE__ */ jsx12(DropdownLabel, { className: "px-2 py-1.5 text-sm font-semibold", children: "Panel Position" }),
719
- ["left", "right", "top", "bottom"].map((e) => /* @__PURE__ */ jsxs4(
885
+ ["left", "right", "top", "bottom"].map((e) => /* @__PURE__ */ jsxs5(
720
886
  DropdownItem,
721
887
  {
722
888
  className: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
@@ -732,7 +898,7 @@ var DataPanelHeader = React12.forwardRef(
732
898
  e
733
899
  )),
734
900
  /* @__PURE__ */ jsx12(DropdownSeparator, { className: "-mx-1 my-1 h-px bg-muted" }),
735
- /* @__PURE__ */ jsxs4(
901
+ /* @__PURE__ */ jsxs5(
736
902
  DropdownItem,
737
903
  {
738
904
  className: "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground",
@@ -764,13 +930,22 @@ var DataPanelHeader = React12.forwardRef(
764
930
  }
765
931
  );
766
932
  DataPanelHeader.displayName = "DataPanelHeader";
767
- var DataPanelContent = React12.forwardRef(
768
- ({ children, className }, ref) => {
769
- return /* @__PURE__ */ jsx12("div", { ref, className: cn("flex-1 overflow-auto p-4", className), children });
933
+ var DataPanelContent = React13.forwardRef(
934
+ ({ children, className, "aria-label": ariaLabel = "Panel content" }, ref) => {
935
+ return /* @__PURE__ */ jsx12(
936
+ "div",
937
+ {
938
+ ref,
939
+ role: "region",
940
+ "aria-label": ariaLabel,
941
+ className: cn("flex-1 overflow-auto p-4", className),
942
+ children
943
+ }
944
+ );
770
945
  }
771
946
  );
772
947
  DataPanelContent.displayName = "DataPanelContent";
773
- var DataPanelFooter = React12.forwardRef(
948
+ var DataPanelFooter = React13.forwardRef(
774
949
  ({ actions = [], footerContent, footerActions, className }, ref) => {
775
950
  if (!footerContent && actions.length === 0 && !footerActions) {
776
951
  return null;
@@ -787,7 +962,7 @@ var DataPanelFooter = React12.forwardRef(
787
962
  "flex items-center justify-end gap-2 border-t border-border bg-muted/30 px-4 py-3",
788
963
  className
789
964
  ),
790
- children: footerContent ? footerContent : /* @__PURE__ */ jsxs4(Fragment, { children: [
965
+ children: footerContent ? footerContent : /* @__PURE__ */ jsxs5(Fragment, { children: [
791
966
  footerActions,
792
967
  actions.map((action, index) => /* @__PURE__ */ jsx12(
793
968
  Button,
@@ -806,7 +981,7 @@ var DataPanelFooter = React12.forwardRef(
806
981
  }
807
982
  );
808
983
  DataPanelFooter.displayName = "DataPanelFooter";
809
- var DataPanelTab = React12.forwardRef(
984
+ var DataPanelTab = React13.forwardRef(
810
985
  ({ title = "Panel", edge, onClick, className }, ref) => {
811
986
  const positionClasses = {
812
987
  left: "left-0 top-1/2 -translate-y-1/2 rounded-r-md border-l-0",
@@ -821,7 +996,7 @@ var DataPanelTab = React12.forwardRef(
821
996
  top: "rotate-180",
822
997
  bottom: ""
823
998
  }[edge];
824
- return /* @__PURE__ */ jsxs4(
999
+ return /* @__PURE__ */ jsxs5(
825
1000
  "button",
826
1001
  {
827
1002
  ref,
@@ -843,7 +1018,186 @@ var DataPanelTab = React12.forwardRef(
843
1018
  }
844
1019
  );
845
1020
  DataPanelTab.displayName = "DataPanelTab";
846
- var DataPanel = React12.forwardRef(
1021
+ var KEYBOARD_RESIZE_STEP = 10;
1022
+ var KEYBOARD_RESIZE_STEP_LARGE = 50;
1023
+ function ResizeHandles({
1024
+ mode,
1025
+ edge,
1026
+ detachedSize,
1027
+ pinnedSize,
1028
+ onResizeStart,
1029
+ onSizeChange,
1030
+ onPinnedSizeChange,
1031
+ constraints
1032
+ }) {
1033
+ const handleDetachedKeyDown = React13.useCallback(
1034
+ (e, handle) => {
1035
+ const step = e.shiftKey ? KEYBOARD_RESIZE_STEP_LARGE : KEYBOARD_RESIZE_STEP;
1036
+ let newSize = { ...detachedSize };
1037
+ let handled = false;
1038
+ switch (e.key) {
1039
+ case "ArrowUp":
1040
+ if (handle.includes("top")) {
1041
+ newSize.height = Math.max(constraints.minHeight || 200, detachedSize.height + step);
1042
+ handled = true;
1043
+ } else if (handle.includes("bottom")) {
1044
+ newSize.height = Math.max(constraints.minHeight || 200, detachedSize.height - step);
1045
+ handled = true;
1046
+ }
1047
+ break;
1048
+ case "ArrowDown":
1049
+ if (handle.includes("top")) {
1050
+ newSize.height = Math.max(constraints.minHeight || 200, detachedSize.height - step);
1051
+ handled = true;
1052
+ } else if (handle.includes("bottom")) {
1053
+ newSize.height = Math.min(constraints.maxHeight || 800, detachedSize.height + step);
1054
+ handled = true;
1055
+ }
1056
+ break;
1057
+ case "ArrowLeft":
1058
+ if (handle.includes("left")) {
1059
+ newSize.width = Math.max(constraints.minWidth || 280, detachedSize.width + step);
1060
+ handled = true;
1061
+ } else if (handle.includes("right")) {
1062
+ newSize.width = Math.max(constraints.minWidth || 280, detachedSize.width - step);
1063
+ handled = true;
1064
+ }
1065
+ break;
1066
+ case "ArrowRight":
1067
+ if (handle.includes("left")) {
1068
+ newSize.width = Math.max(constraints.minWidth || 280, detachedSize.width - step);
1069
+ handled = true;
1070
+ } else if (handle.includes("right")) {
1071
+ newSize.width = Math.min(constraints.maxWidth || 600, detachedSize.width + step);
1072
+ handled = true;
1073
+ }
1074
+ break;
1075
+ }
1076
+ if (handled) {
1077
+ e.preventDefault();
1078
+ onSizeChange(newSize);
1079
+ }
1080
+ },
1081
+ [detachedSize, constraints, onSizeChange]
1082
+ );
1083
+ const handlePinnedKeyDown = React13.useCallback(
1084
+ (e) => {
1085
+ const step = e.shiftKey ? KEYBOARD_RESIZE_STEP_LARGE : KEYBOARD_RESIZE_STEP;
1086
+ let newSize = pinnedSize;
1087
+ let handled = false;
1088
+ const isHorizontal2 = edge === "left" || edge === "right";
1089
+ if (isHorizontal2) {
1090
+ if (e.key === "ArrowLeft" && edge === "right" || e.key === "ArrowRight" && edge === "left") {
1091
+ newSize = Math.min(constraints.maxWidth || 600, pinnedSize + step);
1092
+ handled = true;
1093
+ } else if (e.key === "ArrowRight" && edge === "right" || e.key === "ArrowLeft" && edge === "left") {
1094
+ newSize = Math.max(constraints.minWidth || 280, pinnedSize - step);
1095
+ handled = true;
1096
+ }
1097
+ } else {
1098
+ if (e.key === "ArrowUp" && edge === "bottom" || e.key === "ArrowDown" && edge === "top") {
1099
+ newSize = Math.min(constraints.maxHeight || 800, pinnedSize + step);
1100
+ handled = true;
1101
+ } else if (e.key === "ArrowDown" && edge === "bottom" || e.key === "ArrowUp" && edge === "top") {
1102
+ newSize = Math.max(constraints.minHeight || 200, pinnedSize - step);
1103
+ handled = true;
1104
+ }
1105
+ }
1106
+ if (handled) {
1107
+ e.preventDefault();
1108
+ onPinnedSizeChange(newSize);
1109
+ }
1110
+ },
1111
+ [edge, pinnedSize, constraints, onPinnedSizeChange]
1112
+ );
1113
+ const getAriaValueNow = (handle) => {
1114
+ if (handle.includes("left") || handle.includes("right")) {
1115
+ return mode === "detached" ? detachedSize.width : pinnedSize;
1116
+ }
1117
+ return mode === "detached" ? detachedSize.height : pinnedSize;
1118
+ };
1119
+ const getAriaValueMinMax = (handle) => {
1120
+ const isWidth = handle.includes("left") || handle.includes("right");
1121
+ if (isWidth) {
1122
+ return {
1123
+ min: constraints.minWidth || 280,
1124
+ max: constraints.maxWidth || 600
1125
+ };
1126
+ }
1127
+ return {
1128
+ min: constraints.minHeight || 200,
1129
+ max: constraints.maxHeight || 800
1130
+ };
1131
+ };
1132
+ if (mode === "detached") {
1133
+ const handles = [
1134
+ "top",
1135
+ "bottom",
1136
+ "left",
1137
+ "right",
1138
+ "top-left",
1139
+ "top-right",
1140
+ "bottom-left",
1141
+ "bottom-right"
1142
+ ];
1143
+ return /* @__PURE__ */ jsx12(Fragment, { children: handles.map((handle) => {
1144
+ const isCorner = handle.includes("-");
1145
+ const isHorizontal2 = handle === "top" || handle === "bottom";
1146
+ const isVertical = handle === "left" || handle === "right";
1147
+ const { min: min2, max: max2 } = getAriaValueMinMax(handle);
1148
+ return /* @__PURE__ */ jsx12(
1149
+ "div",
1150
+ {
1151
+ className: cn(
1152
+ "resize-handle",
1153
+ isHorizontal2 && "resize-handle--horizontal",
1154
+ isVertical && "resize-handle--vertical",
1155
+ isCorner && "resize-handle--corner",
1156
+ `resize-handle--${handle}`
1157
+ ),
1158
+ role: "slider",
1159
+ tabIndex: 0,
1160
+ "aria-orientation": isVertical ? "vertical" : "horizontal",
1161
+ "aria-label": `Resize panel from ${handle.replace("-", " ")} ${isCorner ? "corner" : "edge"}`,
1162
+ "aria-valuenow": getAriaValueNow(handle),
1163
+ "aria-valuemin": min2,
1164
+ "aria-valuemax": max2,
1165
+ "aria-valuetext": `${getAriaValueNow(handle)} pixels`,
1166
+ style: isCorner ? { "--handle-radius": "6px" } : void 0,
1167
+ onPointerDown: (e) => onResizeStart(e, handle),
1168
+ onKeyDown: (e) => handleDetachedKeyDown(e, handle)
1169
+ },
1170
+ handle
1171
+ );
1172
+ }) });
1173
+ }
1174
+ const pinnedHandle = getPinnedResizeHandle(edge);
1175
+ const isHorizontal = edge === "left" || edge === "right";
1176
+ const { min, max } = getAriaValueMinMax(pinnedHandle);
1177
+ return /* @__PURE__ */ jsx12(
1178
+ "div",
1179
+ {
1180
+ className: cn(
1181
+ "resize-handle",
1182
+ edge === "left" && "resize-handle--vertical resize-handle--right",
1183
+ edge === "right" && "resize-handle--vertical resize-handle--left",
1184
+ edge === "top" && "resize-handle--horizontal resize-handle--bottom",
1185
+ edge === "bottom" && "resize-handle--horizontal resize-handle--top"
1186
+ ),
1187
+ role: "slider",
1188
+ tabIndex: 0,
1189
+ "aria-orientation": isHorizontal ? "horizontal" : "vertical",
1190
+ "aria-label": `Resize panel from ${edge} edge`,
1191
+ "aria-valuenow": pinnedSize,
1192
+ "aria-valuemin": min,
1193
+ "aria-valuemax": max,
1194
+ "aria-valuetext": `${pinnedSize} pixels`,
1195
+ onPointerDown: (e) => onResizeStart(e, pinnedHandle),
1196
+ onKeyDown: handlePinnedKeyDown
1197
+ }
1198
+ );
1199
+ }
1200
+ var DataPanel = React13.forwardRef(
847
1201
  ({
848
1202
  open: controlledOpen,
849
1203
  onOpenChange,
@@ -872,31 +1226,33 @@ var DataPanel = React12.forwardRef(
872
1226
  className
873
1227
  }, ref) => {
874
1228
  const resolvedConstraints = { ...DEFAULT_CONSTRAINTS, ...constraints };
875
- const [internalOpen, setInternalOpen] = React12.useState(true);
876
- const [internalMode, setInternalMode] = React12.useState(DEFAULT_PANEL_STATE.mode);
877
- const [internalEdge, setInternalEdge] = React12.useState(DEFAULT_PANEL_STATE.edge);
878
- const [internalExpanded, setInternalExpanded] = React12.useState(
1229
+ const panelId = React13.useId();
1230
+ const titleId = `${panelId}-title`;
1231
+ const [internalOpen, setInternalOpen] = React13.useState(true);
1232
+ const [internalMode, setInternalMode] = React13.useState(DEFAULT_PANEL_STATE.mode);
1233
+ const [internalEdge, setInternalEdge] = React13.useState(DEFAULT_PANEL_STATE.edge);
1234
+ const [internalExpanded, setInternalExpanded] = React13.useState(
879
1235
  DEFAULT_PANEL_STATE.isExpanded
880
1236
  );
881
- const [detachedPosition, setDetachedPosition] = React12.useState(
1237
+ const [detachedPosition, setDetachedPosition] = React13.useState(
882
1238
  DEFAULT_PANEL_STATE.detachedPosition
883
1239
  );
884
- const [detachedSize, setDetachedSize] = React12.useState(DEFAULT_PANEL_STATE.detachedSize);
885
- const [pinnedSize, setPinnedSize] = React12.useState(DEFAULT_PANEL_STATE.pinnedSize);
886
- const [isDragging, setIsDragging] = React12.useState(false);
887
- const [isResizing, setIsResizing] = React12.useState(false);
888
- const [snapPreview, setSnapPreview] = React12.useState(null);
889
- const [isPinnedDragging, setIsPinnedDragging] = React12.useState(false);
890
- const [pullOffset, setPullOffset] = React12.useState(0);
891
- const dragStateRef = React12.useRef({
1240
+ const [detachedSize, setDetachedSize] = React13.useState(DEFAULT_PANEL_STATE.detachedSize);
1241
+ const [pinnedSize, setPinnedSize] = React13.useState(DEFAULT_PANEL_STATE.pinnedSize);
1242
+ const [isDragging, setIsDragging] = React13.useState(false);
1243
+ const [isResizing, setIsResizing] = React13.useState(false);
1244
+ const [snapPreview, setSnapPreview] = React13.useState(null);
1245
+ const [isPinnedDragging, setIsPinnedDragging] = React13.useState(false);
1246
+ const [pullOffset, setPullOffset] = React13.useState(0);
1247
+ const dragStateRef = React13.useRef({
892
1248
  startPosition: { x: 0, y: 0 },
893
1249
  startPanelPosition: { x: 0, y: 0 }
894
1250
  });
895
- const pinnedDragStateRef = React12.useRef({
1251
+ const pinnedDragStateRef = React13.useRef({
896
1252
  startPosition: { x: 0, y: 0 },
897
1253
  hasDetached: false
898
1254
  });
899
- const resizeStateRef = React12.useRef({
1255
+ const resizeStateRef = React13.useRef({
900
1256
  handle: null,
901
1257
  startPosition: { x: 0, y: 0 },
902
1258
  startSize: { width: 0, height: 0 },
@@ -906,8 +1262,8 @@ var DataPanel = React12.forwardRef(
906
1262
  const mode = controlledMode ?? internalMode;
907
1263
  const edge = controlledEdge ?? internalEdge;
908
1264
  const isExpanded = controlledExpanded ?? internalExpanded;
909
- const saveTimeoutRef = React12.useRef(null);
910
- const debouncedSave = React12.useCallback(() => {
1265
+ const saveTimeoutRef = React13.useRef(null);
1266
+ const debouncedSave = React13.useCallback(() => {
911
1267
  if (!persistKey) return;
912
1268
  if (saveTimeoutRef.current) {
913
1269
  clearTimeout(saveTimeoutRef.current);
@@ -933,7 +1289,7 @@ var DataPanel = React12.forwardRef(
933
1289
  detachedSize,
934
1290
  pinnedSize
935
1291
  ]);
936
- React12.useEffect(() => {
1292
+ React13.useEffect(() => {
937
1293
  if (!persistKey) return;
938
1294
  const persisted = loadPanelState(persistKey);
939
1295
  if (persisted) {
@@ -946,7 +1302,7 @@ var DataPanel = React12.forwardRef(
946
1302
  if (persisted.pinnedSize) setPinnedSize(persisted.pinnedSize);
947
1303
  }
948
1304
  }, [persistKey, controlledMode, controlledEdge, controlledExpanded]);
949
- React12.useEffect(() => {
1305
+ React13.useEffect(() => {
950
1306
  if (mode === "detached" && detachedPosition.x === DEFAULT_PANEL_STATE.detachedPosition.x && detachedPosition.y === DEFAULT_PANEL_STATE.detachedPosition.y) {
951
1307
  setDetachedPosition(
952
1308
  getInitialDetachedPosition(detachedSize, window.innerWidth, window.innerHeight)
@@ -1004,7 +1360,7 @@ var DataPanel = React12.forwardRef(
1004
1360
  document.body.style.userSelect = "none";
1005
1361
  }
1006
1362
  };
1007
- const handleDragMove = React12.useCallback(
1363
+ const handleDragMove = React13.useCallback(
1008
1364
  (e) => {
1009
1365
  if (!isDragging) return;
1010
1366
  const currentPos = getPointerPosition(e);
@@ -1031,7 +1387,7 @@ var DataPanel = React12.forwardRef(
1031
1387
  },
1032
1388
  [isDragging, detachedSize, snapThreshold]
1033
1389
  );
1034
- const handleDragEnd = React12.useCallback(() => {
1390
+ const handleDragEnd = React13.useCallback(() => {
1035
1391
  if (!isDragging) return;
1036
1392
  setIsDragging(false);
1037
1393
  document.body.style.cursor = "";
@@ -1044,7 +1400,7 @@ var DataPanel = React12.forwardRef(
1044
1400
  debouncedSave();
1045
1401
  }
1046
1402
  }, [isDragging, snapPreview, handleModeChange, handleEdgeChange, debouncedSave]);
1047
- const handlePinnedDragMove = React12.useCallback(
1403
+ const handlePinnedDragMove = React13.useCallback(
1048
1404
  (e) => {
1049
1405
  if (!isPinnedDragging) return;
1050
1406
  const currentPos = getPointerPosition(e);
@@ -1080,7 +1436,7 @@ var DataPanel = React12.forwardRef(
1080
1436
  },
1081
1437
  [isPinnedDragging, edge, detachThreshold, pinnedSize, detachedSize, handleModeChange]
1082
1438
  );
1083
- const handlePinnedDragEnd = React12.useCallback(() => {
1439
+ const handlePinnedDragEnd = React13.useCallback(() => {
1084
1440
  if (!isPinnedDragging) return;
1085
1441
  setIsPinnedDragging(false);
1086
1442
  setPullOffset(0);
@@ -1102,7 +1458,7 @@ var DataPanel = React12.forwardRef(
1102
1458
  document.body.style.cursor = getResizeCursor(handle);
1103
1459
  document.body.style.userSelect = "none";
1104
1460
  };
1105
- const handleResizeMove = React12.useCallback(
1461
+ const handleResizeMove = React13.useCallback(
1106
1462
  (e) => {
1107
1463
  if (!isResizing || !resizeStateRef.current.handle) return;
1108
1464
  const currentPos = getPointerPosition(e);
@@ -1140,7 +1496,7 @@ var DataPanel = React12.forwardRef(
1140
1496
  },
1141
1497
  [isResizing, mode, edge, resolvedConstraints]
1142
1498
  );
1143
- const handleResizeEnd = React12.useCallback(() => {
1499
+ const handleResizeEnd = React13.useCallback(() => {
1144
1500
  if (!isResizing) return;
1145
1501
  setIsResizing(false);
1146
1502
  resizeStateRef.current.handle = null;
@@ -1148,7 +1504,7 @@ var DataPanel = React12.forwardRef(
1148
1504
  document.body.style.userSelect = "";
1149
1505
  debouncedSave();
1150
1506
  }, [isResizing, debouncedSave]);
1151
- React12.useEffect(() => {
1507
+ React13.useEffect(() => {
1152
1508
  if (isDragging) {
1153
1509
  window.addEventListener("pointermove", handleDragMove);
1154
1510
  window.addEventListener("pointerup", handleDragEnd);
@@ -1158,7 +1514,7 @@ var DataPanel = React12.forwardRef(
1158
1514
  };
1159
1515
  }
1160
1516
  }, [isDragging, handleDragMove, handleDragEnd]);
1161
- React12.useEffect(() => {
1517
+ React13.useEffect(() => {
1162
1518
  if (isPinnedDragging) {
1163
1519
  window.addEventListener("pointermove", handlePinnedDragMove);
1164
1520
  window.addEventListener("pointerup", handlePinnedDragEnd);
@@ -1168,7 +1524,7 @@ var DataPanel = React12.forwardRef(
1168
1524
  };
1169
1525
  }
1170
1526
  }, [isPinnedDragging, handlePinnedDragMove, handlePinnedDragEnd]);
1171
- React12.useEffect(() => {
1527
+ React13.useEffect(() => {
1172
1528
  if (isResizing) {
1173
1529
  window.addEventListener("pointermove", handleResizeMove);
1174
1530
  window.addEventListener("pointerup", handleResizeEnd);
@@ -1178,7 +1534,7 @@ var DataPanel = React12.forwardRef(
1178
1534
  };
1179
1535
  }
1180
1536
  }, [isResizing, handleResizeMove, handleResizeEnd]);
1181
- const panelStyles = React12.useMemo(() => {
1537
+ const panelStyles = React13.useMemo(() => {
1182
1538
  if (mode === "pinned") {
1183
1539
  const baseStyles = getPinnedPositionStyles(edge, pinnedSize);
1184
1540
  if (pullOffset > 0) {
@@ -1205,7 +1561,7 @@ var DataPanel = React12.forwardRef(
1205
1561
  if (!isExpanded && mode === "pinned") {
1206
1562
  return /* @__PURE__ */ jsx12(DataPanelTab, { title, edge, onClick: () => handleExpandedChange(true) });
1207
1563
  }
1208
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
1564
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1209
1565
  snapPreview && /* @__PURE__ */ jsx12(
1210
1566
  "div",
1211
1567
  {
@@ -1218,7 +1574,7 @@ var DataPanel = React12.forwardRef(
1218
1574
  )
1219
1575
  }
1220
1576
  ),
1221
- /* @__PURE__ */ jsxs4(
1577
+ /* @__PURE__ */ jsxs5(
1222
1578
  "div",
1223
1579
  {
1224
1580
  ref,
@@ -1231,9 +1587,10 @@ var DataPanel = React12.forwardRef(
1231
1587
  className
1232
1588
  ),
1233
1589
  style: panelStyles,
1234
- role: mode === "detached" ? "dialog" : void 0,
1590
+ role: mode === "detached" ? "dialog" : "complementary",
1235
1591
  "aria-modal": mode === "detached" ? "true" : void 0,
1236
- "aria-labelledby": title ? "data-panel-title" : void 0,
1592
+ "aria-labelledby": title ? titleId : void 0,
1593
+ "aria-label": !title ? "Side panel" : void 0,
1237
1594
  onPointerDown: handleDragStart,
1238
1595
  children: [
1239
1596
  /* @__PURE__ */ jsx12(
@@ -1241,6 +1598,7 @@ var DataPanel = React12.forwardRef(
1241
1598
  {
1242
1599
  title,
1243
1600
  subtitle,
1601
+ titleId,
1244
1602
  mode,
1245
1603
  edge,
1246
1604
  isExpanded,
@@ -1264,80 +1622,31 @@ var DataPanel = React12.forwardRef(
1264
1622
  footerActions
1265
1623
  }
1266
1624
  ),
1267
- !disableResize && /* @__PURE__ */ jsx12(Fragment, { children: mode === "detached" ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1268
- /* @__PURE__ */ jsx12(
1269
- "div",
1270
- {
1271
- className: "resize-handle resize-handle--horizontal resize-handle--top",
1272
- onPointerDown: (e) => handleResizeStart(e, "top")
1273
- }
1274
- ),
1275
- /* @__PURE__ */ jsx12(
1276
- "div",
1277
- {
1278
- className: "resize-handle resize-handle--horizontal resize-handle--bottom",
1279
- onPointerDown: (e) => handleResizeStart(e, "bottom")
1280
- }
1281
- ),
1282
- /* @__PURE__ */ jsx12(
1283
- "div",
1284
- {
1285
- className: "resize-handle resize-handle--vertical resize-handle--left",
1286
- onPointerDown: (e) => handleResizeStart(e, "left")
1287
- }
1288
- ),
1289
- /* @__PURE__ */ jsx12(
1290
- "div",
1291
- {
1292
- className: "resize-handle resize-handle--vertical resize-handle--right",
1293
- onPointerDown: (e) => handleResizeStart(e, "right")
1294
- }
1295
- ),
1296
- /* @__PURE__ */ jsx12(
1297
- "div",
1298
- {
1299
- className: "resize-handle resize-handle--corner resize-handle--top-left",
1300
- style: { "--handle-radius": "6px" },
1301
- onPointerDown: (e) => handleResizeStart(e, "top-left")
1302
- }
1303
- ),
1304
- /* @__PURE__ */ jsx12(
1305
- "div",
1306
- {
1307
- className: "resize-handle resize-handle--corner resize-handle--top-right",
1308
- style: { "--handle-radius": "6px" },
1309
- onPointerDown: (e) => handleResizeStart(e, "top-right")
1310
- }
1311
- ),
1312
- /* @__PURE__ */ jsx12(
1313
- "div",
1314
- {
1315
- className: "resize-handle resize-handle--corner resize-handle--bottom-left",
1316
- style: { "--handle-radius": "6px" },
1317
- onPointerDown: (e) => handleResizeStart(e, "bottom-left")
1318
- }
1319
- ),
1320
- /* @__PURE__ */ jsx12(
1321
- "div",
1322
- {
1323
- className: "resize-handle resize-handle--corner resize-handle--bottom-right",
1324
- style: { "--handle-radius": "6px" },
1325
- onPointerDown: (e) => handleResizeStart(e, "bottom-right")
1326
- }
1327
- )
1328
- ] }) : /* @__PURE__ */ jsx12(
1329
- "div",
1625
+ !disableResize && /* @__PURE__ */ jsx12(
1626
+ ResizeHandles,
1330
1627
  {
1331
- className: cn(
1332
- "resize-handle",
1333
- edge === "left" && "resize-handle--vertical resize-handle--right",
1334
- edge === "right" && "resize-handle--vertical resize-handle--left",
1335
- edge === "top" && "resize-handle--horizontal resize-handle--bottom",
1336
- edge === "bottom" && "resize-handle--horizontal resize-handle--top"
1337
- ),
1338
- onPointerDown: (e) => handleResizeStart(e, getPinnedResizeHandle(edge))
1628
+ mode,
1629
+ edge,
1630
+ detachedSize,
1631
+ pinnedSize,
1632
+ onResizeStart: handleResizeStart,
1633
+ onSizeChange: (size) => setDetachedSize(constrainSize(size, resolvedConstraints)),
1634
+ onPinnedSizeChange: (size) => {
1635
+ if (typeof window === "undefined") {
1636
+ setPinnedSize(size);
1637
+ return;
1638
+ }
1639
+ if (isHorizontalEdge(edge)) {
1640
+ setPinnedSize(constrainPinnedWidth(size, resolvedConstraints, window.innerWidth));
1641
+ } else {
1642
+ setPinnedSize(
1643
+ constrainPinnedHeight(size, resolvedConstraints, window.innerHeight)
1644
+ );
1645
+ }
1646
+ },
1647
+ constraints: resolvedConstraints
1339
1648
  }
1340
- ) })
1649
+ )
1341
1650
  ]
1342
1651
  }
1343
1652
  )
@@ -1345,6 +1654,891 @@ var DataPanel = React12.forwardRef(
1345
1654
  }
1346
1655
  );
1347
1656
  DataPanel.displayName = "DataPanel";
1657
+
1658
+ // src/components/Skeleton.tsx
1659
+ import * as React14 from "react";
1660
+ import { cva as cva5 } from "class-variance-authority";
1661
+ import { jsx as jsx13 } from "react/jsx-runtime";
1662
+ var skeletonVariants = cva5("rounded-md bg-muted", {
1663
+ variants: {
1664
+ variant: {
1665
+ default: "",
1666
+ text: "h-4 w-full",
1667
+ avatar: "h-12 w-12 rounded-full",
1668
+ card: "h-32 w-full",
1669
+ button: "h-10 w-24",
1670
+ title: "h-6 w-3/4"
1671
+ }
1672
+ },
1673
+ defaultVariants: {
1674
+ variant: "default"
1675
+ }
1676
+ });
1677
+ var Skeleton = React14.forwardRef(
1678
+ ({ className, variant, animation = "pulse", ...props }, ref) => {
1679
+ const animationClass = animation === "pulse" ? "animate-pulse" : animation === "shimmer" ? "animate-shimmer" : "";
1680
+ return /* @__PURE__ */ jsx13(
1681
+ "div",
1682
+ {
1683
+ ref,
1684
+ className: cn(skeletonVariants({ variant }), animationClass, className),
1685
+ ...props
1686
+ }
1687
+ );
1688
+ }
1689
+ );
1690
+ Skeleton.displayName = "Skeleton";
1691
+
1692
+ // src/components/Spinner.tsx
1693
+ import * as React15 from "react";
1694
+ import { cva as cva6 } from "class-variance-authority";
1695
+ import { jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
1696
+ var spinnerVariants = cva6("animate-spin text-current", {
1697
+ variants: {
1698
+ size: {
1699
+ sm: "h-4 w-4",
1700
+ md: "h-6 w-6",
1701
+ lg: "h-8 w-8",
1702
+ xl: "h-12 w-12"
1703
+ }
1704
+ },
1705
+ defaultVariants: {
1706
+ size: "md"
1707
+ }
1708
+ });
1709
+ var Spinner = React15.forwardRef(
1710
+ ({ className, size, label = "Loading...", ...props }, ref) => {
1711
+ return /* @__PURE__ */ jsxs6(
1712
+ "svg",
1713
+ {
1714
+ ref,
1715
+ className: cn(spinnerVariants({ size }), className),
1716
+ xmlns: "http://www.w3.org/2000/svg",
1717
+ fill: "none",
1718
+ viewBox: "0 0 24 24",
1719
+ "aria-label": label,
1720
+ role: "status",
1721
+ ...props,
1722
+ children: [
1723
+ /* @__PURE__ */ jsx14(
1724
+ "circle",
1725
+ {
1726
+ className: "opacity-25",
1727
+ cx: "12",
1728
+ cy: "12",
1729
+ r: "10",
1730
+ stroke: "currentColor",
1731
+ strokeWidth: "4"
1732
+ }
1733
+ ),
1734
+ /* @__PURE__ */ jsx14(
1735
+ "path",
1736
+ {
1737
+ className: "opacity-75",
1738
+ fill: "currentColor",
1739
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
1740
+ }
1741
+ ),
1742
+ /* @__PURE__ */ jsx14("title", { children: label })
1743
+ ]
1744
+ }
1745
+ );
1746
+ }
1747
+ );
1748
+ Spinner.displayName = "Spinner";
1749
+
1750
+ // src/components/LoadingLogo.tsx
1751
+ import * as React16 from "react";
1752
+ import { jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
1753
+ var logoPaths = [
1754
+ "M9.902 16.6c.3.7 1.7.8 1-.6-.6-1.4-2.3-5.1-6.1-6.3-1.7-.5-3.7.4-4.3 1.6-.4.7-1.1 2.9.5 4.3.8.7 1.9 1.2 3.6 1 .4-.1.9-.5.6-.8-.5-.4-1.9-1.7-1.1-2.7.7-.8 1.4-.6 2-.3.4.2 2.1 1 3.7 3.6l.1.2z",
1755
+ "M14.602 16.6c-.3.7-1.7.8-1-.6.6-1.4 2.3-5.1 6.1-6.3 1.7-.5 3.7.4 4.3 1.6.4.7 1.1 2.9-.5 4.3-.8.7-1.9 1.2-3.6 1-.4-.1-.9-.5-.6-.8.5-.4 1.9-1.7 1.1-2.7-.7-.8-1.4-.6-2-.3-.4.2-2.1 1-3.7 3.6l-.1.2z",
1756
+ "M12.202 0c.7 1.3 2.1 3.2 2.3 5.3.1 1.5-.8 8.3-2.3 11.7-1.5-3.5-2.4-10.3-2.3-11.7.2-2.1 1.6-4 2.3-5.3z",
1757
+ "M9.002 18.1c-.8 0-.7.8-.1.8h6.7c.7 0 .7-.8-.1-.8h-6.5z",
1758
+ "M13.802 24.8c-1.1-1.1-.9-4.6-.9-4.6h-1.3s.2 3.6-.9 4.6h3.1z",
1759
+ "M14.202 22.5c-.1-.3-.5-1.4-.6-2.3 0-.2.6-.5.7.1.1.6.4 1.6.6 1.9.3.5-.4.9-.7.3z",
1760
+ "M10.302 22.5c.1-.3.5-1.4.6-2.2 0-.3-.5-.6-.7 0-.1.6-.4 1.6-.6 1.9-.3.5.4.9.7.3z"
1761
+ ];
1762
+ var colors = {
1763
+ dark: {
1764
+ fill: "#C50F22",
1765
+ fillLoading: "#ffffff",
1766
+ stroke: "#C50F22"
1767
+ },
1768
+ light: {
1769
+ fill: "#ffffff",
1770
+ fillLoading: "#C50F22",
1771
+ stroke: "#ffffff"
1772
+ }
1773
+ };
1774
+ var LoadingLogo = React16.forwardRef(
1775
+ ({ width = 40, height = 40, loading = false, variant = "dark", className, ...props }, ref) => {
1776
+ const currentColors = colors[variant];
1777
+ const fillColor = loading ? currentColors.fillLoading : currentColors.fill;
1778
+ const strokeColor = currentColors.stroke;
1779
+ return /* @__PURE__ */ jsxs7(
1780
+ "div",
1781
+ {
1782
+ ref,
1783
+ className: cn("inline-flex items-center justify-center", className),
1784
+ style: { width: `${width}px`, height: `${height}px` },
1785
+ role: loading ? "status" : "img",
1786
+ "aria-label": loading ? "Loading" : "Classic Homes",
1787
+ ...props,
1788
+ children: [
1789
+ /* @__PURE__ */ jsxs7(
1790
+ "svg",
1791
+ {
1792
+ width: "100%",
1793
+ height: "100%",
1794
+ viewBox: "-1 0 26 25",
1795
+ fill: "none",
1796
+ xmlns: "http://www.w3.org/2000/svg",
1797
+ "aria-hidden": "true",
1798
+ children: [
1799
+ /* @__PURE__ */ jsx15("g", { fill: fillColor, className: "transition-all duration-300", children: logoPaths.map((path, i) => /* @__PURE__ */ jsx15("path", { d: path }, i)) }),
1800
+ loading && /* @__PURE__ */ jsx15(
1801
+ "g",
1802
+ {
1803
+ fill: "none",
1804
+ stroke: strokeColor,
1805
+ strokeWidth: "0.5",
1806
+ strokeDasharray: "10 4",
1807
+ className: "animate-trace",
1808
+ children: logoPaths.map((path, i) => /* @__PURE__ */ jsx15("path", { d: path }, i))
1809
+ }
1810
+ )
1811
+ ]
1812
+ }
1813
+ ),
1814
+ loading && /* @__PURE__ */ jsx15("span", { className: "sr-only", children: "Loading" })
1815
+ ]
1816
+ }
1817
+ );
1818
+ }
1819
+ );
1820
+ LoadingLogo.displayName = "LoadingLogo";
1821
+
1822
+ // src/components/ProgressBar.tsx
1823
+ import * as React17 from "react";
1824
+ import { cva as cva7 } from "class-variance-authority";
1825
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1826
+ var progressBarVariants = cva7("w-full overflow-hidden rounded-full bg-muted", {
1827
+ variants: {
1828
+ size: {
1829
+ sm: "h-1",
1830
+ md: "h-2",
1831
+ lg: "h-3"
1832
+ }
1833
+ },
1834
+ defaultVariants: {
1835
+ size: "md"
1836
+ }
1837
+ });
1838
+ var progressFillVariants = cva7("h-full rounded-full transition-all duration-300", {
1839
+ variants: {
1840
+ variant: {
1841
+ default: "bg-primary",
1842
+ success: "bg-success",
1843
+ warning: "bg-warning",
1844
+ destructive: "bg-destructive"
1845
+ }
1846
+ },
1847
+ defaultVariants: {
1848
+ variant: "default"
1849
+ }
1850
+ });
1851
+ var ProgressBar = React17.forwardRef(
1852
+ ({ className, size, variant, value, max = 100, label = "Progress", showValue = false, ...props }, ref) => {
1853
+ const isIndeterminate = value === void 0;
1854
+ const clampedValue = isIndeterminate ? 0 : Math.min(Math.max(0, value), max);
1855
+ const percentage = isIndeterminate ? 0 : Math.round(clampedValue / max * 100);
1856
+ return /* @__PURE__ */ jsxs8(
1857
+ "div",
1858
+ {
1859
+ ref,
1860
+ role: "progressbar",
1861
+ "aria-label": label,
1862
+ "aria-valuenow": isIndeterminate ? void 0 : clampedValue,
1863
+ "aria-valuemin": 0,
1864
+ "aria-valuemax": max,
1865
+ className: cn("flex items-center gap-2", className),
1866
+ ...props,
1867
+ children: [
1868
+ /* @__PURE__ */ jsx16("div", { className: cn(progressBarVariants({ size })), children: /* @__PURE__ */ jsx16(
1869
+ "div",
1870
+ {
1871
+ className: cn(
1872
+ progressFillVariants({ variant }),
1873
+ isIndeterminate && "w-1/4 animate-progress-indeterminate"
1874
+ ),
1875
+ style: isIndeterminate ? void 0 : { width: `${percentage}%` }
1876
+ }
1877
+ ) }),
1878
+ showValue && !isIndeterminate && /* @__PURE__ */ jsxs8("span", { className: "text-sm tabular-nums text-muted-foreground", children: [
1879
+ percentage,
1880
+ "%"
1881
+ ] })
1882
+ ]
1883
+ }
1884
+ );
1885
+ }
1886
+ );
1887
+ ProgressBar.displayName = "ProgressBar";
1888
+
1889
+ // src/components/LoadingOverlay.tsx
1890
+ import * as React18 from "react";
1891
+ import { Fragment as Fragment2, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1892
+ var spinnerSizeMap = { sm: "sm", md: "md", lg: "lg", xl: "xl" };
1893
+ var logoSizeMap = { sm: 32, md: 40, lg: 56, xl: 80 };
1894
+ var LoadingOverlay = React18.forwardRef(
1895
+ ({
1896
+ loading = true,
1897
+ variant = "spinner",
1898
+ mode = "overlay",
1899
+ size = "md",
1900
+ message,
1901
+ progress,
1902
+ blur = true,
1903
+ opaque = false,
1904
+ delay = 0,
1905
+ className,
1906
+ children,
1907
+ ...props
1908
+ }, ref) => {
1909
+ const [visible, setVisible] = React18.useState(delay === 0 && loading);
1910
+ React18.useEffect(() => {
1911
+ if (!loading) {
1912
+ setVisible(false);
1913
+ return;
1914
+ }
1915
+ if (delay === 0) {
1916
+ setVisible(true);
1917
+ return;
1918
+ }
1919
+ const timer = setTimeout(() => setVisible(true), delay);
1920
+ return () => clearTimeout(timer);
1921
+ }, [loading, delay]);
1922
+ if (!visible) {
1923
+ return mode === "inline" ? null : /* @__PURE__ */ jsx17(Fragment2, { children });
1924
+ }
1925
+ const indicator = /* @__PURE__ */ jsxs9(Fragment2, { children: [
1926
+ variant === "spinner" && /* @__PURE__ */ jsx17(Spinner, { size: spinnerSizeMap[size] }),
1927
+ variant === "logo" && /* @__PURE__ */ jsx17(LoadingLogo, { loading: true, width: logoSizeMap[size], height: logoSizeMap[size] }),
1928
+ variant === "progress" && /* @__PURE__ */ jsx17(ProgressBar, { value: progress, className: "w-48 max-w-full" }),
1929
+ variant === "skeleton" && /* @__PURE__ */ jsxs9("div", { className: "w-full space-y-3", children: [
1930
+ /* @__PURE__ */ jsx17(Skeleton, { variant: "title" }),
1931
+ /* @__PURE__ */ jsx17(Skeleton, { variant: "text" }),
1932
+ /* @__PURE__ */ jsx17(Skeleton, { variant: "text" }),
1933
+ /* @__PURE__ */ jsx17(Skeleton, { variant: "text", className: "w-2/3" })
1934
+ ] }),
1935
+ message && /* @__PURE__ */ jsx17("p", { className: "mt-2 text-sm text-muted-foreground animate-pulse-subtle", children: message }),
1936
+ !message && /* @__PURE__ */ jsx17("span", { className: "sr-only", children: "Loading" })
1937
+ ] });
1938
+ if (mode === "inline") {
1939
+ return /* @__PURE__ */ jsx17(
1940
+ "div",
1941
+ {
1942
+ ref,
1943
+ role: "status",
1944
+ "aria-live": "polite",
1945
+ className: cn("inline-flex items-center gap-2", className),
1946
+ ...props,
1947
+ children: indicator
1948
+ }
1949
+ );
1950
+ }
1951
+ const bgClass = opaque ? "bg-background" : mode === "fullscreen" ? "bg-background/80" : "bg-background/60";
1952
+ const positionClass = mode === "fullscreen" ? "fixed inset-0 z-50" : "absolute inset-0 z-10";
1953
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1954
+ children,
1955
+ /* @__PURE__ */ jsx17(
1956
+ "div",
1957
+ {
1958
+ ref,
1959
+ role: "status",
1960
+ "aria-live": "polite",
1961
+ className: cn(
1962
+ positionClass,
1963
+ bgClass,
1964
+ blur && "backdrop-blur-sm",
1965
+ "flex flex-col items-center justify-center",
1966
+ className
1967
+ ),
1968
+ ...props,
1969
+ children: indicator
1970
+ }
1971
+ )
1972
+ ] });
1973
+ }
1974
+ );
1975
+ LoadingOverlay.displayName = "LoadingOverlay";
1976
+
1977
+ // src/components/LoadingPage.tsx
1978
+ import * as React19 from "react";
1979
+ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
1980
+ var LoadingPage = React19.forwardRef(
1981
+ ({
1982
+ loading = true,
1983
+ message,
1984
+ progress,
1985
+ showLogo = true,
1986
+ logoVariant = "dark",
1987
+ delay = 200,
1988
+ className,
1989
+ ...props
1990
+ }, ref) => {
1991
+ const [visible, setVisible] = React19.useState(delay === 0 && loading);
1992
+ React19.useEffect(() => {
1993
+ if (!loading) {
1994
+ setVisible(false);
1995
+ return;
1996
+ }
1997
+ if (delay === 0) {
1998
+ setVisible(true);
1999
+ return;
2000
+ }
2001
+ const timer = setTimeout(() => setVisible(true), delay);
2002
+ return () => clearTimeout(timer);
2003
+ }, [loading, delay]);
2004
+ if (!visible) return null;
2005
+ return /* @__PURE__ */ jsxs10(
2006
+ "div",
2007
+ {
2008
+ ref,
2009
+ role: "status",
2010
+ "aria-live": "polite",
2011
+ className: cn(
2012
+ "fixed inset-0 z-50 flex flex-col items-center justify-center bg-background",
2013
+ className
2014
+ ),
2015
+ ...props,
2016
+ children: [
2017
+ showLogo ? /* @__PURE__ */ jsx18(LoadingLogo, { loading: true, width: 64, height: 64, variant: logoVariant }) : /* @__PURE__ */ jsx18(Spinner, { size: "xl" }),
2018
+ progress !== void 0 && /* @__PURE__ */ jsx18(ProgressBar, { value: progress, className: "mt-6 w-48 max-w-full" }),
2019
+ message && /* @__PURE__ */ jsx18("p", { className: "mt-4 text-sm text-muted-foreground animate-pulse-subtle", children: message }),
2020
+ /* @__PURE__ */ jsxs10("span", { className: "sr-only", children: [
2021
+ message || "Loading",
2022
+ progress !== void 0 && `, ${Math.round(progress)}% complete`
2023
+ ] })
2024
+ ]
2025
+ }
2026
+ );
2027
+ }
2028
+ );
2029
+ LoadingPage.displayName = "LoadingPage";
2030
+
2031
+ // src/components/LoadingSection.tsx
2032
+ import * as React20 from "react";
2033
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
2034
+ var LoadingSection = React20.forwardRef(
2035
+ ({
2036
+ loading = false,
2037
+ variant = "spinner",
2038
+ message,
2039
+ progress,
2040
+ minHeight = "12rem",
2041
+ blur = true,
2042
+ delay = 0,
2043
+ className,
2044
+ children,
2045
+ ...props
2046
+ }, ref) => {
2047
+ return /* @__PURE__ */ jsxs11(
2048
+ "div",
2049
+ {
2050
+ ref,
2051
+ className: cn("relative", className),
2052
+ style: { minHeight },
2053
+ "aria-busy": loading,
2054
+ ...props,
2055
+ children: [
2056
+ children,
2057
+ loading && /* @__PURE__ */ jsx19(
2058
+ LoadingOverlay,
2059
+ {
2060
+ mode: "overlay",
2061
+ variant,
2062
+ message,
2063
+ progress,
2064
+ blur,
2065
+ delay
2066
+ }
2067
+ )
2068
+ ]
2069
+ }
2070
+ );
2071
+ }
2072
+ );
2073
+ LoadingSection.displayName = "LoadingSection";
2074
+
2075
+ // src/components/LiveRegion.tsx
2076
+ import * as React21 from "react";
2077
+ import { jsx as jsx20 } from "react/jsx-runtime";
2078
+ var LiveRegion = React21.forwardRef(
2079
+ ({ message, politeness = "polite", atomic = true, visuallyHidden = true, className, children }, ref) => {
2080
+ return /* @__PURE__ */ jsx20(
2081
+ "div",
2082
+ {
2083
+ ref,
2084
+ role: "status",
2085
+ "aria-live": politeness,
2086
+ "aria-atomic": atomic,
2087
+ className: cn(
2088
+ visuallyHidden && "sr-only absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0",
2089
+ className
2090
+ ),
2091
+ children: message || children
2092
+ }
2093
+ );
2094
+ }
2095
+ );
2096
+ LiveRegion.displayName = "LiveRegion";
2097
+
2098
+ // src/components/SkipLink.tsx
2099
+ import * as React22 from "react";
2100
+ import { jsx as jsx21 } from "react/jsx-runtime";
2101
+ var SkipLink = React22.forwardRef(
2102
+ ({ targetId, className, children = "Skip to main content", ...props }, ref) => {
2103
+ const handleClick = (e) => {
2104
+ e.preventDefault();
2105
+ const target = document.getElementById(targetId);
2106
+ if (target) {
2107
+ if (target.tabIndex === -1) {
2108
+ target.setAttribute("tabindex", "-1");
2109
+ }
2110
+ target.focus();
2111
+ target.scrollIntoView();
2112
+ } else if (process.env.NODE_ENV !== "production") {
2113
+ console.warn(
2114
+ `SkipLink: Target element with id="${targetId}" not found. Ensure the target element exists and has the correct id attribute.`
2115
+ );
2116
+ }
2117
+ props.onClick?.(e);
2118
+ };
2119
+ return /* @__PURE__ */ jsx21(
2120
+ "a",
2121
+ {
2122
+ ref,
2123
+ href: `#${targetId}`,
2124
+ className: cn("skip-link", className),
2125
+ onClick: handleClick,
2126
+ ...props,
2127
+ children
2128
+ }
2129
+ );
2130
+ }
2131
+ );
2132
+ SkipLink.displayName = "SkipLink";
2133
+
2134
+ // src/hooks/useFocusTrap.ts
2135
+ import { useEffect as useEffect5, useRef as useRef3, useCallback as useCallback3 } from "react";
2136
+ var FOCUSABLE_SELECTOR = [
2137
+ "a[href]",
2138
+ "area[href]",
2139
+ 'input:not([disabled]):not([type="hidden"])',
2140
+ "select:not([disabled])",
2141
+ "textarea:not([disabled])",
2142
+ "button:not([disabled])",
2143
+ "iframe",
2144
+ "object",
2145
+ "embed",
2146
+ "[contenteditable]",
2147
+ '[tabindex]:not([tabindex="-1"])'
2148
+ ].join(",");
2149
+ function getFocusableElements(container) {
2150
+ const elements = container.querySelectorAll(FOCUSABLE_SELECTOR);
2151
+ return Array.from(elements).filter(
2152
+ (el) => el.offsetParent !== null && getComputedStyle(el).visibility !== "hidden"
2153
+ );
2154
+ }
2155
+ function useFocusTrap(options = {}) {
2156
+ const { active = true, returnFocusTo, initialFocus, preventScroll = false } = options;
2157
+ const containerRef = useRef3(null);
2158
+ const previousActiveElementRef = useRef3(null);
2159
+ const handleKeyDown = useCallback3(
2160
+ (event) => {
2161
+ if (!active || event.key !== "Tab") return;
2162
+ const container = containerRef.current;
2163
+ if (!container) return;
2164
+ const focusableElements = getFocusableElements(container);
2165
+ if (focusableElements.length === 0) return;
2166
+ const firstElement = focusableElements[0];
2167
+ const lastElement = focusableElements[focusableElements.length - 1];
2168
+ if (event.shiftKey) {
2169
+ if (document.activeElement === firstElement) {
2170
+ event.preventDefault();
2171
+ lastElement.focus({ preventScroll });
2172
+ }
2173
+ } else {
2174
+ if (document.activeElement === lastElement) {
2175
+ event.preventDefault();
2176
+ firstElement.focus({ preventScroll });
2177
+ }
2178
+ }
2179
+ },
2180
+ [active, preventScroll]
2181
+ );
2182
+ useEffect5(() => {
2183
+ if (!active) return;
2184
+ const container = containerRef.current;
2185
+ if (!container) return;
2186
+ previousActiveElementRef.current = document.activeElement;
2187
+ const focusableElements = getFocusableElements(container);
2188
+ if (focusableElements.length > 0) {
2189
+ let elementToFocus = null;
2190
+ if (initialFocus) {
2191
+ elementToFocus = container.querySelector(initialFocus);
2192
+ }
2193
+ if (!elementToFocus) {
2194
+ elementToFocus = focusableElements[0];
2195
+ }
2196
+ requestAnimationFrame(() => {
2197
+ elementToFocus?.focus({ preventScroll });
2198
+ });
2199
+ }
2200
+ document.addEventListener("keydown", handleKeyDown);
2201
+ return () => {
2202
+ document.removeEventListener("keydown", handleKeyDown);
2203
+ const elementToReturn = returnFocusTo ?? previousActiveElementRef.current;
2204
+ if (elementToReturn && typeof elementToReturn.focus === "function") {
2205
+ elementToReturn.focus({ preventScroll });
2206
+ }
2207
+ };
2208
+ }, [active, initialFocus, returnFocusTo, handleKeyDown, preventScroll]);
2209
+ return containerRef;
2210
+ }
2211
+
2212
+ // src/hooks/useFocusRef.ts
2213
+ import { useRef as useRef4, useEffect as useEffect6 } from "react";
2214
+ function useFocusRef(options = {}) {
2215
+ const { focusOnMount = false, preventScroll = false, delay = 0 } = options;
2216
+ const ref = useRef4(null);
2217
+ useEffect6(() => {
2218
+ if (!focusOnMount) return;
2219
+ const focus = () => {
2220
+ if (ref.current && typeof ref.current.focus === "function") {
2221
+ ref.current.focus({ preventScroll });
2222
+ }
2223
+ };
2224
+ if (delay > 0) {
2225
+ const timer = setTimeout(focus, delay);
2226
+ return () => clearTimeout(timer);
2227
+ } else {
2228
+ const frame = requestAnimationFrame(focus);
2229
+ return () => cancelAnimationFrame(frame);
2230
+ }
2231
+ }, [focusOnMount, preventScroll, delay]);
2232
+ return ref;
2233
+ }
2234
+
2235
+ // src/hooks/useReducedMotion.ts
2236
+ import { useState as useState6, useEffect as useEffect7 } from "react";
2237
+ function useReducedMotion() {
2238
+ const [reducedMotion, setReducedMotion] = useState6(false);
2239
+ useEffect7(() => {
2240
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
2241
+ setReducedMotion(mediaQuery.matches);
2242
+ const handleChange = (event) => {
2243
+ setReducedMotion(event.matches);
2244
+ };
2245
+ mediaQuery.addEventListener("change", handleChange);
2246
+ return () => {
2247
+ mediaQuery.removeEventListener("change", handleChange);
2248
+ };
2249
+ }, []);
2250
+ return reducedMotion;
2251
+ }
2252
+
2253
+ // src/hooks/useArrowNavigation.ts
2254
+ import { useCallback as useCallback4, useRef as useRef5 } from "react";
2255
+ var DEFAULT_SELECTOR = '[role="menuitem"], [role="option"], [role="tab"], button, a, [tabindex]:not([tabindex="-1"])';
2256
+ function useArrowNavigation(options = {}) {
2257
+ const {
2258
+ orientation = "vertical",
2259
+ loop = true,
2260
+ itemSelector = DEFAULT_SELECTOR,
2261
+ onFocusChange
2262
+ } = options;
2263
+ const containerRef = useRef5(null);
2264
+ const focusedIndexRef = useRef5(-1);
2265
+ const getFocusableItems = useCallback4(() => {
2266
+ if (!containerRef.current) return [];
2267
+ return Array.from(containerRef.current.querySelectorAll(itemSelector));
2268
+ }, [itemSelector]);
2269
+ const focusItem = useCallback4(
2270
+ (index) => {
2271
+ const items = getFocusableItems();
2272
+ if (items.length === 0) return;
2273
+ let targetIndex = index;
2274
+ if (loop) {
2275
+ if (targetIndex < 0) targetIndex = items.length - 1;
2276
+ if (targetIndex >= items.length) targetIndex = 0;
2277
+ } else {
2278
+ targetIndex = Math.max(0, Math.min(targetIndex, items.length - 1));
2279
+ }
2280
+ const element = items[targetIndex];
2281
+ if (element) {
2282
+ element.focus();
2283
+ focusedIndexRef.current = targetIndex;
2284
+ onFocusChange?.(targetIndex, element);
2285
+ }
2286
+ },
2287
+ [getFocusableItems, loop, onFocusChange]
2288
+ );
2289
+ const handleKeyDown = useCallback4(
2290
+ (event) => {
2291
+ const items = getFocusableItems();
2292
+ if (items.length === 0) return;
2293
+ const currentIndex = focusedIndexRef.current;
2294
+ let handled = false;
2295
+ switch (event.key) {
2296
+ case "ArrowUp":
2297
+ if (orientation === "vertical" || orientation === "both") {
2298
+ focusItem(currentIndex - 1);
2299
+ handled = true;
2300
+ }
2301
+ break;
2302
+ case "ArrowDown":
2303
+ if (orientation === "vertical" || orientation === "both") {
2304
+ focusItem(currentIndex + 1);
2305
+ handled = true;
2306
+ }
2307
+ break;
2308
+ case "ArrowLeft":
2309
+ if (orientation === "horizontal" || orientation === "both") {
2310
+ focusItem(currentIndex - 1);
2311
+ handled = true;
2312
+ }
2313
+ break;
2314
+ case "ArrowRight":
2315
+ if (orientation === "horizontal" || orientation === "both") {
2316
+ focusItem(currentIndex + 1);
2317
+ handled = true;
2318
+ }
2319
+ break;
2320
+ case "Home":
2321
+ focusItem(0);
2322
+ handled = true;
2323
+ break;
2324
+ case "End":
2325
+ focusItem(items.length - 1);
2326
+ handled = true;
2327
+ break;
2328
+ }
2329
+ if (handled) {
2330
+ event.preventDefault();
2331
+ event.stopPropagation();
2332
+ }
2333
+ },
2334
+ [orientation, focusItem, getFocusableItems]
2335
+ );
2336
+ const handleFocus = useCallback4(
2337
+ (event) => {
2338
+ const items = getFocusableItems();
2339
+ const index = items.indexOf(event.target);
2340
+ if (index !== -1) {
2341
+ focusedIndexRef.current = index;
2342
+ }
2343
+ },
2344
+ [getFocusableItems]
2345
+ );
2346
+ const containerProps = {
2347
+ ref: (node) => {
2348
+ containerRef.current = node;
2349
+ },
2350
+ onKeyDown: handleKeyDown,
2351
+ onFocus: handleFocus
2352
+ };
2353
+ const getItemProps = useCallback4(
2354
+ (index) => ({
2355
+ tabIndex: index === 0 ? 0 : -1,
2356
+ onFocus: () => {
2357
+ focusedIndexRef.current = index;
2358
+ }
2359
+ }),
2360
+ []
2361
+ );
2362
+ return {
2363
+ containerProps,
2364
+ getItemProps,
2365
+ focusItem,
2366
+ getFocusedIndex: () => focusedIndexRef.current
2367
+ };
2368
+ }
2369
+
2370
+ // src/hooks/useRovingTabindex.ts
2371
+ import { useState as useState7, useCallback as useCallback5, useRef as useRef6 } from "react";
2372
+ var DEFAULT_SELECTOR2 = "[data-roving-tabindex-item]";
2373
+ function useRovingTabindex(options = {}) {
2374
+ const { loop = true, itemSelector = DEFAULT_SELECTOR2, initialIndex = 0, onIndexChange } = options;
2375
+ const [activeIndex, setActiveIndex] = useState7(initialIndex);
2376
+ const containerRef = useRef6(null);
2377
+ const itemsRef = useRef6(/* @__PURE__ */ new Map());
2378
+ const getItems = useCallback5(() => {
2379
+ if (!containerRef.current) {
2380
+ return Array.from(itemsRef.current.values());
2381
+ }
2382
+ return Array.from(containerRef.current.querySelectorAll(itemSelector));
2383
+ }, [itemSelector]);
2384
+ const focusIndex = useCallback5(
2385
+ (index) => {
2386
+ const items = getItems();
2387
+ if (items.length === 0) return;
2388
+ let targetIndex = index;
2389
+ if (loop) {
2390
+ if (targetIndex < 0) targetIndex = items.length - 1;
2391
+ if (targetIndex >= items.length) targetIndex = 0;
2392
+ } else {
2393
+ targetIndex = Math.max(0, Math.min(targetIndex, items.length - 1));
2394
+ }
2395
+ setActiveIndex(targetIndex);
2396
+ onIndexChange?.(targetIndex);
2397
+ const element = items[targetIndex];
2398
+ if (element) {
2399
+ element.focus();
2400
+ }
2401
+ },
2402
+ [getItems, loop, onIndexChange]
2403
+ );
2404
+ const handleKeyDown = useCallback5(
2405
+ (event) => {
2406
+ const items = getItems();
2407
+ if (items.length === 0) return;
2408
+ let handled = false;
2409
+ switch (event.key) {
2410
+ case "ArrowUp":
2411
+ case "ArrowLeft":
2412
+ focusIndex(activeIndex - 1);
2413
+ handled = true;
2414
+ break;
2415
+ case "ArrowDown":
2416
+ case "ArrowRight":
2417
+ focusIndex(activeIndex + 1);
2418
+ handled = true;
2419
+ break;
2420
+ case "Home":
2421
+ focusIndex(0);
2422
+ handled = true;
2423
+ break;
2424
+ case "End":
2425
+ focusIndex(items.length - 1);
2426
+ handled = true;
2427
+ break;
2428
+ }
2429
+ if (handled) {
2430
+ event.preventDefault();
2431
+ event.stopPropagation();
2432
+ }
2433
+ },
2434
+ [activeIndex, focusIndex, getItems]
2435
+ );
2436
+ const containerProps = {
2437
+ ref: (node) => {
2438
+ containerRef.current = node;
2439
+ },
2440
+ onKeyDown: handleKeyDown
2441
+ };
2442
+ const getItemProps = useCallback5(
2443
+ (index) => ({
2444
+ "data-roving-tabindex-item": true,
2445
+ tabIndex: index === activeIndex ? 0 : -1,
2446
+ ref: (node) => {
2447
+ if (node) {
2448
+ itemsRef.current.set(index, node);
2449
+ } else {
2450
+ itemsRef.current.delete(index);
2451
+ }
2452
+ },
2453
+ onClick: () => {
2454
+ setActiveIndex(index);
2455
+ onIndexChange?.(index);
2456
+ },
2457
+ onFocus: () => {
2458
+ if (activeIndex !== index) {
2459
+ setActiveIndex(index);
2460
+ onIndexChange?.(index);
2461
+ }
2462
+ }
2463
+ }),
2464
+ [activeIndex, onIndexChange]
2465
+ );
2466
+ return {
2467
+ containerProps,
2468
+ getItemProps,
2469
+ activeIndex,
2470
+ setActiveIndex: focusIndex,
2471
+ focusNext: () => focusIndex(activeIndex + 1),
2472
+ focusPrevious: () => focusIndex(activeIndex - 1),
2473
+ focusFirst: () => focusIndex(0),
2474
+ focusLast: () => focusIndex(getItems().length - 1)
2475
+ };
2476
+ }
2477
+
2478
+ // src/hooks/useEscapeKey.ts
2479
+ import * as React23 from "react";
2480
+ function useEscapeKey(callback, options) {
2481
+ const { enabled = true, preventDefault = true, stopPropagation = false } = options ?? {};
2482
+ const callbackRef = React23.useRef(callback);
2483
+ React23.useEffect(() => {
2484
+ callbackRef.current = callback;
2485
+ }, [callback]);
2486
+ React23.useEffect(() => {
2487
+ if (!enabled) return;
2488
+ const handleKeyDown = (event) => {
2489
+ if (event.key === "Escape") {
2490
+ if (preventDefault) {
2491
+ event.preventDefault();
2492
+ }
2493
+ if (stopPropagation) {
2494
+ event.stopPropagation();
2495
+ }
2496
+ callbackRef.current(event);
2497
+ }
2498
+ };
2499
+ document.addEventListener("keydown", handleKeyDown);
2500
+ return () => {
2501
+ document.removeEventListener("keydown", handleKeyDown);
2502
+ };
2503
+ }, [enabled, preventDefault, stopPropagation]);
2504
+ }
2505
+
2506
+ // src/hooks/useClickOutside.ts
2507
+ import * as React24 from "react";
2508
+ function useClickOutside(ref, callback, options) {
2509
+ const {
2510
+ enabled = true,
2511
+ events = ["mousedown", "touchstart"],
2512
+ ignoreElements = []
2513
+ } = options ?? {};
2514
+ const callbackRef = React24.useRef(callback);
2515
+ React24.useEffect(() => {
2516
+ callbackRef.current = callback;
2517
+ }, [callback]);
2518
+ React24.useEffect(() => {
2519
+ if (!enabled) return;
2520
+ const handleEvent = (event) => {
2521
+ const target = event.target;
2522
+ if (ref.current?.contains(target)) {
2523
+ return;
2524
+ }
2525
+ for (const ignoreRef of ignoreElements) {
2526
+ if (ignoreRef.current?.contains(target)) {
2527
+ return;
2528
+ }
2529
+ }
2530
+ callbackRef.current(event);
2531
+ };
2532
+ events.forEach((eventType) => {
2533
+ document.addEventListener(eventType, handleEvent);
2534
+ });
2535
+ return () => {
2536
+ events.forEach((eventType) => {
2537
+ document.removeEventListener(eventType, handleEvent);
2538
+ });
2539
+ };
2540
+ }, [enabled, events, ignoreElements, ref]);
2541
+ }
1348
2542
  export {
1349
2543
  Avatar,
1350
2544
  AvatarFallback,
@@ -1374,9 +2568,18 @@ export {
1374
2568
  DialogTrigger,
1375
2569
  Input,
1376
2570
  Label,
2571
+ LiveRegion,
2572
+ LoadingLogo,
2573
+ LoadingOverlay,
2574
+ LoadingPage,
2575
+ LoadingSection,
1377
2576
  PageHeader,
1378
2577
  PasswordInput,
2578
+ ProgressBar,
1379
2579
  Separator,
2580
+ Skeleton,
2581
+ SkipLink,
2582
+ Spinner,
1380
2583
  Switch,
1381
2584
  badgeVariants,
1382
2585
  buttonVariants,
@@ -1385,5 +2588,16 @@ export {
1385
2588
  pageHeaderContainerVariants,
1386
2589
  pageHeaderSubtitleVariants,
1387
2590
  pageHeaderTitleVariants,
1388
- useDataPanel
2591
+ progressBarVariants,
2592
+ skeletonVariants,
2593
+ spinnerVariants,
2594
+ useAnnounce,
2595
+ useArrowNavigation,
2596
+ useClickOutside,
2597
+ useDataPanel,
2598
+ useEscapeKey,
2599
+ useFocusRef,
2600
+ useFocusTrap,
2601
+ useReducedMotion,
2602
+ useRovingTabindex
1389
2603
  };