glasswind 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1930 @@
1
+ import { createContext, forwardRef, useCallback, useMemo, useId, useState, useEffect, useRef, useReducer, useContext, useLayoutEffect } from 'react';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { createPortal } from 'react-dom';
4
+
5
+ // src/components/Accordion/Accordion.tsx
6
+
7
+ // src/utils/cn.ts
8
+ function cn(...inputs) {
9
+ const out = [];
10
+ for (const input of inputs) {
11
+ if (!input) continue;
12
+ if (Array.isArray(input)) {
13
+ const nested = cn(...input);
14
+ if (nested) out.push(nested);
15
+ } else if (typeof input === "string" || typeof input === "number") {
16
+ out.push(String(input));
17
+ }
18
+ }
19
+ return out.join(" ");
20
+ }
21
+ function useDisclosure(defaultOpen = false) {
22
+ const [isOpen, setOpen] = useState(defaultOpen);
23
+ const open = useCallback(() => setOpen(true), []);
24
+ const close = useCallback(() => setOpen(false), []);
25
+ const toggle = useCallback(() => setOpen((v) => !v), []);
26
+ return { isOpen, open, close, toggle, setOpen };
27
+ }
28
+ function useClickOutside(ref, handler, enabled = true) {
29
+ useEffect(() => {
30
+ if (!enabled) return;
31
+ const listener = (event) => {
32
+ const el = ref.current;
33
+ if (!el || el.contains(event.target)) return;
34
+ handler(event);
35
+ };
36
+ document.addEventListener("mousedown", listener);
37
+ document.addEventListener("touchstart", listener);
38
+ return () => {
39
+ document.removeEventListener("mousedown", listener);
40
+ document.removeEventListener("touchstart", listener);
41
+ };
42
+ }, [ref, handler, enabled]);
43
+ }
44
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
45
+
46
+ // src/hooks/useScrollLock.ts
47
+ function useScrollLock(locked) {
48
+ useIsomorphicLayoutEffect(() => {
49
+ if (!locked || typeof document === "undefined") return;
50
+ const { body } = document;
51
+ const prevOverflow = body.style.overflow;
52
+ const prevPaddingRight = body.style.paddingRight;
53
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
54
+ body.style.overflow = "hidden";
55
+ if (scrollbarWidth > 0) {
56
+ body.style.paddingRight = `${scrollbarWidth}px`;
57
+ }
58
+ return () => {
59
+ body.style.overflow = prevOverflow;
60
+ body.style.paddingRight = prevPaddingRight;
61
+ };
62
+ }, [locked]);
63
+ }
64
+ function useControllableState(params) {
65
+ const { value, defaultValue, onChange } = params;
66
+ const isControlled = value !== void 0;
67
+ const [internal, setInternal] = useState(defaultValue);
68
+ const onChangeRef = useRef(onChange);
69
+ onChangeRef.current = onChange;
70
+ const current = isControlled ? value : internal;
71
+ const setValue = useCallback(
72
+ (next) => {
73
+ if (!isControlled) setInternal(next);
74
+ onChangeRef.current?.(next);
75
+ },
76
+ [isControlled]
77
+ );
78
+ return [current, setValue];
79
+ }
80
+ var AccordionContext = createContext(null);
81
+ function useAccordionContext() {
82
+ const ctx = useContext(AccordionContext);
83
+ if (ctx === null) {
84
+ throw new Error(
85
+ "Glasswind: <AccordionItem> must be rendered inside an <Accordion>."
86
+ );
87
+ }
88
+ return ctx;
89
+ }
90
+ function toOpenList(value) {
91
+ if (Array.isArray(value)) return value;
92
+ return value === "" ? [] : [value];
93
+ }
94
+ var NAV_KEYS = ["ArrowDown", "ArrowUp", "Home", "End"];
95
+ var Accordion = forwardRef(
96
+ function Accordion2({
97
+ type = "single",
98
+ value: valueProp,
99
+ defaultValue,
100
+ onChange,
101
+ collapsible = true,
102
+ className,
103
+ children,
104
+ ...rest
105
+ }, ref) {
106
+ const [value, setValue] = useControllableState({
107
+ value: valueProp,
108
+ defaultValue: defaultValue ?? (type === "multiple" ? [] : ""),
109
+ onChange
110
+ });
111
+ const toggle = useCallback(
112
+ (itemValue) => {
113
+ if (type === "multiple") {
114
+ const open = toOpenList(value);
115
+ const next = open.includes(itemValue) ? open.filter((v) => v !== itemValue) : [...open, itemValue];
116
+ setValue(next);
117
+ return;
118
+ }
119
+ const current = toOpenList(value)[0] ?? "";
120
+ if (current === itemValue) {
121
+ if (collapsible) setValue("");
122
+ } else {
123
+ setValue(itemValue);
124
+ }
125
+ },
126
+ [type, collapsible, value, setValue]
127
+ );
128
+ const ctx = useMemo(
129
+ () => ({
130
+ type,
131
+ isOpen: (itemValue) => toOpenList(value).includes(itemValue),
132
+ toggle
133
+ }),
134
+ [type, value, toggle]
135
+ );
136
+ return /* @__PURE__ */ jsx(
137
+ "div",
138
+ {
139
+ ref,
140
+ "data-gl-accordion": "",
141
+ className: cn("gl-accordion", className),
142
+ ...rest,
143
+ children: /* @__PURE__ */ jsx(AccordionContext.Provider, { value: ctx, children })
144
+ }
145
+ );
146
+ }
147
+ );
148
+ var AccordionItem = forwardRef(
149
+ function AccordionItem2({ value, title, disabled = false, className, children, ...rest }, ref) {
150
+ const { isOpen, toggle } = useAccordionContext();
151
+ const open = isOpen(value);
152
+ const baseId = useId();
153
+ const triggerId = `${baseId}-trigger`;
154
+ const panelId = `${baseId}-panel`;
155
+ const handleKeyDown = useCallback(
156
+ (event) => {
157
+ if (!NAV_KEYS.includes(event.key)) return;
158
+ const trigger = event.currentTarget;
159
+ const root = trigger.closest("[data-gl-accordion]");
160
+ if (root === null) return;
161
+ const triggers = Array.from(
162
+ root.querySelectorAll(
163
+ ".gl-accordion__trigger:not([disabled])"
164
+ )
165
+ ).filter((el) => el.closest("[data-gl-accordion]") === root);
166
+ if (triggers.length === 0) return;
167
+ const currentIndex = triggers.indexOf(trigger);
168
+ let nextIndex = currentIndex;
169
+ switch (event.key) {
170
+ case "ArrowDown":
171
+ nextIndex = (currentIndex + 1) % triggers.length;
172
+ break;
173
+ case "ArrowUp":
174
+ nextIndex = (currentIndex - 1 + triggers.length) % triggers.length;
175
+ break;
176
+ case "Home":
177
+ nextIndex = 0;
178
+ break;
179
+ case "End":
180
+ nextIndex = triggers.length - 1;
181
+ break;
182
+ default:
183
+ return;
184
+ }
185
+ event.preventDefault();
186
+ triggers[nextIndex]?.focus();
187
+ },
188
+ []
189
+ );
190
+ return /* @__PURE__ */ jsxs(
191
+ "div",
192
+ {
193
+ ref,
194
+ "data-state": open ? "open" : "closed",
195
+ className: cn(
196
+ "gl-accordion__item",
197
+ disabled && "gl-accordion__item--disabled",
198
+ className
199
+ ),
200
+ ...rest,
201
+ children: [
202
+ /* @__PURE__ */ jsx("h3", { className: "gl-accordion__header", children: /* @__PURE__ */ jsxs(
203
+ "button",
204
+ {
205
+ type: "button",
206
+ id: triggerId,
207
+ className: "gl-accordion__trigger",
208
+ "aria-expanded": open,
209
+ "aria-controls": panelId,
210
+ disabled,
211
+ onClick: () => toggle(value),
212
+ onKeyDown: handleKeyDown,
213
+ children: [
214
+ /* @__PURE__ */ jsx("span", { className: "gl-accordion__title", children: title }),
215
+ /* @__PURE__ */ jsx("span", { className: "gl-accordion__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
216
+ "svg",
217
+ {
218
+ viewBox: "0 0 24 24",
219
+ width: "1em",
220
+ height: "1em",
221
+ fill: "none",
222
+ stroke: "currentColor",
223
+ strokeWidth: "2",
224
+ strokeLinecap: "round",
225
+ strokeLinejoin: "round",
226
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
227
+ }
228
+ ) })
229
+ ]
230
+ }
231
+ ) }),
232
+ /* @__PURE__ */ jsx("div", { className: "gl-accordion__panel", "data-state": open ? "open" : "closed", children: /* @__PURE__ */ jsx(
233
+ "div",
234
+ {
235
+ id: panelId,
236
+ role: "region",
237
+ "aria-labelledby": triggerId,
238
+ className: "gl-accordion__content",
239
+ children: /* @__PURE__ */ jsx("div", { className: "gl-accordion__body", children })
240
+ }
241
+ ) })
242
+ ]
243
+ }
244
+ );
245
+ }
246
+ );
247
+ var STATUS_LABELS = {
248
+ online: "Online",
249
+ offline: "Offline",
250
+ busy: "Busy",
251
+ away: "Away"
252
+ };
253
+ function getInitials(name) {
254
+ const words = name.trim().split(/\s+/).filter(Boolean);
255
+ if (words.length === 0) return "";
256
+ return words.slice(0, 2).map((word) => word.charAt(0)).join("").toUpperCase();
257
+ }
258
+ function FallbackIcon() {
259
+ return /* @__PURE__ */ jsx(
260
+ "svg",
261
+ {
262
+ className: "gl-avatar__icon",
263
+ viewBox: "0 0 24 24",
264
+ fill: "none",
265
+ "aria-hidden": "true",
266
+ focusable: "false",
267
+ children: /* @__PURE__ */ jsx(
268
+ "path",
269
+ {
270
+ d: "M12 12.5a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm0 1.75c-3.6 0-6.5 2-6.5 4.5 0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25 0-2.5-2.9-4.5-6.5-4.5Z",
271
+ fill: "currentColor"
272
+ }
273
+ )
274
+ }
275
+ );
276
+ }
277
+ var Avatar = forwardRef(function Avatar2({
278
+ src,
279
+ alt,
280
+ name,
281
+ size = "md",
282
+ shape = "circle",
283
+ status,
284
+ className,
285
+ children,
286
+ ...rest
287
+ }, ref) {
288
+ const [imgFailed, setImgFailed] = useState(false);
289
+ useEffect(() => {
290
+ setImgFailed(false);
291
+ }, [src]);
292
+ const showImage = Boolean(src) && !imgFailed;
293
+ const initials = name ? getInitials(name) : "";
294
+ const a11yLabel = alt ?? name;
295
+ return /* @__PURE__ */ jsxs(
296
+ "span",
297
+ {
298
+ ref,
299
+ className: cn(
300
+ "gl-avatar",
301
+ `gl-avatar--${size}`,
302
+ `gl-avatar--${shape}`,
303
+ "gl-focusable",
304
+ className
305
+ ),
306
+ ...showImage ? {} : { role: "img", "aria-label": a11yLabel || "Avatar" },
307
+ ...rest,
308
+ children: [
309
+ showImage ? /* @__PURE__ */ jsx(
310
+ "img",
311
+ {
312
+ className: "gl-avatar__img",
313
+ src,
314
+ alt: alt ?? name ?? "",
315
+ draggable: false,
316
+ onError: () => setImgFailed(true)
317
+ }
318
+ ) : initials ? /* @__PURE__ */ jsx("span", { className: "gl-avatar__initials", "aria-hidden": "true", children: initials }) : /* @__PURE__ */ jsx(FallbackIcon, {}),
319
+ children,
320
+ status ? /* @__PURE__ */ jsx(
321
+ "span",
322
+ {
323
+ className: cn("gl-avatar__status", `gl-avatar__status--${status}`),
324
+ role: "img",
325
+ "aria-label": STATUS_LABELS[status]
326
+ }
327
+ ) : null
328
+ ]
329
+ }
330
+ );
331
+ });
332
+ var Badge = forwardRef(function Badge2({
333
+ variant = "glass",
334
+ size = "md",
335
+ dot = false,
336
+ pill = true,
337
+ className,
338
+ children,
339
+ ...rest
340
+ }, ref) {
341
+ return /* @__PURE__ */ jsxs(
342
+ "span",
343
+ {
344
+ ref,
345
+ className: cn(
346
+ "gl-badge",
347
+ `gl-badge--${variant}`,
348
+ `gl-badge--${size}`,
349
+ pill && "gl-badge--pill",
350
+ className
351
+ ),
352
+ ...rest,
353
+ children: [
354
+ dot ? /* @__PURE__ */ jsx("span", { className: "gl-badge__dot", "aria-hidden": "true" }) : null,
355
+ children != null ? /* @__PURE__ */ jsx("span", { className: "gl-badge__label", children }) : null
356
+ ]
357
+ }
358
+ );
359
+ });
360
+ var Button = forwardRef(function Button2({
361
+ variant = "glass",
362
+ size = "md",
363
+ loading = false,
364
+ leftIcon,
365
+ rightIcon,
366
+ fullWidth = false,
367
+ className,
368
+ children,
369
+ disabled,
370
+ type = "button",
371
+ ...rest
372
+ }, ref) {
373
+ return /* @__PURE__ */ jsxs(
374
+ "button",
375
+ {
376
+ ref,
377
+ type,
378
+ className: cn(
379
+ "gl-btn",
380
+ `gl-btn--${variant}`,
381
+ `gl-btn--${size}`,
382
+ fullWidth && "gl-btn--block",
383
+ loading && "gl-btn--loading",
384
+ "gl-focusable",
385
+ className
386
+ ),
387
+ disabled: disabled || loading,
388
+ "aria-busy": loading || void 0,
389
+ ...rest,
390
+ children: [
391
+ loading && /* @__PURE__ */ jsx("span", { className: "gl-btn__spinner", "aria-hidden": "true" }),
392
+ !loading && leftIcon ? /* @__PURE__ */ jsx("span", { className: "gl-btn__icon", "aria-hidden": "true", children: leftIcon }) : null,
393
+ children != null ? /* @__PURE__ */ jsx("span", { className: "gl-btn__label", children }) : null,
394
+ !loading && rightIcon ? /* @__PURE__ */ jsx("span", { className: "gl-btn__icon", "aria-hidden": "true", children: rightIcon }) : null
395
+ ]
396
+ }
397
+ );
398
+ });
399
+ var Card = forwardRef(function Card2({
400
+ variant = "glass",
401
+ padding = "md",
402
+ hoverable = false,
403
+ className,
404
+ children,
405
+ ...rest
406
+ }, ref) {
407
+ return /* @__PURE__ */ jsx(
408
+ "div",
409
+ {
410
+ ref,
411
+ className: cn(
412
+ "gl-card",
413
+ `gl-card--${variant}`,
414
+ `gl-card--pad-${padding}`,
415
+ hoverable && "gl-card--hoverable",
416
+ className
417
+ ),
418
+ ...rest,
419
+ children
420
+ }
421
+ );
422
+ });
423
+ var CardHeader = forwardRef(
424
+ function CardHeader2({ className, children, ...rest }, ref) {
425
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-card__header", className), ...rest, children });
426
+ }
427
+ );
428
+ var CardBody = forwardRef(
429
+ function CardBody2({ className, children, ...rest }, ref) {
430
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-card__body", className), ...rest, children });
431
+ }
432
+ );
433
+ var CardFooter = forwardRef(
434
+ function CardFooter2({ className, children, ...rest }, ref) {
435
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-card__footer", className), ...rest, children });
436
+ }
437
+ );
438
+ var Checkbox = forwardRef(
439
+ function Checkbox2({
440
+ label,
441
+ indeterminate = false,
442
+ error = false,
443
+ boxSize = "md",
444
+ className,
445
+ disabled,
446
+ ...rest
447
+ }, ref) {
448
+ const innerRef = useRef(null);
449
+ const setRef = useCallback(
450
+ (node) => {
451
+ innerRef.current = node;
452
+ if (typeof ref === "function") {
453
+ ref(node);
454
+ } else if (ref) {
455
+ ref.current = node;
456
+ }
457
+ },
458
+ [ref]
459
+ );
460
+ useIsomorphicLayoutEffect(() => {
461
+ if (innerRef.current) {
462
+ innerRef.current.indeterminate = indeterminate;
463
+ }
464
+ }, [indeterminate]);
465
+ return /* @__PURE__ */ jsxs(
466
+ "label",
467
+ {
468
+ className: cn(
469
+ "gl-checkbox",
470
+ `gl-checkbox--${boxSize}`,
471
+ error && "gl-checkbox--error",
472
+ disabled && "gl-checkbox--disabled",
473
+ className
474
+ ),
475
+ children: [
476
+ /* @__PURE__ */ jsx(
477
+ "input",
478
+ {
479
+ ref: setRef,
480
+ type: "checkbox",
481
+ className: "gl-sr-only gl-checkbox__input",
482
+ disabled,
483
+ "aria-invalid": error || void 0,
484
+ ...rest
485
+ }
486
+ ),
487
+ /* @__PURE__ */ jsxs("span", { className: "gl-checkbox__box", "aria-hidden": "true", children: [
488
+ /* @__PURE__ */ jsx(
489
+ "svg",
490
+ {
491
+ className: "gl-checkbox__check",
492
+ viewBox: "0 0 16 16",
493
+ fill: "none",
494
+ xmlns: "http://www.w3.org/2000/svg",
495
+ children: /* @__PURE__ */ jsx(
496
+ "path",
497
+ {
498
+ d: "M3.5 8.5L6.5 11.5L12.5 4.5",
499
+ stroke: "currentColor",
500
+ strokeWidth: "2",
501
+ strokeLinecap: "round",
502
+ strokeLinejoin: "round"
503
+ }
504
+ )
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsx("span", { className: "gl-checkbox__dash" })
508
+ ] }),
509
+ label != null ? /* @__PURE__ */ jsx("span", { className: "gl-checkbox__label", children: label }) : null
510
+ ]
511
+ }
512
+ );
513
+ }
514
+ );
515
+ function Portal({ children, container }) {
516
+ const [mounted, setMounted] = useState(false);
517
+ useIsomorphicLayoutEffect(() => {
518
+ setMounted(true);
519
+ return () => setMounted(false);
520
+ }, []);
521
+ if (!mounted || typeof document === "undefined") return null;
522
+ return createPortal(children, container ?? document.body);
523
+ }
524
+ function assignRef(ref, node) {
525
+ if (typeof ref === "function") {
526
+ ref(node);
527
+ } else if (ref) {
528
+ ref.current = node;
529
+ }
530
+ }
531
+ var Drawer = forwardRef(function Drawer2({
532
+ isOpen,
533
+ onClose,
534
+ children,
535
+ side = "right",
536
+ size = "340px",
537
+ title,
538
+ closeOnBackdrop = true,
539
+ closeOnEsc = true,
540
+ showClose = true,
541
+ className,
542
+ style,
543
+ ...rest
544
+ }, ref) {
545
+ const panelRef = useRef(null);
546
+ const previousFocusRef = useRef(null);
547
+ const reactId = useId();
548
+ const titleId = `${reactId}-title`;
549
+ useScrollLock(isOpen);
550
+ const setPanelRef = useCallback(
551
+ (node) => {
552
+ panelRef.current = node;
553
+ assignRef(ref, node);
554
+ },
555
+ [ref]
556
+ );
557
+ useEffect(() => {
558
+ if (!isOpen || !closeOnEsc) return;
559
+ const onKeyDown = (event) => {
560
+ if (event.key === "Escape") {
561
+ event.stopPropagation();
562
+ onClose();
563
+ }
564
+ };
565
+ document.addEventListener("keydown", onKeyDown);
566
+ return () => document.removeEventListener("keydown", onKeyDown);
567
+ }, [isOpen, closeOnEsc, onClose]);
568
+ useEffect(() => {
569
+ if (!isOpen) return;
570
+ previousFocusRef.current = document.activeElement;
571
+ panelRef.current?.focus();
572
+ return () => {
573
+ previousFocusRef.current?.focus?.();
574
+ };
575
+ }, [isOpen]);
576
+ if (!isOpen) return null;
577
+ const isHorizontal = side === "left" || side === "right";
578
+ const sizeValue = typeof size === "number" ? `${size}px` : size;
579
+ const panelStyle = {
580
+ ...style,
581
+ ...isHorizontal ? { width: sizeValue } : { height: sizeValue }
582
+ };
583
+ const hasHeader = title != null || showClose;
584
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs("div", { className: "gl-drawer", children: [
585
+ /* @__PURE__ */ jsx(
586
+ "div",
587
+ {
588
+ className: "gl-drawer__backdrop",
589
+ "aria-hidden": "true",
590
+ onClick: closeOnBackdrop ? onClose : void 0
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsxs(
594
+ "div",
595
+ {
596
+ ref: setPanelRef,
597
+ role: "dialog",
598
+ "aria-modal": "true",
599
+ "aria-labelledby": title != null ? titleId : void 0,
600
+ tabIndex: -1,
601
+ style: panelStyle,
602
+ className: cn(
603
+ "gl-drawer__panel",
604
+ `gl-drawer__panel--${side}`,
605
+ "gl-focusable",
606
+ className
607
+ ),
608
+ ...rest,
609
+ children: [
610
+ hasHeader ? /* @__PURE__ */ jsxs("div", { className: "gl-drawer__header", children: [
611
+ title != null ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "gl-drawer__title", children: title }) : null,
612
+ showClose ? /* @__PURE__ */ jsx(
613
+ "button",
614
+ {
615
+ type: "button",
616
+ className: "gl-drawer__close gl-focusable",
617
+ "aria-label": "Close",
618
+ onClick: onClose,
619
+ children: /* @__PURE__ */ jsx(
620
+ "svg",
621
+ {
622
+ viewBox: "0 0 24 24",
623
+ width: "18",
624
+ height: "18",
625
+ fill: "none",
626
+ stroke: "currentColor",
627
+ strokeWidth: "2",
628
+ strokeLinecap: "round",
629
+ "aria-hidden": "true",
630
+ children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" })
631
+ }
632
+ )
633
+ }
634
+ ) : null
635
+ ] }) : null,
636
+ children
637
+ ]
638
+ }
639
+ )
640
+ ] }) });
641
+ });
642
+ var DrawerHeader = forwardRef(
643
+ function DrawerHeader2({ className, children, ...rest }, ref) {
644
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-drawer__header", className), ...rest, children });
645
+ }
646
+ );
647
+ var DrawerBody = forwardRef(
648
+ function DrawerBody2({ className, children, ...rest }, ref) {
649
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-drawer__body", className), ...rest, children });
650
+ }
651
+ );
652
+ var DrawerFooter = forwardRef(
653
+ function DrawerFooter2({ className, children, ...rest }, ref) {
654
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-drawer__footer", className), ...rest, children });
655
+ }
656
+ );
657
+ var DropdownContext = createContext(null);
658
+ function useDropdownContext(part) {
659
+ const ctx = useContext(DropdownContext);
660
+ if (!ctx) {
661
+ throw new Error(`${part} must be rendered inside a <Dropdown>.`);
662
+ }
663
+ return ctx;
664
+ }
665
+ var ITEM_SELECTOR = '[role="menuitem"]:not([disabled]):not([aria-disabled="true"])';
666
+ function getMenuItems(menu) {
667
+ if (!menu) return [];
668
+ return Array.from(menu.querySelectorAll(ITEM_SELECTOR));
669
+ }
670
+ function assignRef2(ref, value) {
671
+ if (typeof ref === "function") {
672
+ ref(value);
673
+ } else if (ref) {
674
+ ref.current = value;
675
+ }
676
+ }
677
+ function Dropdown({
678
+ open: openProp,
679
+ defaultOpen = false,
680
+ onOpenChange,
681
+ placement = "bottom-start",
682
+ children
683
+ }) {
684
+ const [open, setOpen] = useControllableState({
685
+ value: openProp,
686
+ defaultValue: defaultOpen,
687
+ onChange: onOpenChange
688
+ });
689
+ const rootRef = useRef(null);
690
+ const triggerRef = useRef(null);
691
+ const menuRef = useRef(null);
692
+ const reactId = useId();
693
+ const triggerId = `${reactId}-trigger`;
694
+ const menuId = `${reactId}-menu`;
695
+ const close = useCallback(() => setOpen(false), [setOpen]);
696
+ const closeAndFocusTrigger = useCallback(() => {
697
+ setOpen(false);
698
+ triggerRef.current?.focus();
699
+ }, [setOpen]);
700
+ useClickOutside(rootRef, close, open);
701
+ useEffect(() => {
702
+ if (!open) return;
703
+ const onKeyDown = (event) => {
704
+ if (event.key === "Escape") {
705
+ event.preventDefault();
706
+ setOpen(false);
707
+ triggerRef.current?.focus();
708
+ }
709
+ };
710
+ document.addEventListener("keydown", onKeyDown);
711
+ return () => document.removeEventListener("keydown", onKeyDown);
712
+ }, [open, setOpen]);
713
+ const value = {
714
+ open,
715
+ setOpen,
716
+ placement,
717
+ triggerId,
718
+ menuId,
719
+ triggerRef,
720
+ menuRef,
721
+ closeAndFocusTrigger
722
+ };
723
+ return /* @__PURE__ */ jsx(DropdownContext.Provider, { value, children: /* @__PURE__ */ jsx("span", { ref: rootRef, className: "gl-dropdown", children }) });
724
+ }
725
+ var DropdownTrigger = forwardRef(function DropdownTrigger2({ className, children, onClick, onKeyDown, type = "button", ...rest }, ref) {
726
+ const ctx = useDropdownContext("DropdownTrigger");
727
+ const setRef = useCallback(
728
+ (node) => {
729
+ ctx.triggerRef.current = node;
730
+ assignRef2(ref, node);
731
+ },
732
+ [ctx.triggerRef, ref]
733
+ );
734
+ const handleClick = useCallback(
735
+ (event) => {
736
+ ctx.setOpen(!ctx.open);
737
+ onClick?.(event);
738
+ },
739
+ [ctx, onClick]
740
+ );
741
+ const handleKeyDown = useCallback(
742
+ (event) => {
743
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
744
+ event.preventDefault();
745
+ if (!ctx.open) {
746
+ ctx.setOpen(true);
747
+ } else {
748
+ getMenuItems(ctx.menuRef.current)[0]?.focus();
749
+ }
750
+ }
751
+ onKeyDown?.(event);
752
+ },
753
+ [ctx, onKeyDown]
754
+ );
755
+ return /* @__PURE__ */ jsx(
756
+ "button",
757
+ {
758
+ ref: setRef,
759
+ type,
760
+ id: ctx.triggerId,
761
+ className: cn("gl-dropdown__trigger", "gl-focusable", className),
762
+ "aria-haspopup": "menu",
763
+ "aria-expanded": ctx.open,
764
+ "aria-controls": ctx.open ? ctx.menuId : void 0,
765
+ onClick: handleClick,
766
+ onKeyDown: handleKeyDown,
767
+ ...rest,
768
+ children
769
+ }
770
+ );
771
+ });
772
+ var DropdownMenu = forwardRef(
773
+ function DropdownMenu2({ className, children, onKeyDown, ...rest }, ref) {
774
+ const ctx = useDropdownContext("DropdownMenu");
775
+ const { open, menuRef } = ctx;
776
+ const setRef = useCallback(
777
+ (node) => {
778
+ menuRef.current = node;
779
+ assignRef2(ref, node);
780
+ },
781
+ [menuRef, ref]
782
+ );
783
+ useEffect(() => {
784
+ if (!open) return;
785
+ getMenuItems(menuRef.current)[0]?.focus();
786
+ }, [open, menuRef]);
787
+ const handleKeyDown = useCallback(
788
+ (event) => {
789
+ const items = getMenuItems(menuRef.current);
790
+ if (items.length > 0) {
791
+ const active = document.activeElement;
792
+ const currentIndex = active ? items.indexOf(active) : -1;
793
+ switch (event.key) {
794
+ case "ArrowDown": {
795
+ event.preventDefault();
796
+ const next = currentIndex < 0 ? 0 : (currentIndex + 1) % items.length;
797
+ items[next]?.focus();
798
+ break;
799
+ }
800
+ case "ArrowUp": {
801
+ event.preventDefault();
802
+ const prev = currentIndex < 0 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
803
+ items[prev]?.focus();
804
+ break;
805
+ }
806
+ case "Home": {
807
+ event.preventDefault();
808
+ items[0]?.focus();
809
+ break;
810
+ }
811
+ case "End": {
812
+ event.preventDefault();
813
+ items[items.length - 1]?.focus();
814
+ break;
815
+ }
816
+ }
817
+ }
818
+ onKeyDown?.(event);
819
+ },
820
+ [menuRef, onKeyDown]
821
+ );
822
+ if (!open) return null;
823
+ return /* @__PURE__ */ jsx(
824
+ "div",
825
+ {
826
+ ref: setRef,
827
+ id: ctx.menuId,
828
+ role: "menu",
829
+ "aria-orientation": "vertical",
830
+ "aria-labelledby": ctx.triggerId,
831
+ tabIndex: -1,
832
+ className: cn(
833
+ "gl-dropdown__menu",
834
+ `gl-dropdown__menu--${ctx.placement}`,
835
+ className
836
+ ),
837
+ onKeyDown: handleKeyDown,
838
+ ...rest,
839
+ children
840
+ }
841
+ );
842
+ }
843
+ );
844
+ var DropdownItem = forwardRef(
845
+ function DropdownItem2({ className, children, onSelect, disabled = false, leftIcon, onClick, type = "button", ...rest }, ref) {
846
+ const ctx = useDropdownContext("DropdownItem");
847
+ const handleClick = useCallback(
848
+ (event) => {
849
+ if (disabled) return;
850
+ onClick?.(event);
851
+ onSelect?.();
852
+ ctx.closeAndFocusTrigger();
853
+ },
854
+ [ctx, disabled, onClick, onSelect]
855
+ );
856
+ return /* @__PURE__ */ jsxs(
857
+ "button",
858
+ {
859
+ ref,
860
+ type,
861
+ role: "menuitem",
862
+ tabIndex: -1,
863
+ disabled,
864
+ className: cn("gl-dropdown__item", "gl-focusable", className),
865
+ onClick: handleClick,
866
+ ...rest,
867
+ children: [
868
+ leftIcon != null ? /* @__PURE__ */ jsx("span", { className: "gl-dropdown__item-icon", "aria-hidden": "true", children: leftIcon }) : null,
869
+ /* @__PURE__ */ jsx("span", { className: "gl-dropdown__item-label", children })
870
+ ]
871
+ }
872
+ );
873
+ }
874
+ );
875
+ var DropdownSeparator = forwardRef(function DropdownSeparator2({ className, ...rest }, ref) {
876
+ return /* @__PURE__ */ jsx(
877
+ "div",
878
+ {
879
+ ref,
880
+ role: "separator",
881
+ "aria-orientation": "horizontal",
882
+ className: cn("gl-dropdown__separator", className),
883
+ ...rest
884
+ }
885
+ );
886
+ });
887
+ var DropdownLabel = forwardRef(
888
+ function DropdownLabel2({ className, children, ...rest }, ref) {
889
+ return /* @__PURE__ */ jsx(
890
+ "div",
891
+ {
892
+ ref,
893
+ role: "presentation",
894
+ className: cn("gl-dropdown__label", className),
895
+ ...rest,
896
+ children
897
+ }
898
+ );
899
+ }
900
+ );
901
+ var Input = forwardRef(function Input2({
902
+ variant = "subtle",
903
+ inputSize = "md",
904
+ error = false,
905
+ leftIcon,
906
+ rightIcon,
907
+ fullWidth = false,
908
+ className,
909
+ disabled,
910
+ "aria-invalid": ariaInvalid,
911
+ ...rest
912
+ }, ref) {
913
+ return /* @__PURE__ */ jsxs(
914
+ "span",
915
+ {
916
+ className: cn(
917
+ "gl-input",
918
+ `gl-input--${variant}`,
919
+ `gl-input--${inputSize}`,
920
+ error && "gl-input--error",
921
+ fullWidth && "gl-input--block",
922
+ disabled && "gl-input--disabled",
923
+ className
924
+ ),
925
+ children: [
926
+ leftIcon != null ? /* @__PURE__ */ jsx("span", { className: "gl-input__icon gl-input__icon--left", "aria-hidden": "true", children: leftIcon }) : null,
927
+ /* @__PURE__ */ jsx(
928
+ "input",
929
+ {
930
+ ref,
931
+ className: "gl-input__field",
932
+ disabled,
933
+ "aria-invalid": ariaInvalid ?? (error || void 0),
934
+ ...rest
935
+ }
936
+ ),
937
+ rightIcon != null ? /* @__PURE__ */ jsx(
938
+ "span",
939
+ {
940
+ className: "gl-input__icon gl-input__icon--right",
941
+ "aria-hidden": "true",
942
+ children: rightIcon
943
+ }
944
+ ) : null
945
+ ]
946
+ }
947
+ );
948
+ });
949
+ var Modal = forwardRef(function Modal2({
950
+ isOpen,
951
+ onClose,
952
+ children,
953
+ title,
954
+ size = "md",
955
+ closeOnBackdrop = true,
956
+ closeOnEsc = true,
957
+ showClose = true,
958
+ className,
959
+ ...rest
960
+ }, ref) {
961
+ const titleId = useId();
962
+ const panelRef = useRef(null);
963
+ const setPanelRef = useCallback(
964
+ (node) => {
965
+ panelRef.current = node;
966
+ if (typeof ref === "function") {
967
+ ref(node);
968
+ } else if (ref) {
969
+ ref.current = node;
970
+ }
971
+ },
972
+ [ref]
973
+ );
974
+ useScrollLock(isOpen);
975
+ useEffect(() => {
976
+ if (!isOpen || !closeOnEsc) return;
977
+ const handleKeyDown = (event) => {
978
+ if (event.key === "Escape") {
979
+ event.stopPropagation();
980
+ onClose();
981
+ }
982
+ };
983
+ document.addEventListener("keydown", handleKeyDown);
984
+ return () => document.removeEventListener("keydown", handleKeyDown);
985
+ }, [isOpen, closeOnEsc, onClose]);
986
+ useEffect(() => {
987
+ if (!isOpen) return;
988
+ const previouslyFocused = typeof document !== "undefined" ? document.activeElement : null;
989
+ panelRef.current?.focus();
990
+ return () => {
991
+ previouslyFocused?.focus?.();
992
+ };
993
+ }, [isOpen]);
994
+ if (!isOpen) return null;
995
+ const handleBackdropClick = (event) => {
996
+ if (closeOnBackdrop && event.target === event.currentTarget) {
997
+ onClose();
998
+ }
999
+ };
1000
+ const hasHeader = title != null || showClose;
1001
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx("div", { className: "gl-modal__backdrop", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs(
1002
+ "div",
1003
+ {
1004
+ ref: setPanelRef,
1005
+ role: "dialog",
1006
+ "aria-modal": "true",
1007
+ "aria-labelledby": title != null ? titleId : void 0,
1008
+ tabIndex: -1,
1009
+ className: cn(
1010
+ "gl-modal__panel",
1011
+ `gl-modal__panel--${size}`,
1012
+ "gl-focusable",
1013
+ className
1014
+ ),
1015
+ ...rest,
1016
+ children: [
1017
+ hasHeader ? /* @__PURE__ */ jsxs("header", { className: "gl-modal__header", children: [
1018
+ title != null ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "gl-modal__title", children: title }) : null,
1019
+ showClose ? /* @__PURE__ */ jsx(
1020
+ "button",
1021
+ {
1022
+ type: "button",
1023
+ className: "gl-modal__close gl-focusable",
1024
+ "aria-label": "Close",
1025
+ onClick: onClose,
1026
+ children: /* @__PURE__ */ jsx(
1027
+ "svg",
1028
+ {
1029
+ viewBox: "0 0 24 24",
1030
+ width: "1em",
1031
+ height: "1em",
1032
+ fill: "none",
1033
+ stroke: "currentColor",
1034
+ strokeWidth: 2,
1035
+ strokeLinecap: "round",
1036
+ "aria-hidden": "true",
1037
+ children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" })
1038
+ }
1039
+ )
1040
+ }
1041
+ ) : null
1042
+ ] }) : null,
1043
+ children
1044
+ ]
1045
+ }
1046
+ ) }) });
1047
+ });
1048
+ var ModalHeader = forwardRef(
1049
+ function ModalHeader2({ className, children, ...rest }, ref) {
1050
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-modal__header", className), ...rest, children });
1051
+ }
1052
+ );
1053
+ var ModalBody = forwardRef(
1054
+ function ModalBody2({ className, children, ...rest }, ref) {
1055
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-modal__body", className), ...rest, children });
1056
+ }
1057
+ );
1058
+ var ModalFooter = forwardRef(
1059
+ function ModalFooter2({ className, children, ...rest }, ref) {
1060
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("gl-modal__footer", className), ...rest, children });
1061
+ }
1062
+ );
1063
+ var Progress = forwardRef(function Progress2({
1064
+ value = 0,
1065
+ max = 100,
1066
+ indeterminate = false,
1067
+ barSize = "md",
1068
+ color = "primary",
1069
+ showLabel = false,
1070
+ className,
1071
+ ...rest
1072
+ }, ref) {
1073
+ const safeMax = max > 0 ? max : 100;
1074
+ const clamped = Math.min(Math.max(value, 0), safeMax);
1075
+ const ratio = clamped / safeMax;
1076
+ const percent = Math.round(ratio * 100);
1077
+ const barStyle = indeterminate ? void 0 : { "--gl-progress-value": ratio };
1078
+ return /* @__PURE__ */ jsxs(
1079
+ "div",
1080
+ {
1081
+ ref,
1082
+ role: "progressbar",
1083
+ "aria-valuemin": 0,
1084
+ "aria-valuemax": safeMax,
1085
+ "aria-valuenow": indeterminate ? void 0 : clamped,
1086
+ "aria-valuetext": indeterminate ? void 0 : `${percent}%`,
1087
+ className: cn(
1088
+ "gl-progress",
1089
+ `gl-progress--${barSize}`,
1090
+ `gl-progress--${color}`,
1091
+ className
1092
+ ),
1093
+ ...rest,
1094
+ children: [
1095
+ /* @__PURE__ */ jsx("div", { className: "gl-progress__track", children: /* @__PURE__ */ jsx(
1096
+ "div",
1097
+ {
1098
+ className: cn(
1099
+ "gl-progress__bar",
1100
+ indeterminate && "gl-progress__bar--indeterminate"
1101
+ ),
1102
+ style: barStyle
1103
+ }
1104
+ ) }),
1105
+ showLabel && !indeterminate ? /* @__PURE__ */ jsxs("span", { className: "gl-progress__label", children: [
1106
+ percent,
1107
+ "%"
1108
+ ] }) : null
1109
+ ]
1110
+ }
1111
+ );
1112
+ });
1113
+ var RadioGroupContext = createContext(
1114
+ null
1115
+ );
1116
+ function useRadioGroupContext(component) {
1117
+ const ctx = useContext(RadioGroupContext);
1118
+ if (ctx === null) {
1119
+ throw new Error(
1120
+ `Glasswind: <${component}> must be rendered inside a <RadioGroup>.`
1121
+ );
1122
+ }
1123
+ return ctx;
1124
+ }
1125
+ var RadioGroup = forwardRef(
1126
+ function RadioGroup2({
1127
+ value,
1128
+ defaultValue,
1129
+ onChange,
1130
+ name,
1131
+ orientation = "vertical",
1132
+ className,
1133
+ children,
1134
+ ...rest
1135
+ }, ref) {
1136
+ const fallbackName = useId();
1137
+ const [selected, setSelected] = useControllableState({
1138
+ value,
1139
+ defaultValue: defaultValue ?? "",
1140
+ onChange
1141
+ });
1142
+ const ctx = useMemo(
1143
+ () => ({
1144
+ name: name ?? fallbackName,
1145
+ value: selected,
1146
+ setValue: setSelected
1147
+ }),
1148
+ [name, fallbackName, selected, setSelected]
1149
+ );
1150
+ return /* @__PURE__ */ jsx(
1151
+ "div",
1152
+ {
1153
+ ref,
1154
+ role: "radiogroup",
1155
+ "aria-orientation": orientation,
1156
+ className: cn(
1157
+ "gl-radio-group",
1158
+ `gl-radio-group--${orientation}`,
1159
+ className
1160
+ ),
1161
+ ...rest,
1162
+ children: /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: ctx, children })
1163
+ }
1164
+ );
1165
+ }
1166
+ );
1167
+ var Radio = forwardRef(function Radio2({ value, label, disabled = false, className, ...rest }, ref) {
1168
+ const ctx = useRadioGroupContext("Radio");
1169
+ const checked = ctx.value === value;
1170
+ return /* @__PURE__ */ jsxs(
1171
+ "label",
1172
+ {
1173
+ className: cn(
1174
+ "gl-radio",
1175
+ checked && "gl-radio--checked",
1176
+ disabled && "gl-radio--disabled",
1177
+ className
1178
+ ),
1179
+ children: [
1180
+ /* @__PURE__ */ jsx(
1181
+ "input",
1182
+ {
1183
+ ref,
1184
+ type: "radio",
1185
+ className: "gl-sr-only gl-radio__input",
1186
+ name: ctx.name,
1187
+ value,
1188
+ checked,
1189
+ disabled,
1190
+ onChange: () => ctx.setValue(value),
1191
+ ...rest
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ jsx("span", { className: "gl-radio__dot", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "gl-radio__mark" }) }),
1195
+ label != null ? /* @__PURE__ */ jsx("span", { className: "gl-radio__label", children: label }) : null
1196
+ ]
1197
+ }
1198
+ );
1199
+ });
1200
+ var Select = forwardRef(function Select2({
1201
+ options,
1202
+ placeholder,
1203
+ selectSize = "md",
1204
+ error = false,
1205
+ fullWidth = false,
1206
+ className,
1207
+ children,
1208
+ disabled,
1209
+ value,
1210
+ defaultValue,
1211
+ "aria-invalid": ariaInvalid,
1212
+ ...rest
1213
+ }, ref) {
1214
+ const isControlled = value !== void 0;
1215
+ const resolvedDefaultValue = !isControlled && defaultValue === void 0 && placeholder != null ? "" : defaultValue;
1216
+ const selectionProps = isControlled ? { value } : { defaultValue: resolvedDefaultValue };
1217
+ return /* @__PURE__ */ jsxs(
1218
+ "span",
1219
+ {
1220
+ className: cn(
1221
+ "gl-select",
1222
+ `gl-select--${selectSize}`,
1223
+ error && "gl-select--error",
1224
+ fullWidth && "gl-select--block",
1225
+ disabled && "gl-select--disabled",
1226
+ className
1227
+ ),
1228
+ children: [
1229
+ /* @__PURE__ */ jsxs(
1230
+ "select",
1231
+ {
1232
+ ref,
1233
+ className: "gl-select__field",
1234
+ disabled,
1235
+ "aria-invalid": ariaInvalid ?? (error || void 0),
1236
+ ...selectionProps,
1237
+ ...rest,
1238
+ children: [
1239
+ placeholder != null ? /* @__PURE__ */ jsx("option", { value: "", disabled: true, hidden: true, children: placeholder }) : null,
1240
+ options ? options.map((opt) => /* @__PURE__ */ jsx(
1241
+ "option",
1242
+ {
1243
+ value: opt.value,
1244
+ disabled: opt.disabled,
1245
+ children: opt.label
1246
+ },
1247
+ String(opt.value)
1248
+ )) : children
1249
+ ]
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsx("span", { className: "gl-select__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1253
+ "svg",
1254
+ {
1255
+ viewBox: "0 0 24 24",
1256
+ width: "1em",
1257
+ height: "1em",
1258
+ fill: "none",
1259
+ stroke: "currentColor",
1260
+ strokeWidth: "2",
1261
+ strokeLinecap: "round",
1262
+ strokeLinejoin: "round",
1263
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
1264
+ }
1265
+ ) })
1266
+ ]
1267
+ }
1268
+ );
1269
+ });
1270
+ var Slider = forwardRef(function Slider2({
1271
+ min = 0,
1272
+ max = 100,
1273
+ step = 1,
1274
+ value,
1275
+ defaultValue,
1276
+ onChange,
1277
+ showValue = false,
1278
+ className,
1279
+ style,
1280
+ disabled,
1281
+ ...rest
1282
+ }, ref) {
1283
+ const [current, setValue] = useControllableState({
1284
+ value,
1285
+ defaultValue: defaultValue ?? min,
1286
+ onChange
1287
+ });
1288
+ const range = max - min;
1289
+ const clamped = Math.min(max, Math.max(min, current));
1290
+ const fillPct = range > 0 ? (clamped - min) / range * 100 : 0;
1291
+ const handleChange = (event) => {
1292
+ const next = event.target.valueAsNumber;
1293
+ setValue(Number.isNaN(next) ? min : next);
1294
+ };
1295
+ const wrapperStyle = {
1296
+ ...style,
1297
+ "--gl-slider-fill": `${fillPct}%`
1298
+ };
1299
+ return /* @__PURE__ */ jsxs(
1300
+ "span",
1301
+ {
1302
+ className: cn(
1303
+ "gl-slider",
1304
+ showValue && "gl-slider--show-value",
1305
+ disabled && "gl-slider--disabled",
1306
+ className
1307
+ ),
1308
+ style: wrapperStyle,
1309
+ children: [
1310
+ /* @__PURE__ */ jsx(
1311
+ "input",
1312
+ {
1313
+ ref,
1314
+ type: "range",
1315
+ className: "gl-slider__input",
1316
+ min,
1317
+ max,
1318
+ step,
1319
+ value: clamped,
1320
+ disabled,
1321
+ onChange: handleChange,
1322
+ ...rest
1323
+ }
1324
+ ),
1325
+ showValue ? /* @__PURE__ */ jsx("span", { className: "gl-slider__value", "aria-hidden": "true", children: current }) : null
1326
+ ]
1327
+ }
1328
+ );
1329
+ });
1330
+ var Spinner = forwardRef(function Spinner2({ size = "md", label = "Loading", thickness, className, style, ...rest }, ref) {
1331
+ const styleWithThickness = thickness != null ? { ...style, "--gl-spinner-bw": `${thickness}px` } : style;
1332
+ return /* @__PURE__ */ jsxs(
1333
+ "span",
1334
+ {
1335
+ ref,
1336
+ role: "status",
1337
+ "aria-live": "polite",
1338
+ className: cn("gl-spinner", `gl-spinner--${size}`, className),
1339
+ style: styleWithThickness,
1340
+ ...rest,
1341
+ children: [
1342
+ /* @__PURE__ */ jsx("span", { className: "gl-spinner__ring", "aria-hidden": "true" }),
1343
+ /* @__PURE__ */ jsx("span", { className: "gl-sr-only", children: label })
1344
+ ]
1345
+ }
1346
+ );
1347
+ });
1348
+ var Switch = forwardRef(function Switch2({
1349
+ checked,
1350
+ defaultChecked = false,
1351
+ onChange,
1352
+ label,
1353
+ switchSize = "md",
1354
+ disabled = false,
1355
+ className,
1356
+ id,
1357
+ ...rest
1358
+ }, ref) {
1359
+ const reactId = useId();
1360
+ const inputId = id ?? reactId;
1361
+ const [isChecked, setChecked] = useControllableState({
1362
+ value: checked,
1363
+ defaultValue: defaultChecked,
1364
+ onChange
1365
+ });
1366
+ const handleChange = (event) => {
1367
+ setChecked(event.target.checked);
1368
+ };
1369
+ return /* @__PURE__ */ jsxs(
1370
+ "label",
1371
+ {
1372
+ className: cn(
1373
+ "gl-switch",
1374
+ `gl-switch--${switchSize}`,
1375
+ isChecked && "gl-switch--checked",
1376
+ disabled && "gl-switch--disabled",
1377
+ className
1378
+ ),
1379
+ htmlFor: inputId,
1380
+ children: [
1381
+ /* @__PURE__ */ jsx(
1382
+ "input",
1383
+ {
1384
+ ref,
1385
+ id: inputId,
1386
+ type: "checkbox",
1387
+ role: "switch",
1388
+ className: "gl-switch__input gl-sr-only",
1389
+ checked: isChecked,
1390
+ disabled,
1391
+ onChange: handleChange,
1392
+ ...rest
1393
+ }
1394
+ ),
1395
+ /* @__PURE__ */ jsx("span", { className: "gl-switch__track", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "gl-switch__thumb" }) }),
1396
+ label != null ? /* @__PURE__ */ jsx("span", { className: "gl-switch__label", children: label }) : null
1397
+ ]
1398
+ }
1399
+ );
1400
+ });
1401
+ var TabsContext = createContext(null);
1402
+ function useTabsContext(component) {
1403
+ const ctx = useContext(TabsContext);
1404
+ if (!ctx) {
1405
+ throw new Error(`<${component}> must be rendered inside <Tabs>.`);
1406
+ }
1407
+ return ctx;
1408
+ }
1409
+ var idSafe = (value) => value.replace(/\s+/g, "-");
1410
+ var Tabs = forwardRef(function Tabs2({ value, defaultValue, onChange, className, children, ...rest }, ref) {
1411
+ const baseId = useId();
1412
+ const [activeValue, setControllableValue] = useControllableState({
1413
+ value,
1414
+ defaultValue,
1415
+ onChange: onChange ? (next) => {
1416
+ if (next !== void 0) onChange(next);
1417
+ } : void 0
1418
+ });
1419
+ const orderRef = useRef([]);
1420
+ const disabledRef = useRef(/* @__PURE__ */ new Map());
1421
+ const [, bump] = useReducer((n) => n + 1, 0);
1422
+ const registerTab = useCallback((v) => {
1423
+ if (orderRef.current.includes(v)) return;
1424
+ orderRef.current = [...orderRef.current, v];
1425
+ if (!disabledRef.current.has(v)) disabledRef.current.set(v, false);
1426
+ bump();
1427
+ }, []);
1428
+ const unregisterTab = useCallback((v) => {
1429
+ if (!orderRef.current.includes(v)) return;
1430
+ orderRef.current = orderRef.current.filter((x) => x !== v);
1431
+ disabledRef.current.delete(v);
1432
+ bump();
1433
+ }, []);
1434
+ const setTabDisabled = useCallback((v, disabled) => {
1435
+ if (disabledRef.current.get(v) === disabled) return;
1436
+ disabledRef.current.set(v, disabled);
1437
+ bump();
1438
+ }, []);
1439
+ const activeValueRef = useRef(activeValue);
1440
+ activeValueRef.current = activeValue;
1441
+ const setActiveValue = useCallback(
1442
+ (v) => {
1443
+ if (activeValueRef.current === v) return;
1444
+ setControllableValue(v);
1445
+ },
1446
+ [setControllableValue]
1447
+ );
1448
+ const getTabId = useCallback(
1449
+ (v) => `${baseId}-tab-${idSafe(v)}`,
1450
+ [baseId]
1451
+ );
1452
+ const getPanelId = useCallback(
1453
+ (v) => `${baseId}-panel-${idSafe(v)}`,
1454
+ [baseId]
1455
+ );
1456
+ const order = orderRef.current;
1457
+ const disabledMap = disabledRef.current;
1458
+ const activeIsFocusable = activeValue !== void 0 && order.includes(activeValue) && !disabledMap.get(activeValue);
1459
+ const focusableValue = activeIsFocusable ? activeValue : order.find((v) => !disabledMap.get(v));
1460
+ const ctx = useMemo(
1461
+ () => ({
1462
+ baseId,
1463
+ activeValue,
1464
+ setActiveValue,
1465
+ focusableValue,
1466
+ registerTab,
1467
+ unregisterTab,
1468
+ setTabDisabled,
1469
+ getTabId,
1470
+ getPanelId
1471
+ }),
1472
+ [
1473
+ baseId,
1474
+ activeValue,
1475
+ setActiveValue,
1476
+ focusableValue,
1477
+ registerTab,
1478
+ unregisterTab,
1479
+ setTabDisabled,
1480
+ getTabId,
1481
+ getPanelId
1482
+ ]
1483
+ );
1484
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { ref, className: cn("gl-tabs", className), ...rest, children }) });
1485
+ });
1486
+ var TabList = forwardRef(function TabList2({ className, children, onKeyDown, ...rest }, ref) {
1487
+ const { setActiveValue } = useTabsContext("TabList");
1488
+ const listRef = useRef(null);
1489
+ const setRefs = useCallback(
1490
+ (node) => {
1491
+ listRef.current = node;
1492
+ if (typeof ref === "function") ref(node);
1493
+ else if (ref) ref.current = node;
1494
+ },
1495
+ [ref]
1496
+ );
1497
+ const handleKeyDown = (event) => {
1498
+ onKeyDown?.(event);
1499
+ if (event.defaultPrevented) return;
1500
+ const nav = ["ArrowRight", "ArrowLeft", "Home", "End"];
1501
+ if (!nav.includes(event.key)) return;
1502
+ const list = listRef.current;
1503
+ if (!list) return;
1504
+ const tabs = Array.from(
1505
+ list.querySelectorAll('[role="tab"]:not([disabled])')
1506
+ );
1507
+ if (tabs.length === 0) return;
1508
+ const currentIndex = tabs.findIndex((t) => t === document.activeElement);
1509
+ let nextIndex;
1510
+ switch (event.key) {
1511
+ case "Home":
1512
+ nextIndex = 0;
1513
+ break;
1514
+ case "End":
1515
+ nextIndex = tabs.length - 1;
1516
+ break;
1517
+ case "ArrowRight":
1518
+ nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % tabs.length;
1519
+ break;
1520
+ default:
1521
+ nextIndex = currentIndex < 0 ? tabs.length - 1 : (currentIndex - 1 + tabs.length) % tabs.length;
1522
+ }
1523
+ const next = tabs[nextIndex];
1524
+ event.preventDefault();
1525
+ next.focus();
1526
+ const nextValue = next.getAttribute("data-value");
1527
+ if (nextValue !== null) setActiveValue(nextValue);
1528
+ };
1529
+ return /* @__PURE__ */ jsx(
1530
+ "div",
1531
+ {
1532
+ ref: setRefs,
1533
+ role: "tablist",
1534
+ "aria-orientation": "horizontal",
1535
+ className: cn("gl-tabs__list", className),
1536
+ onKeyDown: handleKeyDown,
1537
+ ...rest,
1538
+ children
1539
+ }
1540
+ );
1541
+ });
1542
+ var Tab = forwardRef(function Tab2({ value, disabled = false, className, children, onClick, type = "button", ...rest }, ref) {
1543
+ const ctx = useTabsContext("Tab");
1544
+ const { registerTab, unregisterTab, setTabDisabled, setActiveValue } = ctx;
1545
+ const selected = ctx.activeValue === value;
1546
+ const focusable = ctx.focusableValue === value;
1547
+ useIsomorphicLayoutEffect(() => {
1548
+ registerTab(value);
1549
+ return () => unregisterTab(value);
1550
+ }, [registerTab, unregisterTab, value]);
1551
+ useIsomorphicLayoutEffect(() => {
1552
+ setTabDisabled(value, disabled);
1553
+ }, [setTabDisabled, value, disabled]);
1554
+ const handleClick = (event) => {
1555
+ onClick?.(event);
1556
+ if (event.defaultPrevented) return;
1557
+ setActiveValue(value);
1558
+ };
1559
+ return /* @__PURE__ */ jsx(
1560
+ "button",
1561
+ {
1562
+ ref,
1563
+ type,
1564
+ role: "tab",
1565
+ id: ctx.getTabId(value),
1566
+ "data-value": value,
1567
+ "aria-selected": selected,
1568
+ "aria-controls": ctx.getPanelId(value),
1569
+ tabIndex: focusable ? 0 : -1,
1570
+ disabled,
1571
+ className: cn(
1572
+ "gl-tabs__tab",
1573
+ selected && "gl-tabs__tab--active",
1574
+ "gl-focusable",
1575
+ className
1576
+ ),
1577
+ onClick: handleClick,
1578
+ ...rest,
1579
+ children: /* @__PURE__ */ jsx("span", { className: "gl-tabs__tab-label", children })
1580
+ }
1581
+ );
1582
+ });
1583
+ var TabPanel = forwardRef(function TabPanel2({ value, className, children, ...rest }, ref) {
1584
+ const ctx = useTabsContext("TabPanel");
1585
+ const selected = ctx.activeValue === value;
1586
+ return /* @__PURE__ */ jsx(
1587
+ "div",
1588
+ {
1589
+ ref,
1590
+ role: "tabpanel",
1591
+ id: ctx.getPanelId(value),
1592
+ "aria-labelledby": ctx.getTabId(value),
1593
+ hidden: !selected,
1594
+ tabIndex: 0,
1595
+ className: cn("gl-tabs__panel", className),
1596
+ ...rest,
1597
+ children
1598
+ }
1599
+ );
1600
+ });
1601
+ var Textarea = forwardRef(
1602
+ function Textarea2({
1603
+ variant = "subtle",
1604
+ error = false,
1605
+ fullWidth = false,
1606
+ autoResize = false,
1607
+ className,
1608
+ onChange,
1609
+ value,
1610
+ defaultValue,
1611
+ rows,
1612
+ disabled,
1613
+ ...rest
1614
+ }, ref) {
1615
+ const innerRef = useRef(null);
1616
+ const setRef = useCallback(
1617
+ (node) => {
1618
+ innerRef.current = node;
1619
+ if (typeof ref === "function") {
1620
+ ref(node);
1621
+ } else if (ref) {
1622
+ ref.current = node;
1623
+ }
1624
+ },
1625
+ [ref]
1626
+ );
1627
+ const resize = useCallback(() => {
1628
+ const el = innerRef.current;
1629
+ if (!el) return;
1630
+ if (!autoResize) {
1631
+ el.style.height = "";
1632
+ return;
1633
+ }
1634
+ el.style.height = "auto";
1635
+ el.style.height = `${el.scrollHeight}px`;
1636
+ }, [autoResize]);
1637
+ useIsomorphicLayoutEffect(() => {
1638
+ resize();
1639
+ }, [resize, value, defaultValue, rows]);
1640
+ const handleChange = useCallback(
1641
+ (event) => {
1642
+ if (autoResize) resize();
1643
+ onChange?.(event);
1644
+ },
1645
+ [autoResize, resize, onChange]
1646
+ );
1647
+ return /* @__PURE__ */ jsx(
1648
+ "div",
1649
+ {
1650
+ className: cn(
1651
+ "gl-textarea",
1652
+ `gl-textarea--${variant}`,
1653
+ fullWidth && "gl-textarea--full",
1654
+ error && "gl-textarea--error",
1655
+ autoResize && "gl-textarea--auto",
1656
+ disabled && "gl-textarea--disabled",
1657
+ className
1658
+ ),
1659
+ children: /* @__PURE__ */ jsx(
1660
+ "textarea",
1661
+ {
1662
+ ref: setRef,
1663
+ className: "gl-textarea__field",
1664
+ value,
1665
+ defaultValue,
1666
+ rows,
1667
+ disabled,
1668
+ "aria-invalid": error || void 0,
1669
+ onChange: handleChange,
1670
+ ...rest
1671
+ }
1672
+ )
1673
+ }
1674
+ );
1675
+ }
1676
+ );
1677
+ var DEFAULT_DURATION = 4e3;
1678
+ var ToastContext = createContext(null);
1679
+ function ToastItem({ toast, onDismiss }) {
1680
+ const { id, title, description, variant, duration } = toast;
1681
+ const autoDismiss = duration > 0 && Number.isFinite(duration);
1682
+ const timerRef = useRef(null);
1683
+ const remainingRef = useRef(duration);
1684
+ const startedRef = useRef(0);
1685
+ const clearTimer = useCallback(() => {
1686
+ if (timerRef.current !== null) {
1687
+ window.clearTimeout(timerRef.current);
1688
+ timerRef.current = null;
1689
+ }
1690
+ }, []);
1691
+ const resumeTimer = useCallback(() => {
1692
+ if (!autoDismiss || timerRef.current !== null) return;
1693
+ startedRef.current = performance.now();
1694
+ timerRef.current = window.setTimeout(() => {
1695
+ timerRef.current = null;
1696
+ onDismiss(id);
1697
+ }, remainingRef.current);
1698
+ }, [autoDismiss, id, onDismiss]);
1699
+ const pauseTimer = useCallback(() => {
1700
+ if (timerRef.current === null) return;
1701
+ clearTimer();
1702
+ remainingRef.current = Math.max(
1703
+ 0,
1704
+ remainingRef.current - (performance.now() - startedRef.current)
1705
+ );
1706
+ }, [clearTimer]);
1707
+ useEffect(() => {
1708
+ remainingRef.current = duration;
1709
+ resumeTimer();
1710
+ return clearTimer;
1711
+ }, [duration, resumeTimer, clearTimer]);
1712
+ const handleKeyDown = (event) => {
1713
+ if (event.key === "Escape") {
1714
+ event.stopPropagation();
1715
+ onDismiss(id);
1716
+ }
1717
+ };
1718
+ return /* @__PURE__ */ jsxs(
1719
+ "div",
1720
+ {
1721
+ className: cn("gl-toast", `gl-toast--${variant}`),
1722
+ role: "status",
1723
+ "aria-atomic": "true",
1724
+ onMouseEnter: pauseTimer,
1725
+ onMouseLeave: resumeTimer,
1726
+ onFocus: pauseTimer,
1727
+ onBlur: resumeTimer,
1728
+ onKeyDown: handleKeyDown,
1729
+ children: [
1730
+ /* @__PURE__ */ jsx("span", { className: "gl-toast__accent", "aria-hidden": "true" }),
1731
+ /* @__PURE__ */ jsxs("div", { className: "gl-toast__body", children: [
1732
+ title != null ? /* @__PURE__ */ jsx("div", { className: "gl-toast__title", children: title }) : null,
1733
+ description != null ? /* @__PURE__ */ jsx("div", { className: "gl-toast__description", children: description }) : null
1734
+ ] }),
1735
+ /* @__PURE__ */ jsx(
1736
+ "button",
1737
+ {
1738
+ type: "button",
1739
+ className: "gl-toast__close gl-focusable",
1740
+ "aria-label": "Dismiss notification",
1741
+ onClick: () => onDismiss(id),
1742
+ children: /* @__PURE__ */ jsx(
1743
+ "svg",
1744
+ {
1745
+ className: "gl-toast__close-icon",
1746
+ viewBox: "0 0 24 24",
1747
+ fill: "none",
1748
+ "aria-hidden": "true",
1749
+ children: /* @__PURE__ */ jsx(
1750
+ "path",
1751
+ {
1752
+ d: "M6 6l12 12M18 6L6 18",
1753
+ stroke: "currentColor",
1754
+ strokeWidth: "2",
1755
+ strokeLinecap: "round"
1756
+ }
1757
+ )
1758
+ }
1759
+ )
1760
+ }
1761
+ )
1762
+ ]
1763
+ }
1764
+ );
1765
+ }
1766
+ function ToastProvider({
1767
+ children,
1768
+ position = "bottom-right"
1769
+ }) {
1770
+ const [toasts, setToasts] = useState([]);
1771
+ const baseId = useId();
1772
+ const counterRef = useRef(0);
1773
+ const dismiss = useCallback((id) => {
1774
+ setToasts((prev) => prev.filter((item) => item.id !== id));
1775
+ }, []);
1776
+ const toast = useCallback(
1777
+ (options) => {
1778
+ const id = `${baseId}${counterRef.current++}`;
1779
+ setToasts((prev) => [
1780
+ ...prev,
1781
+ {
1782
+ id,
1783
+ title: options.title,
1784
+ description: options.description,
1785
+ variant: options.variant ?? "glass",
1786
+ duration: options.duration ?? DEFAULT_DURATION
1787
+ }
1788
+ ]);
1789
+ return id;
1790
+ },
1791
+ [baseId]
1792
+ );
1793
+ const success = useCallback(
1794
+ (title, options) => toast({ ...options, title, variant: "success" }),
1795
+ [toast]
1796
+ );
1797
+ const error = useCallback(
1798
+ (title, options) => toast({ ...options, title, variant: "danger" }),
1799
+ [toast]
1800
+ );
1801
+ const info = useCallback(
1802
+ (title, options) => toast({ ...options, title, variant: "info" }),
1803
+ [toast]
1804
+ );
1805
+ const warning = useCallback(
1806
+ (title, options) => toast({ ...options, title, variant: "warning" }),
1807
+ [toast]
1808
+ );
1809
+ const value = useMemo(
1810
+ () => ({ toast, dismiss, success, error, info, warning }),
1811
+ [toast, dismiss, success, error, info, warning]
1812
+ );
1813
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value, children: [
1814
+ children,
1815
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
1816
+ "div",
1817
+ {
1818
+ className: cn("gl-toast-viewport", `gl-toast-viewport--${position}`),
1819
+ role: "region",
1820
+ "aria-label": "Notifications",
1821
+ children: toasts.map((item) => /* @__PURE__ */ jsx(ToastItem, { toast: item, onDismiss: dismiss }, item.id))
1822
+ }
1823
+ ) })
1824
+ ] });
1825
+ }
1826
+ function useToast() {
1827
+ const ctx = useContext(ToastContext);
1828
+ if (ctx === null) {
1829
+ throw new Error(
1830
+ "useToast must be used within a <ToastProvider>. Wrap your app (or the subtree that shows toasts) in <ToastProvider>."
1831
+ );
1832
+ }
1833
+ return ctx;
1834
+ }
1835
+ var Tooltip = forwardRef(function Tooltip2({
1836
+ content,
1837
+ children,
1838
+ placement = "top",
1839
+ delay = 200,
1840
+ disabled = false,
1841
+ className,
1842
+ onMouseEnter,
1843
+ onMouseLeave,
1844
+ onFocus,
1845
+ onBlur,
1846
+ onKeyDown,
1847
+ ...rest
1848
+ }, ref) {
1849
+ const [visible, setVisible] = useState(false);
1850
+ const timeoutRef = useRef(null);
1851
+ const tooltipId = useId();
1852
+ const clearTimer = useCallback(() => {
1853
+ if (timeoutRef.current !== null) {
1854
+ clearTimeout(timeoutRef.current);
1855
+ timeoutRef.current = null;
1856
+ }
1857
+ }, []);
1858
+ const show = useCallback(() => {
1859
+ if (disabled) return;
1860
+ clearTimer();
1861
+ timeoutRef.current = setTimeout(() => {
1862
+ timeoutRef.current = null;
1863
+ setVisible(true);
1864
+ }, delay);
1865
+ }, [disabled, delay, clearTimer]);
1866
+ const hide = useCallback(() => {
1867
+ clearTimer();
1868
+ setVisible(false);
1869
+ }, [clearTimer]);
1870
+ useEffect(() => {
1871
+ if (disabled) {
1872
+ clearTimer();
1873
+ setVisible(false);
1874
+ }
1875
+ }, [disabled, clearTimer]);
1876
+ useEffect(() => clearTimer, [clearTimer]);
1877
+ const handleMouseEnter = (event) => {
1878
+ show();
1879
+ onMouseEnter?.(event);
1880
+ };
1881
+ const handleMouseLeave = (event) => {
1882
+ hide();
1883
+ onMouseLeave?.(event);
1884
+ };
1885
+ const handleFocus = (event) => {
1886
+ show();
1887
+ onFocus?.(event);
1888
+ };
1889
+ const handleBlur = (event) => {
1890
+ hide();
1891
+ onBlur?.(event);
1892
+ };
1893
+ const handleKeyDown = (event) => {
1894
+ if (event.key === "Escape") {
1895
+ hide();
1896
+ }
1897
+ onKeyDown?.(event);
1898
+ };
1899
+ const open = visible && !disabled;
1900
+ return /* @__PURE__ */ jsxs(
1901
+ "span",
1902
+ {
1903
+ ref,
1904
+ className: cn("gl-tooltip-wrap", className),
1905
+ onMouseEnter: handleMouseEnter,
1906
+ onMouseLeave: handleMouseLeave,
1907
+ onFocus: handleFocus,
1908
+ onBlur: handleBlur,
1909
+ onKeyDown: handleKeyDown,
1910
+ "aria-describedby": open ? tooltipId : void 0,
1911
+ ...rest,
1912
+ children: [
1913
+ children,
1914
+ open ? /* @__PURE__ */ jsx(
1915
+ "span",
1916
+ {
1917
+ id: tooltipId,
1918
+ role: "tooltip",
1919
+ className: cn("gl-tooltip", `gl-tooltip--${placement}`),
1920
+ children: content
1921
+ }
1922
+ ) : null
1923
+ ]
1924
+ }
1925
+ );
1926
+ });
1927
+
1928
+ export { Accordion, AccordionItem, Avatar, Badge, Button, Card, CardBody, CardFooter, CardHeader, Checkbox, Drawer, DrawerBody, DrawerFooter, DrawerHeader, Dropdown, DropdownItem, DropdownLabel, DropdownMenu, DropdownSeparator, DropdownTrigger, Input, Modal, ModalBody, ModalFooter, ModalHeader, Portal, Progress, Radio, RadioGroup, RadioGroupContext, Select, Slider, Spinner, Switch, Tab, TabList, TabPanel, Tabs, Textarea, ToastProvider, Tooltip, cn, useClickOutside, useControllableState, useDisclosure, useIsomorphicLayoutEffect, useScrollLock, useToast };
1929
+ //# sourceMappingURL=index.js.map
1930
+ //# sourceMappingURL=index.js.map