@bigtablet/design-system 3.2.1 → 3.3.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,7 +1,7 @@
1
1
  "use client";
2
2
  import './index.css';
3
- import * as React19 from 'react';
4
- import { createContext, useRef, useState, useLayoutEffect, useId, useEffect, useContext, useCallback, useMemo, Fragment } from 'react';
3
+ import * as React20 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
6
  import { ChevronDown, Globe, ChevronRight, ChevronLeft, Check, Image, X, TriangleAlert, XCircle, AlertTriangle, CheckCircle2, Info, Bell } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
@@ -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 = React19.useRef(null);
42
- React19.useEffect(() => {
41
+ const previousActiveElement = React20.useRef(null);
42
+ React20.useEffect(() => {
43
43
  if (!isActive) return;
44
44
  const container = containerRef.current;
45
45
  if (!container) return;
@@ -87,14 +87,48 @@ function useFocusTrap(containerRef, isActive) {
87
87
  };
88
88
  }, [isActive, containerRef]);
89
89
  }
90
+ var QUERY = "(prefers-reduced-motion: reduce)";
91
+ var mqCache = null;
92
+ var cachedFor = null;
93
+ function getMediaQuery() {
94
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
95
+ mqCache = null;
96
+ cachedFor = null;
97
+ return null;
98
+ }
99
+ if (!mqCache || cachedFor !== window.matchMedia) {
100
+ mqCache = window.matchMedia(QUERY);
101
+ cachedFor = window.matchMedia;
102
+ }
103
+ return mqCache;
104
+ }
105
+ function subscribe(onStoreChange) {
106
+ const mq = getMediaQuery();
107
+ if (!mq) return () => {
108
+ };
109
+ mq.addEventListener("change", onStoreChange);
110
+ return () => mq.removeEventListener("change", onStoreChange);
111
+ }
112
+ function getSnapshot() {
113
+ return getMediaQuery()?.matches ?? false;
114
+ }
115
+ function getServerSnapshot() {
116
+ return false;
117
+ }
118
+ function useReducedMotion() {
119
+ return React20.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
120
+ }
90
121
  var useSafeLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
91
122
  function useSpringHover({
92
123
  scale = 1.02,
93
124
  lift = -2
94
125
  } = {}) {
95
- const [hovered, setHovered] = React19.useState(false);
126
+ const [hovered, setHovered] = React20.useState(false);
127
+ const reduced = useReducedMotion();
96
128
  const style = useSpring({
97
129
  transform: hovered ? `translateY(${lift}px) scale(${scale})` : "translateY(0px) scale(1)",
130
+ // reduced-motion: lift/scale 모션 제거 (WCAG 2.3.3)
131
+ immediate: reduced,
98
132
  config: { tension: 320, friction: 22 }
99
133
  });
100
134
  const bind = {
@@ -110,12 +144,15 @@ function useSpringPresence({
110
144
  from = "translateY(8px)",
111
145
  onExitComplete
112
146
  }) {
147
+ const reduced = useReducedMotion();
113
148
  return useSpring({
114
149
  from: { opacity: 0, transform: from },
115
150
  to: {
116
151
  opacity: visible ? 1 : 0,
117
152
  transform: visible ? "translateY(0px)" : from
118
153
  },
154
+ // reduced-motion: 진입/퇴출 모션 없이 즉시 최종 상태로 점프 (WCAG 2.3.3). onRest는 그대로 발화.
155
+ immediate: reduced,
119
156
  config: {
120
157
  tension: 280,
121
158
  // Vercel 식 부드러운 spring
@@ -135,18 +172,19 @@ var Accordion = ({
135
172
  multiple = false,
136
173
  defaultOpenKeys = [],
137
174
  openKeys: controlledKeys,
175
+ onValueChange,
138
176
  onChange,
139
177
  className,
140
178
  ...props
141
179
  }) => {
142
180
  const isControlled = controlledKeys !== void 0;
143
- const [internalKeys, setInternalKeys] = React19.useState(defaultOpenKeys);
181
+ const [internalKeys, setInternalKeys] = React20.useState(defaultOpenKeys);
144
182
  const open = isControlled ? controlledKeys ?? [] : internalKeys;
145
183
  const toggle = (key) => {
146
184
  const isOpen = open.includes(key);
147
185
  const next = isOpen ? open.filter((k) => k !== key) : multiple ? [...open, key] : [key];
148
186
  if (!isControlled) setInternalKeys(next);
149
- onChange?.(next);
187
+ (onValueChange ?? onChange)?.(next);
150
188
  };
151
189
  return /* @__PURE__ */ jsx("div", { className: cn("accordion", className), ...props, children: items.map((item) => {
152
190
  const isOpen = open.includes(item.key);
@@ -183,6 +221,7 @@ var Accordion = ({
183
221
  role: "region",
184
222
  "aria-labelledby": headerId,
185
223
  "aria-hidden": !isOpen,
224
+ inert: !isOpen,
186
225
  className: cn("accordion_panel", isOpen && "accordion_panel_open"),
187
226
  children: /* @__PURE__ */ jsx("div", { className: "accordion_panel_wrap", children: /* @__PURE__ */ jsx("div", { className: "accordion_content", children: item.content }) })
188
227
  }
@@ -206,7 +245,7 @@ var Avatar = ({
206
245
  style,
207
246
  ...props
208
247
  }) => {
209
- const [imgFailed, setImgFailed] = React19.useState(false);
248
+ const [imgFailed, setImgFailed] = React20.useState(false);
210
249
  const showImage = src && !imgFailed;
211
250
  const initials = getInitials(name);
212
251
  return /* @__PURE__ */ jsx(
@@ -424,11 +463,12 @@ var ErrorState = ({
424
463
  );
425
464
  };
426
465
  var Menu = ({ items, trigger, align = "start" }) => {
427
- const [open, setOpen] = React19.useState(false);
428
- const wrapperRef = React19.useRef(null);
429
- const menuId = React19.useId();
466
+ const [open, setOpen] = React20.useState(false);
467
+ const wrapperRef = React20.useRef(null);
468
+ const itemRefs = React20.useRef([]);
469
+ const menuId = React20.useId();
430
470
  const style = useSpringPresence({ visible: open, from: "translateY(-4px)" });
431
- React19.useEffect(() => {
471
+ React20.useEffect(() => {
432
472
  if (!open) return;
433
473
  const handleClick = (e) => {
434
474
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -443,7 +483,53 @@ var Menu = ({ items, trigger, align = "start" }) => {
443
483
  document.removeEventListener("keydown", handleEsc);
444
484
  };
445
485
  }, [open]);
446
- const triggerWithProps = React19.cloneElement(
486
+ React20.useEffect(() => {
487
+ const active = document.activeElement;
488
+ if (!open || active && itemRefs.current.includes(active)) return;
489
+ const first = items.findIndex((it) => !it.disabled);
490
+ if (first >= 0) itemRefs.current[first]?.focus();
491
+ }, [open, items]);
492
+ const moveFocus = (dir) => {
493
+ const enabled = items.flatMap((it, i) => it.disabled ? [] : [i]);
494
+ if (enabled.length === 0) return;
495
+ if (dir === "first") return void itemRefs.current[enabled[0]]?.focus();
496
+ if (dir === "last") return void itemRefs.current[enabled[enabled.length - 1]]?.focus();
497
+ const pos = enabled.findIndex((i) => itemRefs.current[i] === document.activeElement);
498
+ const next = pos < 0 ? dir === 1 ? 0 : enabled.length - 1 : (pos + dir + enabled.length) % enabled.length;
499
+ itemRefs.current[enabled[next]]?.focus();
500
+ };
501
+ const handleMenuKeyDown = (e) => {
502
+ if (["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(e.key)) {
503
+ e.stopPropagation();
504
+ }
505
+ switch (e.key) {
506
+ case "ArrowDown":
507
+ e.preventDefault();
508
+ moveFocus(1);
509
+ break;
510
+ case "ArrowUp":
511
+ e.preventDefault();
512
+ moveFocus(-1);
513
+ break;
514
+ case "Home":
515
+ e.preventDefault();
516
+ moveFocus("first");
517
+ break;
518
+ case "End":
519
+ e.preventDefault();
520
+ moveFocus("last");
521
+ break;
522
+ case "Escape":
523
+ e.preventDefault();
524
+ setOpen(false);
525
+ wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
526
+ break;
527
+ case "Tab":
528
+ setOpen(false);
529
+ break;
530
+ }
531
+ };
532
+ const triggerWithProps = React20.cloneElement(
447
533
  trigger,
448
534
  {
449
535
  onClick: () => setOpen((o) => !o),
@@ -461,11 +547,16 @@ var Menu = ({ items, trigger, align = "start" }) => {
461
547
  role: "menu",
462
548
  style,
463
549
  className: cn("menu", `menu_align_${align}`),
464
- children: items.map((item) => /* @__PURE__ */ jsxs(
550
+ onKeyDown: handleMenuKeyDown,
551
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(
465
552
  "button",
466
553
  {
554
+ ref: (el) => {
555
+ itemRefs.current[index] = el;
556
+ },
467
557
  type: "button",
468
558
  role: "menuitem",
559
+ tabIndex: -1,
469
560
  disabled: item.disabled,
470
561
  className: cn(
471
562
  "menu_item",
@@ -506,7 +597,7 @@ var NavBar = ({
506
597
  const linksRef = useRef(null);
507
598
  const [indicator, setIndicator] = useState(null);
508
599
  const [hasMounted, setHasMounted] = useState(false);
509
- useLayoutEffect(() => {
600
+ useSafeLayoutEffect(() => {
510
601
  const links = linksRef.current;
511
602
  if (!links) return;
512
603
  const update = () => {
@@ -577,6 +668,7 @@ var NavBar = ({
577
668
  var LocaleSwitcher = ({ locale }) => {
578
669
  const [open, setOpen] = useState(false);
579
670
  const wrapperRef = useRef(null);
671
+ const itemRefs = useRef([]);
580
672
  const menuId = useId();
581
673
  const currentOption = locale.options.find((o) => o.value === locale.current);
582
674
  useEffect(() => {
@@ -594,8 +686,53 @@ var LocaleSwitcher = ({ locale }) => {
594
686
  document.removeEventListener("keydown", escHandler);
595
687
  };
596
688
  }, [open]);
689
+ useEffect(() => {
690
+ const active = document.activeElement;
691
+ if (!open || active && itemRefs.current.includes(active)) return;
692
+ itemRefs.current[0]?.focus();
693
+ }, [open]);
694
+ const moveFocus = (dir) => {
695
+ const n = locale.options.length;
696
+ if (n === 0) return;
697
+ if (dir === "first") return void itemRefs.current[0]?.focus();
698
+ if (dir === "last") return void itemRefs.current[n - 1]?.focus();
699
+ const pos = itemRefs.current.indexOf(document.activeElement);
700
+ const next = pos < 0 ? dir === 1 ? 0 : n - 1 : (pos + dir + n) % n;
701
+ itemRefs.current[next]?.focus();
702
+ };
703
+ const handleMenuKeyDown = (e) => {
704
+ if (["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(e.key)) {
705
+ e.stopPropagation();
706
+ }
707
+ switch (e.key) {
708
+ case "ArrowDown":
709
+ e.preventDefault();
710
+ moveFocus(1);
711
+ break;
712
+ case "ArrowUp":
713
+ e.preventDefault();
714
+ moveFocus(-1);
715
+ break;
716
+ case "Home":
717
+ e.preventDefault();
718
+ moveFocus("first");
719
+ break;
720
+ case "End":
721
+ e.preventDefault();
722
+ moveFocus("last");
723
+ break;
724
+ case "Escape":
725
+ e.preventDefault();
726
+ setOpen(false);
727
+ wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
728
+ break;
729
+ case "Tab":
730
+ setOpen(false);
731
+ break;
732
+ }
733
+ };
597
734
  const handleSelect = (value) => {
598
- locale.onChange(value);
735
+ (locale.onValueChange ?? locale.onChange)?.(value);
599
736
  setOpen(false);
600
737
  };
601
738
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "nav_bar_locale", children: [
@@ -615,19 +752,32 @@ var LocaleSwitcher = ({ locale }) => {
615
752
  ]
616
753
  }
617
754
  ),
618
- 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(
619
- "button",
755
+ open && /* @__PURE__ */ jsx(
756
+ "ul",
620
757
  {
621
- type: "button",
622
- role: "menuitem",
623
- className: cn(
624
- "nav_bar_locale_item",
625
- opt.value === locale.current && "nav_bar_locale_item_active"
626
- ),
627
- onClick: () => handleSelect(opt.value),
628
- children: opt.label
758
+ id: menuId,
759
+ role: "menu",
760
+ className: "nav_bar_locale_menu",
761
+ onKeyDown: handleMenuKeyDown,
762
+ children: locale.options.map((opt, index) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
763
+ "button",
764
+ {
765
+ ref: (el) => {
766
+ itemRefs.current[index] = el;
767
+ },
768
+ type: "button",
769
+ role: "menuitem",
770
+ tabIndex: -1,
771
+ className: cn(
772
+ "nav_bar_locale_item",
773
+ opt.value === locale.current && "nav_bar_locale_item_active"
774
+ ),
775
+ onClick: () => handleSelect(opt.value),
776
+ children: opt.label
777
+ }
778
+ ) }, opt.value))
629
779
  }
630
- ) }, opt.value)) })
780
+ )
631
781
  ] });
632
782
  };
633
783
  var NavLink = ({ active, className, children, ...props }) => {
@@ -662,9 +812,9 @@ var Sidebar = ({
662
812
  ...props
663
813
  }) => {
664
814
  const isControlled = collapsedProp !== void 0;
665
- const [internalCollapsed, setInternalCollapsed] = React19.useState(defaultCollapsed);
815
+ const [internalCollapsed, setInternalCollapsed] = React20.useState(defaultCollapsed);
666
816
  const collapsed = isControlled ? collapsedProp : internalCollapsed;
667
- const toggle = React19.useCallback(() => {
817
+ const toggle = React20.useCallback(() => {
668
818
  const next = !collapsed;
669
819
  if (!isControlled) setInternalCollapsed(next);
670
820
  onCollapsedChange?.(next);
@@ -737,9 +887,9 @@ var SidebarSection = ({ label, className, children, ...props }) => {
737
887
  children
738
888
  ] });
739
889
  };
740
- var TabsContext = React19.createContext(null);
890
+ var TabsContext = React20.createContext(null);
741
891
  function useTabsContext() {
742
- const ctx = React19.useContext(TabsContext);
892
+ const ctx = React20.useContext(TabsContext);
743
893
  if (!ctx) {
744
894
  throw new Error(
745
895
  '[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>'
@@ -758,17 +908,17 @@ var Tabs = ({
758
908
  ...props
759
909
  }) => {
760
910
  const isControlled = controlledValue !== void 0;
761
- const [internalValue, setInternalValue] = React19.useState(defaultValue);
911
+ const [internalValue, setInternalValue] = React20.useState(defaultValue);
762
912
  const value = isControlled ? controlledValue : internalValue;
763
- const setValue = React19.useCallback(
913
+ const setValue = React20.useCallback(
764
914
  (v) => {
765
915
  if (!isControlled) setInternalValue(v);
766
916
  onValueChange?.(v);
767
917
  },
768
918
  [isControlled, onValueChange]
769
919
  );
770
- const idPrefix = React19.useId();
771
- const ctx = React19.useMemo(
920
+ const idPrefix = React20.useId();
921
+ const ctx = React20.useMemo(
772
922
  () => ({ value, setValue, variant, size, idPrefix }),
773
923
  [value, setValue, variant, size, idPrefix]
774
924
  );
@@ -776,10 +926,10 @@ var Tabs = ({
776
926
  };
777
927
  var TabList = ({ ariaLabel, className, children, ...props }) => {
778
928
  const { variant } = useTabsContext();
779
- const listRef = React19.useRef(null);
780
- const [indicator, setIndicator] = React19.useState(null);
781
- const [hasMounted, setHasMounted] = React19.useState(false);
782
- React19.useLayoutEffect(() => {
929
+ const listRef = React20.useRef(null);
930
+ const [indicator, setIndicator] = React20.useState(null);
931
+ const [hasMounted, setHasMounted] = React20.useState(false);
932
+ useSafeLayoutEffect(() => {
783
933
  const list = listRef.current;
784
934
  if (!list) return;
785
935
  const update = () => {
@@ -927,15 +1077,15 @@ var Popover = ({
927
1077
  className
928
1078
  }) => {
929
1079
  const isControlled = openProp !== void 0;
930
- const [internalOpen, setInternalOpen] = React19.useState(defaultOpen);
1080
+ const [internalOpen, setInternalOpen] = React20.useState(defaultOpen);
931
1081
  const open = isControlled ? openProp : internalOpen;
932
- const [shouldRender, setShouldRender] = React19.useState(open ?? false);
1082
+ const [shouldRender, setShouldRender] = React20.useState(open ?? false);
933
1083
  if (open && !shouldRender) setShouldRender(true);
934
- const wrapperRef = React19.useRef(null);
935
- const popoverRef = React19.useRef(null);
936
- const previousFocusRef = React19.useRef(null);
937
- const popoverId = React19.useId();
938
- const setOpen = React19.useCallback(
1084
+ const wrapperRef = React20.useRef(null);
1085
+ const popoverRef = React20.useRef(null);
1086
+ const previousFocusRef = React20.useRef(null);
1087
+ const popoverId = React20.useId();
1088
+ const setOpen = React20.useCallback(
939
1089
  (next) => {
940
1090
  if (!isControlled) setInternalOpen(next);
941
1091
  onOpenChange?.(next);
@@ -955,7 +1105,7 @@ var Popover = ({
955
1105
  }
956
1106
  })();
957
1107
  const style = useSpringPresence({ visible: open, from: fromTransform, onExitComplete: () => setShouldRender(false) });
958
- React19.useEffect(() => {
1108
+ React20.useEffect(() => {
959
1109
  if (!open) return;
960
1110
  previousFocusRef.current = document.activeElement;
961
1111
  const node = popoverRef.current;
@@ -963,7 +1113,7 @@ var Popover = ({
963
1113
  const focusable = node.querySelector(FOCUSABLE_SELECTORS2);
964
1114
  (focusable ?? node).focus();
965
1115
  }, [open]);
966
- React19.useEffect(() => {
1116
+ React20.useEffect(() => {
967
1117
  if (!open) return;
968
1118
  const handleClick = (e) => {
969
1119
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -981,7 +1131,7 @@ var Popover = ({
981
1131
  document.removeEventListener("keydown", handleKeyDown);
982
1132
  };
983
1133
  }, [open, setOpen]);
984
- const triggerWithProps = React19.cloneElement(
1134
+ const triggerWithProps = React20.cloneElement(
985
1135
  trigger,
986
1136
  {
987
1137
  onClick: (e) => {
@@ -1020,18 +1170,18 @@ var Tooltip = ({
1020
1170
  disabled = false,
1021
1171
  children
1022
1172
  }) => {
1023
- const [open, setOpen] = React19.useState(false);
1024
- const timerRef = React19.useRef(null);
1025
- const tooltipId = React19.useId();
1026
- const show = React19.useCallback(() => {
1173
+ const [open, setOpen] = React20.useState(false);
1174
+ const timerRef = React20.useRef(null);
1175
+ const tooltipId = React20.useId();
1176
+ const show = React20.useCallback(() => {
1027
1177
  if (timerRef.current) clearTimeout(timerRef.current);
1028
1178
  timerRef.current = setTimeout(() => setOpen(true), delay);
1029
1179
  }, [delay]);
1030
- const hide = React19.useCallback(() => {
1180
+ const hide = React20.useCallback(() => {
1031
1181
  if (timerRef.current) clearTimeout(timerRef.current);
1032
1182
  setOpen(false);
1033
1183
  }, []);
1034
- React19.useEffect(() => {
1184
+ React20.useEffect(() => {
1035
1185
  return () => {
1036
1186
  if (timerRef.current) clearTimeout(timerRef.current);
1037
1187
  };
@@ -1049,12 +1199,27 @@ var Tooltip = ({
1049
1199
  }
1050
1200
  })();
1051
1201
  const style = useSpringPresence({ visible: open, from: fromTransform });
1052
- const trigger = React19.cloneElement(children, {
1053
- onMouseEnter: show,
1054
- onMouseLeave: hide,
1055
- onFocus: show,
1056
- onBlur: hide,
1057
- "aria-describedby": open ? tooltipId : void 0
1202
+ const child = children;
1203
+ const childProps = child.props;
1204
+ const trigger = React20.cloneElement(child, {
1205
+ onMouseEnter: (e) => {
1206
+ childProps.onMouseEnter?.(e);
1207
+ show();
1208
+ },
1209
+ onMouseLeave: (e) => {
1210
+ childProps.onMouseLeave?.(e);
1211
+ hide();
1212
+ },
1213
+ onFocus: (e) => {
1214
+ childProps.onFocus?.(e);
1215
+ show();
1216
+ },
1217
+ onBlur: (e) => {
1218
+ childProps.onBlur?.(e);
1219
+ hide();
1220
+ },
1221
+ // 자식의 기존 aria-describedby 보존 + tooltip id 합성 (폼 설명/에러 연결 끊김 방지)
1222
+ "aria-describedby": open ? [childProps["aria-describedby"], tooltipId].filter(Boolean).join(" ") : childProps["aria-describedby"]
1058
1223
  });
1059
1224
  if (disabled) return children;
1060
1225
  return /* @__PURE__ */ jsxs("span", { className: "tooltip_wrapper", children: [
@@ -1128,10 +1293,13 @@ var baseColors = {
1128
1293
  neutral900: "#121212",
1129
1294
  neutral925: "#141414",
1130
1295
  neutral950: "#0A0A0A",
1131
- statusError: "#EF4444",
1132
- statusSuccess: "#10B981",
1133
- statusWarning: "#F59E0B",
1134
- statusInfo: "#3B82F6",
1296
+ // caption 전용 AA 값 (neutral_500 과 분리 — SCSS _index.scss 와 동일)
1297
+ textCaptionLight: "#6F6F6F",
1298
+ // status — SCSS _index.scss 와 동일한 AA 통과(red/green/amber/blue-700) 값
1299
+ statusError: "#B91C1C",
1300
+ statusSuccess: "#047857",
1301
+ statusWarning: "#B45309",
1302
+ statusInfo: "#1D4ED8",
1135
1303
  alphaBlack5: "rgba(0, 0, 0, 0.05)",
1136
1304
  alphaBlack8: "rgba(0, 0, 0, 0.08)",
1137
1305
  alphaBlack12: "rgba(26, 26, 26, 0.12)",
@@ -1149,7 +1317,7 @@ var colors = {
1149
1317
  text: {
1150
1318
  heading: baseColors.neutral900,
1151
1319
  body: baseColors.neutral700,
1152
- caption: baseColors.neutral500,
1320
+ caption: baseColors.textCaptionLight,
1153
1321
  brand: baseColors.brandPrimary,
1154
1322
  inverse: baseColors.neutral0,
1155
1323
  disabled: baseColors.alphaBlack38
@@ -1555,6 +1723,8 @@ var Button = ({
1555
1723
  fullWidth = false,
1556
1724
  radius: radius2,
1557
1725
  danger = false,
1726
+ type = "button",
1727
+ ref,
1558
1728
  className,
1559
1729
  children,
1560
1730
  ...props
@@ -1568,7 +1738,7 @@ var Button = ({
1568
1738
  danger && "button_danger",
1569
1739
  className
1570
1740
  );
1571
- return /* @__PURE__ */ jsxs("button", { className: buttonClassName, ...props, children: [
1741
+ return /* @__PURE__ */ jsxs("button", { ref, type, className: buttonClassName, ...props, children: [
1572
1742
  leadingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: leadingIcon }),
1573
1743
  children && /* @__PURE__ */ jsx("span", { className: "button_label", children }),
1574
1744
  trailingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: trailingIcon })
@@ -1634,12 +1804,12 @@ var AlertModal = ({
1634
1804
  onCancel,
1635
1805
  onClose
1636
1806
  }) => {
1637
- const panelRef = React19.useRef(null);
1638
- const titleId = React19.useId();
1639
- const messageId = React19.useId();
1807
+ const panelRef = React20.useRef(null);
1808
+ const titleId = React20.useId();
1809
+ const messageId = React20.useId();
1640
1810
  const [shouldRender, setShouldRender] = useState(isOpen);
1641
1811
  useFocusTrap(panelRef, isOpen);
1642
- React19.useEffect(() => {
1812
+ React20.useEffect(() => {
1643
1813
  if (isOpen) setShouldRender(true);
1644
1814
  }, [isOpen]);
1645
1815
  const overlayStyle = useSpring({
@@ -1720,6 +1890,7 @@ var Card = ({
1720
1890
  interactive = false,
1721
1891
  footer,
1722
1892
  footerAlign = "end",
1893
+ ref,
1723
1894
  className,
1724
1895
  children,
1725
1896
  ...props
@@ -1732,7 +1903,7 @@ var Card = ({
1732
1903
  { card_bordered: bordered, card_interactive: interactive },
1733
1904
  className
1734
1905
  );
1735
- return /* @__PURE__ */ jsxs("div", { className: cardClassName, ...props, children: [
1906
+ return /* @__PURE__ */ jsxs("div", { ref, className: cardClassName, ...props, children: [
1736
1907
  heading ? /* @__PURE__ */ jsx(HeadingTag, { className: "card_title", children: heading }) : null,
1737
1908
  /* @__PURE__ */ jsx("div", { className: "card_body", children }),
1738
1909
  footer ? /* @__PURE__ */ jsx("div", { className: cn("card_footer", `card_footer_${footerAlign}`), children: footer }) : null
@@ -1746,12 +1917,12 @@ var Checkbox = ({
1746
1917
  ref,
1747
1918
  ...props
1748
1919
  }) => {
1749
- const inputRef = React19.useRef(null);
1750
- React19.useImperativeHandle(
1920
+ const inputRef = React20.useRef(null);
1921
+ React20.useImperativeHandle(
1751
1922
  ref,
1752
1923
  () => inputRef.current
1753
1924
  );
1754
- React19.useEffect(() => {
1925
+ React20.useEffect(() => {
1755
1926
  if (!inputRef.current) return;
1756
1927
  inputRef.current.indeterminate = Boolean(indeterminate);
1757
1928
  }, [indeterminate]);
@@ -1929,6 +2100,7 @@ var Dropdown = ({
1929
2100
  placeholder = "Select\u2026",
1930
2101
  options,
1931
2102
  value,
2103
+ onValueChange,
1932
2104
  onChange,
1933
2105
  defaultValue = null,
1934
2106
  disabled,
@@ -1953,9 +2125,9 @@ var Dropdown = ({
1953
2125
  (next) => {
1954
2126
  const option = options.find((o) => o.value === next) ?? null;
1955
2127
  if (!isControlled) setInternalValue(next);
1956
- onChange?.(next, option);
2128
+ (onValueChange ?? onChange)?.(next, option);
1957
2129
  },
1958
- [isControlled, onChange, options]
2130
+ [isControlled, onValueChange, onChange, options]
1959
2131
  );
1960
2132
  useEffect(() => {
1961
2133
  const handleOutsideClick = (e) => {
@@ -2141,6 +2313,7 @@ var range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => st
2141
2313
  var DatePicker = ({
2142
2314
  label,
2143
2315
  value,
2316
+ onValueChange,
2144
2317
  onChange,
2145
2318
  mode = "year-month-day",
2146
2319
  startYear = 1950,
@@ -2156,9 +2329,9 @@ var DatePicker = ({
2156
2329
  minDateSrFormat = "Minimum date: {date}",
2157
2330
  selectableRangeUntilTodaySrText = "Selectable up to today"
2158
2331
  }) => {
2159
- const groupId = React19.useId();
2160
- const constraintId = React19.useId();
2161
- const { todayYear, todayMonth, todayDay } = React19.useMemo(() => {
2332
+ const groupId = React20.useId();
2333
+ const constraintId = React20.useId();
2334
+ const { todayYear, todayMonth, todayDay } = React20.useMemo(() => {
2162
2335
  const now = /* @__PURE__ */ new Date();
2163
2336
  return {
2164
2337
  todayYear: now.getFullYear(),
@@ -2167,7 +2340,7 @@ var DatePicker = ({
2167
2340
  };
2168
2341
  }, []);
2169
2342
  const endYear = endYearProp ?? todayYear + 10;
2170
- const parsed = React19.useMemo(() => {
2343
+ const parsed = React20.useMemo(() => {
2171
2344
  if (!value) return { year: 0, month: 0, day: 0 };
2172
2345
  const [y, m, d] = value.split("-").map(Number);
2173
2346
  return {
@@ -2176,7 +2349,7 @@ var DatePicker = ({
2176
2349
  day: d || 0
2177
2350
  };
2178
2351
  }, [value]);
2179
- const min = React19.useMemo(() => {
2352
+ const min = React20.useMemo(() => {
2180
2353
  if (!minDate) return { year: 0, month: 0, day: 0 };
2181
2354
  const [y, m, d] = minDate.split("-").map(Number);
2182
2355
  return {
@@ -2196,7 +2369,7 @@ var DatePicker = ({
2196
2369
  min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
2197
2370
  )
2198
2371
  );
2199
- const maxDay = React19.useMemo(() => {
2372
+ const maxDay = React20.useMemo(() => {
2200
2373
  if (!year || !month) return 31;
2201
2374
  const daysInMonth = getDaysInMonth(year, month);
2202
2375
  if (selectableRange === "until-today" && year === todayYear && month === todayMonth) {
@@ -2204,39 +2377,40 @@ var DatePicker = ({
2204
2377
  }
2205
2378
  return daysInMonth;
2206
2379
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
2207
- const yearOptions = React19.useMemo(
2380
+ const yearOptions = React20.useMemo(
2208
2381
  () => range(startYear, maxYear).map((y) => ({
2209
2382
  value: String(y),
2210
2383
  label: String(y)
2211
2384
  })),
2212
2385
  [startYear, maxYear]
2213
2386
  );
2214
- const monthOptions = React19.useMemo(
2387
+ const monthOptions = React20.useMemo(
2215
2388
  () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
2216
2389
  value: String(m),
2217
2390
  label: pad(m)
2218
2391
  })),
2219
2392
  [minMonth, maxMonth]
2220
2393
  );
2221
- const dayOptions = React19.useMemo(
2394
+ const dayOptions = React20.useMemo(
2222
2395
  () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
2223
2396
  value: String(d),
2224
2397
  label: pad(d)
2225
2398
  })),
2226
2399
  [minDay, maxDay]
2227
2400
  );
2228
- const emit = React19.useCallback(
2401
+ const emit = React20.useCallback(
2229
2402
  (yy, mm, dd) => {
2403
+ const cb = onValueChange ?? onChange;
2230
2404
  if (mode === "year-month") {
2231
- onChange(`${yy}-${pad(mm)}`);
2405
+ cb?.(`${yy}-${pad(mm)}`);
2232
2406
  return;
2233
2407
  }
2234
2408
  const safeDay = Math.min(dd ?? 1, getDaysInMonth(yy, mm));
2235
- onChange(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2409
+ cb?.(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2236
2410
  },
2237
- [mode, onChange]
2411
+ [mode, onValueChange, onChange]
2238
2412
  );
2239
- const handleYearChange = React19.useCallback(
2413
+ const handleYearChange = React20.useCallback(
2240
2414
  (raw) => {
2241
2415
  if (!raw) return;
2242
2416
  const newYear = Number(raw);
@@ -2253,14 +2427,14 @@ var DatePicker = ({
2253
2427
  },
2254
2428
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
2255
2429
  );
2256
- const handleMonthChange = React19.useCallback(
2430
+ const handleMonthChange = React20.useCallback(
2257
2431
  (raw) => {
2258
2432
  if (!raw || !year) return;
2259
2433
  emit(year, Number(raw), day || void 0);
2260
2434
  },
2261
2435
  [year, day, emit]
2262
2436
  );
2263
- const handleDayChange = React19.useCallback(
2437
+ const handleDayChange = React20.useCallback(
2264
2438
  (raw) => {
2265
2439
  if (!raw || !year || !month) return;
2266
2440
  emit(year, month, Number(raw));
@@ -2296,7 +2470,7 @@ var DatePicker = ({
2296
2470
  placeholder: yearLabel,
2297
2471
  options: yearOptions,
2298
2472
  value: year ? String(year) : null,
2299
- onChange: handleYearChange,
2473
+ onValueChange: handleYearChange,
2300
2474
  disabled
2301
2475
  }
2302
2476
  ),
@@ -2309,7 +2483,7 @@ var DatePicker = ({
2309
2483
  placeholder: monthLabel,
2310
2484
  options: monthOptions,
2311
2485
  value: month ? String(month) : null,
2312
- onChange: handleMonthChange,
2486
+ onValueChange: handleMonthChange,
2313
2487
  disabled: disabled || !year
2314
2488
  }
2315
2489
  ),
@@ -2322,7 +2496,7 @@ var DatePicker = ({
2322
2496
  placeholder: dayLabel,
2323
2497
  options: dayOptions,
2324
2498
  value: day ? String(day) : null,
2325
- onChange: handleDayChange,
2499
+ onValueChange: handleDayChange,
2326
2500
  disabled: disabled || !month
2327
2501
  }
2328
2502
  )
@@ -2348,16 +2522,15 @@ var FileInput = ({
2348
2522
  onChange,
2349
2523
  ...props
2350
2524
  }) => {
2351
- const inputId = React19.useId();
2352
- const helperId = React19.useId();
2353
- const inputRef = React19.useRef(null);
2354
- const [previewUrls, setPreviewUrls] = React19.useState([]);
2525
+ const inputId = React20.useId();
2526
+ const helperId = React20.useId();
2527
+ const inputRef = React20.useRef(null);
2528
+ const [previewUrls, setPreviewUrls] = React20.useState([]);
2529
+ const previewUrlsRef = React20.useRef([]);
2355
2530
  const isPreviewVariant = variant === "preview";
2356
2531
  const showPreview = isPreviewVariant || preview;
2357
2532
  const handleChange = (e) => {
2358
2533
  const files = e.currentTarget.files;
2359
- onFiles?.(files);
2360
- onChange?.(e);
2361
2534
  if (showPreview && files) {
2362
2535
  for (const url of previewUrls) {
2363
2536
  URL.revokeObjectURL(url);
@@ -2370,12 +2543,16 @@ var FileInput = ({
2370
2543
  }
2371
2544
  }
2372
2545
  setPreviewUrls(urls);
2546
+ previewUrlsRef.current = urls;
2373
2547
  } else {
2374
2548
  for (const url of previewUrls) {
2375
2549
  URL.revokeObjectURL(url);
2376
2550
  }
2377
2551
  setPreviewUrls([]);
2552
+ previewUrlsRef.current = [];
2378
2553
  }
2554
+ onFiles?.(files);
2555
+ onChange?.(e);
2379
2556
  };
2380
2557
  const handleRemove = (e) => {
2381
2558
  e.preventDefault();
@@ -2384,18 +2561,19 @@ var FileInput = ({
2384
2561
  URL.revokeObjectURL(url);
2385
2562
  }
2386
2563
  setPreviewUrls([]);
2564
+ previewUrlsRef.current = [];
2387
2565
  if (inputRef.current) {
2388
2566
  inputRef.current.value = "";
2389
2567
  }
2390
2568
  onFiles?.(null);
2391
2569
  };
2392
- React19.useEffect(() => {
2570
+ React20.useEffect(() => {
2393
2571
  return () => {
2394
- for (const url of previewUrls) {
2572
+ for (const url of previewUrlsRef.current) {
2395
2573
  URL.revokeObjectURL(url);
2396
2574
  }
2397
2575
  };
2398
- }, [previewUrls]);
2576
+ }, []);
2399
2577
  const hasImage = previewUrls.length > 0;
2400
2578
  const rootClassName = cn(
2401
2579
  "file_input",
@@ -2527,6 +2705,8 @@ var IconButton = ({
2527
2705
  variant = "standard",
2528
2706
  size = "md",
2529
2707
  icon,
2708
+ type = "button",
2709
+ ref,
2530
2710
  className,
2531
2711
  ...props
2532
2712
  }) => {
@@ -2536,7 +2716,7 @@ var IconButton = ({
2536
2716
  `icon_button_size_${size}`,
2537
2717
  className
2538
2718
  );
2539
- return /* @__PURE__ */ jsx("button", { className: buttonClassName, ...props, children: /* @__PURE__ */ jsx("span", { className: "icon_button_icon", "aria-hidden": "true", children: icon }) });
2719
+ return /* @__PURE__ */ jsx("button", { ref, type, className: buttonClassName, ...props, children: /* @__PURE__ */ jsx("span", { className: "icon_button_icon", "aria-hidden": "true", children: icon }) });
2540
2720
  };
2541
2721
  var LinearProgress = ({
2542
2722
  totalSteps,
@@ -2609,7 +2789,7 @@ var ListItem = ({
2609
2789
  if (disabled || !onClick) return;
2610
2790
  if (e.key === "Enter" || e.key === " ") {
2611
2791
  e.preventDefault();
2612
- onClick(e);
2792
+ e.currentTarget.click();
2613
2793
  }
2614
2794
  },
2615
2795
  role: onClick ? "button" : void 0,
@@ -2686,11 +2866,11 @@ var Modal = ({
2686
2866
  ariaLabel,
2687
2867
  ...props
2688
2868
  }) => {
2689
- const panelRef = React19.useRef(null);
2690
- const titleId = React19.useId();
2691
- const [shouldRender, setShouldRender] = React19.useState(open);
2869
+ const panelRef = React20.useRef(null);
2870
+ const titleId = React20.useId();
2871
+ const [shouldRender, setShouldRender] = React20.useState(open);
2692
2872
  useFocusTrap(panelRef, open);
2693
- React19.useEffect(() => {
2873
+ React20.useEffect(() => {
2694
2874
  if (open) setShouldRender(true);
2695
2875
  }, [open]);
2696
2876
  const overlayStyle = useSpring({
@@ -2705,15 +2885,7 @@ var Modal = ({
2705
2885
  transform: open ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
2706
2886
  config: { tension: 280, friction: 28, clamp: !open }
2707
2887
  });
2708
- const handleEscape = React19.useEffectEvent((e) => {
2709
- if (e.key === "Escape") onClose?.();
2710
- });
2711
- React19.useEffect(() => {
2712
- if (!open) return;
2713
- document.addEventListener("keydown", handleEscape);
2714
- return () => document.removeEventListener("keydown", handleEscape);
2715
- }, [open]);
2716
- React19.useEffect(() => {
2888
+ React20.useEffect(() => {
2717
2889
  if (!open) return;
2718
2890
  const body = document.body;
2719
2891
  const openModals = parseInt(body.dataset.openModals || "0", 10);
@@ -2747,7 +2919,10 @@ var Modal = ({
2747
2919
  "aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
2748
2920
  onClick: () => closeOnOverlay && onClose?.(),
2749
2921
  onKeyDown: (e) => {
2750
- if (e.key === "Escape") onClose?.();
2922
+ if (e.key === "Escape") {
2923
+ e.stopPropagation();
2924
+ onClose?.();
2925
+ }
2751
2926
  },
2752
2927
  children: /* @__PURE__ */ jsxs(
2753
2928
  animated.div,
@@ -2785,6 +2960,7 @@ var Modal = ({
2785
2960
  var OtpInput = ({
2786
2961
  length = 6,
2787
2962
  value = "",
2963
+ onValueChange,
2788
2964
  onChange,
2789
2965
  error = false,
2790
2966
  disabled = false,
@@ -2793,12 +2969,12 @@ var OtpInput = ({
2793
2969
  ariaLabel = "OTP \uC785\uB825",
2794
2970
  className
2795
2971
  }) => {
2796
- const inputsRef = React19.useRef([]);
2797
- const isTypingRef = React19.useRef(false);
2798
- React19.useEffect(() => {
2972
+ const inputsRef = React20.useRef([]);
2973
+ const isTypingRef = React20.useRef(false);
2974
+ React20.useEffect(() => {
2799
2975
  if (autoFocus) inputsRef.current[0]?.focus();
2800
2976
  }, [autoFocus]);
2801
- const digits = React19.useMemo(() => {
2977
+ const digits = React20.useMemo(() => {
2802
2978
  const chars = value.split("").slice(0, length);
2803
2979
  while (chars.length < length) chars.push("");
2804
2980
  return chars;
@@ -2807,7 +2983,7 @@ var OtpInput = ({
2807
2983
  inputsRef.current[index]?.focus();
2808
2984
  };
2809
2985
  const updateValue = (newDigits) => {
2810
- onChange?.(newDigits.join(""));
2986
+ (onValueChange ?? onChange)?.(newDigits.join(""));
2811
2987
  };
2812
2988
  const handleChange = (index, e) => {
2813
2989
  const nextDigit = e.target.value;
@@ -2931,20 +3107,22 @@ var getPaginationItems = (page, totalPages) => {
2931
3107
  var Pagination = ({
2932
3108
  page,
2933
3109
  totalPages,
3110
+ onPageChange,
2934
3111
  onChange,
2935
3112
  prevLabel = "Previous page",
2936
3113
  nextLabel = "Next page"
2937
3114
  }) => {
3115
+ const emit = onPageChange ?? onChange;
2938
3116
  const prevDisabled = page <= 1;
2939
3117
  const nextDisabled = page >= totalPages;
2940
- const items = React19.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
3118
+ const items = React20.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
2941
3119
  return /* @__PURE__ */ jsxs("nav", { className: "pagination", "aria-label": "Pagination", children: [
2942
3120
  /* @__PURE__ */ jsx(
2943
3121
  "button",
2944
3122
  {
2945
3123
  type: "button",
2946
3124
  className: "pagination_item",
2947
- onClick: () => onChange(page - 1),
3125
+ onClick: () => emit?.(page - 1),
2948
3126
  disabled: prevDisabled,
2949
3127
  "aria-label": prevLabel,
2950
3128
  children: "\u2039"
@@ -2963,7 +3141,7 @@ var Pagination = ({
2963
3141
  {
2964
3142
  type: "button",
2965
3143
  className: buttonClassName,
2966
- onClick: () => onChange(it),
3144
+ onClick: () => emit?.(it),
2967
3145
  "aria-current": isActive ? "page" : void 0,
2968
3146
  children: it
2969
3147
  }
@@ -2974,7 +3152,7 @@ var Pagination = ({
2974
3152
  {
2975
3153
  type: "button",
2976
3154
  className: "pagination_item",
2977
- onClick: () => onChange(page + 1),
3155
+ onClick: () => emit?.(page + 1),
2978
3156
  disabled: nextDisabled,
2979
3157
  "aria-label": nextLabel,
2980
3158
  children: "\u203A"
@@ -2982,9 +3160,9 @@ var Pagination = ({
2982
3160
  )
2983
3161
  ] });
2984
3162
  };
2985
- var RadioGroupContext = React19.createContext(null);
3163
+ var RadioGroupContext = React20.createContext(null);
2986
3164
  function useRadioGroupContext() {
2987
- return React19.useContext(RadioGroupContext);
3165
+ return React20.useContext(RadioGroupContext);
2988
3166
  }
2989
3167
  var RadioGroup = ({
2990
3168
  value: controlledValue,
@@ -3002,21 +3180,21 @@ var RadioGroup = ({
3002
3180
  ...props
3003
3181
  }) => {
3004
3182
  const isControlled = controlledValue !== void 0;
3005
- const [internalValue, setInternalValue] = React19.useState(defaultValue);
3183
+ const [internalValue, setInternalValue] = React20.useState(defaultValue);
3006
3184
  const value = isControlled ? controlledValue : internalValue;
3007
- const generatedName = React19.useId();
3185
+ const generatedName = React20.useId();
3008
3186
  const name = nameProp ?? generatedName;
3009
- const idPrefix = React19.useId();
3187
+ const idPrefix = React20.useId();
3010
3188
  const labelId = label ? `${idPrefix}-label` : void 0;
3011
3189
  const helperId = supportingText ? `${idPrefix}-help` : void 0;
3012
- const onChange = React19.useCallback(
3190
+ const onChange = React20.useCallback(
3013
3191
  (next) => {
3014
3192
  if (!isControlled) setInternalValue(next);
3015
3193
  onValueChange?.(next);
3016
3194
  },
3017
3195
  [isControlled, onValueChange]
3018
3196
  );
3019
- const ctx = React19.useMemo(
3197
+ const ctx = React20.useMemo(
3020
3198
  () => ({ name, value, onChange, size, disabled }),
3021
3199
  [name, value, onChange, size, disabled]
3022
3200
  );
@@ -3214,9 +3392,9 @@ var Table = ({
3214
3392
  isEmpty && /* @__PURE__ */ jsx("div", { className: "table_empty", role: "status", children: emptyMessage })
3215
3393
  ] });
3216
3394
  };
3217
- var ThemeContext = React19.createContext(null);
3395
+ var ThemeContext = React20.createContext(null);
3218
3396
  var useTheme = () => {
3219
- const ctx = React19.useContext(ThemeContext);
3397
+ const ctx = React20.useContext(ThemeContext);
3220
3398
  if (!ctx) {
3221
3399
  throw new Error(
3222
3400
  '[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>'
@@ -3240,14 +3418,15 @@ var ThemeProvider = ({
3240
3418
  targetSelector,
3241
3419
  children
3242
3420
  }) => {
3243
- const [mode, setModeState] = React19.useState(() => {
3244
- return readStored(storageKey) ?? defaultMode;
3245
- });
3246
- const [systemDark, setSystemDark] = React19.useState(() => {
3247
- if (!isClient) return false;
3248
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
3249
- });
3250
- React19.useEffect(() => {
3421
+ const [mode, setModeState] = React20.useState(defaultMode);
3422
+ const [systemDark, setSystemDark] = React20.useState(false);
3423
+ React20.useEffect(() => {
3424
+ setModeState(readStored(storageKey) ?? defaultMode);
3425
+ if (isClient) {
3426
+ setSystemDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
3427
+ }
3428
+ }, [storageKey, defaultMode]);
3429
+ React20.useEffect(() => {
3251
3430
  if (!isClient) return;
3252
3431
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
3253
3432
  const handler = (e) => setSystemDark(e.matches);
@@ -3255,7 +3434,7 @@ var ThemeProvider = ({
3255
3434
  return () => mq.removeEventListener("change", handler);
3256
3435
  }, []);
3257
3436
  const resolved = mode === "system" ? systemDark ? "dark" : "light" : mode;
3258
- React19.useEffect(() => {
3437
+ React20.useEffect(() => {
3259
3438
  if (!isClient) return;
3260
3439
  const target = targetSelector ? document.querySelector(targetSelector) : document.documentElement;
3261
3440
  if (!target) return;
@@ -3265,7 +3444,7 @@ var ThemeProvider = ({
3265
3444
  target.setAttribute("data-theme", mode);
3266
3445
  }
3267
3446
  }, [mode, targetSelector]);
3268
- const setMode = React19.useCallback(
3447
+ const setMode = React20.useCallback(
3269
3448
  (next) => {
3270
3449
  setModeState(next);
3271
3450
  if (storageKey && isClient) {
@@ -3277,7 +3456,7 @@ var ThemeProvider = ({
3277
3456
  },
3278
3457
  [storageKey]
3279
3458
  );
3280
- const value = React19.useMemo(
3459
+ const value = React20.useMemo(
3281
3460
  () => ({ mode, resolved, setMode }),
3282
3461
  [mode, resolved, setMode]
3283
3462
  );
@@ -3296,6 +3475,7 @@ var TextField = ({
3296
3475
  fullWidth,
3297
3476
  size = "md",
3298
3477
  className,
3478
+ onValueChange,
3299
3479
  onChangeAction,
3300
3480
  imeStrategy = "delayed",
3301
3481
  value,
@@ -3326,10 +3506,10 @@ var TextField = ({
3326
3506
  setInnerValue(nextValue);
3327
3507
  if (nextValue !== lastEmittedValueRef.current) {
3328
3508
  lastEmittedValueRef.current = nextValue;
3329
- onChangeAction?.(nextValue);
3509
+ (onValueChange ?? onChangeAction)?.(nextValue);
3330
3510
  }
3331
3511
  },
3332
- [onChangeAction]
3512
+ [onValueChange, onChangeAction]
3333
3513
  );
3334
3514
  const handleClear = useCallback(() => {
3335
3515
  emit("");
@@ -3379,7 +3559,7 @@ var TextField = ({
3379
3559
  setInnerValue(rawValue);
3380
3560
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3381
3561
  lastEmittedValueRef.current = rawValue;
3382
- onChangeAction?.(rawValue);
3562
+ (onValueChange ?? onChangeAction)?.(rawValue);
3383
3563
  }
3384
3564
  return;
3385
3565
  }
@@ -3409,6 +3589,7 @@ var Textarea = ({
3409
3589
  fullWidth,
3410
3590
  size = "md",
3411
3591
  className,
3592
+ onValueChange,
3412
3593
  onChangeAction,
3413
3594
  imeStrategy = "delayed",
3414
3595
  value,
@@ -3476,7 +3657,7 @@ var Textarea = ({
3476
3657
  setInnerValue(nextValue);
3477
3658
  if (nextValue !== lastEmittedValueRef.current) {
3478
3659
  lastEmittedValueRef.current = nextValue;
3479
- onChangeAction?.(nextValue);
3660
+ (onValueChange ?? onChangeAction)?.(nextValue);
3480
3661
  }
3481
3662
  };
3482
3663
  return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
@@ -3508,7 +3689,7 @@ var Textarea = ({
3508
3689
  setInnerValue(rawValue);
3509
3690
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3510
3691
  lastEmittedValueRef.current = rawValue;
3511
- onChangeAction?.(rawValue);
3692
+ (onValueChange ?? onChangeAction)?.(rawValue);
3512
3693
  }
3513
3694
  return;
3514
3695
  }
@@ -3523,7 +3704,7 @@ var Textarea = ({
3523
3704
  ] });
3524
3705
  };
3525
3706
  Textarea.displayName = "Textarea";
3526
- var ToastContext = React19.createContext(null);
3707
+ var ToastContext = React20.createContext(null);
3527
3708
  var VARIANT_ICONS = {
3528
3709
  success: /* @__PURE__ */ jsx(CheckCircle2, { size: 18 }),
3529
3710
  error: /* @__PURE__ */ jsx(XCircle, { size: 18 }),
@@ -3532,15 +3713,15 @@ var VARIANT_ICONS = {
3532
3713
  default: /* @__PURE__ */ jsx(Bell, { size: 18 })
3533
3714
  };
3534
3715
  var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
3535
- const [visible, setVisible] = React19.useState(true);
3536
- const closingRef = React19.useRef(false);
3716
+ const [visible, setVisible] = React20.useState(true);
3717
+ const closingRef = React20.useRef(false);
3537
3718
  const style = useSpringPresence({
3538
3719
  visible,
3539
3720
  from: "translateX(20px)",
3540
3721
  // 우측에서 슬라이드 인
3541
3722
  onExitComplete: () => onRemove(item.id)
3542
3723
  });
3543
- const close = React19.useCallback(() => {
3724
+ const close = React20.useCallback(() => {
3544
3725
  if (closingRef.current) return;
3545
3726
  closingRef.current = true;
3546
3727
  setVisible(false);
@@ -3573,22 +3754,23 @@ var ToastProvider = ({
3573
3754
  maxCount = 5,
3574
3755
  closeAriaLabel = "Close"
3575
3756
  }) => {
3576
- const [toasts, setToasts] = React19.useState([]);
3577
- const [isMounted, setIsMounted] = React19.useState(false);
3578
- React19.useEffect(() => {
3757
+ const [toasts, setToasts] = React20.useState([]);
3758
+ const [isMounted, setIsMounted] = React20.useState(false);
3759
+ React20.useEffect(() => {
3579
3760
  setIsMounted(true);
3580
3761
  }, []);
3581
- const addToast = React19.useCallback(
3762
+ const addToast = React20.useCallback(
3582
3763
  (message, variant, duration = 3e3) => {
3583
3764
  const id = crypto.randomUUID();
3584
3765
  setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
3585
3766
  },
3586
3767
  [maxCount]
3587
3768
  );
3588
- const removeToast = React19.useCallback((id) => {
3769
+ const removeToast = React20.useCallback((id) => {
3589
3770
  setToasts((prev) => prev.filter((t) => t.id !== id));
3590
3771
  }, []);
3591
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
3772
+ const contextValue = React20.useMemo(() => ({ addToast }), [addToast]);
3773
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
3592
3774
  children,
3593
3775
  isMounted && createPortal(
3594
3776
  // biome-ignore lint/a11y/useSemanticElements: <section> would create unwanted nesting in portal; role=region is the WAI-ARIA equivalent for live announcements
@@ -3638,6 +3820,7 @@ var useToast = () => {
3638
3820
  var Toggle = ({
3639
3821
  checked,
3640
3822
  defaultChecked,
3823
+ onCheckedChange,
3641
3824
  onChange,
3642
3825
  size = "sm",
3643
3826
  disabled,
@@ -3647,13 +3830,13 @@ var Toggle = ({
3647
3830
  ...props
3648
3831
  }) => {
3649
3832
  const isControlled = checked !== void 0;
3650
- const [innerChecked, setInnerChecked] = React19.useState(!!defaultChecked);
3833
+ const [innerChecked, setInnerChecked] = React20.useState(!!defaultChecked);
3651
3834
  const isOn = isControlled ? !!checked : innerChecked;
3652
3835
  const handleToggle = () => {
3653
3836
  if (disabled) return;
3654
3837
  const next = !isOn;
3655
3838
  if (!isControlled) setInnerChecked(next);
3656
- onChange?.(next);
3839
+ (onCheckedChange ?? onChange)?.(next);
3657
3840
  };
3658
3841
  const rootClassName = cn(
3659
3842
  "toggle",
@@ -3714,6 +3897,7 @@ var Container = ({
3714
3897
  size = "xl",
3715
3898
  center = true,
3716
3899
  as: Tag = "div",
3900
+ ref,
3717
3901
  className,
3718
3902
  children,
3719
3903
  ...props
@@ -3721,6 +3905,7 @@ var Container = ({
3721
3905
  return /* @__PURE__ */ jsx(
3722
3906
  Tag,
3723
3907
  {
3908
+ ref,
3724
3909
  className: cn("container", `container_size_${size}`, center && "container_center", className),
3725
3910
  ...props,
3726
3911
  children
@@ -3731,6 +3916,7 @@ var Section = ({
3731
3916
  spacing: spacing2 = "md",
3732
3917
  bg = "default",
3733
3918
  as: Tag = "section",
3919
+ ref,
3734
3920
  className,
3735
3921
  children,
3736
3922
  ...props
@@ -3738,6 +3924,7 @@ var Section = ({
3738
3924
  return /* @__PURE__ */ jsx(
3739
3925
  Tag,
3740
3926
  {
3927
+ ref,
3741
3928
  className: cn(
3742
3929
  "section",
3743
3930
  `section_spacing_${spacing2}`,
@@ -3756,6 +3943,7 @@ var Stack = ({
3756
3943
  justify,
3757
3944
  wrap,
3758
3945
  as: Tag = "div",
3946
+ ref,
3759
3947
  className,
3760
3948
  children,
3761
3949
  style,
@@ -3764,6 +3952,7 @@ var Stack = ({
3764
3952
  return /* @__PURE__ */ jsx(
3765
3953
  Tag,
3766
3954
  {
3955
+ ref,
3767
3956
  className: cn(
3768
3957
  "stack",
3769
3958
  `stack_${direction}`,
@@ -3786,6 +3975,7 @@ var Grid = ({
3786
3975
  colGap,
3787
3976
  singleColOnMobile = true,
3788
3977
  as: Tag = "div",
3978
+ ref,
3789
3979
  className,
3790
3980
  children,
3791
3981
  style,
@@ -3795,6 +3985,7 @@ var Grid = ({
3795
3985
  return /* @__PURE__ */ jsx(
3796
3986
  Tag,
3797
3987
  {
3988
+ ref,
3798
3989
  className: cn("grid_layout", singleColOnMobile && "grid_layout_mobile_single", className),
3799
3990
  style: {
3800
3991
  "--grid-cols": gridTemplateColumns,
@@ -3809,4 +4000,4 @@ var Grid = ({
3809
4000
  );
3810
4001
  };
3811
4002
 
3812
- 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 };
4003
+ 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, useReducedMotion, useSpringHover, useSpringPresence, useTheme, useToast, zIndex };