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.
- package/AGENTS.md +62 -8
- package/README.md +13 -0
- package/dist/components/SButton/README.md +2 -0
- package/dist/components/SChatAttachedFile/README.md +34 -0
- package/dist/components/SChatAttachedFile/SChatAttachedFile.d.ts +54 -0
- package/dist/components/SChatAttachedFile/chatAttachedFile.config.d.ts +43 -0
- package/dist/components/SChatAttachedFile/index.d.ts +2 -0
- package/dist/components/SChatInput/README.md +44 -0
- package/dist/components/SChatInput/SChatInput.d.ts +60 -0
- package/dist/components/SChatInput/index.d.ts +1 -0
- package/dist/components/SChatMessage/README.md +82 -0
- package/dist/components/SChatMessage/SChatMessage.d.ts +107 -0
- package/dist/components/SChatMessage/index.d.ts +1 -0
- package/dist/components/SChatSystemMessage/README.md +12 -0
- package/dist/components/SChatSystemMessage/SChatSystemMessage.d.ts +18 -0
- package/dist/components/SChatSystemMessage/index.d.ts +1 -0
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/SIcon/README.md +4 -0
- package/dist/components/SIcon/icons.gen.d.ts +4 -1
- package/dist/components/SImage/README.md +5 -0
- package/dist/components/SLoginCard/README.md +34 -0
- package/dist/components/SLoginCard/SLoginCard.d.ts +28 -0
- package/dist/components/SLoginCard/index.d.ts +1 -0
- package/dist/components/SLoginCard/sellmate-wordmark.d.ts +13 -0
- package/dist/components/SSystemActionButton/README.md +20 -0
- package/dist/components/STextarea/README.md +2 -0
- package/dist/fonts/LICENSE.txt +94 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.0.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.1.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.10.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.11.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.12.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.13.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.14.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.15.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.16.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.17.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.18.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.19.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.2.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.20.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.21.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.22.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.23.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.24.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.25.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.26.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.27.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.28.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.29.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.3.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.30.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.31.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.32.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.33.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.34.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.35.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.36.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.37.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.38.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.39.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.4.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.40.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.41.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.42.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.43.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.44.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.45.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.46.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.47.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.48.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.49.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.5.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.50.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.51.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.52.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.53.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.54.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.55.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.56.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.57.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.58.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.59.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.6.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.60.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.61.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.62.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.63.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.64.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.65.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.66.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.67.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.68.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.69.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.7.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.70.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.71.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.72.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.73.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.74.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.75.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.76.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.77.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.78.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.79.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.8.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.80.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.81.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.82.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.83.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.84.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.85.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.86.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.87.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.88.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.89.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.9.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.90.woff2 +0 -0
- package/dist/fonts/woff2-dynamic-subset/PretendardVariable.subset.91.woff2 +0 -0
- package/dist/index.cjs +3036 -2556
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3027 -2560
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +285 -8
- package/dist/llms.txt +64 -10
- package/dist/pretendard.css +937 -0
- package/dist/styles.css +850 -2
- package/dist/theme.css +16 -0
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
| 분류 | 컴포넌트 |
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
|
|
30
|
-
| **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
30
|
+
| **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
31
31
|
| **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
32
|
-
| **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
|
-
| **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
32
|
+
| **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(대화에 딸린 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
|
+
| **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
34
34
|
| **내비게이션** | `STabs` `SPagination` `SStepper` |
|
|
35
35
|
| **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
|
|
36
36
|
| **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
|
|
@@ -138,6 +138,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
138
138
|
|
|
139
139
|
**`toLocaleDateString()` 은 쓰지 않는다** — 로케일에 따라 결과가 바뀌어 표기를 지킬 수 없다.
|
|
140
140
|
|
|
141
|
+
**대화 화면의 시각은 예외다.** `SChatMessage` 의 `time` 처럼 말풍선에 붙는 시각은 `오후 5:52` 로 쓴다. 여기서 시각은 정렬하거나 크기를 비교하는 값이 아니라 대화의 흐름을 짚는 표시라, 자릿수를 맞춘 `17:52` 보다 말하듯 읽히는 표기가 맞다. **이 예외는 대화 말풍선에만 열린다** — 같은 화면이라도 표·상세·목록에 들어가는 일시는 그대로 `YYYY-MM-DD HH:mm` 이다.
|
|
142
|
+
|
|
141
143
|
---
|
|
142
144
|
|
|
143
145
|
## 2. 조합 규칙 — 화면을 어떻게 쌓는가
|
|
@@ -151,8 +153,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
151
153
|
| 층 | 무엇인가 | 컴포넌트 |
|
|
152
154
|
| --- | --- | --- |
|
|
153
155
|
| **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
|
|
154
|
-
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
155
|
-
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `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` |
|
|
156
|
+
| **블록** | `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` |
|
|
157
|
+
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `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` |
|
|
156
158
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
|
|
157
159
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
158
160
|
|
|
@@ -242,6 +244,13 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
242
244
|
|
|
243
245
|
**보조 설명의 색** — 기본은 `text-fg-tertiary`(`grey_65`) 다. 보조 설명 안에서 위계가 한 단계 더 필요할 때만 `text-fg-secondary`(`grey_80`) → `text-fg-tertiary`(`grey_65`) 순으로 내려 쓴다 (§2-3).
|
|
244
246
|
|
|
247
|
+
**글꼴은 지정하지 않는다.** `theme.css` 가 문서 전역 글꼴을 `--font-base` 로 깔고 글꼴 파일(Pretendard)까지 함께 싣는다. 앱이 할 일은 없다 — 글꼴 유틸리티(기본 sans 유틸리티, 임의 값 글꼴 클래스)나 `fontFamily` 인라인 스타일을 붙일 이유가 없다. 기본 sans 유틸리티도 같은 스택을 가리킨다.
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
✅ <p className="typo-body-sm-default">주문이 접수되었습니다.</p>
|
|
251
|
+
❌ <p style={{ fontFamily: 'Pretendard' }}>주문이 접수되었습니다.</p> // 폴백 스택이 사라진다
|
|
252
|
+
```
|
|
253
|
+
|
|
245
254
|
### 2-2. 간격 (spacing)
|
|
246
255
|
|
|
247
256
|
- **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
|
|
@@ -474,6 +483,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
474
483
|
| --- | --- | --- |
|
|
475
484
|
| 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
|
|
476
485
|
| 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
|
|
486
|
+
| 대화 화면 아래에서 메시지를 입력받는다 | `SChatInput` | |
|
|
477
487
|
| 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
|
|
478
488
|
| 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
|
|
479
489
|
| 숫자(수량·금액)를 받는다 | `SNumberInput` | §3-7-13 |
|
|
@@ -489,7 +499,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
489
499
|
| 목록을 좁히는 필터를 켜고 끄게 한다 | `SToggle` | §3-7-3 |
|
|
490
500
|
| 자유 입력값을 여러 개 쌓게 한다 | `SChipInput` | §3-1 |
|
|
491
501
|
| 입력된 값 하나를 지우거나 고치게 한다 | `SChip` | §3-1 |
|
|
492
|
-
| 파일을 받는다 | `SFilePicker` | |
|
|
502
|
+
| 파일을 받는다 (고르게 한다) | `SFilePicker` | 고른·보낸 파일을 **보여주는** 것은 `SChatAttachedFile` |
|
|
503
|
+
| 대화에 딸린 파일을 보여준다 (보내기 전 첨부·보낸 파일) | `SChatAttachedFile` | 고르게 하는 것은 `SFilePicker` |
|
|
493
504
|
| 날짜 하나를 받는다 | `SDatePicker` | §3-7-4 |
|
|
494
505
|
| 연도 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerYearListbox` | §3-7-4 |
|
|
495
506
|
| 연도+월 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerMonthListbox` | §3-7-4 |
|
|
@@ -509,6 +520,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
509
520
|
| 여러 건을 여러 열로 보여주고 열끼리 비교하게 한다 | `STable` | §3-7-6 |
|
|
510
521
|
| 항목 하나의 속성들을 `라벨: 값` 으로 보여준다 | `SKeyValueTable` | §4-4 |
|
|
511
522
|
| 한 줄로 읽히는 항목을 세로로 나열한다 | `SList` + `SListItem` | §3-7-6 |
|
|
523
|
+
| 대화의 메시지 한 건을 보여준다 | `SChatMessage` | |
|
|
524
|
+
| 대화 중간에 시스템이 알리는 사실을 끼운다 (상담사 배정·대화 종료 등) | `SChatSystemMessage` | 사람이 보낸 말이면 `SChatMessage` |
|
|
512
525
|
| 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
|
|
513
526
|
| 부모-자식 계층을 들여쓰기로 보여준다 | `STree` | §3-7-7 |
|
|
514
527
|
| 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
|
|
@@ -542,6 +555,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
542
555
|
| 페이지 제목(+ 서브 텍스트·뒤로가기·우측 슬롯)을 만든다 | `SPageHeader` (`SLayout` 안에서 `SPage` 앞에 둔다) | §4-1 |
|
|
543
556
|
| 제목 있는 섹션으로 묶는다 | `SSectionHeaderCard` | §3-7-8 |
|
|
544
557
|
| 제목 없이 흰 면으로만 묶는다 | `SCard` | §3-7-8 |
|
|
558
|
+
| 통합 계정 로그인 화면을 만든다 | `SLoginCard` | §4-6 |
|
|
545
559
|
| 가로선으로 끊는다 | `SDivider` | §3-6 |
|
|
546
560
|
| 사용자가 영역 크기를 조절하게 한다 | `SSplitter` | §3-6 |
|
|
547
561
|
| 특정 영역 안에서만 스크롤시킨다 | `SScrollArea` | |
|
|
@@ -920,7 +934,7 @@ const columns: STableColumn[] = [
|
|
|
920
934
|
render: (row: SRow) => (
|
|
921
935
|
<div className="flex items-center justify-center gap-sd-4">
|
|
922
936
|
<SGhostButton size="xs" intent="action" icon="edit" ariaLabel="수정" onClick={() => editRow(row)} />
|
|
923
|
-
<SGhostButton size="xs" icon="
|
|
937
|
+
<SGhostButton size="xs" icon="remove" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
|
|
924
938
|
</div>
|
|
925
939
|
) },
|
|
926
940
|
|
|
@@ -1131,7 +1145,7 @@ const columns: STableColumn[] = [
|
|
|
1131
1145
|
<SGhostButton size="xs" intent="action" icon="edit"
|
|
1132
1146
|
ariaLabel="수정" onClick={() => editRow(row)} />
|
|
1133
1147
|
{/* 되돌릴 수 없는 삭제일 때만 intent="danger" */}
|
|
1134
|
-
<SGhostButton size="xs" icon="
|
|
1148
|
+
<SGhostButton size="xs" icon="remove"
|
|
1135
1149
|
ariaLabel="삭제" onClick={() => confirmRemove(row)} />
|
|
1136
1150
|
</div>
|
|
1137
1151
|
) },
|
|
@@ -2226,6 +2240,45 @@ export default function ProductDetailPage() {
|
|
|
2226
2240
|
|
|
2227
2241
|
본문 래퍼는 `className` 을 받지 않으므로(여백은 `padding` prop 으로만 받는다) 루트에서 내려 준다. **하단 액션 영역이 있으면 본문이 더 이상 마지막 자식이 아니다** — 그때는 `[&>div:nth-last-child(2)]` 로 겨눈다. 흔한 구성은 아니다 — 대부분은 `STable` 이 자기 안에서 스크롤하므로 여기까지 갈 일이 없다.
|
|
2228
2242
|
|
|
2243
|
+
### 4-6. 로그인 화면 — SLoginCard
|
|
2244
|
+
|
|
2245
|
+
앱 셸이 아직 없는 유일한 화면이다. `SLayout`·`SGnb`·`SPage` 가 없고, 회색 바탕 위에 카드 하나만 선다.
|
|
2246
|
+
|
|
2247
|
+
```tsx
|
|
2248
|
+
<div className="flex h-screen items-stretch justify-center bg-(--sys-color-bg-neutralLight) p-sd-24">
|
|
2249
|
+
<SLoginCard
|
|
2250
|
+
product="AI assistant"
|
|
2251
|
+
title="셀메이트 AI 어시스턴트와 함께 자료 찾는 시간은 줄이고, 업무는 빠르게!"
|
|
2252
|
+
description="(사내 답변은 사내 문서를 기반으로 답변합니다.)"
|
|
2253
|
+
onLogin={signIn}
|
|
2254
|
+
footerNote="계정이 없다면 담당자에게 문의를 요청해 주세요."
|
|
2255
|
+
/>
|
|
2256
|
+
</div>
|
|
2257
|
+
```
|
|
2258
|
+
|
|
2259
|
+
**바깥 한 겹은 앱이 만든다.** 카드는 화면을 잡지 않는다 — 덕분에 로그인 화면 밖(세션 만료 재인증 등)에서도 같은 카드를 그대로 쓴다. 그 한 겹은 위 세 가지만 하면 되고, 그 이상은 하지 않는다.
|
|
2260
|
+
|
|
2261
|
+
- 바탕은 `bg-(--sys-color-bg-neutralLight)`, 여백은 `p-sd-24`. **여백은 카드가 최소 폭(368px)을 지킬 수 있는 값으로 둔다** — 좌우 합이 창 폭에서 368px 을 남기지 못하면 카드가 넘친다
|
|
2262
|
+
- `items-stretch` — 카드가 남은 높이를 채운다. `items-center` 로 두면 카드가 내용 높이로 줄어든다
|
|
2263
|
+
- `justify-center` — 카드를 가로 가운데에
|
|
2264
|
+
|
|
2265
|
+
| Prop | 용도 |
|
|
2266
|
+
| --- | --- |
|
|
2267
|
+
| `product` | 로고 아래 제품명 (`AI assistant` · `account` · `chat`) |
|
|
2268
|
+
| `title` | 제품을 한 줄로 소개하는 문장 |
|
|
2269
|
+
| `description` | 소개 바로 아래 붙는 보조 설명. 소개와 한 문단으로 읽힌다 — 없으면 생략한다 |
|
|
2270
|
+
| `onLogin` | 로그인 버튼을 눌렀을 때. **버튼은 카드가 그리므로 동작만 넘긴다** — 문구·색·크기는 세 제품에서 같아야 한다 |
|
|
2271
|
+
| `footerNote` | 버튼 아래 안내 문구 |
|
|
2272
|
+
|
|
2273
|
+
**문구 말고는 주지 않는다.** 라운드·간격·타이포·버튼 색은 카드가 쥐고 있어 제품이 달라도 같은 화면이 선다.
|
|
2274
|
+
|
|
2275
|
+
**크기는 부모가 정한다 — `className` 으로 폭을 박지 않는다.** 카드는 폭을 368~640px 사이에서 부모에 맞추고 높이도 부모가 준 만큼 채운다. 밖에서 고정하면 상하한이 무의미해져, 좁은 화면에서 함께 좁아져야 할 카드가 그대로 남아 넘친다. 좁게 세워야 하면 카드가 아니라 바깥 한 겹을 좁힌다.
|
|
2276
|
+
|
|
2277
|
+
**Sellmate 워드마크와 로그인 버튼은 카드가 직접 그린다 — prop 이 없다.** 세 제품이 같은 로고·같은 버튼 문구(`통합 계정으로 로그인`)로 서므로 넘길 것도, 갈아 끼울 것도 없다. 앱이 주는 것은 문구와 `onLogin` 동작뿐이다.
|
|
2278
|
+
|
|
2279
|
+
**로그인 카드 옆에 다른 블록을 두지 않는다.** 이 화면에서 할 일은 로그인 하나이고, 옆에 공지·배너가 붙는 순간 그 뜻이 깨진다. 안내가 필요하면 `description` 이나 `footerNote` 로 넣는다.
|
|
2280
|
+
|
|
2281
|
+
|
|
2229
2282
|
---
|
|
2230
2283
|
|
|
2231
2284
|
## 5. 자가 점검 체크리스트
|
|
@@ -2235,6 +2288,7 @@ export default function ProductDetailPage() {
|
|
|
2235
2288
|
- [ ] 생 HTML 컨트롤(`<button>` `<input>` `<select>` `<table>` …)이 없는가
|
|
2236
2289
|
- [ ] `text-[14px]`, `bg-[#...]` 같은 리터럴 임의 값이 없는가 (`var(--sys-*)` 참조는 허용)
|
|
2237
2290
|
- [ ] 텍스트에 `typo-*` 프리셋을 썼는가
|
|
2291
|
+
- [ ] 글꼴을 직접 지정하지 않았는가 (글꼴 유틸리티·`fontFamily` 인라인 ❌ — 전역 기본이 이미 적용된다, §2-1)
|
|
2238
2292
|
- [ ] 간격이 전부 `sd-` 접두 스케일 값인가 (`gap-13`·`gap-sd-13` ❌ → `gap-sd-12` ✅)
|
|
2239
2293
|
- [ ] 본문이 12px(`typo-body-sm-default`)인가 (14px 본문 ❌)
|
|
2240
2294
|
- [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
|
package/README.md
CHANGED
|
@@ -50,6 +50,19 @@ npx sellmate-ds init
|
|
|
50
50
|
`sd-` 접두로 씁니다: `p-sd-16`(16px) · `gap-sd-8`(8px). 자세한 규칙은 `AGENTS.md` §2-2.
|
|
51
51
|
- `@source` — 라이브러리 `dist`를 Tailwind 스캔 대상에 포함시켜 컴포넌트 클래스가 생성되게 합니다. **경로는 여러분의 CSS 파일 위치 기준 상대경로**이며, `node_modules` 전체가 아니라 반드시 이 패키지 `dist`만 지정하세요.
|
|
52
52
|
|
|
53
|
+
## 글꼴
|
|
54
|
+
|
|
55
|
+
`theme.css` 가 문서 전역 글꼴을 디자인 토큰 `--font-base`(Pretendard 스택)로 깝니다. 앱은 **글꼴 클래스를 붙이지 않습니다** — `font-sans` 도 같은 스택을 가리키므로 붙일 이유가 없습니다.
|
|
56
|
+
|
|
57
|
+
**글꼴 파일도 패키지에 들어 있습니다.** Pretendard Variable 다이내믹 서브셋을 `theme.css` 가 함께 싣기 때문에, 앱은 CDN 을 걸거나 폰트를 따로 설치할 필요가 없습니다. 위 **방식 1**(또는 방식 2) 설정만 하면 끝입니다.
|
|
58
|
+
|
|
59
|
+
- **CDN 을 쓰지 않습니다.** 사내망·오프라인·폐쇄망에서도 그대로 동작하고, 외부로 나가는 요청이 없습니다.
|
|
60
|
+
- **버전이 이 패키지에 고정됩니다.** CDN 최신 추적으로 글꼴이 말없이 바뀌는 일이 없습니다.
|
|
61
|
+
- 다이내믹 서브셋이라 브라우저는 화면에 실제 쓰인 글자 범위의 조각만 내려받습니다. 한글 전체 폰트(약 1MB)를 통째로 받지 않습니다. 패키지 용량은 약 3MB 늘지만 **앱 JS 번들에는 전혀 들어가지 않습니다.**
|
|
62
|
+
- 폰트 자산은 `dist/fonts/` 에 있고 라이선스(SIL OFL 1.1)는 `dist/fonts/LICENSE.txt` 입니다.
|
|
63
|
+
|
|
64
|
+
앱이 Pretendard 를 이미 자체 호스팅하고 있어도 문제되지 않습니다 — `@font-face` 선언이 겹칠 뿐, 브라우저가 실제로 내려받는 파일은 한 벌입니다.
|
|
65
|
+
|
|
53
66
|
## 설정 (방식 2 — 폴백, Tailwind 미사용)
|
|
54
67
|
|
|
55
68
|
Tailwind를 쓰지 않는 앱은 컴파일된 CSS를 직접 import 합니다.
|
|
@@ -60,6 +60,7 @@ export const BUTTON_SIZES = ['xs', 'sm', 'md', 'lg'] as const;
|
|
|
60
60
|
- [SFooter](../SFooter)
|
|
61
61
|
- [SKeyValueTable](../SKeyValueTable)
|
|
62
62
|
- [SLoadingModal](../SLoadingModal)
|
|
63
|
+
- [SLoginCard](../SLoginCard)
|
|
63
64
|
- [SPopover](../SPopover)
|
|
64
65
|
- [SToast](../SToast)
|
|
65
66
|
|
|
@@ -79,6 +80,7 @@ graph TD;
|
|
|
79
80
|
SFooter --> SButton
|
|
80
81
|
SKeyValueTable --> SButton
|
|
81
82
|
SLoadingModal --> SButton
|
|
83
|
+
SLoginCard --> SButton
|
|
82
84
|
SPopover --> SButton
|
|
83
85
|
SToast --> SButton
|
|
84
86
|
style SButton fill:#f9f,stroke:#333,stroke-width:4px
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SChatAttachedFile
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SChatAttachedFile
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `name` | `string` | — | 파일명. 칸을 넘치면 말줄임된다 — 칸의 폭은 부모가 정한다. |
|
|
12
|
+
| `extension?` | `string` | — | 보조 줄에 쓸 형식. 주지 않으면 **파일명의 마지막 점 뒤**를 쓴다. 파일명에서 뽑는 것이 틀리는 자리에서만 준다 — 확장자가 없거나(`README`), 확장자처럼 안 생겼거나(`backup.2026` → `2026`), 두 마디인 경우(`archive.tar.gz` → `gz`). 그 밖에는 주지 않는 편이 낫다. 파일명과 어긋난 형식이 적히는 일을 막는다. 앞의 점과 대소문자는 알아서 정리한다 — `'.CSV'` 와 `'csv'` 가 같게 나온다. |
|
|
13
|
+
| `size?` | `number` | — | 파일 크기(바이트). 형식(`extension`)과 묶어 `csv / 9.4KB` 로 그린다. **바이트 수를 그대로 넘긴다** — `'9.4KB'` 같은 완성된 문자열이 아니다. 표기를 앱이 만들면 같은 파일이 화면마다 다른 크기로 읽히므로 서식은 디자인 시스템이 쥔다. `attached` 는 `File.size`, `sent` 은 서버가 준 바이트 수를 그대로 넘기면 된다. **`sent` 에서는 필수다** — 보낸 파일은 확장자와 크기를 반드시 밝힌다. `attached` 에는 넘겨도 그리지 않는다(한 줄짜리 칸이다). 앱이 데이터를 한 번만 매핑해 자리와 무관하게 넘길 수 있도록 `attached` 에서 막지는 않고 무시한다. |
|
|
14
|
+
| `variant` | `'attached' \| 'sent'` | — | 파일 칸이 서는 자리. **크기·면·선·글자·아이콘이 전부 여기서 갈린다.** `attached` 는 아직 보내지 않은 첨부다 — 입력창 위에 서는 한 줄짜리 칸이고 눌러 뺄 수 있다. `sent` 은 이미 보낸 파일이다 — 대화창에 서는 두 줄짜리 칸이고 `size` 가 **필수**다. |
|
|
15
|
+
|
|
16
|
+
#### Events
|
|
17
|
+
|
|
18
|
+
| Event | Type | Description |
|
|
19
|
+
|-------|------|-------------|
|
|
20
|
+
| `onRemove` | `() => void` | 제거 버튼을 눌렀을 때. 주지 않으면 버튼이 서지 않는다. **`attached` 에만 쓰인다** — 이미 보낸 파일은 무를 수 없으므로 `sent` 에는 그리지 않는다. |
|
|
21
|
+
|
|
22
|
+
## Dependencies
|
|
23
|
+
|
|
24
|
+
### Depends on
|
|
25
|
+
|
|
26
|
+
- [SIcon](../SIcon)
|
|
27
|
+
|
|
28
|
+
### Graph
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
graph TD;
|
|
32
|
+
SChatAttachedFile --> SIcon
|
|
33
|
+
style SChatAttachedFile fill:#f9f,stroke:#333,stroke-width:4px
|
|
34
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
interface SChatAttachedFileBaseProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
3
|
+
/** 파일명. 칸을 넘치면 말줄임된다 — 칸의 폭은 부모가 정한다. */
|
|
4
|
+
name: string;
|
|
5
|
+
/**
|
|
6
|
+
* 보조 줄에 쓸 형식. 주지 않으면 **파일명의 마지막 점 뒤**를 쓴다.
|
|
7
|
+
*
|
|
8
|
+
* 파일명에서 뽑는 것이 틀리는 자리에서만 준다 — 확장자가 없거나(`README`),
|
|
9
|
+
* 확장자처럼 안 생겼거나(`backup.2026` → `2026`), 두 마디인 경우(`archive.tar.gz` → `gz`).
|
|
10
|
+
* 그 밖에는 주지 않는 편이 낫다. 파일명과 어긋난 형식이 적히는 일을 막는다.
|
|
11
|
+
*
|
|
12
|
+
* 앞의 점과 대소문자는 알아서 정리한다 — `'.CSV'` 와 `'csv'` 가 같게 나온다.
|
|
13
|
+
*/
|
|
14
|
+
extension?: string;
|
|
15
|
+
/**
|
|
16
|
+
* 파일 크기(바이트). 형식(`extension`)과 묶어 `csv / 9.4KB` 로 그린다.
|
|
17
|
+
*
|
|
18
|
+
* **바이트 수를 그대로 넘긴다** — `'9.4KB'` 같은 완성된 문자열이 아니다. 표기를 앱이 만들면
|
|
19
|
+
* 같은 파일이 화면마다 다른 크기로 읽히므로 서식은 디자인 시스템이 쥔다.
|
|
20
|
+
* `attached` 는 `File.size`, `sent` 은 서버가 준 바이트 수를 그대로 넘기면 된다.
|
|
21
|
+
*
|
|
22
|
+
* **`sent` 에서는 필수다** — 보낸 파일은 확장자와 크기를 반드시 밝힌다.
|
|
23
|
+
* `attached` 에는 넘겨도 그리지 않는다(한 줄짜리 칸이다). 앱이 데이터를 한 번만 매핑해
|
|
24
|
+
* 자리와 무관하게 넘길 수 있도록 `attached` 에서 막지는 않고 무시한다.
|
|
25
|
+
*/
|
|
26
|
+
size?: number;
|
|
27
|
+
/**
|
|
28
|
+
* 제거 버튼을 눌렀을 때. 주지 않으면 버튼이 서지 않는다.
|
|
29
|
+
*
|
|
30
|
+
* **`attached` 에만 쓰인다** — 이미 보낸 파일은 무를 수 없으므로 `sent` 에는 그리지 않는다.
|
|
31
|
+
*/
|
|
32
|
+
onRemove?: () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 자리에 따라 필수 prop 이 갈린다.
|
|
36
|
+
*
|
|
37
|
+
* `sent` 은 `size` 가 **필수**다 — 보낸 파일은 확장자와 크기를 반드시 밝혀야 하는데,
|
|
38
|
+
* 선택으로 두면 빠뜨려도 타입이 통과하고 화면에서 보조 줄만 조용히 사라진다.
|
|
39
|
+
* `attached` 는 한 줄짜리 칸이라 필요 없다.
|
|
40
|
+
*/
|
|
41
|
+
export type SChatAttachedFileProps = (SChatAttachedFileBaseProps & {
|
|
42
|
+
/**
|
|
43
|
+
* 파일 칸이 서는 자리. **크기·면·선·글자·아이콘이 전부 여기서 갈린다.**
|
|
44
|
+
*
|
|
45
|
+
* `attached` 는 아직 보내지 않은 첨부다 — 입력창 위에 서는 한 줄짜리 칸이고 눌러 뺄 수 있다.
|
|
46
|
+
* `sent` 은 이미 보낸 파일이다 — 대화창에 서는 두 줄짜리 칸이고 `size` 가 **필수**다.
|
|
47
|
+
*/
|
|
48
|
+
variant: 'attached';
|
|
49
|
+
}) | (SChatAttachedFileBaseProps & {
|
|
50
|
+
variant: 'sent';
|
|
51
|
+
size: number;
|
|
52
|
+
});
|
|
53
|
+
export declare const SChatAttachedFile: import("react").ForwardRefExoticComponent<SChatAttachedFileProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 파일 칸이 서는 자리. **이 하나로 크기·면·선·글자·아이콘이 전부 갈린다.**
|
|
3
|
+
*
|
|
4
|
+
* - `attached` — 아직 보내지 않은 첨부. 입력창 위에 가로로 늘어서고 눌러 뺄 수 있다.
|
|
5
|
+
* - `sent` — 이미 보낸 파일. 대화창에 세로로 쌓이고 뺄 수 없다.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CHAT_ATTACHED_FILE_VARIANTS: readonly ["attached", "sent"];
|
|
8
|
+
export type SChatAttachedFileVariant = (typeof CHAT_ATTACHED_FILE_VARIANTS)[number];
|
|
9
|
+
interface ChatAttachedFilePreset {
|
|
10
|
+
/** 바깥 상자 — 높이·좌우 여백·항목 간격·면·선 */
|
|
11
|
+
box: string;
|
|
12
|
+
/** 클립 아이콘이 앉는 정사각 타일 */
|
|
13
|
+
tile: string;
|
|
14
|
+
/** 타일 안 클립 아이콘 크기(px) */
|
|
15
|
+
iconSize: number;
|
|
16
|
+
/** 파일명 타이포 프리셋 */
|
|
17
|
+
nameTypo: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 높이(30/56)와 타일 크기(20/28)는 대응 토큰이 없어 값으로 둔다
|
|
21
|
+
* (디자인팀 토큰 추가 요청 대상). 세로 여백이 스페이싱 스케일에 떨어지지 않아
|
|
22
|
+
* 여백을 주는 대신 높이를 고정하고 세로 가운데 정렬로 세운다.
|
|
23
|
+
*/
|
|
24
|
+
export declare const CHAT_ATTACHED_FILE_CONFIG: Record<SChatAttachedFileVariant, ChatAttachedFilePreset>;
|
|
25
|
+
/**
|
|
26
|
+
* 바이트 수 → `9.4KB` 표기.
|
|
27
|
+
*
|
|
28
|
+
* **표기를 앱이 아니라 여기서 정하는 이유**: 문자열로 받으면 제품마다 `9.4KB`·`9.4 KB`·
|
|
29
|
+
* `9,626B` 로 갈린다. 같은 파일이 화면마다 다른 크기로 읽히는 것을 막으려면 한 곳에서 만든다.
|
|
30
|
+
*
|
|
31
|
+
* 1024 배수로 올리고, 바이트는 소수점이 뜻이 없어 정수로, 그 위는 한 자리까지 쓴다(시안 표기).
|
|
32
|
+
* 금액·수량의 세 자리 콤마 규칙(AGENTS.md §1-4)과는 별개다 — 용량은 단위가 자리를 대신한다.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatFileSize(bytes: number): string;
|
|
35
|
+
/** 앱이 준 확장자를 표기 형태로 맞춘다 — 앞의 점을 떼고 소문자로. `'.CSV'` → `'csv'` */
|
|
36
|
+
export declare function normalizeExtension(extension: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* 파일명 → 소문자 확장자. 확장자가 없으면 빈 문자열.
|
|
39
|
+
*
|
|
40
|
+
* 맨 앞의 점은 확장자가 아니다(`.gitignore` 는 이름 전체가 이름이다).
|
|
41
|
+
*/
|
|
42
|
+
export declare function fileExtension(name: string): string;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SChatInput
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SChatInput
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `value?` | `string` | `''` | 입력값 (제어) |
|
|
12
|
+
| `placeholder?` | `string` | `DEFAULT_PLACEHOLDER` | 안내 문구. 세 제품이 같은 말로 서도록 기본값을 쥐고 있다 |
|
|
13
|
+
| `disabled?` | `boolean` | `false` | 비활성 |
|
|
14
|
+
| `busy?` | `boolean` | `false` | 응답을 기다리는 중인지. 켜면 전송 버튼 자리에 정지 버튼이 서고 Enter 전송이 잠긴다 — 답이 오는 동안 또 보내지 못하게 막는 자리다. |
|
|
15
|
+
| `footerAction?` | `ReactNode` | — | 입력창 아래 보조 액션 슬롯. 없으면 그 줄이 통째로 빠진다. 상담 종료처럼 **대화 자체를 끝내는, 전송과 층이 다른 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 입력창은 가운데에 세우는 것까지만 한다 — `STextLink` 를 넣는 것이 보통이다. |
|
|
16
|
+
| `maxRows?` | `number` | `DEFAULT_MAX_ROWS` | 입력창이 자랄 수 있는 최대 줄 수. 여기 닿으면 더 자라지 않고 그 안에서 스크롤한다. |
|
|
17
|
+
| `className?` | `string` | — | 바깥 컨테이너 className (안쪽 textarea 가 아니다) |
|
|
18
|
+
| `style?` | `CSSProperties` | — | 바깥 컨테이너 style |
|
|
19
|
+
| `textareaClassName?` | `string` | — | 안쪽 textarea className |
|
|
20
|
+
|
|
21
|
+
#### Events
|
|
22
|
+
|
|
23
|
+
| Event | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `onValueChange` | `(value: string) => void` | 값 변경 |
|
|
26
|
+
| `onSend` | `(value: string) => void` | 전송. 전송 버튼을 누르거나 Enter 를 쳤을 때 현재 값을 넘긴다. **값이 비면 전송 버튼이 잠긴다** — 빈 메시지는 보낼 수 없다. |
|
|
27
|
+
| `onAttach` | `() => void` | 파일 첨부. **주면 입력창 왼쪽에 첨부 버튼이 선다** |
|
|
28
|
+
| `onStop` | `() => void` | 정지. `busy` 일 때 정지 버튼을 누르면 불린다 |
|
|
29
|
+
|
|
30
|
+
## Dependencies
|
|
31
|
+
|
|
32
|
+
### Depends on
|
|
33
|
+
|
|
34
|
+
- [SGhostButton](../SGhostButton)
|
|
35
|
+
- [STextarea](../STextarea)
|
|
36
|
+
|
|
37
|
+
### Graph
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
graph TD;
|
|
41
|
+
SChatInput --> SGhostButton
|
|
42
|
+
SChatInput --> STextarea
|
|
43
|
+
style SChatInput fill:#f9f,stroke:#333,stroke-width:4px
|
|
44
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode, type TextareaHTMLAttributes } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 안쪽 textarea 로 그대로 흘러가는 네이티브 속성 — `SInput`·`STextarea` 와 같은 방식이다.
|
|
4
|
+
* 컴포넌트가 직접 다루는 것만 뺀다.
|
|
5
|
+
*/
|
|
6
|
+
export interface SChatInputProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange' | 'placeholder' | 'disabled' | 'rows' | 'className' | 'style'> {
|
|
7
|
+
/** 입력값 (제어) */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** 값 변경 */
|
|
10
|
+
onValueChange?: (value: string) => void;
|
|
11
|
+
/** 안내 문구. 세 제품이 같은 말로 서도록 기본값을 쥐고 있다 */
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
/** 비활성 */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 전송. 전송 버튼을 누르거나 Enter 를 쳤을 때 현재 값을 넘긴다.
|
|
17
|
+
*
|
|
18
|
+
* **값이 비면 전송 버튼이 잠긴다** — 빈 메시지는 보낼 수 없다.
|
|
19
|
+
*/
|
|
20
|
+
onSend?: (value: string) => void;
|
|
21
|
+
/** 파일 첨부. **주면 입력창 왼쪽에 첨부 버튼이 선다** */
|
|
22
|
+
onAttach?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* 응답을 기다리는 중인지. 켜면 전송 버튼 자리에 정지 버튼이 서고 Enter 전송이 잠긴다 —
|
|
25
|
+
* 답이 오는 동안 또 보내지 못하게 막는 자리다.
|
|
26
|
+
*/
|
|
27
|
+
busy?: boolean;
|
|
28
|
+
/** 정지. `busy` 일 때 정지 버튼을 누르면 불린다 */
|
|
29
|
+
onStop?: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* 입력창 아래 보조 액션 슬롯. 없으면 그 줄이 통째로 빠진다.
|
|
32
|
+
*
|
|
33
|
+
* 상담 종료처럼 **대화 자체를 끝내는, 전송과 층이 다른 동작**이 오는 자리다. 무엇을 놓을지는
|
|
34
|
+
* 앱이 정하고, 입력창은 가운데에 세우는 것까지만 한다 — `STextLink` 를 넣는 것이 보통이다.
|
|
35
|
+
*/
|
|
36
|
+
footerAction?: ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* 입력창이 자랄 수 있는 최대 줄 수. 여기 닿으면 더 자라지 않고 그 안에서 스크롤한다.
|
|
39
|
+
*/
|
|
40
|
+
maxRows?: number;
|
|
41
|
+
/** 바깥 컨테이너 className (안쪽 textarea 가 아니다) */
|
|
42
|
+
className?: string;
|
|
43
|
+
/** 바깥 컨테이너 style */
|
|
44
|
+
style?: CSSProperties;
|
|
45
|
+
/** 안쪽 textarea className */
|
|
46
|
+
textareaClassName?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* SChatInput — 대화 화면 맨 아래에 서는 입력창.
|
|
50
|
+
*
|
|
51
|
+
* 첨부·입력·전송을 한 줄로 세우고, 필요하면 그 아래 보조 액션(상담 종료 등)을 붙인다.
|
|
52
|
+
* Enter 로 보내고 Shift+Enter 로 줄을 바꾼다 — 한글 조합 중의 Enter 는 보내지 않는다.
|
|
53
|
+
*
|
|
54
|
+
* **글이 길어지면 입력창이 자란다** — `maxRows` 줄까지 늘어나고, 거기 닿으면 그 안에서
|
|
55
|
+
* 스크롤한다. 상한이 없으면 대화가 화면 밖으로 밀려난다.
|
|
56
|
+
*
|
|
57
|
+
* **응답을 기다리는 동안에는 `busy` 를 켠다** — 전송 버튼이 정지 버튼으로 바뀌고 Enter 전송이
|
|
58
|
+
* 잠겨, 답이 오는 중에 또 보내는 일이 막힌다.
|
|
59
|
+
*/
|
|
60
|
+
export declare const SChatInput: import("react").ForwardRefExoticComponent<SChatInputProps & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SChatInput, type SChatInputProps } from './SChatInput';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SChatMessage
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SChatMessage
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `direction` | `SChatMessageDirection` | `'incoming'` | 받은 메시지(왼쪽)인지 보낸 메시지(오른쪽)인지. **아바타와 이름 유무도 여기서 갈린다** — 받은 메시지에만 아바타와 `sender` 가 선다. 보낸 메시지는 내가 보냈다는 것이 자리로 이미 드러나 얼굴도 이름도 필요 없다. |
|
|
12
|
+
| `sender?` | `ReactNode` | — | 말풍선 위에 붙는 보낸 사람 이름. 없으면 자리째 빠진다. **받은 메시지에만 쓰인다** — 보낸 메시지에는 넘겨도 그리지 않는다(`direction` 참고). 앱이 데이터를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다. |
|
|
13
|
+
| `time?` | `ReactNode` | — | 말풍선 아래에 붙는 시각. 표기는 앱이 정해 문자열로 넘긴다 — 대화 화면의 시각은 `오후 5:52` 처럼 읽는 표기를 쓰므로 카드가 서식을 강제하지 않는다 (AGENTS.md §1-4 예외). |
|
|
14
|
+
| `readStatus?` | `SChatMessageReadStatus` | — | 상대가 읽었는지. 시각 왼쪽에 `읽음`·`안 읽음` 으로 붙고, 주지 않으면 표시하지 않는다. **내가 보낸 메시지에만 붙인다** — 받은 메시지에 읽음 표시를 다는 것은 뜻이 없다. |
|
|
15
|
+
| `avatarSrc?` | `string` | — | 아바타 이미지 경로. 없으면 사람 아이콘 자리표시가 선다. **받은 메시지에만 쓰인다** — 보낸 메시지에는 아바타가 서지 않는다(`direction` 참고). |
|
|
16
|
+
| `avatarAlt?` | `string` | — | 아바타 대체 텍스트 |
|
|
17
|
+
| `continued?` | `boolean` | `false` | 같은 사람이 이어서 말한 묶음인지. 켜면 **아바타와 `sender` 를 그리지 않는다** — 말풍선이 위 묶음과 같은 선에서 시작해 한 사람의 말로 이어 읽힌다. 받은 메시지에서는 아바타 자리를 비워 둔 채 남긴다 — 안 남기면 말풍선이 44px 왼쪽으로 밀려 위 묶음과 어긋난다. 보낸 메시지는 아바타가 없으므로 이름만 숨긴다. `time`·`readStatus` 는 그대로 나온다. 시간이 벌어져 묶음을 나눴어도 얼굴은 한 번만 보이면 되고 시각은 묶음마다 필요하기 때문이다. **얼마나 벌어져야 묶음을 나눌지는 앱이 정한다** — 30분이든 하루든 대화의 성격에 달렸으므로 컴포넌트는 기준을 갖지 않고 스위치만 연다. |
|
|
18
|
+
| `variant?` | `SChatMessageVariant` | `'default'` | 말풍선 종류. `template` 이면 말풍선이 옅은 파랑 면으로 서서 미리 등록된 서식으로 보내진 메시지임을 드러낸다. **보낸 메시지(`direction="outgoing"`)에만 쓰인다** — 받은 메시지에는 넘겨도 그리지 않는다. 템플릿은 사업자가 보내는 것이라 받은 쪽에는 뜻이 없다. 앱이 메시지를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다. |
|
|
19
|
+
| `actions?` | `ReactNode` | — | 시각 아래에 붙는 동작 슬롯. 없으면 그 줄이 통째로 빠진다. 「상담원 연결」처럼 **이 메시지에 이어서 할 수 있는 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 메시지는 줄로 세우고 방향에 맞춰 정렬하는 것까지만 한다. 시각과 같은 선에서 시작한다 — 말풍선 끝에서 한 칸 안이다. |
|
|
20
|
+
| `messages?` | `ReactNode[]` | — | 말풍선 목록. **한 항목이 말풍선 하나**가 되어 위에서 아래로 쌓인다. 한 사람이 연달아 말하면 아바타·이름·시각은 한 번만 서고 말풍선만 늘어난다 — 그 묶음이 이 컴포넌트 한 개다. 말풍선 안에는 글뿐 아니라 버튼 같은 요소도 올 수 있다. |
|
|
21
|
+
|
|
22
|
+
## Types
|
|
23
|
+
|
|
24
|
+
### SChatMessageDirection
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
export type SChatMessageDirection = (typeof CHAT_MESSAGE_DIRECTIONS)[number];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SChatMessageReadStatus
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export type SChatMessageReadStatus = (typeof CHAT_MESSAGE_READ_STATUSES)[number];
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### SChatMessageVariant
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
export type SChatMessageVariant = (typeof CHAT_MESSAGE_VARIANTS)[number];
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### CHAT_MESSAGE_DIRECTIONS
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
/** 메시지가 오는 쪽 — 받은 메시지(incoming)는 왼쪽, 보낸 메시지(outgoing)는 오른쪽에 선다. */
|
|
46
|
+
export const CHAT_MESSAGE_DIRECTIONS = ['incoming', 'outgoing'] as const;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### CHAT_MESSAGE_READ_STATUSES
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
/** 상대가 내 메시지를 읽었는지. 시각 왼쪽에 붙는다. */
|
|
53
|
+
export const CHAT_MESSAGE_READ_STATUSES = ['read', 'unread'] as const;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### CHAT_MESSAGE_VARIANTS
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
/**
|
|
60
|
+
* 말풍선의 종류. `template` 은 미리 등록된 서식으로 보내진 메시지다.
|
|
61
|
+
*
|
|
62
|
+
* **보낸 메시지에서만 면이 갈린다.** 템플릿은 사업자가 보내는 것이라 받은 메시지에는
|
|
63
|
+
* 뜻이 없고, 받은 메시지는 템플릿이든 아니든 같은 면으로 선다.
|
|
64
|
+
*/
|
|
65
|
+
export const CHAT_MESSAGE_VARIANTS = ['default', 'template'] as const;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Dependencies
|
|
69
|
+
|
|
70
|
+
### Depends on
|
|
71
|
+
|
|
72
|
+
- [SIcon](../SIcon)
|
|
73
|
+
- [SImage](../SImage)
|
|
74
|
+
|
|
75
|
+
### Graph
|
|
76
|
+
|
|
77
|
+
```mermaid
|
|
78
|
+
graph TD;
|
|
79
|
+
SChatMessage --> SIcon
|
|
80
|
+
SChatMessage --> SImage
|
|
81
|
+
style SChatMessage fill:#f9f,stroke:#333,stroke-width:4px
|
|
82
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
/** 메시지가 오는 쪽 — 받은 메시지(incoming)는 왼쪽, 보낸 메시지(outgoing)는 오른쪽에 선다. */
|
|
3
|
+
export declare const CHAT_MESSAGE_DIRECTIONS: readonly ["incoming", "outgoing"];
|
|
4
|
+
export type SChatMessageDirection = (typeof CHAT_MESSAGE_DIRECTIONS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* 말풍선의 종류. `template` 은 미리 등록된 서식으로 보내진 메시지다.
|
|
7
|
+
*
|
|
8
|
+
* **보낸 메시지에서만 면이 갈린다.** 템플릿은 사업자가 보내는 것이라 받은 메시지에는
|
|
9
|
+
* 뜻이 없고, 받은 메시지는 템플릿이든 아니든 같은 면으로 선다.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CHAT_MESSAGE_VARIANTS: readonly ["default", "template"];
|
|
12
|
+
export type SChatMessageVariant = (typeof CHAT_MESSAGE_VARIANTS)[number];
|
|
13
|
+
/** 상대가 내 메시지를 읽었는지. 시각 왼쪽에 붙는다. */
|
|
14
|
+
export declare const CHAT_MESSAGE_READ_STATUSES: readonly ["read", "unread"];
|
|
15
|
+
export type SChatMessageReadStatus = (typeof CHAT_MESSAGE_READ_STATUSES)[number];
|
|
16
|
+
export interface SChatMessageProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
17
|
+
/**
|
|
18
|
+
* 받은 메시지(왼쪽)인지 보낸 메시지(오른쪽)인지.
|
|
19
|
+
*
|
|
20
|
+
* **아바타와 이름 유무도 여기서 갈린다** — 받은 메시지에만 아바타와 `sender` 가 선다.
|
|
21
|
+
* 보낸 메시지는 내가 보냈다는 것이 자리로 이미 드러나 얼굴도 이름도 필요 없다.
|
|
22
|
+
*/
|
|
23
|
+
direction: SChatMessageDirection;
|
|
24
|
+
/**
|
|
25
|
+
* 말풍선 위에 붙는 보낸 사람 이름. 없으면 자리째 빠진다.
|
|
26
|
+
*
|
|
27
|
+
* **받은 메시지에만 쓰인다** — 보낸 메시지에는 넘겨도 그리지 않는다(`direction` 참고).
|
|
28
|
+
* 앱이 데이터를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다.
|
|
29
|
+
*/
|
|
30
|
+
sender?: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* 말풍선 아래에 붙는 시각. 표기는 앱이 정해 문자열로 넘긴다 — 대화 화면의 시각은
|
|
33
|
+
* `오후 5:52` 처럼 읽는 표기를 쓰므로 카드가 서식을 강제하지 않는다 (AGENTS.md §1-4 예외).
|
|
34
|
+
*/
|
|
35
|
+
time?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* 상대가 읽었는지. 시각 왼쪽에 `읽음`·`안 읽음` 으로 붙고, 주지 않으면 표시하지 않는다.
|
|
38
|
+
*
|
|
39
|
+
* **내가 보낸 메시지에만 붙인다** — 받은 메시지에 읽음 표시를 다는 것은 뜻이 없다.
|
|
40
|
+
*/
|
|
41
|
+
readStatus?: SChatMessageReadStatus;
|
|
42
|
+
/**
|
|
43
|
+
* 아바타 이미지 경로. 없으면 사람 아이콘 자리표시가 선다.
|
|
44
|
+
*
|
|
45
|
+
* **받은 메시지에만 쓰인다** — 보낸 메시지에는 아바타가 서지 않는다(`direction` 참고).
|
|
46
|
+
*/
|
|
47
|
+
avatarSrc?: string;
|
|
48
|
+
/** 아바타 대체 텍스트 */
|
|
49
|
+
avatarAlt?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 같은 사람이 이어서 말한 묶음인지. 켜면 **아바타와 `sender` 를 그리지 않는다** —
|
|
52
|
+
* 말풍선이 위 묶음과 같은 선에서 시작해 한 사람의 말로 이어 읽힌다.
|
|
53
|
+
*
|
|
54
|
+
* 받은 메시지에서는 아바타 자리를 비워 둔 채 남긴다 — 안 남기면 말풍선이 44px 왼쪽으로 밀려
|
|
55
|
+
* 위 묶음과 어긋난다. 보낸 메시지는 아바타가 없으므로 이름만 숨긴다.
|
|
56
|
+
*
|
|
57
|
+
* `time`·`readStatus` 는 그대로 나온다. 시간이 벌어져 묶음을 나눴어도 얼굴은 한 번만 보이면
|
|
58
|
+
* 되고 시각은 묶음마다 필요하기 때문이다.
|
|
59
|
+
*
|
|
60
|
+
* **얼마나 벌어져야 묶음을 나눌지는 앱이 정한다** — 30분이든 하루든 대화의 성격에 달렸으므로
|
|
61
|
+
* 컴포넌트는 기준을 갖지 않고 스위치만 연다.
|
|
62
|
+
*/
|
|
63
|
+
continued?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* 말풍선 종류. `template` 이면 말풍선이 옅은 파랑 면으로 서서 미리 등록된 서식으로 보내진
|
|
66
|
+
* 메시지임을 드러낸다.
|
|
67
|
+
*
|
|
68
|
+
* **보낸 메시지(`direction="outgoing"`)에만 쓰인다** — 받은 메시지에는 넘겨도 그리지 않는다.
|
|
69
|
+
* 템플릿은 사업자가 보내는 것이라 받은 쪽에는 뜻이 없다. 앱이 메시지를 한 번만 매핑해
|
|
70
|
+
* 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다.
|
|
71
|
+
*/
|
|
72
|
+
variant?: SChatMessageVariant;
|
|
73
|
+
/**
|
|
74
|
+
* 시각 아래에 붙는 동작 슬롯. 없으면 그 줄이 통째로 빠진다.
|
|
75
|
+
*
|
|
76
|
+
* 「상담원 연결」처럼 **이 메시지에 이어서 할 수 있는 동작**이 오는 자리다. 무엇을 놓을지는
|
|
77
|
+
* 앱이 정하고, 메시지는 줄로 세우고 방향에 맞춰 정렬하는 것까지만 한다.
|
|
78
|
+
*
|
|
79
|
+
* 시각과 같은 선에서 시작한다 — 말풍선 끝에서 한 칸 안이다.
|
|
80
|
+
*/
|
|
81
|
+
actions?: ReactNode;
|
|
82
|
+
/**
|
|
83
|
+
* 말풍선 목록. **한 항목이 말풍선 하나**가 되어 위에서 아래로 쌓인다.
|
|
84
|
+
*
|
|
85
|
+
* 한 사람이 연달아 말하면 아바타·이름·시각은 한 번만 서고 말풍선만 늘어난다 —
|
|
86
|
+
* 그 묶음이 이 컴포넌트 한 개다. 말풍선 안에는 글뿐 아니라 버튼 같은 요소도 올 수 있다.
|
|
87
|
+
*/
|
|
88
|
+
messages?: ReactNode[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* SChatMessage — 대화 화면에서 한 사람이 한 번에 말한 묶음.
|
|
92
|
+
*
|
|
93
|
+
* 아바타·보낸 사람(받은 메시지만)·말풍선·시각을 한 줄로 세운다. **말풍선은 `messages` 항목 수만큼 쌓이고**
|
|
94
|
+
* 아바타·이름·시각은 그 묶음에 한 번만 붙는다 — 연달아 말한 여러 줄이 한 덩어리로 읽힌다.
|
|
95
|
+
*
|
|
96
|
+
* 시각 아래에 동작을 붙여야 하면 `actions` 슬롯에 넣는다 — 시각과 같은 선에서 시작해 방향에
|
|
97
|
+
* 맞춰 정렬된다.
|
|
98
|
+
*
|
|
99
|
+
* 시간이 벌어져 묶음을 나눠야 하는데 말한 사람은 같을 때는 `continued` 를 켠다 — 아바타 자리는
|
|
100
|
+
* 유지한 채 얼굴과 이름만 빠져, 두 묶음이 한 사람의 말로 이어 읽힌다.
|
|
101
|
+
`direction` 하나로 받은 메시지(왼쪽)와
|
|
102
|
+
* 보낸 메시지(오른쪽)가 갈리고, 그에 따라 정렬과 말풍선 색이 함께 뒤집힌다.
|
|
103
|
+
*
|
|
104
|
+
* **말풍선 폭은 내용이 정한다** — 짧은 메시지는 글자만큼만 차지하고, 길면 부모 폭에서 접힌다.
|
|
105
|
+
* 대화 폭을 제한하려면 이 컴포넌트가 아니라 담는 쪽을 좁힌다.
|
|
106
|
+
*/
|
|
107
|
+
export declare const SChatMessage: import("react").ForwardRefExoticComponent<SChatMessageProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SChatMessage, CHAT_MESSAGE_DIRECTIONS, CHAT_MESSAGE_READ_STATUSES, CHAT_MESSAGE_VARIANTS, type SChatMessageDirection, type SChatMessageReadStatus, type SChatMessageVariant, type SChatMessageProps, } from './SChatMessage';
|