@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
@@ -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);
@@ -133,6 +133,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
133
133
  };
134
134
  var useClickOutside_default = useClickOutside;
135
135
 
136
+ // src/tokens/hooks/useOverlayKeyboard.ts
137
+ var import_react3 = __toESM(require("react"), 1);
138
+ var FOCUSABLE = [
139
+ "a[href]",
140
+ "button:not([disabled])",
141
+ "input:not([disabled]):not([type='hidden'])",
142
+ "select:not([disabled])",
143
+ "textarea:not([disabled])",
144
+ "[tabindex]:not([tabindex='-1'])"
145
+ ].join(",");
146
+ var useEscapeKey = (enabled, onEscape) => {
147
+ import_react3.default.useEffect(() => {
148
+ if (!enabled) return;
149
+ const onKey = (e) => {
150
+ if (e.key !== "Escape") return;
151
+ if (e.defaultPrevented) return;
152
+ e.preventDefault();
153
+ onEscape();
154
+ };
155
+ document.addEventListener("keydown", onKey);
156
+ return () => document.removeEventListener("keydown", onKey);
157
+ }, [enabled, onEscape]);
158
+ };
159
+
136
160
  // src/tokens/svg/action/CopyIcon.tsx
137
161
  var import_jsx_runtime = require("react/jsx-runtime");
138
162
 
@@ -1163,27 +1187,32 @@ var FieldMessage = (props) => {
1163
1187
  };
1164
1188
  FieldMessage.displayName = "FieldMessage";
1165
1189
  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 });
1190
+ const { label, required = false, htmlFor, validations, children } = props;
1191
+ const hasMessages = !!validations && validations.length > 0;
1192
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1168
1193
  return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("div", { className: "lib-xplat-field", children: [
1194
+ label && /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
1195
+ label,
1196
+ required && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
1197
+ ] }),
1169
1198
  children,
1170
- /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1199
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1171
1200
  ] });
1172
1201
  };
1173
1202
  Field.displayName = "Field";
1174
1203
  var hasError = (validations) => !!validations?.some((v) => v.status === "error");
1175
1204
 
1176
1205
  // src/components/Select/context.ts
1177
- var import_react3 = __toESM(require("react"), 1);
1178
- var SelectContext = import_react3.default.createContext(null);
1206
+ var import_react4 = __toESM(require("react"), 1);
1207
+ var SelectContext = import_react4.default.createContext(null);
1179
1208
  var context_default = SelectContext;
1180
1209
 
1181
1210
  // src/components/Select/SelectItem.tsx
1182
- var import_react4 = __toESM(require("react"), 1);
1211
+ var import_react5 = __toESM(require("react"), 1);
1183
1212
  var import_jsx_runtime296 = require("react/jsx-runtime");
1184
1213
  var SelectItem = (props) => {
1185
1214
  const { children, value, onClick, disabled = false } = props;
1186
- const ctx = import_react4.default.useContext(context_default);
1215
+ const ctx = import_react5.default.useContext(context_default);
1187
1216
  const handleClick = (e) => {
1188
1217
  e.preventDefault();
1189
1218
  e.stopPropagation();
@@ -1223,32 +1252,37 @@ var SelectRoot = (props) => {
1223
1252
  onChange,
1224
1253
  children,
1225
1254
  disabled = false,
1255
+ searchable = false,
1256
+ searchPlaceholder = "\uAC80\uC0C9",
1226
1257
  error = false,
1227
1258
  validations,
1259
+ label,
1260
+ required,
1228
1261
  size = "md"
1229
1262
  } = props;
1230
- const itemChildren = import_react5.default.Children.toArray(children).filter(
1231
- (child) => import_react5.default.isValidElement(child) && child.type === SelectItem_default
1263
+ const fieldId = import_react6.default.useId();
1264
+ const itemChildren = import_react6.default.Children.toArray(children).filter(
1265
+ (child) => import_react6.default.isValidElement(child) && child.type === SelectItem_default
1232
1266
  );
1233
1267
  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(() => {
1268
+ const [isOpen, setOpen] = import_react6.default.useState(false);
1269
+ const [uncontrolledLabel, setUncontrolledLabel] = import_react6.default.useState(null);
1270
+ const [uncontrolledValue, setUncontrolledValue] = import_react6.default.useState(void 0);
1271
+ const controlledLabel = import_react6.default.useMemo(() => {
1238
1272
  if (!isControlled) return null;
1239
1273
  const match = itemChildren.find((child) => child.props.value === valueProp);
1240
1274
  return match ? match.props.children : null;
1241
1275
  }, [isControlled, valueProp, itemChildren]);
1242
1276
  const selectedLabel = isControlled ? controlledLabel : uncontrolledLabel;
1243
1277
  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(() => {
1278
+ const triggerRef = import_react6.default.useRef(null);
1279
+ const contentRef = import_react6.default.useRef(null);
1280
+ const [mounted, setMounted] = import_react6.default.useState(false);
1281
+ const [visible, setVisible] = import_react6.default.useState(false);
1282
+ import_react6.default.useEffect(() => {
1249
1283
  if (disabled && isOpen) setOpen(false);
1250
1284
  }, [disabled, isOpen]);
1251
- import_react5.default.useEffect(() => {
1285
+ import_react6.default.useEffect(() => {
1252
1286
  if (isOpen) {
1253
1287
  setMounted(true);
1254
1288
  const t2 = setTimeout(() => setVisible(true), 1);
@@ -1258,22 +1292,42 @@ var SelectRoot = (props) => {
1258
1292
  const t = setTimeout(() => setMounted(false), ANIMATION_DURATION_MS);
1259
1293
  return () => clearTimeout(t);
1260
1294
  }, [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), []);
1295
+ const [query, setQuery] = import_react6.default.useState("");
1296
+ const textOf = (node) => {
1297
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
1298
+ if (typeof node === "string" || typeof node === "number") return String(node);
1299
+ if (Array.isArray(node)) return node.map(textOf).join("");
1300
+ if (import_react6.default.isValidElement(node)) return textOf(node.props.children);
1301
+ return "";
1302
+ };
1303
+ const visibleItems = import_react6.default.useMemo(() => {
1304
+ if (!searchable || !query.trim()) return itemChildren;
1305
+ const q = query.trim().toLowerCase();
1306
+ return itemChildren.filter((c) => textOf(c.props.children).toLowerCase().includes(q));
1307
+ }, [searchable, query, itemChildren]);
1308
+ const open = import_react6.default.useCallback(() => setOpen(true), []);
1309
+ const close = import_react6.default.useCallback(() => {
1310
+ setOpen(false);
1311
+ setQuery("");
1312
+ }, []);
1313
+ const toggle = import_react6.default.useCallback(() => setOpen((prev) => !prev), []);
1264
1314
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1315
+ useEscapeKey(isOpen, () => {
1316
+ close();
1317
+ triggerRef.current?.focus({ preventScroll: true });
1318
+ });
1265
1319
  const position = useAutoPosition_default(triggerRef, contentRef, mounted);
1266
- const setSelected = import_react5.default.useCallback(
1267
- (label, itemValue) => {
1320
+ const setSelected = import_react6.default.useCallback(
1321
+ (label2, itemValue) => {
1268
1322
  if (!isControlled) {
1269
- setUncontrolledLabel(label);
1323
+ setUncontrolledLabel(label2);
1270
1324
  setUncontrolledValue(itemValue);
1271
1325
  }
1272
- onChange?.(itemValue, label);
1326
+ onChange?.(itemValue, label2);
1273
1327
  },
1274
1328
  [isControlled, onChange]
1275
1329
  );
1276
- const ctxValue = import_react5.default.useMemo(
1330
+ const ctxValue = import_react6.default.useMemo(
1277
1331
  () => ({
1278
1332
  isOpen,
1279
1333
  mounted,
@@ -1309,6 +1363,7 @@ var SelectRoot = (props) => {
1309
1363
  /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)(
1310
1364
  "div",
1311
1365
  {
1366
+ id: fieldId,
1312
1367
  ref: triggerRef,
1313
1368
  className: clsx_default(
1314
1369
  "select-trigger",
@@ -1351,20 +1406,33 @@ var SelectRoot = (props) => {
1351
1406
  ]
1352
1407
  }
1353
1408
  ),
1354
- mounted && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(
1409
+ mounted && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)(
1355
1410
  "div",
1356
1411
  {
1357
1412
  className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
1358
1413
  ref: contentRef,
1359
1414
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
1360
1415
  role: "listbox",
1361
- children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(context_default.Provider, { value: ctxValue, children: itemChildren })
1416
+ children: [
1417
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)("div", { className: "select-search", children: /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(
1418
+ "input",
1419
+ {
1420
+ type: "text",
1421
+ value: query,
1422
+ placeholder: searchPlaceholder,
1423
+ onChange: (e) => setQuery(e.target.value),
1424
+ autoFocus: true,
1425
+ "aria-label": searchPlaceholder
1426
+ }
1427
+ ) }),
1428
+ /* @__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" }) })
1429
+ ]
1362
1430
  }
1363
1431
  ) })
1364
1432
  ]
1365
1433
  }
1366
1434
  ) });
1367
- return /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Field, { validations, children: control });
1435
+ return /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(Field, { validations, label, required, htmlFor: fieldId, children: control });
1368
1436
  };
1369
1437
  SelectRoot.displayName = "Select";
1370
1438
  var Select = Object.assign(SelectRoot, {
@@ -6,6 +6,21 @@
6
6
  flex-direction: column;
7
7
  gap: var(--spacing-space-2);
8
8
  }
9
+ .lib-xplat-field-label {
10
+ font-family: var(--semantic-typo-label-2-m-font);
11
+ font-size: var(--semantic-typo-label-2-m-size);
12
+ line-height: var(--semantic-typo-label-2-m-lh);
13
+ font-weight: var(--semantic-typo-label-2-m-weight);
14
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
15
+ color: var(--semantic-text-secondary);
16
+ display: inline-flex;
17
+ align-items: center;
18
+ gap: var(--spacing-space-1);
19
+ cursor: pointer;
20
+ }
21
+ .lib-xplat-field-label > .required {
22
+ color: var(--semantic-text-error);
23
+ }
9
24
  .lib-xplat-field-message-wrap {
10
25
  display: flex;
11
26
  gap: var(--spacing-space-2);
@@ -144,6 +159,45 @@
144
159
  .lib-xplat-select .select-trigger .select-trigger-icon svg {
145
160
  font-size: var(--semantic-typo-label-1-m-size);
146
161
  }
162
+ .lib-xplat-select-content .select-search {
163
+ position: sticky;
164
+ top: 0;
165
+ z-index: 1;
166
+ padding: var(--spacing-space-2);
167
+ background-color: var(--semantic-surface-neutral-primary);
168
+ border-bottom: 1px solid var(--semantic-border-secondary);
169
+ }
170
+ .lib-xplat-select-content .select-search > input {
171
+ width: 100%;
172
+ border: 1px solid var(--semantic-border-primary);
173
+ border-radius: var(--spacing-radius-sm);
174
+ padding: var(--spacing-space-1) var(--spacing-space-2);
175
+ outline: none;
176
+ color: var(--semantic-text-primary);
177
+ background-color: var(--semantic-surface-neutral-primary);
178
+ font-family: var(--semantic-typo-label-2-m-font);
179
+ font-size: var(--semantic-typo-label-2-m-size);
180
+ line-height: var(--semantic-typo-label-2-m-lh);
181
+ font-weight: var(--semantic-typo-label-2-m-weight);
182
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
183
+ transition: border-color 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94);
184
+ }
185
+ .lib-xplat-select-content .select-search > input:focus {
186
+ border-color: var(--semantic-interaction-focus-ring);
187
+ box-shadow: 0 0 0 2px var(--semantic-interaction-overlay-10);
188
+ outline: 2px solid transparent;
189
+ outline-offset: 2px;
190
+ }
191
+ .lib-xplat-select-content .select-empty {
192
+ padding: var(--spacing-space-3);
193
+ text-align: center;
194
+ color: var(--semantic-text-tertiary);
195
+ font-family: var(--semantic-typo-label-2-m-font);
196
+ font-size: var(--semantic-typo-label-2-m-size);
197
+ line-height: var(--semantic-typo-label-2-m-lh);
198
+ font-weight: var(--semantic-typo-label-2-m-weight);
199
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
200
+ }
147
201
  .lib-xplat-select-content {
148
202
  position: fixed;
149
203
  z-index: 1000;
@@ -8,6 +8,7 @@ interface SelectItemProps {
8
8
  value?: string | number;
9
9
  /** 클릭 시 추가로 실행할 핸들러 (선택 처리 후 호출) */
10
10
  onClick?: () => void;
11
+ /** 열리지 않게 막는다 */
11
12
  disabled?: boolean;
12
13
  }
13
14
  declare const SelectItem: {
@@ -18,7 +19,9 @@ declare const SelectItem: {
18
19
  /** Select.Item 으로만 만든 엘리먼트 (다른 컴포넌트/노드 넣으면 타입 오류) */
19
20
  type SelectItemElement = React.ReactElement<SelectItemProps, typeof SelectItem>;
20
21
  /** 값 변경 시 호출 (value: 아이템 value, label: 표시 텍스트) */
21
- type SelectChangeHandler = (value: string | number | undefined, label: React.ReactNode) => void;
22
+ type SelectChangeHandler = (
23
+ /** 선택된 값. 주면 controlled 가 된다 */
24
+ value: string | number | undefined, label: React.ReactNode) => void;
22
25
  type SelectSize = "sm" | "md" | "lg";
23
26
  interface SelectProps {
24
27
  /** 선택 전 표시할 문구 */
@@ -34,6 +37,18 @@ interface SelectProps {
34
37
  /** 에러 상태 */
35
38
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
36
39
  validations?: Validation[];
40
+ /** 메시지 없이 테두리만 붉게 한다. validations 와 함께 쓰면 둘 중 하나만 참이어도 적용된다 */
41
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
42
+ label?: React.ReactNode;
43
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
44
+ required?: boolean;
45
+ /**
46
+ * 목록 위에 검색칸을 띄운다. 항목이 많아 스크롤로 찾기 어려울 때 켠다.
47
+ * 항목의 children 텍스트로 걸러낸다
48
+ */
49
+ searchable?: boolean;
50
+ /** 검색칸 안내 문구 */
51
+ searchPlaceholder?: string;
37
52
  error?: boolean;
38
53
  /** 사이즈 */
39
54
  size?: SelectSize;
@@ -8,6 +8,7 @@ interface SelectItemProps {
8
8
  value?: string | number;
9
9
  /** 클릭 시 추가로 실행할 핸들러 (선택 처리 후 호출) */
10
10
  onClick?: () => void;
11
+ /** 열리지 않게 막는다 */
11
12
  disabled?: boolean;
12
13
  }
13
14
  declare const SelectItem: {
@@ -18,7 +19,9 @@ declare const SelectItem: {
18
19
  /** Select.Item 으로만 만든 엘리먼트 (다른 컴포넌트/노드 넣으면 타입 오류) */
19
20
  type SelectItemElement = React.ReactElement<SelectItemProps, typeof SelectItem>;
20
21
  /** 값 변경 시 호출 (value: 아이템 value, label: 표시 텍스트) */
21
- type SelectChangeHandler = (value: string | number | undefined, label: React.ReactNode) => void;
22
+ type SelectChangeHandler = (
23
+ /** 선택된 값. 주면 controlled 가 된다 */
24
+ value: string | number | undefined, label: React.ReactNode) => void;
22
25
  type SelectSize = "sm" | "md" | "lg";
23
26
  interface SelectProps {
24
27
  /** 선택 전 표시할 문구 */
@@ -34,6 +37,18 @@ interface SelectProps {
34
37
  /** 에러 상태 */
35
38
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
36
39
  validations?: Validation[];
40
+ /** 메시지 없이 테두리만 붉게 한다. validations 와 함께 쓰면 둘 중 하나만 참이어도 적용된다 */
41
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
42
+ label?: React.ReactNode;
43
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
44
+ required?: boolean;
45
+ /**
46
+ * 목록 위에 검색칸을 띄운다. 항목이 많아 스크롤로 찾기 어려울 때 켠다.
47
+ * 항목의 children 텍스트로 걸러낸다
48
+ */
49
+ searchable?: boolean;
50
+ /** 검색칸 안내 문구 */
51
+ searchPlaceholder?: string;
37
52
  error?: boolean;
38
53
  /** 사이즈 */
39
54
  size?: SelectSize;
@@ -1,5 +1,5 @@
1
1
  // src/components/Select/Select.tsx
2
- import React5 from "react";
2
+ import React6 from "react";
3
3
 
4
4
  // src/tokens/hooks/Portal.tsx
5
5
  import ReactDOM from "react-dom";
@@ -96,6 +96,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
96
96
  };
97
97
  var useClickOutside_default = useClickOutside;
98
98
 
99
+ // src/tokens/hooks/useOverlayKeyboard.ts
100
+ import React3 from "react";
101
+ var FOCUSABLE = [
102
+ "a[href]",
103
+ "button:not([disabled])",
104
+ "input:not([disabled]):not([type='hidden'])",
105
+ "select:not([disabled])",
106
+ "textarea:not([disabled])",
107
+ "[tabindex]:not([tabindex='-1'])"
108
+ ].join(",");
109
+ var useEscapeKey = (enabled, onEscape) => {
110
+ React3.useEffect(() => {
111
+ if (!enabled) return;
112
+ const onKey = (e) => {
113
+ if (e.key !== "Escape") return;
114
+ if (e.defaultPrevented) return;
115
+ e.preventDefault();
116
+ onEscape();
117
+ };
118
+ document.addEventListener("keydown", onKey);
119
+ return () => document.removeEventListener("keydown", onKey);
120
+ }, [enabled, onEscape]);
121
+ };
122
+
99
123
  // src/tokens/svg/action/CopyIcon.tsx
100
124
  import { jsx, jsxs } from "react/jsx-runtime";
101
125
 
@@ -1126,27 +1150,32 @@ var FieldMessage = (props) => {
1126
1150
  };
1127
1151
  FieldMessage.displayName = "FieldMessage";
1128
1152
  var Field = (props) => {
1129
- const { validations, children } = props;
1130
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx295(Fragment, { children });
1153
+ const { label, required = false, htmlFor, validations, children } = props;
1154
+ const hasMessages = !!validations && validations.length > 0;
1155
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx295(Fragment, { children });
1131
1156
  return /* @__PURE__ */ jsxs189("div", { className: "lib-xplat-field", children: [
1157
+ label && /* @__PURE__ */ jsxs189("label", { className: "lib-xplat-field-label", htmlFor, children: [
1158
+ label,
1159
+ required && /* @__PURE__ */ jsx295("span", { className: "required", "aria-hidden": "true", children: "*" })
1160
+ ] }),
1132
1161
  children,
1133
- /* @__PURE__ */ jsx295(FieldMessage, { validations })
1162
+ hasMessages && /* @__PURE__ */ jsx295(FieldMessage, { validations })
1134
1163
  ] });
1135
1164
  };
1136
1165
  Field.displayName = "Field";
1137
1166
  var hasError = (validations) => !!validations?.some((v) => v.status === "error");
1138
1167
 
1139
1168
  // src/components/Select/context.ts
1140
- import React3 from "react";
1141
- var SelectContext = React3.createContext(null);
1169
+ import React4 from "react";
1170
+ var SelectContext = React4.createContext(null);
1142
1171
  var context_default = SelectContext;
1143
1172
 
1144
1173
  // src/components/Select/SelectItem.tsx
1145
- import React4 from "react";
1174
+ import React5 from "react";
1146
1175
  import { jsx as jsx296 } from "react/jsx-runtime";
1147
1176
  var SelectItem = (props) => {
1148
1177
  const { children, value, onClick, disabled = false } = props;
1149
- const ctx = React4.useContext(context_default);
1178
+ const ctx = React5.useContext(context_default);
1150
1179
  const handleClick = (e) => {
1151
1180
  e.preventDefault();
1152
1181
  e.stopPropagation();
@@ -1186,32 +1215,37 @@ var SelectRoot = (props) => {
1186
1215
  onChange,
1187
1216
  children,
1188
1217
  disabled = false,
1218
+ searchable = false,
1219
+ searchPlaceholder = "\uAC80\uC0C9",
1189
1220
  error = false,
1190
1221
  validations,
1222
+ label,
1223
+ required,
1191
1224
  size = "md"
1192
1225
  } = props;
1193
- const itemChildren = React5.Children.toArray(children).filter(
1194
- (child) => React5.isValidElement(child) && child.type === SelectItem_default
1226
+ const fieldId = React6.useId();
1227
+ const itemChildren = React6.Children.toArray(children).filter(
1228
+ (child) => React6.isValidElement(child) && child.type === SelectItem_default
1195
1229
  );
1196
1230
  const isControlled = valueProp !== void 0;
1197
- const [isOpen, setOpen] = React5.useState(false);
1198
- const [uncontrolledLabel, setUncontrolledLabel] = React5.useState(null);
1199
- const [uncontrolledValue, setUncontrolledValue] = React5.useState(void 0);
1200
- const controlledLabel = React5.useMemo(() => {
1231
+ const [isOpen, setOpen] = React6.useState(false);
1232
+ const [uncontrolledLabel, setUncontrolledLabel] = React6.useState(null);
1233
+ const [uncontrolledValue, setUncontrolledValue] = React6.useState(void 0);
1234
+ const controlledLabel = React6.useMemo(() => {
1201
1235
  if (!isControlled) return null;
1202
1236
  const match = itemChildren.find((child) => child.props.value === valueProp);
1203
1237
  return match ? match.props.children : null;
1204
1238
  }, [isControlled, valueProp, itemChildren]);
1205
1239
  const selectedLabel = isControlled ? controlledLabel : uncontrolledLabel;
1206
1240
  const selectedValue = isControlled ? valueProp : uncontrolledValue;
1207
- const triggerRef = React5.useRef(null);
1208
- const contentRef = React5.useRef(null);
1209
- const [mounted, setMounted] = React5.useState(false);
1210
- const [visible, setVisible] = React5.useState(false);
1211
- React5.useEffect(() => {
1241
+ const triggerRef = React6.useRef(null);
1242
+ const contentRef = React6.useRef(null);
1243
+ const [mounted, setMounted] = React6.useState(false);
1244
+ const [visible, setVisible] = React6.useState(false);
1245
+ React6.useEffect(() => {
1212
1246
  if (disabled && isOpen) setOpen(false);
1213
1247
  }, [disabled, isOpen]);
1214
- React5.useEffect(() => {
1248
+ React6.useEffect(() => {
1215
1249
  if (isOpen) {
1216
1250
  setMounted(true);
1217
1251
  const t2 = setTimeout(() => setVisible(true), 1);
@@ -1221,22 +1255,42 @@ var SelectRoot = (props) => {
1221
1255
  const t = setTimeout(() => setMounted(false), ANIMATION_DURATION_MS);
1222
1256
  return () => clearTimeout(t);
1223
1257
  }, [isOpen]);
1224
- const open = React5.useCallback(() => setOpen(true), []);
1225
- const close = React5.useCallback(() => setOpen(false), []);
1226
- const toggle = React5.useCallback(() => setOpen((prev) => !prev), []);
1258
+ const [query, setQuery] = React6.useState("");
1259
+ const textOf = (node) => {
1260
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
1261
+ if (typeof node === "string" || typeof node === "number") return String(node);
1262
+ if (Array.isArray(node)) return node.map(textOf).join("");
1263
+ if (React6.isValidElement(node)) return textOf(node.props.children);
1264
+ return "";
1265
+ };
1266
+ const visibleItems = React6.useMemo(() => {
1267
+ if (!searchable || !query.trim()) return itemChildren;
1268
+ const q = query.trim().toLowerCase();
1269
+ return itemChildren.filter((c) => textOf(c.props.children).toLowerCase().includes(q));
1270
+ }, [searchable, query, itemChildren]);
1271
+ const open = React6.useCallback(() => setOpen(true), []);
1272
+ const close = React6.useCallback(() => {
1273
+ setOpen(false);
1274
+ setQuery("");
1275
+ }, []);
1276
+ const toggle = React6.useCallback(() => setOpen((prev) => !prev), []);
1227
1277
  useClickOutside_default([contentRef, triggerRef], close, isOpen);
1278
+ useEscapeKey(isOpen, () => {
1279
+ close();
1280
+ triggerRef.current?.focus({ preventScroll: true });
1281
+ });
1228
1282
  const position = useAutoPosition_default(triggerRef, contentRef, mounted);
1229
- const setSelected = React5.useCallback(
1230
- (label, itemValue) => {
1283
+ const setSelected = React6.useCallback(
1284
+ (label2, itemValue) => {
1231
1285
  if (!isControlled) {
1232
- setUncontrolledLabel(label);
1286
+ setUncontrolledLabel(label2);
1233
1287
  setUncontrolledValue(itemValue);
1234
1288
  }
1235
- onChange?.(itemValue, label);
1289
+ onChange?.(itemValue, label2);
1236
1290
  },
1237
1291
  [isControlled, onChange]
1238
1292
  );
1239
- const ctxValue = React5.useMemo(
1293
+ const ctxValue = React6.useMemo(
1240
1294
  () => ({
1241
1295
  isOpen,
1242
1296
  mounted,
@@ -1272,6 +1326,7 @@ var SelectRoot = (props) => {
1272
1326
  /* @__PURE__ */ jsxs190(
1273
1327
  "div",
1274
1328
  {
1329
+ id: fieldId,
1275
1330
  ref: triggerRef,
1276
1331
  className: clsx_default(
1277
1332
  "select-trigger",
@@ -1314,20 +1369,33 @@ var SelectRoot = (props) => {
1314
1369
  ]
1315
1370
  }
1316
1371
  ),
1317
- mounted && /* @__PURE__ */ jsx297(Portal_default, { children: /* @__PURE__ */ jsx297(
1372
+ mounted && /* @__PURE__ */ jsx297(Portal_default, { children: /* @__PURE__ */ jsxs190(
1318
1373
  "div",
1319
1374
  {
1320
1375
  className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
1321
1376
  ref: contentRef,
1322
1377
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
1323
1378
  role: "listbox",
1324
- children: /* @__PURE__ */ jsx297(context_default.Provider, { value: ctxValue, children: itemChildren })
1379
+ children: [
1380
+ searchable && /* @__PURE__ */ jsx297("div", { className: "select-search", children: /* @__PURE__ */ jsx297(
1381
+ "input",
1382
+ {
1383
+ type: "text",
1384
+ value: query,
1385
+ placeholder: searchPlaceholder,
1386
+ onChange: (e) => setQuery(e.target.value),
1387
+ autoFocus: true,
1388
+ "aria-label": searchPlaceholder
1389
+ }
1390
+ ) }),
1391
+ /* @__PURE__ */ jsx297(context_default.Provider, { value: ctxValue, children: visibleItems.length > 0 ? visibleItems : /* @__PURE__ */ jsx297("div", { className: "select-empty", children: "\uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" }) })
1392
+ ]
1325
1393
  }
1326
1394
  ) })
1327
1395
  ]
1328
1396
  }
1329
1397
  ) });
1330
- return /* @__PURE__ */ jsx297(Field, { validations, children: control });
1398
+ return /* @__PURE__ */ jsx297(Field, { validations, label, required, htmlFor: fieldId, children: control });
1331
1399
  };
1332
1400
  SelectRoot.displayName = "Select";
1333
1401
  var Select = Object.assign(SelectRoot, {
@@ -3,8 +3,11 @@ import React from 'react';
3
3
  type SkeletonVariant = "text" | "circular" | "rectangular";
4
4
  type SizeToken = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full";
5
5
  interface SkeletonProps {
6
+ /** 자리표시자 모양 */
6
7
  variant?: SkeletonVariant;
8
+ /** 가로 크기 */
7
9
  width?: SizeToken;
10
+ /** 세로 크기 */
8
11
  height?: SizeToken;
9
12
  }
10
13
  declare const Skeleton: React.FC<SkeletonProps>;
@@ -3,8 +3,11 @@ import React from 'react';
3
3
  type SkeletonVariant = "text" | "circular" | "rectangular";
4
4
  type SizeToken = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full";
5
5
  interface SkeletonProps {
6
+ /** 자리표시자 모양 */
6
7
  variant?: SkeletonVariant;
8
+ /** 가로 크기 */
7
9
  width?: SizeToken;
10
+ /** 세로 크기 */
8
11
  height?: SizeToken;
9
12
  }
10
13
  declare const Skeleton: React.FC<SkeletonProps>;
@@ -24,6 +24,9 @@ __export(Spinner_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(Spinner_exports);
26
26
 
27
+ // src/tokens/interface/status.ts
28
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
29
+
27
30
  // ../../node_modules/clsx/dist/clsx.mjs
28
31
  function r(e) {
29
32
  var t, f, n = "";
@@ -45,12 +48,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
45
48
  var Spinner = (props) => {
46
49
  const {
47
50
  size = "md",
48
- type = "brand"
51
+ type = "primary"
49
52
  } = props;
53
+ const statusType = normalizeStatus(type);
50
54
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
51
55
  "div",
52
56
  {
53
- className: clsx_default("lib-xplat-spinner", size, type),
57
+ className: clsx_default("lib-xplat-spinner", size, statusType),
54
58
  role: "status",
55
59
  "aria-label": "\uB85C\uB529 \uC911",
56
60
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
@@ -33,6 +33,7 @@
33
33
  stroke-dasharray: 42;
34
34
  stroke-dashoffset: 28;
35
35
  }
36
+ .lib-xplat-spinner.primary .indicator,
36
37
  .lib-xplat-spinner.brand .indicator {
37
38
  stroke: var(--semantic-surface-brand-default);
38
39
  }