@bigtablet/design-system 3.2.1 → 3.2.2

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.css CHANGED
@@ -7,7 +7,7 @@
7
7
  --bt-color-brand-primary-container: rgba(0, 0, 0, 0.05);
8
8
  --bt-color-text-heading: #121212;
9
9
  --bt-color-text-body: #666666;
10
- --bt-color-text-caption: #888888;
10
+ --bt-color-text-caption: #6F6F6F;
11
11
  --bt-color-text-brand: #121212;
12
12
  --bt-color-text-inverse: #FFFFFF;
13
13
  --bt-color-text-disabled: rgba(26, 26, 26, 0.38);
@@ -72,7 +72,7 @@
72
72
  -moz-osx-font-smoothing: grayscale;
73
73
  --bt-color-text-heading: rgba(255, 255, 255, 0.95);
74
74
  --bt-color-text-body: #B3B3B3;
75
- --bt-color-text-caption: #777777;
75
+ --bt-color-text-caption: #808080;
76
76
  --bt-color-text-inverse: #121212;
77
77
  --bt-color-text-disabled: rgba(255, 255, 255, 0.38);
78
78
  --bt-color-bg-solid: #0A0A0A;
@@ -130,7 +130,7 @@
130
130
  -moz-osx-font-smoothing: grayscale;
131
131
  --bt-color-text-heading: rgba(255, 255, 255, 0.95);
132
132
  --bt-color-text-body: #B3B3B3;
133
- --bt-color-text-caption: #777777;
133
+ --bt-color-text-caption: #808080;
134
134
  --bt-color-text-inverse: #121212;
135
135
  --bt-color-text-disabled: rgba(255, 255, 255, 0.38);
136
136
  --bt-color-bg-solid: #0A0A0A;
@@ -830,6 +830,11 @@
830
830
  cursor: not-allowed;
831
831
  opacity: 0.38;
832
832
  }
833
+ @media (prefers-reduced-motion: reduce) {
834
+ .menu_item {
835
+ transition: none;
836
+ }
837
+ }
833
838
 
834
839
  /* src/ui/navigation/nav-bar/style.scss */
835
840
  @keyframes skeleton_loading {
@@ -1095,6 +1100,14 @@
1095
1100
  .nav_bar_variant_transparent .nav_bar_indicator {
1096
1101
  background: var(--bt-color-brand-on-primary);
1097
1102
  }
1103
+ @media (prefers-reduced-motion: reduce) {
1104
+ .nav_bar_indicator_animated {
1105
+ transition: none;
1106
+ }
1107
+ .nav_bar_locale_chevron {
1108
+ transition: none;
1109
+ }
1110
+ }
1098
1111
 
1099
1112
  /* src/ui/navigation/sidebar/style.scss */
1100
1113
  @keyframes skeleton_loading {
@@ -1392,6 +1405,19 @@
1392
1405
  clip: auto;
1393
1406
  }
1394
1407
  }
1408
+ @media (prefers-reduced-motion: reduce) {
1409
+ .sidebar,
1410
+ .sidebar_header,
1411
+ .sidebar_header_layer,
1412
+ .sidebar_item,
1413
+ .sidebar_item_label,
1414
+ .sidebar_collapse_btn {
1415
+ transition: none;
1416
+ }
1417
+ .sidebar_collapse_btn:hover {
1418
+ transform: none;
1419
+ }
1420
+ }
1395
1421
 
1396
1422
  /* src/ui/navigation/tabs/style.scss */
1397
1423
  @keyframes skeleton_loading {
@@ -1512,6 +1538,12 @@
1512
1538
  box-shadow: var(--bt-focus-ring);
1513
1539
  border-radius: 6px;
1514
1540
  }
1541
+ @media (prefers-reduced-motion: reduce) {
1542
+ .tabs_indicator_animated,
1543
+ .tabs_tab {
1544
+ transition: none;
1545
+ }
1546
+ }
1515
1547
 
1516
1548
  /* src/ui/overlay/popover/style.scss */
1517
1549
  @keyframes skeleton_loading {
@@ -1878,6 +1910,15 @@
1878
1910
  .button_danger.button_variant_text:hover:not(:disabled) {
1879
1911
  background: color-mix(in srgb, var(--bt-color-status-error) 8%, transparent);
1880
1912
  }
1913
+ @media (prefers-reduced-motion: reduce) {
1914
+ .button,
1915
+ .button::before {
1916
+ transition: none;
1917
+ }
1918
+ .button:active {
1919
+ transform: none;
1920
+ }
1921
+ }
1881
1922
 
1882
1923
  /* src/ui/feedback/alert/style.scss */
1883
1924
  @keyframes skeleton_loading {
@@ -2252,6 +2293,11 @@
2252
2293
  transform: translate(-50%, -50%) scaleX(1);
2253
2294
  }
2254
2295
  }
2296
+ @media (prefers-reduced-motion: reduce) {
2297
+ .checkbox_icon::after {
2298
+ animation: none;
2299
+ }
2300
+ }
2255
2301
 
2256
2302
  /* src/ui/display/chip/style.scss */
2257
2303
  @keyframes skeleton_loading {
@@ -2753,6 +2799,11 @@
2753
2799
  border: none;
2754
2800
  margin: 0;
2755
2801
  }
2802
+ @media (prefers-reduced-motion: reduce) {
2803
+ .dropdown_icon {
2804
+ transition: none;
2805
+ }
2806
+ }
2756
2807
 
2757
2808
  /* src/ui/forms/date-picker/style.scss */
2758
2809
  @keyframes skeleton_loading {
@@ -2993,6 +3044,14 @@
2993
3044
  .file_input_variant_preview .file_input_helper {
2994
3045
  display: block;
2995
3046
  }
3047
+ @media (prefers-reduced-motion: reduce) {
3048
+ .file_input_preview_remove {
3049
+ transition: none;
3050
+ }
3051
+ .file_input_preview_remove:hover {
3052
+ transform: none;
3053
+ }
3054
+ }
2996
3055
 
2997
3056
  /* src/ui/display/hero/style.scss */
2998
3057
  @keyframes skeleton_loading {
@@ -3653,6 +3712,14 @@
3653
3712
  .media_card_image_overlay .media_card_meta {
3654
3713
  color: rgba(255, 255, 255, 0.6);
3655
3714
  }
3715
+ @media (prefers-reduced-motion: reduce) {
3716
+ .media_card {
3717
+ transition: none;
3718
+ }
3719
+ .media_card_clickable:hover {
3720
+ transform: none;
3721
+ }
3722
+ }
3656
3723
 
3657
3724
  /* src/ui/overlay/modal/style.scss */
3658
3725
  @keyframes skeleton_loading {
@@ -4090,6 +4157,11 @@
4090
4157
  transform: translate(-50%, -50%) scale(1);
4091
4158
  }
4092
4159
  }
4160
+ @media (prefers-reduced-motion: reduce) {
4161
+ .radio_dot::after {
4162
+ animation: none;
4163
+ }
4164
+ }
4093
4165
 
4094
4166
  /* src/ui/feedback/skeleton/style.scss */
4095
4167
  @keyframes skeleton_loading {
@@ -4150,6 +4222,11 @@
4150
4222
  background-position: 0 0;
4151
4223
  }
4152
4224
  }
4225
+ @media (prefers-reduced-motion: reduce) {
4226
+ .skeleton {
4227
+ animation: none;
4228
+ }
4229
+ }
4153
4230
 
4154
4231
  /* src/ui/feedback/spinner/style.scss */
4155
4232
  @keyframes skeleton_loading {
@@ -4952,6 +5029,12 @@
4952
5029
  .toggle_disabled .toggle_thumb {
4953
5030
  background: var(--bt-color-brand-on-primary);
4954
5031
  }
5032
+ @media (prefers-reduced-motion: reduce) {
5033
+ .toggle,
5034
+ .toggle_thumb {
5035
+ transition: none;
5036
+ }
5037
+ }
4955
5038
 
4956
5039
  /* src/ui/feedback/top-loading/style.scss */
4957
5040
  @keyframes skeleton_loading {
@@ -4988,6 +5071,14 @@
4988
5071
  transform: translateX(400%);
4989
5072
  }
4990
5073
  }
5074
+ @media (prefers-reduced-motion: reduce) {
5075
+ .top_loading_bar {
5076
+ transition: none;
5077
+ }
5078
+ .top_loading_indeterminate {
5079
+ animation: none;
5080
+ }
5081
+ }
4991
5082
 
4992
5083
  /* src/ui/layout/container/style.scss */
4993
5084
  @keyframes skeleton_loading {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import './index.css';
3
- import * as React19 from 'react';
3
+ import * as React20 from 'react';
4
4
  import { createContext, useRef, useState, useLayoutEffect, useId, useEffect, useContext, useCallback, useMemo, Fragment } 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';
@@ -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
@@ -140,7 +177,7 @@ var Accordion = ({
140
177
  ...props
141
178
  }) => {
142
179
  const isControlled = controlledKeys !== void 0;
143
- const [internalKeys, setInternalKeys] = React19.useState(defaultOpenKeys);
180
+ const [internalKeys, setInternalKeys] = React20.useState(defaultOpenKeys);
144
181
  const open = isControlled ? controlledKeys ?? [] : internalKeys;
145
182
  const toggle = (key) => {
146
183
  const isOpen = open.includes(key);
@@ -206,7 +243,7 @@ var Avatar = ({
206
243
  style,
207
244
  ...props
208
245
  }) => {
209
- const [imgFailed, setImgFailed] = React19.useState(false);
246
+ const [imgFailed, setImgFailed] = React20.useState(false);
210
247
  const showImage = src && !imgFailed;
211
248
  const initials = getInitials(name);
212
249
  return /* @__PURE__ */ jsx(
@@ -424,11 +461,11 @@ var ErrorState = ({
424
461
  );
425
462
  };
426
463
  var Menu = ({ items, trigger, align = "start" }) => {
427
- const [open, setOpen] = React19.useState(false);
428
- const wrapperRef = React19.useRef(null);
429
- const menuId = React19.useId();
464
+ const [open, setOpen] = React20.useState(false);
465
+ const wrapperRef = React20.useRef(null);
466
+ const menuId = React20.useId();
430
467
  const style = useSpringPresence({ visible: open, from: "translateY(-4px)" });
431
- React19.useEffect(() => {
468
+ React20.useEffect(() => {
432
469
  if (!open) return;
433
470
  const handleClick = (e) => {
434
471
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -443,7 +480,7 @@ var Menu = ({ items, trigger, align = "start" }) => {
443
480
  document.removeEventListener("keydown", handleEsc);
444
481
  };
445
482
  }, [open]);
446
- const triggerWithProps = React19.cloneElement(
483
+ const triggerWithProps = React20.cloneElement(
447
484
  trigger,
448
485
  {
449
486
  onClick: () => setOpen((o) => !o),
@@ -662,9 +699,9 @@ var Sidebar = ({
662
699
  ...props
663
700
  }) => {
664
701
  const isControlled = collapsedProp !== void 0;
665
- const [internalCollapsed, setInternalCollapsed] = React19.useState(defaultCollapsed);
702
+ const [internalCollapsed, setInternalCollapsed] = React20.useState(defaultCollapsed);
666
703
  const collapsed = isControlled ? collapsedProp : internalCollapsed;
667
- const toggle = React19.useCallback(() => {
704
+ const toggle = React20.useCallback(() => {
668
705
  const next = !collapsed;
669
706
  if (!isControlled) setInternalCollapsed(next);
670
707
  onCollapsedChange?.(next);
@@ -737,9 +774,9 @@ var SidebarSection = ({ label, className, children, ...props }) => {
737
774
  children
738
775
  ] });
739
776
  };
740
- var TabsContext = React19.createContext(null);
777
+ var TabsContext = React20.createContext(null);
741
778
  function useTabsContext() {
742
- const ctx = React19.useContext(TabsContext);
779
+ const ctx = React20.useContext(TabsContext);
743
780
  if (!ctx) {
744
781
  throw new Error(
745
782
  '[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 +795,17 @@ var Tabs = ({
758
795
  ...props
759
796
  }) => {
760
797
  const isControlled = controlledValue !== void 0;
761
- const [internalValue, setInternalValue] = React19.useState(defaultValue);
798
+ const [internalValue, setInternalValue] = React20.useState(defaultValue);
762
799
  const value = isControlled ? controlledValue : internalValue;
763
- const setValue = React19.useCallback(
800
+ const setValue = React20.useCallback(
764
801
  (v) => {
765
802
  if (!isControlled) setInternalValue(v);
766
803
  onValueChange?.(v);
767
804
  },
768
805
  [isControlled, onValueChange]
769
806
  );
770
- const idPrefix = React19.useId();
771
- const ctx = React19.useMemo(
807
+ const idPrefix = React20.useId();
808
+ const ctx = React20.useMemo(
772
809
  () => ({ value, setValue, variant, size, idPrefix }),
773
810
  [value, setValue, variant, size, idPrefix]
774
811
  );
@@ -776,10 +813,10 @@ var Tabs = ({
776
813
  };
777
814
  var TabList = ({ ariaLabel, className, children, ...props }) => {
778
815
  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(() => {
816
+ const listRef = React20.useRef(null);
817
+ const [indicator, setIndicator] = React20.useState(null);
818
+ const [hasMounted, setHasMounted] = React20.useState(false);
819
+ React20.useLayoutEffect(() => {
783
820
  const list = listRef.current;
784
821
  if (!list) return;
785
822
  const update = () => {
@@ -927,15 +964,15 @@ var Popover = ({
927
964
  className
928
965
  }) => {
929
966
  const isControlled = openProp !== void 0;
930
- const [internalOpen, setInternalOpen] = React19.useState(defaultOpen);
967
+ const [internalOpen, setInternalOpen] = React20.useState(defaultOpen);
931
968
  const open = isControlled ? openProp : internalOpen;
932
- const [shouldRender, setShouldRender] = React19.useState(open ?? false);
969
+ const [shouldRender, setShouldRender] = React20.useState(open ?? false);
933
970
  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(
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(
939
976
  (next) => {
940
977
  if (!isControlled) setInternalOpen(next);
941
978
  onOpenChange?.(next);
@@ -955,7 +992,7 @@ var Popover = ({
955
992
  }
956
993
  })();
957
994
  const style = useSpringPresence({ visible: open, from: fromTransform, onExitComplete: () => setShouldRender(false) });
958
- React19.useEffect(() => {
995
+ React20.useEffect(() => {
959
996
  if (!open) return;
960
997
  previousFocusRef.current = document.activeElement;
961
998
  const node = popoverRef.current;
@@ -963,7 +1000,7 @@ var Popover = ({
963
1000
  const focusable = node.querySelector(FOCUSABLE_SELECTORS2);
964
1001
  (focusable ?? node).focus();
965
1002
  }, [open]);
966
- React19.useEffect(() => {
1003
+ React20.useEffect(() => {
967
1004
  if (!open) return;
968
1005
  const handleClick = (e) => {
969
1006
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -981,7 +1018,7 @@ var Popover = ({
981
1018
  document.removeEventListener("keydown", handleKeyDown);
982
1019
  };
983
1020
  }, [open, setOpen]);
984
- const triggerWithProps = React19.cloneElement(
1021
+ const triggerWithProps = React20.cloneElement(
985
1022
  trigger,
986
1023
  {
987
1024
  onClick: (e) => {
@@ -1020,18 +1057,18 @@ var Tooltip = ({
1020
1057
  disabled = false,
1021
1058
  children
1022
1059
  }) => {
1023
- const [open, setOpen] = React19.useState(false);
1024
- const timerRef = React19.useRef(null);
1025
- const tooltipId = React19.useId();
1026
- const show = React19.useCallback(() => {
1060
+ const [open, setOpen] = React20.useState(false);
1061
+ const timerRef = React20.useRef(null);
1062
+ const tooltipId = React20.useId();
1063
+ const show = React20.useCallback(() => {
1027
1064
  if (timerRef.current) clearTimeout(timerRef.current);
1028
1065
  timerRef.current = setTimeout(() => setOpen(true), delay);
1029
1066
  }, [delay]);
1030
- const hide = React19.useCallback(() => {
1067
+ const hide = React20.useCallback(() => {
1031
1068
  if (timerRef.current) clearTimeout(timerRef.current);
1032
1069
  setOpen(false);
1033
1070
  }, []);
1034
- React19.useEffect(() => {
1071
+ React20.useEffect(() => {
1035
1072
  return () => {
1036
1073
  if (timerRef.current) clearTimeout(timerRef.current);
1037
1074
  };
@@ -1049,7 +1086,7 @@ var Tooltip = ({
1049
1086
  }
1050
1087
  })();
1051
1088
  const style = useSpringPresence({ visible: open, from: fromTransform });
1052
- const trigger = React19.cloneElement(children, {
1089
+ const trigger = React20.cloneElement(children, {
1053
1090
  onMouseEnter: show,
1054
1091
  onMouseLeave: hide,
1055
1092
  onFocus: show,
@@ -1634,12 +1671,12 @@ var AlertModal = ({
1634
1671
  onCancel,
1635
1672
  onClose
1636
1673
  }) => {
1637
- const panelRef = React19.useRef(null);
1638
- const titleId = React19.useId();
1639
- const messageId = React19.useId();
1674
+ const panelRef = React20.useRef(null);
1675
+ const titleId = React20.useId();
1676
+ const messageId = React20.useId();
1640
1677
  const [shouldRender, setShouldRender] = useState(isOpen);
1641
1678
  useFocusTrap(panelRef, isOpen);
1642
- React19.useEffect(() => {
1679
+ React20.useEffect(() => {
1643
1680
  if (isOpen) setShouldRender(true);
1644
1681
  }, [isOpen]);
1645
1682
  const overlayStyle = useSpring({
@@ -1746,12 +1783,12 @@ var Checkbox = ({
1746
1783
  ref,
1747
1784
  ...props
1748
1785
  }) => {
1749
- const inputRef = React19.useRef(null);
1750
- React19.useImperativeHandle(
1786
+ const inputRef = React20.useRef(null);
1787
+ React20.useImperativeHandle(
1751
1788
  ref,
1752
1789
  () => inputRef.current
1753
1790
  );
1754
- React19.useEffect(() => {
1791
+ React20.useEffect(() => {
1755
1792
  if (!inputRef.current) return;
1756
1793
  inputRef.current.indeterminate = Boolean(indeterminate);
1757
1794
  }, [indeterminate]);
@@ -2156,9 +2193,9 @@ var DatePicker = ({
2156
2193
  minDateSrFormat = "Minimum date: {date}",
2157
2194
  selectableRangeUntilTodaySrText = "Selectable up to today"
2158
2195
  }) => {
2159
- const groupId = React19.useId();
2160
- const constraintId = React19.useId();
2161
- const { todayYear, todayMonth, todayDay } = React19.useMemo(() => {
2196
+ const groupId = React20.useId();
2197
+ const constraintId = React20.useId();
2198
+ const { todayYear, todayMonth, todayDay } = React20.useMemo(() => {
2162
2199
  const now = /* @__PURE__ */ new Date();
2163
2200
  return {
2164
2201
  todayYear: now.getFullYear(),
@@ -2167,7 +2204,7 @@ var DatePicker = ({
2167
2204
  };
2168
2205
  }, []);
2169
2206
  const endYear = endYearProp ?? todayYear + 10;
2170
- const parsed = React19.useMemo(() => {
2207
+ const parsed = React20.useMemo(() => {
2171
2208
  if (!value) return { year: 0, month: 0, day: 0 };
2172
2209
  const [y, m, d] = value.split("-").map(Number);
2173
2210
  return {
@@ -2176,7 +2213,7 @@ var DatePicker = ({
2176
2213
  day: d || 0
2177
2214
  };
2178
2215
  }, [value]);
2179
- const min = React19.useMemo(() => {
2216
+ const min = React20.useMemo(() => {
2180
2217
  if (!minDate) return { year: 0, month: 0, day: 0 };
2181
2218
  const [y, m, d] = minDate.split("-").map(Number);
2182
2219
  return {
@@ -2196,7 +2233,7 @@ var DatePicker = ({
2196
2233
  min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
2197
2234
  )
2198
2235
  );
2199
- const maxDay = React19.useMemo(() => {
2236
+ const maxDay = React20.useMemo(() => {
2200
2237
  if (!year || !month) return 31;
2201
2238
  const daysInMonth = getDaysInMonth(year, month);
2202
2239
  if (selectableRange === "until-today" && year === todayYear && month === todayMonth) {
@@ -2204,28 +2241,28 @@ var DatePicker = ({
2204
2241
  }
2205
2242
  return daysInMonth;
2206
2243
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
2207
- const yearOptions = React19.useMemo(
2244
+ const yearOptions = React20.useMemo(
2208
2245
  () => range(startYear, maxYear).map((y) => ({
2209
2246
  value: String(y),
2210
2247
  label: String(y)
2211
2248
  })),
2212
2249
  [startYear, maxYear]
2213
2250
  );
2214
- const monthOptions = React19.useMemo(
2251
+ const monthOptions = React20.useMemo(
2215
2252
  () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
2216
2253
  value: String(m),
2217
2254
  label: pad(m)
2218
2255
  })),
2219
2256
  [minMonth, maxMonth]
2220
2257
  );
2221
- const dayOptions = React19.useMemo(
2258
+ const dayOptions = React20.useMemo(
2222
2259
  () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
2223
2260
  value: String(d),
2224
2261
  label: pad(d)
2225
2262
  })),
2226
2263
  [minDay, maxDay]
2227
2264
  );
2228
- const emit = React19.useCallback(
2265
+ const emit = React20.useCallback(
2229
2266
  (yy, mm, dd) => {
2230
2267
  if (mode === "year-month") {
2231
2268
  onChange(`${yy}-${pad(mm)}`);
@@ -2236,7 +2273,7 @@ var DatePicker = ({
2236
2273
  },
2237
2274
  [mode, onChange]
2238
2275
  );
2239
- const handleYearChange = React19.useCallback(
2276
+ const handleYearChange = React20.useCallback(
2240
2277
  (raw) => {
2241
2278
  if (!raw) return;
2242
2279
  const newYear = Number(raw);
@@ -2253,14 +2290,14 @@ var DatePicker = ({
2253
2290
  },
2254
2291
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
2255
2292
  );
2256
- const handleMonthChange = React19.useCallback(
2293
+ const handleMonthChange = React20.useCallback(
2257
2294
  (raw) => {
2258
2295
  if (!raw || !year) return;
2259
2296
  emit(year, Number(raw), day || void 0);
2260
2297
  },
2261
2298
  [year, day, emit]
2262
2299
  );
2263
- const handleDayChange = React19.useCallback(
2300
+ const handleDayChange = React20.useCallback(
2264
2301
  (raw) => {
2265
2302
  if (!raw || !year || !month) return;
2266
2303
  emit(year, month, Number(raw));
@@ -2348,10 +2385,10 @@ var FileInput = ({
2348
2385
  onChange,
2349
2386
  ...props
2350
2387
  }) => {
2351
- const inputId = React19.useId();
2352
- const helperId = React19.useId();
2353
- const inputRef = React19.useRef(null);
2354
- const [previewUrls, setPreviewUrls] = React19.useState([]);
2388
+ const inputId = React20.useId();
2389
+ const helperId = React20.useId();
2390
+ const inputRef = React20.useRef(null);
2391
+ const [previewUrls, setPreviewUrls] = React20.useState([]);
2355
2392
  const isPreviewVariant = variant === "preview";
2356
2393
  const showPreview = isPreviewVariant || preview;
2357
2394
  const handleChange = (e) => {
@@ -2389,7 +2426,7 @@ var FileInput = ({
2389
2426
  }
2390
2427
  onFiles?.(null);
2391
2428
  };
2392
- React19.useEffect(() => {
2429
+ React20.useEffect(() => {
2393
2430
  return () => {
2394
2431
  for (const url of previewUrls) {
2395
2432
  URL.revokeObjectURL(url);
@@ -2686,11 +2723,11 @@ var Modal = ({
2686
2723
  ariaLabel,
2687
2724
  ...props
2688
2725
  }) => {
2689
- const panelRef = React19.useRef(null);
2690
- const titleId = React19.useId();
2691
- const [shouldRender, setShouldRender] = React19.useState(open);
2726
+ const panelRef = React20.useRef(null);
2727
+ const titleId = React20.useId();
2728
+ const [shouldRender, setShouldRender] = React20.useState(open);
2692
2729
  useFocusTrap(panelRef, open);
2693
- React19.useEffect(() => {
2730
+ React20.useEffect(() => {
2694
2731
  if (open) setShouldRender(true);
2695
2732
  }, [open]);
2696
2733
  const overlayStyle = useSpring({
@@ -2705,15 +2742,15 @@ var Modal = ({
2705
2742
  transform: open ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
2706
2743
  config: { tension: 280, friction: 28, clamp: !open }
2707
2744
  });
2708
- const handleEscape = React19.useEffectEvent((e) => {
2745
+ const handleEscape = React20.useEffectEvent((e) => {
2709
2746
  if (e.key === "Escape") onClose?.();
2710
2747
  });
2711
- React19.useEffect(() => {
2748
+ React20.useEffect(() => {
2712
2749
  if (!open) return;
2713
2750
  document.addEventListener("keydown", handleEscape);
2714
2751
  return () => document.removeEventListener("keydown", handleEscape);
2715
2752
  }, [open]);
2716
- React19.useEffect(() => {
2753
+ React20.useEffect(() => {
2717
2754
  if (!open) return;
2718
2755
  const body = document.body;
2719
2756
  const openModals = parseInt(body.dataset.openModals || "0", 10);
@@ -2793,12 +2830,12 @@ var OtpInput = ({
2793
2830
  ariaLabel = "OTP \uC785\uB825",
2794
2831
  className
2795
2832
  }) => {
2796
- const inputsRef = React19.useRef([]);
2797
- const isTypingRef = React19.useRef(false);
2798
- React19.useEffect(() => {
2833
+ const inputsRef = React20.useRef([]);
2834
+ const isTypingRef = React20.useRef(false);
2835
+ React20.useEffect(() => {
2799
2836
  if (autoFocus) inputsRef.current[0]?.focus();
2800
2837
  }, [autoFocus]);
2801
- const digits = React19.useMemo(() => {
2838
+ const digits = React20.useMemo(() => {
2802
2839
  const chars = value.split("").slice(0, length);
2803
2840
  while (chars.length < length) chars.push("");
2804
2841
  return chars;
@@ -2937,7 +2974,7 @@ var Pagination = ({
2937
2974
  }) => {
2938
2975
  const prevDisabled = page <= 1;
2939
2976
  const nextDisabled = page >= totalPages;
2940
- const items = React19.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
2977
+ const items = React20.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
2941
2978
  return /* @__PURE__ */ jsxs("nav", { className: "pagination", "aria-label": "Pagination", children: [
2942
2979
  /* @__PURE__ */ jsx(
2943
2980
  "button",
@@ -2982,9 +3019,9 @@ var Pagination = ({
2982
3019
  )
2983
3020
  ] });
2984
3021
  };
2985
- var RadioGroupContext = React19.createContext(null);
3022
+ var RadioGroupContext = React20.createContext(null);
2986
3023
  function useRadioGroupContext() {
2987
- return React19.useContext(RadioGroupContext);
3024
+ return React20.useContext(RadioGroupContext);
2988
3025
  }
2989
3026
  var RadioGroup = ({
2990
3027
  value: controlledValue,
@@ -3002,21 +3039,21 @@ var RadioGroup = ({
3002
3039
  ...props
3003
3040
  }) => {
3004
3041
  const isControlled = controlledValue !== void 0;
3005
- const [internalValue, setInternalValue] = React19.useState(defaultValue);
3042
+ const [internalValue, setInternalValue] = React20.useState(defaultValue);
3006
3043
  const value = isControlled ? controlledValue : internalValue;
3007
- const generatedName = React19.useId();
3044
+ const generatedName = React20.useId();
3008
3045
  const name = nameProp ?? generatedName;
3009
- const idPrefix = React19.useId();
3046
+ const idPrefix = React20.useId();
3010
3047
  const labelId = label ? `${idPrefix}-label` : void 0;
3011
3048
  const helperId = supportingText ? `${idPrefix}-help` : void 0;
3012
- const onChange = React19.useCallback(
3049
+ const onChange = React20.useCallback(
3013
3050
  (next) => {
3014
3051
  if (!isControlled) setInternalValue(next);
3015
3052
  onValueChange?.(next);
3016
3053
  },
3017
3054
  [isControlled, onValueChange]
3018
3055
  );
3019
- const ctx = React19.useMemo(
3056
+ const ctx = React20.useMemo(
3020
3057
  () => ({ name, value, onChange, size, disabled }),
3021
3058
  [name, value, onChange, size, disabled]
3022
3059
  );
@@ -3214,9 +3251,9 @@ var Table = ({
3214
3251
  isEmpty && /* @__PURE__ */ jsx("div", { className: "table_empty", role: "status", children: emptyMessage })
3215
3252
  ] });
3216
3253
  };
3217
- var ThemeContext = React19.createContext(null);
3254
+ var ThemeContext = React20.createContext(null);
3218
3255
  var useTheme = () => {
3219
- const ctx = React19.useContext(ThemeContext);
3256
+ const ctx = React20.useContext(ThemeContext);
3220
3257
  if (!ctx) {
3221
3258
  throw new Error(
3222
3259
  '[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 +3277,14 @@ var ThemeProvider = ({
3240
3277
  targetSelector,
3241
3278
  children
3242
3279
  }) => {
3243
- const [mode, setModeState] = React19.useState(() => {
3280
+ const [mode, setModeState] = React20.useState(() => {
3244
3281
  return readStored(storageKey) ?? defaultMode;
3245
3282
  });
3246
- const [systemDark, setSystemDark] = React19.useState(() => {
3283
+ const [systemDark, setSystemDark] = React20.useState(() => {
3247
3284
  if (!isClient) return false;
3248
3285
  return window.matchMedia("(prefers-color-scheme: dark)").matches;
3249
3286
  });
3250
- React19.useEffect(() => {
3287
+ React20.useEffect(() => {
3251
3288
  if (!isClient) return;
3252
3289
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
3253
3290
  const handler = (e) => setSystemDark(e.matches);
@@ -3255,7 +3292,7 @@ var ThemeProvider = ({
3255
3292
  return () => mq.removeEventListener("change", handler);
3256
3293
  }, []);
3257
3294
  const resolved = mode === "system" ? systemDark ? "dark" : "light" : mode;
3258
- React19.useEffect(() => {
3295
+ React20.useEffect(() => {
3259
3296
  if (!isClient) return;
3260
3297
  const target = targetSelector ? document.querySelector(targetSelector) : document.documentElement;
3261
3298
  if (!target) return;
@@ -3265,7 +3302,7 @@ var ThemeProvider = ({
3265
3302
  target.setAttribute("data-theme", mode);
3266
3303
  }
3267
3304
  }, [mode, targetSelector]);
3268
- const setMode = React19.useCallback(
3305
+ const setMode = React20.useCallback(
3269
3306
  (next) => {
3270
3307
  setModeState(next);
3271
3308
  if (storageKey && isClient) {
@@ -3277,7 +3314,7 @@ var ThemeProvider = ({
3277
3314
  },
3278
3315
  [storageKey]
3279
3316
  );
3280
- const value = React19.useMemo(
3317
+ const value = React20.useMemo(
3281
3318
  () => ({ mode, resolved, setMode }),
3282
3319
  [mode, resolved, setMode]
3283
3320
  );
@@ -3523,7 +3560,7 @@ var Textarea = ({
3523
3560
  ] });
3524
3561
  };
3525
3562
  Textarea.displayName = "Textarea";
3526
- var ToastContext = React19.createContext(null);
3563
+ var ToastContext = React20.createContext(null);
3527
3564
  var VARIANT_ICONS = {
3528
3565
  success: /* @__PURE__ */ jsx(CheckCircle2, { size: 18 }),
3529
3566
  error: /* @__PURE__ */ jsx(XCircle, { size: 18 }),
@@ -3532,15 +3569,15 @@ var VARIANT_ICONS = {
3532
3569
  default: /* @__PURE__ */ jsx(Bell, { size: 18 })
3533
3570
  };
3534
3571
  var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
3535
- const [visible, setVisible] = React19.useState(true);
3536
- const closingRef = React19.useRef(false);
3572
+ const [visible, setVisible] = React20.useState(true);
3573
+ const closingRef = React20.useRef(false);
3537
3574
  const style = useSpringPresence({
3538
3575
  visible,
3539
3576
  from: "translateX(20px)",
3540
3577
  // 우측에서 슬라이드 인
3541
3578
  onExitComplete: () => onRemove(item.id)
3542
3579
  });
3543
- const close = React19.useCallback(() => {
3580
+ const close = React20.useCallback(() => {
3544
3581
  if (closingRef.current) return;
3545
3582
  closingRef.current = true;
3546
3583
  setVisible(false);
@@ -3573,19 +3610,19 @@ var ToastProvider = ({
3573
3610
  maxCount = 5,
3574
3611
  closeAriaLabel = "Close"
3575
3612
  }) => {
3576
- const [toasts, setToasts] = React19.useState([]);
3577
- const [isMounted, setIsMounted] = React19.useState(false);
3578
- React19.useEffect(() => {
3613
+ const [toasts, setToasts] = React20.useState([]);
3614
+ const [isMounted, setIsMounted] = React20.useState(false);
3615
+ React20.useEffect(() => {
3579
3616
  setIsMounted(true);
3580
3617
  }, []);
3581
- const addToast = React19.useCallback(
3618
+ const addToast = React20.useCallback(
3582
3619
  (message, variant, duration = 3e3) => {
3583
3620
  const id = crypto.randomUUID();
3584
3621
  setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
3585
3622
  },
3586
3623
  [maxCount]
3587
3624
  );
3588
- const removeToast = React19.useCallback((id) => {
3625
+ const removeToast = React20.useCallback((id) => {
3589
3626
  setToasts((prev) => prev.filter((t) => t.id !== id));
3590
3627
  }, []);
3591
3628
  return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: { addToast }, children: [
@@ -3647,7 +3684,7 @@ var Toggle = ({
3647
3684
  ...props
3648
3685
  }) => {
3649
3686
  const isControlled = checked !== void 0;
3650
- const [innerChecked, setInnerChecked] = React19.useState(!!defaultChecked);
3687
+ const [innerChecked, setInnerChecked] = React20.useState(!!defaultChecked);
3651
3688
  const isOn = isControlled ? !!checked : innerChecked;
3652
3689
  const handleToggle = () => {
3653
3690
  if (disabled) return;
@@ -32,6 +32,11 @@ $color_base_neutral_800: #333333;
32
32
  $color_base_neutral_900: #121212;
33
33
  $color_base_neutral_925: #141414; // dark panel/card bg (Vercel-style)
34
34
  $color_base_neutral_950: #0A0A0A; // darkest page bg (Vercel-style)
35
+
36
+ // Text caption - AA 전용값 (neutral scale 과 분리: neutral_500/600 은 border-hover 등 재사용되므로 caption 만 따로)
37
+ $color_base_text_caption_light: #6F6F6F; // on #fff 5.0:1 / on #F4F4F4 4.57:1 (WCAG AA)
38
+ $color_base_text_caption_dark: #808080; // on #0A0A0A 5.0:1 / on #141414 4.66:1 (WCAG AA)
39
+
35
40
  // Status - light mode default (darkened for AA on #fff text, 5+:1)
36
41
  // 이전 값 (#EF4444 등) 은 #fff 텍스트 조합 시 AA 미달 (2.0~3.7:1) - Tailwind 700 계열로 교체.
37
42
  $color_base_status_error: #B91C1C; // red-700, with #fff = 6.4:1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",