@bigtablet/design-system 3.14.1 → 3.15.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.css CHANGED
@@ -5708,8 +5708,8 @@ body:has(.bottom_nav) {
5708
5708
  }
5709
5709
  .drawer_close {
5710
5710
  position: absolute;
5711
- top: 12px;
5712
- right: 12px;
5711
+ top: 16px;
5712
+ right: 16px;
5713
5713
  width: 32px;
5714
5714
  height: 32px;
5715
5715
  display: inline-flex;
@@ -5794,6 +5794,7 @@ body:has(.bottom_nav) {
5794
5794
  bottom: 0;
5795
5795
  left: 0;
5796
5796
  display: grid;
5797
+ grid-template-columns: minmax(0, 1fr);
5797
5798
  place-items: center;
5798
5799
  background: var(--bt-color-bg-overlay);
5799
5800
  z-index: 100;
@@ -5821,8 +5822,8 @@ body:has(.bottom_nav) {
5821
5822
  }
5822
5823
  .modal_close {
5823
5824
  position: absolute;
5824
- top: 12px;
5825
- right: 12px;
5825
+ top: 16px;
5826
+ right: 16px;
5826
5827
  width: 32px;
5827
5828
  height: 32px;
5828
5829
  display: inline-flex;
@@ -5843,9 +5844,15 @@ body:has(.bottom_nav) {
5843
5844
  outline: none;
5844
5845
  box-shadow: var(--bt-focus-ring);
5845
5846
  }
5847
+ @media (min-width: 600px) {
5848
+ .modal_close {
5849
+ top: 24px;
5850
+ right: 24px;
5851
+ }
5852
+ }
5846
5853
  .modal_title {
5847
5854
  margin: 0;
5848
- padding-right: 32px;
5855
+ padding-right: 24px;
5849
5856
  color: var(--bt-color-text-heading);
5850
5857
  font-size: 28px;
5851
5858
  font-weight: 700;
package/dist/index.d.ts CHANGED
@@ -866,6 +866,7 @@ declare const spacing: {
866
866
  readonly "4": "4px";
867
867
  readonly "6": "6px";
868
868
  readonly "8": "8px";
869
+ readonly "10": "10px";
869
870
  readonly "12": "12px";
870
871
  readonly "16": "16px";
871
872
  readonly "20": "20px";
@@ -873,6 +874,9 @@ declare const spacing: {
873
874
  readonly "32": "32px";
874
875
  readonly "40": "40px";
875
876
  readonly "48": "48px";
877
+ readonly "64": "64px";
878
+ readonly "96": "96px";
879
+ readonly "128": "128px";
876
880
  };
877
881
 
878
882
  declare const baseTypography: {
@@ -880,6 +884,7 @@ declare const baseTypography: {
880
884
  readonly primary: "Pretendard";
881
885
  };
882
886
  readonly fontSize: {
887
+ readonly "10": "10px";
883
888
  readonly "12": "12px";
884
889
  readonly "13": "13px";
885
890
  readonly "14": "14px";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import './index.css';
3
- import * as React16 from 'react';
3
+ import * as React11 from 'react';
4
4
  import { createContext, useRef, useState, useId, useEffect, useContext, useCallback, useMemo, Fragment, useImperativeHandle, useLayoutEffect } from 'react';
5
5
  import { useSpring, animated } from '@react-spring/web';
6
6
  import { ChevronDown, ChevronRight, Globe, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, XCircle, AlertTriangle, CheckCircle2, Info, Bell, Search, Check, Image, X, EyeOff, Eye, TriangleAlert } from 'lucide-react';
@@ -58,11 +58,11 @@ function registerOverlay(onEscape) {
58
58
  };
59
59
  }
60
60
  function useOverlayEscape(active, onEscape) {
61
- const handlerRef = React16.useRef(onEscape);
62
- React16.useEffect(() => {
61
+ const handlerRef = React11.useRef(onEscape);
62
+ React11.useEffect(() => {
63
63
  handlerRef.current = onEscape;
64
64
  });
65
- React16.useEffect(() => {
65
+ React11.useEffect(() => {
66
66
  if (!active) return;
67
67
  return registerOverlay(() => handlerRef.current());
68
68
  }, [active]);
@@ -121,6 +121,15 @@ function unlockBodyScroll() {
121
121
  body.dataset[COUNTER] = String(remaining);
122
122
  }
123
123
  }
124
+
125
+ // src/utils/spring-motion.ts
126
+ function springEnterFrom(reduced, transform) {
127
+ if (reduced) return {};
128
+ return { from: transform === void 0 ? { opacity: 0 } : { opacity: 0, transform } };
129
+ }
130
+ var OVERLAY_PANEL_CLOSED_TRANSFORM = "scale(0.96) translateY(-4px)";
131
+ var OVERLAY_PANEL_OPEN_TRANSFORM = "scale(1) translateY(0px)";
132
+ var OVERLAY_SPRING_CONFIG = { tension: 280, friction: 28, clamp: true };
124
133
  var useSafeLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
125
134
 
126
135
  // src/utils/use-anchored-position.ts
@@ -194,7 +203,7 @@ function useAnchoredPosition({
194
203
  gap,
195
204
  padding
196
205
  }) {
197
- const [state, setState] = React16.useState({
206
+ const [state, setState] = React11.useState({
198
207
  x: 0,
199
208
  y: 0,
200
209
  placement,
@@ -253,8 +262,8 @@ var FOCUSABLE_SELECTORS = [
253
262
  '[tabindex]:not([tabindex="-1"])'
254
263
  ].join(", ");
255
264
  function useFocusTrap(containerRef, isActive) {
256
- const previousActiveElement = React16.useRef(null);
257
- React16.useEffect(() => {
265
+ const previousActiveElement = React11.useRef(null);
266
+ React11.useEffect(() => {
258
267
  if (!isActive) return;
259
268
  const container = containerRef.current;
260
269
  if (!container) return;
@@ -304,8 +313,8 @@ function useFocusTrap(containerRef, isActive) {
304
313
  }, [isActive, containerRef]);
305
314
  }
306
315
  function useIsMounted() {
307
- const [mounted, setMounted] = React16.useState(false);
308
- React16.useEffect(() => {
316
+ const [mounted, setMounted] = React11.useState(false);
317
+ React11.useEffect(() => {
309
318
  setMounted(true);
310
319
  }, []);
311
320
  return mounted;
@@ -339,13 +348,13 @@ function getServerSnapshot() {
339
348
  return false;
340
349
  }
341
350
  function useReducedMotion() {
342
- return React16.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
351
+ return React11.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
343
352
  }
344
353
  function useSpringHover({
345
354
  scale = 1.02,
346
355
  lift = -2
347
356
  } = {}) {
348
- const [hovered, setHovered] = React16.useState(false);
357
+ const [hovered, setHovered] = React11.useState(false);
349
358
  const reduced = useReducedMotion();
350
359
  const style = useSpring({
351
360
  transform: hovered ? `translateY(${lift}px) scale(${scale})` : "translateY(0px) scale(1)",
@@ -368,7 +377,10 @@ function useSpringPresence({
368
377
  }) {
369
378
  const reduced = useReducedMotion();
370
379
  return useSpring({
371
- from: { opacity: 0, transform: from },
380
+ // reduced-motion 에서는 `from` 생략한다 - 주면 첫 프레임에 from 이 DOM 에 커밋된 뒤
381
+ // immediate 가 목표값으로 점프해 모션 대신 한 프레임 깜빡임이 남는다. Toast 는 항상
382
+ // visible=true 로 마운트하므로 이 경로를 무조건 탄다. 자세한 근거는 springEnterFrom JSDoc.
383
+ ...springEnterFrom(reduced, from),
372
384
  to: {
373
385
  opacity: visible ? 1 : 0,
374
386
  transform: visible ? "translateY(0px)" : from
@@ -409,9 +421,9 @@ var Accordion = ({
409
421
  ...props
410
422
  }) => {
411
423
  const isControlled = controlledKeys !== void 0;
412
- const [internalKeys, setInternalKeys] = React16.useState(defaultOpenKeys);
424
+ const [internalKeys, setInternalKeys] = React11.useState(defaultOpenKeys);
413
425
  const open = isControlled ? controlledKeys ?? [] : internalKeys;
414
- const idPrefix = React16.useId();
426
+ const idPrefix = React11.useId();
415
427
  const toggle = (key) => {
416
428
  const isOpen = open.includes(key);
417
429
  const next = isOpen ? open.filter((k) => k !== key) : multiple ? [...open, key] : [key];
@@ -477,7 +489,7 @@ var Avatar = ({
477
489
  style,
478
490
  ...props
479
491
  }) => {
480
- const [imgFailed, setImgFailed] = React16.useState(false);
492
+ const [imgFailed, setImgFailed] = React11.useState(false);
481
493
  const showImage = src && !imgFailed;
482
494
  const initials = getInitials(name);
483
495
  return /* @__PURE__ */ jsx(
@@ -491,15 +503,7 @@ var Avatar = ({
491
503
  ...props,
492
504
  children: showImage ? (
493
505
  // biome-ignore lint/performance/noImgElement: DS is framework-agnostic - consumers wrap with next/image
494
- /* @__PURE__ */ jsx(
495
- "img",
496
- {
497
- src,
498
- alt: name,
499
- onError: () => setImgFailed(true),
500
- className: "avatar_image"
501
- }
502
- )
506
+ /* @__PURE__ */ jsx("img", { src, alt: name, onError: () => setImgFailed(true), className: "avatar_image" })
503
507
  ) : /* @__PURE__ */ jsx("span", { className: "avatar_initials", "aria-hidden": "true", children: initials })
504
508
  }
505
509
  );
@@ -602,27 +606,10 @@ var BottomNav = ({
602
606
  children,
603
607
  ...props
604
608
  }) => {
605
- return /* @__PURE__ */ jsx(
606
- "nav",
607
- {
608
- className: cn("bottom_nav", className),
609
- "aria-label": ariaLabel,
610
- ...props,
611
- children
612
- }
613
- );
609
+ return /* @__PURE__ */ jsx("nav", { className: cn("bottom_nav", className), "aria-label": ariaLabel, ...props, children });
614
610
  };
615
611
  var BottomNavItem = (props) => {
616
- const {
617
- icon,
618
- label,
619
- active,
620
- badge,
621
- as = "button",
622
- className,
623
- disabled,
624
- ...rest
625
- } = props;
612
+ const { icon, label, active, badge, as = "button", className, disabled, ...rest } = props;
626
613
  const classes = cn(
627
614
  "bottom_nav_item",
628
615
  active && "bottom_nav_item_active",
@@ -639,27 +626,24 @@ var BottomNavItem = (props) => {
639
626
  ] });
640
627
  if (as === "a") {
641
628
  const { href, onClick: onClick2, ...anchorRest } = rest;
642
- return (
643
- // biome-ignore lint/a11y/useValidAnchor: navigation link with optional active state
644
- /* @__PURE__ */ jsx(
645
- "a",
646
- {
647
- className: classes,
648
- href,
649
- "aria-current": ariaCurrent,
650
- "aria-disabled": disabled ? "true" : void 0,
651
- tabIndex: disabled ? -1 : void 0,
652
- onClick: (e) => {
653
- if (disabled) {
654
- e.preventDefault();
655
- return;
656
- }
657
- onClick2?.(e);
658
- },
659
- ...anchorRest,
660
- children: content
661
- }
662
- )
629
+ return /* @__PURE__ */ jsx(
630
+ "a",
631
+ {
632
+ className: classes,
633
+ href,
634
+ "aria-current": ariaCurrent,
635
+ "aria-disabled": disabled ? "true" : void 0,
636
+ tabIndex: disabled ? -1 : void 0,
637
+ onClick: (e) => {
638
+ if (disabled) {
639
+ e.preventDefault();
640
+ return;
641
+ }
642
+ onClick2?.(e);
643
+ },
644
+ ...anchorRest,
645
+ children: content
646
+ }
663
647
  );
664
648
  }
665
649
  const { type, onClick, ...buttonRest } = rest;
@@ -692,30 +676,19 @@ var Breadcrumb = ({
692
676
  return (
693
677
  // biome-ignore lint/suspicious/noArrayIndexKey: breadcrumb items have stable order
694
678
  /* @__PURE__ */ jsxs("li", { className: "breadcrumb_item", children: [
695
- isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ? (
696
- // biome-ignore lint/a11y/useValidAnchor: navigation link
697
- /* @__PURE__ */ jsx("a", { className: "breadcrumb_link", href: item.href, onClick: item.onClick, children: item.label })
698
- ) : /* @__PURE__ */ jsx(
699
- "button",
700
- {
701
- type: "button",
702
- className: "breadcrumb_link",
703
- onClick: item.onClick,
704
- children: item.label
705
- }
706
- ),
679
+ isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ? /* @__PURE__ */ jsx("a", { className: "breadcrumb_link", href: item.href, onClick: item.onClick, children: item.label }) : /* @__PURE__ */ jsx("button", { type: "button", className: "breadcrumb_link", onClick: item.onClick, children: item.label }),
707
680
  !isLast && /* @__PURE__ */ jsx("span", { className: "breadcrumb_separator", "aria-hidden": "true", children: sep })
708
681
  ] }, idx)
709
682
  );
710
683
  }) }) });
711
684
  };
712
685
  var Menu = ({ items, trigger, align = "start" }) => {
713
- const [open, setOpen] = React16.useState(false);
714
- const wrapperRef = React16.useRef(null);
715
- const itemRefs = React16.useRef([]);
716
- const menuId = React16.useId();
686
+ const [open, setOpen] = React11.useState(false);
687
+ const wrapperRef = React11.useRef(null);
688
+ const itemRefs = React11.useRef([]);
689
+ const menuId = React11.useId();
717
690
  const style = useSpringPresence({ visible: open, from: "translateY(-4px)" });
718
- React16.useEffect(() => {
691
+ React11.useEffect(() => {
719
692
  if (!open) return;
720
693
  const handleClick = (e) => {
721
694
  if (!wrapperRef.current?.contains(e.target)) setOpen(false);
@@ -730,7 +703,7 @@ var Menu = ({ items, trigger, align = "start" }) => {
730
703
  document.removeEventListener("keydown", handleEsc);
731
704
  };
732
705
  }, [open]);
733
- React16.useEffect(() => {
706
+ React11.useEffect(() => {
734
707
  const active = document.activeElement;
735
708
  if (!open || active && itemRefs.current.includes(active)) return;
736
709
  const first = items.findIndex((it) => !it.disabled);
@@ -776,7 +749,7 @@ var Menu = ({ items, trigger, align = "start" }) => {
776
749
  break;
777
750
  }
778
751
  };
779
- const triggerWithProps = React16.cloneElement(
752
+ const triggerWithProps = React11.cloneElement(
780
753
  trigger,
781
754
  {
782
755
  onClick: () => setOpen((o) => !o),
@@ -893,10 +866,7 @@ var NavBar = ({
893
866
  "span",
894
867
  {
895
868
  "aria-hidden": "true",
896
- className: cn(
897
- "nav_bar_indicator",
898
- hasMounted && "nav_bar_indicator_animated"
899
- ),
869
+ className: cn("nav_bar_indicator", hasMounted && "nav_bar_indicator_animated"),
900
870
  style: { left: indicator.left, width: indicator.width }
901
871
  }
902
872
  )
@@ -997,58 +967,39 @@ var LocaleSwitcher = ({ locale }) => {
997
967
  children: [
998
968
  /* @__PURE__ */ jsx(Globe, { size: iconSize.sm, "aria-hidden": "true" }),
999
969
  !locale.hideLabel && /* @__PURE__ */ jsx("span", { className: "nav_bar_locale_label", children: currentLabel }),
1000
- /* @__PURE__ */ jsx(
1001
- ChevronDown,
1002
- {
1003
- size: iconSize.xs,
1004
- "aria-hidden": "true",
1005
- className: "nav_bar_locale_chevron"
1006
- }
1007
- )
970
+ /* @__PURE__ */ jsx(ChevronDown, { size: iconSize.xs, "aria-hidden": "true", className: "nav_bar_locale_chevron" })
1008
971
  ]
1009
972
  }
1010
973
  ),
1011
- open && /* @__PURE__ */ jsx(
1012
- "ul",
974
+ open && /* @__PURE__ */ jsx("ul", { id: menuId, role: "menu", className: "nav_bar_locale_menu", onKeyDown: handleMenuKeyDown, children: locale.options.map((opt, index) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
975
+ "button",
1013
976
  {
1014
- id: menuId,
1015
- role: "menu",
1016
- className: "nav_bar_locale_menu",
1017
- onKeyDown: handleMenuKeyDown,
1018
- children: locale.options.map((opt, index) => /* @__PURE__ */ jsx("li", { role: "none", children: /* @__PURE__ */ jsx(
1019
- "button",
1020
- {
1021
- ref: (el) => {
1022
- itemRefs.current[index] = el;
1023
- },
1024
- type: "button",
1025
- role: "menuitemradio",
1026
- "aria-checked": opt.value === locale.current,
1027
- tabIndex: -1,
1028
- className: cn(
1029
- "nav_bar_locale_item",
1030
- opt.value === locale.current && "nav_bar_locale_item_active"
1031
- ),
1032
- onClick: () => handleSelect(opt.value),
1033
- children: opt.label
1034
- }
1035
- ) }, opt.value))
977
+ ref: (el) => {
978
+ itemRefs.current[index] = el;
979
+ },
980
+ type: "button",
981
+ role: "menuitemradio",
982
+ "aria-checked": opt.value === locale.current,
983
+ tabIndex: -1,
984
+ className: cn(
985
+ "nav_bar_locale_item",
986
+ opt.value === locale.current && "nav_bar_locale_item_active"
987
+ ),
988
+ onClick: () => handleSelect(opt.value),
989
+ children: opt.label
1036
990
  }
1037
- )
991
+ ) }, opt.value)) })
1038
992
  ] });
1039
993
  };
1040
994
  var NavLink = ({ active, className, children, ...props }) => {
1041
- return (
1042
- // biome-ignore lint/a11y/useValidAnchor: navigation link
1043
- /* @__PURE__ */ jsx(
1044
- "a",
1045
- {
1046
- className: cn("nav_bar_link", active && "nav_bar_link_active"),
1047
- "aria-current": active ? "page" : void 0,
1048
- ...props,
1049
- children
1050
- }
1051
- )
995
+ return /* @__PURE__ */ jsx(
996
+ "a",
997
+ {
998
+ className: cn("nav_bar_link", active && "nav_bar_link_active"),
999
+ "aria-current": active ? "page" : void 0,
1000
+ ...props,
1001
+ children
1002
+ }
1052
1003
  );
1053
1004
  };
1054
1005
  var Sidebar = ({
@@ -1069,9 +1020,9 @@ var Sidebar = ({
1069
1020
  ...props
1070
1021
  }) => {
1071
1022
  const isControlled = collapsedProp !== void 0;
1072
- const [internalCollapsed, setInternalCollapsed] = React16.useState(defaultCollapsed);
1023
+ const [internalCollapsed, setInternalCollapsed] = React11.useState(defaultCollapsed);
1073
1024
  const collapsed = isControlled ? collapsedProp : internalCollapsed;
1074
- const toggle = React16.useCallback(() => {
1025
+ const toggle = React11.useCallback(() => {
1075
1026
  const next = !collapsed;
1076
1027
  if (!isControlled) setInternalCollapsed(next);
1077
1028
  onCollapsedChange?.(next);
@@ -1121,22 +1072,10 @@ var SidebarItem = (props) => {
1121
1072
  ] });
1122
1073
  if (as === "a") {
1123
1074
  const { href, ...anchorRest } = rest;
1124
- return (
1125
- // biome-ignore lint/a11y/useValidAnchor: navigation link with optional active state
1126
- /* @__PURE__ */ jsx("a", { className: classes, href, "aria-current": ariaCurrent, ...anchorRest, children: inner })
1127
- );
1075
+ return /* @__PURE__ */ jsx("a", { className: classes, href, "aria-current": ariaCurrent, ...anchorRest, children: inner });
1128
1076
  }
1129
1077
  const { type, ...buttonRest } = rest;
1130
- return /* @__PURE__ */ jsx(
1131
- "button",
1132
- {
1133
- type: type ?? "button",
1134
- className: classes,
1135
- "aria-current": ariaCurrent,
1136
- ...buttonRest,
1137
- children: inner
1138
- }
1139
- );
1078
+ return /* @__PURE__ */ jsx("button", { type: type ?? "button", className: classes, "aria-current": ariaCurrent, ...buttonRest, children: inner });
1140
1079
  };
1141
1080
  var SidebarSection = ({ label, className, children, ...props }) => {
1142
1081
  return /* @__PURE__ */ jsxs("div", { className: cn("sidebar_section", className), ...props, children: [
@@ -1144,9 +1083,9 @@ var SidebarSection = ({ label, className, children, ...props }) => {
1144
1083
  children
1145
1084
  ] });
1146
1085
  };
1147
- var TabsContext = React16.createContext(null);
1086
+ var TabsContext = React11.createContext(null);
1148
1087
  function useTabsContext() {
1149
- const ctx = React16.useContext(TabsContext);
1088
+ const ctx = React11.useContext(TabsContext);
1150
1089
  if (!ctx) {
1151
1090
  throw new Error(
1152
1091
  '[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>'
@@ -1165,18 +1104,18 @@ var Tabs = ({
1165
1104
  ...props
1166
1105
  }) => {
1167
1106
  const isControlled = controlledValue !== void 0;
1168
- const [internalValue, setInternalValue] = React16.useState(defaultValue);
1107
+ const [internalValue, setInternalValue] = React11.useState(defaultValue);
1169
1108
  const value = isControlled ? controlledValue : internalValue;
1170
- const setValue = React16.useCallback(
1109
+ const setValue = React11.useCallback(
1171
1110
  (v) => {
1172
1111
  if (!isControlled) setInternalValue(v);
1173
1112
  onValueChange?.(v);
1174
1113
  },
1175
1114
  [isControlled, onValueChange]
1176
1115
  );
1177
- const orderRef = React16.useRef([]);
1178
- const [firstTabValue, setFirstTabValue] = React16.useState();
1179
- const registerTab = React16.useCallback((v) => {
1116
+ const orderRef = React11.useRef([]);
1117
+ const [firstTabValue, setFirstTabValue] = React11.useState();
1118
+ const registerTab = React11.useCallback((v) => {
1180
1119
  if (!orderRef.current.includes(v)) {
1181
1120
  orderRef.current = [...orderRef.current, v];
1182
1121
  setFirstTabValue(orderRef.current[0]);
@@ -1186,18 +1125,25 @@ var Tabs = ({
1186
1125
  setFirstTabValue(orderRef.current[0]);
1187
1126
  };
1188
1127
  }, []);
1189
- const idPrefix = React16.useId();
1190
- const ctx = React16.useMemo(
1128
+ const idPrefix = React11.useId();
1129
+ const ctx = React11.useMemo(
1191
1130
  () => ({ value, setValue, variant, size, idPrefix, registerTab, firstTabValue }),
1192
1131
  [value, setValue, variant, size, idPrefix, registerTab, firstTabValue]
1193
1132
  );
1194
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { className: cn("tabs", `tabs_variant_${variant}`, `tabs_size_${size}`, className), ...props, children }) });
1133
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
1134
+ "div",
1135
+ {
1136
+ className: cn("tabs", `tabs_variant_${variant}`, `tabs_size_${size}`, className),
1137
+ ...props,
1138
+ children
1139
+ }
1140
+ ) });
1195
1141
  };
1196
1142
  var TabList = ({ ariaLabel, className, children, ...props }) => {
1197
1143
  const { variant } = useTabsContext();
1198
- const listRef = React16.useRef(null);
1199
- const [indicator, setIndicator] = React16.useState(null);
1200
- const [hasMounted, setHasMounted] = React16.useState(false);
1144
+ const listRef = React11.useRef(null);
1145
+ const [indicator, setIndicator] = React11.useState(null);
1146
+ const [hasMounted, setHasMounted] = React11.useState(false);
1201
1147
  useSafeLayoutEffect(() => {
1202
1148
  const list = listRef.current;
1203
1149
  if (!list) return;
@@ -1260,7 +1206,7 @@ var Tab = ({ value, className, children, onClick, onKeyDown, ...props }) => {
1260
1206
  const panelId = `${ctx.idPrefix}-panel-${value}`;
1261
1207
  const tabId = `${ctx.idPrefix}-tab-${value}`;
1262
1208
  const { registerTab } = ctx;
1263
- React16.useEffect(() => registerTab(value), [registerTab, value]);
1209
+ React11.useEffect(() => registerTab(value), [registerTab, value]);
1264
1210
  const handleClick = (e) => {
1265
1211
  ctx.setValue(value);
1266
1212
  onClick?.(e);
@@ -1268,10 +1214,13 @@ var Tab = ({ value, className, children, onClick, onKeyDown, ...props }) => {
1268
1214
  const handleKeyDown2 = (e) => {
1269
1215
  onKeyDown?.(e);
1270
1216
  if (e.defaultPrevented) return;
1271
- if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End") return;
1217
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End")
1218
+ return;
1272
1219
  const list = e.currentTarget.closest('[role="tablist"]');
1273
1220
  if (!list) return;
1274
- const tabs = Array.from(list.querySelectorAll('[role="tab"]:not([disabled])'));
1221
+ const tabs = Array.from(
1222
+ list.querySelectorAll('[role="tab"]:not([disabled])')
1223
+ );
1275
1224
  const currentIndex = tabs.indexOf(e.currentTarget);
1276
1225
  if (currentIndex < 0) return;
1277
1226
  e.preventDefault();
@@ -1343,15 +1292,15 @@ var Popover = ({
1343
1292
  className
1344
1293
  }) => {
1345
1294
  const isControlled = openProp !== void 0;
1346
- const [internalOpen, setInternalOpen] = React16.useState(defaultOpen);
1295
+ const [internalOpen, setInternalOpen] = React11.useState(defaultOpen);
1347
1296
  const open = isControlled ? openProp : internalOpen;
1348
- const [shouldRender, setShouldRender] = React16.useState(open ?? false);
1297
+ const [shouldRender, setShouldRender] = React11.useState(open ?? false);
1349
1298
  if (open && !shouldRender) setShouldRender(true);
1350
- const wrapperRef = React16.useRef(null);
1351
- const positionRef = React16.useRef(null);
1352
- const popoverRef = React16.useRef(null);
1353
- const popoverId = React16.useId();
1354
- const setOpen = React16.useCallback(
1299
+ const wrapperRef = React11.useRef(null);
1300
+ const positionRef = React11.useRef(null);
1301
+ const popoverRef = React11.useRef(null);
1302
+ const popoverId = React11.useId();
1303
+ const setOpen = React11.useCallback(
1355
1304
  (next) => {
1356
1305
  if (!isControlled) setInternalOpen(next);
1357
1306
  onOpenChange?.(next);
@@ -1384,7 +1333,7 @@ var Popover = ({
1384
1333
  onExitComplete: () => setShouldRender(false)
1385
1334
  });
1386
1335
  useFocusTrap(popoverRef, open);
1387
- React16.useEffect(() => {
1336
+ React11.useEffect(() => {
1388
1337
  if (!open) return;
1389
1338
  const handleClick = (e) => {
1390
1339
  const target = e.target;
@@ -1398,7 +1347,7 @@ var Popover = ({
1398
1347
  useOverlayEscape(open, () => {
1399
1348
  setOpen(false);
1400
1349
  });
1401
- const triggerWithProps = React16.cloneElement(
1350
+ const triggerWithProps = React11.cloneElement(
1402
1351
  trigger,
1403
1352
  {
1404
1353
  onClick: (e) => {
@@ -1457,30 +1406,30 @@ var Tooltip = ({
1457
1406
  disabled = false,
1458
1407
  children
1459
1408
  }) => {
1460
- const [open, setOpen] = React16.useState(false);
1461
- const [shouldRender, setShouldRender] = React16.useState(false);
1409
+ const [open, setOpen] = React11.useState(false);
1410
+ const [shouldRender, setShouldRender] = React11.useState(false);
1462
1411
  if (open && !shouldRender) setShouldRender(true);
1463
- const timerRef = React16.useRef(null);
1464
- const tooltipId = React16.useId();
1465
- const wrapperRef = React16.useRef(null);
1466
- const positionRef = React16.useRef(null);
1412
+ const timerRef = React11.useRef(null);
1413
+ const tooltipId = React11.useId();
1414
+ const wrapperRef = React11.useRef(null);
1415
+ const positionRef = React11.useRef(null);
1467
1416
  const HIDE_DELAY = 120;
1468
- const show = React16.useCallback(() => {
1417
+ const show = React11.useCallback(() => {
1469
1418
  if (timerRef.current) clearTimeout(timerRef.current);
1470
1419
  timerRef.current = setTimeout(() => setOpen(true), delay);
1471
1420
  }, [delay]);
1472
- const hideNow = React16.useCallback(() => {
1421
+ const hideNow = React11.useCallback(() => {
1473
1422
  if (timerRef.current) clearTimeout(timerRef.current);
1474
1423
  setOpen(false);
1475
1424
  }, []);
1476
- const hide = React16.useCallback(() => {
1425
+ const hide = React11.useCallback(() => {
1477
1426
  if (timerRef.current) clearTimeout(timerRef.current);
1478
1427
  timerRef.current = setTimeout(() => setOpen(false), HIDE_DELAY);
1479
1428
  }, []);
1480
- const cancelHide = React16.useCallback(() => {
1429
+ const cancelHide = React11.useCallback(() => {
1481
1430
  if (timerRef.current) clearTimeout(timerRef.current);
1482
1431
  }, []);
1483
- React16.useEffect(() => {
1432
+ React11.useEffect(() => {
1484
1433
  return () => {
1485
1434
  if (timerRef.current) clearTimeout(timerRef.current);
1486
1435
  };
@@ -1513,7 +1462,7 @@ var Tooltip = ({
1513
1462
  });
1514
1463
  const child = children;
1515
1464
  const childProps = child.props;
1516
- const trigger = React16.cloneElement(child, {
1465
+ const trigger = React11.cloneElement(child, {
1517
1466
  onMouseEnter: (e) => {
1518
1467
  childProps.onMouseEnter?.(e);
1519
1468
  show();
@@ -1750,13 +1699,17 @@ var spacing = {
1750
1699
  "4": "4px",
1751
1700
  "6": "6px",
1752
1701
  "8": "8px",
1702
+ "10": "10px",
1753
1703
  "12": "12px",
1754
1704
  "16": "16px",
1755
1705
  "20": "20px",
1756
1706
  "24": "24px",
1757
1707
  "32": "32px",
1758
1708
  "40": "40px",
1759
- "48": "48px"
1709
+ "48": "48px",
1710
+ "64": "64px",
1711
+ "96": "96px",
1712
+ "128": "128px"
1760
1713
  };
1761
1714
 
1762
1715
  // src/styles/skeleton/index.ts
@@ -1791,6 +1744,7 @@ var baseTypography = {
1791
1744
  primary: "Pretendard"
1792
1745
  },
1793
1746
  fontSize: {
1747
+ "10": "10px",
1794
1748
  "12": "12px",
1795
1749
  "13": "13px",
1796
1750
  "14": "14px",
@@ -2462,8 +2416,8 @@ var MediaCard = ({
2462
2416
  ] });
2463
2417
  };
2464
2418
  var Prose = ({ size = "md", className, children, ref, ...props }) => {
2465
- const rootRef = React16.useRef(null);
2466
- React16.useImperativeHandle(ref, () => rootRef.current, []);
2419
+ const rootRef = React11.useRef(null);
2420
+ React11.useImperativeHandle(ref, () => rootRef.current, []);
2467
2421
  useSafeLayoutEffect(() => {
2468
2422
  const root = rootRef.current;
2469
2423
  if (!root) return;
@@ -2519,12 +2473,12 @@ var Checkbox = ({
2519
2473
  ref,
2520
2474
  ...props
2521
2475
  }) => {
2522
- const inputRef = React16.useRef(null);
2523
- React16.useImperativeHandle(
2476
+ const inputRef = React11.useRef(null);
2477
+ React11.useImperativeHandle(
2524
2478
  ref,
2525
2479
  () => inputRef.current
2526
2480
  );
2527
- React16.useEffect(() => {
2481
+ React11.useEffect(() => {
2528
2482
  if (!inputRef.current) return;
2529
2483
  inputRef.current.indeterminate = Boolean(indeterminate);
2530
2484
  }, [indeterminate]);
@@ -2582,8 +2536,8 @@ var Table = ({
2582
2536
  const isEmpty = !isLoading && data.length === 0;
2583
2537
  const getRowKey = (item, index) => rowKey?.(item) ?? String(index);
2584
2538
  const allRowKeys = selectable ? data.map((item, index) => getRowKey(item, index)) : [];
2585
- const rowClickHintId = React16.useId();
2586
- const selectedSet = React16.useMemo(() => new Set(selectedKeys ?? []), [selectedKeys]);
2539
+ const rowClickHintId = React11.useId();
2540
+ const selectedSet = React11.useMemo(() => new Set(selectedKeys ?? []), [selectedKeys]);
2587
2541
  const selectedCount = allRowKeys.filter((key) => selectedSet.has(key)).length;
2588
2542
  const isAllSelected = allRowKeys.length > 0 && selectedCount === allRowKeys.length;
2589
2543
  const isSomeSelected = selectedCount > 0 && !isAllSelected;
@@ -2806,29 +2760,33 @@ var AlertModal = ({
2806
2760
  onClose
2807
2761
  }) => {
2808
2762
  const dismiss = onCancel ?? onClose;
2809
- const panelRef = React16.useRef(null);
2810
- const titleId = React16.useId();
2811
- const messageId = React16.useId();
2763
+ const panelRef = React11.useRef(null);
2764
+ const titleId = React11.useId();
2765
+ const messageId = React11.useId();
2812
2766
  const [shouldRender, setShouldRender] = useState(isOpen);
2813
2767
  useFocusTrap(panelRef, isOpen);
2814
2768
  useOverlayEscape(isOpen, () => dismiss());
2815
2769
  if (isOpen && !shouldRender) setShouldRender(true);
2816
2770
  const reduced = useReducedMotion();
2817
2771
  const overlayStyle = useSpring({
2818
- opacity: isOpen ? 1 : 0,
2772
+ ...springEnterFrom(reduced),
2773
+ to: { opacity: isOpen ? 1 : 0 },
2819
2774
  immediate: reduced,
2820
- config: { tension: 280, friction: 28, clamp: !isOpen },
2775
+ config: OVERLAY_SPRING_CONFIG,
2821
2776
  onRest: (result) => {
2822
2777
  if (!isOpen && result.finished) setShouldRender(false);
2823
2778
  }
2824
2779
  });
2825
2780
  const panelStyle = useSpring({
2826
- opacity: isOpen ? 1 : 0,
2827
- transform: isOpen ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
2781
+ ...springEnterFrom(reduced, OVERLAY_PANEL_CLOSED_TRANSFORM),
2782
+ to: {
2783
+ opacity: isOpen ? 1 : 0,
2784
+ transform: isOpen ? OVERLAY_PANEL_OPEN_TRANSFORM : OVERLAY_PANEL_CLOSED_TRANSFORM
2785
+ },
2828
2786
  immediate: reduced,
2829
- config: { tension: 280, friction: 28, clamp: !isOpen }
2787
+ config: OVERLAY_SPRING_CONFIG
2830
2788
  });
2831
- React16.useEffect(() => {
2789
+ React11.useEffect(() => {
2832
2790
  if (!shouldRender) return;
2833
2791
  lockBodyScroll();
2834
2792
  return unlockBodyScroll;
@@ -2838,7 +2796,6 @@ var AlertModal = ({
2838
2796
  const cancelVariant = "outline";
2839
2797
  const modalClassName = cn("alert_modal", `alert_variant_${variant}`);
2840
2798
  return createPortal(
2841
- // biome-ignore lint/a11y/noStaticElementInteractions: modal overlay pattern
2842
2799
  /* @__PURE__ */ jsx(
2843
2800
  animated.div,
2844
2801
  {
@@ -2912,10 +2869,7 @@ var LinearProgress = ({
2912
2869
  Array.from({ length: dotCount }, (_, i) => /* @__PURE__ */ jsx(
2913
2870
  "span",
2914
2871
  {
2915
- className: cn(
2916
- "linear_progress_step",
2917
- i <= clampedStep && "linear_progress_step_done"
2918
- ),
2872
+ className: cn("linear_progress_step", i <= clampedStep && "linear_progress_step_done"),
2919
2873
  "aria-hidden": "true"
2920
2874
  },
2921
2875
  i
@@ -2939,7 +2893,7 @@ var Spinner = ({ size = 24, ariaLabel = "\uB85C\uB529 \uC911" }) => {
2939
2893
  }
2940
2894
  );
2941
2895
  };
2942
- var ToastContext = React16.createContext(null);
2896
+ var ToastContext = React11.createContext(null);
2943
2897
  var VARIANT_ICONS = {
2944
2898
  success: /* @__PURE__ */ jsx(CheckCircle2, { size: iconSize.md }),
2945
2899
  error: /* @__PURE__ */ jsx(XCircle, { size: iconSize.md }),
@@ -2948,9 +2902,9 @@ var VARIANT_ICONS = {
2948
2902
  default: /* @__PURE__ */ jsx(Bell, { size: iconSize.md })
2949
2903
  };
2950
2904
  var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
2951
- const [visible, setVisible] = React16.useState(true);
2952
- const closingRef = React16.useRef(false);
2953
- const rootRef = React16.useRef(null);
2905
+ const [visible, setVisible] = React11.useState(true);
2906
+ const closingRef = React11.useRef(false);
2907
+ const rootRef = React11.useRef(null);
2954
2908
  const style = useSpringPresence({
2955
2909
  visible,
2956
2910
  from: "translateX(20px)",
@@ -2970,7 +2924,7 @@ var ToastItemComponent = ({ item, onRemove, closeAriaLabel }) => {
2970
2924
  onRemove(item.id);
2971
2925
  }
2972
2926
  });
2973
- const close = React16.useCallback(() => {
2927
+ const close = React11.useCallback(() => {
2974
2928
  if (closingRef.current) return;
2975
2929
  closingRef.current = true;
2976
2930
  setVisible(false);
@@ -3005,19 +2959,19 @@ var ToastProvider = ({
3005
2959
  closeAriaLabel = "\uB2EB\uAE30",
3006
2960
  regionLabel = "\uC54C\uB9BC"
3007
2961
  }) => {
3008
- const [toasts, setToasts] = React16.useState([]);
2962
+ const [toasts, setToasts] = React11.useState([]);
3009
2963
  const isMounted = useIsMounted();
3010
- const addToast = React16.useCallback(
2964
+ const addToast = React11.useCallback(
3011
2965
  (message, variant, duration = 3e3) => {
3012
2966
  const id = crypto.randomUUID();
3013
2967
  setToasts((prev) => [{ id, message, variant, duration }, ...prev].slice(0, maxCount));
3014
2968
  },
3015
2969
  [maxCount]
3016
2970
  );
3017
- const removeToast = React16.useCallback((id) => {
2971
+ const removeToast = React11.useCallback((id) => {
3018
2972
  setToasts((prev) => prev.filter((t) => t.id !== id));
3019
2973
  }, []);
3020
- const contextValue = React16.useMemo(() => ({ addToast }), [addToast]);
2974
+ const contextValue = React11.useMemo(() => ({ addToast }), [addToast]);
3021
2975
  return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
3022
2976
  children,
3023
2977
  isMounted && createPortal(
@@ -3457,9 +3411,9 @@ var DatePicker = ({
3457
3411
  minDateSrFormat = "\uCD5C\uC18C \uB0A0\uC9DC: {date}",
3458
3412
  selectableRangeUntilTodaySrText = "\uC624\uB298\uAE4C\uC9C0 \uC120\uD0DD \uAC00\uB2A5"
3459
3413
  }) => {
3460
- const groupId = React16.useId();
3461
- const constraintId = React16.useId();
3462
- const { todayYear, todayMonth, todayDay } = React16.useMemo(() => {
3414
+ const groupId = React11.useId();
3415
+ const constraintId = React11.useId();
3416
+ const { todayYear, todayMonth, todayDay } = React11.useMemo(() => {
3463
3417
  const now = /* @__PURE__ */ new Date();
3464
3418
  return {
3465
3419
  todayYear: now.getFullYear(),
@@ -3468,7 +3422,7 @@ var DatePicker = ({
3468
3422
  };
3469
3423
  }, []);
3470
3424
  const endYear = endYearProp ?? todayYear + 10;
3471
- const parsed = React16.useMemo(() => {
3425
+ const parsed = React11.useMemo(() => {
3472
3426
  if (!value) return { year: 0, month: 0, day: 0 };
3473
3427
  const [y, m, d] = value.split("-").map(Number);
3474
3428
  return {
@@ -3477,7 +3431,7 @@ var DatePicker = ({
3477
3431
  day: d || 0
3478
3432
  };
3479
3433
  }, [value]);
3480
- const min = React16.useMemo(() => {
3434
+ const min = React11.useMemo(() => {
3481
3435
  if (!minDate) return { year: 0, month: 0, day: 0 };
3482
3436
  const [y, m, d] = minDate.split("-").map(Number);
3483
3437
  return {
@@ -3497,7 +3451,7 @@ var DatePicker = ({
3497
3451
  min.year > 0 && min.month > 0 && year === min.year && month === min.month ? min.day : 1
3498
3452
  )
3499
3453
  );
3500
- const maxDay = React16.useMemo(() => {
3454
+ const maxDay = React11.useMemo(() => {
3501
3455
  if (!year || !month) return 31;
3502
3456
  const daysInMonth = getDaysInMonth(year, month);
3503
3457
  if (selectableRange === "until-today" && year === todayYear && month === todayMonth) {
@@ -3505,28 +3459,28 @@ var DatePicker = ({
3505
3459
  }
3506
3460
  return daysInMonth;
3507
3461
  }, [year, month, selectableRange, todayYear, todayMonth, todayDay]);
3508
- const yearOptions = React16.useMemo(
3462
+ const yearOptions = React11.useMemo(
3509
3463
  () => range(startYear, maxYear).map((y) => ({
3510
3464
  value: String(y),
3511
3465
  label: String(y)
3512
3466
  })),
3513
3467
  [startYear, maxYear]
3514
3468
  );
3515
- const monthOptions = React16.useMemo(
3469
+ const monthOptions = React11.useMemo(
3516
3470
  () => range(minMonth, Math.max(minMonth, maxMonth)).map((m) => ({
3517
3471
  value: String(m),
3518
3472
  label: pad(m)
3519
3473
  })),
3520
3474
  [minMonth, maxMonth]
3521
3475
  );
3522
- const dayOptions = React16.useMemo(
3476
+ const dayOptions = React11.useMemo(
3523
3477
  () => range(minDay, Math.max(minDay, maxDay)).map((d) => ({
3524
3478
  value: String(d),
3525
3479
  label: pad(d)
3526
3480
  })),
3527
3481
  [minDay, maxDay]
3528
3482
  );
3529
- const emit = React16.useCallback(
3483
+ const emit = React11.useCallback(
3530
3484
  (yy, mm, dd) => {
3531
3485
  const cb = onValueChange ?? onChange;
3532
3486
  if (mode === "year-month") {
@@ -3538,7 +3492,7 @@ var DatePicker = ({
3538
3492
  },
3539
3493
  [mode, onValueChange, onChange]
3540
3494
  );
3541
- const handleYearChange = React16.useCallback(
3495
+ const handleYearChange = React11.useCallback(
3542
3496
  (raw) => {
3543
3497
  if (!raw) return;
3544
3498
  const newYear = Number(raw);
@@ -3555,14 +3509,14 @@ var DatePicker = ({
3555
3509
  },
3556
3510
  [month, day, min.year, min.month, selectableRange, todayYear, todayMonth, emit]
3557
3511
  );
3558
- const handleMonthChange = React16.useCallback(
3512
+ const handleMonthChange = React11.useCallback(
3559
3513
  (raw) => {
3560
3514
  if (!raw || !year) return;
3561
3515
  emit(year, Number(raw), day || void 0);
3562
3516
  },
3563
3517
  [year, day, emit]
3564
3518
  );
3565
- const handleDayChange = React16.useCallback(
3519
+ const handleDayChange = React11.useCallback(
3566
3520
  (raw) => {
3567
3521
  if (!raw || !year || !month) return;
3568
3522
  emit(year, month, Number(raw));
@@ -3646,11 +3600,11 @@ var FileInput = ({
3646
3600
  onChange,
3647
3601
  ...props
3648
3602
  }) => {
3649
- const inputId = React16.useId();
3650
- const helperId = React16.useId();
3651
- const inputRef = React16.useRef(null);
3652
- const [previewUrls, setPreviewUrls] = React16.useState([]);
3653
- const previewUrlsRef = React16.useRef([]);
3603
+ const inputId = React11.useId();
3604
+ const helperId = React11.useId();
3605
+ const inputRef = React11.useRef(null);
3606
+ const [previewUrls, setPreviewUrls] = React11.useState([]);
3607
+ const previewUrlsRef = React11.useRef([]);
3654
3608
  const isPreviewVariant = variant === "preview";
3655
3609
  const showPreview = isPreviewVariant || preview;
3656
3610
  const handleChange = (e) => {
@@ -3691,7 +3645,7 @@ var FileInput = ({
3691
3645
  }
3692
3646
  onFiles?.(null);
3693
3647
  };
3694
- React16.useEffect(() => {
3648
+ React11.useEffect(() => {
3695
3649
  return () => {
3696
3650
  for (const url of previewUrlsRef.current) {
3697
3651
  URL.revokeObjectURL(url);
@@ -3729,14 +3683,7 @@ var FileInput = ({
3729
3683
  style: { width: previewSize, height: previewSize },
3730
3684
  children: hasImage ? (
3731
3685
  // biome-ignore lint/performance/noImgElement: framework-agnostic DS - host app should swap with next/image if needed
3732
- /* @__PURE__ */ jsx(
3733
- "img",
3734
- {
3735
- src: previewUrls[0],
3736
- alt: "",
3737
- className: "file_input_preview_image"
3738
- }
3739
- )
3686
+ /* @__PURE__ */ jsx("img", { src: previewUrls[0], alt: "", className: "file_input_preview_image" })
3740
3687
  ) : /* @__PURE__ */ jsxs("span", { className: "file_input_preview_empty", children: [
3741
3688
  /* @__PURE__ */ jsx(Image, { size: iconSize.xl, "aria-hidden": "true" }),
3742
3689
  /* @__PURE__ */ jsx("span", { className: "file_input_preview_empty_text", children: label })
@@ -3756,15 +3703,7 @@ var FileInput = ({
3756
3703
  supportingText && /* @__PURE__ */ jsx("span", { id: helperId, className: "file_input_helper", children: supportingText }),
3757
3704
  !isPreviewVariant && previewUrls.length > 0 && /* @__PURE__ */ jsx("div", { className: "file_input_preview", children: previewUrls.map((url) => (
3758
3705
  // biome-ignore lint/performance/noImgElement: framework-agnostic DS - host app should swap with next/image if needed
3759
- /* @__PURE__ */ jsx(
3760
- "img",
3761
- {
3762
- src: url,
3763
- alt: "",
3764
- className: "file_input_preview_img"
3765
- },
3766
- url
3767
- )
3706
+ /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "file_input_preview_img" }, url)
3768
3707
  )) })
3769
3708
  ] });
3770
3709
  };
@@ -4100,12 +4039,12 @@ var OtpInput = ({
4100
4039
  ariaLabel = "OTP \uC785\uB825",
4101
4040
  className
4102
4041
  }) => {
4103
- const inputsRef = React16.useRef([]);
4104
- const isTypingRef = React16.useRef(false);
4105
- React16.useEffect(() => {
4042
+ const inputsRef = React11.useRef([]);
4043
+ const isTypingRef = React11.useRef(false);
4044
+ React11.useEffect(() => {
4106
4045
  if (autoFocus) inputsRef.current[0]?.focus();
4107
4046
  }, [autoFocus]);
4108
- const digits = React16.useMemo(() => {
4047
+ const digits = React11.useMemo(() => {
4109
4048
  const chars = value.split("").slice(0, length);
4110
4049
  while (chars.length < length) chars.push("");
4111
4050
  return chars;
@@ -4182,7 +4121,7 @@ var OtpInput = ({
4182
4121
  focusInput(nextIndex);
4183
4122
  };
4184
4123
  const rootClassName = cn("otp_input", className);
4185
- const supportingId = React16.useId();
4124
+ const supportingId = React11.useId();
4186
4125
  return (
4187
4126
  // biome-ignore lint/a11y/useSemanticElements: <fieldset> would force border/legend styles; role=group is the WAI-ARIA equivalent for OTP grouping
4188
4127
  /* @__PURE__ */ jsxs("div", { className: rootClassName, role: "group", "aria-label": ariaLabel, children: [
@@ -4226,9 +4165,9 @@ var OtpInput = ({
4226
4165
  );
4227
4166
  };
4228
4167
  OtpInput.displayName = "OtpInput";
4229
- var RadioGroupContext = React16.createContext(null);
4168
+ var RadioGroupContext = React11.createContext(null);
4230
4169
  function useRadioGroupContext() {
4231
- return React16.useContext(RadioGroupContext);
4170
+ return React11.useContext(RadioGroupContext);
4232
4171
  }
4233
4172
  var RadioGroup = ({
4234
4173
  value: controlledValue,
@@ -4246,21 +4185,21 @@ var RadioGroup = ({
4246
4185
  ...props
4247
4186
  }) => {
4248
4187
  const isControlled = controlledValue !== void 0;
4249
- const [internalValue, setInternalValue] = React16.useState(defaultValue);
4188
+ const [internalValue, setInternalValue] = React11.useState(defaultValue);
4250
4189
  const value = isControlled ? controlledValue : internalValue;
4251
- const generatedName = React16.useId();
4190
+ const generatedName = React11.useId();
4252
4191
  const name = nameProp ?? generatedName;
4253
- const idPrefix = React16.useId();
4192
+ const idPrefix = React11.useId();
4254
4193
  const labelId = label ? `${idPrefix}-label` : void 0;
4255
4194
  const helperId = supportingText ? `${idPrefix}-help` : void 0;
4256
- const onChange = React16.useCallback(
4195
+ const onChange = React11.useCallback(
4257
4196
  (next) => {
4258
4197
  if (!isControlled) setInternalValue(next);
4259
4198
  onValueChange?.(next);
4260
4199
  },
4261
4200
  [isControlled, onValueChange]
4262
4201
  );
4263
- const ctx = React16.useMemo(
4202
+ const ctx = React11.useMemo(
4264
4203
  () => ({ name, value, onChange, size, disabled }),
4265
4204
  [name, value, onChange, size, disabled]
4266
4205
  );
@@ -4369,9 +4308,7 @@ var Textarea = ({
4369
4308
  const helperId = supportingText ? `${inputId}-help` : void 0;
4370
4309
  const isControlled = value !== void 0;
4371
4310
  const applyTransform = (nextValue) => transformValue ? transformValue(nextValue) : nextValue;
4372
- const [innerValue, setInnerValue] = useState(
4373
- () => applyTransform(value ?? defaultValue ?? "")
4374
- );
4311
+ const [innerValue, setInnerValue] = useState(() => applyTransform(value ?? defaultValue ?? ""));
4375
4312
  const isComposingRef = useRef(false);
4376
4313
  const innerRef = useRef(null);
4377
4314
  const lastEmittedValueRef = useRef(innerValue);
@@ -4632,7 +4569,7 @@ var Toggle = ({
4632
4569
  ...props
4633
4570
  }) => {
4634
4571
  const isControlled = checked !== void 0;
4635
- const [innerChecked, setInnerChecked] = React16.useState(!!defaultChecked);
4572
+ const [innerChecked, setInnerChecked] = React11.useState(!!defaultChecked);
4636
4573
  const isOn = isControlled ? !!checked : innerChecked;
4637
4574
  const handleToggle = (e) => {
4638
4575
  props.onClick?.(e);
@@ -4722,7 +4659,7 @@ var Pagination = ({
4722
4659
  const emit = onPageChange ?? onChange;
4723
4660
  const prevDisabled = page <= 1;
4724
4661
  const nextDisabled = page >= totalPages;
4725
- const items = React16.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
4662
+ const items = React11.useMemo(() => getPaginationItems(page, totalPages), [page, totalPages]);
4726
4663
  return /* @__PURE__ */ jsxs("nav", { className: "pagination", "aria-label": navLabel, children: [
4727
4664
  /* @__PURE__ */ jsx(
4728
4665
  "button",
@@ -4789,15 +4726,15 @@ var Drawer = ({
4789
4726
  className,
4790
4727
  ...props
4791
4728
  }) => {
4792
- const lastContentRef = React16.useRef({ children, title, footer });
4729
+ const lastContentRef = React11.useRef({ children, title, footer });
4793
4730
  if (open) lastContentRef.current = { children, title, footer };
4794
4731
  const content = open ? { children, title, footer } : lastContentRef.current;
4795
4732
  const overlayDismissible = dismissible ?? closeOnOverlay;
4796
4733
  const escapeDismissible = dismissible ?? true;
4797
- const panelRef = React16.useRef(null);
4798
- const pressedOverlayRef = React16.useRef(false);
4799
- const titleId = React16.useId();
4800
- const [shouldRender, setShouldRender] = React16.useState(open);
4734
+ const panelRef = React11.useRef(null);
4735
+ const pressedOverlayRef = React11.useRef(false);
4736
+ const titleId = React11.useId();
4737
+ const [shouldRender, setShouldRender] = React11.useState(open);
4801
4738
  const reduced = useReducedMotion();
4802
4739
  const isMounted = useIsMounted();
4803
4740
  useFocusTrap(panelRef, open && isMounted);
@@ -4821,7 +4758,7 @@ var Drawer = ({
4821
4758
  immediate: reduced,
4822
4759
  config: { tension: 280, friction: 28, clamp: !open }
4823
4760
  });
4824
- React16.useEffect(() => {
4761
+ React11.useEffect(() => {
4825
4762
  if (!open) return;
4826
4763
  lockBodyScroll();
4827
4764
  return unlockBodyScroll;
@@ -4861,16 +4798,7 @@ var Drawer = ({
4861
4798
  if (e.key !== "Escape") e.stopPropagation();
4862
4799
  },
4863
4800
  children: [
4864
- showCloseIcon && onClose && /* @__PURE__ */ jsx(
4865
- "button",
4866
- {
4867
- type: "button",
4868
- className: "drawer_close",
4869
- onClick: onClose,
4870
- "aria-label": closeLabel,
4871
- children: /* @__PURE__ */ jsx(X, { size: iconSize.md, "aria-hidden": "true" })
4872
- }
4873
- ),
4801
+ showCloseIcon && onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "drawer_close", onClick: onClose, "aria-label": closeLabel, children: /* @__PURE__ */ jsx(X, { size: iconSize.md, "aria-hidden": "true" }) }),
4874
4802
  content.title && /* @__PURE__ */ jsx("div", { className: "drawer_header", children: /* @__PURE__ */ jsx("h2", { id: titleId, className: "drawer_title", children: content.title }) }),
4875
4803
  content.children && // 본문이 스크롤 컨테이너라 키보드로도 스크롤할 수 있어야 한다(axe
4876
4804
  // `scrollable-region-focusable`). 다만 초기 포커스 대상에서는 제외한다 - 안쪽에
@@ -4904,15 +4832,15 @@ var Modal = ({
4904
4832
  onExited,
4905
4833
  ...props
4906
4834
  }) => {
4907
- const lastContentRef = React16.useRef({ children, title, description, footer });
4835
+ const lastContentRef = React11.useRef({ children, title, description, footer });
4908
4836
  if (open) lastContentRef.current = { children, title, description, footer };
4909
4837
  const content = open ? { children, title, description, footer } : lastContentRef.current;
4910
4838
  const overlayDismissible = dismissible ?? closeOnOverlay;
4911
4839
  const escapeDismissible = dismissible ?? true;
4912
- const panelRef = React16.useRef(null);
4913
- const pressedOverlayRef = React16.useRef(false);
4914
- const titleId = React16.useId();
4915
- const [shouldRender, setShouldRender] = React16.useState(open);
4840
+ const panelRef = React11.useRef(null);
4841
+ const pressedOverlayRef = React11.useRef(false);
4842
+ const titleId = React11.useId();
4843
+ const [shouldRender, setShouldRender] = React11.useState(open);
4916
4844
  const isMounted = useIsMounted();
4917
4845
  useFocusTrap(panelRef, open && isMounted);
4918
4846
  useOverlayEscape(open && isMounted, () => {
@@ -4921,9 +4849,10 @@ var Modal = ({
4921
4849
  if (open && !shouldRender) setShouldRender(true);
4922
4850
  const reduced = useReducedMotion();
4923
4851
  const overlayStyle = useSpring({
4924
- opacity: open ? 1 : 0,
4852
+ ...springEnterFrom(reduced),
4853
+ to: { opacity: open ? 1 : 0 },
4925
4854
  immediate: reduced,
4926
- config: { tension: 280, friction: 28, clamp: !open },
4855
+ config: OVERLAY_SPRING_CONFIG,
4927
4856
  onRest: (result) => {
4928
4857
  if (open || !result.finished) return;
4929
4858
  setShouldRender(false);
@@ -4931,12 +4860,15 @@ var Modal = ({
4931
4860
  }
4932
4861
  });
4933
4862
  const panelStyle = useSpring({
4934
- opacity: open ? 1 : 0,
4935
- transform: open ? "scale(1) translateY(0px)" : "scale(0.96) translateY(-4px)",
4863
+ ...springEnterFrom(reduced, OVERLAY_PANEL_CLOSED_TRANSFORM),
4864
+ to: {
4865
+ opacity: open ? 1 : 0,
4866
+ transform: open ? OVERLAY_PANEL_OPEN_TRANSFORM : OVERLAY_PANEL_CLOSED_TRANSFORM
4867
+ },
4936
4868
  immediate: reduced,
4937
- config: { tension: 280, friction: 28, clamp: !open }
4869
+ config: OVERLAY_SPRING_CONFIG
4938
4870
  });
4939
- React16.useEffect(() => {
4871
+ React11.useEffect(() => {
4940
4872
  if (!open) return;
4941
4873
  lockBodyScroll();
4942
4874
  return unlockBodyScroll;
@@ -4983,6 +4915,9 @@ var Modal = ({
4983
4915
  // 늘어나는 정도의 비용이다.
4984
4916
  // 다만 초기 포커스 대상에서는 제외한다 - 안쪽에 첫 입력이 있는데 빈 wrapper 에
4985
4917
  // 포커스가 놓이면 열자마자 어디에 있는지 알 수 없다.
4918
+ // 스크롤 가능한 영역은 키보드로도 스크롤할 수 있어야 한다 (WCAG 2.1.1) - 포커스
4919
+ // 가능해야 화살표 키가 먹는다.
4920
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: scrollable region needs keyboard scroll
4986
4921
  /* @__PURE__ */ jsx("div", { className: "modal_body", tabIndex: 0, "data-focus-trap-skip-autofocus": "", children: content.children }),
4987
4922
  content.footer && /* @__PURE__ */ jsx("div", { className: cn("modal_footer", `modal_footer_${footerAlign}`), children: content.footer })
4988
4923
  ]
@@ -4993,9 +4928,9 @@ var Modal = ({
4993
4928
  document.body
4994
4929
  );
4995
4930
  };
4996
- var ThemeContext = React16.createContext(null);
4931
+ var ThemeContext = React11.createContext(null);
4997
4932
  var useTheme = () => {
4998
- const ctx = React16.useContext(ThemeContext);
4933
+ const ctx = React11.useContext(ThemeContext);
4999
4934
  if (!ctx) {
5000
4935
  throw new Error(
5001
4936
  '[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>'
@@ -5019,15 +4954,15 @@ var ThemeProvider = ({
5019
4954
  targetSelector,
5020
4955
  children
5021
4956
  }) => {
5022
- const [mode, setModeState] = React16.useState(defaultMode);
5023
- const [systemDark, setSystemDark] = React16.useState(false);
5024
- React16.useEffect(() => {
4957
+ const [mode, setModeState] = React11.useState(defaultMode);
4958
+ const [systemDark, setSystemDark] = React11.useState(false);
4959
+ React11.useEffect(() => {
5025
4960
  setModeState(readStored(storageKey) ?? defaultMode);
5026
4961
  if (isClient) {
5027
4962
  setSystemDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
5028
4963
  }
5029
4964
  }, [storageKey, defaultMode]);
5030
- React16.useEffect(() => {
4965
+ React11.useEffect(() => {
5031
4966
  if (!isClient) return;
5032
4967
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
5033
4968
  const handler = (e) => setSystemDark(e.matches);
@@ -5035,7 +4970,7 @@ var ThemeProvider = ({
5035
4970
  return () => mq.removeEventListener("change", handler);
5036
4971
  }, []);
5037
4972
  const resolved = mode === "system" ? systemDark ? "dark" : "light" : mode;
5038
- React16.useEffect(() => {
4973
+ React11.useEffect(() => {
5039
4974
  if (!isClient) return;
5040
4975
  const target = targetSelector ? document.querySelector(targetSelector) : document.documentElement;
5041
4976
  if (!target) return;
@@ -5045,7 +4980,7 @@ var ThemeProvider = ({
5045
4980
  target.setAttribute("data-theme", mode);
5046
4981
  }
5047
4982
  }, [mode, targetSelector]);
5048
- const setMode = React16.useCallback(
4983
+ const setMode = React11.useCallback(
5049
4984
  (next) => {
5050
4985
  setModeState(next);
5051
4986
  if (storageKey && isClient) {
@@ -5057,7 +4992,7 @@ var ThemeProvider = ({
5057
4992
  },
5058
4993
  [storageKey]
5059
4994
  );
5060
- const value = React16.useMemo(
4995
+ const value = React11.useMemo(
5061
4996
  () => ({ mode, resolved, setMode }),
5062
4997
  [mode, resolved, setMode]
5063
4998
  );
@@ -5127,12 +5062,7 @@ var Section = ({
5127
5062
  Tag,
5128
5063
  {
5129
5064
  ref,
5130
- className: cn(
5131
- "section",
5132
- `section_spacing_${spacing2}`,
5133
- `section_bg_${bg}`,
5134
- className
5135
- ),
5065
+ className: cn("section", `section_spacing_${spacing2}`, `section_bg_${bg}`, className),
5136
5066
  ...props,
5137
5067
  children
5138
5068
  }
@@ -5,6 +5,7 @@ $spacing_3: 3px;
5
5
  $spacing_4: 4px;
6
6
  $spacing_6: 6px;
7
7
  $spacing_8: 8px;
8
+ $spacing_10: 10px;
8
9
  $spacing_12: 12px;
9
10
  $spacing_16: 16px;
10
11
  $spacing_20: 20px;
@@ -12,3 +13,8 @@ $spacing_24: 24px;
12
13
  $spacing_32: 32px;
13
14
  $spacing_40: 40px;
14
15
  $spacing_48: 48px;
16
+ // 레이아웃 리듬 - 컴포넌트 내부 여백이 아니라 Section 처럼 화면 단위 블록의 상하 여백에 쓴다.
17
+ // 48 에서 스케일이 끝나 Section 이 raw 64/96/128 을 적고 있었다.
18
+ $spacing_64: 64px;
19
+ $spacing_96: 96px;
20
+ $spacing_128: 128px;
@@ -4,6 +4,9 @@ $font_family_primary: "Pretendard", sans-serif;
4
4
 
5
5
  // ── Base: Font size ───────────────────────────────────────────────────────────
6
6
 
7
+ // 10px - 본문에는 쓰지 않는다. avatar xs(24px 원) 이니셜과 badge count 처럼 장식적인 숫자·글자
8
+ // 전용이다. 그 자리에 12px 을 넣으면 16px 카운트 뱃지를 넘친다.
9
+ $font_size_10: 10px;
7
10
  $font_size_12: 12px;
8
11
  $font_size_13: 13px;
9
12
  $font_size_14: 14px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "3.14.1",
3
+ "version": "3.15.0",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
@@ -49,6 +49,8 @@
49
49
  "lint:css": "stylelint 'src/ui/**/*.scss' 'src/styles/**/*.scss' 'src/vanilla/**/*.scss'",
50
50
  "check:css-vars": "scripts/check-css-vars.sh",
51
51
  "check:prose": "scripts/check-prose-headings.sh",
52
+ "check:defaults": "python3 scripts/check-prop-defaults.py",
53
+ "check:geometry": "python3 scripts/check-overlay-geometry.py",
52
54
  "size": "size-limit",
53
55
  "prepare": "husky"
54
56
  },