@x-plat/design-system 0.10.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 (110) 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 +35 -7
  24. package/dist/components/Dropdown/index.js +35 -7
  25. package/dist/components/Editor/index.cjs +178 -21
  26. package/dist/components/Editor/index.js +178 -21
  27. package/dist/components/FieldMessage/index.cjs +8 -3
  28. package/dist/components/FieldMessage/index.css +15 -0
  29. package/dist/components/FieldMessage/index.d.cts +11 -4
  30. package/dist/components/FieldMessage/index.d.ts +11 -4
  31. package/dist/components/FieldMessage/index.js +8 -3
  32. package/dist/components/FileUpload/index.cjs +8 -3
  33. package/dist/components/FileUpload/index.css +15 -0
  34. package/dist/components/FileUpload/index.js +8 -3
  35. package/dist/components/ImageSelector/index.cjs +8 -3
  36. package/dist/components/ImageSelector/index.css +15 -0
  37. package/dist/components/ImageSelector/index.js +8 -3
  38. package/dist/components/Input/index.cjs +15 -5
  39. package/dist/components/Input/index.css +15 -0
  40. package/dist/components/Input/index.d.cts +4 -0
  41. package/dist/components/Input/index.d.ts +4 -0
  42. package/dist/components/Input/index.js +15 -5
  43. package/dist/components/Modal/index.cjs +84 -5
  44. package/dist/components/Modal/index.js +84 -5
  45. package/dist/components/NotificationBadge/index.d.cts +1 -1
  46. package/dist/components/NotificationBadge/index.d.ts +1 -1
  47. package/dist/components/Pagination/index.d.cts +1 -1
  48. package/dist/components/Pagination/index.d.ts +1 -1
  49. package/dist/components/PopOver/index.cjs +34 -6
  50. package/dist/components/PopOver/index.js +34 -6
  51. package/dist/components/Progress/index.d.cts +1 -1
  52. package/dist/components/Progress/index.d.ts +1 -1
  53. package/dist/components/Radio/index.cjs +8 -3
  54. package/dist/components/Radio/index.css +15 -0
  55. package/dist/components/Radio/index.d.cts +1 -1
  56. package/dist/components/Radio/index.d.ts +1 -1
  57. package/dist/components/Radio/index.js +8 -3
  58. package/dist/components/Select/index.cjs +99 -31
  59. package/dist/components/Select/index.css +54 -0
  60. package/dist/components/Select/index.d.cts +11 -0
  61. package/dist/components/Select/index.d.ts +11 -0
  62. package/dist/components/Select/index.js +99 -31
  63. package/dist/components/Spinner/index.d.cts +1 -1
  64. package/dist/components/Spinner/index.d.ts +1 -1
  65. package/dist/components/Stat/index.cjs +84 -0
  66. package/dist/components/Stat/index.css +96 -0
  67. package/dist/components/Stat/index.d.cts +37 -0
  68. package/dist/components/Stat/index.d.ts +37 -0
  69. package/dist/components/Stat/index.js +57 -0
  70. package/dist/components/Steps/index.d.cts +1 -1
  71. package/dist/components/Steps/index.d.ts +1 -1
  72. package/dist/components/Switch/index.cjs +14 -5
  73. package/dist/components/Switch/index.css +15 -0
  74. package/dist/components/Switch/index.d.cts +6 -1
  75. package/dist/components/Switch/index.d.ts +6 -1
  76. package/dist/components/Switch/index.js +14 -5
  77. package/dist/components/Tab/index.cjs +49 -25
  78. package/dist/components/Tab/index.css +4 -0
  79. package/dist/components/Tab/index.js +49 -25
  80. package/dist/components/TextArea/index.cjs +14 -4
  81. package/dist/components/TextArea/index.css +15 -0
  82. package/dist/components/TextArea/index.d.cts +4 -0
  83. package/dist/components/TextArea/index.d.ts +4 -0
  84. package/dist/components/TextArea/index.js +14 -4
  85. package/dist/components/TimePicker/index.cjs +448 -0
  86. package/dist/components/TimePicker/index.css +172 -0
  87. package/dist/components/TimePicker/index.d.cts +42 -0
  88. package/dist/components/TimePicker/index.d.ts +42 -0
  89. package/dist/components/TimePicker/index.js +411 -0
  90. package/dist/components/index.cjs +873 -359
  91. package/dist/components/index.css +275 -0
  92. package/dist/components/index.d.cts +3 -1
  93. package/dist/components/index.d.ts +3 -1
  94. package/dist/components/index.js +871 -359
  95. package/dist/index.cjs +897 -383
  96. package/dist/index.css +275 -0
  97. package/dist/index.d.cts +3 -1
  98. package/dist/index.d.ts +3 -1
  99. package/dist/index.js +895 -383
  100. package/dist/status-njWqQWFV.d.cts +20 -0
  101. package/dist/status-njWqQWFV.d.ts +20 -0
  102. package/guidelines/AGENT_PROMPT.md +3 -3
  103. package/guidelines/Guidelines.md +3 -0
  104. package/guidelines/components/editor.md +86 -0
  105. package/guidelines/components/form.md +19 -0
  106. package/guidelines/components/stat.md +31 -0
  107. package/guidelines/components/time-picker.md +49 -0
  108. package/package.json +1 -1
  109. package/dist/status-g_KP3qT6.d.cts +0 -8
  110. package/dist/status-g_KP3qT6.d.ts +0 -8
@@ -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",
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.cjs';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
4
4
 
5
5
  /** NotificationBadge 의 상태 색 */
6
6
  type NotificationBadgeType = "primary" | "success" | "error" | "warning" | "info";
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.js';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
4
4
 
5
5
  /** NotificationBadge 의 상태 색 */
6
6
  type NotificationBadgeType = "primary" | "success" | "error" | "warning" | "info";
@@ -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 PaginationSize = "sm" | "md" | "lg";
5
5
  /** Pagination 의 상태 색 */
@@ -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);
@@ -132,6 +132,30 @@ var useClickOutside = (refs, handler, enabled = true) => {
132
132
  };
133
133
  var useClickOutside_default = useClickOutside;
134
134
 
135
+ // src/tokens/hooks/useOverlayKeyboard.ts
136
+ var import_react3 = __toESM(require("react"), 1);
137
+ var FOCUSABLE = [
138
+ "a[href]",
139
+ "button:not([disabled])",
140
+ "input:not([disabled]):not([type='hidden'])",
141
+ "select:not([disabled])",
142
+ "textarea:not([disabled])",
143
+ "[tabindex]:not([tabindex='-1'])"
144
+ ].join(",");
145
+ var useEscapeKey = (enabled, onEscape) => {
146
+ import_react3.default.useEffect(() => {
147
+ if (!enabled) return;
148
+ const onKey = (e) => {
149
+ if (e.key !== "Escape") return;
150
+ if (e.defaultPrevented) return;
151
+ e.preventDefault();
152
+ onEscape();
153
+ };
154
+ document.addEventListener("keydown", onKey);
155
+ return () => document.removeEventListener("keydown", onKey);
156
+ }, [enabled, onEscape]);
157
+ };
158
+
135
159
  // ../../node_modules/clsx/dist/clsx.mjs
136
160
  function r(e) {
137
161
  var t, f, n = "";
@@ -152,13 +176,17 @@ var clsx_default = clsx;
152
176
  var import_jsx_runtime = require("react/jsx-runtime");
153
177
  var PopOver = (props) => {
154
178
  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);
179
+ const popRef = import_react4.default.useRef(null);
180
+ const triggerRef = import_react4.default.useRef(null);
181
+ const [localOpen, setLocalOpen] = import_react4.default.useState(false);
182
+ const [eventTrigger, setEventTrigger] = import_react4.default.useState(false);
159
183
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
184
+ useEscapeKey(isOpen, () => {
185
+ onClose();
186
+ triggerRef.current?.focus({ preventScroll: true });
187
+ });
160
188
  const position = useAutoPosition_default(triggerRef, popRef, localOpen);
161
- import_react3.default.useEffect(() => {
189
+ import_react4.default.useEffect(() => {
162
190
  if (isOpen) {
163
191
  setLocalOpen(isOpen);
164
192
  setTimeout(() => {
@@ -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";
@@ -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
  // ../../node_modules/clsx/dist/clsx.mjs
100
124
  function r(e) {
101
125
  var t, f, n = "";
@@ -116,13 +140,17 @@ var clsx_default = clsx;
116
140
  import { jsx, jsxs } from "react/jsx-runtime";
117
141
  var PopOver = (props) => {
118
142
  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);
143
+ const popRef = React4.useRef(null);
144
+ const triggerRef = React4.useRef(null);
145
+ const [localOpen, setLocalOpen] = React4.useState(false);
146
+ const [eventTrigger, setEventTrigger] = React4.useState(false);
123
147
  useClickOutside_default([popRef, triggerRef], onClose, isOpen);
148
+ useEscapeKey(isOpen, () => {
149
+ onClose();
150
+ triggerRef.current?.focus({ preventScroll: true });
151
+ });
124
152
  const position = useAutoPosition_default(triggerRef, popRef, localOpen);
125
- React3.useEffect(() => {
153
+ React4.useEffect(() => {
126
154
  if (isOpen) {
127
155
  setLocalOpen(isOpen);
128
156
  setTimeout(() => {
@@ -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";