@bigtablet/design-system 3.2.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import './index.css';
3
- import * as React20 from 'react';
4
- import { createContext, useRef, useState, useLayoutEffect, useId, useEffect, useContext, useCallback, useMemo, Fragment } from 'react';
3
+ import * as React19 from 'react';
4
+ import { createContext, useRef, useState, useId, useEffect, useContext, useCallback, useMemo, Fragment, useLayoutEffect } from 'react';
5
5
  import { useSpring, animated } from '@react-spring/web';
6
- import { ChevronDown, Globe, ChevronRight, ChevronLeft, Check, Image, X, TriangleAlert, XCircle, AlertTriangle, CheckCircle2, Info, Bell } from 'lucide-react';
6
+ import { ChevronDown, ChevronRight, Globe, ChevronLeft, XCircle, AlertTriangle, CheckCircle2, Info, Search, Check, X, Image, ArrowUp, ArrowDown, ArrowUpDown, Bell, TriangleAlert } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
8
8
  import { createPortal } from 'react-dom';
9
9
 
@@ -38,8 +38,8 @@ var FOCUSABLE_SELECTORS = [
38
38
  '[tabindex]:not([tabindex="-1"])'
39
39
  ].join(", ");
40
40
  function useFocusTrap(containerRef, isActive) {
41
- const previousActiveElement = React20.useRef(null);
42
- React20.useEffect(() => {
41
+ const previousActiveElement = React19.useRef(null);
42
+ React19.useEffect(() => {
43
43
  if (!isActive) return;
44
44
  const container = containerRef.current;
45
45
  if (!container) return;
@@ -116,14 +116,14 @@ function getServerSnapshot() {
116
116
  return false;
117
117
  }
118
118
  function useReducedMotion() {
119
- return React20.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
119
+ return React19.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
120
120
  }
121
121
  var useSafeLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
122
122
  function useSpringHover({
123
123
  scale = 1.02,
124
124
  lift = -2
125
125
  } = {}) {
126
- const [hovered, setHovered] = React20.useState(false);
126
+ const [hovered, setHovered] = React19.useState(false);
127
127
  const reduced = useReducedMotion();
128
128
  const style = useSpring({
129
129
  transform: hovered ? `translateY(${lift}px) scale(${scale})` : "translateY(0px) scale(1)",
@@ -167,23 +167,33 @@ function useSpringPresence({
167
167
  }
168
168
  });
169
169
  }
170
+
171
+ // src/styles/icon/index.ts
172
+ var iconSize = {
173
+ xs: 14,
174
+ sm: 16,
175
+ md: 18,
176
+ lg: 20,
177
+ xl: 32
178
+ };
170
179
  var Accordion = ({
171
180
  items,
172
181
  multiple = false,
173
182
  defaultOpenKeys = [],
174
183
  openKeys: controlledKeys,
184
+ onValueChange,
175
185
  onChange,
176
186
  className,
177
187
  ...props
178
188
  }) => {
179
189
  const isControlled = controlledKeys !== void 0;
180
- const [internalKeys, setInternalKeys] = React20.useState(defaultOpenKeys);
190
+ const [internalKeys, setInternalKeys] = React19.useState(defaultOpenKeys);
181
191
  const open = isControlled ? controlledKeys ?? [] : internalKeys;
182
192
  const toggle = (key) => {
183
193
  const isOpen = open.includes(key);
184
194
  const next = isOpen ? open.filter((k) => k !== key) : multiple ? [...open, key] : [key];
185
195
  if (!isControlled) setInternalKeys(next);
186
- onChange?.(next);
196
+ (onValueChange ?? onChange)?.(next);
187
197
  };
188
198
  return /* @__PURE__ */ jsx("div", { className: cn("accordion", className), ...props, children: items.map((item) => {
189
199
  const isOpen = open.includes(item.key);
@@ -205,7 +215,7 @@ var Accordion = ({
205
215
  /* @__PURE__ */ jsx(
206
216
  ChevronDown,
207
217
  {
208
- size: 20,
218
+ size: iconSize.lg,
209
219
  className: cn("accordion_chevron", isOpen && "accordion_chevron_open"),
210
220
  "aria-hidden": "true"
211
221
  }
@@ -220,6 +230,7 @@ var Accordion = ({
220
230
  role: "region",
221
231
  "aria-labelledby": headerId,
222
232
  "aria-hidden": !isOpen,
233
+ inert: !isOpen,
223
234
  className: cn("accordion_panel", isOpen && "accordion_panel_open"),
224
235
  children: /* @__PURE__ */ jsx("div", { className: "accordion_panel_wrap", children: /* @__PURE__ */ jsx("div", { className: "accordion_content", children: item.content }) })
225
236
  }
@@ -243,7 +254,7 @@ var Avatar = ({
243
254
  style,
244
255
  ...props
245
256
  }) => {
246
- const [imgFailed, setImgFailed] = React20.useState(false);
257
+ const [imgFailed, setImgFailed] = React19.useState(false);
247
258
  const showImage = src && !imgFailed;
248
259
  const initials = getInitials(name);
249
260
  return /* @__PURE__ */ jsx(
@@ -392,7 +403,7 @@ var Breadcrumb = ({
392
403
  className,
393
404
  ...props
394
405
  }) => {
395
- const sep = separator ?? /* @__PURE__ */ jsx(ChevronRight, { size: 14, "aria-hidden": "true" });
406
+ const sep = separator ?? /* @__PURE__ */ jsx(ChevronRight, { size: iconSize.xs, "aria-hidden": "true" });
396
407
  return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: cn("breadcrumb", className), ...props, children: /* @__PURE__ */ jsx("ol", { className: "breadcrumb_list", children: items.map((item, idx) => {
397
408
  const isLast = idx === items.length - 1;
398
409
  return (
@@ -461,11 +472,12 @@ var ErrorState = ({
461
472
  );
462
473
  };
463
474
  var Menu = ({ items, trigger, align = "start" }) => {
464
- const [open, setOpen] = React20.useState(false);
465
- const wrapperRef = React20.useRef(null);
466
- const menuId = React20.useId();
475
+ const [open, setOpen] = React19.useState(false);
476
+ const wrapperRef = React19.useRef(null);
477
+ const itemRefs = React19.useRef([]);
478
+ const menuId = React19.useId();
467
479
  const style = useSpringPresence({ visible: open, from: "translateY(-4px)" });
468
- React20.useEffect(() => {
480
+ React19.useEffect(() => {
469
481
  if (!open) return;
470
482
  const handleClick = (e) => {
471
483
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -480,7 +492,53 @@ var Menu = ({ items, trigger, align = "start" }) => {
480
492
  document.removeEventListener("keydown", handleEsc);
481
493
  };
482
494
  }, [open]);
483
- const triggerWithProps = React20.cloneElement(
495
+ React19.useEffect(() => {
496
+ const active = document.activeElement;
497
+ if (!open || active && itemRefs.current.includes(active)) return;
498
+ const first = items.findIndex((it) => !it.disabled);
499
+ if (first >= 0) itemRefs.current[first]?.focus();
500
+ }, [open, items]);
501
+ const moveFocus = (dir) => {
502
+ const enabled = items.flatMap((it, i) => it.disabled ? [] : [i]);
503
+ if (enabled.length === 0) return;
504
+ if (dir === "first") return void itemRefs.current[enabled[0]]?.focus();
505
+ if (dir === "last") return void itemRefs.current[enabled[enabled.length - 1]]?.focus();
506
+ const pos = enabled.findIndex((i) => itemRefs.current[i] === document.activeElement);
507
+ const next = pos < 0 ? dir === 1 ? 0 : enabled.length - 1 : (pos + dir + enabled.length) % enabled.length;
508
+ itemRefs.current[enabled[next]]?.focus();
509
+ };
510
+ const handleMenuKeyDown = (e) => {
511
+ if (["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(e.key)) {
512
+ e.stopPropagation();
513
+ }
514
+ switch (e.key) {
515
+ case "ArrowDown":
516
+ e.preventDefault();
517
+ moveFocus(1);
518
+ break;
519
+ case "ArrowUp":
520
+ e.preventDefault();
521
+ moveFocus(-1);
522
+ break;
523
+ case "Home":
524
+ e.preventDefault();
525
+ moveFocus("first");
526
+ break;
527
+ case "End":
528
+ e.preventDefault();
529
+ moveFocus("last");
530
+ break;
531
+ case "Escape":
532
+ e.preventDefault();
533
+ setOpen(false);
534
+ wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
535
+ break;
536
+ case "Tab":
537
+ setOpen(false);
538
+ break;
539
+ }
540
+ };
541
+ const triggerWithProps = React19.cloneElement(
484
542
  trigger,
485
543
  {
486
544
  onClick: () => setOpen((o) => !o),
@@ -498,11 +556,16 @@ var Menu = ({ items, trigger, align = "start" }) => {
498
556
  role: "menu",
499
557
  style,
500
558
  className: cn("menu", `menu_align_${align}`),
501
- children: items.map((item) => /* @__PURE__ */ jsxs(
559
+ onKeyDown: handleMenuKeyDown,
560
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(
502
561
  "button",
503
562
  {
563
+ ref: (el) => {
564
+ itemRefs.current[index] = el;
565
+ },
504
566
  type: "button",
505
567
  role: "menuitem",
568
+ tabIndex: -1,
506
569
  disabled: item.disabled,
507
570
  className: cn(
508
571
  "menu_item",
@@ -543,7 +606,7 @@ var NavBar = ({
543
606
  const linksRef = useRef(null);
544
607
  const [indicator, setIndicator] = useState(null);
545
608
  const [hasMounted, setHasMounted] = useState(false);
546
- useLayoutEffect(() => {
609
+ useSafeLayoutEffect(() => {
547
610
  const links = linksRef.current;
548
611
  if (!links) return;
549
612
  const update = () => {
@@ -614,6 +677,7 @@ var NavBar = ({
614
677
  var LocaleSwitcher = ({ locale }) => {
615
678
  const [open, setOpen] = useState(false);
616
679
  const wrapperRef = useRef(null);
680
+ const itemRefs = useRef([]);
617
681
  const menuId = useId();
618
682
  const currentOption = locale.options.find((o) => o.value === locale.current);
619
683
  useEffect(() => {
@@ -631,8 +695,53 @@ var LocaleSwitcher = ({ locale }) => {
631
695
  document.removeEventListener("keydown", escHandler);
632
696
  };
633
697
  }, [open]);
698
+ useEffect(() => {
699
+ const active = document.activeElement;
700
+ if (!open || active && itemRefs.current.includes(active)) return;
701
+ itemRefs.current[0]?.focus();
702
+ }, [open]);
703
+ const moveFocus = (dir) => {
704
+ const n = locale.options.length;
705
+ if (n === 0) return;
706
+ if (dir === "first") return void itemRefs.current[0]?.focus();
707
+ if (dir === "last") return void itemRefs.current[n - 1]?.focus();
708
+ const pos = itemRefs.current.indexOf(document.activeElement);
709
+ const next = pos < 0 ? dir === 1 ? 0 : n - 1 : (pos + dir + n) % n;
710
+ itemRefs.current[next]?.focus();
711
+ };
712
+ const handleMenuKeyDown = (e) => {
713
+ if (["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(e.key)) {
714
+ e.stopPropagation();
715
+ }
716
+ switch (e.key) {
717
+ case "ArrowDown":
718
+ e.preventDefault();
719
+ moveFocus(1);
720
+ break;
721
+ case "ArrowUp":
722
+ e.preventDefault();
723
+ moveFocus(-1);
724
+ break;
725
+ case "Home":
726
+ e.preventDefault();
727
+ moveFocus("first");
728
+ break;
729
+ case "End":
730
+ e.preventDefault();
731
+ moveFocus("last");
732
+ break;
733
+ case "Escape":
734
+ e.preventDefault();
735
+ setOpen(false);
736
+ wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
737
+ break;
738
+ case "Tab":
739
+ setOpen(false);
740
+ break;
741
+ }
742
+ };
634
743
  const handleSelect = (value) => {
635
- locale.onChange(value);
744
+ (locale.onValueChange ?? locale.onChange)?.(value);
636
745
  setOpen(false);
637
746
  };
638
747
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "nav_bar_locale", children: [
@@ -646,25 +755,45 @@ var LocaleSwitcher = ({ locale }) => {
646
755
  "aria-controls": menuId,
647
756
  onClick: () => setOpen((p) => !p),
648
757
  children: [
649
- /* @__PURE__ */ jsx(Globe, { size: 16, "aria-hidden": "true" }),
758
+ /* @__PURE__ */ jsx(Globe, { size: iconSize.sm, "aria-hidden": "true" }),
650
759
  !locale.hideLabel && /* @__PURE__ */ jsx("span", { className: "nav_bar_locale_label", children: currentOption?.label ?? locale.current.toUpperCase() }),
651
- /* @__PURE__ */ jsx(ChevronDown, { size: 14, "aria-hidden": "true", className: "nav_bar_locale_chevron" })
760
+ /* @__PURE__ */ jsx(
761
+ ChevronDown,
762
+ {
763
+ size: iconSize.xs,
764
+ "aria-hidden": "true",
765
+ className: "nav_bar_locale_chevron"
766
+ }
767
+ )
652
768
  ]
653
769
  }
654
770
  ),
655
- open && /* @__PURE__ */ jsx("ul", { id: menuId, role: "menu", className: "nav_bar_locale_menu", children: locale.options.map((opt) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
656
- "button",
771
+ open && /* @__PURE__ */ jsx(
772
+ "ul",
657
773
  {
658
- type: "button",
659
- role: "menuitem",
660
- className: cn(
661
- "nav_bar_locale_item",
662
- opt.value === locale.current && "nav_bar_locale_item_active"
663
- ),
664
- onClick: () => handleSelect(opt.value),
665
- children: opt.label
774
+ id: menuId,
775
+ role: "menu",
776
+ className: "nav_bar_locale_menu",
777
+ onKeyDown: handleMenuKeyDown,
778
+ children: locale.options.map((opt, index) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
779
+ "button",
780
+ {
781
+ ref: (el) => {
782
+ itemRefs.current[index] = el;
783
+ },
784
+ type: "button",
785
+ role: "menuitem",
786
+ tabIndex: -1,
787
+ className: cn(
788
+ "nav_bar_locale_item",
789
+ opt.value === locale.current && "nav_bar_locale_item_active"
790
+ ),
791
+ onClick: () => handleSelect(opt.value),
792
+ children: opt.label
793
+ }
794
+ ) }, opt.value))
666
795
  }
667
- ) }, opt.value)) })
796
+ )
668
797
  ] });
669
798
  };
670
799
  var NavLink = ({ active, className, children, ...props }) => {
@@ -699,9 +828,9 @@ var Sidebar = ({
699
828
  ...props
700
829
  }) => {
701
830
  const isControlled = collapsedProp !== void 0;
702
- const [internalCollapsed, setInternalCollapsed] = React20.useState(defaultCollapsed);
831
+ const [internalCollapsed, setInternalCollapsed] = React19.useState(defaultCollapsed);
703
832
  const collapsed = isControlled ? collapsedProp : internalCollapsed;
704
- const toggle = React20.useCallback(() => {
833
+ const toggle = React19.useCallback(() => {
705
834
  const next = !collapsed;
706
835
  if (!isControlled) setInternalCollapsed(next);
707
836
  onCollapsedChange?.(next);
@@ -733,7 +862,7 @@ var Sidebar = ({
733
862
  onClick: toggle,
734
863
  "aria-label": toggleLabel,
735
864
  "aria-expanded": !collapsed,
736
- children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { size: 14 }) : /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
865
+ children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { size: iconSize.xs }) : /* @__PURE__ */ jsx(ChevronLeft, { size: iconSize.xs })
737
866
  }
738
867
  )
739
868
  ]
@@ -774,9 +903,9 @@ var SidebarSection = ({ label, className, children, ...props }) => {
774
903
  children
775
904
  ] });
776
905
  };
777
- var TabsContext = React20.createContext(null);
906
+ var TabsContext = React19.createContext(null);
778
907
  function useTabsContext() {
779
- const ctx = React20.useContext(TabsContext);
908
+ const ctx = React19.useContext(TabsContext);
780
909
  if (!ctx) {
781
910
  throw new Error(
782
911
  '[Bigtablet DS] <Tab>, <TabList>, <TabPanel>\uC740 \uBC18\uB4DC\uC2DC <Tabs> \uC548\uC5D0 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.\n\n\uC62C\uBC14\uB978 \uC0AC\uC6A9 \uC608:\n <Tabs defaultValue="a">\n <TabList>\n <Tab value="a">A</Tab>\n </TabList>\n <TabPanel value="a">...</TabPanel>\n </Tabs>'
@@ -795,17 +924,17 @@ var Tabs = ({
795
924
  ...props
796
925
  }) => {
797
926
  const isControlled = controlledValue !== void 0;
798
- const [internalValue, setInternalValue] = React20.useState(defaultValue);
927
+ const [internalValue, setInternalValue] = React19.useState(defaultValue);
799
928
  const value = isControlled ? controlledValue : internalValue;
800
- const setValue = React20.useCallback(
929
+ const setValue = React19.useCallback(
801
930
  (v) => {
802
931
  if (!isControlled) setInternalValue(v);
803
932
  onValueChange?.(v);
804
933
  },
805
934
  [isControlled, onValueChange]
806
935
  );
807
- const idPrefix = React20.useId();
808
- const ctx = React20.useMemo(
936
+ const idPrefix = React19.useId();
937
+ const ctx = React19.useMemo(
809
938
  () => ({ value, setValue, variant, size, idPrefix }),
810
939
  [value, setValue, variant, size, idPrefix]
811
940
  );
@@ -813,10 +942,10 @@ var Tabs = ({
813
942
  };
814
943
  var TabList = ({ ariaLabel, className, children, ...props }) => {
815
944
  const { variant } = useTabsContext();
816
- const listRef = React20.useRef(null);
817
- const [indicator, setIndicator] = React20.useState(null);
818
- const [hasMounted, setHasMounted] = React20.useState(false);
819
- React20.useLayoutEffect(() => {
945
+ const listRef = React19.useRef(null);
946
+ const [indicator, setIndicator] = React19.useState(null);
947
+ const [hasMounted, setHasMounted] = React19.useState(false);
948
+ useSafeLayoutEffect(() => {
820
949
  const list = listRef.current;
821
950
  if (!list) return;
822
951
  const update = () => {
@@ -964,15 +1093,15 @@ var Popover = ({
964
1093
  className
965
1094
  }) => {
966
1095
  const isControlled = openProp !== void 0;
967
- const [internalOpen, setInternalOpen] = React20.useState(defaultOpen);
1096
+ const [internalOpen, setInternalOpen] = React19.useState(defaultOpen);
968
1097
  const open = isControlled ? openProp : internalOpen;
969
- const [shouldRender, setShouldRender] = React20.useState(open ?? false);
1098
+ const [shouldRender, setShouldRender] = React19.useState(open ?? false);
970
1099
  if (open && !shouldRender) setShouldRender(true);
971
- const wrapperRef = React20.useRef(null);
972
- const popoverRef = React20.useRef(null);
973
- const previousFocusRef = React20.useRef(null);
974
- const popoverId = React20.useId();
975
- const setOpen = React20.useCallback(
1100
+ const wrapperRef = React19.useRef(null);
1101
+ const popoverRef = React19.useRef(null);
1102
+ const previousFocusRef = React19.useRef(null);
1103
+ const popoverId = React19.useId();
1104
+ const setOpen = React19.useCallback(
976
1105
  (next) => {
977
1106
  if (!isControlled) setInternalOpen(next);
978
1107
  onOpenChange?.(next);
@@ -992,7 +1121,7 @@ var Popover = ({
992
1121
  }
993
1122
  })();
994
1123
  const style = useSpringPresence({ visible: open, from: fromTransform, onExitComplete: () => setShouldRender(false) });
995
- React20.useEffect(() => {
1124
+ React19.useEffect(() => {
996
1125
  if (!open) return;
997
1126
  previousFocusRef.current = document.activeElement;
998
1127
  const node = popoverRef.current;
@@ -1000,7 +1129,7 @@ var Popover = ({
1000
1129
  const focusable = node.querySelector(FOCUSABLE_SELECTORS2);
1001
1130
  (focusable ?? node).focus();
1002
1131
  }, [open]);
1003
- React20.useEffect(() => {
1132
+ React19.useEffect(() => {
1004
1133
  if (!open) return;
1005
1134
  const handleClick = (e) => {
1006
1135
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -1018,7 +1147,7 @@ var Popover = ({
1018
1147
  document.removeEventListener("keydown", handleKeyDown);
1019
1148
  };
1020
1149
  }, [open, setOpen]);
1021
- const triggerWithProps = React20.cloneElement(
1150
+ const triggerWithProps = React19.cloneElement(
1022
1151
  trigger,
1023
1152
  {
1024
1153
  onClick: (e) => {
@@ -1057,18 +1186,18 @@ var Tooltip = ({
1057
1186
  disabled = false,
1058
1187
  children
1059
1188
  }) => {
1060
- const [open, setOpen] = React20.useState(false);
1061
- const timerRef = React20.useRef(null);
1062
- const tooltipId = React20.useId();
1063
- const show = React20.useCallback(() => {
1189
+ const [open, setOpen] = React19.useState(false);
1190
+ const timerRef = React19.useRef(null);
1191
+ const tooltipId = React19.useId();
1192
+ const show = React19.useCallback(() => {
1064
1193
  if (timerRef.current) clearTimeout(timerRef.current);
1065
1194
  timerRef.current = setTimeout(() => setOpen(true), delay);
1066
1195
  }, [delay]);
1067
- const hide = React20.useCallback(() => {
1196
+ const hide = React19.useCallback(() => {
1068
1197
  if (timerRef.current) clearTimeout(timerRef.current);
1069
1198
  setOpen(false);
1070
1199
  }, []);
1071
- React20.useEffect(() => {
1200
+ React19.useEffect(() => {
1072
1201
  return () => {
1073
1202
  if (timerRef.current) clearTimeout(timerRef.current);
1074
1203
  };
@@ -1086,12 +1215,27 @@ var Tooltip = ({
1086
1215
  }
1087
1216
  })();
1088
1217
  const style = useSpringPresence({ visible: open, from: fromTransform });
1089
- const trigger = React20.cloneElement(children, {
1090
- onMouseEnter: show,
1091
- onMouseLeave: hide,
1092
- onFocus: show,
1093
- onBlur: hide,
1094
- "aria-describedby": open ? tooltipId : void 0
1218
+ const child = children;
1219
+ const childProps = child.props;
1220
+ const trigger = React19.cloneElement(child, {
1221
+ onMouseEnter: (e) => {
1222
+ childProps.onMouseEnter?.(e);
1223
+ show();
1224
+ },
1225
+ onMouseLeave: (e) => {
1226
+ childProps.onMouseLeave?.(e);
1227
+ hide();
1228
+ },
1229
+ onFocus: (e) => {
1230
+ childProps.onFocus?.(e);
1231
+ show();
1232
+ },
1233
+ onBlur: (e) => {
1234
+ childProps.onBlur?.(e);
1235
+ hide();
1236
+ },
1237
+ // 자식의 기존 aria-describedby 보존 + tooltip id 합성 (폼 설명/에러 연결 끊김 방지)
1238
+ "aria-describedby": open ? [childProps["aria-describedby"], tooltipId].filter(Boolean).join(" ") : childProps["aria-describedby"]
1095
1239
  });
1096
1240
  if (disabled) return children;
1097
1241
  return /* @__PURE__ */ jsxs("span", { className: "tooltip_wrapper", children: [
@@ -1165,10 +1309,13 @@ var baseColors = {
1165
1309
  neutral900: "#121212",
1166
1310
  neutral925: "#141414",
1167
1311
  neutral950: "#0A0A0A",
1168
- statusError: "#EF4444",
1169
- statusSuccess: "#10B981",
1170
- statusWarning: "#F59E0B",
1171
- statusInfo: "#3B82F6",
1312
+ // caption 전용 AA 값 (neutral_500 과 분리 — SCSS _index.scss 와 동일)
1313
+ textCaptionLight: "#6F6F6F",
1314
+ // status — SCSS _index.scss 와 동일한 AA 통과(red/green/amber/blue-700) 값
1315
+ statusError: "#B91C1C",
1316
+ statusSuccess: "#047857",
1317
+ statusWarning: "#B45309",
1318
+ statusInfo: "#1D4ED8",
1172
1319
  alphaBlack5: "rgba(0, 0, 0, 0.05)",
1173
1320
  alphaBlack8: "rgba(0, 0, 0, 0.08)",
1174
1321
  alphaBlack12: "rgba(26, 26, 26, 0.12)",
@@ -1186,7 +1333,7 @@ var colors = {
1186
1333
  text: {
1187
1334
  heading: baseColors.neutral900,
1188
1335
  body: baseColors.neutral700,
1189
- caption: baseColors.neutral500,
1336
+ caption: baseColors.textCaptionLight,
1190
1337
  brand: baseColors.brandPrimary,
1191
1338
  inverse: baseColors.neutral0,
1192
1339
  disabled: baseColors.alphaBlack38
@@ -1592,6 +1739,8 @@ var Button = ({
1592
1739
  fullWidth = false,
1593
1740
  radius: radius2,
1594
1741
  danger = false,
1742
+ type = "button",
1743
+ ref,
1595
1744
  className,
1596
1745
  children,
1597
1746
  ...props
@@ -1605,17 +1754,17 @@ var Button = ({
1605
1754
  danger && "button_danger",
1606
1755
  className
1607
1756
  );
1608
- return /* @__PURE__ */ jsxs("button", { className: buttonClassName, ...props, children: [
1757
+ return /* @__PURE__ */ jsxs("button", { ref, type, className: buttonClassName, ...props, children: [
1609
1758
  leadingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: leadingIcon }),
1610
1759
  children && /* @__PURE__ */ jsx("span", { className: "button_label", children }),
1611
1760
  trailingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: trailingIcon })
1612
1761
  ] });
1613
1762
  };
1614
1763
  var ICONS = {
1615
- info: /* @__PURE__ */ jsx(Info, { size: 20, "aria-hidden": "true" }),
1616
- success: /* @__PURE__ */ jsx(CheckCircle2, { size: 20, "aria-hidden": "true" }),
1617
- warning: /* @__PURE__ */ jsx(AlertTriangle, { size: 20, "aria-hidden": "true" }),
1618
- error: /* @__PURE__ */ jsx(XCircle, { size: 20, "aria-hidden": "true" })
1764
+ info: /* @__PURE__ */ jsx(Info, { size: iconSize.lg, "aria-hidden": "true" }),
1765
+ success: /* @__PURE__ */ jsx(CheckCircle2, { size: iconSize.lg, "aria-hidden": "true" }),
1766
+ warning: /* @__PURE__ */ jsx(AlertTriangle, { size: iconSize.lg, "aria-hidden": "true" }),
1767
+ error: /* @__PURE__ */ jsx(XCircle, { size: iconSize.lg, "aria-hidden": "true" })
1619
1768
  };
1620
1769
  var AlertContext = createContext(null);
1621
1770
  var useAlert = () => {
@@ -1671,12 +1820,12 @@ var AlertModal = ({
1671
1820
  onCancel,
1672
1821
  onClose
1673
1822
  }) => {
1674
- const panelRef = React20.useRef(null);
1675
- const titleId = React20.useId();
1676
- const messageId = React20.useId();
1823
+ const panelRef = React19.useRef(null);
1824
+ const titleId = React19.useId();
1825
+ const messageId = React19.useId();
1677
1826
  const [shouldRender, setShouldRender] = useState(isOpen);
1678
1827
  useFocusTrap(panelRef, isOpen);
1679
- React20.useEffect(() => {
1828
+ React19.useEffect(() => {
1680
1829
  if (isOpen) setShouldRender(true);
1681
1830
  }, [isOpen]);
1682
1831
  const overlayStyle = useSpring({
@@ -1757,6 +1906,7 @@ var Card = ({
1757
1906
  interactive = false,
1758
1907
  footer,
1759
1908
  footerAlign = "end",
1909
+ ref,
1760
1910
  className,
1761
1911
  children,
1762
1912
  ...props
@@ -1769,7 +1919,7 @@ var Card = ({
1769
1919
  { card_bordered: bordered, card_interactive: interactive },
1770
1920
  className
1771
1921
  );
1772
- return /* @__PURE__ */ jsxs("div", { className: cardClassName, ...props, children: [
1922
+ return /* @__PURE__ */ jsxs("div", { ref, className: cardClassName, ...props, children: [
1773
1923
  heading ? /* @__PURE__ */ jsx(HeadingTag, { className: "card_title", children: heading }) : null,
1774
1924
  /* @__PURE__ */ jsx("div", { className: "card_body", children }),
1775
1925
  footer ? /* @__PURE__ */ jsx("div", { className: cn("card_footer", `card_footer_${footerAlign}`), children: footer }) : null
@@ -1783,12 +1933,12 @@ var Checkbox = ({
1783
1933
  ref,
1784
1934
  ...props
1785
1935
  }) => {
1786
- const inputRef = React20.useRef(null);
1787
- React20.useImperativeHandle(
1936
+ const inputRef = React19.useRef(null);
1937
+ React19.useImperativeHandle(
1788
1938
  ref,
1789
1939
  () => inputRef.current
1790
1940
  );
1791
- React20.useEffect(() => {
1941
+ React19.useEffect(() => {
1792
1942
  if (!inputRef.current) return;
1793
1943
  inputRef.current.indeterminate = Boolean(indeterminate);
1794
1944
  }, [indeterminate]);
@@ -1960,39 +2110,91 @@ var Chip = ({
1960
2110
  )
1961
2111
  ] });
1962
2112
  };
1963
- var Dropdown = ({
1964
- id,
1965
- label,
1966
- placeholder = "Select\u2026",
1967
- options,
1968
- value,
1969
- onChange,
1970
- defaultValue = null,
1971
- disabled,
1972
- size = "md",
1973
- className
1974
- }) => {
2113
+ var normalizeForSearch = (s) => s.toLowerCase().replace(/\s+/g, "");
2114
+ var Dropdown = (props) => {
2115
+ const {
2116
+ id,
2117
+ label,
2118
+ placeholder = "Select\u2026",
2119
+ options,
2120
+ disabled,
2121
+ size = "md",
2122
+ className,
2123
+ searchable = false,
2124
+ searchPlaceholder = "\uAC80\uC0C9\u2026",
2125
+ emptyText = "\uACB0\uACFC \uC5C6\uC74C",
2126
+ selectedSummary = (count) => `${count}\uAC1C \uC120\uD0DD`
2127
+ } = props;
2128
+ const multiple = props.multiple === true;
1975
2129
  const internalId = useId();
1976
2130
  const dropdownId = id ?? internalId;
1977
- const isControlled = value !== void 0;
1978
- const [internalValue, setInternalValue] = useState(defaultValue);
1979
- const currentValue = isControlled ? value ?? null : internalValue;
2131
+ const isControlled = props.value !== void 0;
2132
+ const [internalSingle, setInternalSingle] = useState(
2133
+ () => props.multiple === true ? null : props.defaultValue ?? null
2134
+ );
2135
+ const [internalMulti, setInternalMulti] = useState(
2136
+ () => props.multiple === true ? props.defaultValue ?? [] : []
2137
+ );
1980
2138
  const [isOpen, setIsOpen] = useState(false);
1981
2139
  const [activeIndex, setActiveIndex] = useState(-1);
1982
2140
  const [dropUp, setDropUp] = useState(false);
2141
+ const [searchText, setSearchText] = useState("");
2142
+ const [committedQuery, setCommittedQuery] = useState("");
2143
+ const isComposingRef = useRef(false);
1983
2144
  const wrapperRef = useRef(null);
1984
2145
  const controlRef = useRef(null);
1985
- const currentOption = useMemo(
1986
- () => options.find((o) => o.value === currentValue) ?? null,
1987
- [options, currentValue]
1988
- );
1989
- const setValue = useCallback(
2146
+ const searchRef = useRef(null);
2147
+ const selectedValues = useMemo(() => {
2148
+ if (multiple) {
2149
+ const v2 = isControlled ? props.value : internalMulti;
2150
+ return v2 ?? [];
2151
+ }
2152
+ const v = isControlled ? props.value : internalSingle;
2153
+ return v != null ? [v] : [];
2154
+ }, [multiple, isControlled, props.value, internalMulti, internalSingle]);
2155
+ const visibleOptions = useMemo(() => {
2156
+ if (!searchable) return options;
2157
+ const q = normalizeForSearch(committedQuery);
2158
+ if (q === "") return options;
2159
+ return options.filter((o) => normalizeForSearch(o.label).includes(q));
2160
+ }, [options, searchable, committedQuery]);
2161
+ const selectSingle = useCallback(
1990
2162
  (next) => {
1991
2163
  const option = options.find((o) => o.value === next) ?? null;
1992
- if (!isControlled) setInternalValue(next);
1993
- onChange?.(next, option);
2164
+ if (!isControlled) setInternalSingle(next);
2165
+ if (props.multiple !== true) {
2166
+ (props.onValueChange ?? props.onChange)?.(next, option);
2167
+ }
2168
+ },
2169
+ [isControlled, options, props.multiple, props.onValueChange, props.onChange]
2170
+ );
2171
+ const toggleMultiple = useCallback(
2172
+ (opt) => {
2173
+ const exists = selectedValues.includes(opt.value);
2174
+ const nextValues = exists ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value];
2175
+ const nextOptions = nextValues.map((v) => options.find((o) => o.value === v)).filter((o) => Boolean(o));
2176
+ if (!isControlled) setInternalMulti(nextValues);
2177
+ if (props.multiple === true) {
2178
+ (props.onValueChange ?? props.onChange)?.(nextValues, nextOptions);
2179
+ }
1994
2180
  },
1995
- [isControlled, onChange, options]
2181
+ [selectedValues, options, isControlled, props.multiple, props.onValueChange, props.onChange]
2182
+ );
2183
+ const closePanel = useCallback(() => {
2184
+ setIsOpen(false);
2185
+ if (searchable) controlRef.current?.focus();
2186
+ }, [searchable]);
2187
+ const selectOption = useCallback(
2188
+ (opt) => {
2189
+ if (opt.disabled) return;
2190
+ if (multiple) {
2191
+ toggleMultiple(opt);
2192
+ } else {
2193
+ selectSingle(opt.value);
2194
+ closePanel();
2195
+ }
2196
+ },
2197
+ [multiple, toggleMultiple, selectSingle, closePanel]
1996
2198
  );
1997
2199
  useEffect(() => {
1998
2200
  const handleOutsideClick = (e) => {
@@ -2003,31 +2205,33 @@ var Dropdown = ({
2003
2205
  document.addEventListener("mousedown", handleOutsideClick);
2004
2206
  return () => document.removeEventListener("mousedown", handleOutsideClick);
2005
2207
  }, []);
2006
- const moveActive = (dir) => {
2007
- if (options.length === 0) return;
2008
- if (!isOpen) {
2009
- setIsOpen(true);
2010
- return;
2011
- }
2012
- let i = activeIndex;
2013
- const len = options.length;
2014
- for (let step = 0; step < len; step++) {
2015
- i = (i + dir + len) % len;
2016
- if (!options[i].disabled) {
2017
- setActiveIndex(i);
2018
- break;
2208
+ const moveActive = useCallback(
2209
+ (dir) => {
2210
+ if (visibleOptions.length === 0) return;
2211
+ if (!isOpen) {
2212
+ setIsOpen(true);
2213
+ return;
2019
2214
  }
2020
- }
2021
- };
2022
- const commitActive = () => {
2023
- if (activeIndex < 0 || activeIndex >= options.length) return;
2024
- const opt = options[activeIndex];
2025
- if (!opt.disabled) {
2026
- setValue(opt.value);
2027
- setIsOpen(false);
2028
- }
2029
- };
2030
- const onKeyDown = (e) => {
2215
+ let i = activeIndex;
2216
+ if (i === -1) {
2217
+ i = dir === 1 ? -1 : 0;
2218
+ }
2219
+ const len = visibleOptions.length;
2220
+ for (let step = 0; step < len; step++) {
2221
+ i = (i + dir + len) % len;
2222
+ if (!visibleOptions[i].disabled) {
2223
+ setActiveIndex(i);
2224
+ break;
2225
+ }
2226
+ }
2227
+ },
2228
+ [visibleOptions, isOpen, activeIndex]
2229
+ );
2230
+ const commitActive = useCallback(() => {
2231
+ if (activeIndex < 0 || activeIndex >= visibleOptions.length) return;
2232
+ selectOption(visibleOptions[activeIndex]);
2233
+ }, [activeIndex, visibleOptions, selectOption]);
2234
+ const onControlKeyDown = (e) => {
2031
2235
  if (disabled) return;
2032
2236
  switch (e.key) {
2033
2237
  case " ":
@@ -2047,13 +2251,13 @@ var Dropdown = ({
2047
2251
  case "Home":
2048
2252
  e.preventDefault();
2049
2253
  setIsOpen(true);
2050
- setActiveIndex(options.findIndex((o) => !o.disabled));
2254
+ setActiveIndex(visibleOptions.findIndex((o) => !o.disabled));
2051
2255
  break;
2052
2256
  case "End":
2053
2257
  e.preventDefault();
2054
2258
  setIsOpen(true);
2055
- for (let i = options.length - 1; i >= 0; i--) {
2056
- if (!options[i].disabled) {
2259
+ for (let i = visibleOptions.length - 1; i >= 0; i--) {
2260
+ if (!visibleOptions[i].disabled) {
2057
2261
  setActiveIndex(i);
2058
2262
  break;
2059
2263
  }
@@ -2065,16 +2269,46 @@ var Dropdown = ({
2065
2269
  break;
2066
2270
  }
2067
2271
  };
2272
+ const onSearchKeyDown = (e) => {
2273
+ if (e.nativeEvent.isComposing) return;
2274
+ switch (e.key) {
2275
+ case "ArrowDown":
2276
+ e.preventDefault();
2277
+ moveActive(1);
2278
+ break;
2279
+ case "ArrowUp":
2280
+ e.preventDefault();
2281
+ moveActive(-1);
2282
+ break;
2283
+ case "Enter":
2284
+ e.preventDefault();
2285
+ commitActive();
2286
+ break;
2287
+ case "Escape":
2288
+ e.preventDefault();
2289
+ closePanel();
2290
+ break;
2291
+ }
2292
+ };
2068
2293
  useEffect(() => {
2069
2294
  if (!isOpen) return;
2070
- const matchedIndex = options.findIndex((o) => o.value === currentValue && !o.disabled);
2071
- setActiveIndex(
2072
- matchedIndex >= 0 ? matchedIndex : Math.max(
2073
- 0,
2074
- options.findIndex((o) => !o.disabled)
2075
- )
2295
+ const selectedIdx = visibleOptions.findIndex(
2296
+ (o) => selectedValues.includes(o.value) && !o.disabled
2076
2297
  );
2077
- }, [isOpen, options, currentValue]);
2298
+ setActiveIndex(selectedIdx >= 0 ? selectedIdx : visibleOptions.findIndex((o) => !o.disabled));
2299
+ }, [isOpen, visibleOptions]);
2300
+ useEffect(() => {
2301
+ if (!isOpen) {
2302
+ setSearchText("");
2303
+ setCommittedQuery("");
2304
+ isComposingRef.current = false;
2305
+ }
2306
+ }, [isOpen]);
2307
+ useEffect(() => {
2308
+ if (isOpen && searchable) {
2309
+ searchRef.current?.focus();
2310
+ }
2311
+ }, [isOpen, searchable]);
2078
2312
  useEffect(() => {
2079
2313
  if (!isOpen || !controlRef.current) return;
2080
2314
  const rect = controlRef.current.getBoundingClientRect();
@@ -2083,6 +2317,13 @@ var Dropdown = ({
2083
2317
  const MIN_BELOW = 120;
2084
2318
  setDropUp(spaceBelow < MIN_BELOW && spaceAbove > spaceBelow);
2085
2319
  }, [isOpen]);
2320
+ const currentOption = useMemo(
2321
+ () => multiple ? null : options.find((o) => o.value === selectedValues[0]) ?? null,
2322
+ [multiple, options, selectedValues]
2323
+ );
2324
+ const hasSelection = selectedValues.length > 0;
2325
+ const showValue = multiple ? hasSelection : currentOption != null;
2326
+ const controlText = multiple ? hasSelection ? selectedSummary(selectedValues.length) : placeholder : currentOption ? currentOption.label : placeholder;
2086
2327
  const rootClassName = cn("dropdown", `dropdown_size_${size}`, className);
2087
2328
  const fieldsetClassName = cn("dropdown_fieldset", { is_open: isOpen, is_disabled: disabled });
2088
2329
  const listClassName = cn("dropdown_list", { dropdown_list_up: dropUp });
@@ -2090,86 +2331,107 @@ var Dropdown = ({
2090
2331
  visible: isOpen,
2091
2332
  from: dropUp ? "translateY(4px)" : "translateY(-4px)"
2092
2333
  });
2093
- return /* @__PURE__ */ jsxs(
2094
- "div",
2095
- {
2096
- ref: wrapperRef,
2097
- className: rootClassName,
2098
- children: [
2099
- label && /* @__PURE__ */ jsx("label", { htmlFor: dropdownId, className: "dropdown_label", children: label }),
2100
- /* @__PURE__ */ jsx("div", { className: fieldsetClassName, children: /* @__PURE__ */ jsxs(
2101
- "button",
2334
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: rootClassName, children: [
2335
+ label && /* @__PURE__ */ jsx("label", { htmlFor: dropdownId, className: "dropdown_label", children: label }),
2336
+ /* @__PURE__ */ jsx("div", { className: fieldsetClassName, children: /* @__PURE__ */ jsxs(
2337
+ "button",
2338
+ {
2339
+ ref: controlRef,
2340
+ id: dropdownId,
2341
+ type: "button",
2342
+ className: cn("dropdown_control", { is_disabled: disabled }),
2343
+ "aria-haspopup": "listbox",
2344
+ "aria-expanded": isOpen,
2345
+ "aria-controls": `${dropdownId}_listbox`,
2346
+ onClick: () => !disabled && setIsOpen((o) => !o),
2347
+ onKeyDown: onControlKeyDown,
2348
+ disabled,
2349
+ children: [
2350
+ /* @__PURE__ */ jsx("span", { className: showValue ? "dropdown_value" : "dropdown_placeholder", children: controlText }),
2351
+ /* @__PURE__ */ jsx("span", { className: cn("dropdown_icon", { dropdown_icon_open: isOpen }), "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: iconSize.lg }) })
2352
+ ]
2353
+ }
2354
+ ) }),
2355
+ isOpen && /* @__PURE__ */ jsxs(animated.div, { className: listClassName, style: listStyle, children: [
2356
+ searchable && /* @__PURE__ */ jsxs("div", { className: "dropdown_search", children: [
2357
+ /* @__PURE__ */ jsx("span", { className: "dropdown_search_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Search, { size: iconSize.sm }) }),
2358
+ /* @__PURE__ */ jsx(
2359
+ "input",
2102
2360
  {
2103
- ref: controlRef,
2104
- id: dropdownId,
2105
- type: "button",
2106
- className: cn("dropdown_control", { is_disabled: disabled }),
2107
- "aria-haspopup": "listbox",
2361
+ ref: searchRef,
2362
+ type: "text",
2363
+ className: "dropdown_search_input",
2364
+ placeholder: searchPlaceholder,
2365
+ "aria-label": searchPlaceholder,
2366
+ autoComplete: "off",
2367
+ role: "combobox",
2368
+ "aria-autocomplete": "list",
2108
2369
  "aria-expanded": isOpen,
2109
2370
  "aria-controls": `${dropdownId}_listbox`,
2110
- onClick: () => !disabled && setIsOpen((o) => !o),
2111
- onKeyDown,
2112
- disabled,
2113
- children: [
2114
- /* @__PURE__ */ jsx("span", { className: currentOption ? "dropdown_value" : "dropdown_placeholder", children: currentOption ? currentOption.label : placeholder }),
2115
- /* @__PURE__ */ jsx(
2116
- "span",
2117
- {
2118
- className: cn("dropdown_icon", { dropdown_icon_open: isOpen }),
2119
- "aria-hidden": "true",
2120
- children: /* @__PURE__ */ jsx(ChevronDown, { size: 20 })
2121
- }
2122
- )
2123
- ]
2124
- }
2125
- ) }),
2126
- isOpen && /* @__PURE__ */ jsx(
2127
- animated.div,
2128
- {
2129
- id: `${dropdownId}_listbox`,
2130
- role: "listbox",
2131
- className: listClassName,
2132
- style: listStyle,
2133
- children: options.map((opt, i) => {
2134
- const selected = currentValue === opt.value;
2135
- const active = i === activeIndex;
2136
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2137
- /* @__PURE__ */ jsxs(
2138
- "div",
2139
- {
2140
- role: "option",
2141
- tabIndex: -1,
2142
- "aria-selected": selected,
2143
- "aria-disabled": opt.disabled ? true : void 0,
2144
- className: cn("dropdown_option", {
2145
- is_selected: selected,
2146
- is_active: active,
2147
- is_disabled: opt.disabled
2148
- }),
2149
- onMouseEnter: () => !opt.disabled && setActiveIndex(i),
2150
- onClick: () => {
2151
- if (opt.disabled) return;
2152
- setValue(opt.value);
2153
- setIsOpen(false);
2154
- },
2155
- children: [
2156
- opt.leadingIcon && /* @__PURE__ */ jsx("span", { className: "dropdown_option_icon", children: opt.leadingIcon }),
2157
- /* @__PURE__ */ jsxs("span", { className: "dropdown_option_content", children: [
2158
- /* @__PURE__ */ jsx("span", { className: "dropdown_option_label", children: opt.label }),
2159
- opt.supportingText && /* @__PURE__ */ jsx("span", { className: "dropdown_option_supporting", children: opt.supportingText })
2160
- ] }),
2161
- (selected || opt.trailingIcon) && /* @__PURE__ */ jsx("span", { className: "dropdown_option_trailing", children: opt.trailingIcon ?? /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" }) })
2162
- ]
2163
- }
2164
- ),
2165
- opt.showDivider && /* @__PURE__ */ jsx("hr", { className: "dropdown_option_divider" })
2166
- ] }, opt.value);
2167
- })
2371
+ "aria-activedescendant": activeIndex >= 0 && visibleOptions[activeIndex] ? `${dropdownId}_option_${activeIndex}` : void 0,
2372
+ value: searchText,
2373
+ onChange: (e) => {
2374
+ const v = e.target.value;
2375
+ setSearchText(v);
2376
+ if (!isComposingRef.current) setCommittedQuery(v);
2377
+ },
2378
+ onCompositionStart: () => {
2379
+ isComposingRef.current = true;
2380
+ },
2381
+ onCompositionEnd: (e) => {
2382
+ isComposingRef.current = false;
2383
+ const v = e.currentTarget.value;
2384
+ setSearchText(v);
2385
+ setCommittedQuery(v);
2386
+ },
2387
+ onKeyDown: onSearchKeyDown
2168
2388
  }
2169
2389
  )
2170
- ]
2171
- }
2172
- );
2390
+ ] }),
2391
+ /* @__PURE__ */ jsx(
2392
+ "div",
2393
+ {
2394
+ id: `${dropdownId}_listbox`,
2395
+ role: "listbox",
2396
+ className: "dropdown_options",
2397
+ "aria-multiselectable": multiple || void 0,
2398
+ children: visibleOptions.length === 0 ? searchable && /* @__PURE__ */ jsx("div", { className: "dropdown_empty", children: emptyText }) : visibleOptions.map((opt, i) => {
2399
+ const selected = selectedValues.includes(opt.value);
2400
+ const active = i === activeIndex;
2401
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2402
+ /* @__PURE__ */ jsxs(
2403
+ "div",
2404
+ {
2405
+ id: `${dropdownId}_option_${i}`,
2406
+ role: "option",
2407
+ tabIndex: -1,
2408
+ "aria-selected": selected,
2409
+ "aria-disabled": opt.disabled ? true : void 0,
2410
+ className: cn("dropdown_option", {
2411
+ is_selected: selected,
2412
+ is_active: active,
2413
+ is_disabled: opt.disabled
2414
+ }),
2415
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
2416
+ onClick: () => selectOption(opt),
2417
+ children: [
2418
+ multiple && /* @__PURE__ */ jsx("span", { className: "dropdown_option_check", "aria-hidden": "true", children: selected && /* @__PURE__ */ jsx(Check, { size: iconSize.sm }) }),
2419
+ opt.leadingIcon && /* @__PURE__ */ jsx("span", { className: "dropdown_option_icon", children: opt.leadingIcon }),
2420
+ /* @__PURE__ */ jsxs("span", { className: "dropdown_option_content", children: [
2421
+ /* @__PURE__ */ jsx("span", { className: "dropdown_option_label", children: opt.label }),
2422
+ opt.supportingText && /* @__PURE__ */ jsx("span", { className: "dropdown_option_supporting", children: opt.supportingText })
2423
+ ] }),
2424
+ (opt.trailingIcon || !multiple && selected) && /* @__PURE__ */ jsx("span", { className: "dropdown_option_trailing", children: opt.trailingIcon ?? /* @__PURE__ */ jsx(Check, { size: iconSize.sm, "aria-hidden": "true" }) })
2425
+ ]
2426
+ }
2427
+ ),
2428
+ opt.showDivider && /* @__PURE__ */ jsx("hr", { className: "dropdown_option_divider" })
2429
+ ] }, opt.value);
2430
+ })
2431
+ }
2432
+ )
2433
+ ] })
2434
+ ] });
2173
2435
  };
2174
2436
  var pad = (n) => String(n).padStart(2, "0");
2175
2437
  var getDaysInMonth = (year, month) => new Date(year, month, 0).getDate();
@@ -2178,6 +2440,7 @@ var range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => st
2178
2440
  var DatePicker = ({
2179
2441
  label,
2180
2442
  value,
2443
+ onValueChange,
2181
2444
  onChange,
2182
2445
  mode = "year-month-day",
2183
2446
  startYear = 1950,
@@ -2193,9 +2456,9 @@ var DatePicker = ({
2193
2456
  minDateSrFormat = "Minimum date: {date}",
2194
2457
  selectableRangeUntilTodaySrText = "Selectable up to today"
2195
2458
  }) => {
2196
- const groupId = React20.useId();
2197
- const constraintId = React20.useId();
2198
- const { todayYear, todayMonth, todayDay } = React20.useMemo(() => {
2459
+ const groupId = React19.useId();
2460
+ const constraintId = React19.useId();
2461
+ const { todayYear, todayMonth, todayDay } = React19.useMemo(() => {
2199
2462
  const now = /* @__PURE__ */ new Date();
2200
2463
  return {
2201
2464
  todayYear: now.getFullYear(),
@@ -2204,7 +2467,7 @@ var DatePicker = ({
2204
2467
  };
2205
2468
  }, []);
2206
2469
  const endYear = endYearProp ?? todayYear + 10;
2207
- const parsed = React20.useMemo(() => {
2470
+ const parsed = React19.useMemo(() => {
2208
2471
  if (!value) return { year: 0, month: 0, day: 0 };
2209
2472
  const [y, m, d] = value.split("-").map(Number);
2210
2473
  return {
@@ -2213,7 +2476,7 @@ var DatePicker = ({
2213
2476
  day: d || 0
2214
2477
  };
2215
2478
  }, [value]);
2216
- const min = React20.useMemo(() => {
2479
+ const min = React19.useMemo(() => {
2217
2480
  if (!minDate) return { year: 0, month: 0, day: 0 };
2218
2481
  const [y, m, d] = minDate.split("-").map(Number);
2219
2482
  return {
@@ -2233,7 +2496,7 @@ var DatePicker = ({
2233
2496
  min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
2234
2497
  )
2235
2498
  );
2236
- const maxDay = React20.useMemo(() => {
2499
+ const maxDay = React19.useMemo(() => {
2237
2500
  if (!year || !month) return 31;
2238
2501
  const daysInMonth = getDaysInMonth(year, month);
2239
2502
  if (selectableRange === "until-today" && year === todayYear && month === todayMonth) {
@@ -2241,39 +2504,40 @@ var DatePicker = ({
2241
2504
  }
2242
2505
  return daysInMonth;
2243
2506
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
2244
- const yearOptions = React20.useMemo(
2507
+ const yearOptions = React19.useMemo(
2245
2508
  () => range(startYear, maxYear).map((y) => ({
2246
2509
  value: String(y),
2247
2510
  label: String(y)
2248
2511
  })),
2249
2512
  [startYear, maxYear]
2250
2513
  );
2251
- const monthOptions = React20.useMemo(
2514
+ const monthOptions = React19.useMemo(
2252
2515
  () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
2253
2516
  value: String(m),
2254
2517
  label: pad(m)
2255
2518
  })),
2256
2519
  [minMonth, maxMonth]
2257
2520
  );
2258
- const dayOptions = React20.useMemo(
2521
+ const dayOptions = React19.useMemo(
2259
2522
  () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
2260
2523
  value: String(d),
2261
2524
  label: pad(d)
2262
2525
  })),
2263
2526
  [minDay, maxDay]
2264
2527
  );
2265
- const emit = React20.useCallback(
2528
+ const emit = React19.useCallback(
2266
2529
  (yy, mm, dd) => {
2530
+ const cb = onValueChange ?? onChange;
2267
2531
  if (mode === "year-month") {
2268
- onChange(`${yy}-${pad(mm)}`);
2532
+ cb?.(`${yy}-${pad(mm)}`);
2269
2533
  return;
2270
2534
  }
2271
2535
  const safeDay = Math.min(dd ?? 1, getDaysInMonth(yy, mm));
2272
- onChange(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2536
+ cb?.(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2273
2537
  },
2274
- [mode, onChange]
2538
+ [mode, onValueChange, onChange]
2275
2539
  );
2276
- const handleYearChange = React20.useCallback(
2540
+ const handleYearChange = React19.useCallback(
2277
2541
  (raw) => {
2278
2542
  if (!raw) return;
2279
2543
  const newYear = Number(raw);
@@ -2290,14 +2554,14 @@ var DatePicker = ({
2290
2554
  },
2291
2555
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
2292
2556
  );
2293
- const handleMonthChange = React20.useCallback(
2557
+ const handleMonthChange = React19.useCallback(
2294
2558
  (raw) => {
2295
2559
  if (!raw || !year) return;
2296
2560
  emit(year, Number(raw), day || void 0);
2297
2561
  },
2298
2562
  [year, day, emit]
2299
2563
  );
2300
- const handleDayChange = React20.useCallback(
2564
+ const handleDayChange = React19.useCallback(
2301
2565
  (raw) => {
2302
2566
  if (!raw || !year || !month) return;
2303
2567
  emit(year, month, Number(raw));
@@ -2333,7 +2597,7 @@ var DatePicker = ({
2333
2597
  placeholder: yearLabel,
2334
2598
  options: yearOptions,
2335
2599
  value: year ? String(year) : null,
2336
- onChange: handleYearChange,
2600
+ onValueChange: handleYearChange,
2337
2601
  disabled
2338
2602
  }
2339
2603
  ),
@@ -2346,7 +2610,7 @@ var DatePicker = ({
2346
2610
  placeholder: monthLabel,
2347
2611
  options: monthOptions,
2348
2612
  value: month ? String(month) : null,
2349
- onChange: handleMonthChange,
2613
+ onValueChange: handleMonthChange,
2350
2614
  disabled: disabled || !year
2351
2615
  }
2352
2616
  ),
@@ -2359,7 +2623,7 @@ var DatePicker = ({
2359
2623
  placeholder: dayLabel,
2360
2624
  options: dayOptions,
2361
2625
  value: day ? String(day) : null,
2362
- onChange: handleDayChange,
2626
+ onValueChange: handleDayChange,
2363
2627
  disabled: disabled || !month
2364
2628
  }
2365
2629
  )
@@ -2372,6 +2636,119 @@ var Divider = ({ weight = "standard", className, ...props }) => {
2372
2636
  const dividerClassName = cn("divider", `divider_weight_${weight}`, className);
2373
2637
  return /* @__PURE__ */ jsx("hr", { className: dividerClassName, ...props });
2374
2638
  };
2639
+ var SLIDE_FROM = {
2640
+ left: "translateX(-100%)",
2641
+ right: "translateX(100%)",
2642
+ bottom: "translateY(100%)"
2643
+ };
2644
+ var Drawer = ({
2645
+ open,
2646
+ onClose,
2647
+ placement = "right",
2648
+ size = 360,
2649
+ title,
2650
+ footer,
2651
+ closeOnOverlay = true,
2652
+ showCloseIcon = true,
2653
+ closeLabel = "\uB2EB\uAE30",
2654
+ ariaLabel,
2655
+ children,
2656
+ className,
2657
+ ...props
2658
+ }) => {
2659
+ const panelRef = React19.useRef(null);
2660
+ const titleId = React19.useId();
2661
+ const [shouldRender, setShouldRender] = React19.useState(open);
2662
+ const reduced = useReducedMotion();
2663
+ useFocusTrap(panelRef, open);
2664
+ if (open && !shouldRender) setShouldRender(true);
2665
+ const overlayStyle = useSpringPresence({
2666
+ visible: open,
2667
+ from: "translateY(0px)",
2668
+ onExitComplete: () => setShouldRender(false)
2669
+ });
2670
+ const slideFrom = SLIDE_FROM[placement];
2671
+ const slideRest = placement === "bottom" ? "translateY(0%)" : "translateX(0%)";
2672
+ const panelStyle = useSpring({
2673
+ from: { transform: slideFrom },
2674
+ to: { transform: open ? slideRest : slideFrom },
2675
+ immediate: reduced,
2676
+ config: { tension: 280, friction: 28, clamp: !open }
2677
+ });
2678
+ React19.useEffect(() => {
2679
+ if (!open) return;
2680
+ const body = document.body;
2681
+ const openModals = parseInt(body.dataset.openModals || "0", 10);
2682
+ if (openModals === 0) {
2683
+ body.dataset.originalOverflow = window.getComputedStyle(body).overflow;
2684
+ body.style.overflow = "hidden";
2685
+ }
2686
+ body.dataset.openModals = String(openModals + 1);
2687
+ return () => {
2688
+ const currentOpenModals = parseInt(body.dataset.openModals || "1", 10);
2689
+ const nextOpenModals = currentOpenModals - 1;
2690
+ if (nextOpenModals === 0) {
2691
+ body.style.overflow = body.dataset.originalOverflow || "";
2692
+ delete body.dataset.openModals;
2693
+ delete body.dataset.originalOverflow;
2694
+ } else {
2695
+ body.dataset.openModals = String(nextOpenModals);
2696
+ }
2697
+ };
2698
+ }, [open]);
2699
+ if (!open && !shouldRender) return null;
2700
+ const hasTitle = !!title;
2701
+ const sizeValue = typeof size === "number" ? `${size}px` : size;
2702
+ const panelSizeStyle = placement === "bottom" ? { height: sizeValue } : { width: sizeValue };
2703
+ return /* @__PURE__ */ jsx(
2704
+ animated.div,
2705
+ {
2706
+ className: cn("drawer", `drawer_placement_${placement}`),
2707
+ style: overlayStyle,
2708
+ role: "dialog",
2709
+ "aria-modal": "true",
2710
+ "aria-labelledby": hasTitle && !ariaLabel ? titleId : void 0,
2711
+ "aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
2712
+ onClick: () => closeOnOverlay && onClose?.(),
2713
+ onKeyDown: (e) => {
2714
+ if (e.key === "Escape") {
2715
+ e.stopPropagation();
2716
+ onClose?.();
2717
+ }
2718
+ },
2719
+ children: /* @__PURE__ */ jsxs(
2720
+ animated.div,
2721
+ {
2722
+ ref: panelRef,
2723
+ className: cn("drawer_panel", className),
2724
+ style: { ...panelStyle, ...panelSizeStyle },
2725
+ role: "document",
2726
+ onClick: (e) => e.stopPropagation(),
2727
+ onKeyDown: (e) => {
2728
+ if (e.key !== "Escape") e.stopPropagation();
2729
+ },
2730
+ ...props,
2731
+ children: [
2732
+ showCloseIcon && onClose && /* @__PURE__ */ jsx(
2733
+ "button",
2734
+ {
2735
+ type: "button",
2736
+ className: "drawer_close",
2737
+ onClick: onClose,
2738
+ "aria-label": closeLabel,
2739
+ children: /* @__PURE__ */ jsx(X, { size: iconSize.md, "aria-hidden": "true" })
2740
+ }
2741
+ ),
2742
+ title && /* @__PURE__ */ jsx("div", { className: "drawer_header", children: /* @__PURE__ */ jsx("h2", { id: titleId, className: "drawer_title", children: title }) }),
2743
+ children && /* @__PURE__ */ jsx("div", { className: "drawer_body", children }),
2744
+ footer && /* @__PURE__ */ jsx("div", { className: "drawer_footer", children: footer })
2745
+ ]
2746
+ }
2747
+ )
2748
+ }
2749
+ );
2750
+ };
2751
+ Drawer.displayName = "Drawer";
2375
2752
  var FileInput = ({
2376
2753
  label = "Choose file",
2377
2754
  onFiles,
@@ -2385,16 +2762,15 @@ var FileInput = ({
2385
2762
  onChange,
2386
2763
  ...props
2387
2764
  }) => {
2388
- const inputId = React20.useId();
2389
- const helperId = React20.useId();
2390
- const inputRef = React20.useRef(null);
2391
- const [previewUrls, setPreviewUrls] = React20.useState([]);
2765
+ const inputId = React19.useId();
2766
+ const helperId = React19.useId();
2767
+ const inputRef = React19.useRef(null);
2768
+ const [previewUrls, setPreviewUrls] = React19.useState([]);
2769
+ const previewUrlsRef = React19.useRef([]);
2392
2770
  const isPreviewVariant = variant === "preview";
2393
2771
  const showPreview = isPreviewVariant || preview;
2394
2772
  const handleChange = (e) => {
2395
2773
  const files = e.currentTarget.files;
2396
- onFiles?.(files);
2397
- onChange?.(e);
2398
2774
  if (showPreview && files) {
2399
2775
  for (const url of previewUrls) {
2400
2776
  URL.revokeObjectURL(url);
@@ -2407,12 +2783,16 @@ var FileInput = ({
2407
2783
  }
2408
2784
  }
2409
2785
  setPreviewUrls(urls);
2786
+ previewUrlsRef.current = urls;
2410
2787
  } else {
2411
2788
  for (const url of previewUrls) {
2412
2789
  URL.revokeObjectURL(url);
2413
2790
  }
2414
2791
  setPreviewUrls([]);
2792
+ previewUrlsRef.current = [];
2415
2793
  }
2794
+ onFiles?.(files);
2795
+ onChange?.(e);
2416
2796
  };
2417
2797
  const handleRemove = (e) => {
2418
2798
  e.preventDefault();
@@ -2421,18 +2801,19 @@ var FileInput = ({
2421
2801
  URL.revokeObjectURL(url);
2422
2802
  }
2423
2803
  setPreviewUrls([]);
2804
+ previewUrlsRef.current = [];
2424
2805
  if (inputRef.current) {
2425
2806
  inputRef.current.value = "";
2426
2807
  }
2427
2808
  onFiles?.(null);
2428
2809
  };
2429
- React20.useEffect(() => {
2810
+ React19.useEffect(() => {
2430
2811
  return () => {
2431
- for (const url of previewUrls) {
2812
+ for (const url of previewUrlsRef.current) {
2432
2813
  URL.revokeObjectURL(url);
2433
2814
  }
2434
2815
  };
2435
- }, [previewUrls]);
2816
+ }, []);
2436
2817
  const hasImage = previewUrls.length > 0;
2437
2818
  const rootClassName = cn(
2438
2819
  "file_input",
@@ -2473,7 +2854,7 @@ var FileInput = ({
2473
2854
  }
2474
2855
  )
2475
2856
  ) : /* @__PURE__ */ jsxs("span", { className: "file_input_preview_empty", children: [
2476
- /* @__PURE__ */ jsx(Image, { size: 32, "aria-hidden": "true" }),
2857
+ /* @__PURE__ */ jsx(Image, { size: iconSize.xl, "aria-hidden": "true" }),
2477
2858
  /* @__PURE__ */ jsx("span", { className: "file_input_preview_empty_text", children: label })
2478
2859
  ] })
2479
2860
  }
@@ -2485,7 +2866,7 @@ var FileInput = ({
2485
2866
  className: "file_input_preview_remove",
2486
2867
  onClick: handleRemove,
2487
2868
  "aria-label": "\uC774\uBBF8\uC9C0 \uC81C\uAC70",
2488
- children: /* @__PURE__ */ jsx(X, { size: 14, "aria-hidden": "true" })
2869
+ children: /* @__PURE__ */ jsx(X, { size: iconSize.xs, "aria-hidden": "true" })
2489
2870
  }
2490
2871
  ),
2491
2872
  supportingText && /* @__PURE__ */ jsx("span", { id: helperId, className: "file_input_helper", children: supportingText }),
@@ -2564,6 +2945,8 @@ var IconButton = ({
2564
2945
  variant = "standard",
2565
2946
  size = "md",
2566
2947
  icon,
2948
+ type = "button",
2949
+ ref,
2567
2950
  className,
2568
2951
  ...props
2569
2952
  }) => {
@@ -2573,7 +2956,7 @@ var IconButton = ({
2573
2956
  `icon_button_size_${size}`,
2574
2957
  className
2575
2958
  );
2576
- return /* @__PURE__ */ jsx("button", { className: buttonClassName, ...props, children: /* @__PURE__ */ jsx("span", { className: "icon_button_icon", "aria-hidden": "true", children: icon }) });
2959
+ return /* @__PURE__ */ jsx("button", { ref, type, className: buttonClassName, ...props, children: /* @__PURE__ */ jsx("span", { className: "icon_button_icon", "aria-hidden": "true", children: icon }) });
2577
2960
  };
2578
2961
  var LinearProgress = ({
2579
2962
  totalSteps,
@@ -2646,7 +3029,7 @@ var ListItem = ({
2646
3029
  if (disabled || !onClick) return;
2647
3030
  if (e.key === "Enter" || e.key === " ") {
2648
3031
  e.preventDefault();
2649
- onClick(e);
3032
+ e.currentTarget.click();
2650
3033
  }
2651
3034
  },
2652
3035
  role: onClick ? "button" : void 0,
@@ -2723,13 +3106,11 @@ var Modal = ({
2723
3106
  ariaLabel,
2724
3107
  ...props
2725
3108
  }) => {
2726
- const panelRef = React20.useRef(null);
2727
- const titleId = React20.useId();
2728
- const [shouldRender, setShouldRender] = React20.useState(open);
3109
+ const panelRef = React19.useRef(null);
3110
+ const titleId = React19.useId();
3111
+ const [shouldRender, setShouldRender] = React19.useState(open);
2729
3112
  useFocusTrap(panelRef, open);
2730
- React20.useEffect(() => {
2731
- if (open) setShouldRender(true);
2732
- }, [open]);
3113
+ if (open && !shouldRender) setShouldRender(true);
2733
3114
  const overlayStyle = useSpring({
2734
3115
  opacity: open ? 1 : 0,
2735
3116
  config: { tension: 280, friction: 28, clamp: !open },
@@ -2742,15 +3123,7 @@ var Modal = ({
2742
3123
  transform: open ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
2743
3124
  config: { tension: 280, friction: 28, clamp: !open }
2744
3125
  });
2745
- const handleEscape = React20.useEffectEvent((e) => {
2746
- if (e.key === "Escape") onClose?.();
2747
- });
2748
- React20.useEffect(() => {
2749
- if (!open) return;
2750
- document.addEventListener("keydown", handleEscape);
2751
- return () => document.removeEventListener("keydown", handleEscape);
2752
- }, [open]);
2753
- React20.useEffect(() => {
3126
+ React19.useEffect(() => {
2754
3127
  if (!open) return;
2755
3128
  const body = document.body;
2756
3129
  const openModals = parseInt(body.dataset.openModals || "0", 10);
@@ -2771,7 +3144,7 @@ var Modal = ({
2771
3144
  }
2772
3145
  };
2773
3146
  }, [open]);
2774
- if (!shouldRender) return null;
3147
+ if (!open && !shouldRender) return null;
2775
3148
  const hasTitle = !!title;
2776
3149
  return /* @__PURE__ */ jsx(
2777
3150
  animated.div,
@@ -2784,7 +3157,10 @@ var Modal = ({
2784
3157
  "aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
2785
3158
  onClick: () => closeOnOverlay && onClose?.(),
2786
3159
  onKeyDown: (e) => {
2787
- if (e.key === "Escape") onClose?.();
3160
+ if (e.key === "Escape") {
3161
+ e.stopPropagation();
3162
+ onClose?.();
3163
+ }
2788
3164
  },
2789
3165
  children: /* @__PURE__ */ jsxs(
2790
3166
  animated.div,
@@ -2806,7 +3182,7 @@ var Modal = ({
2806
3182
  className: "modal_close",
2807
3183
  onClick: onClose,
2808
3184
  "aria-label": closeLabel,
2809
- children: /* @__PURE__ */ jsx(X, { size: 18, "aria-hidden": "true" })
3185
+ children: /* @__PURE__ */ jsx(X, { size: iconSize.md, "aria-hidden": "true" })
2810
3186
  }
2811
3187
  ),
2812
3188
  title && /* @__PURE__ */ jsx("h2", { id: titleId, className: "modal_title", children: title }),
@@ -2822,6 +3198,7 @@ var Modal = ({
2822
3198
  var OtpInput = ({
2823
3199
  length = 6,
2824
3200
  value = "",
3201
+ onValueChange,
2825
3202
  onChange,
2826
3203
  error = false,
2827
3204
  disabled = false,
@@ -2830,12 +3207,12 @@ var OtpInput = ({
2830
3207
  ariaLabel = "OTP \uC785\uB825",
2831
3208
  className
2832
3209
  }) => {
2833
- const inputsRef = React20.useRef([]);
2834
- const isTypingRef = React20.useRef(false);
2835
- React20.useEffect(() => {
3210
+ const inputsRef = React19.useRef([]);
3211
+ const isTypingRef = React19.useRef(false);
3212
+ React19.useEffect(() => {
2836
3213
  if (autoFocus) inputsRef.current[0]?.focus();
2837
3214
  }, [autoFocus]);
2838
- const digits = React20.useMemo(() => {
3215
+ const digits = React19.useMemo(() => {
2839
3216
  const chars = value.split("").slice(0, length);
2840
3217
  while (chars.length < length) chars.push("");
2841
3218
  return chars;
@@ -2844,7 +3221,7 @@ var OtpInput = ({
2844
3221
  inputsRef.current[index]?.focus();
2845
3222
  };
2846
3223
  const updateValue = (newDigits) => {
2847
- onChange?.(newDigits.join(""));
3224
+ (onValueChange ?? onChange)?.(newDigits.join(""));
2848
3225
  };
2849
3226
  const handleChange = (index, e) => {
2850
3227
  const nextDigit = e.target.value;
@@ -2968,20 +3345,22 @@ var getPaginationItems = (page, totalPages) => {
2968
3345
  var Pagination = ({
2969
3346
  page,
2970
3347
  totalPages,
3348
+ onPageChange,
2971
3349
  onChange,
2972
3350
  prevLabel = "Previous page",
2973
3351
  nextLabel = "Next page"
2974
3352
  }) => {
3353
+ const emit = onPageChange ?? onChange;
2975
3354
  const prevDisabled = page <= 1;
2976
3355
  const nextDisabled = page >= totalPages;
2977
- const items = React20.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
3356
+ const items = React19.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
2978
3357
  return /* @__PURE__ */ jsxs("nav", { className: "pagination", "aria-label": "Pagination", children: [
2979
3358
  /* @__PURE__ */ jsx(
2980
3359
  "button",
2981
3360
  {
2982
3361
  type: "button",
2983
3362
  className: "pagination_item",
2984
- onClick: () => onChange(page - 1),
3363
+ onClick: () => emit?.(page - 1),
2985
3364
  disabled: prevDisabled,
2986
3365
  "aria-label": prevLabel,
2987
3366
  children: "\u2039"
@@ -3000,7 +3379,7 @@ var Pagination = ({
3000
3379
  {
3001
3380
  type: "button",
3002
3381
  className: buttonClassName,
3003
- onClick: () => onChange(it),
3382
+ onClick: () => emit?.(it),
3004
3383
  "aria-current": isActive ? "page" : void 0,
3005
3384
  children: it
3006
3385
  }
@@ -3011,7 +3390,7 @@ var Pagination = ({
3011
3390
  {
3012
3391
  type: "button",
3013
3392
  className: "pagination_item",
3014
- onClick: () => onChange(page + 1),
3393
+ onClick: () => emit?.(page + 1),
3015
3394
  disabled: nextDisabled,
3016
3395
  "aria-label": nextLabel,
3017
3396
  children: "\u203A"
@@ -3019,9 +3398,9 @@ var Pagination = ({
3019
3398
  )
3020
3399
  ] });
3021
3400
  };
3022
- var RadioGroupContext = React20.createContext(null);
3401
+ var RadioGroupContext = React19.createContext(null);
3023
3402
  function useRadioGroupContext() {
3024
- return React20.useContext(RadioGroupContext);
3403
+ return React19.useContext(RadioGroupContext);
3025
3404
  }
3026
3405
  var RadioGroup = ({
3027
3406
  value: controlledValue,
@@ -3039,21 +3418,21 @@ var RadioGroup = ({
3039
3418
  ...props
3040
3419
  }) => {
3041
3420
  const isControlled = controlledValue !== void 0;
3042
- const [internalValue, setInternalValue] = React20.useState(defaultValue);
3421
+ const [internalValue, setInternalValue] = React19.useState(defaultValue);
3043
3422
  const value = isControlled ? controlledValue : internalValue;
3044
- const generatedName = React20.useId();
3423
+ const generatedName = React19.useId();
3045
3424
  const name = nameProp ?? generatedName;
3046
- const idPrefix = React20.useId();
3425
+ const idPrefix = React19.useId();
3047
3426
  const labelId = label ? `${idPrefix}-label` : void 0;
3048
3427
  const helperId = supportingText ? `${idPrefix}-help` : void 0;
3049
- const onChange = React20.useCallback(
3428
+ const onChange = React19.useCallback(
3050
3429
  (next) => {
3051
3430
  if (!isControlled) setInternalValue(next);
3052
3431
  onValueChange?.(next);
3053
3432
  },
3054
3433
  [isControlled, onValueChange]
3055
3434
  );
3056
- const ctx = React20.useMemo(
3435
+ const ctx = React19.useMemo(
3057
3436
  () => ({ name, value, onChange, size, disabled }),
3058
3437
  [name, value, onChange, size, disabled]
3059
3438
  );
@@ -3182,7 +3561,15 @@ var Table = ({
3182
3561
  stickyHeader = false,
3183
3562
  ariaLabel,
3184
3563
  className,
3185
- onRowClick
3564
+ onRowClick,
3565
+ sort,
3566
+ onSortChange,
3567
+ selectAllAriaLabel = "\uC804\uCCB4 \uC120\uD0DD",
3568
+ selectRowAriaLabel = (index) => `${index + 1}\uBC88\uC9F8 \uD589 \uC120\uD0DD`,
3569
+ selectable = false,
3570
+ rowKey,
3571
+ selectedKeys,
3572
+ onSelectionChange
3186
3573
  }) => {
3187
3574
  const wrapperClassName = cn(
3188
3575
  "table_wrapper",
@@ -3191,69 +3578,171 @@ var Table = ({
3191
3578
  className
3192
3579
  );
3193
3580
  const isEmpty = !isLoading && data.length === 0;
3581
+ const getRowKey = (item, index) => rowKey?.(item) ?? String(index);
3582
+ const allRowKeys = selectable ? data.map((item, index) => getRowKey(item, index)) : [];
3583
+ const selectedSet = React19.useMemo(() => new Set(selectedKeys ?? []), [selectedKeys]);
3584
+ const selectedCount = allRowKeys.filter((key) => selectedSet.has(key)).length;
3585
+ const isAllSelected = allRowKeys.length > 0 && selectedCount === allRowKeys.length;
3586
+ const isSomeSelected = selectedCount > 0 && !isAllSelected;
3587
+ const handleToggleAll = () => {
3588
+ const pageKeys = new Set(allRowKeys);
3589
+ const offPage = (selectedKeys ?? []).filter((k) => !pageKeys.has(k));
3590
+ onSelectionChange?.(isAllSelected ? offPage : [...offPage, ...allRowKeys]);
3591
+ };
3592
+ const handleToggleRow = (key) => {
3593
+ const current = selectedKeys ?? [];
3594
+ const next = current.includes(key) ? current.filter((k) => k !== key) : [...current, key];
3595
+ onSelectionChange?.(next);
3596
+ };
3597
+ const handleSortClick = (key) => {
3598
+ const next = sort?.key !== key ? { key, direction: "asc" } : sort.direction === "asc" ? { key, direction: "desc" } : void 0;
3599
+ onSortChange?.(next);
3600
+ };
3194
3601
  return /* @__PURE__ */ jsxs("div", { className: wrapperClassName, children: [
3195
3602
  /* @__PURE__ */ jsxs("table", { className: "table", "aria-label": ariaLabel, children: [
3196
- /* @__PURE__ */ jsx("thead", { className: "table_thead", children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx(
3197
- "th",
3198
- {
3199
- scope: "col",
3200
- className: cn("table_th", col.align && `table_align_${col.align}`),
3201
- style: { width: col.width },
3202
- children: col.header
3203
- },
3204
- col.key
3205
- )) }) }),
3603
+ /* @__PURE__ */ jsx("thead", { className: "table_thead", children: /* @__PURE__ */ jsxs("tr", { children: [
3604
+ selectable && /* @__PURE__ */ jsx("th", { scope: "col", className: "table_th table_th_checkbox", children: /* @__PURE__ */ jsx(
3605
+ Checkbox,
3606
+ {
3607
+ "aria-label": selectAllAriaLabel,
3608
+ checked: isAllSelected,
3609
+ indeterminate: isSomeSelected,
3610
+ disabled: isLoading || allRowKeys.length === 0,
3611
+ onChange: handleToggleAll
3612
+ }
3613
+ ) }),
3614
+ columns.map((col) => {
3615
+ const isSortActive = Boolean(col.sortable) && sort?.key === col.key;
3616
+ const direction = isSortActive ? sort?.direction : void 0;
3617
+ const ariaSort = col.sortable ? direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none" : void 0;
3618
+ return /* @__PURE__ */ jsx(
3619
+ "th",
3620
+ {
3621
+ scope: "col",
3622
+ className: cn(
3623
+ "table_th",
3624
+ col.align && `table_align_${col.align}`,
3625
+ col.sortable && "table_th_sortable"
3626
+ ),
3627
+ style: { width: col.width },
3628
+ "aria-sort": ariaSort,
3629
+ children: col.sortable ? /* @__PURE__ */ jsxs(
3630
+ "button",
3631
+ {
3632
+ type: "button",
3633
+ className: "table_sort_button",
3634
+ onClick: () => handleSortClick(col.key),
3635
+ disabled: isLoading,
3636
+ children: [
3637
+ /* @__PURE__ */ jsx("span", { className: "table_sort_label", children: col.header }),
3638
+ direction === "asc" ? /* @__PURE__ */ jsx(
3639
+ ArrowUp,
3640
+ {
3641
+ size: iconSize.sm,
3642
+ className: "table_sort_icon table_sort_icon_active",
3643
+ "aria-hidden": "true"
3644
+ }
3645
+ ) : direction === "desc" ? /* @__PURE__ */ jsx(
3646
+ ArrowDown,
3647
+ {
3648
+ size: iconSize.sm,
3649
+ className: "table_sort_icon table_sort_icon_active",
3650
+ "aria-hidden": "true"
3651
+ }
3652
+ ) : /* @__PURE__ */ jsx(
3653
+ ArrowUpDown,
3654
+ {
3655
+ size: iconSize.sm,
3656
+ className: "table_sort_icon",
3657
+ "aria-hidden": "true"
3658
+ }
3659
+ )
3660
+ ]
3661
+ }
3662
+ ) : col.header
3663
+ },
3664
+ col.key
3665
+ );
3666
+ })
3667
+ ] }) }),
3206
3668
  /* @__PURE__ */ jsx("tbody", { className: "table_tbody", children: isLoading ? Array.from({ length: skeletonRows }).map((_, rowIndex) => (
3207
3669
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows have no stable identity
3208
- /* @__PURE__ */ jsx("tr", { className: "table_row table_row_skeleton", children: columns.map((col) => /* @__PURE__ */ jsx(
3209
- "td",
3210
- {
3211
- className: cn("table_td", col.align && `table_align_${col.align}`),
3212
- style: { width: col.width },
3213
- children: /* @__PURE__ */ jsx(Skeleton, { variant: "text" })
3214
- },
3215
- col.key
3216
- )) }, rowIndex)
3217
- )) : data.map((item, rowIndex) => /* @__PURE__ */ jsx(
3218
- "tr",
3219
- {
3220
- className: cn(
3221
- "table_row",
3222
- hoverable && "table_row_hoverable",
3223
- onRowClick && "table_row_clickable"
3224
- ),
3225
- role: onRowClick ? "button" : void 0,
3226
- tabIndex: onRowClick ? 0 : void 0,
3227
- onClick: onRowClick ? () => onRowClick(item, rowIndex) : void 0,
3228
- onKeyDown: onRowClick ? (e) => {
3229
- if (e.key === "Enter" || e.key === " ") {
3230
- e.preventDefault();
3231
- onRowClick(item, rowIndex);
3232
- }
3233
- } : void 0,
3234
- children: columns.map((col) => /* @__PURE__ */ jsx(
3670
+ /* @__PURE__ */ jsxs("tr", { className: "table_row table_row_skeleton", children: [
3671
+ selectable && /* @__PURE__ */ jsx("td", { className: "table_td table_td_checkbox" }),
3672
+ columns.map((col) => /* @__PURE__ */ jsx(
3235
3673
  "td",
3236
3674
  {
3237
3675
  className: cn("table_td", col.align && `table_align_${col.align}`),
3238
3676
  style: { width: col.width },
3239
- children: (() => {
3240
- if (col.render) return col.render(item, rowIndex);
3241
- const value = item[col.key];
3242
- return value === null || value === void 0 || value === "" ? "-" : String(value);
3243
- })()
3677
+ children: /* @__PURE__ */ jsx(Skeleton, { variant: "text" })
3244
3678
  },
3245
3679
  col.key
3246
3680
  ))
3247
- },
3248
- keyExtractor(item, rowIndex)
3249
- )) })
3681
+ ] }, rowIndex)
3682
+ )) : data.map((item, rowIndex) => {
3683
+ const rowIdentity = selectable ? getRowKey(item, rowIndex) : void 0;
3684
+ const isSelected = rowIdentity !== void 0 && selectedSet.has(rowIdentity);
3685
+ return /* @__PURE__ */ jsxs(
3686
+ "tr",
3687
+ {
3688
+ className: cn(
3689
+ "table_row",
3690
+ hoverable && "table_row_hoverable",
3691
+ onRowClick && "table_row_clickable",
3692
+ isSelected && "table_row_selected"
3693
+ ),
3694
+ "aria-selected": isSelected ? "true" : void 0,
3695
+ role: onRowClick && !selectable ? "button" : void 0,
3696
+ tabIndex: onRowClick ? 0 : void 0,
3697
+ onClick: onRowClick ? () => onRowClick(item, rowIndex) : void 0,
3698
+ onKeyDown: onRowClick ? (e) => {
3699
+ if (e.key === "Enter" || e.key === " ") {
3700
+ e.preventDefault();
3701
+ onRowClick(item, rowIndex);
3702
+ }
3703
+ } : void 0,
3704
+ children: [
3705
+ selectable && rowIdentity !== void 0 && /* @__PURE__ */ jsx(
3706
+ "td",
3707
+ {
3708
+ className: "table_td table_td_checkbox",
3709
+ onClick: (e) => e.stopPropagation(),
3710
+ onKeyDown: (e) => e.stopPropagation(),
3711
+ children: /* @__PURE__ */ jsx(
3712
+ Checkbox,
3713
+ {
3714
+ "aria-label": selectRowAriaLabel(rowIndex),
3715
+ checked: isSelected,
3716
+ onChange: () => handleToggleRow(rowIdentity)
3717
+ }
3718
+ )
3719
+ }
3720
+ ),
3721
+ columns.map((col) => /* @__PURE__ */ jsx(
3722
+ "td",
3723
+ {
3724
+ className: cn("table_td", col.align && `table_align_${col.align}`),
3725
+ style: { width: col.width },
3726
+ children: (() => {
3727
+ if (col.render) return col.render(item, rowIndex);
3728
+ const value = item[col.key];
3729
+ return value === null || value === void 0 || value === "" ? "-" : String(value);
3730
+ })()
3731
+ },
3732
+ col.key
3733
+ ))
3734
+ ]
3735
+ },
3736
+ keyExtractor(item, rowIndex)
3737
+ );
3738
+ }) })
3250
3739
  ] }),
3251
3740
  isEmpty && /* @__PURE__ */ jsx("div", { className: "table_empty", role: "status", children: emptyMessage })
3252
3741
  ] });
3253
3742
  };
3254
- var ThemeContext = React20.createContext(null);
3743
+ var ThemeContext = React19.createContext(null);
3255
3744
  var useTheme = () => {
3256
- const ctx = React20.useContext(ThemeContext);
3745
+ const ctx = React19.useContext(ThemeContext);
3257
3746
  if (!ctx) {
3258
3747
  throw new Error(
3259
3748
  '[Bigtablet DS] useTheme\uB294 <ThemeProvider> \uC548\uC5D0\uC11C\uB9CC \uC0AC\uC6A9 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n\n\uC571 \uCD5C\uC0C1\uB2E8\uC5D0 <ThemeProvider>\uB85C \uAC10\uC2F8\uC8FC\uC138\uC694:\n <ThemeProvider mode="system">\n <YourApp />\n </ThemeProvider>'
@@ -3277,14 +3766,15 @@ var ThemeProvider = ({
3277
3766
  targetSelector,
3278
3767
  children
3279
3768
  }) => {
3280
- const [mode, setModeState] = React20.useState(() => {
3281
- return readStored(storageKey) ?? defaultMode;
3282
- });
3283
- const [systemDark, setSystemDark] = React20.useState(() => {
3284
- if (!isClient) return false;
3285
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
3286
- });
3287
- React20.useEffect(() => {
3769
+ const [mode, setModeState] = React19.useState(defaultMode);
3770
+ const [systemDark, setSystemDark] = React19.useState(false);
3771
+ React19.useEffect(() => {
3772
+ setModeState(readStored(storageKey) ?? defaultMode);
3773
+ if (isClient) {
3774
+ setSystemDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
3775
+ }
3776
+ }, [storageKey, defaultMode]);
3777
+ React19.useEffect(() => {
3288
3778
  if (!isClient) return;
3289
3779
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
3290
3780
  const handler = (e) => setSystemDark(e.matches);
@@ -3292,7 +3782,7 @@ var ThemeProvider = ({
3292
3782
  return () => mq.removeEventListener("change", handler);
3293
3783
  }, []);
3294
3784
  const resolved = mode === "system" ? systemDark ? "dark" : "light" : mode;
3295
- React20.useEffect(() => {
3785
+ React19.useEffect(() => {
3296
3786
  if (!isClient) return;
3297
3787
  const target = targetSelector ? document.querySelector(targetSelector) : document.documentElement;
3298
3788
  if (!target) return;
@@ -3302,7 +3792,7 @@ var ThemeProvider = ({
3302
3792
  target.setAttribute("data-theme", mode);
3303
3793
  }
3304
3794
  }, [mode, targetSelector]);
3305
- const setMode = React20.useCallback(
3795
+ const setMode = React19.useCallback(
3306
3796
  (next) => {
3307
3797
  setModeState(next);
3308
3798
  if (storageKey && isClient) {
@@ -3314,13 +3804,13 @@ var ThemeProvider = ({
3314
3804
  },
3315
3805
  [storageKey]
3316
3806
  );
3317
- const value = React20.useMemo(
3807
+ const value = React19.useMemo(
3318
3808
  () => ({ mode, resolved, setMode }),
3319
3809
  [mode, resolved, setMode]
3320
3810
  );
3321
3811
  return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
3322
3812
  };
3323
- var ClearIcon = () => /* @__PURE__ */ jsx(X, { size: 20, "aria-hidden": "true" });
3813
+ var ClearIcon = () => /* @__PURE__ */ jsx(X, { size: iconSize.lg, "aria-hidden": "true" });
3324
3814
  var TextField = ({
3325
3815
  id,
3326
3816
  label,
@@ -3333,6 +3823,7 @@ var TextField = ({
3333
3823
  fullWidth,
3334
3824
  size = "md",
3335
3825
  className,
3826
+ onValueChange,
3336
3827
  onChangeAction,
3337
3828
  imeStrategy = "delayed",
3338
3829
  value,
@@ -3363,10 +3854,10 @@ var TextField = ({
3363
3854
  setInnerValue(nextValue);
3364
3855
  if (nextValue !== lastEmittedValueRef.current) {
3365
3856
  lastEmittedValueRef.current = nextValue;
3366
- onChangeAction?.(nextValue);
3857
+ (onValueChange ?? onChangeAction)?.(nextValue);
3367
3858
  }
3368
3859
  },
3369
- [onChangeAction]
3860
+ [onValueChange, onChangeAction]
3370
3861
  );
3371
3862
  const handleClear = useCallback(() => {
3372
3863
  emit("");
@@ -3416,7 +3907,7 @@ var TextField = ({
3416
3907
  setInnerValue(rawValue);
3417
3908
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3418
3909
  lastEmittedValueRef.current = rawValue;
3419
- onChangeAction?.(rawValue);
3910
+ (onValueChange ?? onChangeAction)?.(rawValue);
3420
3911
  }
3421
3912
  return;
3422
3913
  }
@@ -3446,6 +3937,7 @@ var Textarea = ({
3446
3937
  fullWidth,
3447
3938
  size = "md",
3448
3939
  className,
3940
+ onValueChange,
3449
3941
  onChangeAction,
3450
3942
  imeStrategy = "delayed",
3451
3943
  value,
@@ -3513,7 +4005,7 @@ var Textarea = ({
3513
4005
  setInnerValue(nextValue);
3514
4006
  if (nextValue !== lastEmittedValueRef.current) {
3515
4007
  lastEmittedValueRef.current = nextValue;
3516
- onChangeAction?.(nextValue);
4008
+ (onValueChange ?? onChangeAction)?.(nextValue);
3517
4009
  }
3518
4010
  };
3519
4011
  return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
@@ -3545,7 +4037,7 @@ var Textarea = ({
3545
4037
  setInnerValue(rawValue);
3546
4038
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3547
4039
  lastEmittedValueRef.current = rawValue;
3548
- onChangeAction?.(rawValue);
4040
+ (onValueChange ?? onChangeAction)?.(rawValue);
3549
4041
  }
3550
4042
  return;
3551
4043
  }
@@ -3560,24 +4052,24 @@ var Textarea = ({
3560
4052
  ] });
3561
4053
  };
3562
4054
  Textarea.displayName = "Textarea";
3563
- var ToastContext = React20.createContext(null);
4055
+ var ToastContext = React19.createContext(null);
3564
4056
  var VARIANT_ICONS = {
3565
- success: /* @__PURE__ */ jsx(CheckCircle2, { size: 18 }),
3566
- error: /* @__PURE__ */ jsx(XCircle, { size: 18 }),
3567
- warning: /* @__PURE__ */ jsx(AlertTriangle, { size: 18 }),
3568
- info: /* @__PURE__ */ jsx(Info, { size: 18 }),
3569
- default: /* @__PURE__ */ jsx(Bell, { size: 18 })
4057
+ success: /* @__PURE__ */ jsx(CheckCircle2, { size: iconSize.md }),
4058
+ error: /* @__PURE__ */ jsx(XCircle, { size: iconSize.md }),
4059
+ warning: /* @__PURE__ */ jsx(AlertTriangle, { size: iconSize.md }),
4060
+ info: /* @__PURE__ */ jsx(Info, { size: iconSize.md }),
4061
+ default: /* @__PURE__ */ jsx(Bell, { size: iconSize.md })
3570
4062
  };
3571
4063
  var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
3572
- const [visible, setVisible] = React20.useState(true);
3573
- const closingRef = React20.useRef(false);
4064
+ const [visible, setVisible] = React19.useState(true);
4065
+ const closingRef = React19.useRef(false);
3574
4066
  const style = useSpringPresence({
3575
4067
  visible,
3576
4068
  from: "translateX(20px)",
3577
4069
  // 우측에서 슬라이드 인
3578
4070
  onExitComplete: () => onRemove(item.id)
3579
4071
  });
3580
- const close = React20.useCallback(() => {
4072
+ const close = React19.useCallback(() => {
3581
4073
  if (closingRef.current) return;
3582
4074
  closingRef.current = true;
3583
4075
  setVisible(false);
@@ -3591,7 +4083,7 @@ var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
3591
4083
  children: [
3592
4084
  /* @__PURE__ */ jsx("span", { className: `toast_icon toast_icon_${item.variant}`, "aria-hidden": "true", children: VARIANT_ICONS[item.variant] }),
3593
4085
  /* @__PURE__ */ jsx("span", { className: "toast_message", children: item.message }),
3594
- /* @__PURE__ */ jsx("button", { type: "button", className: "toast_close", onClick: close, "aria-label": closeAriaLabel, children: /* @__PURE__ */ jsx(X, { size: 14 }) }),
4086
+ /* @__PURE__ */ jsx("button", { type: "button", className: "toast_close", onClick: close, "aria-label": closeAriaLabel, children: /* @__PURE__ */ jsx(X, { size: iconSize.xs }) }),
3595
4087
  /* @__PURE__ */ jsx(
3596
4088
  "div",
3597
4089
  {
@@ -3610,22 +4102,23 @@ var ToastProvider = ({
3610
4102
  maxCount = 5,
3611
4103
  closeAriaLabel = "Close"
3612
4104
  }) => {
3613
- const [toasts, setToasts] = React20.useState([]);
3614
- const [isMounted, setIsMounted] = React20.useState(false);
3615
- React20.useEffect(() => {
4105
+ const [toasts, setToasts] = React19.useState([]);
4106
+ const [isMounted, setIsMounted] = React19.useState(false);
4107
+ React19.useEffect(() => {
3616
4108
  setIsMounted(true);
3617
4109
  }, []);
3618
- const addToast = React20.useCallback(
4110
+ const addToast = React19.useCallback(
3619
4111
  (message, variant, duration = 3e3) => {
3620
4112
  const id = crypto.randomUUID();
3621
4113
  setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
3622
4114
  },
3623
4115
  [maxCount]
3624
4116
  );
3625
- const removeToast = React20.useCallback((id) => {
4117
+ const removeToast = React19.useCallback((id) => {
3626
4118
  setToasts((prev) => prev.filter((t) => t.id !== id));
3627
4119
  }, []);
3628
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
4120
+ const contextValue = React19.useMemo(() => ({ addToast }), [addToast]);
4121
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
3629
4122
  children,
3630
4123
  isMounted && createPortal(
3631
4124
  // biome-ignore lint/a11y/useSemanticElements: <section> would create unwanted nesting in portal; role=region is the WAI-ARIA equivalent for live announcements
@@ -3675,6 +4168,7 @@ var useToast = () => {
3675
4168
  var Toggle = ({
3676
4169
  checked,
3677
4170
  defaultChecked,
4171
+ onCheckedChange,
3678
4172
  onChange,
3679
4173
  size = "sm",
3680
4174
  disabled,
@@ -3684,13 +4178,13 @@ var Toggle = ({
3684
4178
  ...props
3685
4179
  }) => {
3686
4180
  const isControlled = checked !== void 0;
3687
- const [innerChecked, setInnerChecked] = React20.useState(!!defaultChecked);
4181
+ const [innerChecked, setInnerChecked] = React19.useState(!!defaultChecked);
3688
4182
  const isOn = isControlled ? !!checked : innerChecked;
3689
4183
  const handleToggle = () => {
3690
4184
  if (disabled) return;
3691
4185
  const next = !isOn;
3692
4186
  if (!isControlled) setInnerChecked(next);
3693
- onChange?.(next);
4187
+ (onCheckedChange ?? onChange)?.(next);
3694
4188
  };
3695
4189
  const rootClassName = cn(
3696
4190
  "toggle",
@@ -3751,6 +4245,7 @@ var Container = ({
3751
4245
  size = "xl",
3752
4246
  center = true,
3753
4247
  as: Tag = "div",
4248
+ ref,
3754
4249
  className,
3755
4250
  children,
3756
4251
  ...props
@@ -3758,6 +4253,7 @@ var Container = ({
3758
4253
  return /* @__PURE__ */ jsx(
3759
4254
  Tag,
3760
4255
  {
4256
+ ref,
3761
4257
  className: cn("container", `container_size_${size}`, center && "container_center", className),
3762
4258
  ...props,
3763
4259
  children
@@ -3768,6 +4264,7 @@ var Section = ({
3768
4264
  spacing: spacing2 = "md",
3769
4265
  bg = "default",
3770
4266
  as: Tag = "section",
4267
+ ref,
3771
4268
  className,
3772
4269
  children,
3773
4270
  ...props
@@ -3775,6 +4272,7 @@ var Section = ({
3775
4272
  return /* @__PURE__ */ jsx(
3776
4273
  Tag,
3777
4274
  {
4275
+ ref,
3778
4276
  className: cn(
3779
4277
  "section",
3780
4278
  `section_spacing_${spacing2}`,
@@ -3793,6 +4291,7 @@ var Stack = ({
3793
4291
  justify,
3794
4292
  wrap,
3795
4293
  as: Tag = "div",
4294
+ ref,
3796
4295
  className,
3797
4296
  children,
3798
4297
  style,
@@ -3801,6 +4300,7 @@ var Stack = ({
3801
4300
  return /* @__PURE__ */ jsx(
3802
4301
  Tag,
3803
4302
  {
4303
+ ref,
3804
4304
  className: cn(
3805
4305
  "stack",
3806
4306
  `stack_${direction}`,
@@ -3823,6 +4323,7 @@ var Grid = ({
3823
4323
  colGap,
3824
4324
  singleColOnMobile = true,
3825
4325
  as: Tag = "div",
4326
+ ref,
3826
4327
  className,
3827
4328
  children,
3828
4329
  style,
@@ -3832,6 +4333,7 @@ var Grid = ({
3832
4333
  return /* @__PURE__ */ jsx(
3833
4334
  Tag,
3834
4335
  {
4336
+ ref,
3835
4337
  className: cn("grid_layout", singleColOnMobile && "grid_layout_mobile_single", className),
3836
4338
  style: {
3837
4339
  "--grid-cols": gridTemplateColumns,
@@ -3846,4 +4348,4 @@ var Grid = ({
3846
4348
  );
3847
4349
  };
3848
4350
 
3849
- export { Accordion, AlertProvider, Avatar, Badge, BottomNav, BottomNavItem, BottomNavSpacer, Breadcrumb, Button, Card, Checkbox, Chip, Container, DatePicker, Divider, Dropdown, EmptyState, ErrorState, FileInput, Grid, Hero, Icon, IconButton, LinearProgress, ListItem, MediaCard, Menu, Modal, NavBar, NavLink, OtpInput, Pagination, Popover, Radio, RadioGroup, Section, Sidebar, SidebarItem, SidebarSection, Skeleton, Spinner, Stack, Tab, TabList, TabPanel, Table, Tabs, TextField, Textarea, ThemeProvider, ToastProvider, Toggle, Tooltip, TopLoading, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, cn, colors, elevation, motion, opacity, radius, skeleton, spacing, typography, useAlert, useFocusTrap, useSpringHover, useSpringPresence, useTheme, useToast, zIndex };
4351
+ export { Accordion, AlertProvider, Avatar, Badge, BottomNav, BottomNavItem, BottomNavSpacer, Breadcrumb, Button, Card, Checkbox, Chip, Container, DatePicker, Divider, Drawer, Dropdown, EmptyState, ErrorState, FileInput, Grid, Hero, Icon, IconButton, LinearProgress, ListItem, MediaCard, Menu, Modal, NavBar, NavLink, OtpInput, Pagination, Popover, Radio, RadioGroup, Section, Sidebar, SidebarItem, SidebarSection, Skeleton, Spinner, Stack, Tab, TabList, TabPanel, Table, Tabs, TextField, Textarea, ThemeProvider, ToastProvider, Toggle, Tooltip, TopLoading, a11y, baseBorderWidth, baseColors, baseTypography, borderWidth, breakpoints, cn, colors, elevation, iconSize, motion, opacity, radius, skeleton, spacing, typography, useAlert, useFocusTrap, useReducedMotion, useSpringHover, useSpringPresence, useTheme, useToast, zIndex };