@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
@@ -0,0 +1,96 @@
1
+ /* src/components/Stat/stat.scss */
2
+ .lib-xplat-stat {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-space-1);
6
+ }
7
+ .lib-xplat-stat .stat-label {
8
+ font-family: var(--semantic-typo-caption-1-r-font);
9
+ font-size: var(--semantic-typo-caption-1-r-size);
10
+ line-height: var(--semantic-typo-caption-1-r-lh);
11
+ font-weight: var(--semantic-typo-caption-1-r-weight);
12
+ letter-spacing: var(--semantic-typo-caption-1-r-ls);
13
+ color: var(--semantic-text-tertiary);
14
+ }
15
+ .lib-xplat-stat .stat-value-row {
16
+ display: flex;
17
+ align-items: baseline;
18
+ gap: var(--spacing-space-2);
19
+ }
20
+ .lib-xplat-stat .stat-icon {
21
+ display: flex;
22
+ align-items: center;
23
+ align-self: center;
24
+ color: var(--semantic-icon-secondary);
25
+ }
26
+ .lib-xplat-stat .stat-value {
27
+ font-family: var(--semantic-typo-title-1-sb-font);
28
+ font-size: var(--semantic-typo-title-1-sb-size);
29
+ line-height: var(--semantic-typo-title-1-sb-lh);
30
+ font-weight: var(--semantic-typo-title-1-sb-weight);
31
+ letter-spacing: var(--semantic-typo-title-1-sb-ls);
32
+ color: var(--semantic-text-primary);
33
+ font-variant-numeric: tabular-nums;
34
+ }
35
+ .lib-xplat-stat .stat-unit {
36
+ font-family: var(--semantic-typo-label-2-m-font);
37
+ font-size: var(--semantic-typo-label-2-m-size);
38
+ line-height: var(--semantic-typo-label-2-m-lh);
39
+ font-weight: var(--semantic-typo-label-2-m-weight);
40
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
41
+ color: var(--semantic-text-tertiary);
42
+ }
43
+ .lib-xplat-stat .stat-trend {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: var(--spacing-space-1);
47
+ font-family: var(--semantic-typo-caption-1-m-font);
48
+ font-size: var(--semantic-typo-caption-1-m-size);
49
+ line-height: var(--semantic-typo-caption-1-m-lh);
50
+ font-weight: var(--semantic-typo-caption-1-m-weight);
51
+ letter-spacing: var(--semantic-typo-caption-1-m-ls);
52
+ font-variant-numeric: tabular-nums;
53
+ }
54
+ .lib-xplat-stat .stat-trend.up {
55
+ color: var(--semantic-text-success);
56
+ }
57
+ .lib-xplat-stat .stat-trend.down {
58
+ color: var(--semantic-text-error);
59
+ }
60
+ .lib-xplat-stat .stat-trend.flat {
61
+ color: var(--semantic-text-tertiary);
62
+ }
63
+ .lib-xplat-stat .stat-description {
64
+ font-family: var(--semantic-typo-caption-2-r-font);
65
+ font-size: var(--semantic-typo-caption-2-r-size);
66
+ line-height: var(--semantic-typo-caption-2-r-lh);
67
+ font-weight: var(--semantic-typo-caption-2-r-weight);
68
+ letter-spacing: var(--semantic-typo-caption-2-r-ls);
69
+ color: var(--semantic-text-tertiary);
70
+ }
71
+ .lib-xplat-stat.sm .stat-value {
72
+ font-family: var(--semantic-typo-heading-1-sb-font);
73
+ font-size: var(--semantic-typo-heading-1-sb-size);
74
+ line-height: var(--semantic-typo-heading-1-sb-lh);
75
+ font-weight: var(--semantic-typo-heading-1-sb-weight);
76
+ letter-spacing: var(--semantic-typo-heading-1-sb-ls);
77
+ }
78
+ .lib-xplat-stat.lg .stat-value {
79
+ font-family: var(--semantic-typo-display-2-sb-font);
80
+ font-size: var(--semantic-typo-display-2-sb-size);
81
+ line-height: var(--semantic-typo-display-2-sb-lh);
82
+ font-weight: var(--semantic-typo-display-2-sb-weight);
83
+ letter-spacing: var(--semantic-typo-display-2-sb-ls);
84
+ }
85
+ .lib-xplat-stat.success .stat-value {
86
+ color: var(--semantic-text-success);
87
+ }
88
+ .lib-xplat-stat.error .stat-value {
89
+ color: var(--semantic-text-error);
90
+ }
91
+ .lib-xplat-stat.warning .stat-value {
92
+ color: var(--semantic-text-warning);
93
+ }
94
+ .lib-xplat-stat.info .stat-value {
95
+ color: var(--semantic-text-info);
96
+ }
@@ -0,0 +1,37 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { S as StatusTypeInput } from '../../status-njWqQWFV.cjs';
4
+
5
+ type StatSize = "sm" | "md" | "lg";
6
+ type TrendDirection = "up" | "down" | "flat";
7
+ /** 큰 숫자 하나를 강조해 보여주는 칸. 대시보드의 지표 타일에 쓴다 */
8
+ interface StatProps {
9
+ /** 지표 이름 (예: "총 호출 수") */
10
+ label: React.ReactNode;
11
+ /** 강조할 값. 포맷은 호출부가 정한다 (천 단위 구분·통화 기호 등) */
12
+ value: React.ReactNode;
13
+ /** 값 뒤에 붙는 단위 (예: "원", "회"). 값보다 작게 표시된다 */
14
+ unit?: React.ReactNode;
15
+ /** 값 아래 보조 설명 */
16
+ description?: React.ReactNode;
17
+ /**
18
+ * 증감 표시. `value` 는 이미 포맷된 문자열을 그대로 쓴다 (예: "+12.4%").
19
+ * `direction` 이 색과 화살표를 정한다
20
+ */
21
+ trend?: {
22
+ direction: TrendDirection;
23
+ value: React.ReactNode;
24
+ };
25
+ /** 값 왼쪽에 놓을 아이콘 */
26
+ icon?: React.ReactNode;
27
+ /** 타일 크기 */
28
+ size?: StatSize;
29
+ /** 값 색. 상태를 나타낼 때 쓴다 */
30
+ type?: StatusTypeInput;
31
+ }
32
+ declare const Stat: {
33
+ (props: StatProps): react_jsx_runtime.JSX.Element;
34
+ displayName: string;
35
+ };
36
+
37
+ export { Stat };
@@ -0,0 +1,37 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { S as StatusTypeInput } from '../../status-njWqQWFV.js';
4
+
5
+ type StatSize = "sm" | "md" | "lg";
6
+ type TrendDirection = "up" | "down" | "flat";
7
+ /** 큰 숫자 하나를 강조해 보여주는 칸. 대시보드의 지표 타일에 쓴다 */
8
+ interface StatProps {
9
+ /** 지표 이름 (예: "총 호출 수") */
10
+ label: React.ReactNode;
11
+ /** 강조할 값. 포맷은 호출부가 정한다 (천 단위 구분·통화 기호 등) */
12
+ value: React.ReactNode;
13
+ /** 값 뒤에 붙는 단위 (예: "원", "회"). 값보다 작게 표시된다 */
14
+ unit?: React.ReactNode;
15
+ /** 값 아래 보조 설명 */
16
+ description?: React.ReactNode;
17
+ /**
18
+ * 증감 표시. `value` 는 이미 포맷된 문자열을 그대로 쓴다 (예: "+12.4%").
19
+ * `direction` 이 색과 화살표를 정한다
20
+ */
21
+ trend?: {
22
+ direction: TrendDirection;
23
+ value: React.ReactNode;
24
+ };
25
+ /** 값 왼쪽에 놓을 아이콘 */
26
+ icon?: React.ReactNode;
27
+ /** 타일 크기 */
28
+ size?: StatSize;
29
+ /** 값 색. 상태를 나타낼 때 쓴다 */
30
+ type?: StatusTypeInput;
31
+ }
32
+ declare const Stat: {
33
+ (props: StatProps): react_jsx_runtime.JSX.Element;
34
+ displayName: string;
35
+ };
36
+
37
+ export { Stat };
@@ -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";