@x-plat/design-system 0.10.0 → 0.12.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 (115) 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 +4 -0
  4. package/dist/components/AutoResizeTextArea/index.d.ts +4 -0
  5. package/dist/components/AutoResizeTextArea/index.js +14 -5
  6. package/dist/components/Badge/index.d.cts +1 -1
  7. package/dist/components/Badge/index.d.ts +1 -1
  8. package/dist/components/ChatInput/index.cjs +20 -7
  9. package/dist/components/ChatInput/index.css +15 -0
  10. package/dist/components/ChatInput/index.d.cts +4 -0
  11. package/dist/components/ChatInput/index.d.ts +4 -0
  12. package/dist/components/ChatInput/index.js +20 -7
  13. package/dist/components/CheckBox/index.cjs +8 -3
  14. package/dist/components/CheckBox/index.css +15 -0
  15. package/dist/components/CheckBox/index.d.cts +1 -1
  16. package/dist/components/CheckBox/index.d.ts +1 -1
  17. package/dist/components/CheckBox/index.js +8 -3
  18. package/dist/components/DatePicker/index.cjs +179 -64
  19. package/dist/components/DatePicker/index.css +19 -0
  20. package/dist/components/DatePicker/index.js +179 -64
  21. package/dist/components/Drawer/index.cjs +83 -4
  22. package/dist/components/Drawer/index.js +83 -4
  23. package/dist/components/Dropdown/index.cjs +57 -16
  24. package/dist/components/Dropdown/index.css +3 -0
  25. package/dist/components/Dropdown/index.js +57 -16
  26. package/dist/components/Editor/index.cjs +779 -50
  27. package/dist/components/Editor/index.css +100 -0
  28. package/dist/components/Editor/index.d.cts +1 -1
  29. package/dist/components/Editor/index.d.ts +1 -1
  30. package/dist/components/Editor/index.js +779 -50
  31. package/dist/components/FieldMessage/index.cjs +8 -3
  32. package/dist/components/FieldMessage/index.css +15 -0
  33. package/dist/components/FieldMessage/index.d.cts +11 -4
  34. package/dist/components/FieldMessage/index.d.ts +11 -4
  35. package/dist/components/FieldMessage/index.js +8 -3
  36. package/dist/components/FileUpload/index.cjs +8 -3
  37. package/dist/components/FileUpload/index.css +15 -0
  38. package/dist/components/FileUpload/index.js +8 -3
  39. package/dist/components/ImageSelector/index.cjs +8 -3
  40. package/dist/components/ImageSelector/index.css +15 -0
  41. package/dist/components/ImageSelector/index.js +8 -3
  42. package/dist/components/Input/index.cjs +15 -5
  43. package/dist/components/Input/index.css +15 -0
  44. package/dist/components/Input/index.d.cts +4 -0
  45. package/dist/components/Input/index.d.ts +4 -0
  46. package/dist/components/Input/index.js +15 -5
  47. package/dist/components/Modal/index.cjs +84 -5
  48. package/dist/components/Modal/index.js +84 -5
  49. package/dist/components/NotificationBadge/index.d.cts +1 -1
  50. package/dist/components/NotificationBadge/index.d.ts +1 -1
  51. package/dist/components/Pagination/index.d.cts +1 -1
  52. package/dist/components/Pagination/index.d.ts +1 -1
  53. package/dist/components/PopOver/index.cjs +55 -13
  54. package/dist/components/PopOver/index.css +3 -0
  55. package/dist/components/PopOver/index.js +55 -13
  56. package/dist/components/Progress/index.d.cts +1 -1
  57. package/dist/components/Progress/index.d.ts +1 -1
  58. package/dist/components/Radio/index.cjs +8 -3
  59. package/dist/components/Radio/index.css +15 -0
  60. package/dist/components/Radio/index.d.cts +1 -1
  61. package/dist/components/Radio/index.d.ts +1 -1
  62. package/dist/components/Radio/index.js +8 -3
  63. package/dist/components/Select/index.cjs +120 -39
  64. package/dist/components/Select/index.css +57 -0
  65. package/dist/components/Select/index.d.cts +11 -0
  66. package/dist/components/Select/index.d.ts +11 -0
  67. package/dist/components/Select/index.js +120 -39
  68. package/dist/components/Spinner/index.d.cts +1 -1
  69. package/dist/components/Spinner/index.d.ts +1 -1
  70. package/dist/components/Stat/index.cjs +84 -0
  71. package/dist/components/Stat/index.css +96 -0
  72. package/dist/components/Stat/index.d.cts +37 -0
  73. package/dist/components/Stat/index.d.ts +37 -0
  74. package/dist/components/Stat/index.js +57 -0
  75. package/dist/components/Steps/index.d.cts +1 -1
  76. package/dist/components/Steps/index.d.ts +1 -1
  77. package/dist/components/Switch/index.cjs +14 -5
  78. package/dist/components/Switch/index.css +15 -0
  79. package/dist/components/Switch/index.d.cts +6 -1
  80. package/dist/components/Switch/index.d.ts +6 -1
  81. package/dist/components/Switch/index.js +14 -5
  82. package/dist/components/Tab/index.cjs +49 -25
  83. package/dist/components/Tab/index.css +4 -0
  84. package/dist/components/Tab/index.js +49 -25
  85. package/dist/components/TextArea/index.cjs +14 -4
  86. package/dist/components/TextArea/index.css +15 -0
  87. package/dist/components/TextArea/index.d.cts +4 -0
  88. package/dist/components/TextArea/index.d.ts +4 -0
  89. package/dist/components/TextArea/index.js +14 -4
  90. package/dist/components/TimePicker/index.cjs +461 -0
  91. package/dist/components/TimePicker/index.css +175 -0
  92. package/dist/components/TimePicker/index.d.cts +42 -0
  93. package/dist/components/TimePicker/index.d.ts +42 -0
  94. package/dist/components/TimePicker/index.js +424 -0
  95. package/dist/components/index.cjs +1500 -398
  96. package/dist/components/index.css +385 -0
  97. package/dist/components/index.d.cts +3 -1
  98. package/dist/components/index.d.ts +3 -1
  99. package/dist/components/index.js +1498 -398
  100. package/dist/index.cjs +1524 -422
  101. package/dist/index.css +385 -0
  102. package/dist/index.d.cts +3 -1
  103. package/dist/index.d.ts +3 -1
  104. package/dist/index.js +1522 -422
  105. package/dist/status-njWqQWFV.d.cts +20 -0
  106. package/dist/status-njWqQWFV.d.ts +20 -0
  107. package/guidelines/AGENT_PROMPT.md +3 -3
  108. package/guidelines/Guidelines.md +3 -0
  109. package/guidelines/components/editor.md +141 -0
  110. package/guidelines/components/form.md +19 -0
  111. package/guidelines/components/stat.md +31 -0
  112. package/guidelines/components/time-picker.md +49 -0
  113. package/package.json +1 -1
  114. package/dist/status-g_KP3qT6.d.cts +0 -8
  115. package/dist/status-g_KP3qT6.d.ts +0 -8
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.js';
2
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
3
3
 
4
4
  type PaginationSize = "sm" | "md" | "lg";
5
5
  /** Pagination 의 상태 색 */
@@ -35,7 +35,7 @@ __export(PopOver_exports, {
35
35
  module.exports = __toCommonJS(PopOver_exports);
36
36
 
37
37
  // src/components/PopOver/PopOver.tsx
38
- var import_react3 = __toESM(require("react"), 1);
38
+ var import_react4 = __toESM(require("react"), 1);
39
39
 
40
40
  // src/tokens/hooks/Portal.tsx
41
41
  var import_react_dom = __toESM(require("react-dom"), 1);
@@ -51,8 +51,10 @@ var import_react = __toESM(require("react"), 1);
51
51
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
52
52
  const [position, setPosition] = import_react.default.useState({
53
53
  position: {},
54
- direction: "bottom"
54
+ direction: "bottom",
55
+ repositioning: false
55
56
  });
57
+ const placedRef = import_react.default.useRef(false);
56
58
  const calculatePosition = import_react.default.useCallback(() => {
57
59
  if (!triggerRef.current || !popRef.current) return;
58
60
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -77,11 +79,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
77
79
  if (left < 8) left = 8;
78
80
  setPosition({
79
81
  position: { top, left },
80
- direction
82
+ direction,
83
+ repositioning: placedRef.current
81
84
  });
85
+ placedRef.current = true;
82
86
  }, [triggerRef, popRef]);
83
87
  import_react.default.useLayoutEffect(() => {
84
- if (!enabled) return;
88
+ if (!enabled) {
89
+ placedRef.current = false;
90
+ return;
91
+ }
85
92
  calculatePosition();
86
93
  const raf = requestAnimationFrame(calculatePosition);
87
94
  return () => cancelAnimationFrame(raf);
@@ -96,11 +103,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
96
103
  }, [calculatePosition, enabled, popRef]);
97
104
  import_react.default.useEffect(() => {
98
105
  if (!enabled) return;
99
- window.addEventListener("resize", calculatePosition);
100
- window.addEventListener("scroll", calculatePosition, true);
106
+ let raf = 0;
107
+ const onScrollOrResize = () => {
108
+ cancelAnimationFrame(raf);
109
+ raf = requestAnimationFrame(calculatePosition);
110
+ };
111
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
112
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
101
113
  return () => {
102
- window.removeEventListener("resize", calculatePosition);
103
- window.removeEventListener("scroll", calculatePosition, true);
114
+ cancelAnimationFrame(raf);
115
+ window.removeEventListener("resize", onScrollOrResize);
116
+ window.removeEventListener("scroll", onScrollOrResize, true);
104
117
  };
105
118
  }, [calculatePosition, enabled]);
106
119
  return position;
@@ -132,6 +145,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
132
145
  };
133
146
  var useClickOutside_default = useClickOutside;
134
147
 
148
+ // src/tokens/hooks/useOverlayKeyboard.ts
149
+ var import_react3 = __toESM(require("react"), 1);
150
+ var FOCUSABLE = [
151
+ "a[href]",
152
+ "button:not([disabled])",
153
+ "input:not([disabled]):not([type='hidden'])",
154
+ "select:not([disabled])",
155
+ "textarea:not([disabled])",
156
+ "[tabindex]:not([tabindex='-1'])"
157
+ ].join(",");
158
+ var useEscapeKey = (enabled, onEscape) => {
159
+ import_react3.default.useEffect(() => {
160
+ if (!enabled) return;
161
+ const onKey = (e) => {
162
+ if (e.key !== "Escape") return;
163
+ if (e.defaultPrevented) return;
164
+ e.preventDefault();
165
+ onEscape();
166
+ };
167
+ document.addEventListener("keydown", onKey);
168
+ return () => document.removeEventListener("keydown", onKey);
169
+ }, [enabled, onEscape]);
170
+ };
171
+
135
172
  // ../../node_modules/clsx/dist/clsx.mjs
136
173
  function r(e) {
137
174
  var t, f, n = "";
@@ -152,13 +189,17 @@ var clsx_default = clsx;
152
189
  var import_jsx_runtime = require("react/jsx-runtime");
153
190
  var PopOver = (props) => {
154
191
  const { children, isOpen, onClose, PopOverEl } = props;
155
- const popRef = import_react3.default.useRef(null);
156
- const triggerRef = import_react3.default.useRef(null);
157
- const [localOpen, setLocalOpen] = import_react3.default.useState(false);
158
- const [eventTrigger, setEventTrigger] = import_react3.default.useState(false);
192
+ const popRef = import_react4.default.useRef(null);
193
+ const triggerRef = import_react4.default.useRef(null);
194
+ const [localOpen, setLocalOpen] = import_react4.default.useState(false);
195
+ const [eventTrigger, setEventTrigger] = import_react4.default.useState(false);
159
196
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
197
+ useEscapeKey(isOpen, () => {
198
+ onClose();
199
+ triggerRef.current?.focus({ preventScroll: true });
200
+ });
160
201
  const position = useAutoPosition_default(triggerRef, popRef, localOpen);
161
- import_react3.default.useEffect(() => {
202
+ import_react4.default.useEffect(() => {
162
203
  if (isOpen) {
163
204
  setLocalOpen(isOpen);
164
205
  setTimeout(() => {
@@ -187,6 +228,7 @@ var PopOver = (props) => {
187
228
  className: clsx_default(
188
229
  "lib-xplat-pop-over-content",
189
230
  position.direction,
231
+ position.repositioning && "repositioning",
190
232
  eventTrigger && "visible"
191
233
  ),
192
234
  ref: popRef,
@@ -27,3 +27,6 @@
27
27
  opacity: 1;
28
28
  transform: scale(1);
29
29
  }
30
+ .lib-xplat-pop-over-content.repositioning {
31
+ transition: none;
32
+ }
@@ -1,5 +1,5 @@
1
1
  // src/components/PopOver/PopOver.tsx
2
- import React3 from "react";
2
+ import React4 from "react";
3
3
 
4
4
  // src/tokens/hooks/Portal.tsx
5
5
  import ReactDOM from "react-dom";
@@ -15,8 +15,10 @@ import React from "react";
15
15
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
16
16
  const [position, setPosition] = React.useState({
17
17
  position: {},
18
- direction: "bottom"
18
+ direction: "bottom",
19
+ repositioning: false
19
20
  });
21
+ const placedRef = React.useRef(false);
20
22
  const calculatePosition = React.useCallback(() => {
21
23
  if (!triggerRef.current || !popRef.current) return;
22
24
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -41,11 +43,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
41
43
  if (left < 8) left = 8;
42
44
  setPosition({
43
45
  position: { top, left },
44
- direction
46
+ direction,
47
+ repositioning: placedRef.current
45
48
  });
49
+ placedRef.current = true;
46
50
  }, [triggerRef, popRef]);
47
51
  React.useLayoutEffect(() => {
48
- if (!enabled) return;
52
+ if (!enabled) {
53
+ placedRef.current = false;
54
+ return;
55
+ }
49
56
  calculatePosition();
50
57
  const raf = requestAnimationFrame(calculatePosition);
51
58
  return () => cancelAnimationFrame(raf);
@@ -60,11 +67,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
60
67
  }, [calculatePosition, enabled, popRef]);
61
68
  React.useEffect(() => {
62
69
  if (!enabled) return;
63
- window.addEventListener("resize", calculatePosition);
64
- window.addEventListener("scroll", calculatePosition, true);
70
+ let raf = 0;
71
+ const onScrollOrResize = () => {
72
+ cancelAnimationFrame(raf);
73
+ raf = requestAnimationFrame(calculatePosition);
74
+ };
75
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
76
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
65
77
  return () => {
66
- window.removeEventListener("resize", calculatePosition);
67
- window.removeEventListener("scroll", calculatePosition, true);
78
+ cancelAnimationFrame(raf);
79
+ window.removeEventListener("resize", onScrollOrResize);
80
+ window.removeEventListener("scroll", onScrollOrResize, true);
68
81
  };
69
82
  }, [calculatePosition, enabled]);
70
83
  return position;
@@ -96,6 +109,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
96
109
  };
97
110
  var useClickOutside_default = useClickOutside;
98
111
 
112
+ // src/tokens/hooks/useOverlayKeyboard.ts
113
+ import React3 from "react";
114
+ var FOCUSABLE = [
115
+ "a[href]",
116
+ "button:not([disabled])",
117
+ "input:not([disabled]):not([type='hidden'])",
118
+ "select:not([disabled])",
119
+ "textarea:not([disabled])",
120
+ "[tabindex]:not([tabindex='-1'])"
121
+ ].join(",");
122
+ var useEscapeKey = (enabled, onEscape) => {
123
+ React3.useEffect(() => {
124
+ if (!enabled) return;
125
+ const onKey = (e) => {
126
+ if (e.key !== "Escape") return;
127
+ if (e.defaultPrevented) return;
128
+ e.preventDefault();
129
+ onEscape();
130
+ };
131
+ document.addEventListener("keydown", onKey);
132
+ return () => document.removeEventListener("keydown", onKey);
133
+ }, [enabled, onEscape]);
134
+ };
135
+
99
136
  // ../../node_modules/clsx/dist/clsx.mjs
100
137
  function r(e) {
101
138
  var t, f, n = "";
@@ -116,13 +153,17 @@ var clsx_default = clsx;
116
153
  import { jsx, jsxs } from "react/jsx-runtime";
117
154
  var PopOver = (props) => {
118
155
  const { children, isOpen, onClose, PopOverEl } = props;
119
- const popRef = React3.useRef(null);
120
- const triggerRef = React3.useRef(null);
121
- const [localOpen, setLocalOpen] = React3.useState(false);
122
- const [eventTrigger, setEventTrigger] = React3.useState(false);
156
+ const popRef = React4.useRef(null);
157
+ const triggerRef = React4.useRef(null);
158
+ const [localOpen, setLocalOpen] = React4.useState(false);
159
+ const [eventTrigger, setEventTrigger] = React4.useState(false);
123
160
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
161
+ useEscapeKey(isOpen, () => {
162
+ onClose();
163
+ triggerRef.current?.focus({ preventScroll: true });
164
+ });
124
165
  const position = useAutoPosition_default(triggerRef, popRef, localOpen);
125
- React3.useEffect(() => {
166
+ React4.useEffect(() => {
126
167
  if (isOpen) {
127
168
  setLocalOpen(isOpen);
128
169
  setTimeout(() => {
@@ -151,6 +192,7 @@ var PopOver = (props) => {
151
192
  className: clsx_default(
152
193
  "lib-xplat-pop-over-content",
153
194
  position.direction,
195
+ position.repositioning && "repositioning",
154
196
  eventTrigger && "visible"
155
197
  ),
156
198
  ref: popRef,
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.cjs';
2
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
3
3
 
4
4
  type ProgressSize = "sm" | "md" | "lg";
5
5
  /** Progress 의 상태 색 */
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.js';
2
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
3
3
 
4
4
  type ProgressSize = "sm" | "md" | "lg";
5
5
  /** Progress 의 상태 색 */
@@ -177,11 +177,16 @@ var FieldMessage = (props) => {
177
177
  };
178
178
  FieldMessage.displayName = "FieldMessage";
179
179
  var Field = (props) => {
180
- const { validations, children } = props;
181
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
180
+ const { label, required = false, htmlFor, validations, children } = props;
181
+ const hasMessages = !!validations && validations.length > 0;
182
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
182
183
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
184
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
185
+ label,
186
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
187
+ ] }),
183
188
  children,
184
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
189
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
185
190
  ] });
186
191
  };
187
192
  Field.displayName = "Field";
@@ -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);
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { I as InputAttributes } from '../../attributes-DJIWir_0.cjs';
3
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.cjs';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
4
4
  import { V as Validation } from '../../validation-Dqo1zqhT.cjs';
5
5
  import React from 'react';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { I as InputAttributes } from '../../attributes-DJIWir_0.js';
3
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.js';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
4
4
  import { V as Validation } from '../../validation-Dqo1zqhT.js';
5
5
  import React from 'react';
6
6
 
@@ -140,11 +140,16 @@ var FieldMessage = (props) => {
140
140
  };
141
141
  FieldMessage.displayName = "FieldMessage";
142
142
  var Field = (props) => {
143
- const { validations, children } = props;
144
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx4(Fragment, { children });
143
+ const { label, required = false, htmlFor, validations, children } = props;
144
+ const hasMessages = !!validations && validations.length > 0;
145
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx4(Fragment, { children });
145
146
  return /* @__PURE__ */ jsxs3("div", { className: "lib-xplat-field", children: [
147
+ label && /* @__PURE__ */ jsxs3("label", { className: "lib-xplat-field-label", htmlFor, children: [
148
+ label,
149
+ required && /* @__PURE__ */ jsx4("span", { className: "required", "aria-hidden": "true", children: "*" })
150
+ ] }),
146
151
  children,
147
- /* @__PURE__ */ jsx4(FieldMessage, { validations })
152
+ hasMessages && /* @__PURE__ */ jsx4(FieldMessage, { validations })
148
153
  ] });
149
154
  };
150
155
  Field.displayName = "Field";
@@ -36,7 +36,7 @@ __export(Select_exports, {
36
36
  module.exports = __toCommonJS(Select_exports);
37
37
 
38
38
  // src/components/Select/Select.tsx
39
- var import_react5 = __toESM(require("react"), 1);
39
+ var import_react6 = __toESM(require("react"), 1);
40
40
 
41
41
  // src/tokens/hooks/Portal.tsx
42
42
  var import_react_dom = __toESM(require("react-dom"), 1);
@@ -52,8 +52,10 @@ var import_react = __toESM(require("react"), 1);
52
52
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
53
53
  const [position, setPosition] = import_react.default.useState({
54
54
  position: {},
55
- direction: "bottom"
55
+ direction: "bottom",
56
+ repositioning: false
56
57
  });
58
+ const placedRef = import_react.default.useRef(false);
57
59
  const calculatePosition = import_react.default.useCallback(() => {
58
60
  if (!triggerRef.current || !popRef.current) return;
59
61
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -78,11 +80,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
78
80
  if (left < 8) left = 8;
79
81
  setPosition({
80
82
  position: { top, left },
81
- direction
83
+ direction,
84
+ repositioning: placedRef.current
82
85
  });
86
+ placedRef.current = true;
83
87
  }, [triggerRef, popRef]);
84
88
  import_react.default.useLayoutEffect(() => {
85
- if (!enabled) return;
89
+ if (!enabled) {
90
+ placedRef.current = false;
91
+ return;
92
+ }
86
93
  calculatePosition();
87
94
  const raf = requestAnimationFrame(calculatePosition);
88
95
  return () => cancelAnimationFrame(raf);
@@ -97,11 +104,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
97
104
  }, [calculatePosition, enabled, popRef]);
98
105
  import_react.default.useEffect(() => {
99
106
  if (!enabled) return;
100
- window.addEventListener("resize", calculatePosition);
101
- window.addEventListener("scroll", calculatePosition, true);
107
+ let raf = 0;
108
+ const onScrollOrResize = () => {
109
+ cancelAnimationFrame(raf);
110
+ raf = requestAnimationFrame(calculatePosition);
111
+ };
112
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
113
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
102
114
  return () => {
103
- window.removeEventListener("resize", calculatePosition);
104
- window.removeEventListener("scroll", calculatePosition, true);
115
+ cancelAnimationFrame(raf);
116
+ window.removeEventListener("resize", onScrollOrResize);
117
+ window.removeEventListener("scroll", onScrollOrResize, true);
105
118
  };
106
119
  }, [calculatePosition, enabled]);
107
120
  return position;
@@ -133,6 +146,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
133
146
  };
134
147
  var useClickOutside_default = useClickOutside;
135
148
 
149
+ // src/tokens/hooks/useOverlayKeyboard.ts
150
+ var import_react3 = __toESM(require("react"), 1);
151
+ var FOCUSABLE = [
152
+ "a[href]",
153
+ "button:not([disabled])",
154
+ "input:not([disabled]):not([type='hidden'])",
155
+ "select:not([disabled])",
156
+ "textarea:not([disabled])",
157
+ "[tabindex]:not([tabindex='-1'])"
158
+ ].join(",");
159
+ var useEscapeKey = (enabled, onEscape) => {
160
+ import_react3.default.useEffect(() => {
161
+ if (!enabled) return;
162
+ const onKey = (e) => {
163
+ if (e.key !== "Escape") return;
164
+ if (e.defaultPrevented) return;
165
+ e.preventDefault();
166
+ onEscape();
167
+ };
168
+ document.addEventListener("keydown", onKey);
169
+ return () => document.removeEventListener("keydown", onKey);
170
+ }, [enabled, onEscape]);
171
+ };
172
+
136
173
  // src/tokens/svg/action/CopyIcon.tsx
137
174
  var import_jsx_runtime = require("react/jsx-runtime");
138
175
 
@@ -1163,27 +1200,32 @@ var FieldMessage = (props) => {
1163
1200
  };
1164
1201
  FieldMessage.displayName = "FieldMessage";
1165
1202
  var Field = (props) => {
1166
- const { validations, children } = props;
1167
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1203
+ const { label, required = false, htmlFor, validations, children } = props;
1204
+ const hasMessages = !!validations && validations.length > 0;
1205
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1168
1206
  return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("div", { className: "lib-xplat-field", children: [
1207
+ label && /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
1208
+ label,
1209
+ required && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
1210
+ ] }),
1169
1211
  children,
1170
- /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1212
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1171
1213
  ] });
1172
1214
  };
1173
1215
  Field.displayName = "Field";
1174
1216
  var hasError = (validations) => !!validations?.some((v) => v.status === "error");
1175
1217
 
1176
1218
  // src/components/Select/context.ts
1177
- var import_react3 = __toESM(require("react"), 1);
1178
- var SelectContext = import_react3.default.createContext(null);
1219
+ var import_react4 = __toESM(require("react"), 1);
1220
+ var SelectContext = import_react4.default.createContext(null);
1179
1221
  var context_default = SelectContext;
1180
1222
 
1181
1223
  // src/components/Select/SelectItem.tsx
1182
- var import_react4 = __toESM(require("react"), 1);
1224
+ var import_react5 = __toESM(require("react"), 1);
1183
1225
  var import_jsx_runtime296 = require("react/jsx-runtime");
1184
1226
  var SelectItem = (props) => {
1185
1227
  const { children, value, onClick, disabled = false } = props;
1186
- const ctx = import_react4.default.useContext(context_default);
1228
+ const ctx = import_react5.default.useContext(context_default);
1187
1229
  const handleClick = (e) => {
1188
1230
  e.preventDefault();
1189
1231
  e.stopPropagation();
@@ -1223,32 +1265,37 @@ var SelectRoot = (props) => {
1223
1265
  onChange,
1224
1266
  children,
1225
1267
  disabled = false,
1268
+ searchable = false,
1269
+ searchPlaceholder = "\uAC80\uC0C9",
1226
1270
  error = false,
1227
1271
  validations,
1272
+ label,
1273
+ required,
1228
1274
  size = "md"
1229
1275
  } = props;
1230
- const itemChildren = import_react5.default.Children.toArray(children).filter(
1231
- (child) => import_react5.default.isValidElement(child) && child.type === SelectItem_default
1276
+ const fieldId = import_react6.default.useId();
1277
+ const itemChildren = import_react6.default.Children.toArray(children).filter(
1278
+ (child) => import_react6.default.isValidElement(child) && child.type === SelectItem_default
1232
1279
  );
1233
1280
  const isControlled = valueProp !== void 0;
1234
- const [isOpen, setOpen] = import_react5.default.useState(false);
1235
- const [uncontrolledLabel, setUncontrolledLabel] = import_react5.default.useState(null);
1236
- const [uncontrolledValue, setUncontrolledValue] = import_react5.default.useState(void 0);
1237
- const controlledLabel = import_react5.default.useMemo(() => {
1281
+ const [isOpen, setOpen] = import_react6.default.useState(false);
1282
+ const [uncontrolledLabel, setUncontrolledLabel] = import_react6.default.useState(null);
1283
+ const [uncontrolledValue, setUncontrolledValue] = import_react6.default.useState(void 0);
1284
+ const controlledLabel = import_react6.default.useMemo(() => {
1238
1285
  if (!isControlled) return null;
1239
1286
  const match = itemChildren.find((child) => child.props.value === valueProp);
1240
1287
  return match ? match.props.children : null;
1241
1288
  }, [isControlled, valueProp, itemChildren]);
1242
1289
  const selectedLabel = isControlled ? controlledLabel : uncontrolledLabel;
1243
1290
  const selectedValue = isControlled ? valueProp : uncontrolledValue;
1244
- const triggerRef = import_react5.default.useRef(null);
1245
- const contentRef = import_react5.default.useRef(null);
1246
- const [mounted, setMounted] = import_react5.default.useState(false);
1247
- const [visible, setVisible] = import_react5.default.useState(false);
1248
- import_react5.default.useEffect(() => {
1291
+ const triggerRef = import_react6.default.useRef(null);
1292
+ const contentRef = import_react6.default.useRef(null);
1293
+ const [mounted, setMounted] = import_react6.default.useState(false);
1294
+ const [visible, setVisible] = import_react6.default.useState(false);
1295
+ import_react6.default.useEffect(() => {
1249
1296
  if (disabled && isOpen) setOpen(false);
1250
1297
  }, [disabled, isOpen]);
1251
- import_react5.default.useEffect(() => {
1298
+ import_react6.default.useEffect(() => {
1252
1299
  if (isOpen) {
1253
1300
  setMounted(true);
1254
1301
  const t2 = setTimeout(() => setVisible(true), 1);
@@ -1258,22 +1305,42 @@ var SelectRoot = (props) => {
1258
1305
  const t = setTimeout(() => setMounted(false), ANIMATION_DURATION_MS);
1259
1306
  return () => clearTimeout(t);
1260
1307
  }, [isOpen]);
1261
- const open = import_react5.default.useCallback(() => setOpen(true), []);
1262
- const close = import_react5.default.useCallback(() => setOpen(false), []);
1263
- const toggle = import_react5.default.useCallback(() => setOpen((prev) => !prev), []);
1308
+ const [query, setQuery] = import_react6.default.useState("");
1309
+ const textOf = (node) => {
1310
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
1311
+ if (typeof node === "string" || typeof node === "number") return String(node);
1312
+ if (Array.isArray(node)) return node.map(textOf).join("");
1313
+ if (import_react6.default.isValidElement(node)) return textOf(node.props.children);
1314
+ return "";
1315
+ };
1316
+ const visibleItems = import_react6.default.useMemo(() => {
1317
+ if (!searchable || !query.trim()) return itemChildren;
1318
+ const q = query.trim().toLowerCase();
1319
+ return itemChildren.filter((c) => textOf(c.props.children).toLowerCase().includes(q));
1320
+ }, [searchable, query, itemChildren]);
1321
+ const open = import_react6.default.useCallback(() => setOpen(true), []);
1322
+ const close = import_react6.default.useCallback(() => {
1323
+ setOpen(false);
1324
+ setQuery("");
1325
+ }, []);
1326
+ const toggle = import_react6.default.useCallback(() => setOpen((prev) => !prev), []);
1264
1327
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1328
+ useEscapeKey(isOpen, () => {
1329
+ close();
1330
+ triggerRef.current?.focus({ preventScroll: true });
1331
+ });
1265
1332
  const position = useAutoPosition_default(triggerRef, contentRef, mounted);
1266
- const setSelected = import_react5.default.useCallback(
1267
- (label, itemValue) => {
1333
+ const setSelected = import_react6.default.useCallback(
1334
+ (label2, itemValue) => {
1268
1335
  if (!isControlled) {
1269
- setUncontrolledLabel(label);
1336
+ setUncontrolledLabel(label2);
1270
1337
  setUncontrolledValue(itemValue);
1271
1338
  }
1272
- onChange?.(itemValue, label);
1339
+ onChange?.(itemValue, label2);
1273
1340
  },
1274
1341
  [isControlled, onChange]
1275
1342
  );
1276
- const ctxValue = import_react5.default.useMemo(
1343
+ const ctxValue = import_react6.default.useMemo(
1277
1344
  () => ({
1278
1345
  isOpen,
1279
1346
  mounted,
@@ -1309,6 +1376,7 @@ var SelectRoot = (props) => {
1309
1376
  /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)(
1310
1377
  "div",
1311
1378
  {
1379
+ id: fieldId,
1312
1380
  ref: triggerRef,
1313
1381
  className: clsx_default(
1314
1382
  "select-trigger",
@@ -1351,20 +1419,33 @@ var SelectRoot = (props) => {
1351
1419
  ]
1352
1420
  }
1353
1421
  ),
1354
- mounted && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(
1422
+ mounted && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)(
1355
1423
  "div",
1356
1424
  {
1357
- className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
1425
+ className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass, position.repositioning && "repositioning"),
1358
1426
  ref: contentRef,
1359
1427
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
1360
1428
  role: "listbox",
1361
- children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(context_default.Provider, { value: ctxValue, children: itemChildren })
1429
+ children: [
1430
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)("div", { className: "select-search", children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(
1431
+ "input",
1432
+ {
1433
+ type: "text",
1434
+ value: query,
1435
+ placeholder: searchPlaceholder,
1436
+ onChange: (e) => setQuery(e.target.value),
1437
+ autoFocus: true,
1438
+ "aria-label": searchPlaceholder
1439
+ }
1440
+ ) }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(context_default.Provider, { value: ctxValue, children: visibleItems.length > 0 ? visibleItems : /* @__PURE__ */ (0, import_jsx_runtime297.jsx)("div", { className: "select-empty", children: "\uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" }) })
1442
+ ]
1362
1443
  }
1363
1444
  ) })
1364
1445
  ]
1365
1446
  }
1366
1447
  ) });
1367
- return /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Field, { validations, children: control });
1448
+ return /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Field, { validations, label, required, htmlFor: fieldId, children: control });
1368
1449
  };
1369
1450
  SelectRoot.displayName = "Select";
1370
1451
  var Select = Object.assign(SelectRoot, {