@x-plat/design-system 0.9.0 → 0.11.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.
Files changed (156) hide show
  1. package/dist/components/AutoResizeTextArea/index.cjs +14 -5
  2. package/dist/components/AutoResizeTextArea/index.css +15 -0
  3. package/dist/components/AutoResizeTextArea/index.d.cts +5 -0
  4. package/dist/components/AutoResizeTextArea/index.d.ts +5 -0
  5. package/dist/components/AutoResizeTextArea/index.js +14 -5
  6. package/dist/components/Badge/index.cjs +5 -1
  7. package/dist/components/Badge/index.css +1 -0
  8. package/dist/components/Badge/index.d.cts +1 -0
  9. package/dist/components/Badge/index.d.ts +1 -0
  10. package/dist/components/Badge/index.js +5 -1
  11. package/dist/components/Box/index.d.cts +3 -0
  12. package/dist/components/Box/index.d.ts +3 -0
  13. package/dist/components/Chart/index.cjs +7 -6
  14. package/dist/components/Chart/index.d.cts +12 -1
  15. package/dist/components/Chart/index.d.ts +12 -1
  16. package/dist/components/Chart/index.js +7 -6
  17. package/dist/components/ChatInput/index.cjs +20 -7
  18. package/dist/components/ChatInput/index.css +15 -0
  19. package/dist/components/ChatInput/index.d.cts +10 -0
  20. package/dist/components/ChatInput/index.d.ts +10 -0
  21. package/dist/components/ChatInput/index.js +20 -7
  22. package/dist/components/CheckBox/index.cjs +14 -5
  23. package/dist/components/CheckBox/index.css +17 -0
  24. package/dist/components/CheckBox/index.d.cts +10 -2
  25. package/dist/components/CheckBox/index.d.ts +10 -2
  26. package/dist/components/CheckBox/index.js +14 -5
  27. package/dist/components/Chip/index.d.cts +2 -0
  28. package/dist/components/Chip/index.d.ts +2 -0
  29. package/dist/components/DatePicker/index.cjs +181 -65
  30. package/dist/components/DatePicker/index.css +19 -0
  31. package/dist/components/DatePicker/index.js +181 -65
  32. package/dist/components/Drawer/index.cjs +83 -4
  33. package/dist/components/Drawer/index.d.cts +1 -0
  34. package/dist/components/Drawer/index.d.ts +1 -0
  35. package/dist/components/Drawer/index.js +83 -4
  36. package/dist/components/Dropdown/index.cjs +35 -7
  37. package/dist/components/Dropdown/index.js +35 -7
  38. package/dist/components/Editor/index.cjs +178 -21
  39. package/dist/components/Editor/index.js +178 -21
  40. package/dist/components/FieldMessage/index.cjs +8 -3
  41. package/dist/components/FieldMessage/index.css +15 -0
  42. package/dist/components/FieldMessage/index.d.cts +11 -4
  43. package/dist/components/FieldMessage/index.d.ts +11 -4
  44. package/dist/components/FieldMessage/index.js +8 -3
  45. package/dist/components/FileUpload/index.cjs +8 -3
  46. package/dist/components/FileUpload/index.css +15 -0
  47. package/dist/components/FileUpload/index.d.cts +6 -0
  48. package/dist/components/FileUpload/index.d.ts +6 -0
  49. package/dist/components/FileUpload/index.js +8 -3
  50. package/dist/components/ImageSelector/index.cjs +8 -3
  51. package/dist/components/ImageSelector/index.css +15 -0
  52. package/dist/components/ImageSelector/index.d.cts +3 -0
  53. package/dist/components/ImageSelector/index.d.ts +3 -0
  54. package/dist/components/ImageSelector/index.js +8 -3
  55. package/dist/components/Input/index.cjs +15 -5
  56. package/dist/components/Input/index.css +15 -0
  57. package/dist/components/Input/index.d.cts +8 -0
  58. package/dist/components/Input/index.d.ts +8 -0
  59. package/dist/components/Input/index.js +15 -5
  60. package/dist/components/Modal/index.cjs +84 -5
  61. package/dist/components/Modal/index.js +84 -5
  62. package/dist/components/NotificationBadge/index.cjs +5 -1
  63. package/dist/components/NotificationBadge/index.css +1 -0
  64. package/dist/components/NotificationBadge/index.d.cts +6 -2
  65. package/dist/components/NotificationBadge/index.d.ts +6 -2
  66. package/dist/components/NotificationBadge/index.js +5 -1
  67. package/dist/components/Pagination/index.cjs +6 -2
  68. package/dist/components/Pagination/index.css +1 -0
  69. package/dist/components/Pagination/index.d.cts +13 -2
  70. package/dist/components/Pagination/index.d.ts +13 -2
  71. package/dist/components/Pagination/index.js +6 -2
  72. package/dist/components/PopOver/index.cjs +34 -6
  73. package/dist/components/PopOver/index.js +34 -6
  74. package/dist/components/Progress/index.cjs +6 -2
  75. package/dist/components/Progress/index.css +1 -0
  76. package/dist/components/Progress/index.d.cts +11 -2
  77. package/dist/components/Progress/index.d.ts +11 -2
  78. package/dist/components/Progress/index.js +6 -2
  79. package/dist/components/Radio/index.cjs +14 -5
  80. package/dist/components/Radio/index.css +17 -0
  81. package/dist/components/Radio/index.d.cts +11 -2
  82. package/dist/components/Radio/index.d.ts +11 -2
  83. package/dist/components/Radio/index.js +14 -5
  84. package/dist/components/Select/index.cjs +99 -31
  85. package/dist/components/Select/index.css +54 -0
  86. package/dist/components/Select/index.d.cts +16 -1
  87. package/dist/components/Select/index.d.ts +16 -1
  88. package/dist/components/Select/index.js +99 -31
  89. package/dist/components/Skeleton/index.d.cts +3 -0
  90. package/dist/components/Skeleton/index.d.ts +3 -0
  91. package/dist/components/Spinner/index.cjs +6 -2
  92. package/dist/components/Spinner/index.css +1 -0
  93. package/dist/components/Spinner/index.d.cts +8 -2
  94. package/dist/components/Spinner/index.d.ts +8 -2
  95. package/dist/components/Spinner/index.js +6 -2
  96. package/dist/components/Stat/index.cjs +84 -0
  97. package/dist/components/Stat/index.css +96 -0
  98. package/dist/components/Stat/index.d.cts +37 -0
  99. package/dist/components/Stat/index.d.ts +37 -0
  100. package/dist/components/Stat/index.js +57 -0
  101. package/dist/components/Steps/index.cjs +6 -2
  102. package/dist/components/Steps/index.css +11 -0
  103. package/dist/components/Steps/index.d.cts +9 -2
  104. package/dist/components/Steps/index.d.ts +9 -2
  105. package/dist/components/Steps/index.js +6 -2
  106. package/dist/components/Switch/index.cjs +20 -7
  107. package/dist/components/Switch/index.css +17 -0
  108. package/dist/components/Switch/index.d.cts +15 -2
  109. package/dist/components/Switch/index.d.ts +15 -2
  110. package/dist/components/Switch/index.js +20 -7
  111. package/dist/components/Tab/index.cjs +51 -26
  112. package/dist/components/Tab/index.css +4 -0
  113. package/dist/components/Tab/index.d.cts +12 -1
  114. package/dist/components/Tab/index.d.ts +12 -1
  115. package/dist/components/Tab/index.js +51 -26
  116. package/dist/components/Tag/index.cjs +5 -3
  117. package/dist/components/Tag/index.d.cts +12 -2
  118. package/dist/components/Tag/index.d.ts +12 -2
  119. package/dist/components/Tag/index.js +5 -3
  120. package/dist/components/TextArea/index.cjs +14 -4
  121. package/dist/components/TextArea/index.css +15 -0
  122. package/dist/components/TextArea/index.d.cts +7 -0
  123. package/dist/components/TextArea/index.d.ts +7 -0
  124. package/dist/components/TextArea/index.js +14 -4
  125. package/dist/components/TimePicker/index.cjs +448 -0
  126. package/dist/components/TimePicker/index.css +172 -0
  127. package/dist/components/TimePicker/index.d.cts +42 -0
  128. package/dist/components/TimePicker/index.d.ts +42 -0
  129. package/dist/components/TimePicker/index.js +411 -0
  130. package/dist/components/Tooltip/index.cjs +4 -2
  131. package/dist/components/Tooltip/index.d.cts +11 -1
  132. package/dist/components/Tooltip/index.d.ts +11 -1
  133. package/dist/components/Tooltip/index.js +4 -2
  134. package/dist/components/index.cjs +916 -385
  135. package/dist/components/index.css +296 -0
  136. package/dist/components/index.d.cts +3 -0
  137. package/dist/components/index.d.ts +3 -0
  138. package/dist/components/index.js +914 -385
  139. package/dist/index.cjs +940 -409
  140. package/dist/index.css +296 -0
  141. package/dist/index.d.cts +3 -0
  142. package/dist/index.d.ts +3 -0
  143. package/dist/index.js +938 -409
  144. package/dist/status-njWqQWFV.d.cts +20 -0
  145. package/dist/status-njWqQWFV.d.ts +20 -0
  146. package/guidelines/AGENT_PROMPT.md +18 -3
  147. package/guidelines/Guidelines.md +3 -0
  148. package/guidelines/components/card.md +3 -3
  149. package/guidelines/components/editor.md +86 -0
  150. package/guidelines/components/feedback.md +1 -1
  151. package/guidelines/components/form.md +19 -0
  152. package/guidelines/components/navigation.md +16 -15
  153. package/guidelines/components/overlay.md +43 -4
  154. package/guidelines/components/stat.md +31 -0
  155. package/guidelines/components/time-picker.md +49 -0
  156. package/package.json +1 -1
@@ -178,11 +178,16 @@ var FieldMessage = (props) => {
178
178
  FieldMessage.displayName = "FieldMessage";
179
179
  var FieldMessage_default = FieldMessage;
180
180
  var Field = (props) => {
181
- const { validations, children } = props;
182
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
181
+ const { label, required = false, htmlFor, validations, children } = props;
182
+ const hasMessages = !!validations && validations.length > 0;
183
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
183
184
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
185
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
186
+ label,
187
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
188
+ ] }),
184
189
  children,
185
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
190
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
186
191
  ] });
187
192
  };
188
193
  Field.displayName = "Field";
@@ -281,8 +286,12 @@ var Input = import_react.default.forwardRef((props, ref) => {
281
286
  suffix,
282
287
  disabled,
283
288
  validations,
289
+ label,
290
+ required,
284
291
  ...inputProps
285
292
  } = props;
293
+ const autoId = import_react.default.useId();
294
+ const fieldId = inputProps.id ?? autoId;
286
295
  const localValue = formatValue(type, value);
287
296
  const handleChange = (e) => {
288
297
  let val = e.target.value;
@@ -298,7 +307,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
298
307
  onChange(event);
299
308
  }
300
309
  };
301
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
310
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { label, required, htmlFor: fieldId, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
302
311
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
303
312
  "div",
304
313
  {
@@ -309,6 +318,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
309
318
  {
310
319
  ...inputProps,
311
320
  ref,
321
+ id: fieldId,
312
322
  disabled,
313
323
  type: type === "number" ? "text" : type,
314
324
  value: localValue,
@@ -321,7 +331,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
321
331
  }
322
332
  ),
323
333
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FieldMessage_default, { validations })
324
- ] });
334
+ ] }) });
325
335
  });
326
336
  Input.displayName = "Input";
327
337
  var Input_default = Input;
@@ -4,6 +4,21 @@
4
4
  flex-direction: column;
5
5
  gap: var(--spacing-space-2);
6
6
  }
7
+ .lib-xplat-field-label {
8
+ font-family: var(--semantic-typo-label-2-m-font);
9
+ font-size: var(--semantic-typo-label-2-m-size);
10
+ line-height: var(--semantic-typo-label-2-m-lh);
11
+ font-weight: var(--semantic-typo-label-2-m-weight);
12
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
13
+ color: var(--semantic-text-secondary);
14
+ display: inline-flex;
15
+ align-items: center;
16
+ gap: var(--spacing-space-1);
17
+ cursor: pointer;
18
+ }
19
+ .lib-xplat-field-label > .required {
20
+ color: var(--semantic-text-error);
21
+ }
7
22
  .lib-xplat-field-message-wrap {
8
23
  display: flex;
9
24
  gap: var(--spacing-space-2);
@@ -4,9 +4,16 @@ import { V as Validation } from '../../validation-Dqo1zqhT.cjs';
4
4
 
5
5
  type InputSize$1 = "sm" | "md" | "lg";
6
6
  interface InputProps extends Omit<InputAttributes, "size"> {
7
+ /** 입력 종류. number 는 천 단위 구분, tel 은 하이픈을 자동으로 넣는다 */
7
8
  type?: "text" | "number" | "email" | "password" | "tel";
9
+ /** 높이와 글자 크기 */
8
10
  size?: InputSize$1;
9
11
  validations?: Validation[];
12
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 입력칸에 포커스가 간다 */
13
+ label?: React.ReactNode;
14
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
15
+ required?: boolean;
16
+ /** 입력칸 오른쪽에 붙일 요소 (단위·아이콘 등) */
10
17
  suffix?: React.ReactNode;
11
18
  }
12
19
  declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -14,6 +21,7 @@ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttri
14
21
  type InputSize = "sm" | "md" | "lg";
15
22
  interface PasswordInputProps extends Omit<InputAttributes, "size"> {
16
23
  validations?: Validation[];
24
+ /** 높이와 글자 크기 */
17
25
  size?: InputSize;
18
26
  reg?: RegExp;
19
27
  }
@@ -4,9 +4,16 @@ import { V as Validation } from '../../validation-Dqo1zqhT.js';
4
4
 
5
5
  type InputSize$1 = "sm" | "md" | "lg";
6
6
  interface InputProps extends Omit<InputAttributes, "size"> {
7
+ /** 입력 종류. number 는 천 단위 구분, tel 은 하이픈을 자동으로 넣는다 */
7
8
  type?: "text" | "number" | "email" | "password" | "tel";
9
+ /** 높이와 글자 크기 */
8
10
  size?: InputSize$1;
9
11
  validations?: Validation[];
12
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 입력칸에 포커스가 간다 */
13
+ label?: React.ReactNode;
14
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
15
+ required?: boolean;
16
+ /** 입력칸 오른쪽에 붙일 요소 (단위·아이콘 등) */
10
17
  suffix?: React.ReactNode;
11
18
  }
12
19
  declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -14,6 +21,7 @@ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttri
14
21
  type InputSize = "sm" | "md" | "lg";
15
22
  interface PasswordInputProps extends Omit<InputAttributes, "size"> {
16
23
  validations?: Validation[];
24
+ /** 높이와 글자 크기 */
17
25
  size?: InputSize;
18
26
  reg?: RegExp;
19
27
  }
@@ -141,11 +141,16 @@ var FieldMessage = (props) => {
141
141
  FieldMessage.displayName = "FieldMessage";
142
142
  var FieldMessage_default = FieldMessage;
143
143
  var Field = (props) => {
144
- const { validations, children } = props;
145
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx4(Fragment, { children });
144
+ const { label, required = false, htmlFor, validations, children } = props;
145
+ const hasMessages = !!validations && validations.length > 0;
146
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx4(Fragment, { children });
146
147
  return /* @__PURE__ */ jsxs3("div", { className: "lib-xplat-field", children: [
148
+ label && /* @__PURE__ */ jsxs3("label", { className: "lib-xplat-field-label", htmlFor, children: [
149
+ label,
150
+ required && /* @__PURE__ */ jsx4("span", { className: "required", "aria-hidden": "true", children: "*" })
151
+ ] }),
147
152
  children,
148
- /* @__PURE__ */ jsx4(FieldMessage, { validations })
153
+ hasMessages && /* @__PURE__ */ jsx4(FieldMessage, { validations })
149
154
  ] });
150
155
  };
151
156
  Field.displayName = "Field";
@@ -244,8 +249,12 @@ var Input = React.forwardRef((props, ref) => {
244
249
  suffix,
245
250
  disabled,
246
251
  validations,
252
+ label,
253
+ required,
247
254
  ...inputProps
248
255
  } = props;
256
+ const autoId = React.useId();
257
+ const fieldId = inputProps.id ?? autoId;
249
258
  const localValue = formatValue(type, value);
250
259
  const handleChange = (e) => {
251
260
  let val = e.target.value;
@@ -261,7 +270,7 @@ var Input = React.forwardRef((props, ref) => {
261
270
  onChange(event);
262
271
  }
263
272
  };
264
- return /* @__PURE__ */ jsxs4("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
273
+ return /* @__PURE__ */ jsx5(Field, { label, required, htmlFor: fieldId, children: /* @__PURE__ */ jsxs4("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
265
274
  /* @__PURE__ */ jsxs4(
266
275
  "div",
267
276
  {
@@ -272,6 +281,7 @@ var Input = React.forwardRef((props, ref) => {
272
281
  {
273
282
  ...inputProps,
274
283
  ref,
284
+ id: fieldId,
275
285
  disabled,
276
286
  type: type === "number" ? "text" : type,
277
287
  value: localValue,
@@ -284,7 +294,7 @@ var Input = React.forwardRef((props, ref) => {
284
294
  }
285
295
  ),
286
296
  /* @__PURE__ */ jsx5(FieldMessage_default, { validations })
287
- ] });
297
+ ] }) });
288
298
  });
289
299
  Input.displayName = "Input";
290
300
  var Input_default = Input;
@@ -35,9 +35,84 @@ __export(Modal_exports, {
35
35
  module.exports = __toCommonJS(Modal_exports);
36
36
 
37
37
  // src/components/Modal/Modal.tsx
38
- var import_react = __toESM(require("react"), 1);
38
+ var import_react2 = __toESM(require("react"), 1);
39
39
  var import_react_dom = require("react-dom");
40
40
 
41
+ // src/tokens/hooks/useOverlayKeyboard.ts
42
+ var import_react = __toESM(require("react"), 1);
43
+ var FOCUSABLE = [
44
+ "a[href]",
45
+ "button:not([disabled])",
46
+ "input:not([disabled]):not([type='hidden'])",
47
+ "select:not([disabled])",
48
+ "textarea:not([disabled])",
49
+ "[tabindex]:not([tabindex='-1'])"
50
+ ].join(",");
51
+ var focusableIn = (el) => Array.from(el.querySelectorAll(FOCUSABLE)).filter(
52
+ // 화면에 없는 것은 제외 (display:none 등)
53
+ (n) => n.offsetWidth > 0 || n.offsetHeight > 0 || n === document.activeElement
54
+ );
55
+ var useEscapeKey = (enabled, onEscape) => {
56
+ import_react.default.useEffect(() => {
57
+ if (!enabled) return;
58
+ const onKey = (e) => {
59
+ if (e.key !== "Escape") return;
60
+ if (e.defaultPrevented) return;
61
+ e.preventDefault();
62
+ onEscape();
63
+ };
64
+ document.addEventListener("keydown", onKey);
65
+ return () => document.removeEventListener("keydown", onKey);
66
+ }, [enabled, onEscape]);
67
+ };
68
+ var useFocusTrap = (containerRef, enabled, options = {}) => {
69
+ const { trap = false, initialFocus } = options;
70
+ const restoreRef = import_react.default.useRef(null);
71
+ import_react.default.useEffect(() => {
72
+ if (!enabled) return;
73
+ restoreRef.current = document.activeElement;
74
+ const raf = requestAnimationFrame(() => {
75
+ const el = containerRef.current;
76
+ if (!el) return;
77
+ const target = initialFocus?.current ?? focusableIn(el)[0] ?? el;
78
+ if (!target.hasAttribute("tabindex") && !focusableIn(el).includes(target)) {
79
+ target.setAttribute("tabindex", "-1");
80
+ }
81
+ target.focus({ preventScroll: true });
82
+ });
83
+ return () => {
84
+ cancelAnimationFrame(raf);
85
+ const prev = restoreRef.current;
86
+ if (prev && document.contains(prev)) prev.focus({ preventScroll: true });
87
+ };
88
+ }, [enabled, containerRef, initialFocus]);
89
+ import_react.default.useEffect(() => {
90
+ if (!enabled || !trap) return;
91
+ const onKey = (e) => {
92
+ if (e.key !== "Tab") return;
93
+ const el = containerRef.current;
94
+ if (!el) return;
95
+ const items = focusableIn(el);
96
+ if (items.length === 0) {
97
+ e.preventDefault();
98
+ return;
99
+ }
100
+ const first = items[0];
101
+ const last = items[items.length - 1];
102
+ const active = document.activeElement;
103
+ if (e.shiftKey && (active === first || !el.contains(active))) {
104
+ e.preventDefault();
105
+ last.focus();
106
+ } else if (!e.shiftKey && (active === last || !el.contains(active))) {
107
+ e.preventDefault();
108
+ first.focus();
109
+ }
110
+ };
111
+ document.addEventListener("keydown", onKey);
112
+ return () => document.removeEventListener("keydown", onKey);
113
+ }, [enabled, trap, containerRef]);
114
+ };
115
+
41
116
  // ../../node_modules/clsx/dist/clsx.mjs
42
117
  function r(e) {
43
118
  var t, f, n = "";
@@ -59,10 +134,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
59
134
  var ANIMATION_DURATION_MS = 200;
60
135
  var Modal = (props) => {
61
136
  const { isOpen, onClose, children } = props;
62
- const [mounted, setMounted] = import_react.default.useState(false);
63
- const [visible, setVisible] = import_react.default.useState(false);
64
- const [settled, setSettled] = import_react.default.useState(false);
65
- import_react.default.useEffect(() => {
137
+ const [mounted, setMounted] = import_react2.default.useState(false);
138
+ const [visible, setVisible] = import_react2.default.useState(false);
139
+ const [settled, setSettled] = import_react2.default.useState(false);
140
+ const boxRef = import_react2.default.useRef(null);
141
+ useEscapeKey(isOpen, onClose);
142
+ useFocusTrap(boxRef, isOpen && mounted, { trap: true });
143
+ import_react2.default.useEffect(() => {
66
144
  if (isOpen) {
67
145
  setMounted(true);
68
146
  const show = setTimeout(() => setVisible(true), 1);
@@ -89,6 +167,7 @@ var Modal = (props) => {
89
167
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
90
168
  "div",
91
169
  {
170
+ ref: boxRef,
92
171
  className: clsx_default("lib-xplat-modal", "modal-box", stateClass, settled && "settled"),
93
172
  role: "dialog",
94
173
  "aria-modal": "true",
@@ -1,7 +1,82 @@
1
1
  // src/components/Modal/Modal.tsx
2
- import React from "react";
2
+ import React2 from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
+ // src/tokens/hooks/useOverlayKeyboard.ts
6
+ import React from "react";
7
+ var FOCUSABLE = [
8
+ "a[href]",
9
+ "button:not([disabled])",
10
+ "input:not([disabled]):not([type='hidden'])",
11
+ "select:not([disabled])",
12
+ "textarea:not([disabled])",
13
+ "[tabindex]:not([tabindex='-1'])"
14
+ ].join(",");
15
+ var focusableIn = (el) => Array.from(el.querySelectorAll(FOCUSABLE)).filter(
16
+ // 화면에 없는 것은 제외 (display:none 등)
17
+ (n) => n.offsetWidth > 0 || n.offsetHeight > 0 || n === document.activeElement
18
+ );
19
+ var useEscapeKey = (enabled, onEscape) => {
20
+ React.useEffect(() => {
21
+ if (!enabled) return;
22
+ const onKey = (e) => {
23
+ if (e.key !== "Escape") return;
24
+ if (e.defaultPrevented) return;
25
+ e.preventDefault();
26
+ onEscape();
27
+ };
28
+ document.addEventListener("keydown", onKey);
29
+ return () => document.removeEventListener("keydown", onKey);
30
+ }, [enabled, onEscape]);
31
+ };
32
+ var useFocusTrap = (containerRef, enabled, options = {}) => {
33
+ const { trap = false, initialFocus } = options;
34
+ const restoreRef = React.useRef(null);
35
+ React.useEffect(() => {
36
+ if (!enabled) return;
37
+ restoreRef.current = document.activeElement;
38
+ const raf = requestAnimationFrame(() => {
39
+ const el = containerRef.current;
40
+ if (!el) return;
41
+ const target = initialFocus?.current ?? focusableIn(el)[0] ?? el;
42
+ if (!target.hasAttribute("tabindex") && !focusableIn(el).includes(target)) {
43
+ target.setAttribute("tabindex", "-1");
44
+ }
45
+ target.focus({ preventScroll: true });
46
+ });
47
+ return () => {
48
+ cancelAnimationFrame(raf);
49
+ const prev = restoreRef.current;
50
+ if (prev && document.contains(prev)) prev.focus({ preventScroll: true });
51
+ };
52
+ }, [enabled, containerRef, initialFocus]);
53
+ React.useEffect(() => {
54
+ if (!enabled || !trap) return;
55
+ const onKey = (e) => {
56
+ if (e.key !== "Tab") return;
57
+ const el = containerRef.current;
58
+ if (!el) return;
59
+ const items = focusableIn(el);
60
+ if (items.length === 0) {
61
+ e.preventDefault();
62
+ return;
63
+ }
64
+ const first = items[0];
65
+ const last = items[items.length - 1];
66
+ const active = document.activeElement;
67
+ if (e.shiftKey && (active === first || !el.contains(active))) {
68
+ e.preventDefault();
69
+ last.focus();
70
+ } else if (!e.shiftKey && (active === last || !el.contains(active))) {
71
+ e.preventDefault();
72
+ first.focus();
73
+ }
74
+ };
75
+ document.addEventListener("keydown", onKey);
76
+ return () => document.removeEventListener("keydown", onKey);
77
+ }, [enabled, trap, containerRef]);
78
+ };
79
+
5
80
  // ../../node_modules/clsx/dist/clsx.mjs
6
81
  function r(e) {
7
82
  var t, f, n = "";
@@ -23,10 +98,13 @@ import { jsx } from "react/jsx-runtime";
23
98
  var ANIMATION_DURATION_MS = 200;
24
99
  var Modal = (props) => {
25
100
  const { isOpen, onClose, children } = props;
26
- const [mounted, setMounted] = React.useState(false);
27
- const [visible, setVisible] = React.useState(false);
28
- const [settled, setSettled] = React.useState(false);
29
- React.useEffect(() => {
101
+ const [mounted, setMounted] = React2.useState(false);
102
+ const [visible, setVisible] = React2.useState(false);
103
+ const [settled, setSettled] = React2.useState(false);
104
+ const boxRef = React2.useRef(null);
105
+ useEscapeKey(isOpen, onClose);
106
+ useFocusTrap(boxRef, isOpen && mounted, { trap: true });
107
+ React2.useEffect(() => {
30
108
  if (isOpen) {
31
109
  setMounted(true);
32
110
  const show = setTimeout(() => setVisible(true), 1);
@@ -53,6 +131,7 @@ var Modal = (props) => {
53
131
  children: /* @__PURE__ */ jsx(
54
132
  "div",
55
133
  {
134
+ ref: boxRef,
56
135
  className: clsx_default("lib-xplat-modal", "modal-box", stateClass, settled && "settled"),
57
136
  role: "dialog",
58
137
  "aria-modal": "true",
@@ -25,6 +25,9 @@ __export(NotificationBadge_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(NotificationBadge_exports);
27
27
 
28
+ // src/tokens/interface/status.ts
29
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
30
+
28
31
  // ../../node_modules/clsx/dist/clsx.mjs
29
32
  function r(e) {
30
33
  var t, f, n = "";
@@ -52,11 +55,12 @@ var NotificationBadge = (props) => {
52
55
  dot = false,
53
56
  size = "md"
54
57
  } = props;
58
+ const statusType = normalizeStatus(type);
55
59
  const showBadge = dot || count !== void 0 && count > 0;
56
60
  const displayCount = count !== void 0 && count > maxCount ? `${maxCount}+` : count;
57
61
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: clsx_default("lib-xplat-notification-badge", size), children: [
58
62
  children,
59
- showBadge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: clsx_default("badge-indicator", type, { dot }), children: !dot && displayCount })
63
+ showBadge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: clsx_default("badge-indicator", statusType, { dot }), children: !dot && displayCount })
60
64
  ] });
61
65
  };
62
66
  NotificationBadge.displayName = "NotificationBadge";
@@ -70,6 +70,7 @@
70
70
  .lib-xplat-notification-badge .badge-indicator.info {
71
71
  background-color: var(--semantic-surface-info-default);
72
72
  }
73
+ .lib-xplat-notification-badge .badge-indicator.primary,
73
74
  .lib-xplat-notification-badge .badge-indicator.brand {
74
75
  background-color: var(--semantic-surface-brand-default);
75
76
  }
@@ -1,7 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
3
4
 
4
- type NotificationBadgeType = "error" | "success" | "warning" | "info" | "brand";
5
+ /** NotificationBadge 의 상태 색 */
6
+ type NotificationBadgeType = "primary" | "success" | "error" | "warning" | "info";
7
+ /** 입력으로는 예전 이름 `brand` 도 받는다 (= `primary`) */
8
+ type NotificationBadgeTypeInput = NotificationBadgeType | LegacyStatusType;
5
9
  type NotificationBadgeSize = "sm" | "md" | "lg";
6
10
  /**
7
11
  * 알림 뱃지 — 다른 요소의 **우상단 모서리에 얹히는** 작은 원.
@@ -33,7 +37,7 @@ interface NotificationBadgeProps {
33
37
  /** 개수 대신 점만 표시한다. `count` 없이 "새 항목 있음" 만 알릴 때 쓴다 */
34
38
  dot?: boolean;
35
39
  /** 뱃지 색. 뱃지가 렌더될 때만 의미가 있다 */
36
- type?: NotificationBadgeType;
40
+ type?: NotificationBadgeTypeInput;
37
41
  /** `count` 가 이 값을 넘으면 `99+` 처럼 표시한다 */
38
42
  maxCount?: number;
39
43
  /** 뱃지 크기. 대상의 크기가 아니다 */
@@ -1,7 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
3
4
 
4
- type NotificationBadgeType = "error" | "success" | "warning" | "info" | "brand";
5
+ /** NotificationBadge 의 상태 색 */
6
+ type NotificationBadgeType = "primary" | "success" | "error" | "warning" | "info";
7
+ /** 입력으로는 예전 이름 `brand` 도 받는다 (= `primary`) */
8
+ type NotificationBadgeTypeInput = NotificationBadgeType | LegacyStatusType;
5
9
  type NotificationBadgeSize = "sm" | "md" | "lg";
6
10
  /**
7
11
  * 알림 뱃지 — 다른 요소의 **우상단 모서리에 얹히는** 작은 원.
@@ -33,7 +37,7 @@ interface NotificationBadgeProps {
33
37
  /** 개수 대신 점만 표시한다. `count` 없이 "새 항목 있음" 만 알릴 때 쓴다 */
34
38
  dot?: boolean;
35
39
  /** 뱃지 색. 뱃지가 렌더될 때만 의미가 있다 */
36
- type?: NotificationBadgeType;
40
+ type?: NotificationBadgeTypeInput;
37
41
  /** `count` 가 이 값을 넘으면 `99+` 처럼 표시한다 */
38
42
  maxCount?: number;
39
43
  /** 뱃지 크기. 대상의 크기가 아니다 */
@@ -1,3 +1,6 @@
1
+ // src/tokens/interface/status.ts
2
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
3
+
1
4
  // ../../node_modules/clsx/dist/clsx.mjs
2
5
  function r(e) {
3
6
  var t, f, n = "";
@@ -25,11 +28,12 @@ var NotificationBadge = (props) => {
25
28
  dot = false,
26
29
  size = "md"
27
30
  } = props;
31
+ const statusType = normalizeStatus(type);
28
32
  const showBadge = dot || count !== void 0 && count > 0;
29
33
  const displayCount = count !== void 0 && count > maxCount ? `${maxCount}+` : count;
30
34
  return /* @__PURE__ */ jsxs("div", { className: clsx_default("lib-xplat-notification-badge", size), children: [
31
35
  children,
32
- showBadge && /* @__PURE__ */ jsx("span", { className: clsx_default("badge-indicator", type, { dot }), children: !dot && displayCount })
36
+ showBadge && /* @__PURE__ */ jsx("span", { className: clsx_default("badge-indicator", statusType, { dot }), children: !dot && displayCount })
33
37
  ] });
34
38
  };
35
39
  NotificationBadge.displayName = "NotificationBadge";
@@ -24,6 +24,9 @@ __export(Pagination_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(Pagination_exports);
26
26
 
27
+ // src/tokens/interface/status.ts
28
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
29
+
27
30
  // src/tokens/svg/action/CopyIcon.tsx
28
31
  var import_jsx_runtime = require("react/jsx-runtime");
29
32
 
@@ -981,8 +984,9 @@ var Pagination = (props) => {
981
984
  siblingCount = 1,
982
985
  onChange,
983
986
  size = "md",
984
- type = "brand"
987
+ type = "primary"
985
988
  } = props;
989
+ const statusType = normalizeStatus(type);
986
990
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
987
991
  const pages = getPageRange(current, totalPages, siblingCount);
988
992
  const handleClick = (page) => {
@@ -990,7 +994,7 @@ var Pagination = (props) => {
990
994
  onChange?.(page);
991
995
  }
992
996
  };
993
- return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("nav", { className: clsx_default("lib-xplat-pagination", size, type), "aria-label": "\uD398\uC774\uC9C0 \uB124\uBE44\uAC8C\uC774\uC158", children: [
997
+ return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("nav", { className: clsx_default("lib-xplat-pagination", size, statusType), "aria-label": "\uD398\uC774\uC9C0 \uB124\uBE44\uAC8C\uC774\uC158", children: [
994
998
  /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(
995
999
  "button",
996
1000
  {
@@ -93,6 +93,7 @@
93
93
  .lib-xplat-pagination .page-btn svg {
94
94
  font-size: var(--semantic-typo-label-2-m-size);
95
95
  }
96
+ .lib-xplat-pagination.primary .page-btn.active,
96
97
  .lib-xplat-pagination.brand .page-btn.active {
97
98
  background-color: var(--semantic-surface-brand-default);
98
99
  }
@@ -1,15 +1,26 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
2
3
 
3
4
  type PaginationSize = "sm" | "md" | "lg";
4
- type PaginationType = "brand" | "success" | "error" | "warning" | "info";
5
+ /** Pagination 의 상태 색 */
6
+ type PaginationType = "primary" | "success" | "error" | "warning" | "info";
7
+ /** 입력으로는 예전 이름 `brand` 도 받는다 (= `primary`) */
8
+ type PaginationTypeInput = PaginationType | LegacyStatusType;
5
9
  interface PaginationProps {
10
+ /** 현재 페이지 (1부터 시작) */
6
11
  current: number;
12
+ /** **전체 아이템 수**. 페이지 수가 아니다 */
7
13
  total: number;
14
+ /** 한 페이지에 담기는 아이템 수. total 과 함께 페이지 수를 계산한다 */
8
15
  pageSize?: number;
16
+ /** 현재 페이지 좌우로 보여줄 번호 개수 */
9
17
  siblingCount?: number;
18
+ /** 페이지를 누르면 새 페이지 번호를 준다 */
10
19
  onChange?: (page: number) => void;
20
+ /** 버튼 크기 */
11
21
  size?: PaginationSize;
12
- type?: PaginationType;
22
+ /** 활성 페이지 색 */
23
+ type?: PaginationTypeInput;
13
24
  }
14
25
  declare const Pagination: {
15
26
  (props: PaginationProps): react_jsx_runtime.JSX.Element;
@@ -1,15 +1,26 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
2
3
 
3
4
  type PaginationSize = "sm" | "md" | "lg";
4
- type PaginationType = "brand" | "success" | "error" | "warning" | "info";
5
+ /** Pagination 의 상태 색 */
6
+ type PaginationType = "primary" | "success" | "error" | "warning" | "info";
7
+ /** 입력으로는 예전 이름 `brand` 도 받는다 (= `primary`) */
8
+ type PaginationTypeInput = PaginationType | LegacyStatusType;
5
9
  interface PaginationProps {
10
+ /** 현재 페이지 (1부터 시작) */
6
11
  current: number;
12
+ /** **전체 아이템 수**. 페이지 수가 아니다 */
7
13
  total: number;
14
+ /** 한 페이지에 담기는 아이템 수. total 과 함께 페이지 수를 계산한다 */
8
15
  pageSize?: number;
16
+ /** 현재 페이지 좌우로 보여줄 번호 개수 */
9
17
  siblingCount?: number;
18
+ /** 페이지를 누르면 새 페이지 번호를 준다 */
10
19
  onChange?: (page: number) => void;
20
+ /** 버튼 크기 */
11
21
  size?: PaginationSize;
12
- type?: PaginationType;
22
+ /** 활성 페이지 색 */
23
+ type?: PaginationTypeInput;
13
24
  }
14
25
  declare const Pagination: {
15
26
  (props: PaginationProps): react_jsx_runtime.JSX.Element;
@@ -1,3 +1,6 @@
1
+ // src/tokens/interface/status.ts
2
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
3
+
1
4
  // src/tokens/svg/action/CopyIcon.tsx
2
5
  import { jsx, jsxs } from "react/jsx-runtime";
3
6
 
@@ -955,8 +958,9 @@ var Pagination = (props) => {
955
958
  siblingCount = 1,
956
959
  onChange,
957
960
  size = "md",
958
- type = "brand"
961
+ type = "primary"
959
962
  } = props;
963
+ const statusType = normalizeStatus(type);
960
964
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
961
965
  const pages = getPageRange(current, totalPages, siblingCount);
962
966
  const handleClick = (page) => {
@@ -964,7 +968,7 @@ var Pagination = (props) => {
964
968
  onChange?.(page);
965
969
  }
966
970
  };
967
- return /* @__PURE__ */ jsxs189("nav", { className: clsx_default("lib-xplat-pagination", size, type), "aria-label": "\uD398\uC774\uC9C0 \uB124\uBE44\uAC8C\uC774\uC158", children: [
971
+ return /* @__PURE__ */ jsxs189("nav", { className: clsx_default("lib-xplat-pagination", size, statusType), "aria-label": "\uD398\uC774\uC9C0 \uB124\uBE44\uAC8C\uC774\uC158", children: [
968
972
  /* @__PURE__ */ jsx295(
969
973
  "button",
970
974
  {