@bigtablet/design-system 3.2.2 → 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
3
  import * as React20 from 'react';
4
- import { createContext, useRef, useState, useLayoutEffect, useId, useEffect, useContext, useCallback, useMemo, Fragment } 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';
@@ -172,6 +172,7 @@ var Accordion = ({
172
172
  multiple = false,
173
173
  defaultOpenKeys = [],
174
174
  openKeys: controlledKeys,
175
+ onValueChange,
175
176
  onChange,
176
177
  className,
177
178
  ...props
@@ -183,7 +184,7 @@ var Accordion = ({
183
184
  const isOpen = open.includes(key);
184
185
  const next = isOpen ? open.filter((k) => k !== key) : multiple ? [...open, key] : [key];
185
186
  if (!isControlled) setInternalKeys(next);
186
- onChange?.(next);
187
+ (onValueChange ?? onChange)?.(next);
187
188
  };
188
189
  return /* @__PURE__ */ jsx("div", { className: cn("accordion", className), ...props, children: items.map((item) => {
189
190
  const isOpen = open.includes(item.key);
@@ -220,6 +221,7 @@ var Accordion = ({
220
221
  role: "region",
221
222
  "aria-labelledby": headerId,
222
223
  "aria-hidden": !isOpen,
224
+ inert: !isOpen,
223
225
  className: cn("accordion_panel", isOpen && "accordion_panel_open"),
224
226
  children: /* @__PURE__ */ jsx("div", { className: "accordion_panel_wrap", children: /* @__PURE__ */ jsx("div", { className: "accordion_content", children: item.content }) })
225
227
  }
@@ -463,6 +465,7 @@ var ErrorState = ({
463
465
  var Menu = ({ items, trigger, align = "start" }) => {
464
466
  const [open, setOpen] = React20.useState(false);
465
467
  const wrapperRef = React20.useRef(null);
468
+ const itemRefs = React20.useRef([]);
466
469
  const menuId = React20.useId();
467
470
  const style = useSpringPresence({ visible: open, from: "translateY(-4px)" });
468
471
  React20.useEffect(() => {
@@ -480,6 +483,52 @@ var Menu = ({ items, trigger, align = "start" }) => {
480
483
  document.removeEventListener("keydown", handleEsc);
481
484
  };
482
485
  }, [open]);
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
+ };
483
532
  const triggerWithProps = React20.cloneElement(
484
533
  trigger,
485
534
  {
@@ -498,11 +547,16 @@ var Menu = ({ items, trigger, align = "start" }) => {
498
547
  role: "menu",
499
548
  style,
500
549
  className: cn("menu", `menu_align_${align}`),
501
- children: items.map((item) => /* @__PURE__ */ jsxs(
550
+ onKeyDown: handleMenuKeyDown,
551
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(
502
552
  "button",
503
553
  {
554
+ ref: (el) => {
555
+ itemRefs.current[index] = el;
556
+ },
504
557
  type: "button",
505
558
  role: "menuitem",
559
+ tabIndex: -1,
506
560
  disabled: item.disabled,
507
561
  className: cn(
508
562
  "menu_item",
@@ -543,7 +597,7 @@ var NavBar = ({
543
597
  const linksRef = useRef(null);
544
598
  const [indicator, setIndicator] = useState(null);
545
599
  const [hasMounted, setHasMounted] = useState(false);
546
- useLayoutEffect(() => {
600
+ useSafeLayoutEffect(() => {
547
601
  const links = linksRef.current;
548
602
  if (!links) return;
549
603
  const update = () => {
@@ -614,6 +668,7 @@ var NavBar = ({
614
668
  var LocaleSwitcher = ({ locale }) => {
615
669
  const [open, setOpen] = useState(false);
616
670
  const wrapperRef = useRef(null);
671
+ const itemRefs = useRef([]);
617
672
  const menuId = useId();
618
673
  const currentOption = locale.options.find((o) => o.value === locale.current);
619
674
  useEffect(() => {
@@ -631,8 +686,53 @@ var LocaleSwitcher = ({ locale }) => {
631
686
  document.removeEventListener("keydown", escHandler);
632
687
  };
633
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
+ };
634
734
  const handleSelect = (value) => {
635
- locale.onChange(value);
735
+ (locale.onValueChange ?? locale.onChange)?.(value);
636
736
  setOpen(false);
637
737
  };
638
738
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "nav_bar_locale", children: [
@@ -652,19 +752,32 @@ var LocaleSwitcher = ({ locale }) => {
652
752
  ]
653
753
  }
654
754
  ),
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",
755
+ open && /* @__PURE__ */ jsx(
756
+ "ul",
657
757
  {
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
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))
666
779
  }
667
- ) }, opt.value)) })
780
+ )
668
781
  ] });
669
782
  };
670
783
  var NavLink = ({ active, className, children, ...props }) => {
@@ -816,7 +929,7 @@ var TabList = ({ ariaLabel, className, children, ...props }) => {
816
929
  const listRef = React20.useRef(null);
817
930
  const [indicator, setIndicator] = React20.useState(null);
818
931
  const [hasMounted, setHasMounted] = React20.useState(false);
819
- React20.useLayoutEffect(() => {
932
+ useSafeLayoutEffect(() => {
820
933
  const list = listRef.current;
821
934
  if (!list) return;
822
935
  const update = () => {
@@ -1086,12 +1199,27 @@ var Tooltip = ({
1086
1199
  }
1087
1200
  })();
1088
1201
  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
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"]
1095
1223
  });
1096
1224
  if (disabled) return children;
1097
1225
  return /* @__PURE__ */ jsxs("span", { className: "tooltip_wrapper", children: [
@@ -1165,10 +1293,13 @@ var baseColors = {
1165
1293
  neutral900: "#121212",
1166
1294
  neutral925: "#141414",
1167
1295
  neutral950: "#0A0A0A",
1168
- statusError: "#EF4444",
1169
- statusSuccess: "#10B981",
1170
- statusWarning: "#F59E0B",
1171
- 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",
1172
1303
  alphaBlack5: "rgba(0, 0, 0, 0.05)",
1173
1304
  alphaBlack8: "rgba(0, 0, 0, 0.08)",
1174
1305
  alphaBlack12: "rgba(26, 26, 26, 0.12)",
@@ -1186,7 +1317,7 @@ var colors = {
1186
1317
  text: {
1187
1318
  heading: baseColors.neutral900,
1188
1319
  body: baseColors.neutral700,
1189
- caption: baseColors.neutral500,
1320
+ caption: baseColors.textCaptionLight,
1190
1321
  brand: baseColors.brandPrimary,
1191
1322
  inverse: baseColors.neutral0,
1192
1323
  disabled: baseColors.alphaBlack38
@@ -1592,6 +1723,8 @@ var Button = ({
1592
1723
  fullWidth = false,
1593
1724
  radius: radius2,
1594
1725
  danger = false,
1726
+ type = "button",
1727
+ ref,
1595
1728
  className,
1596
1729
  children,
1597
1730
  ...props
@@ -1605,7 +1738,7 @@ var Button = ({
1605
1738
  danger && "button_danger",
1606
1739
  className
1607
1740
  );
1608
- return /* @__PURE__ */ jsxs("button", { className: buttonClassName, ...props, children: [
1741
+ return /* @__PURE__ */ jsxs("button", { ref, type, className: buttonClassName, ...props, children: [
1609
1742
  leadingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: leadingIcon }),
1610
1743
  children && /* @__PURE__ */ jsx("span", { className: "button_label", children }),
1611
1744
  trailingIcon && /* @__PURE__ */ jsx("span", { className: "button_icon", "aria-hidden": "true", children: trailingIcon })
@@ -1757,6 +1890,7 @@ var Card = ({
1757
1890
  interactive = false,
1758
1891
  footer,
1759
1892
  footerAlign = "end",
1893
+ ref,
1760
1894
  className,
1761
1895
  children,
1762
1896
  ...props
@@ -1769,7 +1903,7 @@ var Card = ({
1769
1903
  { card_bordered: bordered, card_interactive: interactive },
1770
1904
  className
1771
1905
  );
1772
- return /* @__PURE__ */ jsxs("div", { className: cardClassName, ...props, children: [
1906
+ return /* @__PURE__ */ jsxs("div", { ref, className: cardClassName, ...props, children: [
1773
1907
  heading ? /* @__PURE__ */ jsx(HeadingTag, { className: "card_title", children: heading }) : null,
1774
1908
  /* @__PURE__ */ jsx("div", { className: "card_body", children }),
1775
1909
  footer ? /* @__PURE__ */ jsx("div", { className: cn("card_footer", `card_footer_${footerAlign}`), children: footer }) : null
@@ -1966,6 +2100,7 @@ var Dropdown = ({
1966
2100
  placeholder = "Select\u2026",
1967
2101
  options,
1968
2102
  value,
2103
+ onValueChange,
1969
2104
  onChange,
1970
2105
  defaultValue = null,
1971
2106
  disabled,
@@ -1990,9 +2125,9 @@ var Dropdown = ({
1990
2125
  (next) => {
1991
2126
  const option = options.find((o) => o.value === next) ?? null;
1992
2127
  if (!isControlled) setInternalValue(next);
1993
- onChange?.(next, option);
2128
+ (onValueChange ?? onChange)?.(next, option);
1994
2129
  },
1995
- [isControlled, onChange, options]
2130
+ [isControlled, onValueChange, onChange, options]
1996
2131
  );
1997
2132
  useEffect(() => {
1998
2133
  const handleOutsideClick = (e) => {
@@ -2178,6 +2313,7 @@ var range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => st
2178
2313
  var DatePicker = ({
2179
2314
  label,
2180
2315
  value,
2316
+ onValueChange,
2181
2317
  onChange,
2182
2318
  mode = "year-month-day",
2183
2319
  startYear = 1950,
@@ -2264,14 +2400,15 @@ var DatePicker = ({
2264
2400
  );
2265
2401
  const emit = React20.useCallback(
2266
2402
  (yy, mm, dd) => {
2403
+ const cb = onValueChange ?? onChange;
2267
2404
  if (mode === "year-month") {
2268
- onChange(`${yy}-${pad(mm)}`);
2405
+ cb?.(`${yy}-${pad(mm)}`);
2269
2406
  return;
2270
2407
  }
2271
2408
  const safeDay = Math.min(dd ?? 1, getDaysInMonth(yy, mm));
2272
- onChange(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2409
+ cb?.(`${yy}-${pad(mm)}-${pad(safeDay)}`);
2273
2410
  },
2274
- [mode, onChange]
2411
+ [mode, onValueChange, onChange]
2275
2412
  );
2276
2413
  const handleYearChange = React20.useCallback(
2277
2414
  (raw) => {
@@ -2333,7 +2470,7 @@ var DatePicker = ({
2333
2470
  placeholder: yearLabel,
2334
2471
  options: yearOptions,
2335
2472
  value: year ? String(year) : null,
2336
- onChange: handleYearChange,
2473
+ onValueChange: handleYearChange,
2337
2474
  disabled
2338
2475
  }
2339
2476
  ),
@@ -2346,7 +2483,7 @@ var DatePicker = ({
2346
2483
  placeholder: monthLabel,
2347
2484
  options: monthOptions,
2348
2485
  value: month ? String(month) : null,
2349
- onChange: handleMonthChange,
2486
+ onValueChange: handleMonthChange,
2350
2487
  disabled: disabled || !year
2351
2488
  }
2352
2489
  ),
@@ -2359,7 +2496,7 @@ var DatePicker = ({
2359
2496
  placeholder: dayLabel,
2360
2497
  options: dayOptions,
2361
2498
  value: day ? String(day) : null,
2362
- onChange: handleDayChange,
2499
+ onValueChange: handleDayChange,
2363
2500
  disabled: disabled || !month
2364
2501
  }
2365
2502
  )
@@ -2389,12 +2526,11 @@ var FileInput = ({
2389
2526
  const helperId = React20.useId();
2390
2527
  const inputRef = React20.useRef(null);
2391
2528
  const [previewUrls, setPreviewUrls] = React20.useState([]);
2529
+ const previewUrlsRef = React20.useRef([]);
2392
2530
  const isPreviewVariant = variant === "preview";
2393
2531
  const showPreview = isPreviewVariant || preview;
2394
2532
  const handleChange = (e) => {
2395
2533
  const files = e.currentTarget.files;
2396
- onFiles?.(files);
2397
- onChange?.(e);
2398
2534
  if (showPreview && files) {
2399
2535
  for (const url of previewUrls) {
2400
2536
  URL.revokeObjectURL(url);
@@ -2407,12 +2543,16 @@ var FileInput = ({
2407
2543
  }
2408
2544
  }
2409
2545
  setPreviewUrls(urls);
2546
+ previewUrlsRef.current = urls;
2410
2547
  } else {
2411
2548
  for (const url of previewUrls) {
2412
2549
  URL.revokeObjectURL(url);
2413
2550
  }
2414
2551
  setPreviewUrls([]);
2552
+ previewUrlsRef.current = [];
2415
2553
  }
2554
+ onFiles?.(files);
2555
+ onChange?.(e);
2416
2556
  };
2417
2557
  const handleRemove = (e) => {
2418
2558
  e.preventDefault();
@@ -2421,6 +2561,7 @@ var FileInput = ({
2421
2561
  URL.revokeObjectURL(url);
2422
2562
  }
2423
2563
  setPreviewUrls([]);
2564
+ previewUrlsRef.current = [];
2424
2565
  if (inputRef.current) {
2425
2566
  inputRef.current.value = "";
2426
2567
  }
@@ -2428,11 +2569,11 @@ var FileInput = ({
2428
2569
  };
2429
2570
  React20.useEffect(() => {
2430
2571
  return () => {
2431
- for (const url of previewUrls) {
2572
+ for (const url of previewUrlsRef.current) {
2432
2573
  URL.revokeObjectURL(url);
2433
2574
  }
2434
2575
  };
2435
- }, [previewUrls]);
2576
+ }, []);
2436
2577
  const hasImage = previewUrls.length > 0;
2437
2578
  const rootClassName = cn(
2438
2579
  "file_input",
@@ -2564,6 +2705,8 @@ var IconButton = ({
2564
2705
  variant = "standard",
2565
2706
  size = "md",
2566
2707
  icon,
2708
+ type = "button",
2709
+ ref,
2567
2710
  className,
2568
2711
  ...props
2569
2712
  }) => {
@@ -2573,7 +2716,7 @@ var IconButton = ({
2573
2716
  `icon_button_size_${size}`,
2574
2717
  className
2575
2718
  );
2576
- 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 }) });
2577
2720
  };
2578
2721
  var LinearProgress = ({
2579
2722
  totalSteps,
@@ -2646,7 +2789,7 @@ var ListItem = ({
2646
2789
  if (disabled || !onClick) return;
2647
2790
  if (e.key === "Enter" || e.key === " ") {
2648
2791
  e.preventDefault();
2649
- onClick(e);
2792
+ e.currentTarget.click();
2650
2793
  }
2651
2794
  },
2652
2795
  role: onClick ? "button" : void 0,
@@ -2742,14 +2885,6 @@ var Modal = ({
2742
2885
  transform: open ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
2743
2886
  config: { tension: 280, friction: 28, clamp: !open }
2744
2887
  });
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
2888
  React20.useEffect(() => {
2754
2889
  if (!open) return;
2755
2890
  const body = document.body;
@@ -2784,7 +2919,10 @@ var Modal = ({
2784
2919
  "aria-label": !hasTitle ? ariaLabel ?? "Dialog" : ariaLabel,
2785
2920
  onClick: () => closeOnOverlay && onClose?.(),
2786
2921
  onKeyDown: (e) => {
2787
- if (e.key === "Escape") onClose?.();
2922
+ if (e.key === "Escape") {
2923
+ e.stopPropagation();
2924
+ onClose?.();
2925
+ }
2788
2926
  },
2789
2927
  children: /* @__PURE__ */ jsxs(
2790
2928
  animated.div,
@@ -2822,6 +2960,7 @@ var Modal = ({
2822
2960
  var OtpInput = ({
2823
2961
  length = 6,
2824
2962
  value = "",
2963
+ onValueChange,
2825
2964
  onChange,
2826
2965
  error = false,
2827
2966
  disabled = false,
@@ -2844,7 +2983,7 @@ var OtpInput = ({
2844
2983
  inputsRef.current[index]?.focus();
2845
2984
  };
2846
2985
  const updateValue = (newDigits) => {
2847
- onChange?.(newDigits.join(""));
2986
+ (onValueChange ?? onChange)?.(newDigits.join(""));
2848
2987
  };
2849
2988
  const handleChange = (index, e) => {
2850
2989
  const nextDigit = e.target.value;
@@ -2968,10 +3107,12 @@ var getPaginationItems = (page, totalPages) => {
2968
3107
  var Pagination = ({
2969
3108
  page,
2970
3109
  totalPages,
3110
+ onPageChange,
2971
3111
  onChange,
2972
3112
  prevLabel = "Previous page",
2973
3113
  nextLabel = "Next page"
2974
3114
  }) => {
3115
+ const emit = onPageChange ?? onChange;
2975
3116
  const prevDisabled = page <= 1;
2976
3117
  const nextDisabled = page >= totalPages;
2977
3118
  const items = React20.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
@@ -2981,7 +3122,7 @@ var Pagination = ({
2981
3122
  {
2982
3123
  type: "button",
2983
3124
  className: "pagination_item",
2984
- onClick: () => onChange(page - 1),
3125
+ onClick: () => emit?.(page - 1),
2985
3126
  disabled: prevDisabled,
2986
3127
  "aria-label": prevLabel,
2987
3128
  children: "\u2039"
@@ -3000,7 +3141,7 @@ var Pagination = ({
3000
3141
  {
3001
3142
  type: "button",
3002
3143
  className: buttonClassName,
3003
- onClick: () => onChange(it),
3144
+ onClick: () => emit?.(it),
3004
3145
  "aria-current": isActive ? "page" : void 0,
3005
3146
  children: it
3006
3147
  }
@@ -3011,7 +3152,7 @@ var Pagination = ({
3011
3152
  {
3012
3153
  type: "button",
3013
3154
  className: "pagination_item",
3014
- onClick: () => onChange(page + 1),
3155
+ onClick: () => emit?.(page + 1),
3015
3156
  disabled: nextDisabled,
3016
3157
  "aria-label": nextLabel,
3017
3158
  children: "\u203A"
@@ -3277,13 +3418,14 @@ var ThemeProvider = ({
3277
3418
  targetSelector,
3278
3419
  children
3279
3420
  }) => {
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
- });
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]);
3287
3429
  React20.useEffect(() => {
3288
3430
  if (!isClient) return;
3289
3431
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
@@ -3333,6 +3475,7 @@ var TextField = ({
3333
3475
  fullWidth,
3334
3476
  size = "md",
3335
3477
  className,
3478
+ onValueChange,
3336
3479
  onChangeAction,
3337
3480
  imeStrategy = "delayed",
3338
3481
  value,
@@ -3363,10 +3506,10 @@ var TextField = ({
3363
3506
  setInnerValue(nextValue);
3364
3507
  if (nextValue !== lastEmittedValueRef.current) {
3365
3508
  lastEmittedValueRef.current = nextValue;
3366
- onChangeAction?.(nextValue);
3509
+ (onValueChange ?? onChangeAction)?.(nextValue);
3367
3510
  }
3368
3511
  },
3369
- [onChangeAction]
3512
+ [onValueChange, onChangeAction]
3370
3513
  );
3371
3514
  const handleClear = useCallback(() => {
3372
3515
  emit("");
@@ -3416,7 +3559,7 @@ var TextField = ({
3416
3559
  setInnerValue(rawValue);
3417
3560
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3418
3561
  lastEmittedValueRef.current = rawValue;
3419
- onChangeAction?.(rawValue);
3562
+ (onValueChange ?? onChangeAction)?.(rawValue);
3420
3563
  }
3421
3564
  return;
3422
3565
  }
@@ -3446,6 +3589,7 @@ var Textarea = ({
3446
3589
  fullWidth,
3447
3590
  size = "md",
3448
3591
  className,
3592
+ onValueChange,
3449
3593
  onChangeAction,
3450
3594
  imeStrategy = "delayed",
3451
3595
  value,
@@ -3513,7 +3657,7 @@ var Textarea = ({
3513
3657
  setInnerValue(nextValue);
3514
3658
  if (nextValue !== lastEmittedValueRef.current) {
3515
3659
  lastEmittedValueRef.current = nextValue;
3516
- onChangeAction?.(nextValue);
3660
+ (onValueChange ?? onChangeAction)?.(nextValue);
3517
3661
  }
3518
3662
  };
3519
3663
  return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
@@ -3545,7 +3689,7 @@ var Textarea = ({
3545
3689
  setInnerValue(rawValue);
3546
3690
  if (imeStrategy === "immediate" && rawValue !== lastEmittedValueRef.current) {
3547
3691
  lastEmittedValueRef.current = rawValue;
3548
- onChangeAction?.(rawValue);
3692
+ (onValueChange ?? onChangeAction)?.(rawValue);
3549
3693
  }
3550
3694
  return;
3551
3695
  }
@@ -3625,7 +3769,8 @@ var ToastProvider = ({
3625
3769
  const removeToast = React20.useCallback((id) => {
3626
3770
  setToasts((prev) => prev.filter((t) => t.id !== id));
3627
3771
  }, []);
3628
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
3772
+ const contextValue = React20.useMemo(() => ({ addToast }), [addToast]);
3773
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
3629
3774
  children,
3630
3775
  isMounted && createPortal(
3631
3776
  // biome-ignore lint/a11y/useSemanticElements: <section> would create unwanted nesting in portal; role=region is the WAI-ARIA equivalent for live announcements
@@ -3675,6 +3820,7 @@ var useToast = () => {
3675
3820
  var Toggle = ({
3676
3821
  checked,
3677
3822
  defaultChecked,
3823
+ onCheckedChange,
3678
3824
  onChange,
3679
3825
  size = "sm",
3680
3826
  disabled,
@@ -3690,7 +3836,7 @@ var Toggle = ({
3690
3836
  if (disabled) return;
3691
3837
  const next = !isOn;
3692
3838
  if (!isControlled) setInnerChecked(next);
3693
- onChange?.(next);
3839
+ (onCheckedChange ?? onChange)?.(next);
3694
3840
  };
3695
3841
  const rootClassName = cn(
3696
3842
  "toggle",
@@ -3751,6 +3897,7 @@ var Container = ({
3751
3897
  size = "xl",
3752
3898
  center = true,
3753
3899
  as: Tag = "div",
3900
+ ref,
3754
3901
  className,
3755
3902
  children,
3756
3903
  ...props
@@ -3758,6 +3905,7 @@ var Container = ({
3758
3905
  return /* @__PURE__ */ jsx(
3759
3906
  Tag,
3760
3907
  {
3908
+ ref,
3761
3909
  className: cn("container", `container_size_${size}`, center && "container_center", className),
3762
3910
  ...props,
3763
3911
  children
@@ -3768,6 +3916,7 @@ var Section = ({
3768
3916
  spacing: spacing2 = "md",
3769
3917
  bg = "default",
3770
3918
  as: Tag = "section",
3919
+ ref,
3771
3920
  className,
3772
3921
  children,
3773
3922
  ...props
@@ -3775,6 +3924,7 @@ var Section = ({
3775
3924
  return /* @__PURE__ */ jsx(
3776
3925
  Tag,
3777
3926
  {
3927
+ ref,
3778
3928
  className: cn(
3779
3929
  "section",
3780
3930
  `section_spacing_${spacing2}`,
@@ -3793,6 +3943,7 @@ var Stack = ({
3793
3943
  justify,
3794
3944
  wrap,
3795
3945
  as: Tag = "div",
3946
+ ref,
3796
3947
  className,
3797
3948
  children,
3798
3949
  style,
@@ -3801,6 +3952,7 @@ var Stack = ({
3801
3952
  return /* @__PURE__ */ jsx(
3802
3953
  Tag,
3803
3954
  {
3955
+ ref,
3804
3956
  className: cn(
3805
3957
  "stack",
3806
3958
  `stack_${direction}`,
@@ -3823,6 +3975,7 @@ var Grid = ({
3823
3975
  colGap,
3824
3976
  singleColOnMobile = true,
3825
3977
  as: Tag = "div",
3978
+ ref,
3826
3979
  className,
3827
3980
  children,
3828
3981
  style,
@@ -3832,6 +3985,7 @@ var Grid = ({
3832
3985
  return /* @__PURE__ */ jsx(
3833
3986
  Tag,
3834
3987
  {
3988
+ ref,
3835
3989
  className: cn("grid_layout", singleColOnMobile && "grid_layout_mobile_single", className),
3836
3990
  style: {
3837
3991
  "--grid-cols": gridTemplateColumns,
@@ -3846,4 +4000,4 @@ var Grid = ({
3846
4000
  );
3847
4001
  };
3848
4002
 
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 };
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 };