@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
@@ -166,11 +166,16 @@ var FieldMessage = (props) => {
166
166
  FieldMessage.displayName = "FieldMessage";
167
167
  var FieldMessage_default = FieldMessage;
168
168
  var Field = (props) => {
169
- const { validations, children } = props;
170
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
169
+ const { label, required = false, htmlFor, validations, children } = props;
170
+ const hasMessages = !!validations && validations.length > 0;
171
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
171
172
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
173
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
174
+ label,
175
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
176
+ ] }),
172
177
  children,
173
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
178
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
174
179
  ] });
175
180
  };
176
181
  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);
@@ -24,11 +24,18 @@ declare const FieldMessage: {
24
24
  * 기존 사용처의 DOM 구조와 레이아웃이 바뀌지 않도록 하기 위해서다.
25
25
  * (폼 컨트롤들의 루트가 label·div·textarea 로 제각각이라 무조건 감싸면 깨진다.)
26
26
  */
27
+ interface FieldProps {
28
+ /** 컨트롤 위에 붙는 라벨. htmlFor 로 컨트롤과 연결된다 */
29
+ label?: React.ReactNode;
30
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
31
+ required?: boolean;
32
+ /** 라벨의 htmlFor 와 컨트롤의 id 를 잇는 값 */
33
+ htmlFor?: string;
34
+ validations?: Validation[];
35
+ children: React.ReactNode;
36
+ }
27
37
  declare const Field: {
28
- (props: {
29
- validations?: Validation[];
30
- children: React.ReactNode;
31
- }): react_jsx_runtime.JSX.Element;
38
+ (props: FieldProps): react_jsx_runtime.JSX.Element;
32
39
  displayName: string;
33
40
  };
34
41
  /** validations 중 하나라도 error 면 true. aria-invalid 판정에 쓴다 */
@@ -24,11 +24,18 @@ declare const FieldMessage: {
24
24
  * 기존 사용처의 DOM 구조와 레이아웃이 바뀌지 않도록 하기 위해서다.
25
25
  * (폼 컨트롤들의 루트가 label·div·textarea 로 제각각이라 무조건 감싸면 깨진다.)
26
26
  */
27
+ interface FieldProps {
28
+ /** 컨트롤 위에 붙는 라벨. htmlFor 로 컨트롤과 연결된다 */
29
+ label?: React.ReactNode;
30
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
31
+ required?: boolean;
32
+ /** 라벨의 htmlFor 와 컨트롤의 id 를 잇는 값 */
33
+ htmlFor?: string;
34
+ validations?: Validation[];
35
+ children: React.ReactNode;
36
+ }
27
37
  declare const Field: {
28
- (props: {
29
- validations?: Validation[];
30
- children: React.ReactNode;
31
- }): react_jsx_runtime.JSX.Element;
38
+ (props: FieldProps): react_jsx_runtime.JSX.Element;
32
39
  displayName: string;
33
40
  };
34
41
  /** validations 중 하나라도 error 면 true. aria-invalid 판정에 쓴다 */
@@ -138,11 +138,16 @@ var FieldMessage = (props) => {
138
138
  FieldMessage.displayName = "FieldMessage";
139
139
  var FieldMessage_default = FieldMessage;
140
140
  var Field = (props) => {
141
- const { validations, children } = props;
142
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx4(Fragment, { children });
141
+ const { label, required = false, htmlFor, validations, children } = props;
142
+ const hasMessages = !!validations && validations.length > 0;
143
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx4(Fragment, { children });
143
144
  return /* @__PURE__ */ jsxs3("div", { className: "lib-xplat-field", children: [
145
+ label && /* @__PURE__ */ jsxs3("label", { className: "lib-xplat-field-label", htmlFor, children: [
146
+ label,
147
+ required && /* @__PURE__ */ jsx4("span", { className: "required", "aria-hidden": "true", children: "*" })
148
+ ] }),
144
149
  children,
145
- /* @__PURE__ */ jsx4(FieldMessage, { validations })
150
+ hasMessages && /* @__PURE__ */ jsx4(FieldMessage, { validations })
146
151
  ] });
147
152
  };
148
153
  Field.displayName = "Field";
@@ -1076,11 +1076,16 @@ var FieldMessage = (props) => {
1076
1076
  };
1077
1077
  FieldMessage.displayName = "FieldMessage";
1078
1078
  var Field = (props) => {
1079
- const { validations, children } = props;
1080
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1079
+ const { label, required = false, htmlFor, validations, children } = props;
1080
+ const hasMessages = !!validations && validations.length > 0;
1081
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1081
1082
  return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("div", { className: "lib-xplat-field", children: [
1083
+ label && /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
1084
+ label,
1085
+ required && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
1086
+ ] }),
1082
1087
  children,
1083
- /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1088
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1084
1089
  ] });
1085
1090
  };
1086
1091
  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);
@@ -1040,11 +1040,16 @@ var FieldMessage = (props) => {
1040
1040
  };
1041
1041
  FieldMessage.displayName = "FieldMessage";
1042
1042
  var Field = (props) => {
1043
- const { validations, children } = props;
1044
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx295(Fragment, { children });
1043
+ const { label, required = false, htmlFor, validations, children } = props;
1044
+ const hasMessages = !!validations && validations.length > 0;
1045
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx295(Fragment, { children });
1045
1046
  return /* @__PURE__ */ jsxs189("div", { className: "lib-xplat-field", children: [
1047
+ label && /* @__PURE__ */ jsxs189("label", { className: "lib-xplat-field-label", htmlFor, children: [
1048
+ label,
1049
+ required && /* @__PURE__ */ jsx295("span", { className: "required", "aria-hidden": "true", children: "*" })
1050
+ ] }),
1046
1051
  children,
1047
- /* @__PURE__ */ jsx295(FieldMessage, { validations })
1052
+ hasMessages && /* @__PURE__ */ jsx295(FieldMessage, { validations })
1048
1053
  ] });
1049
1054
  };
1050
1055
  Field.displayName = "Field";
@@ -1143,11 +1143,16 @@ var FieldMessage = (props) => {
1143
1143
  };
1144
1144
  FieldMessage.displayName = "FieldMessage";
1145
1145
  var Field = (props) => {
1146
- const { validations, children } = props;
1147
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1146
+ const { label, required = false, htmlFor, validations, children } = props;
1147
+ const hasMessages = !!validations && validations.length > 0;
1148
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(import_jsx_runtime295.Fragment, { children });
1148
1149
  return /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("div", { className: "lib-xplat-field", children: [
1150
+ label && /* @__PURE__ */ (0, import_jsx_runtime295.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
1151
+ label,
1152
+ required && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
1153
+ ] }),
1149
1154
  children,
1150
- /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1155
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime295.jsx)(FieldMessage, { validations })
1151
1156
  ] });
1152
1157
  };
1153
1158
  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);
@@ -1107,11 +1107,16 @@ var FieldMessage = (props) => {
1107
1107
  };
1108
1108
  FieldMessage.displayName = "FieldMessage";
1109
1109
  var Field = (props) => {
1110
- const { validations, children } = props;
1111
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx295(Fragment, { children });
1110
+ const { label, required = false, htmlFor, validations, children } = props;
1111
+ const hasMessages = !!validations && validations.length > 0;
1112
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx295(Fragment, { children });
1112
1113
  return /* @__PURE__ */ jsxs189("div", { className: "lib-xplat-field", children: [
1114
+ label && /* @__PURE__ */ jsxs189("label", { className: "lib-xplat-field-label", htmlFor, children: [
1115
+ label,
1116
+ required && /* @__PURE__ */ jsx295("span", { className: "required", "aria-hidden": "true", children: "*" })
1117
+ ] }),
1113
1118
  children,
1114
- /* @__PURE__ */ jsx295(FieldMessage, { validations })
1119
+ hasMessages && /* @__PURE__ */ jsx295(FieldMessage, { validations })
1115
1120
  ] });
1116
1121
  };
1117
1122
  Field.displayName = "Field";
@@ -178,11 +178,16 @@ var FieldMessage = (props) => {
178
178
  FieldMessage.displayName = "FieldMessage";
179
179
  var FieldMessage_default = FieldMessage;
180
180
  var Field = (props) => {
181
- const { validations, children } = props;
182
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
181
+ const { label, required = false, htmlFor, validations, children } = props;
182
+ const hasMessages = !!validations && validations.length > 0;
183
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
183
184
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
185
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
186
+ label,
187
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
188
+ ] }),
184
189
  children,
185
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
190
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
186
191
  ] });
187
192
  };
188
193
  Field.displayName = "Field";
@@ -281,8 +286,12 @@ var Input = import_react.default.forwardRef((props, ref) => {
281
286
  suffix,
282
287
  disabled,
283
288
  validations,
289
+ label,
290
+ required,
284
291
  ...inputProps
285
292
  } = props;
293
+ const autoId = import_react.default.useId();
294
+ const fieldId = inputProps.id ?? autoId;
286
295
  const localValue = formatValue(type, value);
287
296
  const handleChange = (e) => {
288
297
  let val = e.target.value;
@@ -298,7 +307,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
298
307
  onChange(event);
299
308
  }
300
309
  };
301
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
310
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { label, required, htmlFor: fieldId, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: clsx_default("lib-xplat-input-wrap", className), children: [
302
311
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
303
312
  "div",
304
313
  {
@@ -309,6 +318,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
309
318
  {
310
319
  ...inputProps,
311
320
  ref,
321
+ id: fieldId,
312
322
  disabled,
313
323
  type: type === "number" ? "text" : type,
314
324
  value: localValue,
@@ -321,7 +331,7 @@ var Input = import_react.default.forwardRef((props, ref) => {
321
331
  }
322
332
  ),
323
333
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FieldMessage_default, { validations })
324
- ] });
334
+ ] }) });
325
335
  });
326
336
  Input.displayName = "Input";
327
337
  var Input_default = Input;
@@ -4,6 +4,21 @@
4
4
  flex-direction: column;
5
5
  gap: var(--spacing-space-2);
6
6
  }
7
+ .lib-xplat-field-label {
8
+ font-family: var(--semantic-typo-label-2-m-font);
9
+ font-size: var(--semantic-typo-label-2-m-size);
10
+ line-height: var(--semantic-typo-label-2-m-lh);
11
+ font-weight: var(--semantic-typo-label-2-m-weight);
12
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
13
+ color: var(--semantic-text-secondary);
14
+ display: inline-flex;
15
+ align-items: center;
16
+ gap: var(--spacing-space-1);
17
+ cursor: pointer;
18
+ }
19
+ .lib-xplat-field-label > .required {
20
+ color: var(--semantic-text-error);
21
+ }
7
22
  .lib-xplat-field-message-wrap {
8
23
  display: flex;
9
24
  gap: var(--spacing-space-2);
@@ -9,6 +9,10 @@ interface InputProps extends Omit<InputAttributes, "size"> {
9
9
  /** 높이와 글자 크기 */
10
10
  size?: InputSize$1;
11
11
  validations?: Validation[];
12
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 입력칸에 포커스가 간다 */
13
+ label?: React.ReactNode;
14
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
15
+ required?: boolean;
12
16
  /** 입력칸 오른쪽에 붙일 요소 (단위·아이콘 등) */
13
17
  suffix?: React.ReactNode;
14
18
  }
@@ -9,6 +9,10 @@ interface InputProps extends Omit<InputAttributes, "size"> {
9
9
  /** 높이와 글자 크기 */
10
10
  size?: InputSize$1;
11
11
  validations?: Validation[];
12
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 입력칸에 포커스가 간다 */
13
+ label?: React.ReactNode;
14
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
15
+ required?: boolean;
12
16
  /** 입력칸 오른쪽에 붙일 요소 (단위·아이콘 등) */
13
17
  suffix?: React.ReactNode;
14
18
  }
@@ -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 의 상태 색 */