@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,172 @@
1
+ /* src/components/FieldMessage/fieldMessage.scss */
2
+ .lib-xplat-field {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-space-2);
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
+ }
22
+ .lib-xplat-field-message-wrap {
23
+ display: flex;
24
+ gap: var(--spacing-space-2);
25
+ margin-left: var(--spacing-space-1);
26
+ }
27
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message {
28
+ display: flex;
29
+ gap: var(--spacing-space-1);
30
+ font-family: var(--semantic-typo-caption-2-r-font);
31
+ font-size: var(--semantic-typo-caption-2-r-size);
32
+ line-height: var(--semantic-typo-caption-2-r-lh);
33
+ font-weight: var(--semantic-typo-caption-2-r-weight);
34
+ letter-spacing: var(--semantic-typo-caption-2-r-ls);
35
+ align-items: center;
36
+ user-select: none;
37
+ }
38
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message.default {
39
+ color: var(--semantic-icon-secondary);
40
+ }
41
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message.success {
42
+ color: var(--semantic-text-success);
43
+ }
44
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message.warning {
45
+ color: var(--semantic-text-warning);
46
+ }
47
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message.error {
48
+ color: var(--semantic-text-error);
49
+ }
50
+ .lib-xplat-field-message-wrap > .lib-xplat-field-message > .icon {
51
+ font-size: var(--semantic-typo-label-2-m-size);
52
+ }
53
+
54
+ /* src/components/TimePicker/timePicker.scss */
55
+ .lib-xplat-time-picker {
56
+ position: relative;
57
+ width: 100%;
58
+ }
59
+ .lib-xplat-time-picker .time-picker-trigger {
60
+ display: flex;
61
+ align-items: center;
62
+ width: 100%;
63
+ cursor: pointer;
64
+ user-select: none;
65
+ outline: none;
66
+ border: 1px solid var(--semantic-border-primary);
67
+ border-radius: var(--spacing-radius-md);
68
+ background-color: var(--semantic-surface-neutral-primary);
69
+ color: var(--semantic-text-primary);
70
+ padding: 0 var(--spacing-space-3);
71
+ transition: border-color 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94);
72
+ }
73
+ .lib-xplat-time-picker .time-picker-trigger:hover {
74
+ border-color: var(--semantic-border-tertiary);
75
+ }
76
+ .lib-xplat-time-picker .time-picker-trigger:focus-visible {
77
+ border-color: var(--semantic-interaction-focus-ring);
78
+ box-shadow: 0 0 0 2px var(--semantic-interaction-overlay-10);
79
+ outline: 2px solid transparent;
80
+ outline-offset: 2px;
81
+ }
82
+ .lib-xplat-time-picker .time-picker-trigger.error {
83
+ border-color: var(--semantic-border-error);
84
+ }
85
+ .lib-xplat-time-picker .time-picker-value.placeholder {
86
+ color: var(--semantic-text-tertiary);
87
+ }
88
+ .lib-xplat-time-picker.sm .time-picker-trigger {
89
+ height: var(--spacing-control-height-sm);
90
+ font-family: var(--semantic-typo-caption-1-r-font);
91
+ font-size: var(--semantic-typo-caption-1-r-size);
92
+ line-height: var(--semantic-typo-caption-1-r-lh);
93
+ font-weight: var(--semantic-typo-caption-1-r-weight);
94
+ letter-spacing: var(--semantic-typo-caption-1-r-ls);
95
+ }
96
+ .lib-xplat-time-picker.md .time-picker-trigger {
97
+ height: var(--spacing-control-height-md);
98
+ font-family: var(--semantic-typo-label-2-m-font);
99
+ font-size: var(--semantic-typo-label-2-m-size);
100
+ line-height: var(--semantic-typo-label-2-m-lh);
101
+ font-weight: var(--semantic-typo-label-2-m-weight);
102
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
103
+ }
104
+ .lib-xplat-time-picker.lg .time-picker-trigger {
105
+ height: var(--spacing-control-height-lg);
106
+ font-family: var(--semantic-typo-label-1-m-font);
107
+ font-size: var(--semantic-typo-label-1-m-size);
108
+ line-height: var(--semantic-typo-label-1-m-lh);
109
+ font-weight: var(--semantic-typo-label-1-m-weight);
110
+ letter-spacing: var(--semantic-typo-label-1-m-ls);
111
+ }
112
+ .lib-xplat-time-picker.disabled .time-picker-trigger {
113
+ cursor: not-allowed;
114
+ pointer-events: none;
115
+ background-color: var(--semantic-surface-neutral-disabled);
116
+ color: var(--semantic-text-disabled);
117
+ }
118
+ .lib-xplat-time-picker-content {
119
+ position: fixed;
120
+ z-index: 1000;
121
+ max-height: 220px;
122
+ overflow-y: auto;
123
+ padding: var(--spacing-space-1) 0;
124
+ border-radius: var(--spacing-radius-md);
125
+ background-color: var(--semantic-surface-neutral-primary);
126
+ box-shadow: var(--semantic-shadow-md);
127
+ opacity: 0;
128
+ transition: opacity 0.2s ease;
129
+ }
130
+ .lib-xplat-time-picker-content.enter {
131
+ opacity: 1;
132
+ }
133
+ .lib-xplat-time-picker-content.exit {
134
+ opacity: 0;
135
+ }
136
+ .lib-xplat-time-picker-content .time-picker-item {
137
+ padding: var(--spacing-space-2) var(--spacing-space-3);
138
+ cursor: pointer;
139
+ outline: none;
140
+ color: var(--semantic-text-primary);
141
+ font-variant-numeric: tabular-nums;
142
+ transition: background-color 0.15s ease;
143
+ }
144
+ .lib-xplat-time-picker-content .time-picker-item:hover,
145
+ .lib-xplat-time-picker-content .time-picker-item:focus-visible {
146
+ background-color: var(--semantic-surface-neutral-secondary);
147
+ }
148
+ .lib-xplat-time-picker-content .time-picker-item.is-selected {
149
+ color: var(--semantic-text-brand-primary);
150
+ font-weight: var(--semantic-typo-label-2-sb-weight);
151
+ }
152
+ .lib-xplat-time-picker-content.sm .time-picker-item {
153
+ font-family: var(--semantic-typo-caption-1-r-font);
154
+ font-size: var(--semantic-typo-caption-1-r-size);
155
+ line-height: var(--semantic-typo-caption-1-r-lh);
156
+ font-weight: var(--semantic-typo-caption-1-r-weight);
157
+ letter-spacing: var(--semantic-typo-caption-1-r-ls);
158
+ }
159
+ .lib-xplat-time-picker-content.md .time-picker-item {
160
+ font-family: var(--semantic-typo-label-2-m-font);
161
+ font-size: var(--semantic-typo-label-2-m-size);
162
+ line-height: var(--semantic-typo-label-2-m-lh);
163
+ font-weight: var(--semantic-typo-label-2-m-weight);
164
+ letter-spacing: var(--semantic-typo-label-2-m-ls);
165
+ }
166
+ .lib-xplat-time-picker-content.lg .time-picker-item {
167
+ font-family: var(--semantic-typo-label-1-m-font);
168
+ font-size: var(--semantic-typo-label-1-m-size);
169
+ line-height: var(--semantic-typo-label-1-m-lh);
170
+ font-weight: var(--semantic-typo-label-1-m-weight);
171
+ letter-spacing: var(--semantic-typo-label-1-m-ls);
172
+ }
@@ -0,0 +1,42 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { V as Validation } from '../../validation-Dqo1zqhT.cjs';
4
+
5
+ type TimePickerSize = "sm" | "md" | "lg";
6
+ interface TimePickerProps {
7
+ /** `"HH:mm"` 24시간 표기. 비어 있으면 placeholder 가 보인다 */
8
+ value?: string;
9
+ /** 시각을 고르면 `"HH:mm"` 로 준다 */
10
+ onChange?: (value: string) => void;
11
+ /** 목록 간격(분). 기본 30 — 30이면 하루 48개가 나온다 */
12
+ step?: number;
13
+ /** 고를 수 있는 가장 이른 시각 `"HH:mm"` */
14
+ min?: string;
15
+ /** 고를 수 있는 가장 늦은 시각 `"HH:mm"` */
16
+ max?: string;
17
+ /** 오전/오후 12시간 표기로 보여준다. onChange 는 그대로 24시간 `"HH:mm"` */
18
+ use12Hours?: boolean;
19
+ /** 비어 있을 때 표시할 안내 글자 */
20
+ placeholder?: string;
21
+ disabled?: boolean;
22
+ size?: TimePickerSize;
23
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
24
+ label?: React.ReactNode;
25
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
26
+ required?: boolean;
27
+ /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
28
+ validations?: Validation[];
29
+ }
30
+ /**
31
+ * 시각을 고르는 드롭다운.
32
+ *
33
+ * `DatePicker` 는 날짜 전용이다. 날짜와 시각이 함께 필요하면 둘을 나란히 두고
34
+ * 호출부에서 합친다 — `DatePicker` 에 시간을 끼워 넣으면 하위 4종의 날짜 전용
35
+ * 비교 로직을 전부 건드려야 해서 기존 사용처가 깨질 위험이 크다.
36
+ */
37
+ declare const TimePicker: {
38
+ (props: TimePickerProps): react_jsx_runtime.JSX.Element;
39
+ displayName: string;
40
+ };
41
+
42
+ export { TimePicker };
@@ -0,0 +1,42 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { V as Validation } from '../../validation-Dqo1zqhT.js';
4
+
5
+ type TimePickerSize = "sm" | "md" | "lg";
6
+ interface TimePickerProps {
7
+ /** `"HH:mm"` 24시간 표기. 비어 있으면 placeholder 가 보인다 */
8
+ value?: string;
9
+ /** 시각을 고르면 `"HH:mm"` 로 준다 */
10
+ onChange?: (value: string) => void;
11
+ /** 목록 간격(분). 기본 30 — 30이면 하루 48개가 나온다 */
12
+ step?: number;
13
+ /** 고를 수 있는 가장 이른 시각 `"HH:mm"` */
14
+ min?: string;
15
+ /** 고를 수 있는 가장 늦은 시각 `"HH:mm"` */
16
+ max?: string;
17
+ /** 오전/오후 12시간 표기로 보여준다. onChange 는 그대로 24시간 `"HH:mm"` */
18
+ use12Hours?: boolean;
19
+ /** 비어 있을 때 표시할 안내 글자 */
20
+ placeholder?: string;
21
+ disabled?: boolean;
22
+ size?: TimePickerSize;
23
+ /** 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 */
24
+ label?: React.ReactNode;
25
+ /** 라벨 뒤에 필수 표시(*)를 붙인다 */
26
+ required?: boolean;
27
+ /** 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 */
28
+ validations?: Validation[];
29
+ }
30
+ /**
31
+ * 시각을 고르는 드롭다운.
32
+ *
33
+ * `DatePicker` 는 날짜 전용이다. 날짜와 시각이 함께 필요하면 둘을 나란히 두고
34
+ * 호출부에서 합친다 — `DatePicker` 에 시간을 끼워 넣으면 하위 4종의 날짜 전용
35
+ * 비교 로직을 전부 건드려야 해서 기존 사용처가 깨질 위험이 크다.
36
+ */
37
+ declare const TimePicker: {
38
+ (props: TimePickerProps): react_jsx_runtime.JSX.Element;
39
+ displayName: string;
40
+ };
41
+
42
+ export { TimePicker };
@@ -0,0 +1,411 @@
1
+ // src/components/TimePicker/TimePicker.tsx
2
+ import React4 from "react";
3
+
4
+ // src/tokens/hooks/Portal.tsx
5
+ import ReactDOM from "react-dom";
6
+ var Portal = ({ children }) => {
7
+ if (typeof document === "undefined") return null;
8
+ return ReactDOM.createPortal(children, document.body);
9
+ };
10
+ Portal.displayName = "Portal";
11
+ var Portal_default = Portal;
12
+
13
+ // src/tokens/hooks/useAutoPosition.ts
14
+ import React from "react";
15
+ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
16
+ const [position, setPosition] = React.useState({
17
+ position: {},
18
+ direction: "bottom"
19
+ });
20
+ const calculatePosition = React.useCallback(() => {
21
+ if (!triggerRef.current || !popRef.current) return;
22
+ const triggerRect = triggerRef.current.getBoundingClientRect();
23
+ const popW = popRef.current.offsetWidth;
24
+ const popH = popRef.current.offsetHeight;
25
+ const viewportHeight = window.innerHeight;
26
+ const viewportWidth = window.innerWidth;
27
+ if (popH === 0 || popW === 0) return;
28
+ let direction = "bottom";
29
+ let top;
30
+ let left;
31
+ if (triggerRect.bottom + popH > viewportHeight && triggerRect.top - popH > 0) {
32
+ direction = "top";
33
+ top = triggerRect.top - popH;
34
+ } else {
35
+ top = triggerRect.bottom;
36
+ }
37
+ left = triggerRect.left;
38
+ if (left + popW > viewportWidth - 8) {
39
+ left = triggerRect.right - popW;
40
+ }
41
+ if (left < 8) left = 8;
42
+ setPosition({
43
+ position: { top, left },
44
+ direction
45
+ });
46
+ }, [triggerRef, popRef]);
47
+ React.useLayoutEffect(() => {
48
+ if (!enabled) return;
49
+ calculatePosition();
50
+ const raf = requestAnimationFrame(calculatePosition);
51
+ return () => cancelAnimationFrame(raf);
52
+ }, [calculatePosition, enabled]);
53
+ React.useEffect(() => {
54
+ if (!enabled || !popRef.current) return;
55
+ const observer = new ResizeObserver(() => {
56
+ calculatePosition();
57
+ });
58
+ observer.observe(popRef.current);
59
+ return () => observer.disconnect();
60
+ }, [calculatePosition, enabled, popRef]);
61
+ React.useEffect(() => {
62
+ if (!enabled) return;
63
+ window.addEventListener("resize", calculatePosition);
64
+ window.addEventListener("scroll", calculatePosition, true);
65
+ return () => {
66
+ window.removeEventListener("resize", calculatePosition);
67
+ window.removeEventListener("scroll", calculatePosition, true);
68
+ };
69
+ }, [calculatePosition, enabled]);
70
+ return position;
71
+ };
72
+ var useAutoPosition_default = useAutoPosition;
73
+
74
+ // src/tokens/hooks/useClickOutside.ts
75
+ import React2 from "react";
76
+ var useClickOutside = (refs, handler, enabled = true) => {
77
+ React2.useEffect(() => {
78
+ if (!enabled) return;
79
+ const refArray = Array.isArray(refs) ? refs : [refs];
80
+ const listener = (event) => {
81
+ const target = event.target;
82
+ const isInside = refArray.some(
83
+ (ref) => ref.current && ref.current.contains(target)
84
+ );
85
+ if (!isInside) {
86
+ handler();
87
+ }
88
+ };
89
+ document.addEventListener("mousedown", listener);
90
+ document.addEventListener("touchstart", listener);
91
+ return () => {
92
+ document.removeEventListener("mousedown", listener);
93
+ document.removeEventListener("touchstart", listener);
94
+ };
95
+ }, [refs, handler, enabled]);
96
+ };
97
+ var useClickOutside_default = useClickOutside;
98
+
99
+ // src/tokens/hooks/useOverlayKeyboard.ts
100
+ import React3 from "react";
101
+ var FOCUSABLE = [
102
+ "a[href]",
103
+ "button:not([disabled])",
104
+ "input:not([disabled]):not([type='hidden'])",
105
+ "select:not([disabled])",
106
+ "textarea:not([disabled])",
107
+ "[tabindex]:not([tabindex='-1'])"
108
+ ].join(",");
109
+ var useEscapeKey = (enabled, onEscape) => {
110
+ React3.useEffect(() => {
111
+ if (!enabled) return;
112
+ const onKey = (e) => {
113
+ if (e.key !== "Escape") return;
114
+ if (e.defaultPrevented) return;
115
+ e.preventDefault();
116
+ onEscape();
117
+ };
118
+ document.addEventListener("keydown", onKey);
119
+ return () => document.removeEventListener("keydown", onKey);
120
+ }, [enabled, onEscape]);
121
+ };
122
+
123
+ // ../../node_modules/clsx/dist/clsx.mjs
124
+ function r(e) {
125
+ var t, f, n = "";
126
+ if ("string" == typeof e || "number" == typeof e) n += e;
127
+ else if ("object" == typeof e) if (Array.isArray(e)) {
128
+ var o = e.length;
129
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
130
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
131
+ return n;
132
+ }
133
+ function clsx() {
134
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
135
+ return n;
136
+ }
137
+ var clsx_default = clsx;
138
+
139
+ // src/tokens/svg/status/ErrorIcon.tsx
140
+ import { jsx, jsxs } from "react/jsx-runtime";
141
+ var ErrorIcon = () => /* @__PURE__ */ jsxs(
142
+ "svg",
143
+ {
144
+ xmlns: "http://www.w3.org/2000/svg",
145
+ width: "1em",
146
+ height: "1em",
147
+ viewBox: "0 0 16 16",
148
+ fill: "none",
149
+ children: [
150
+ /* @__PURE__ */ jsxs("g", { clipPath: "url(#clip0_1196_9603)", children: [
151
+ /* @__PURE__ */ jsx(
152
+ "path",
153
+ {
154
+ d: "M9.57588 5.57559C9.81019 5.34127 10.19 5.34127 10.4243 5.57559C10.6586 5.8099 10.6586 6.18971 10.4243 6.42402L8.84854 7.9998L10.4243 9.57559C10.6586 9.8099 10.6586 10.1897 10.4243 10.424C10.19 10.6583 9.81019 10.6583 9.57588 10.424L8.0001 8.84824L6.42432 10.424C6.19 10.6583 5.81019 10.6583 5.57588 10.424C5.34156 10.1897 5.34156 9.8099 5.57588 9.57559L7.15166 7.9998L5.57588 6.42402C5.34156 6.18971 5.34156 5.8099 5.57588 5.57559C5.81019 5.34127 6.19 5.34127 6.42432 5.57559L8.0001 7.15137L9.57588 5.57559Z",
155
+ fill: "currentColor"
156
+ }
157
+ ),
158
+ /* @__PURE__ */ jsx(
159
+ "path",
160
+ {
161
+ fillRule: "evenodd",
162
+ clipRule: "evenodd",
163
+ d: "M8.0001 0.733398C12.0131 0.73361 15.2671 3.98679 15.2673 7.9998C15.2672 12.0129 12.0131 15.2668 8.0001 15.267C3.98701 15.2669 0.733016 12.0129 0.73291 7.9998C0.733051 3.98674 3.98704 0.733539 8.0001 0.733398ZM8.0001 1.9334C4.64978 1.93354 1.93383 4.64948 1.93369 7.9998C1.9338 11.3502 4.64975 14.0669 8.0001 14.067C11.3504 14.0668 14.0672 11.3501 14.0673 7.9998C14.0671 4.64953 11.3504 1.93361 8.0001 1.9334Z",
164
+ fill: "currentColor"
165
+ }
166
+ )
167
+ ] }),
168
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "clip0_1196_9603", children: /* @__PURE__ */ jsx("rect", { width: "16", height: "16", fill: "white" }) }) })
169
+ ]
170
+ }
171
+ );
172
+ var ErrorIcon_default = ErrorIcon;
173
+
174
+ // src/tokens/svg/status/InfoIcon.tsx
175
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
176
+ var InfoIcon = () => /* @__PURE__ */ jsxs2(
177
+ "svg",
178
+ {
179
+ xmlns: "http://www.w3.org/2000/svg",
180
+ width: "1em",
181
+ height: "1em",
182
+ viewBox: "0 0 16 16",
183
+ fill: "none",
184
+ children: [
185
+ /* @__PURE__ */ jsxs2("g", { clipPath: "url(#clip0_1188_11261)", children: [
186
+ /* @__PURE__ */ jsx2(
187
+ "path",
188
+ {
189
+ d: "M8.00156 7.39999C8.33293 7.39999 8.60156 7.66862 8.60156 7.99999V10.6664C8.60156 10.9978 8.33293 11.2664 8.00156 11.2664C7.67019 11.2664 7.40156 10.9978 7.40156 10.6664V7.99999C7.40156 7.66862 7.67019 7.39999 8.00156 7.39999Z",
190
+ fill: "currentColor"
191
+ }
192
+ ),
193
+ /* @__PURE__ */ jsx2(
194
+ "path",
195
+ {
196
+ d: "M8.00859 4.73358C8.3398 4.73378 8.60859 5.00233 8.60859 5.33358C8.60849 5.66474 8.33973 5.93338 8.00859 5.93358H8.00156C7.67026 5.93358 7.40167 5.66486 7.40156 5.33358C7.40156 5.00221 7.67019 4.73358 8.00156 4.73358H8.00859Z",
197
+ fill: "currentColor"
198
+ }
199
+ ),
200
+ /* @__PURE__ */ jsx2(
201
+ "path",
202
+ {
203
+ fillRule: "evenodd",
204
+ clipRule: "evenodd",
205
+ d: "M8.00156 0.733582C12.0146 0.733793 15.2686 3.98697 15.2688 7.99999C15.2686 12.013 12.0146 15.267 8.00156 15.2672C3.98848 15.267 0.734481 12.0131 0.734375 7.99999C0.734516 3.98693 3.9885 0.733722 8.00156 0.733582ZM8.00156 1.93358C4.65124 1.93372 1.9353 4.64967 1.93516 7.99999C1.93526 11.3503 4.65122 14.067 8.00156 14.0672C11.3518 14.067 14.0686 11.3503 14.0688 7.99999C14.0686 4.64971 11.3518 1.93379 8.00156 1.93358Z",
206
+ fill: "currentColor"
207
+ }
208
+ )
209
+ ] }),
210
+ /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsx2("clipPath", { id: "clip0_1188_11261", children: /* @__PURE__ */ jsx2("rect", { width: "16", height: "16", fill: "white" }) }) })
211
+ ]
212
+ }
213
+ );
214
+ var InfoIcon_default = InfoIcon;
215
+
216
+ // src/tokens/svg/status/SuccessIcon.tsx
217
+ import { jsx as jsx3 } from "react/jsx-runtime";
218
+ var SuccessIcon = () => /* @__PURE__ */ jsx3(
219
+ "svg",
220
+ {
221
+ xmlns: "http://www.w3.org/2000/svg",
222
+ width: "1em",
223
+ height: "1em",
224
+ viewBox: "0 0 16 16",
225
+ fill: "none",
226
+ children: /* @__PURE__ */ jsx3(
227
+ "path",
228
+ {
229
+ d: "M12.9084 3.57597C13.1426 3.34172 13.5225 3.34185 13.7568 3.57597C13.9911 3.81028 13.9911 4.19009 13.7568 4.4244L6.424 11.758C6.31157 11.8703 6.15869 11.9337 5.99978 11.9338C5.8409 11.9337 5.68798 11.8703 5.57556 11.758L2.24197 8.4244C2.00782 8.19018 2.00795 7.81028 2.24197 7.57597C2.47626 7.34167 2.85609 7.34172 3.09041 7.57597L5.999 10.4846L12.9084 3.57597Z",
230
+ fill: "currentColor"
231
+ }
232
+ )
233
+ }
234
+ );
235
+ var SuccessIcon_default = SuccessIcon;
236
+
237
+ // src/components/FieldMessage/FieldMessage.tsx
238
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
239
+ var ICON = {
240
+ default: /* @__PURE__ */ jsx4(InfoIcon_default, {}),
241
+ success: /* @__PURE__ */ jsx4(SuccessIcon_default, {}),
242
+ warning: /* @__PURE__ */ jsx4(InfoIcon_default, {}),
243
+ error: /* @__PURE__ */ jsx4(ErrorIcon_default, {})
244
+ };
245
+ var FieldMessage = (props) => {
246
+ const { validations } = props;
247
+ if (!validations || validations.length === 0) return null;
248
+ return /* @__PURE__ */ jsx4("div", { className: "lib-xplat-field-message-wrap", children: validations.map((v, idx) => /* @__PURE__ */ jsxs3(
249
+ "div",
250
+ {
251
+ className: clsx_default("lib-xplat-field-message", v.status),
252
+ children: [
253
+ /* @__PURE__ */ jsx4("div", { className: "icon", children: ICON[v.status] }),
254
+ /* @__PURE__ */ jsx4("span", { children: v.message })
255
+ ]
256
+ },
257
+ `${v.message}_${idx}`
258
+ )) });
259
+ };
260
+ FieldMessage.displayName = "FieldMessage";
261
+ var Field = (props) => {
262
+ const { label, required = false, htmlFor, validations, children } = props;
263
+ const hasMessages = !!validations && validations.length > 0;
264
+ if (!label && !hasMessages) return /* @__PURE__ */ jsx4(Fragment, { children });
265
+ return /* @__PURE__ */ jsxs3("div", { className: "lib-xplat-field", children: [
266
+ label && /* @__PURE__ */ jsxs3("label", { className: "lib-xplat-field-label", htmlFor, children: [
267
+ label,
268
+ required && /* @__PURE__ */ jsx4("span", { className: "required", "aria-hidden": "true", children: "*" })
269
+ ] }),
270
+ children,
271
+ hasMessages && /* @__PURE__ */ jsx4(FieldMessage, { validations })
272
+ ] });
273
+ };
274
+ Field.displayName = "Field";
275
+ var hasError = (validations) => !!validations?.some((v) => v.status === "error");
276
+
277
+ // src/components/TimePicker/TimePicker.tsx
278
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
279
+ var ANIMATION_DURATION_MS = 200;
280
+ var toMinutes = (hhmm) => {
281
+ if (!hhmm) return null;
282
+ const m = /^(\d{1,2}):(\d{2})$/.exec(hhmm.trim());
283
+ if (!m) return null;
284
+ const h = Number(m[1]);
285
+ const min = Number(m[2]);
286
+ if (h > 23 || min > 59) return null;
287
+ return h * 60 + min;
288
+ };
289
+ var toHHmm = (minutes) => `${String(Math.floor(minutes / 60)).padStart(2, "0")}:${String(minutes % 60).padStart(2, "0")}`;
290
+ var display = (hhmm, use12Hours) => {
291
+ if (!use12Hours) return hhmm;
292
+ const total = toMinutes(hhmm);
293
+ if (total === null) return hhmm;
294
+ const h24 = Math.floor(total / 60);
295
+ const mm = String(total % 60).padStart(2, "0");
296
+ const period = h24 < 12 ? "\uC624\uC804" : "\uC624\uD6C4";
297
+ const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
298
+ return `${period} ${h12}:${mm}`;
299
+ };
300
+ var TimePicker = (props) => {
301
+ const {
302
+ value,
303
+ onChange,
304
+ step = 30,
305
+ min,
306
+ max,
307
+ use12Hours = false,
308
+ placeholder = "\uC2DC\uAC01 \uC120\uD0DD",
309
+ disabled = false,
310
+ size = "md",
311
+ label,
312
+ required,
313
+ validations
314
+ } = props;
315
+ const fieldId = React4.useId();
316
+ const triggerRef = React4.useRef(null);
317
+ const contentRef = React4.useRef(null);
318
+ const [isOpen, setOpen] = React4.useState(false);
319
+ const [mounted, setMounted] = React4.useState(false);
320
+ const [visible, setVisible] = React4.useState(false);
321
+ const options = React4.useMemo(() => {
322
+ const safeStep = step > 0 && step <= 720 ? step : 30;
323
+ const lo = toMinutes(min) ?? 0;
324
+ const hi = toMinutes(max) ?? 24 * 60 - 1;
325
+ const out = [];
326
+ for (let t = lo; t <= hi; t += safeStep) out.push(toHHmm(t));
327
+ return out;
328
+ }, [step, min, max]);
329
+ const close = React4.useCallback(() => setOpen(false), []);
330
+ useClickOutside_default([triggerRef, contentRef], close, isOpen);
331
+ useEscapeKey(isOpen, () => {
332
+ close();
333
+ triggerRef.current?.focus({ preventScroll: true });
334
+ });
335
+ const position = useAutoPosition_default(triggerRef, contentRef, mounted);
336
+ React4.useEffect(() => {
337
+ if (disabled && isOpen) setOpen(false);
338
+ }, [disabled, isOpen]);
339
+ React4.useEffect(() => {
340
+ if (isOpen) {
341
+ setMounted(true);
342
+ const t2 = setTimeout(() => setVisible(true), 1);
343
+ return () => clearTimeout(t2);
344
+ }
345
+ setVisible(false);
346
+ const t = setTimeout(() => setMounted(false), ANIMATION_DURATION_MS);
347
+ return () => clearTimeout(t);
348
+ }, [isOpen]);
349
+ const pick = (hhmm) => {
350
+ onChange?.(hhmm);
351
+ close();
352
+ triggerRef.current?.focus({ preventScroll: true });
353
+ };
354
+ const control = /* @__PURE__ */ jsxs4("div", { className: clsx_default("lib-xplat-time-picker", size, disabled && "disabled"), children: [
355
+ /* @__PURE__ */ jsx5(
356
+ "div",
357
+ {
358
+ id: fieldId,
359
+ ref: triggerRef,
360
+ className: clsx_default("time-picker-trigger", hasError(validations) && "error"),
361
+ role: "combobox",
362
+ "aria-expanded": isOpen,
363
+ "aria-haspopup": "listbox",
364
+ "aria-invalid": hasError(validations) || void 0,
365
+ tabIndex: disabled ? void 0 : 0,
366
+ onClick: () => !disabled && setOpen((p) => !p),
367
+ onKeyDown: (e) => {
368
+ if (disabled) return;
369
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
370
+ e.preventDefault();
371
+ setOpen(true);
372
+ }
373
+ },
374
+ children: /* @__PURE__ */ jsx5("span", { className: clsx_default("time-picker-value", !value && "placeholder"), children: value ? display(value, use12Hours) : placeholder })
375
+ }
376
+ ),
377
+ mounted && /* @__PURE__ */ jsx5(Portal_default, { children: /* @__PURE__ */ jsx5(
378
+ "div",
379
+ {
380
+ ref: contentRef,
381
+ className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit"),
382
+ style: { ...position.position, width: triggerRef.current?.offsetWidth },
383
+ role: "listbox",
384
+ children: options.map((t) => /* @__PURE__ */ jsx5(
385
+ "div",
386
+ {
387
+ className: clsx_default("time-picker-item", t === value && "is-selected"),
388
+ role: "option",
389
+ "aria-selected": t === value,
390
+ tabIndex: 0,
391
+ onClick: () => pick(t),
392
+ onKeyDown: (e) => {
393
+ if (e.key === "Enter" || e.key === " ") {
394
+ e.preventDefault();
395
+ pick(t);
396
+ }
397
+ },
398
+ children: display(t, use12Hours)
399
+ },
400
+ t
401
+ ))
402
+ }
403
+ ) })
404
+ ] });
405
+ return /* @__PURE__ */ jsx5(Field, { validations, label, required, htmlFor: fieldId, children: control });
406
+ };
407
+ TimePicker.displayName = "TimePicker";
408
+ var TimePicker_default = TimePicker;
409
+ export {
410
+ TimePicker_default as TimePicker
411
+ };