@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
@@ -0,0 +1,57 @@
1
+ // src/tokens/interface/status.ts
2
+ var normalizeStatus = (value) => value === "brand" ? "primary" : value;
3
+
4
+ // ../../node_modules/clsx/dist/clsx.mjs
5
+ function r(e) {
6
+ var t, f, n = "";
7
+ if ("string" == typeof e || "number" == typeof e) n += e;
8
+ else if ("object" == typeof e) if (Array.isArray(e)) {
9
+ var o = e.length;
10
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
11
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
12
+ return n;
13
+ }
14
+ function clsx() {
15
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
16
+ return n;
17
+ }
18
+ var clsx_default = clsx;
19
+
20
+ // src/components/Stat/Stat.tsx
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ var ARROW = {
23
+ up: "\u25B2",
24
+ down: "\u25BC",
25
+ flat: "\u2014"
26
+ };
27
+ var Stat = (props) => {
28
+ const {
29
+ label,
30
+ value,
31
+ unit,
32
+ description,
33
+ trend,
34
+ icon,
35
+ size = "md",
36
+ type = "primary"
37
+ } = props;
38
+ const statusType = normalizeStatus(type);
39
+ return /* @__PURE__ */ jsxs("div", { className: clsx_default("lib-xplat-stat", size, statusType), children: [
40
+ /* @__PURE__ */ jsx("div", { className: "stat-label", children: label }),
41
+ /* @__PURE__ */ jsxs("div", { className: "stat-value-row", children: [
42
+ icon && /* @__PURE__ */ jsx("span", { className: "stat-icon", children: icon }),
43
+ /* @__PURE__ */ jsx("span", { className: "stat-value", children: value }),
44
+ unit && /* @__PURE__ */ jsx("span", { className: "stat-unit", children: unit }),
45
+ trend && /* @__PURE__ */ jsxs("span", { className: clsx_default("stat-trend", trend.direction), children: [
46
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: ARROW[trend.direction] }),
47
+ trend.value
48
+ ] })
49
+ ] }),
50
+ description && /* @__PURE__ */ jsx("div", { className: "stat-description", children: description })
51
+ ] });
52
+ };
53
+ Stat.displayName = "Stat";
54
+ var Stat_default = Stat;
55
+ export {
56
+ Stat_default as Stat
57
+ };
@@ -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
  interface StepItem {
5
5
  title: string;
@@ -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
  interface StepItem {
5
5
  title: string;
@@ -179,11 +179,16 @@ var FieldMessage = (props) => {
179
179
  };
180
180
  FieldMessage.displayName = "FieldMessage";
181
181
  var Field = (props) => {
182
- const { validations, children } = props;
183
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
182
+ const { label, required = false, htmlFor, validations, children } = props;
183
+ const hasMessages = !!validations && validations.length > 0;
184
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
184
185
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
186
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
187
+ label,
188
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
189
+ ] }),
185
190
  children,
186
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
191
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
187
192
  ] });
188
193
  };
189
194
  Field.displayName = "Field";
@@ -199,8 +204,11 @@ var Switch = (props) => {
199
204
  type = "primary",
200
205
  disabled,
201
206
  onChange,
202
- validations
207
+ validations,
208
+ label,
209
+ required
203
210
  } = props;
211
+ const fieldId = import_react.default.useId();
204
212
  const statusType = normalizeStatus(type);
205
213
  const [isAnimating, setIsAnimating] = import_react.default.useState(false);
206
214
  const timeoutRef = import_react.default.useRef(null);
@@ -222,6 +230,7 @@ var Switch = (props) => {
222
230
  const control = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
223
231
  "div",
224
232
  {
233
+ id: fieldId,
225
234
  className: clsx_default(
226
235
  "lib-xplat-switch",
227
236
  size,
@@ -236,7 +245,7 @@ var Switch = (props) => {
236
245
  children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: clsx_default("knob", value ? "checked" : void 0) })
237
246
  }
238
247
  );
239
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { validations, children: control });
248
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { validations, label, required, htmlFor: fieldId, children: control });
240
249
  };
241
250
  Switch.displayName = "Switch";
242
251
  var Switch_default = Switch;
@@ -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,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.cjs';
2
+ import React from 'react';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.cjs';
3
4
  import { V as Validation } from '../../validation-Dqo1zqhT.cjs';
4
5
 
5
6
  type SwitchSize = "sm" | "md" | "lg";
@@ -13,6 +14,10 @@ interface SwitchProps {
13
14
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
14
15
  validations?: Validation[];
15
16
  /** 스위치 크기 */
17
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
18
+ label?: React.ReactNode;
19
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
20
+ required?: boolean;
16
21
  size?: SwitchSize;
17
22
  /** 켜졌을 때의 색 */
18
23
  type?: SwitchTypeInput;
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as LegacyStatusType } from '../../status-g_KP3qT6.js';
2
+ import React from 'react';
3
+ import { L as LegacyStatusType } from '../../status-njWqQWFV.js';
3
4
  import { V as Validation } from '../../validation-Dqo1zqhT.js';
4
5
 
5
6
  type SwitchSize = "sm" | "md" | "lg";
@@ -13,6 +14,10 @@ interface SwitchProps {
13
14
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
14
15
  validations?: Validation[];
15
16
  /** 스위치 크기 */
17
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
18
+ label?: React.ReactNode;
19
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
20
+ required?: boolean;
16
21
  size?: SwitchSize;
17
22
  /** 켜졌을 때의 색 */
18
23
  type?: SwitchTypeInput;
@@ -143,11 +143,16 @@ var FieldMessage = (props) => {
143
143
  };
144
144
  FieldMessage.displayName = "FieldMessage";
145
145
  var Field = (props) => {
146
- const { validations, children } = props;
147
- if (!validations || validations.length === 0) return /* @__PURE__ */ jsx4(Fragment, { children });
146
+ const { label, required = false, htmlFor, validations, children } = props;
147
+ const hasMessages = !!validations && validations.length > 0;
148
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx4(Fragment, { children });
148
149
  return /* @__PURE__ */ jsxs3("div", { className: "lib-xplat-field", children: [
150
+ label && /* @__PURE__ */ jsxs3("label", { className: "lib-xplat-field-label", htmlFor, children: [
151
+ label,
152
+ required && /* @__PURE__ */ jsx4("span", { className: "required", "aria-hidden": "true", children: "*" })
153
+ ] }),
149
154
  children,
150
- /* @__PURE__ */ jsx4(FieldMessage, { validations })
155
+ hasMessages && /* @__PURE__ */ jsx4(FieldMessage, { validations })
151
156
  ] });
152
157
  };
153
158
  Field.displayName = "Field";
@@ -163,8 +168,11 @@ var Switch = (props) => {
163
168
  type = "primary",
164
169
  disabled,
165
170
  onChange,
166
- validations
171
+ validations,
172
+ label,
173
+ required
167
174
  } = props;
175
+ const fieldId = React.useId();
168
176
  const statusType = normalizeStatus(type);
169
177
  const [isAnimating, setIsAnimating] = React.useState(false);
170
178
  const timeoutRef = React.useRef(null);
@@ -186,6 +194,7 @@ var Switch = (props) => {
186
194
  const control = /* @__PURE__ */ jsx5(
187
195
  "div",
188
196
  {
197
+ id: fieldId,
189
198
  className: clsx_default(
190
199
  "lib-xplat-switch",
191
200
  size,
@@ -200,7 +209,7 @@ var Switch = (props) => {
200
209
  children: /* @__PURE__ */ jsx5("div", { className: clsx_default("knob", value ? "checked" : void 0) })
201
210
  }
202
211
  );
203
- return /* @__PURE__ */ jsx5(Field, { validations, children: control });
212
+ return /* @__PURE__ */ jsx5(Field, { validations, label, required, htmlFor: fieldId, children: control });
204
213
  };
205
214
  Switch.displayName = "Switch";
206
215
  var Switch_default = Switch;
@@ -59,9 +59,13 @@ var import_jsx_runtime = require("react/jsx-runtime");
59
59
  var TabItem = import_react.default.forwardRef((props, ref) => {
60
60
  const { isActive, title, onClick } = props;
61
61
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
62
- "div",
62
+ "button",
63
63
  {
64
64
  ref,
65
+ type: "button",
66
+ role: "tab",
67
+ "aria-selected": isActive,
68
+ tabIndex: isActive ? 0 : -1,
65
69
  className: clsx_default("tab-item", isActive ? "active" : null),
66
70
  onClick,
67
71
  children: title
@@ -81,6 +85,18 @@ var Tab = (props) => {
81
85
  width: 0
82
86
  });
83
87
  const itemRefs = import_react2.default.useRef([]);
88
+ const handleKeyDown = (e) => {
89
+ const last = tabs.length - 1;
90
+ let next = null;
91
+ if (e.key === "ArrowRight") next = activeIndex >= last ? 0 : activeIndex + 1;
92
+ else if (e.key === "ArrowLeft") next = activeIndex <= 0 ? last : activeIndex - 1;
93
+ else if (e.key === "Home") next = 0;
94
+ else if (e.key === "End") next = last;
95
+ if (next === null) return;
96
+ e.preventDefault();
97
+ onChange(tabs[next], next);
98
+ itemRefs.current[next]?.focus();
99
+ };
84
100
  const handleChangeActiveTab = (tabItem, tabIdx) => {
85
101
  onChange(tabItem, tabIdx);
86
102
  };
@@ -90,30 +106,38 @@ var Tab = (props) => {
90
106
  setUnderlineStyle({ left: el.offsetLeft, width: el.offsetWidth });
91
107
  }
92
108
  }, [activeIndex, tabs.length]);
93
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: clsx_default("lib-xplat-tab", `type-${shape}`, size), children: [
94
- tabs.map((tab, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
95
- TabItem_default,
96
- {
97
- onClick: () => handleChangeActiveTab(tab, idx),
98
- isActive: activeIndex === idx,
99
- ref: (el) => {
100
- itemRefs.current[idx] = el;
101
- },
102
- title: tab.title
103
- },
104
- `${tab.value}_${idx}`
105
- )),
106
- shape === "toggle" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
107
- "div",
108
- {
109
- className: "tab-toggle-underline",
110
- style: {
111
- left: underlineStyle.left,
112
- width: underlineStyle.width
113
- }
114
- }
115
- )
116
- ] });
109
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
110
+ "div",
111
+ {
112
+ className: clsx_default("lib-xplat-tab", `type-${shape}`, size),
113
+ role: "tablist",
114
+ onKeyDown: handleKeyDown,
115
+ children: [
116
+ tabs.map((tab, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
117
+ TabItem_default,
118
+ {
119
+ onClick: () => handleChangeActiveTab(tab, idx),
120
+ isActive: activeIndex === idx,
121
+ ref: (el) => {
122
+ itemRefs.current[idx] = el;
123
+ },
124
+ title: tab.title
125
+ },
126
+ `${tab.value}_${idx}`
127
+ )),
128
+ shape === "toggle" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
129
+ "div",
130
+ {
131
+ className: "tab-toggle-underline",
132
+ style: {
133
+ left: underlineStyle.left,
134
+ width: underlineStyle.width
135
+ }
136
+ }
137
+ )
138
+ ]
139
+ }
140
+ );
117
141
  };
118
142
  Tab.displayName = "Tab";
119
143
  var Tab_default = Tab;
@@ -42,6 +42,10 @@
42
42
  border-radius: var(--spacing-radius-md);
43
43
  }
44
44
  .lib-xplat-tab.type-default > .tab-item {
45
+ appearance: none;
46
+ font: inherit;
47
+ border: none;
48
+ background: none;
45
49
  flex: 1;
46
50
  padding: var(--spacing-space-1) var(--spacing-space-6);
47
51
  height: 100%;
@@ -23,9 +23,13 @@ import { jsx } from "react/jsx-runtime";
23
23
  var TabItem = React.forwardRef((props, ref) => {
24
24
  const { isActive, title, onClick } = props;
25
25
  return /* @__PURE__ */ jsx(
26
- "div",
26
+ "button",
27
27
  {
28
28
  ref,
29
+ type: "button",
30
+ role: "tab",
31
+ "aria-selected": isActive,
32
+ tabIndex: isActive ? 0 : -1,
29
33
  className: clsx_default("tab-item", isActive ? "active" : null),
30
34
  onClick,
31
35
  children: title
@@ -45,6 +49,18 @@ var Tab = (props) => {
45
49
  width: 0
46
50
  });
47
51
  const itemRefs = React2.useRef([]);
52
+ const handleKeyDown = (e) => {
53
+ const last = tabs.length - 1;
54
+ let next = null;
55
+ if (e.key === "ArrowRight") next = activeIndex >= last ? 0 : activeIndex + 1;
56
+ else if (e.key === "ArrowLeft") next = activeIndex <= 0 ? last : activeIndex - 1;
57
+ else if (e.key === "Home") next = 0;
58
+ else if (e.key === "End") next = last;
59
+ if (next === null) return;
60
+ e.preventDefault();
61
+ onChange(tabs[next], next);
62
+ itemRefs.current[next]?.focus();
63
+ };
48
64
  const handleChangeActiveTab = (tabItem, tabIdx) => {
49
65
  onChange(tabItem, tabIdx);
50
66
  };
@@ -54,30 +70,38 @@ var Tab = (props) => {
54
70
  setUnderlineStyle({ left: el.offsetLeft, width: el.offsetWidth });
55
71
  }
56
72
  }, [activeIndex, tabs.length]);
57
- return /* @__PURE__ */ jsxs("div", { className: clsx_default("lib-xplat-tab", `type-${shape}`, size), children: [
58
- tabs.map((tab, idx) => /* @__PURE__ */ jsx2(
59
- TabItem_default,
60
- {
61
- onClick: () => handleChangeActiveTab(tab, idx),
62
- isActive: activeIndex === idx,
63
- ref: (el) => {
64
- itemRefs.current[idx] = el;
65
- },
66
- title: tab.title
67
- },
68
- `${tab.value}_${idx}`
69
- )),
70
- shape === "toggle" && /* @__PURE__ */ jsx2(
71
- "div",
72
- {
73
- className: "tab-toggle-underline",
74
- style: {
75
- left: underlineStyle.left,
76
- width: underlineStyle.width
77
- }
78
- }
79
- )
80
- ] });
73
+ return /* @__PURE__ */ jsxs(
74
+ "div",
75
+ {
76
+ className: clsx_default("lib-xplat-tab", `type-${shape}`, size),
77
+ role: "tablist",
78
+ onKeyDown: handleKeyDown,
79
+ children: [
80
+ tabs.map((tab, idx) => /* @__PURE__ */ jsx2(
81
+ TabItem_default,
82
+ {
83
+ onClick: () => handleChangeActiveTab(tab, idx),
84
+ isActive: activeIndex === idx,
85
+ ref: (el) => {
86
+ itemRefs.current[idx] = el;
87
+ },
88
+ title: tab.title
89
+ },
90
+ `${tab.value}_${idx}`
91
+ )),
92
+ shape === "toggle" && /* @__PURE__ */ jsx2(
93
+ "div",
94
+ {
95
+ className: "tab-toggle-underline",
96
+ style: {
97
+ left: underlineStyle.left,
98
+ width: underlineStyle.width
99
+ }
100
+ }
101
+ )
102
+ ]
103
+ }
104
+ );
81
105
  };
82
106
  Tab.displayName = "Tab";
83
107
  var Tab_default = Tab;
@@ -176,11 +176,16 @@ var FieldMessage = (props) => {
176
176
  };
177
177
  FieldMessage.displayName = "FieldMessage";
178
178
  var Field = (props) => {
179
- const { validations, children } = props;
180
- if (!validations || validations.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
179
+ const { label, required = false, htmlFor, validations, children } = props;
180
+ const hasMessages = !!validations && validations.length > 0;
181
+ if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
181
182
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "lib-xplat-field", children: [
183
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
184
+ label,
185
+ required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
186
+ ] }),
182
187
  children,
183
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
188
+ hasMessages && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FieldMessage, { validations })
184
189
  ] });
185
190
  };
186
191
  Field.displayName = "Field";
@@ -194,16 +199,21 @@ var TextArea = import_react.default.forwardRef(
194
199
  className,
195
200
  size = "md",
196
201
  validations,
202
+ label,
203
+ required,
197
204
  resize = "vertical",
198
205
  rows = 3,
199
206
  disabled,
200
207
  ...textareaProps
201
208
  } = props;
209
+ const autoId = import_react.default.useId();
210
+ const fieldId = props.id ?? autoId;
202
211
  const control = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: clsx_default("lib-xplat-textarea-wrap", className), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
203
212
  "textarea",
204
213
  {
205
214
  ...textareaProps,
206
215
  ref,
216
+ id: fieldId,
207
217
  rows,
208
218
  disabled,
209
219
  "aria-invalid": hasError(validations) || void 0,
@@ -211,7 +221,7 @@ var TextArea = import_react.default.forwardRef(
211
221
  style: { resize }
212
222
  }
213
223
  ) });
214
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { validations, children: control });
224
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { validations, label, required, htmlFor: fieldId, children: control });
215
225
  }
216
226
  );
217
227
  TextArea.displayName = "TextArea";
@@ -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);
@@ -6,6 +6,10 @@ type TextAreaSize = "sm" | "md" | "lg";
6
6
  type TextAreaResize = "none" | "vertical" | "horizontal" | "both";
7
7
  interface TextAreaProps extends Omit<TextareaAttributes, "size"> {
8
8
  /** 안쪽 여백과 글자 크기 */
9
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
10
+ label?: React.ReactNode;
11
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
12
+ required?: boolean;
9
13
  size?: TextAreaSize;
10
14
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
11
15
  validations?: Validation[];
@@ -6,6 +6,10 @@ type TextAreaSize = "sm" | "md" | "lg";
6
6
  type TextAreaResize = "none" | "vertical" | "horizontal" | "both";
7
7
  interface TextAreaProps extends Omit<TextareaAttributes, "size"> {
8
8
  /** 안쪽 여백과 글자 크기 */
9
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
10
+ label?: React.ReactNode;
11
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
12
+ required?: boolean;
9
13
  size?: TextAreaSize;
10
14
  /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
11
15
  validations?: Validation[];
@@ -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";
@@ -158,16 +163,21 @@ var TextArea = React.forwardRef(
158
163
  className,
159
164
  size = "md",
160
165
  validations,
166
+ label,
167
+ required,
161
168
  resize = "vertical",
162
169
  rows = 3,
163
170
  disabled,
164
171
  ...textareaProps
165
172
  } = props;
173
+ const autoId = React.useId();
174
+ const fieldId = props.id ?? autoId;
166
175
  const control = /* @__PURE__ */ jsx5("div", { className: clsx_default("lib-xplat-textarea-wrap", className), children: /* @__PURE__ */ jsx5(
167
176
  "textarea",
168
177
  {
169
178
  ...textareaProps,
170
179
  ref,
180
+ id: fieldId,
171
181
  rows,
172
182
  disabled,
173
183
  "aria-invalid": hasError(validations) || void 0,
@@ -175,7 +185,7 @@ var TextArea = React.forwardRef(
175
185
  style: { resize }
176
186
  }
177
187
  ) });
178
- return /* @__PURE__ */ jsx5(Field, { validations, children: control });
188
+ return /* @__PURE__ */ jsx5(Field, { validations, label, required, htmlFor: fieldId, children: control });
179
189
  }
180
190
  );
181
191
  TextArea.displayName = "TextArea";