@x-plat/design-system 0.9.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 (156) 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 +5 -0
  4. package/dist/components/AutoResizeTextArea/index.d.ts +5 -0
  5. package/dist/components/AutoResizeTextArea/index.js +14 -5
  6. package/dist/components/Badge/index.cjs +5 -1
  7. package/dist/components/Badge/index.css +1 -0
  8. package/dist/components/Badge/index.d.cts +1 -0
  9. package/dist/components/Badge/index.d.ts +1 -0
  10. package/dist/components/Badge/index.js +5 -1
  11. package/dist/components/Box/index.d.cts +3 -0
  12. package/dist/components/Box/index.d.ts +3 -0
  13. package/dist/components/Chart/index.cjs +7 -6
  14. package/dist/components/Chart/index.d.cts +12 -1
  15. package/dist/components/Chart/index.d.ts +12 -1
  16. package/dist/components/Chart/index.js +7 -6
  17. package/dist/components/ChatInput/index.cjs +20 -7
  18. package/dist/components/ChatInput/index.css +15 -0
  19. package/dist/components/ChatInput/index.d.cts +10 -0
  20. package/dist/components/ChatInput/index.d.ts +10 -0
  21. package/dist/components/ChatInput/index.js +20 -7
  22. package/dist/components/CheckBox/index.cjs +14 -5
  23. package/dist/components/CheckBox/index.css +17 -0
  24. package/dist/components/CheckBox/index.d.cts +10 -2
  25. package/dist/components/CheckBox/index.d.ts +10 -2
  26. package/dist/components/CheckBox/index.js +14 -5
  27. package/dist/components/Chip/index.d.cts +2 -0
  28. package/dist/components/Chip/index.d.ts +2 -0
  29. package/dist/components/DatePicker/index.cjs +181 -65
  30. package/dist/components/DatePicker/index.css +19 -0
  31. package/dist/components/DatePicker/index.js +181 -65
  32. package/dist/components/Drawer/index.cjs +83 -4
  33. package/dist/components/Drawer/index.d.cts +1 -0
  34. package/dist/components/Drawer/index.d.ts +1 -0
  35. package/dist/components/Drawer/index.js +83 -4
  36. package/dist/components/Dropdown/index.cjs +35 -7
  37. package/dist/components/Dropdown/index.js +35 -7
  38. package/dist/components/Editor/index.cjs +178 -21
  39. package/dist/components/Editor/index.js +178 -21
  40. package/dist/components/FieldMessage/index.cjs +8 -3
  41. package/dist/components/FieldMessage/index.css +15 -0
  42. package/dist/components/FieldMessage/index.d.cts +11 -4
  43. package/dist/components/FieldMessage/index.d.ts +11 -4
  44. package/dist/components/FieldMessage/index.js +8 -3
  45. package/dist/components/FileUpload/index.cjs +8 -3
  46. package/dist/components/FileUpload/index.css +15 -0
  47. package/dist/components/FileUpload/index.d.cts +6 -0
  48. package/dist/components/FileUpload/index.d.ts +6 -0
  49. package/dist/components/FileUpload/index.js +8 -3
  50. package/dist/components/ImageSelector/index.cjs +8 -3
  51. package/dist/components/ImageSelector/index.css +15 -0
  52. package/dist/components/ImageSelector/index.d.cts +3 -0
  53. package/dist/components/ImageSelector/index.d.ts +3 -0
  54. package/dist/components/ImageSelector/index.js +8 -3
  55. package/dist/components/Input/index.cjs +15 -5
  56. package/dist/components/Input/index.css +15 -0
  57. package/dist/components/Input/index.d.cts +8 -0
  58. package/dist/components/Input/index.d.ts +8 -0
  59. package/dist/components/Input/index.js +15 -5
  60. package/dist/components/Modal/index.cjs +84 -5
  61. package/dist/components/Modal/index.js +84 -5
  62. package/dist/components/NotificationBadge/index.cjs +5 -1
  63. package/dist/components/NotificationBadge/index.css +1 -0
  64. package/dist/components/NotificationBadge/index.d.cts +6 -2
  65. package/dist/components/NotificationBadge/index.d.ts +6 -2
  66. package/dist/components/NotificationBadge/index.js +5 -1
  67. package/dist/components/Pagination/index.cjs +6 -2
  68. package/dist/components/Pagination/index.css +1 -0
  69. package/dist/components/Pagination/index.d.cts +13 -2
  70. package/dist/components/Pagination/index.d.ts +13 -2
  71. package/dist/components/Pagination/index.js +6 -2
  72. package/dist/components/PopOver/index.cjs +34 -6
  73. package/dist/components/PopOver/index.js +34 -6
  74. package/dist/components/Progress/index.cjs +6 -2
  75. package/dist/components/Progress/index.css +1 -0
  76. package/dist/components/Progress/index.d.cts +11 -2
  77. package/dist/components/Progress/index.d.ts +11 -2
  78. package/dist/components/Progress/index.js +6 -2
  79. package/dist/components/Radio/index.cjs +14 -5
  80. package/dist/components/Radio/index.css +17 -0
  81. package/dist/components/Radio/index.d.cts +11 -2
  82. package/dist/components/Radio/index.d.ts +11 -2
  83. package/dist/components/Radio/index.js +14 -5
  84. package/dist/components/Select/index.cjs +99 -31
  85. package/dist/components/Select/index.css +54 -0
  86. package/dist/components/Select/index.d.cts +16 -1
  87. package/dist/components/Select/index.d.ts +16 -1
  88. package/dist/components/Select/index.js +99 -31
  89. package/dist/components/Skeleton/index.d.cts +3 -0
  90. package/dist/components/Skeleton/index.d.ts +3 -0
  91. package/dist/components/Spinner/index.cjs +6 -2
  92. package/dist/components/Spinner/index.css +1 -0
  93. package/dist/components/Spinner/index.d.cts +8 -2
  94. package/dist/components/Spinner/index.d.ts +8 -2
  95. package/dist/components/Spinner/index.js +6 -2
  96. package/dist/components/Stat/index.cjs +84 -0
  97. package/dist/components/Stat/index.css +96 -0
  98. package/dist/components/Stat/index.d.cts +37 -0
  99. package/dist/components/Stat/index.d.ts +37 -0
  100. package/dist/components/Stat/index.js +57 -0
  101. package/dist/components/Steps/index.cjs +6 -2
  102. package/dist/components/Steps/index.css +11 -0
  103. package/dist/components/Steps/index.d.cts +9 -2
  104. package/dist/components/Steps/index.d.ts +9 -2
  105. package/dist/components/Steps/index.js +6 -2
  106. package/dist/components/Switch/index.cjs +20 -7
  107. package/dist/components/Switch/index.css +17 -0
  108. package/dist/components/Switch/index.d.cts +15 -2
  109. package/dist/components/Switch/index.d.ts +15 -2
  110. package/dist/components/Switch/index.js +20 -7
  111. package/dist/components/Tab/index.cjs +51 -26
  112. package/dist/components/Tab/index.css +4 -0
  113. package/dist/components/Tab/index.d.cts +12 -1
  114. package/dist/components/Tab/index.d.ts +12 -1
  115. package/dist/components/Tab/index.js +51 -26
  116. package/dist/components/Tag/index.cjs +5 -3
  117. package/dist/components/Tag/index.d.cts +12 -2
  118. package/dist/components/Tag/index.d.ts +12 -2
  119. package/dist/components/Tag/index.js +5 -3
  120. package/dist/components/TextArea/index.cjs +14 -4
  121. package/dist/components/TextArea/index.css +15 -0
  122. package/dist/components/TextArea/index.d.cts +7 -0
  123. package/dist/components/TextArea/index.d.ts +7 -0
  124. package/dist/components/TextArea/index.js +14 -4
  125. package/dist/components/TimePicker/index.cjs +448 -0
  126. package/dist/components/TimePicker/index.css +172 -0
  127. package/dist/components/TimePicker/index.d.cts +42 -0
  128. package/dist/components/TimePicker/index.d.ts +42 -0
  129. package/dist/components/TimePicker/index.js +411 -0
  130. package/dist/components/Tooltip/index.cjs +4 -2
  131. package/dist/components/Tooltip/index.d.cts +11 -1
  132. package/dist/components/Tooltip/index.d.ts +11 -1
  133. package/dist/components/Tooltip/index.js +4 -2
  134. package/dist/components/index.cjs +916 -385
  135. package/dist/components/index.css +296 -0
  136. package/dist/components/index.d.cts +3 -0
  137. package/dist/components/index.d.ts +3 -0
  138. package/dist/components/index.js +914 -385
  139. package/dist/index.cjs +940 -409
  140. package/dist/index.css +296 -0
  141. package/dist/index.d.cts +3 -0
  142. package/dist/index.d.ts +3 -0
  143. package/dist/index.js +938 -409
  144. package/dist/status-njWqQWFV.d.cts +20 -0
  145. package/dist/status-njWqQWFV.d.ts +20 -0
  146. package/guidelines/AGENT_PROMPT.md +18 -3
  147. package/guidelines/Guidelines.md +3 -0
  148. package/guidelines/components/card.md +3 -3
  149. package/guidelines/components/editor.md +86 -0
  150. package/guidelines/components/feedback.md +1 -1
  151. package/guidelines/components/form.md +19 -0
  152. package/guidelines/components/navigation.md +16 -15
  153. package/guidelines/components/overlay.md +43 -4
  154. package/guidelines/components/stat.md +31 -0
  155. package/guidelines/components/time-picker.md +49 -0
  156. package/package.json +1 -1
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 상태 색 공통 어휘.
3
+ *
4
+ * "이 요소가 어떤 상태인가" 를 색으로 나타낼 때 쓴다. 컴포넌트마다 다른 단어를
5
+ * 쓰지 않도록 한 곳에 모은다.
6
+ *
7
+ * 모양·동작 변형은 이것이 아니라 `variant` prop 으로 받는다. 둘은 다른 축이다.
8
+ */
9
+ /** 모든 상태 색 컴포넌트가 공통으로 받는 값 */
10
+ type StatusType = "primary" | "success" | "error" | "warning" | "info";
11
+ /**
12
+ * 예전 이름. `brand` 는 `primary` 와 같은 것을 가리켰다.
13
+ *
14
+ * @deprecated `"primary"` 를 쓸 것. 기존 사용처가 깨지지 않도록 계속 받는다.
15
+ */
16
+ type LegacyStatusType = "brand";
17
+ /** 컴포넌트가 받는 타입. 새 이름과 예전 이름을 모두 허용한다 */
18
+ type StatusTypeInput = StatusType | LegacyStatusType;
19
+
20
+ export type { LegacyStatusType as L, StatusTypeInput as S };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 상태 색 공통 어휘.
3
+ *
4
+ * "이 요소가 어떤 상태인가" 를 색으로 나타낼 때 쓴다. 컴포넌트마다 다른 단어를
5
+ * 쓰지 않도록 한 곳에 모은다.
6
+ *
7
+ * 모양·동작 변형은 이것이 아니라 `variant` prop 으로 받는다. 둘은 다른 축이다.
8
+ */
9
+ /** 모든 상태 색 컴포넌트가 공통으로 받는 값 */
10
+ type StatusType = "primary" | "success" | "error" | "warning" | "info";
11
+ /**
12
+ * 예전 이름. `brand` 는 `primary` 와 같은 것을 가리켰다.
13
+ *
14
+ * @deprecated `"primary"` 를 쓸 것. 기존 사용처가 깨지지 않도록 계속 받는다.
15
+ */
16
+ type LegacyStatusType = "brand";
17
+ /** 컴포넌트가 받는 타입. 새 이름과 예전 이름을 모두 허용한다 */
18
+ type StatusTypeInput = StatusType | LegacyStatusType;
19
+
20
+ export type { LegacyStatusType as L, StatusTypeInput as S };
@@ -593,12 +593,27 @@ CSS 변수 `--semantic-{category}-{role}` 사용.
593
593
 
594
594
  | 카테고리 | 컴포넌트 |
595
595
  |---|---|
596
- | 입력 | `Button`, `Input`, `PasswordInput`, `TextArea`, `Select`, `CheckBox`, `Radio`, `Switch`, `DatePicker`, `Calendar` |
597
- | 표시 | `Table`, `Box`, `Tag`, `Chip`, `Avatar`, `NotificationBadge`, `Skeleton`, `Spinner`, `Divider`, `EmptyState`, `Progress` |
596
+ | 입력 | `Button`, `Input`, `PasswordInput`, `TextArea`, `Select`, `CheckBox`, `Radio`, `Switch`, `DatePicker`, `TimePicker`, `Calendar` |
597
+ | 표시 | `Table`, `Box`, `Tag`, `Chip`, `Avatar`, `NotificationBadge`, `Skeleton`, `Spinner`, `Divider`, `EmptyState`, `Progress`, `Stat` |
598
598
  | 오버레이 | `Modal`, `Drawer`, `PopOver`, `Tooltip`, `Alert`, `Toast` (`ToastProvider` 필요) |
599
599
  | 네비 | `Tab`, `CardTab`, `Breadcrumb`, `Pagination`, `Steps`, `Dropdown`, `Link`, `LinkButton` |
600
600
  | 미디어 | `Video`, `FileUpload`, `ImageSelector`, `Swiper` |
601
- | 기타 | `Accordion`, `Chart`, `HtmlTypeWriter` |
601
+ | 기타 | `Accordion`, `Chart`, `HtmlTypeWriter`, `Editor` |
602
+
603
+ ### prop 규약 — `type` 과 `variant` 는 다른 축이다
604
+
605
+ | prop | 뜻 | 값 |
606
+ |---|---|---|
607
+ | `type` | **상태 색** — 이 요소가 어떤 상태인가 | `primary` `success` `error` `warning` `info` |
608
+ | `variant` | **모양·동작 변형** — 어떻게 생겼는가 | 컴포넌트마다 다름 (`outlined`/`toggle`/`dark` 등) |
609
+
610
+ - 상태 색의 `primary` 는 예전에 `brand` 였다. **`brand` 도 계속 받지만 새 코드는 `primary`** 를 쓴다.
611
+ - `Chart`·`Tab`·`Tooltip`·`Tag` 는 모양 변형을 예전에 `type` 으로 받았다. **`variant` 로 쓸 것.**
612
+ `type` 도 아직 받지만 deprecated 이고, 둘 다 주면 `variant` 가 이긴다.
613
+ - 폼 컨트롤의 검증 메시지는 전부 `validations` 다. [form.md](./components/form.md) 참고.
614
+ - **`Modal` 을 조건부 렌더하지 말 것.** `{open && <MyModal/>}` 은 닫는 애니메이션을
615
+ 없앤다. `<MyModal isOpen={open}/>` 로 계속 마운트한다 — 닫히면 스스로 `null` 을
616
+ 돌려주므로 DOM 에 안 남는다. [overlay.md](./components/overlay.md) 참고.
602
617
 
603
618
  ---
604
619
 
@@ -66,7 +66,10 @@ React 기반 디자인 시스템 라이브러리이다. Figma Make Kit과 1:1
66
66
  - [Dropdown](./components/dropdown.md)
67
67
  - [파일 & 미디어](./components/file-media.md) - FileUpload, ImageSelector, Video
68
68
  - [Swiper](./components/swiper.md)
69
+ - [Editor](./components/editor.md)
69
70
  - [Link](./components/link.md)
71
+ - [Stat](./components/stat.md)
72
+ - [TimePicker](./components/time-picker.md)
70
73
  - [HtmlTypeWriter](./components/html-typewriter.md)
71
74
  4. **컴포지션**
72
75
  - [Grid](./composition/grid.md) - Grid 시스템, 위젯 패턴
@@ -14,9 +14,9 @@ import { Box } from "@x-plat/design-system";
14
14
  | Prop | 타입 | 기본값 | 설명 |
15
15
  |------|------|--------|------|
16
16
  | children * | `ReactNode` | - | |
17
- | title | `ReactNode` | - | |
18
- | variant | `"outlined" \| "elevated" \| "flat"` | - | |
19
- | padding | `"none" \| "sm" \| "md" \| "lg"` | - | |
17
+ | title | `ReactNode` | - | 상자 위쪽 제목 |
18
+ | variant | `"outlined" \| "elevated" \| "flat"` | - | 테두리·그림자 방식 |
19
+ | padding | `"none" \| "sm" \| "md" \| "lg"` | - | 안쪽 여백 |
20
20
 
21
21
  `*` = 필수
22
22
  <!-- /props:Box -->
@@ -0,0 +1,86 @@
1
+ # Editor
2
+
3
+ 외부 라이브러리 없이 자체 구현한 리치 텍스트 에디터. **HTML 문자열**을 주고받는다.
4
+
5
+ ```tsx
6
+ const [html, setHtml] = useState("");
7
+
8
+ <Editor value={html} onChange={setHtml} />
9
+ <Editor toolbar={["bold", "italic", "link"]} minHeight={300} />
10
+ <Editor onImageUpload={async (file) => await uploadToS3(file)} />
11
+ ```
12
+
13
+ <!-- props:Editor -->
14
+ | Prop | 타입 | 기본값 | 설명 |
15
+ |------|------|--------|------|
16
+ | value | `string` | `""` | HTML 문자열. 외부에서 setter 와 함께 controlled 로 사용 |
17
+ | onChange | `(html: string) => void` | - | 내용 변경 콜백 (HTML) |
18
+ | placeholder | `string` | `"내용을 입력하세요"` | placeholder |
19
+ | readOnly | `boolean` | `false` | 읽기 전용 |
20
+ | toolbar | `EditorToolbarItem[]` | `DEFAULT_TOOLBAR` | 툴바 표시 항목. 기본: 전체 |
21
+ | enableSlashCommand | `boolean` | `true` | "/" 입력 시 슬래시 명령 popup. 기본 true |
22
+ | enableMarkdownShortcuts | `boolean` | `true` | 마크다운 단축키 (`# `, `- ` 등). 기본 true |
23
+ | onImageUpload | `(file: File) => Promise<string>` | - | 이미지 업로드 핸들러. 반환된 URL 이 src 로 사용. 없으면 URL 입력 prompt |
24
+ | minHeight | `number` | `200` | 최소 높이 (px). 기본 200 |
25
+
26
+ `*` = 필수
27
+ <!-- /props:Editor -->
28
+
29
+ ## 툴바
30
+
31
+ 기본은 13종 전부다. `toolbar` 로 골라서 줄일 수 있다.
32
+
33
+ `bold` `italic` `underline` `strikethrough` `code` `heading` `list` `ordered-list`
34
+ `blockquote` `code-block` `link` `image` `divider`
35
+
36
+ ## 마크다운 단축키
37
+
38
+ 줄 맨 앞에서 아래를 치고 **스페이스**를 누르면 변환된다. `enableMarkdownShortcuts={false}` 로 끈다.
39
+
40
+ | 입력 | 결과 |
41
+ |---|---|
42
+ | `#` `##` `###` | 제목 1·2·3 |
43
+ | `-` `*` | 글머리 기호 목록 |
44
+ | `1.` | 번호 매기기 목록 |
45
+ | `>` | 인용 |
46
+
47
+ ## 슬래시 명령
48
+
49
+ `/` 를 치면 블록 삽입 메뉴가 뜬다. 방향키로 고르고 Enter 로 넣는다.
50
+ `enableSlashCommand={false}` 로 끈다.
51
+
52
+ ## 코드 블록
53
+
54
+ 여러 줄을 선택하고 코드 블록을 누르면 **하나의** `<pre><code>` 가 된다.
55
+ 블록마다 따로 만들어지지 않는다.
56
+
57
+ ```
58
+ Enter 코드 블록 안에서 줄바꿈 (블록이 쪼개지지 않는다)
59
+ Tab 공백 2칸 들여쓰기 (포커스가 밖으로 나가지 않는다)
60
+ Enter × 빈 줄 둘이 이어지면 코드 블록을 빠져나온다
61
+ ```
62
+
63
+ 코드 블록 안에서 다시 코드 블록을 누르면 **해제**되어 일반 단락으로 돌아간다.
64
+
65
+ ## 코드 블록 안에서는
66
+
67
+ - **서식 툴바가 먹지 않는다.** 코드에 `<strong>` 이나 `<a>` 가 섞이면 복사해서
68
+ 실행할 수 없는 글자가 된다. 코드 블록 토글만 받는다.
69
+ - **마크다운 단축키와 슬래시 메뉴가 뜨지 않는다.** 코드에 `# ` 이나 `/` 는 흔하다.
70
+ - **붙여넣기는 순수 텍스트로만** 들어간다.
71
+
72
+ ## 붙여넣기
73
+
74
+ 붙여넣는 HTML 은 항상 정화된다. 허용 태그 밖은 자식을 살려 벗겨내고,
75
+ `script`·`style`·`iframe` 같은 것은 **내용까지 통째로** 버린다.
76
+ `javascript:` 와 `data:text/html` URL 도 제거된다.
77
+
78
+ ## 값 다루기
79
+
80
+ `value` 는 controlled 다. `onChange` 가 준 HTML 을 그대로 다시 넣어도 커서가 튀지 않는다 —
81
+ 에디터가 자기가 내보낸 값인지 구분한다.
82
+
83
+ - **외부에서 값을 바꾸면**(폼 리셋 등) 에디터가 그 값으로 다시 그린다. 이때 캐럿 위치는
84
+ 유지되지 않는다. 사용자가 입력 중일 때 밖에서 덮어쓰지 않는 편이 낫다.
85
+ - 저장할 때는 받은 HTML 을 **서버에서 한 번 더 정화**한다. 브라우저 정화는 표시용이지
86
+ 신뢰 경계가 아니다.
@@ -56,7 +56,7 @@ toast("error", "오류 발생", 5000);
56
56
  | children * | `ReactNode` | - | 뱃지가 올라탈 **대상**. 뱃지에 표시할 내용이 아니다. 보통 아이콘이나 버튼을 넣는다. |
57
57
  | count | `number` | - | 표시할 개수. `count` 나 `dot` 중 하나가 없으면 **뱃지가 렌더되지 않는다** (`type` 을 줘도 아무 일도 일어나지 않는다). `count={0}` 도 표시하지 않는다 — 읽지 않은 항목이 없으면 숨기는 동작이다. |
58
58
  | dot | `boolean` | `false` | 개수 대신 점만 표시한다. `count` 없이 "새 항목 있음" 만 알릴 때 쓴다 |
59
- | type | `"error" \| "success" \| "warning" \| "info" \| "brand"` | `"error"` | 뱃지 색. 뱃지가 렌더될 때만 의미가 있다 |
59
+ | type | `NotificationBadgeType \| LegacyStatusType` | `"error"` | 뱃지 색. 뱃지가 렌더될 때만 의미가 있다 |
60
60
  | maxCount | `number` | `99` | `count` 가 이 값을 넘으면 `99+` 처럼 표시한다 |
61
61
  | size | `"sm" \| "md" \| "lg"` | `"md"` | 뱃지 크기. 대상의 크기가 아니다 |
62
62
 
@@ -1,5 +1,24 @@
1
1
  # 폼 컨트롤: CheckBox / Radio / Switch
2
2
 
3
+ ## 라벨 — label / required
4
+
5
+ `Input` `TextArea` `AutoResizeTextArea` `ChatInput` `Select` `Switch` 가 `label` 을 받는다.
6
+ `htmlFor` 가 자동으로 연결되어 **라벨을 클릭하면 컨트롤에 포커스**가 간다.
7
+
8
+ ```tsx
9
+ <Input label="이름" required value={v} onChange={onChange} />
10
+ <Select label="담당자" validations={[{ status: "error", message: "선택해 주세요" }]}>…</Select>
11
+ ```
12
+
13
+ - `required` 는 라벨 뒤에 `*` 를 붙인다. 표시 전용이라 검증은 하지 않는다 —
14
+ 실제 검증은 `validations` 로 전달한다.
15
+ - `id` 를 직접 주면 그 값이 쓰인다. 안 주면 `React.useId()` 로 만든다.
16
+ - `CheckBox` `Radio` `FileUpload` `ImageSelector` 는 **자체 `label` 이 이미 있다.**
17
+ 컨트롤 옆에 붙는 형태라 의미가 달라서 그대로 둔다.
18
+ - `label` 도 `validations` 도 없으면 **래퍼가 생기지 않는다.** DOM 이 전과 같다.
19
+
20
+ ---
21
+
3
22
  ## 검증 메시지 — 모든 폼 컴포넌트 공통
4
23
 
5
24
  폼 컨트롤은 **전부 같은 `validations` prop** 으로 에러·경고·안내를 받는다.
@@ -28,11 +28,12 @@ const [activeIndex, setActiveIndex] = useState(0);
28
28
  <!-- props:Tab -->
29
29
  | Prop | 타입 | 기본값 | 설명 |
30
30
  |------|------|--------|------|
31
- | activeIndex * | `number` | - | |
32
- | tabs * | `{ value: string \| number; title: string }[]` | - | |
33
- | onChange * | `(value: { value: string \| number; title: string }, index: number) => void` | - | |
34
- | type * | `"default" \| "toggle"` | - | |
35
- | size | `"sm" \| "md" \| "lg"` | `"md"` | |
31
+ | activeIndex * | `number` | - | 활성 탭 인덱스. controlled 전용이라 onChange 와 함께 쓴다 |
32
+ | tabs * | `{ value: string \| number; title: string }[]` | - | 탭 목록. children 을 받지 않는다 |
33
+ | onChange * | `(value: { value: string \| number; title: string }, index: number) => void` | - | 첫 인자가 탭 객체, **두 번째가 인덱스**다. setActiveIndex 를 그대로 넘기면 안 된다 |
34
+ | variant | `"default" \| "toggle"` | - | 탭 모양. `default` 는 채워진 평면 탭, `toggle` 은 밑줄 인디케이터가 슬라이드 |
35
+ | type | `"default" \| "toggle"` | - | **@deprecated** 모양 변형은 `variant` 로 받는다. `type` 은 상태 색 전용이다. 기존 사용처가 깨지지 않도록 계속 받는다 — 둘 다 주면 `variant` 가 이긴다. |
36
+ | size | `"sm" \| "md" \| "lg"` | `"md"` | 탭 높이와 글자 크기 |
36
37
 
37
38
  `*` = 필수
38
39
  <!-- /props:Tab -->
@@ -130,13 +131,13 @@ const [activeIndex, setActiveIndex] = useState(0);
130
131
  <!-- props:Pagination -->
131
132
  | Prop | 타입 | 기본값 | 설명 |
132
133
  |------|------|--------|------|
133
- | current * | `number` | - | |
134
- | total * | `number` | - | |
135
- | pageSize | `number` | `10` | |
136
- | siblingCount | `number` | `1` | |
137
- | onChange | `(page: number) => void` | - | |
138
- | size | `"sm" \| "md" \| "lg"` | `"md"` | |
139
- | type | `"brand" \| "success" \| "error" \| "warning" \| "info"` | `"brand"` | |
134
+ | current * | `number` | - | 현재 페이지 (1부터 시작) |
135
+ | total * | `number` | - | **전체 아이템 수**. 페이지 수가 아니다 |
136
+ | pageSize | `number` | `10` | 한 페이지에 담기는 아이템 수. total 과 함께 페이지 수를 계산한다 |
137
+ | siblingCount | `number` | `1` | 현재 페이지 좌우로 보여줄 번호 개수 |
138
+ | onChange | `(page: number) => void` | - | 페이지를 누르면 새 페이지 번호를 준다 |
139
+ | size | `"sm" \| "md" \| "lg"` | `"md"` | 버튼 크기 |
140
+ | type | `PaginationType \| LegacyStatusType` | `"primary"` | 활성 페이지 색 |
140
141
 
141
142
  `*` = 필수
142
143
  <!-- /props:Pagination -->
@@ -169,9 +170,9 @@ const [activeIndex, setActiveIndex] = useState(0);
169
170
  <!-- props:Steps -->
170
171
  | Prop | 타입 | 기본값 | 설명 |
171
172
  |------|------|--------|------|
172
- | items * | `{ title: string; description?: string }[]` | - | |
173
- | current * | `number` | - | |
174
- | type | `"brand" \| "success" \| "error" \| "warning" \| "info"` | `"brand"` | |
173
+ | items * | `{ title: string; description?: string }[]` | - | 단계 목록. 순서대로 그려진다 |
174
+ | current * | `number` | - | 진행 중인 단계 인덱스 (0부터). 앞은 완료, 뒤는 대기로 표시된다 |
175
+ | type | `StepsType \| LegacyStatusType` | `"primary"` | 완료·진행 단계의 색 |
175
176
 
176
177
  `*` = 필수
177
178
  <!-- /props:Steps -->
@@ -15,6 +15,44 @@
15
15
  </Modal>
16
16
  ```
17
17
 
18
+ <!-- props:Modal -->
19
+ | Prop | 타입 | 기본값 | 설명 |
20
+ |------|------|--------|------|
21
+ | isOpen * | `boolean` | - | |
22
+ | onClose * | `() => void` | - | |
23
+ | children | `ReactNode` | - | |
24
+
25
+ `*` = 필수
26
+ <!-- /props:Modal -->
27
+
28
+ ### 조건부 렌더하지 말 것
29
+
30
+ ```tsx
31
+ {open && <MyModal />} // ❌ 닫는 애니메이션이 안 돈다
32
+ <MyModal isOpen={open} /> // ✅
33
+ ```
34
+
35
+ `Modal` 은 `isOpen` 이 `false` 가 되면 **애니메이션이 끝난 뒤에** 스스로 사라진다.
36
+ 호출부가 통째로 걷어내면 `isOpen={false}` 를 받아 볼 틈이 없어 그냥 없어진다.
37
+
38
+ `isOpen` 이 `false` 인 동안 `Modal` 은 `null` 을 돌려주므로 **계속 마운트해 둬도
39
+ DOM 에 아무것도 남지 않는다.** 조건부 렌더가 필요 없다.
40
+
41
+ **깨지지 않고 애니메이션만 조용히 빠지는** 종류라 버그로 안 보인다. 새 화면을 만들 때
42
+ 특히 조심할 것.
43
+
44
+ ### 열 때마다 대상이 다른 모달
45
+
46
+ 계속 마운트해 두면 안쪽 폼이 첫 렌더에서 잡은 상태가 남는다. **열릴 때만 바뀌는 `key`**
47
+ 를 줘서 그때 새로 만든다. 닫을 때는 `key` 가 안 바뀌므로 애니메이션이 끝까지 돈다.
48
+
49
+ ```tsx
50
+ <EditForm key={editingId ?? "none"} isOpen={!!editingId} target={editingId} />
51
+ ```
52
+
53
+ 이때 제목 같은 표시값은 **폼이 자기 상태에서** 가져가야 한다. 닫히는 동안 prop 은 이미
54
+ 비어 있어서, 그대로 쓰면 `수정 — undefined` 가 사라지는 상자에 스친다.
55
+
18
56
  ---
19
57
 
20
58
  ## Drawer
@@ -65,11 +103,12 @@
65
103
  | Prop | 타입 | 기본값 | 설명 |
66
104
  |------|------|--------|------|
67
105
  | children * | `ReactNode` | - | |
68
- | title | `ReactNode` | - | |
69
- | description | `ReactNode` | - | |
70
- | type | `"dark" \| "light"` | `"dark"` | |
106
+ | title | `ReactNode` | - | 말풍선 첫 줄. 굵게 표시된다 |
107
+ | description | `ReactNode` | - | 말풍선 본문. title 과 둘 중 하나는 있어야 뜬다 |
108
+ | variant | `"dark" \| "light"` | - | 말풍선 배색 |
109
+ | type | `"dark" \| "light"` | - | **@deprecated** 모양 변형은 `variant` 로 받는다. `type` 은 상태 색 전용이다. 기존 사용처가 깨지지 않도록 계속 받는다 — 둘 다 주면 `variant` 가 이긴다. |
71
110
  | follow | `boolean` | `false` | 커서를 따라다닌다. 기본값은 `false` — 트리거 요소 기준으로 고정된다 (PopOver/Dropdown/Select 와 같은 방식). 넓은 영역 안에서 **가리키는 지점**이 의미를 가질 때만 켠다. 버튼이나 아이콘처럼 대상이 작은 경우는 고정이 읽기 편하다. |
72
- | disabled | `boolean` | `false` | |
111
+ | disabled | `boolean` | `false` | 말풍선을 띄우지 않는다 |
73
112
 
74
113
  `*` = 필수
75
114
  <!-- /props:Tooltip -->
@@ -0,0 +1,31 @@
1
+ # Stat
2
+
3
+ 대시보드에서 **큰 숫자 하나를 강조**하는 타일. 값·단위·증감·설명을 한 묶음으로 보여준다.
4
+
5
+ ```tsx
6
+ <Stat label="총 호출 수" value="1,240" unit="회"
7
+ trend={{ direction: "up", value: "12.4%" }} description="최근 7일" />
8
+
9
+ <Stat size="lg" type="error" label="비용" value="$1.76" />
10
+ ```
11
+
12
+ <!-- props:Stat -->
13
+ | Prop | 타입 | 기본값 | 설명 |
14
+ |------|------|--------|------|
15
+ | label * | `ReactNode` | - | 지표 이름 (예: "총 호출 수") |
16
+ | value * | `ReactNode` | - | 강조할 값. 포맷은 호출부가 정한다 (천 단위 구분·통화 기호 등) |
17
+ | unit | `ReactNode` | - | 값 뒤에 붙는 단위 (예: "원", "회"). 값보다 작게 표시된다 |
18
+ | description | `ReactNode` | - | 값 아래 보조 설명 |
19
+ | trend | `{ direction: "up" \| "down" \| "flat"; value: ReactNode; }` | - | 증감 표시. `value` 는 이미 포맷된 문자열을 그대로 쓴다 (예: "+12.4%"). `direction` 이 색과 화살표를 정한다 |
20
+ | icon | `ReactNode` | - | 값 왼쪽에 놓을 아이콘 |
21
+ | size | `"sm" \| "md" \| "lg"` | `"md"` | 타일 크기 |
22
+ | type | `StatusTypeInput` | `"primary"` | 값 색. 상태를 나타낼 때 쓴다 |
23
+
24
+ `*` = 필수
25
+ <!-- /props:Stat -->
26
+
27
+ - `value` 는 **이미 포맷된 값**을 받는다. 천 단위 구분·통화 기호는 호출부가 정한다.
28
+ - `trend.value` 도 포맷된 문자열이다 (`"12.4%"`). `direction` 이 색과 화살표를 정한다 —
29
+ `up` 초록, `down` 빨강, `flat` 회색.
30
+ - `type` 은 **값 글자색**이다. 임계치를 넘었을 때 `error` 같은 식으로 쓴다.
31
+ - 숫자는 `tabular-nums` 로 자릿수가 흔들리지 않는다.
@@ -0,0 +1,49 @@
1
+ # TimePicker
2
+
3
+ 시각을 고르는 드롭다운. `"HH:mm"` 24시간 문자열을 주고받는다.
4
+
5
+ ```tsx
6
+ <TimePicker label="시작 시각" value={time} onChange={setTime} />
7
+ <TimePicker value={time} onChange={setTime} step={15} min="09:00" max="18:00" />
8
+ <TimePicker value={time} onChange={setTime} use12Hours /> {/* 화면만 "오후 2:30" */}
9
+ ```
10
+
11
+ <!-- props:TimePicker -->
12
+ | Prop | 타입 | 기본값 | 설명 |
13
+ |------|------|--------|------|
14
+ | value | `string` | - | `"HH:mm"` 24시간 표기. 비어 있으면 placeholder 가 보인다 |
15
+ | onChange | `(value: string) => void` | - | 시각을 고르면 `"HH:mm"` 로 준다 |
16
+ | step | `number` | `30` | 목록 간격(분). 기본 30 — 30이면 하루 48개가 나온다 |
17
+ | min | `string` | - | 고를 수 있는 가장 이른 시각 `"HH:mm"` |
18
+ | max | `string` | - | 고를 수 있는 가장 늦은 시각 `"HH:mm"` |
19
+ | use12Hours | `boolean` | `false` | 오전/오후 12시간 표기로 보여준다. onChange 는 그대로 24시간 `"HH:mm"` |
20
+ | placeholder | `string` | `"시각 선택"` | 비어 있을 때 표시할 안내 글자 |
21
+ | disabled | `boolean` | `false` | |
22
+ | size | `"sm" \| "md" \| "lg"` | `"md"` | |
23
+ | label | `ReactNode` | - | 컨트롤 위에 붙는 라벨. 클릭하면 컨트롤에 포커스가 간다 |
24
+ | required | `boolean` | - | 라벨 뒤에 필수 표시(*)를 붙인다 |
25
+ | validations | `Validation[]` | - | 컨트롤 아래에 표시할 검증 메시지. DS 폼 컴포넌트 공통 |
26
+
27
+ `*` = 필수
28
+ <!-- /props:TimePicker -->
29
+
30
+ ## DatePicker 와 함께 쓰기
31
+
32
+ **`DatePicker` 는 날짜 전용이다.** 날짜와 시각이 함께 필요하면 둘을 나란히 두고
33
+ 호출부에서 합친다.
34
+
35
+ ```tsx
36
+ <InputDatePicker value={date} onChange={setDate} />
37
+ <TimePicker value={time} onChange={setTime} />
38
+ // 저장할 때 합친다
39
+ const [h, m] = time.split(":").map(Number);
40
+ const at = new Date(date); at.setHours(h, m, 0, 0);
41
+ ```
42
+
43
+ `DatePicker` 에 시간을 끼워 넣지 않은 이유는, 하위 4종(Input/Popup/Range/Single)이
44
+ 모두 시각을 0으로 맞춘 날짜 비교를 전제하고 있어서다. 거기에 시간을 넣으면 기존
45
+ 사용처의 min/max 판정이 조용히 달라진다.
46
+
47
+ - `use12Hours` 는 **표시만** 바꾼다. `onChange` 는 늘 `"HH:mm"` 24시간이다.
48
+ - `step` 은 분 단위다. 기본 30이면 하루 48개가 나온다.
49
+ - 라벨·검증은 다른 폼 컨트롤과 같다. [form.md](./form.md) 참고.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-plat/design-system",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "XPLAT UI Design System",
5
5
  "author": "XPLAT WOONG",
6
6
  "main": "dist/index.cjs",