sellmate-design-system-react 9.0.0-beta.45 → 9.0.0-beta.47

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 (130) hide show
  1. package/AGENTS.md +62 -8
  2. package/README.md +13 -0
  3. package/dist/components/SButton/README.md +2 -0
  4. package/dist/components/SChatAttachedFile/README.md +34 -0
  5. package/dist/components/SChatAttachedFile/SChatAttachedFile.d.ts +54 -0
  6. package/dist/components/SChatAttachedFile/chatAttachedFile.config.d.ts +43 -0
  7. package/dist/components/SChatAttachedFile/index.d.ts +2 -0
  8. package/dist/components/SChatInput/README.md +44 -0
  9. package/dist/components/SChatInput/SChatInput.d.ts +60 -0
  10. package/dist/components/SChatInput/index.d.ts +1 -0
  11. package/dist/components/SChatMessage/README.md +82 -0
  12. package/dist/components/SChatMessage/SChatMessage.d.ts +107 -0
  13. package/dist/components/SChatMessage/index.d.ts +1 -0
  14. package/dist/components/SChatSystemMessage/README.md +12 -0
  15. package/dist/components/SChatSystemMessage/SChatSystemMessage.d.ts +18 -0
  16. package/dist/components/SChatSystemMessage/index.d.ts +1 -0
  17. package/dist/components/SGhostButton/README.md +2 -0
  18. package/dist/components/SIcon/README.md +4 -0
  19. package/dist/components/SIcon/icons.gen.d.ts +4 -1
  20. package/dist/components/SImage/README.md +5 -0
  21. package/dist/components/SLoginCard/README.md +34 -0
  22. package/dist/components/SLoginCard/SLoginCard.d.ts +28 -0
  23. package/dist/components/SLoginCard/index.d.ts +1 -0
  24. package/dist/components/SLoginCard/sellmate-wordmark.d.ts +13 -0
  25. package/dist/components/SSystemActionButton/README.md +20 -0
  26. package/dist/components/STextarea/README.md +2 -0
  27. package/dist/fonts/LICENSE.txt +94 -0
  28. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.0.woff2 +0 -0
  29. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.1.woff2 +0 -0
  30. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.10.woff2 +0 -0
  31. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.11.woff2 +0 -0
  32. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.12.woff2 +0 -0
  33. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.13.woff2 +0 -0
  34. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.14.woff2 +0 -0
  35. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.15.woff2 +0 -0
  36. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.16.woff2 +0 -0
  37. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.17.woff2 +0 -0
  38. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.18.woff2 +0 -0
  39. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.19.woff2 +0 -0
  40. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.2.woff2 +0 -0
  41. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.20.woff2 +0 -0
  42. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.21.woff2 +0 -0
  43. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.22.woff2 +0 -0
  44. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.23.woff2 +0 -0
  45. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.24.woff2 +0 -0
  46. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.25.woff2 +0 -0
  47. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.26.woff2 +0 -0
  48. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.27.woff2 +0 -0
  49. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.28.woff2 +0 -0
  50. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.29.woff2 +0 -0
  51. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.3.woff2 +0 -0
  52. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.30.woff2 +0 -0
  53. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.31.woff2 +0 -0
  54. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.32.woff2 +0 -0
  55. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.33.woff2 +0 -0
  56. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.34.woff2 +0 -0
  57. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.35.woff2 +0 -0
  58. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.36.woff2 +0 -0
  59. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.37.woff2 +0 -0
  60. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.38.woff2 +0 -0
  61. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.39.woff2 +0 -0
  62. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.4.woff2 +0 -0
  63. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.40.woff2 +0 -0
  64. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.41.woff2 +0 -0
  65. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.42.woff2 +0 -0
  66. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.43.woff2 +0 -0
  67. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.44.woff2 +0 -0
  68. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.45.woff2 +0 -0
  69. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.46.woff2 +0 -0
  70. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.47.woff2 +0 -0
  71. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.48.woff2 +0 -0
  72. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.49.woff2 +0 -0
  73. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.5.woff2 +0 -0
  74. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.50.woff2 +0 -0
  75. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.51.woff2 +0 -0
  76. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.52.woff2 +0 -0
  77. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.53.woff2 +0 -0
  78. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.54.woff2 +0 -0
  79. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.55.woff2 +0 -0
  80. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.56.woff2 +0 -0
  81. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.57.woff2 +0 -0
  82. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.58.woff2 +0 -0
  83. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.59.woff2 +0 -0
  84. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.6.woff2 +0 -0
  85. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.60.woff2 +0 -0
  86. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.61.woff2 +0 -0
  87. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.62.woff2 +0 -0
  88. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.63.woff2 +0 -0
  89. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.64.woff2 +0 -0
  90. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.65.woff2 +0 -0
  91. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.66.woff2 +0 -0
  92. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.67.woff2 +0 -0
  93. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.68.woff2 +0 -0
  94. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.69.woff2 +0 -0
  95. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.7.woff2 +0 -0
  96. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.70.woff2 +0 -0
  97. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.71.woff2 +0 -0
  98. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.72.woff2 +0 -0
  99. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.73.woff2 +0 -0
  100. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.74.woff2 +0 -0
  101. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.75.woff2 +0 -0
  102. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.76.woff2 +0 -0
  103. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.77.woff2 +0 -0
  104. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.78.woff2 +0 -0
  105. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.79.woff2 +0 -0
  106. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.8.woff2 +0 -0
  107. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.80.woff2 +0 -0
  108. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.81.woff2 +0 -0
  109. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.82.woff2 +0 -0
  110. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.83.woff2 +0 -0
  111. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.84.woff2 +0 -0
  112. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.85.woff2 +0 -0
  113. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.86.woff2 +0 -0
  114. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.87.woff2 +0 -0
  115. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.88.woff2 +0 -0
  116. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.89.woff2 +0 -0
  117. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.9.woff2 +0 -0
  118. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.90.woff2 +0 -0
  119. package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.91.woff2 +0 -0
  120. package/dist/index.cjs +3036 -2556
  121. package/dist/index.cjs.map +1 -1
  122. package/dist/index.d.ts +5 -0
  123. package/dist/index.js +3027 -2560
  124. package/dist/index.js.map +1 -1
  125. package/dist/llms-full.txt +285 -8
  126. package/dist/llms.txt +64 -10
  127. package/dist/pretendard.css +937 -0
  128. package/dist/styles.css +850 -2
  129. package/dist/theme.css +16 -0
  130. package/package.json +2 -2
@@ -35,10 +35,10 @@
35
35
  | 분류 | 컴포넌트 |
36
36
  | --- | --- |
37
37
  | **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
38
- | **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
38
+ | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
39
39
  | **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
40
- | **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
41
- | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
40
+ | **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(대화에 딸린 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
41
+ | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
42
42
  | **내비게이션** | `STabs` `SPagination` `SStepper` |
43
43
  | **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
44
44
  | **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
@@ -146,6 +146,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
146
146
 
147
147
  **`toLocaleDateString()` 은 쓰지 않는다** — 로케일에 따라 결과가 바뀌어 표기를 지킬 수 없다.
148
148
 
149
+ **대화 화면의 시각은 예외다.** `SChatMessage` 의 `time` 처럼 말풍선에 붙는 시각은 `오후 5:52` 로 쓴다. 여기서 시각은 정렬하거나 크기를 비교하는 값이 아니라 대화의 흐름을 짚는 표시라, 자릿수를 맞춘 `17:52` 보다 말하듯 읽히는 표기가 맞다. **이 예외는 대화 말풍선에만 열린다** — 같은 화면이라도 표·상세·목록에 들어가는 일시는 그대로 `YYYY-MM-DD HH:mm` 이다.
150
+
149
151
  ---
150
152
 
151
153
  ## 2. 조합 규칙 — 화면을 어떻게 쌓는가
@@ -159,8 +161,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
159
161
  | 층 | 무엇인가 | 컴포넌트 |
160
162
  | --- | --- | --- |
161
163
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
162
- | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
163
- | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
164
+ | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
165
+ | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SChatAttachedFile` `SImage` `SLinearProgress` `SCircleProgress` |
164
166
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
165
167
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
166
168
 
@@ -250,6 +252,13 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
250
252
 
251
253
  **보조 설명의 색** — 기본은 `text-fg-tertiary`(`grey_65`) 다. 보조 설명 안에서 위계가 한 단계 더 필요할 때만 `text-fg-secondary`(`grey_80`) → `text-fg-tertiary`(`grey_65`) 순으로 내려 쓴다 (§2-3).
252
254
 
255
+ **글꼴은 지정하지 않는다.** `theme.css` 가 문서 전역 글꼴을 `--font-base` 로 깔고 글꼴 파일(Pretendard)까지 함께 싣는다. 앱이 할 일은 없다 — 글꼴 유틸리티(기본 sans 유틸리티, 임의 값 글꼴 클래스)나 `fontFamily` 인라인 스타일을 붙일 이유가 없다. 기본 sans 유틸리티도 같은 스택을 가리킨다.
256
+
257
+ ```tsx
258
+ ✅ <p className="typo-body-sm-default">주문이 접수되었습니다.</p>
259
+ ❌ <p style={{ fontFamily: 'Pretendard' }}>주문이 접수되었습니다.</p> // 폴백 스택이 사라진다
260
+ ```
261
+
253
262
  ### 2-2. 간격 (spacing)
254
263
 
255
264
  - **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
@@ -482,6 +491,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
482
491
  | --- | --- | --- |
483
492
  | 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
484
493
  | 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
494
+ | 대화 화면 아래에서 메시지를 입력받는다 | `SChatInput` | |
485
495
  | 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
486
496
  | 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
487
497
  | 숫자(수량·금액)를 받는다 | `SNumberInput` | §3-7-13 |
@@ -497,7 +507,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
497
507
  | 목록을 좁히는 필터를 켜고 끄게 한다 | `SToggle` | §3-7-3 |
498
508
  | 자유 입력값을 여러 개 쌓게 한다 | `SChipInput` | §3-1 |
499
509
  | 입력된 값 하나를 지우거나 고치게 한다 | `SChip` | §3-1 |
500
- | 파일을 받는다 | `SFilePicker` | |
510
+ | 파일을 받는다 (고르게 한다) | `SFilePicker` | 고른·보낸 파일을 **보여주는** 것은 `SChatAttachedFile` |
511
+ | 대화에 딸린 파일을 보여준다 (보내기 전 첨부·보낸 파일) | `SChatAttachedFile` | 고르게 하는 것은 `SFilePicker` |
501
512
  | 날짜 하나를 받는다 | `SDatePicker` | §3-7-4 |
502
513
  | 연도 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerYearListbox` | §3-7-4 |
503
514
  | 연도+월 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerMonthListbox` | §3-7-4 |
@@ -517,6 +528,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
517
528
  | 여러 건을 여러 열로 보여주고 열끼리 비교하게 한다 | `STable` | §3-7-6 |
518
529
  | 항목 하나의 속성들을 `라벨: 값` 으로 보여준다 | `SKeyValueTable` | §4-4 |
519
530
  | 한 줄로 읽히는 항목을 세로로 나열한다 | `SList` + `SListItem` | §3-7-6 |
531
+ | 대화의 메시지 한 건을 보여준다 | `SChatMessage` | |
532
+ | 대화 중간에 시스템이 알리는 사실을 끼운다 (상담사 배정·대화 종료 등) | `SChatSystemMessage` | 사람이 보낸 말이면 `SChatMessage` |
520
533
  | 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
521
534
  | 부모-자식 계층을 들여쓰기로 보여준다 | `STree` | §3-7-7 |
522
535
  | 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
@@ -550,6 +563,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
550
563
  | 페이지 제목(+ 서브 텍스트·뒤로가기·우측 슬롯)을 만든다 | `SPageHeader` (`SLayout` 안에서 `SPage` 앞에 둔다) | §4-1 |
551
564
  | 제목 있는 섹션으로 묶는다 | `SSectionHeaderCard` | §3-7-8 |
552
565
  | 제목 없이 흰 면으로만 묶는다 | `SCard` | §3-7-8 |
566
+ | 통합 계정 로그인 화면을 만든다 | `SLoginCard` | §4-6 |
553
567
  | 가로선으로 끊는다 | `SDivider` | §3-6 |
554
568
  | 사용자가 영역 크기를 조절하게 한다 | `SSplitter` | §3-6 |
555
569
  | 특정 영역 안에서만 스크롤시킨다 | `SScrollArea` | |
@@ -928,7 +942,7 @@ const columns: STableColumn[] = [
928
942
  render: (row: SRow) => (
929
943
  <div className="flex items-center justify-center gap-sd-4">
930
944
  <SGhostButton size="xs" intent="action" icon="edit" ariaLabel="수정" onClick={() => editRow(row)} />
931
- <SGhostButton size="xs" icon="delete" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
945
+ <SGhostButton size="xs" icon="remove" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
932
946
  </div>
933
947
  ) },
934
948
 
@@ -1139,7 +1153,7 @@ const columns: STableColumn[] = [
1139
1153
  <SGhostButton size="xs" intent="action" icon="edit"
1140
1154
  ariaLabel="수정" onClick={() => editRow(row)} />
1141
1155
  {/* 되돌릴 수 없는 삭제일 때만 intent="danger" */}
1142
- <SGhostButton size="xs" icon="delete"
1156
+ <SGhostButton size="xs" icon="remove"
1143
1157
  ariaLabel="삭제" onClick={() => confirmRemove(row)} />
1144
1158
  </div>
1145
1159
  ) },
@@ -2234,6 +2248,45 @@ export default function ProductDetailPage() {
2234
2248
 
2235
2249
  본문 래퍼는 `className` 을 받지 않으므로(여백은 `padding` prop 으로만 받는다) 루트에서 내려 준다. **하단 액션 영역이 있으면 본문이 더 이상 마지막 자식이 아니다** — 그때는 `[&>div:nth-last-child(2)]` 로 겨눈다. 흔한 구성은 아니다 — 대부분은 `STable` 이 자기 안에서 스크롤하므로 여기까지 갈 일이 없다.
2236
2250
 
2251
+ ### 4-6. 로그인 화면 — SLoginCard
2252
+
2253
+ 앱 셸이 아직 없는 유일한 화면이다. `SLayout`·`SGnb`·`SPage` 가 없고, 회색 바탕 위에 카드 하나만 선다.
2254
+
2255
+ ```tsx
2256
+ <div className="flex h-screen items-stretch justify-center bg-(--sys-color-bg-neutralLight) p-sd-24">
2257
+ <SLoginCard
2258
+ product="AI assistant"
2259
+ title="셀메이트 AI 어시스턴트와 함께 자료 찾는 시간은 줄이고, 업무는 빠르게!"
2260
+ description="(사내 답변은 사내 문서를 기반으로 답변합니다.)"
2261
+ onLogin={signIn}
2262
+ footerNote="계정이 없다면 담당자에게 문의를 요청해 주세요."
2263
+ />
2264
+ </div>
2265
+ ```
2266
+
2267
+ **바깥 한 겹은 앱이 만든다.** 카드는 화면을 잡지 않는다 — 덕분에 로그인 화면 밖(세션 만료 재인증 등)에서도 같은 카드를 그대로 쓴다. 그 한 겹은 위 세 가지만 하면 되고, 그 이상은 하지 않는다.
2268
+
2269
+ - 바탕은 `bg-(--sys-color-bg-neutralLight)`, 여백은 `p-sd-24`. **여백은 카드가 최소 폭(368px)을 지킬 수 있는 값으로 둔다** — 좌우 합이 창 폭에서 368px 을 남기지 못하면 카드가 넘친다
2270
+ - `items-stretch` — 카드가 남은 높이를 채운다. `items-center` 로 두면 카드가 내용 높이로 줄어든다
2271
+ - `justify-center` — 카드를 가로 가운데에
2272
+
2273
+ | Prop | 용도 |
2274
+ | --- | --- |
2275
+ | `product` | 로고 아래 제품명 (`AI assistant` · `account` · `chat`) |
2276
+ | `title` | 제품을 한 줄로 소개하는 문장 |
2277
+ | `description` | 소개 바로 아래 붙는 보조 설명. 소개와 한 문단으로 읽힌다 — 없으면 생략한다 |
2278
+ | `onLogin` | 로그인 버튼을 눌렀을 때. **버튼은 카드가 그리므로 동작만 넘긴다** — 문구·색·크기는 세 제품에서 같아야 한다 |
2279
+ | `footerNote` | 버튼 아래 안내 문구 |
2280
+
2281
+ **문구 말고는 주지 않는다.** 라운드·간격·타이포·버튼 색은 카드가 쥐고 있어 제품이 달라도 같은 화면이 선다.
2282
+
2283
+ **크기는 부모가 정한다 — `className` 으로 폭을 박지 않는다.** 카드는 폭을 368~640px 사이에서 부모에 맞추고 높이도 부모가 준 만큼 채운다. 밖에서 고정하면 상하한이 무의미해져, 좁은 화면에서 함께 좁아져야 할 카드가 그대로 남아 넘친다. 좁게 세워야 하면 카드가 아니라 바깥 한 겹을 좁힌다.
2284
+
2285
+ **Sellmate 워드마크와 로그인 버튼은 카드가 직접 그린다 — prop 이 없다.** 세 제품이 같은 로고·같은 버튼 문구(`통합 계정으로 로그인`)로 서므로 넘길 것도, 갈아 끼울 것도 없다. 앱이 주는 것은 문구와 `onLogin` 동작뿐이다.
2286
+
2287
+ **로그인 카드 옆에 다른 블록을 두지 않는다.** 이 화면에서 할 일은 로그인 하나이고, 옆에 공지·배너가 붙는 순간 그 뜻이 깨진다. 안내가 필요하면 `description` 이나 `footerNote` 로 넣는다.
2288
+
2289
+
2237
2290
  ---
2238
2291
 
2239
2292
  ## 5. 자가 점검 체크리스트
@@ -2243,6 +2296,7 @@ export default function ProductDetailPage() {
2243
2296
  - [ ] 생 HTML 컨트롤(`<button>` `<input>` `<select>` `<table>` …)이 없는가
2244
2297
  - [ ] `text-[14px]`, `bg-[#...]` 같은 리터럴 임의 값이 없는가 (`var(--sys-*)` 참조는 허용)
2245
2298
  - [ ] 텍스트에 `typo-*` 프리셋을 썼는가
2299
+ - [ ] 글꼴을 직접 지정하지 않았는가 (글꼴 유틸리티·`fontFamily` 인라인 ❌ — 전역 기본이 이미 적용된다, §2-1)
2246
2300
  - [ ] 간격이 전부 `sd-` 접두 스케일 값인가 (`gap-13`·`gap-sd-13` ❌ → `gap-sd-12` ✅)
2247
2301
  - [ ] 본문이 12px(`typo-body-sm-default`)인가 (14px 본문 ❌)
2248
2302
  - [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
@@ -2801,6 +2855,7 @@ export const BUTTON_SIZES = ['xs', 'sm', 'md', 'lg'] as const;
2801
2855
  - [SFooter](../SFooter)
2802
2856
  - [SKeyValueTable](../SKeyValueTable)
2803
2857
  - [SLoadingModal](../SLoadingModal)
2858
+ - [SLoginCard](../SLoginCard)
2804
2859
  - [SPopover](../SPopover)
2805
2860
  - [SToast](../SToast)
2806
2861
 
@@ -3060,6 +3115,169 @@ export type SCalloutMessage = string | SCalloutMessage[];
3060
3115
 
3061
3116
  ---
3062
3117
 
3118
+ # SChatAttachedFile
3119
+
3120
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
3121
+
3122
+ ### SChatAttachedFile
3123
+
3124
+ #### Props
3125
+
3126
+ | Prop | Type | Default | Description |
3127
+ |------|------|---------|-------------|
3128
+ | `name` | `string` | — | 파일명. 칸을 넘치면 말줄임된다 — 칸의 폭은 부모가 정한다. |
3129
+ | `extension?` | `string` | — | 보조 줄에 쓸 형식. 주지 않으면 **파일명의 마지막 점 뒤**를 쓴다. 파일명에서 뽑는 것이 틀리는 자리에서만 준다 — 확장자가 없거나(`README`), 확장자처럼 안 생겼거나(`backup.2026` → `2026`), 두 마디인 경우(`archive.tar.gz` → `gz`). 그 밖에는 주지 않는 편이 낫다. 파일명과 어긋난 형식이 적히는 일을 막는다. 앞의 점과 대소문자는 알아서 정리한다 — `'.CSV'` 와 `'csv'` 가 같게 나온다. |
3130
+ | `size?` | `number` | — | 파일 크기(바이트). 형식(`extension`)과 묶어 `csv / 9.4KB` 로 그린다. **바이트 수를 그대로 넘긴다** — `'9.4KB'` 같은 완성된 문자열이 아니다. 표기를 앱이 만들면 같은 파일이 화면마다 다른 크기로 읽히므로 서식은 디자인 시스템이 쥔다. `attached` 는 `File.size`, `sent` 은 서버가 준 바이트 수를 그대로 넘기면 된다. **`sent` 에서는 필수다** — 보낸 파일은 확장자와 크기를 반드시 밝힌다. `attached` 에는 넘겨도 그리지 않는다(한 줄짜리 칸이다). 앱이 데이터를 한 번만 매핑해 자리와 무관하게 넘길 수 있도록 `attached` 에서 막지는 않고 무시한다. |
3131
+ | `variant` | `'attached' \| 'sent'` | — | 파일 칸이 서는 자리. **크기·면·선·글자·아이콘이 전부 여기서 갈린다.** `attached` 는 아직 보내지 않은 첨부다 — 입력창 위에 서는 한 줄짜리 칸이고 눌러 뺄 수 있다. `sent` 은 이미 보낸 파일이다 — 대화창에 서는 두 줄짜리 칸이고 `size` 가 **필수**다. |
3132
+
3133
+ #### Events
3134
+
3135
+ | Event | Type | Description |
3136
+ |-------|------|-------------|
3137
+ | `onRemove` | `() => void` | 제거 버튼을 눌렀을 때. 주지 않으면 버튼이 서지 않는다. **`attached` 에만 쓰인다** — 이미 보낸 파일은 무를 수 없으므로 `sent` 에는 그리지 않는다. |
3138
+
3139
+ ## Dependencies
3140
+
3141
+ ### Depends on
3142
+
3143
+ - [SIcon](../SIcon)
3144
+
3145
+ ### Graph
3146
+
3147
+ ---
3148
+
3149
+ # SChatInput
3150
+
3151
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
3152
+
3153
+ ### SChatInput
3154
+
3155
+ #### Props
3156
+
3157
+ | Prop | Type | Default | Description |
3158
+ |------|------|---------|-------------|
3159
+ | `value?` | `string` | `''` | 입력값 (제어) |
3160
+ | `placeholder?` | `string` | `DEFAULT_PLACEHOLDER` | 안내 문구. 세 제품이 같은 말로 서도록 기본값을 쥐고 있다 |
3161
+ | `disabled?` | `boolean` | `false` | 비활성 |
3162
+ | `busy?` | `boolean` | `false` | 응답을 기다리는 중인지. 켜면 전송 버튼 자리에 정지 버튼이 서고 Enter 전송이 잠긴다 — 답이 오는 동안 또 보내지 못하게 막는 자리다. |
3163
+ | `footerAction?` | `ReactNode` | — | 입력창 아래 보조 액션 슬롯. 없으면 그 줄이 통째로 빠진다. 상담 종료처럼 **대화 자체를 끝내는, 전송과 층이 다른 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 입력창은 가운데에 세우는 것까지만 한다 — `STextLink` 를 넣는 것이 보통이다. |
3164
+ | `maxRows?` | `number` | `DEFAULT_MAX_ROWS` | 입력창이 자랄 수 있는 최대 줄 수. 여기 닿으면 더 자라지 않고 그 안에서 스크롤한다. |
3165
+ | `className?` | `string` | — | 바깥 컨테이너 className (안쪽 textarea 가 아니다) |
3166
+ | `style?` | `CSSProperties` | — | 바깥 컨테이너 style |
3167
+ | `textareaClassName?` | `string` | — | 안쪽 textarea className |
3168
+
3169
+ #### Events
3170
+
3171
+ | Event | Type | Description |
3172
+ |-------|------|-------------|
3173
+ | `onValueChange` | `(value: string) => void` | 값 변경 |
3174
+ | `onSend` | `(value: string) => void` | 전송. 전송 버튼을 누르거나 Enter 를 쳤을 때 현재 값을 넘긴다. **값이 비면 전송 버튼이 잠긴다** — 빈 메시지는 보낼 수 없다. |
3175
+ | `onAttach` | `() => void` | 파일 첨부. **주면 입력창 왼쪽에 첨부 버튼이 선다** |
3176
+ | `onStop` | `() => void` | 정지. `busy` 일 때 정지 버튼을 누르면 불린다 |
3177
+
3178
+ ## Dependencies
3179
+
3180
+ ### Depends on
3181
+
3182
+ - [SGhostButton](../SGhostButton)
3183
+ - [STextarea](../STextarea)
3184
+
3185
+ ### Graph
3186
+
3187
+ ---
3188
+
3189
+ # SChatMessage
3190
+
3191
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
3192
+
3193
+ ### SChatMessage
3194
+
3195
+ #### Props
3196
+
3197
+ | Prop | Type | Default | Description |
3198
+ |------|------|---------|-------------|
3199
+ | `direction` | `SChatMessageDirection` | `'incoming'` | 받은 메시지(왼쪽)인지 보낸 메시지(오른쪽)인지. **아바타와 이름 유무도 여기서 갈린다** — 받은 메시지에만 아바타와 `sender` 가 선다. 보낸 메시지는 내가 보냈다는 것이 자리로 이미 드러나 얼굴도 이름도 필요 없다. |
3200
+ | `sender?` | `ReactNode` | — | 말풍선 위에 붙는 보낸 사람 이름. 없으면 자리째 빠진다. **받은 메시지에만 쓰인다** — 보낸 메시지에는 넘겨도 그리지 않는다(`direction` 참고). 앱이 데이터를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다. |
3201
+ | `time?` | `ReactNode` | — | 말풍선 아래에 붙는 시각. 표기는 앱이 정해 문자열로 넘긴다 — 대화 화면의 시각은 `오후 5:52` 처럼 읽는 표기를 쓰므로 카드가 서식을 강제하지 않는다 (AGENTS.md §1-4 예외). |
3202
+ | `readStatus?` | `SChatMessageReadStatus` | — | 상대가 읽었는지. 시각 왼쪽에 `읽음`·`안 읽음` 으로 붙고, 주지 않으면 표시하지 않는다. **내가 보낸 메시지에만 붙인다** — 받은 메시지에 읽음 표시를 다는 것은 뜻이 없다. |
3203
+ | `avatarSrc?` | `string` | — | 아바타 이미지 경로. 없으면 사람 아이콘 자리표시가 선다. **받은 메시지에만 쓰인다** — 보낸 메시지에는 아바타가 서지 않는다(`direction` 참고). |
3204
+ | `avatarAlt?` | `string` | — | 아바타 대체 텍스트 |
3205
+ | `continued?` | `boolean` | `false` | 같은 사람이 이어서 말한 묶음인지. 켜면 **아바타와 `sender` 를 그리지 않는다** — 말풍선이 위 묶음과 같은 선에서 시작해 한 사람의 말로 이어 읽힌다. 받은 메시지에서는 아바타 자리를 비워 둔 채 남긴다 — 안 남기면 말풍선이 44px 왼쪽으로 밀려 위 묶음과 어긋난다. 보낸 메시지는 아바타가 없으므로 이름만 숨긴다. `time`·`readStatus` 는 그대로 나온다. 시간이 벌어져 묶음을 나눴어도 얼굴은 한 번만 보이면 되고 시각은 묶음마다 필요하기 때문이다. **얼마나 벌어져야 묶음을 나눌지는 앱이 정한다** — 30분이든 하루든 대화의 성격에 달렸으므로 컴포넌트는 기준을 갖지 않고 스위치만 연다. |
3206
+ | `variant?` | `SChatMessageVariant` | `'default'` | 말풍선 종류. `template` 이면 말풍선이 옅은 파랑 면으로 서서 미리 등록된 서식으로 보내진 메시지임을 드러낸다. **보낸 메시지(`direction="outgoing"`)에만 쓰인다** — 받은 메시지에는 넘겨도 그리지 않는다. 템플릿은 사업자가 보내는 것이라 받은 쪽에는 뜻이 없다. 앱이 메시지를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다. |
3207
+ | `actions?` | `ReactNode` | — | 시각 아래에 붙는 동작 슬롯. 없으면 그 줄이 통째로 빠진다. 「상담원 연결」처럼 **이 메시지에 이어서 할 수 있는 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 메시지는 줄로 세우고 방향에 맞춰 정렬하는 것까지만 한다. 시각과 같은 선에서 시작한다 — 말풍선 끝에서 한 칸 안이다. |
3208
+ | `messages?` | `ReactNode[]` | — | 말풍선 목록. **한 항목이 말풍선 하나**가 되어 위에서 아래로 쌓인다. 한 사람이 연달아 말하면 아바타·이름·시각은 한 번만 서고 말풍선만 늘어난다 — 그 묶음이 이 컴포넌트 한 개다. 말풍선 안에는 글뿐 아니라 버튼 같은 요소도 올 수 있다. |
3209
+
3210
+ ## Types
3211
+
3212
+ ### SChatMessageDirection
3213
+
3214
+ ```ts
3215
+ export type SChatMessageDirection = (typeof CHAT_MESSAGE_DIRECTIONS)[number];
3216
+ ```
3217
+
3218
+ ### SChatMessageReadStatus
3219
+
3220
+ ```ts
3221
+ export type SChatMessageReadStatus = (typeof CHAT_MESSAGE_READ_STATUSES)[number];
3222
+ ```
3223
+
3224
+ ### SChatMessageVariant
3225
+
3226
+ ```ts
3227
+ export type SChatMessageVariant = (typeof CHAT_MESSAGE_VARIANTS)[number];
3228
+ ```
3229
+
3230
+ ### CHAT_MESSAGE_DIRECTIONS
3231
+
3232
+ ```ts
3233
+ /** 메시지가 오는 쪽 — 받은 메시지(incoming)는 왼쪽, 보낸 메시지(outgoing)는 오른쪽에 선다. */
3234
+ export const CHAT_MESSAGE_DIRECTIONS = ['incoming', 'outgoing'] as const;
3235
+ ```
3236
+
3237
+ ### CHAT_MESSAGE_READ_STATUSES
3238
+
3239
+ ```ts
3240
+ /** 상대가 내 메시지를 읽었는지. 시각 왼쪽에 붙는다. */
3241
+ export const CHAT_MESSAGE_READ_STATUSES = ['read', 'unread'] as const;
3242
+ ```
3243
+
3244
+ ### CHAT_MESSAGE_VARIANTS
3245
+
3246
+ ```ts
3247
+ /**
3248
+ * 말풍선의 종류. `template` 은 미리 등록된 서식으로 보내진 메시지다.
3249
+ *
3250
+ * **보낸 메시지에서만 면이 갈린다.** 템플릿은 사업자가 보내는 것이라 받은 메시지에는
3251
+ * 뜻이 없고, 받은 메시지는 템플릿이든 아니든 같은 면으로 선다.
3252
+ */
3253
+ export const CHAT_MESSAGE_VARIANTS = ['default', 'template'] as const;
3254
+ ```
3255
+
3256
+ ## Dependencies
3257
+
3258
+ ### Depends on
3259
+
3260
+ - [SIcon](../SIcon)
3261
+ - [SImage](../SImage)
3262
+
3263
+ ### Graph
3264
+
3265
+ ---
3266
+
3267
+ # SChatSystemMessage
3268
+
3269
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
3270
+
3271
+ ### SChatSystemMessage
3272
+
3273
+ #### Props
3274
+
3275
+ | Prop | Type | Default | Description |
3276
+ |------|------|---------|-------------|
3277
+ | `message?` | `string[]` | — | 안내 문구. **항목 수만큼 알약이 쌓인다** — 서로 다른 시점에 일어난 사실은 한 알약에 몰아넣지 않고 항목을 나눈다 (`SChatMessage` 의 `messages` 와 같은 규칙). 각 항목은 **인라인 HTML 문자열을 받는다.** `<b>` `<strong>` `<i>` `<em>` `<br>` `<span>` `<a>` 만 살아남고 나머지 태그·속성과 `javascript:` 같은 위험한 스킴은 `sanitizeInlineHtml` 이 제거한다. 그래도 **서버·사용자가 만든 문자열을 그대로 넘기지 않는다** — 살균은 마지막 방어선이지 입력 검증의 대체가 아니다. ```tsx <SChatSystemMessage message={['<b>김상담</b> 상담사가 배정되었습니다.']} /> ``` |
3278
+
3279
+ ---
3280
+
3063
3281
  # SCheckbox
3064
3282
 
3065
3283
  > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
@@ -4872,6 +5090,7 @@ export type SGhostButtonIntent = 'default' | 'danger' | 'action' | 'subAction' |
4872
5090
  - [SBarcodeInput](../SBarcodeInput)
4873
5091
  - [SCalendar](../SCalendar)
4874
5092
  - [SCalendarBoard](../SCalendarBoard)
5093
+ - [SChatInput](../SChatInput)
4875
5094
  - [SChip](../SChip)
4876
5095
  - [SDatePicker](../SDatePicker)
4877
5096
  - [SDateRangePicker](../SDateRangePicker)
@@ -5251,6 +5470,8 @@ export type SGuideMessage = string | SGuideMessage[];
5251
5470
  - [SAccountListBox](../SAccountListBox)
5252
5471
  - [SButton](../SButton)
5253
5472
  - [SCallout](../SCallout)
5473
+ - [SChatAttachedFile](../SChatAttachedFile)
5474
+ - [SChatMessage](../SChatMessage)
5254
5475
  - [SCheckbox](../SCheckbox)
5255
5476
  - [SChipFilter](../SChipFilter)
5256
5477
  - [SChipInput](../SChipInput)
@@ -5336,6 +5557,10 @@ export const IMAGE_FITS = ['cover', 'contain', 'fill', 'none', 'scale-down'] as
5336
5557
 
5337
5558
  ## Dependencies
5338
5559
 
5560
+ ### Used by
5561
+
5562
+ - [SChatMessage](../SChatMessage)
5563
+
5339
5564
  ### Depends on
5340
5565
 
5341
5566
  - [SCircleProgress](../SCircleProgress)
@@ -5821,6 +6046,37 @@ export type SLoadingModalState = 'loading' | 'error';
5821
6046
 
5822
6047
  ---
5823
6048
 
6049
+ # SLoginCard
6050
+
6051
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
6052
+
6053
+ ### SLoginCard
6054
+
6055
+ #### Props
6056
+
6057
+ | Prop | Type | Default | Description |
6058
+ |------|------|---------|-------------|
6059
+ | `product` | `ReactNode` | — | 로고 아래 제품명 (예: `AI assistant` · `account` · `chat`) |
6060
+ | `title` | `ReactNode` | — | 제품을 한 줄로 소개하는 문장 |
6061
+ | `description?` | `ReactNode` | — | 소개 문장 바로 아래 붙는 보조 설명. 소개와 한 덩어리로 읽힌다 |
6062
+ | `footerNote?` | `ReactNode` | — | 버튼 아래 안내 문구 (예: 계정 발급 문의 안내) |
6063
+
6064
+ #### Events
6065
+
6066
+ | Event | Type | Description |
6067
+ |-------|------|-------------|
6068
+ | `onLogin` | `() => void` | 로그인 버튼을 눌렀을 때. 버튼 자체는 카드가 그리므로 **동작만 넘긴다** — 문구·색·크기는 세 제품에서 같아야 해서 카드가 쥐고 있다. |
6069
+
6070
+ ## Dependencies
6071
+
6072
+ ### Depends on
6073
+
6074
+ - [SButton](../SButton)
6075
+
6076
+ ### Graph
6077
+
6078
+ ---
6079
+
5824
6080
  # SModal
5825
6081
 
5826
6082
  > 수작성 문서 — SModal 은 컴포넌트가 아니라 **명령형 모달 서비스**라 `docs:gen`(Props/Events 표) 대상이 아니다.
@@ -7196,6 +7452,16 @@ export const STEPPER_SIZES = ['sm', 'lg'] as const;
7196
7452
  | `color?` | `SSystemActionButtonColor` | — | 색상(놓이는 표면). light = 흰 면, dark = 오션블루 면, darker = 네이비 면. 기본 `'light'` |
7197
7453
  | `disabled?` | `boolean` | — | 비활성 여부. 기본 `false` |
7198
7454
 
7455
+ ### SSystemActionButton
7456
+
7457
+ #### Props
7458
+
7459
+ | Prop | Type | Default | Description |
7460
+ |------|------|---------|-------------|
7461
+ | `type` | `'account' \| 'select' \| 'switch' \| 'subSelect' \| 'status'` | — | 계정 행 |
7462
+ | `label?` | `string` | — | 라벨 텍스트 |
7463
+ | `option` | `<union 5종>` | — | 누르면 뜨는 계정 리스트박스. **필수다** — 계정 행은 늘 이 패널의 트리거이고, 눌러도 아무것도 뜨지 않는 계정 행은 없다. 아이콘은 `user` 로 고정이라 여기서 받지 않는다. |
7464
+
7199
7465
  ### SSystemActionButtonSelect
7200
7466
 
7201
7467
  #### Props
@@ -7236,6 +7502,16 @@ export const STEPPER_SIZES = ['sm', 'lg'] as const;
7236
7502
  | `label?` | `string` | — | 라벨 텍스트 |
7237
7503
  | `option` | `SSystemActionButtonTextOption` | — | 선행 아이콘 + 우측 액션 텍스트 |
7238
7504
 
7505
+ ### SSystemActionButtonText
7506
+
7507
+ #### Props
7508
+
7509
+ | Prop | Type | Default | Description |
7510
+ |------|------|---------|-------------|
7511
+ | `type` | `'switch' \| 'subSelect'` | — | 우측에 보조 조작 문구가 붙는 행 |
7512
+ | `label?` | `string` | — | 라벨 텍스트 |
7513
+ | `option` | `SSystemActionButtonTextOption \| SSystemActionButtonSubSelectOption` | — | 선행 아이콘 + 우측 액션 텍스트 |
7514
+
7239
7515
  ## Types
7240
7516
 
7241
7517
  ### SSystemActionButtonAccountOption
@@ -7810,6 +8086,7 @@ export type STextLinkSize = 'sm' | 'md' | 'lg';
7810
8086
 
7811
8087
  ### Used by
7812
8088
 
8089
+ - [SChatInput](../SChatInput)
7813
8090
  - [SKeyValueTable](../SKeyValueTable)
7814
8091
 
7815
8092
  ### Depends on
package/dist/llms.txt CHANGED
@@ -36,10 +36,10 @@
36
36
  | 분류 | 컴포넌트 |
37
37
  | --- | --- |
38
38
  | **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
39
- | **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
39
+ | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
40
40
  | **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
41
- | **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
42
- | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
41
+ | **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(대화에 딸린 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
42
+ | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
43
43
  | **내비게이션** | `STabs` `SPagination` `SStepper` |
44
44
  | **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
45
45
  | **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
@@ -147,6 +147,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
147
147
 
148
148
  **`toLocaleDateString()` 은 쓰지 않는다** — 로케일에 따라 결과가 바뀌어 표기를 지킬 수 없다.
149
149
 
150
+ **대화 화면의 시각은 예외다.** `SChatMessage` 의 `time` 처럼 말풍선에 붙는 시각은 `오후 5:52` 로 쓴다. 여기서 시각은 정렬하거나 크기를 비교하는 값이 아니라 대화의 흐름을 짚는 표시라, 자릿수를 맞춘 `17:52` 보다 말하듯 읽히는 표기가 맞다. **이 예외는 대화 말풍선에만 열린다** — 같은 화면이라도 표·상세·목록에 들어가는 일시는 그대로 `YYYY-MM-DD HH:mm` 이다.
151
+
150
152
  ---
151
153
 
152
154
  ## 2. 조합 규칙 — 화면을 어떻게 쌓는가
@@ -160,8 +162,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
160
162
  | 층 | 무엇인가 | 컴포넌트 |
161
163
  | --- | --- | --- |
162
164
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
163
- | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
164
- | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
165
+ | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
166
+ | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SChatAttachedFile` `SImage` `SLinearProgress` `SCircleProgress` |
165
167
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
166
168
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
167
169
 
@@ -251,6 +253,13 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
251
253
 
252
254
  **보조 설명의 색** — 기본은 `text-fg-tertiary`(`grey_65`) 다. 보조 설명 안에서 위계가 한 단계 더 필요할 때만 `text-fg-secondary`(`grey_80`) → `text-fg-tertiary`(`grey_65`) 순으로 내려 쓴다 (§2-3).
253
255
 
256
+ **글꼴은 지정하지 않는다.** `theme.css` 가 문서 전역 글꼴을 `--font-base` 로 깔고 글꼴 파일(Pretendard)까지 함께 싣는다. 앱이 할 일은 없다 — 글꼴 유틸리티(기본 sans 유틸리티, 임의 값 글꼴 클래스)나 `fontFamily` 인라인 스타일을 붙일 이유가 없다. 기본 sans 유틸리티도 같은 스택을 가리킨다.
257
+
258
+ ```tsx
259
+ ✅ <p className="typo-body-sm-default">주문이 접수되었습니다.</p>
260
+ ❌ <p style={{ fontFamily: 'Pretendard' }}>주문이 접수되었습니다.</p> // 폴백 스택이 사라진다
261
+ ```
262
+
254
263
  ### 2-2. 간격 (spacing)
255
264
 
256
265
  - **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
@@ -483,6 +492,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
483
492
  | --- | --- | --- |
484
493
  | 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
485
494
  | 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
495
+ | 대화 화면 아래에서 메시지를 입력받는다 | `SChatInput` | |
486
496
  | 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
487
497
  | 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
488
498
  | 숫자(수량·금액)를 받는다 | `SNumberInput` | §3-7-13 |
@@ -498,7 +508,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
498
508
  | 목록을 좁히는 필터를 켜고 끄게 한다 | `SToggle` | §3-7-3 |
499
509
  | 자유 입력값을 여러 개 쌓게 한다 | `SChipInput` | §3-1 |
500
510
  | 입력된 값 하나를 지우거나 고치게 한다 | `SChip` | §3-1 |
501
- | 파일을 받는다 | `SFilePicker` | |
511
+ | 파일을 받는다 (고르게 한다) | `SFilePicker` | 고른·보낸 파일을 **보여주는** 것은 `SChatAttachedFile` |
512
+ | 대화에 딸린 파일을 보여준다 (보내기 전 첨부·보낸 파일) | `SChatAttachedFile` | 고르게 하는 것은 `SFilePicker` |
502
513
  | 날짜 하나를 받는다 | `SDatePicker` | §3-7-4 |
503
514
  | 연도 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerYearListbox` | §3-7-4 |
504
515
  | 연도+월 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerMonthListbox` | §3-7-4 |
@@ -518,6 +529,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
518
529
  | 여러 건을 여러 열로 보여주고 열끼리 비교하게 한다 | `STable` | §3-7-6 |
519
530
  | 항목 하나의 속성들을 `라벨: 값` 으로 보여준다 | `SKeyValueTable` | §4-4 |
520
531
  | 한 줄로 읽히는 항목을 세로로 나열한다 | `SList` + `SListItem` | §3-7-6 |
532
+ | 대화의 메시지 한 건을 보여준다 | `SChatMessage` | |
533
+ | 대화 중간에 시스템이 알리는 사실을 끼운다 (상담사 배정·대화 종료 등) | `SChatSystemMessage` | 사람이 보낸 말이면 `SChatMessage` |
521
534
  | 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
522
535
  | 부모-자식 계층을 들여쓰기로 보여준다 | `STree` | §3-7-7 |
523
536
  | 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
@@ -551,6 +564,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
551
564
  | 페이지 제목(+ 서브 텍스트·뒤로가기·우측 슬롯)을 만든다 | `SPageHeader` (`SLayout` 안에서 `SPage` 앞에 둔다) | §4-1 |
552
565
  | 제목 있는 섹션으로 묶는다 | `SSectionHeaderCard` | §3-7-8 |
553
566
  | 제목 없이 흰 면으로만 묶는다 | `SCard` | §3-7-8 |
567
+ | 통합 계정 로그인 화면을 만든다 | `SLoginCard` | §4-6 |
554
568
  | 가로선으로 끊는다 | `SDivider` | §3-6 |
555
569
  | 사용자가 영역 크기를 조절하게 한다 | `SSplitter` | §3-6 |
556
570
  | 특정 영역 안에서만 스크롤시킨다 | `SScrollArea` | |
@@ -929,7 +943,7 @@ const columns: STableColumn[] = [
929
943
  render: (row: SRow) => (
930
944
  <div className="flex items-center justify-center gap-sd-4">
931
945
  <SGhostButton size="xs" intent="action" icon="edit" ariaLabel="수정" onClick={() => editRow(row)} />
932
- <SGhostButton size="xs" icon="delete" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
946
+ <SGhostButton size="xs" icon="remove" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
933
947
  </div>
934
948
  ) },
935
949
 
@@ -1140,7 +1154,7 @@ const columns: STableColumn[] = [
1140
1154
  <SGhostButton size="xs" intent="action" icon="edit"
1141
1155
  ariaLabel="수정" onClick={() => editRow(row)} />
1142
1156
  {/* 되돌릴 수 없는 삭제일 때만 intent="danger" */}
1143
- <SGhostButton size="xs" icon="delete"
1157
+ <SGhostButton size="xs" icon="remove"
1144
1158
  ariaLabel="삭제" onClick={() => confirmRemove(row)} />
1145
1159
  </div>
1146
1160
  ) },
@@ -2235,6 +2249,45 @@ export default function ProductDetailPage() {
2235
2249
 
2236
2250
  본문 래퍼는 `className` 을 받지 않으므로(여백은 `padding` prop 으로만 받는다) 루트에서 내려 준다. **하단 액션 영역이 있으면 본문이 더 이상 마지막 자식이 아니다** — 그때는 `[&>div:nth-last-child(2)]` 로 겨눈다. 흔한 구성은 아니다 — 대부분은 `STable` 이 자기 안에서 스크롤하므로 여기까지 갈 일이 없다.
2237
2251
 
2252
+ ### 4-6. 로그인 화면 — SLoginCard
2253
+
2254
+ 앱 셸이 아직 없는 유일한 화면이다. `SLayout`·`SGnb`·`SPage` 가 없고, 회색 바탕 위에 카드 하나만 선다.
2255
+
2256
+ ```tsx
2257
+ <div className="flex h-screen items-stretch justify-center bg-(--sys-color-bg-neutralLight) p-sd-24">
2258
+ <SLoginCard
2259
+ product="AI assistant"
2260
+ title="셀메이트 AI 어시스턴트와 함께 자료 찾는 시간은 줄이고, 업무는 빠르게!"
2261
+ description="(사내 답변은 사내 문서를 기반으로 답변합니다.)"
2262
+ onLogin={signIn}
2263
+ footerNote="계정이 없다면 담당자에게 문의를 요청해 주세요."
2264
+ />
2265
+ </div>
2266
+ ```
2267
+
2268
+ **바깥 한 겹은 앱이 만든다.** 카드는 화면을 잡지 않는다 — 덕분에 로그인 화면 밖(세션 만료 재인증 등)에서도 같은 카드를 그대로 쓴다. 그 한 겹은 위 세 가지만 하면 되고, 그 이상은 하지 않는다.
2269
+
2270
+ - 바탕은 `bg-(--sys-color-bg-neutralLight)`, 여백은 `p-sd-24`. **여백은 카드가 최소 폭(368px)을 지킬 수 있는 값으로 둔다** — 좌우 합이 창 폭에서 368px 을 남기지 못하면 카드가 넘친다
2271
+ - `items-stretch` — 카드가 남은 높이를 채운다. `items-center` 로 두면 카드가 내용 높이로 줄어든다
2272
+ - `justify-center` — 카드를 가로 가운데에
2273
+
2274
+ | Prop | 용도 |
2275
+ | --- | --- |
2276
+ | `product` | 로고 아래 제품명 (`AI assistant` · `account` · `chat`) |
2277
+ | `title` | 제품을 한 줄로 소개하는 문장 |
2278
+ | `description` | 소개 바로 아래 붙는 보조 설명. 소개와 한 문단으로 읽힌다 — 없으면 생략한다 |
2279
+ | `onLogin` | 로그인 버튼을 눌렀을 때. **버튼은 카드가 그리므로 동작만 넘긴다** — 문구·색·크기는 세 제품에서 같아야 한다 |
2280
+ | `footerNote` | 버튼 아래 안내 문구 |
2281
+
2282
+ **문구 말고는 주지 않는다.** 라운드·간격·타이포·버튼 색은 카드가 쥐고 있어 제품이 달라도 같은 화면이 선다.
2283
+
2284
+ **크기는 부모가 정한다 — `className` 으로 폭을 박지 않는다.** 카드는 폭을 368~640px 사이에서 부모에 맞추고 높이도 부모가 준 만큼 채운다. 밖에서 고정하면 상하한이 무의미해져, 좁은 화면에서 함께 좁아져야 할 카드가 그대로 남아 넘친다. 좁게 세워야 하면 카드가 아니라 바깥 한 겹을 좁힌다.
2285
+
2286
+ **Sellmate 워드마크와 로그인 버튼은 카드가 직접 그린다 — prop 이 없다.** 세 제품이 같은 로고·같은 버튼 문구(`통합 계정으로 로그인`)로 서므로 넘길 것도, 갈아 끼울 것도 없다. 앱이 주는 것은 문구와 `onLogin` 동작뿐이다.
2287
+
2288
+ **로그인 카드 옆에 다른 블록을 두지 않는다.** 이 화면에서 할 일은 로그인 하나이고, 옆에 공지·배너가 붙는 순간 그 뜻이 깨진다. 안내가 필요하면 `description` 이나 `footerNote` 로 넣는다.
2289
+
2290
+
2238
2291
  ---
2239
2292
 
2240
2293
  ## 5. 자가 점검 체크리스트
@@ -2244,6 +2297,7 @@ export default function ProductDetailPage() {
2244
2297
  - [ ] 생 HTML 컨트롤(`<button>` `<input>` `<select>` `<table>` …)이 없는가
2245
2298
  - [ ] `text-[14px]`, `bg-[#...]` 같은 리터럴 임의 값이 없는가 (`var(--sys-*)` 참조는 허용)
2246
2299
  - [ ] 텍스트에 `typo-*` 프리셋을 썼는가
2300
+ - [ ] 글꼴을 직접 지정하지 않았는가 (글꼴 유틸리티·`fontFamily` 인라인 ❌ — 전역 기본이 이미 적용된다, §2-1)
2247
2301
  - [ ] 간격이 전부 `sd-` 접두 스케일 값인가 (`gap-13`·`gap-sd-13` ❌ → `gap-sd-12` ✅)
2248
2302
  - [ ] 본문이 12px(`typo-body-sm-default`)인가 (14px 본문 ❌)
2249
2303
  - [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
@@ -2504,7 +2558,7 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
2504
2558
  --sys-color-navigation-gnb-bg-dark
2505
2559
  --sys-color-navigation-gnb-bg-system
2506
2560
 
2507
- ## 3. 컴포넌트 인덱스 (72종)
2561
+ ## 3. 컴포넌트 인덱스 (77종)
2508
2562
 
2509
2563
  **Props 는 이 문서에 없다.** 쓸 컴포넌트를 정했으면 그 컴포넌트의 README 를 읽는다.
2510
2564
 
@@ -2513,4 +2567,4 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
2513
2567
  예) SButton 을 쓴다면 `dist/components/SButton/README.md`.
2514
2568
  prop 을 추측해서 쓰지 말고 반드시 README 로 확인한다 (분류별 용도는 §1 의 "전체 컴포넌트 인덱스" 참조).
2515
2569
 
2516
- SAccountListBox · SActionModal · SBadge · SBarcodeInput · SButton · SCalendar · SCalendarBoard · SCallout · SCard · SCheckbox · SChip · SChipFilter · SChipInput · SCircleProgress · SConfirmModal · SDatePicker · SDateRangePicker · SDivider · SDraggableItem · SDraggableList · SDrawer · SDropdownButton · SEditor · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGnbSystem · SGuide · SIcon · SImage · SInput · SKeyValueTable · SLayout · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SModal · SNumberInput · SPage · SPagination · SPopover · SPopup · SPortal · SRadio · SRadioButton · SRadioCard · SScrollArea · SSearchInput · SSectionHeaderCard · SSelect · SSplitter · SStepper · SSwitch · SSystemActionButton · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · SToast · SToggle · STooltip · STree
2570
+ SAccountListBox · SActionModal · SBadge · SBarcodeInput · SButton · SCalendar · SCalendarBoard · SCallout · SCard · SChatAttachedFile · SChatInput · SChatMessage · SChatSystemMessage · SCheckbox · SChip · SChipFilter · SChipInput · SCircleProgress · SConfirmModal · SDatePicker · SDateRangePicker · SDivider · SDraggableItem · SDraggableList · SDrawer · SDropdownButton · SEditor · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGnbSystem · SGuide · SIcon · SImage · SInput · SKeyValueTable · SLayout · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SLoginCard · SModal · SNumberInput · SPage · SPagination · SPopover · SPopup · SPortal · SRadio · SRadioButton · SRadioCard · SScrollArea · SSearchInput · SSectionHeaderCard · SSelect · SSplitter · SStepper · SSwitch · SSystemActionButton · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · SToast · SToggle · STooltip · STree