sellmate-design-system-react 9.0.0-beta.0 → 9.0.0-beta.11
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 +156 -48
- package/README.md +63 -0
- package/dist/components/SBadge/README.md +24 -0
- package/dist/components/SBadge/SBadge.d.ts +1 -1
- package/dist/components/SBarcodeInput/README.md +8 -0
- package/dist/components/SButton/README.md +34 -0
- package/dist/components/SCalendar/README.md +13 -0
- package/dist/components/SCallout/README.md +15 -0
- package/dist/components/SCheckbox/README.md +8 -0
- package/dist/components/SChipFilter/README.md +278 -4
- package/dist/components/SChipFilter/SChipFilter.d.ts +86 -38
- package/dist/components/SChipFilter/index.d.ts +1 -1
- package/dist/components/SChipInput/README.md +14 -0
- package/dist/components/SCircleProgress/README.md +11 -0
- package/dist/components/SCircleProgress/SCircleProgress.d.ts +7 -1
- package/dist/components/SConfirmModal/README.md +16 -0
- package/dist/components/SDatePicker/README.md +55 -3
- package/dist/components/SDatePicker/SDatePicker.d.ts +36 -3
- package/dist/components/SDatePicker/index.d.ts +1 -1
- package/dist/components/SDateRangePicker/README.md +14 -0
- package/dist/components/SDraggableItem/README.md +36 -0
- package/dist/components/SDraggableList/README.md +24 -0
- package/dist/components/SDrawer/README.md +8 -0
- package/dist/components/SDropdownButton/README.md +19 -0
- package/dist/components/SExpansionItem/README.md +36 -0
- package/dist/components/SField/README.md +23 -0
- package/dist/components/SField/SField.d.ts +12 -0
- package/dist/components/SFilePicker/README.md +14 -0
- package/dist/components/SFooter/README.md +21 -0
- package/dist/components/SForm/README.md +11 -0
- package/dist/components/SGhostButton/README.md +16 -2
- package/dist/components/SGnb/README.md +45 -0
- package/dist/components/SGnb/gnb.config.d.ts +2 -0
- package/dist/components/SGuide/README.md +15 -0
- package/dist/components/SIcon/README.md +4 -0
- package/dist/components/SImage/README.md +58 -0
- package/dist/components/SImage/SImage.d.ts +41 -0
- package/dist/components/SImage/index.d.ts +1 -0
- package/dist/components/SKeyValueTable/README.md +84 -0
- package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +8 -1
- package/dist/components/SLayout/README.md +16 -0
- package/dist/components/SLinearProgress/README.md +8 -0
- package/dist/components/SList/README.md +0 -1
- package/dist/components/SList/SList.d.ts +0 -2
- package/dist/components/SListItem/README.md +41 -0
- package/dist/components/SLoadingModal/README.md +8 -0
- package/dist/components/SNumberInput/README.md +8 -0
- package/dist/components/SPage/README.md +40 -0
- package/dist/components/SPage/SPage.d.ts +17 -1
- package/dist/components/SPage/index.d.ts +1 -1
- package/dist/components/SPage/page.config.d.ts +8 -0
- package/dist/components/SPopover/README.md +15 -0
- package/dist/components/SPopup/README.md +19 -0
- package/dist/components/SPortal/README.md +14 -0
- package/dist/components/SRadio/README.md +20 -0
- package/dist/components/SRadioButton/README.md +18 -0
- package/dist/components/SScrollArea/README.md +14 -0
- package/dist/components/SSearchInput/README.md +61 -0
- package/dist/components/SSearchInput/SSearchInput.d.ts +46 -0
- package/dist/components/SSearchInput/index.d.ts +1 -0
- package/dist/components/SSectionHeaderCard/README.md +39 -20
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.d.ts +21 -14
- package/dist/components/SSectionHeaderCard/index.d.ts +1 -1
- package/dist/components/SSelect/README.md +23 -2
- package/dist/components/SSplitter/README.md +15 -0
- package/dist/components/SStepper/README.md +26 -0
- package/dist/components/STable/README.md +104 -0
- package/dist/components/STable/STable.d.ts +20 -0
- package/dist/components/STabs/README.md +12 -1
- package/dist/components/STabs/STabs.d.ts +2 -4
- package/dist/components/STabs/index.d.ts +1 -1
- package/dist/components/STabs/tabs.config.d.ts +3 -4
- package/dist/components/STag/README.md +47 -0
- package/dist/components/STextLink/README.md +14 -0
- package/dist/components/STimePicker/README.md +14 -0
- package/dist/components/STimePicker/timepicker.config.d.ts +5 -0
- package/dist/components/STimeRangePicker/README.md +26 -0
- package/dist/components/SToast/README.md +24 -0
- package/dist/components/SToggle/README.md +9 -0
- package/dist/components/SToggle/SToggle.d.ts +4 -1
- package/dist/components/STooltip/README.md +20 -0
- package/dist/components/STree/README.md +41 -0
- package/dist/components/STree/STree.d.ts +8 -0
- package/dist/components/STree/index.d.ts +1 -1
- package/dist/index.cjs +1621 -451
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1615 -451
- package/dist/index.js.map +1 -1
- package/dist/lib/story-docs.d.ts +19 -3
- package/dist/lib/truncated-value-tooltip.d.ts +18 -0
- package/dist/llms-full.txt +1750 -90
- package/dist/llms.txt +160 -51
- package/dist/styles.css +200 -42
- package/dist/theme.css +34 -11
- package/eslint/index.mjs +5 -0
- package/eslint/rules/field-width-grade.d.mts +41 -0
- package/eslint/rules/field-width-grade.mjs +294 -0
- package/eslint/scale.gen.mjs +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **대상**: 이 패키지로 화면을 만드는 소비 앱의 개발자와 AI 코딩 에이전트(Claude 등).
|
|
4
4
|
> 이 문서는 "무엇을 언제 쓰고, 무엇을 쓰면 안 되는지"의 단일 기준이다.
|
|
5
|
-
> 개별 컴포넌트의 상세 Props/Events는 `node_modules/sellmate-design-system-react/dist/components/<이름>/README.md` 를 참조한다.
|
|
5
|
+
> 개별 컴포넌트의 상세 Props/Events와 그 Props 가 쓰는 타입 정의(Types)는 `node_modules/sellmate-design-system-react/dist/components/<이름>/README.md` 를 참조한다.
|
|
6
6
|
|
|
7
7
|
## 0. 최우선 원칙 — 디자인 시스템 컴포넌트가 먼저다
|
|
8
8
|
|
|
@@ -20,19 +20,19 @@
|
|
|
20
20
|
|
|
21
21
|
### 0-1. 전체 컴포넌트 인덱스
|
|
22
22
|
|
|
23
|
-
무엇을 만들지 정했으면 **이 표에서 먼저 찾는다.** 상세 Props 는 `dist/components/<이름>/README.md` 참조.
|
|
23
|
+
무엇을 만들지 정했으면 **이 표에서 먼저 찾는다.** 상세 Props·Types 는 `dist/components/<이름>/README.md` 참조.
|
|
24
24
|
|
|
25
25
|
**이 표에서 어느 것을 골라야 할지 모르겠으면 §3-0 "의도 → 컴포넌트 라우팅" 으로 간다.** 하려는 일을 문장으로 찾으면 답이 하나 나온다 — 여기 인덱스는 "무엇이 있는지", §3-0 은 "언제 그걸 쓰는지" 를 담당한다.
|
|
26
26
|
|
|
27
27
|
| 분류 | 컴포넌트 |
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
|
|
30
|
-
| **입력 (폼)** | `SForm` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
31
|
-
| **날짜·시간** | `SCalendar` `SDatePicker` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
30
|
+
| **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
31
|
+
| **날짜·시간** | `SCalendar` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
32
32
|
| **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
33
|
| **레이아웃** | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
34
34
|
| **내비게이션** | `STabs` `SPagination` `SStepper` |
|
|
35
|
-
| **표시·상태** | `STag` `SBadge` `SIcon` `SCallout` `SGuide` |
|
|
35
|
+
| **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
|
|
36
36
|
| **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
|
|
37
37
|
| **오버레이** | `STooltip` `SPopover` `SPopup` `SDrawer` `SPortal` |
|
|
38
38
|
| **모달** | `SModal.confirm()` `SModal.create()` + `SActionModal` `SConfirmModal` `SModalOutlet`(앱 루트 1회) |
|
|
@@ -60,6 +60,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
|
|
|
60
60
|
| --- | --- |
|
|
61
61
|
| `<button>` | `SButton`, `SGhostButton`, `SDropdownButton`, `STextLink` |
|
|
62
62
|
| `<input type="text/password/...">` | `SInput` |
|
|
63
|
+
| `<input type="search">` | `SSearchInput` |
|
|
63
64
|
| `<input type="number">` | `SNumberInput` |
|
|
64
65
|
| `<input type="checkbox">` | `SCheckbox`, `SToggle`, `SSwitch` |
|
|
65
66
|
| `<input type="radio">` | `SRadio`, `SRadioButton` |
|
|
@@ -72,7 +73,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
|
|
|
72
73
|
| `alert()`, `confirm()` | `SToast`, `SModal.confirm(...)` |
|
|
73
74
|
| 직접 만든 탭/페이지네이션/스텝퍼 | `STabs`, `SPagination`, `SStepper` |
|
|
74
75
|
| `<ul>`/`<li>` 로 만든 목록 UI | `SList` + `SListItem` (드래그 정렬은 `SDraggableItem`) |
|
|
75
|
-
| 직접 만든 섹션 카드(제목 바 + 본문 박스) | `SSectionHeaderCard`
|
|
76
|
+
| 직접 만든 섹션 카드(제목 바 + 본문 박스) | `SSectionHeaderCard` 의 `title` / `padding` props |
|
|
76
77
|
| `<svg>` 직접 삽입, 이모지 아이콘 | `SIcon` |
|
|
77
78
|
| `<hr>` | `SDivider` |
|
|
78
79
|
| `<details>` / `<summary>` | `SExpansionItem` |
|
|
@@ -138,7 +139,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
138
139
|
| --- | --- | --- |
|
|
139
140
|
| **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) |
|
|
140
141
|
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
141
|
-
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SLinearProgress` `SCircleProgress` |
|
|
142
|
+
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
|
|
142
143
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
|
|
143
144
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
144
145
|
|
|
@@ -158,7 +159,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
158
159
|
| 담는 것 | 올 수 있는 것 | 오면 안 되는 것 |
|
|
159
160
|
| --- | --- | --- |
|
|
160
161
|
| `SPage` | **블록만** | **요소를 직접** — 버튼 하나도 블록에 담아 놓는다 |
|
|
161
|
-
| `SSectionHeaderCard
|
|
162
|
+
| `SSectionHeaderCard` 의 `children` | 그리는 블록 · 요소 | `SSectionHeaderCard` · `SCard` (카드 겹침, §3-7-8) |
|
|
162
163
|
| `SCard` | 그리는 블록 · 요소 | `SCard` · `SSectionHeaderCard` |
|
|
163
164
|
| `SForm` | 블록 (보통 `SKeyValueTable` + 하단 액션) | — |
|
|
164
165
|
| `SSplitter.Before` / `.After` | 블록 | — |
|
|
@@ -222,7 +223,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
222
223
|
|
|
223
224
|
페이지 제목만 18px 로 크게 두고 그 아래는 14 / 12 로 촘촘하게 간다. 중간 크기(16px)는 기본 골격에서 쓰지 않는다.
|
|
224
225
|
|
|
225
|
-
- **페이지·섹션 제목의 타이포를 직접 주지 않는다.** `SPage` 의 `header.title`, `SSectionHeaderCard
|
|
226
|
+
- **페이지·섹션 제목의 타이포를 직접 주지 않는다.** `SPage` 의 `header.title`, `SSectionHeaderCard` 의 `header.title` 이 이미 넣는다 — 그 위에 `typo-heading-lg`/`typo-heading-sm` 을 또 씌우지 않는다.
|
|
226
227
|
- **하위 제목이 필요하면 먼저 섹션을 나눌 수 없는지 본다.** 한 섹션 안에서 제목이 두 단으로 갈린다는 것은 대개 섹션이 둘이라는 뜻이다 (§3-7-8).
|
|
227
228
|
- 본문 안에서 한 단어를 강조할 때는 `typo-body-sm-medium` 을 쓴다. `typo-body-sm-bold` 는 제목 성격의 짧은 라벨에만 쓴다. <!-- TODO(디자인): 강조 굵기 기준 확정 -->
|
|
228
229
|
|
|
@@ -292,7 +293,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
292
293
|
|
|
293
294
|
**페이지 프레임은 예외 없이 `SPage` 가 넣는다.** 아래 규칙은 그 안의 **섹션·패널 레벨에만** 적용된다.
|
|
294
295
|
|
|
295
|
-
**컴포넌트가 자체 여백을 가지면 컴포넌트 기준이 우선한다.** `SKeyValueTable`·`STable` 처럼 자기 여백을 토큰으로 갖고 있는 컴포넌트에는 이 판정을 적용하지 않는다 — 손댈 것이 없다. 아래 판정이 필요한 자리는 **직접 만든 컨테이너**와 **`SSectionHeaderCard
|
|
296
|
+
**컴포넌트가 자체 여백을 가지면 컴포넌트 기준이 우선한다.** `SKeyValueTable`·`STable` 처럼 자기 여백을 토큰으로 갖고 있는 컴포넌트에는 이 판정을 적용하지 않는다 — 손댈 것이 없다. 아래 판정이 필요한 자리는 **직접 만든 컨테이너**와 **`SSectionHeaderCard` 의 `padding`** 두 곳뿐이다.
|
|
296
297
|
|
|
297
298
|
판정은 **그 영역이 담고 있는 콘텐츠 덩어리의 종류 수**로 한다.
|
|
298
299
|
|
|
@@ -327,14 +328,47 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
327
328
|
|
|
328
329
|
**중첩되면 안쪽 여백을 주지 않는다.** 24 영역 안에 또 여백을 주면 가장자리가 40 으로 벌어져 한 면적처럼 읽힌다. 안쪽 카드·목록이 **배경색이 다르거나 테두리가 있어** 경계가 스스로 보이는 경우에만 자기 여백을 유지한다.
|
|
329
330
|
|
|
330
|
-
`SSectionHeaderCard
|
|
331
|
+
`SSectionHeaderCard` 는 이 규칙을 **`padding` prop 으로 받는다** — 직접 `p-sd-*` 를 주지 않는다.
|
|
331
332
|
|
|
332
333
|
```tsx
|
|
333
|
-
<SSectionHeaderCard
|
|
334
|
-
<SSectionHeaderCard
|
|
335
|
-
<SSectionHeaderCard
|
|
334
|
+
<SSectionHeaderCard title="기본 정보">…</SSectionHeaderCard> {/* 기본 = 16 */}
|
|
335
|
+
<SSectionHeaderCard title="기본 정보" padding="wide">…</SSectionHeaderCard> {/* 3종류 이상 */}
|
|
336
|
+
<SSectionHeaderCard title="기본 정보" padding="none">…</SSectionHeaderCard> {/* 표를 가장자리까지 */}
|
|
336
337
|
```
|
|
337
338
|
|
|
339
|
+
##### 카드 가장자리까지 채우는 표는 자기 테두리를 끈다
|
|
340
|
+
|
|
341
|
+
여기서 "표"는 **`STable` 과 `SKeyValueTable` 둘 다**다. 두 컴포넌트 모두 자기 바깥 테두리를 그리는데, 카드도 바깥 테두리를 그린다. `padding="none"` 으로 붙이면 **1px 두 개가 나란히 놓여 그 변만 2px** 로 보인다(카드의 다른 변은 1px 그대로라 굵기가 어긋난다).
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
<SSectionHeaderCard title="발주 내역" padding="none">
|
|
345
|
+
<SKeyValueTable fields={…} bordered={false} radius="useTop" />
|
|
346
|
+
<STable columns={…} rows={…} bordered={false} radius="useTop" />
|
|
347
|
+
</SSectionHeaderCard>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
- **`bordered={false}`** 로 표의 테두리를 끈다. 카드가 이미 그린다.
|
|
351
|
+
- **`radius="useTop"`** 으로 위쪽 모서리를 죽인다. 아래쪽 라운드는 카드가 처리한다.
|
|
352
|
+
- `STable` 은 **페이지네이션 바의 테두리와 `-mt-px` 겹침도 함께 꺼진다** — 본문 테두리가 없으면 겹칠 대상이 없어, 그대로 두면 페이지네이션만 테두리를 갖고 1px 어긋난다.
|
|
353
|
+
|
|
354
|
+
#### 본문 바탕 눌러앉히기 (선택)
|
|
355
|
+
|
|
356
|
+
`SSectionHeaderCard` 는 **`background="neutral"`** 로 본문 바탕을 한 단계 눌러앉힐 수 있다. 흰 면 덩어리(표·리스트)가 여럿일 때 그 덩어리들이 **"면 위에 놓인 객체"로 읽혀 묶음이 더 강하게 보인다.**
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
<SSectionHeaderCard title="발주 상세" background="neutral">…흰 면 표 여럿…</SSectionHeaderCard>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**기본값(`frame`, 흰 면)이 틀린 것이 아니다.** 이 저장소의 표·리스트는 테두리·라운드·헤더 줄과 `gap-sd-12` 를 이미 갖고 있어 흰 바탕에서도 경계가 읽힌다. 위계를 한 단계 더 주고 싶을 때 고르는 수단이지, 덩어리가 둘 이상이면 반드시 깔아야 하는 규칙이 아니다.
|
|
363
|
+
|
|
364
|
+
깔아도 **효과가 없는** 자리는 있다.
|
|
365
|
+
|
|
366
|
+
- **덩어리가 가장자리까지 차는 경우** — `padding="none"` 으로 표를 채우면 깐 바탕이 표에 완전히 가려 보이지 않는다.
|
|
367
|
+
- **덩어리에 회색 면이 섞인 경우** — 그 덩어리가 바탕과 같은 색이 되어 묻힌다.
|
|
368
|
+
- **맨 텍스트·폼 컨트롤만 있는 본문** — 떠오를 흰 면이 없다.
|
|
369
|
+
|
|
370
|
+
바탕을 깐 경우, 표 사이 구분선(`SDivider`)은 대개 불필요해진다 — 색이 이미 경계를 만든다.
|
|
371
|
+
|
|
338
372
|
#### 스크롤 영역의 하단 여백
|
|
339
373
|
|
|
340
374
|
스크롤을 끝까지 내렸을 때 마지막 항목이 화면 경계에 붙으면 **목록이 끝난 것인지 더 있는 것인지** 읽히지 않는다. 그래서 스크롤 영역은 **하단만** 넓게 둔다. 나머지 세 방향은 위 16 / 24 규칙 그대로다.
|
|
@@ -344,7 +378,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
344
378
|
| **패널 자체 스크롤** (좌측 목록, 중앙 대화 등) | 그 패널 안쪽 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` — `SPage` 와 같은 토큰이라 값이 바뀌어도 함께 따라간다 |
|
|
345
379
|
| **페이지 단위 스크롤** | **`SPage` 가 넣는다. 직접 주지 않는다** |
|
|
346
380
|
|
|
347
|
-
`SPage` 는 기본으로 넣으므로 **아무것도 하지 않으면 맞다.**
|
|
381
|
+
`SPage` 는 기본으로 넣으므로 **아무것도 하지 않으면 맞다.** 직접 끌 일은 없다 — **페이지네이션이 붙은 테이블**은 `contentHeight="fill"` 을 쓰고, 이 모드에서는 페이지가 스크롤하지 않아 스크롤 끝 여백이 **자동으로 무시**된다 (§4-2 목록 페이지).
|
|
348
382
|
|
|
349
383
|
### 2-3. 색상
|
|
350
384
|
|
|
@@ -406,6 +440,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
406
440
|
| --- | --- | --- |
|
|
407
441
|
| 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
|
|
408
442
|
| 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
|
|
443
|
+
| 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
|
|
409
444
|
| 숫자(수량·금액)를 받는다 | `SNumberInput` | |
|
|
410
445
|
| 바코드를 스캔해 받는다 | `SBarcodeInput` | |
|
|
411
446
|
| 목록에서 하나 고르게 한다 | `SSelect` | §3-7-2 |
|
|
@@ -419,6 +454,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
419
454
|
| 입력된 값 하나를 지우거나 고치게 한다 | `SChip` | §3-1 |
|
|
420
455
|
| 파일을 받는다 | `SFilePicker` | |
|
|
421
456
|
| 날짜 하나를 받는다 | `SDatePicker` | §3-7-4 |
|
|
457
|
+
| 연도 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerYearListbox` | §3-7-4 |
|
|
458
|
+
| 연도+월 선택 리스트만 커스텀 조합에 넣는다 | `SDatePickerMonthListbox` | §3-7-4 |
|
|
422
459
|
| 날짜 기간을 받는다 | `SDateRangePicker` | §3-7-4 |
|
|
423
460
|
| 시각 하나를 받는다 | `STimePicker` | |
|
|
424
461
|
| 시각 범위를 받는다 | `STimeRangePicker` | |
|
|
@@ -442,6 +479,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
442
479
|
| 상태·분류를 라벨로 찍는다 | `STag` | §3-1 |
|
|
443
480
|
| 색 점만으로 상태를 찍는다 | `SBadge` | §3-1 |
|
|
444
481
|
| 아이콘을 넣는다 | `SIcon` | |
|
|
482
|
+
| 사진·썸네일을 보여준다 (로딩·실패 상태 포함) | `SImage` | §3-7-12 |
|
|
445
483
|
| 문장 안에서 다른 화면으로 보낸다 | `STextLink` | §3-5-6 |
|
|
446
484
|
|
|
447
485
|
#### C. 동작을 실행시킨다
|
|
@@ -549,9 +587,26 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
549
587
|
| | 무엇인가 | 크기 |
|
|
550
588
|
| --- | --- | --- |
|
|
551
589
|
| **SPopup** | **별도 브라우저 창** (`window.open` 으로 여는 전용 라우트) | 창 크기 = 콘텐츠 크기 |
|
|
552
|
-
| **SActionModal** | 같은 창 위 오버레이 카드 | `width`
|
|
590
|
+
| **SActionModal** | 같은 창 위 오버레이 카드 | `width` prop. **높이는 주지 않는다** (아래) |
|
|
553
591
|
| **SModal.confirm** (`SConfirmModal`) | 같은 창 위 확인창 | 고정 |
|
|
554
592
|
|
|
593
|
+
##### 모달 높이는 내용이 정하고, 상한은 시스템이 건다
|
|
594
|
+
|
|
595
|
+
**`height` 를 주지 않는다.** 높이는 내용이 정하고, 카드는 **뷰포트의 85%** 에서 멈춘다(시스템이 모든 모달에 건다). 데이터가 적으면 내용만큼 작아지고, 많으면 85% 에서 멈춘다.
|
|
596
|
+
|
|
597
|
+
가로는 좌우 24px 씩을 뺀 값으로 클램핑하는데 **세로만 비율**인 이유는, 모달이 화면을 거의 다 덮으면 뒤 맥락이 사라져 "떠 있는 것"으로 읽히지 않기 때문이다.
|
|
598
|
+
|
|
599
|
+
**상한에 닿았을 때 스크롤되어야 하는 것은 모달 본문이 아니라 표다.**
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
<SActionModal modalTitle="발주 검토" button={{ label: '확정', onClick: submit }}>
|
|
603
|
+
{/* 표가 남은 높이를 먹고 자기 안에서 스크롤한다 — 헤더·합계·푸터는 늘 보인다 */}
|
|
604
|
+
<STable className="min-h-0 flex-1" columns={columns} rows={rows} />
|
|
605
|
+
</SActionModal>
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
본문(`overflow-auto` 영역)이 통째로 스크롤되면 표 헤더와 합계 줄이 위로 밀려 사라진다. `SActionModal` 의 본문은 이미 `min-h-0 flex-1` 이므로, 표에 `min-h-0 flex-1` 을 주면 세로 축이 이어져 표만 스크롤한다 (§4-2 목록 페이지와 같은 사슬이다).
|
|
609
|
+
|
|
555
610
|
**성격이 먼저 둘로 갈린다.**
|
|
556
611
|
|
|
557
612
|
| 성격 | 정의 | 컴포넌트 |
|
|
@@ -739,6 +794,7 @@ const columns: STableColumn[] = [
|
|
|
739
794
|
|
|
740
795
|
컬럼 폭은 `width` 로 **고정**되고, `<td>` 는 그 폭을 넘는 내용을 잘라낸다(`overflow: hidden`). 텍스트라면 말줄임으로 끝나지만, 셀에 `STag` · `SButton` · `SGhostButton` · `SSelect` · `SInput` · `SNumberInput` 처럼 **자기 폭을 가진 요소**를 넣으면 요소 자체가 잘려 **누르거나 읽거나 입력할 수 없게 된다.** `width` 를 생략해도 내용에 맞춰 늘어나지 않고 `STable` 의 기본 폭이 될 뿐이므로, 컨트롤이 들어가는 컬럼은 폭을 직접 판단해서 준다.
|
|
741
796
|
|
|
797
|
+
- **컨트롤이 들어가는 컬럼에는 `contentType: 'control'` 을 함께 준다.** 좌우 패딩이 텍스트용(넓게)에서 컨트롤용(좁게)으로 바뀌어, 같은 컬럼 폭에서도 요소가 쓸 폭이 넓어진다. 기본값은 `text` 다.
|
|
742
798
|
- 기준은 **요소가 온전히 보이는 폭 + 셀 좌우 패딩**이다. 좌우 패딩은 `STable` 이 토큰으로 넣으므로(직접 주지 않는다) 그만큼을 뺀 나머지가 요소 몫이라는 점을 계산에 넣는다.
|
|
743
799
|
- 요소가 둘 이상이면 요소 폭의 합에 **`gap` 까지** 더한다 (행 내부 인라인 액션 간격은 `gap-sd-4` 고정, §3-5-5).
|
|
744
800
|
- 값에 따라 폭이 달라지는 요소(`STag` 라벨, 라벨 있는 버튼)는 **가장 긴 값** 기준으로 잡는다. `판매중` 에 맞춰두면 `판매중지 요청` 에서 잘린다.
|
|
@@ -746,6 +802,13 @@ const columns: STableColumn[] = [
|
|
|
746
802
|
- 폭을 넉넉히 줄 수 없는 자리는 폭을 줄이는 게 아니라 **요소를 바꾼다** — 라벨 버튼 대신 아이콘만 있는 `SGhostButton`, `size="xs"` (§3-5-2, §3-5-5).
|
|
747
803
|
- **`autoWidth` 는 해법이 아니다.** 내용에 맞춰 늘어나는 게 아니라 고정폭 컬럼들이 가져가고 **남은 폭을 나눠 갖는 것**이라, 테이블이 좁으면 역시 잘린다. 컨트롤 컬럼은 `width` 로 직접 확보한다.
|
|
748
804
|
|
|
805
|
+
```tsx
|
|
806
|
+
{ name: 'normal', label: '정상', field: 'normal', width: '96px',
|
|
807
|
+
align: 'center', contentType: 'control', render: row => <SNumberInput … /> },
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
**셀 좌우 여백은 내용이 정한다.** 텍스트는 넓게, 컨트롤은 좁게다 — `SKeyValueTable` 은 `field.type` 으로 이 판정을 스스로 하지만, `STable` 의 셀은 소비 앱이 넘긴 임의의 `render` 결과라 컴포넌트가 알 수 없다. 그래서 `contentType` 으로 알려준다. 여백 값 자체는 토큰이 정하므로 `tdClass` 로 패딩을 직접 덮어쓰지 않는다.
|
|
811
|
+
|
|
749
812
|
**`resizable` 테이블이면 `minWidth` 를 함께 준다.** resize 하한 기본값은 어떤 컨트롤도 담지 못할 만큼 작아, 사용자가 끝까지 끌면 그대로 잘린다. `width` 를 정한 근거와 같은 값을 하한으로 둔다 — 텍스트 컬럼과 달리 여기서는 더 줄일 여지가 없다.
|
|
750
813
|
|
|
751
814
|
```tsx
|
|
@@ -958,16 +1021,19 @@ const columns: STableColumn[] = [
|
|
|
958
1021
|
|
|
959
1022
|
> §3-0 라우팅에서 이 절을 가리키는 자리들이다. <!-- TODO(디자인): 전체 검수·확정 -->
|
|
960
1023
|
|
|
961
|
-
#### 3-7-1. SInput vs STextarea
|
|
1024
|
+
#### 3-7-1. SInput vs STextarea vs SSearchInput
|
|
962
1025
|
|
|
963
|
-
|
|
1026
|
+
**먼저 "그 값이 저장되는가"를 본다.** 저장되면 폼 필드(`SInput`·`STextarea`), 화면을 좁히기만 하고 사라지면 `SSearchInput` 이다.
|
|
964
1027
|
|
|
965
1028
|
| 값 | 사용 |
|
|
966
1029
|
| --- | --- |
|
|
967
1030
|
| 이름·코드·전화번호·URL 처럼 형식이 정해진 값 | `SInput` |
|
|
968
1031
|
| 메모·사유·설명처럼 길이가 예측되지 않는 문장 | `STextarea` |
|
|
1032
|
+
| 지금 보이는 목록·결과를 좁히는 검색어 | `SSearchInput` |
|
|
1033
|
+
|
|
1034
|
+
폼 필드 둘은 **줄 수가 아니라 값의 성격으로** 갈린다. 값이 길어질 수 있는데 `SInput` 을 쓰면 사용자가 자기가 쓴 것을 다시 읽지 못한다 — 한 줄 안에서 좌우로 스크롤해야 하기 때문이다. 반대로 짧은 값에 `STextarea` 를 쓰면 빈 공간이 남아 입력량을 잘못 기대하게 한다.
|
|
969
1035
|
|
|
970
|
-
|
|
1036
|
+
`SSearchInput` 은 폼 필드가 아니다 — 라벨·힌트·유효성 규칙·에러 메시지를 받지 않고, `SForm` 의 제출 검증 대상에도 들어가지 않는다. 돋보기 아이콘이 항상 앞에 붙어 "여기는 검색"임을 스스로 밝히므로 라벨을 따로 붙이지 않는다. 검색 실행은 `onSearch`(Enter) 로 받고, 값이 바뀔 때마다 좁히는 실시간 필터라면 `onValueChange` 만 쓴다. 반대로 검색어를 **저장하거나 검증해야 한다면** 그것은 폼 값이므로 `SInput` 이다.
|
|
971
1037
|
|
|
972
1038
|
#### 3-7-2. 하나를 고르게 하는 다섯 — SSelect vs SRadioGroup vs SRadioButton vs STabs vs SRadio
|
|
973
1039
|
|
|
@@ -1004,11 +1070,25 @@ const columns: STableColumn[] = [
|
|
|
1004
1070
|
| 판별 | 사용 |
|
|
1005
1071
|
| --- | --- |
|
|
1006
1072
|
| 날짜 **하나**를 값으로 받는다 | `SDatePicker` |
|
|
1073
|
+
| 연도 선택 리스트만 필요하다 (트리거·팝오버는 직접 조합) | `SDatePickerYearListbox` |
|
|
1074
|
+
| 연도+월 선택 리스트만 필요하다 (트리거·팝오버는 직접 조합) | `SDatePickerMonthListbox` |
|
|
1007
1075
|
| **시작~종료** 를 값으로 받는다 | `SDateRangePicker` |
|
|
1008
1076
|
| 달력 격자 **자체가 화면 콘텐츠** 다 (일정·이벤트 보기) | `SCalendar` |
|
|
1009
1077
|
|
|
1010
1078
|
- **기간을 `SDatePicker` 두 개로 만들지 않는다.** 시작이 종료보다 뒤인 입력을 막는 검증과 한쪽만 고른 중간 상태 처리가 `SDateRangePicker` 안에 이미 있다. 두 개로 쪼개면 그게 전부 앱 몫이 된다.
|
|
1011
1079
|
- `SDatePicker`·`SDateRangePicker` 는 내부적으로 `SCalendar` 를 팝오버로 띄운다. 값을 받는 자리에 `SCalendar` 를 직접 쓰지 않는다.
|
|
1080
|
+
- `SDatePickerYearListbox`·`SDatePickerMonthListbox` 는 `SDatePicker` 의 mode listbox 조각만 떼어낸 컴포넌트다. 일반 폼 입력에는 `SDatePicker mode="year" | "month"` 를 우선 쓰고, 다른 트리거·팝오버 안에 리스트만 끼워 넣을 때만 직접 쓴다.
|
|
1081
|
+
|
|
1082
|
+
**날짜·시간 피커는 폭 상한을 스스로 갖는다 — `width` 를 주지 않는다.** 값 길이가 `YYYY-MM-DD` 처럼 정해져 있어 컴포넌트가 사이즈별 상한을 안다. `SKeyValueTable` 이 모든 컨트롤에 `width="100%"` 를 넘기지만, 이 상한 덕분에 행 전체로 늘어나지 않고 제 폭에서 멈춘다.
|
|
1083
|
+
|
|
1084
|
+
| 컴포넌트 | `size="sm"` | `size="md"` |
|
|
1085
|
+
| --- | --- | --- |
|
|
1086
|
+
| `SDatePicker` | md | lg |
|
|
1087
|
+
| `SDateRangePicker` | lg | xl |
|
|
1088
|
+
| `STimePicker` | md | lg |
|
|
1089
|
+
| `STimeRangePicker` | md | lg (오전/오후 표시는 두 사이즈 모두 lg) |
|
|
1090
|
+
|
|
1091
|
+
`SDateRangePicker` 가 한 등급씩 위인 것은 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 로 두 배가 넘기 때문이다. 같은 이유로 `STimeRangePicker` 의 오전/오후 모드도 sm 에서 한 등급 위를 쓴다 — 그 모드의 최소 폭이 md 등급을 이미 넘어, 그대로 두면 하한이 상한을 넘어 상한이 무력해진다.
|
|
1012
1092
|
|
|
1013
1093
|
#### 3-7-5. SField 를 직접 쓰는 경우
|
|
1014
1094
|
|
|
@@ -1056,7 +1136,7 @@ const columns: STableColumn[] = [
|
|
|
1056
1136
|
|
|
1057
1137
|
- 페이지 골격에서 콘텐츠를 묶는 섹션은 **사실상 전부 `SSectionHeaderCard`** 다 (§4-4·§4-5). 제목·필수 표시·도움말·헤더 우측 액션이 전부 여기 붙는다.
|
|
1058
1138
|
- **카드 안에 카드를 겹치지 않는다.** 섹션 안을 더 나눠야 하면 `SDivider` 로 끊거나(§3-6) 섹션을 둘로 분리한다.
|
|
1059
|
-
- 안쪽 여백은 `SSectionHeaderCard
|
|
1139
|
+
- 안쪽 여백은 `SSectionHeaderCard` 의 `padding` prop 으로 준다 — `p-sd-*` 를 직접 주지 않는다 (§2-2).
|
|
1060
1140
|
|
|
1061
1141
|
#### 3-7-9. SLinearProgress vs SCircleProgress
|
|
1062
1142
|
|
|
@@ -1083,7 +1163,24 @@ const columns: STableColumn[] = [
|
|
|
1083
1163
|
- **기본은 `SKeyValueTable` 이다** (§4-2). 조건이 대여섯 개 이하로 고정이면 표로 펼쳐 두는 편이 한눈에 읽힌다.
|
|
1084
1164
|
- `SChipFilter` 는 조건을 **칩 한 줄**로 접고, "필터 추가" 로 필요한 것만 꺼내 쓰게 한다. 칩을 누르면 편집 팝오버가 열리고, 날짜는 프리셋(오늘·지난 7일·사용자 지정)으로 고른다. 조건 후보가 많은 목록 화면에서 필터가 화면을 세로로 잡아먹는 것을 막는 용도다.
|
|
1085
1165
|
- 검색 실행 시점이 다르다 — `SKeyValueTable` 필터는 앱이 검색 버튼을 직접 놓지만, `SChipFilter` 는 편집 팝오버가 닫히거나 "검색" 을 누를 때 `onSearch` 가 값 맵과 함께 호출된다. 값이 바뀌지 않았으면 호출되지 않는다.
|
|
1086
|
-
- 함께 걸어야 하는 조건(예: 기간 중 하나는 필수)이 있으면 `
|
|
1166
|
+
- **`fields` 는 항상 그룹 배열이다.** 묶을 것이 없어도 `[{ fields: [...] }]` 로 한 겹 감싼다. 함께 걸어야 하는 조건(예: 기간 중 하나는 필수)이 있으면 그 필드들만 별도 그룹으로 떼어 `rule` 을 준다 — 규칙을 못 채운 동안 경고 툴팁이 떠 있고 `onSearch` 가 막힌다. 그룹 앞 구분선은 `divider` 로 켠다. 검증 단위와 구분선은 별개라, 묶어서 검증만 하고 싶으면 `divider` 를 주지 않는다.
|
|
1167
|
+
|
|
1168
|
+
#### 3-7-12. 이미지 — SImage
|
|
1169
|
+
|
|
1170
|
+
사진·썸네일은 `<img>` 를 직접 쓰지 않고 `SImage` 를 쓴다. **로딩·실패 상태를 컴포넌트가 이미 처리하기 때문이다.**
|
|
1171
|
+
|
|
1172
|
+
- **로딩 스피너와 실패 표시를 앱이 만들지 않는다.** `src` 가 없거나 로드에 실패하면 빈 이미지 아이콘이, 로딩 중에는 스피너가 자동으로 놓인다. 크기는 상자 높이에 비례하므로 썸네일이든 큰 미리보기든 따로 맞출 것이 없다.
|
|
1173
|
+
- **크기는 `ratio` 와 너비로 준다.** `ratio` 기본값이 `1`(정사각형)이라 너비만 주면 정사각형이 된다. `className`/`style` 로 높이를 직접 주면 그쪽이 이기고 `ratio` 는 무시된다.
|
|
1174
|
+
- 상자를 채우는 방식은 `fit`(기본 `cover`), 붙는 위치는 `position` 이다. 안쪽 `<img>` 에 네이티브 속성이 필요하면 `imgProps` 로 넘긴다 — 목록에서 고스트 드래그를 막는 `imgProps={{ draggable: false }}` 가 대표적이다.
|
|
1175
|
+
- 썸네일이 많은 목록에서는 `loadingShowDelay` 를 준다. 캐시된 이미지가 즉시 로드될 때 스피너가 한 프레임 번쩍이는 것을 막는다.
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
✅ <SImage src={item.thumbnailUrl} alt={item.name} style={{ width: 64 }} /> {/* 정사각 썸네일 */}
|
|
1179
|
+
✅ <SImage src={banner} ratio={16 / 9} className="w-full" /> {/* 가로형 배너 */}
|
|
1180
|
+
|
|
1181
|
+
❌ <img src={item.thumbnailUrl} /> {/* 로딩·실패 상태가 없다 */}
|
|
1182
|
+
❌ {loading ? <SCircleProgress indeterminate /> : <SImage src={url} />} {/* SImage 가 이미 한다 */}
|
|
1183
|
+
```
|
|
1087
1184
|
|
|
1088
1185
|
---
|
|
1089
1186
|
|
|
@@ -1111,15 +1208,27 @@ export default function AppShell({
|
|
|
1111
1208
|
children,
|
|
1112
1209
|
header,
|
|
1113
1210
|
scrollEndSpacing,
|
|
1114
|
-
|
|
1211
|
+
contentHeight,
|
|
1212
|
+
}: {
|
|
1213
|
+
children: React.ReactNode;
|
|
1214
|
+
header?: SPageHeaderProps;
|
|
1215
|
+
scrollEndSpacing?: boolean;
|
|
1216
|
+
contentHeight?: SPageContentHeight;
|
|
1217
|
+
}) {
|
|
1115
1218
|
return (
|
|
1116
1219
|
<SLayout type="box" header="fix">
|
|
1117
1220
|
{/* type/header/folded 는 SLayout 에만 준다 — SGnb 는 context 에서 읽는다 */}
|
|
1118
1221
|
<SGnb items={MENU} value={current} onValueChange={navigate} logo={<Logo />} />
|
|
1119
1222
|
{/* 콘텐츠 패딩은 SPage 가 토큰으로 넣는다 — p-sd-* 로 덮어쓰지 않는다 */}
|
|
1120
1223
|
{/* 스크롤 끝 여백도 SPage 가 넣는다. 끄는 건 페이지네이션 있는 목록뿐이라 페이지가 정한다 */}
|
|
1224
|
+
{/* 본문이 남은 높이를 채우는 목록 페이지는 contentHeight="fill" 을 넘긴다 (§4-2) */}
|
|
1121
1225
|
{/* header 는 페이지마다 달라 AppShell 이 그대로 받아 넘긴다 — 페이지 제목은 여기서 만들지 않는다 */}
|
|
1122
|
-
<SPage
|
|
1226
|
+
<SPage
|
|
1227
|
+
background="frame"
|
|
1228
|
+
scrollEndSpacing={scrollEndSpacing}
|
|
1229
|
+
contentHeight={contentHeight}
|
|
1230
|
+
header={header}
|
|
1231
|
+
>
|
|
1123
1232
|
{children}
|
|
1124
1233
|
</SPage>
|
|
1125
1234
|
</SLayout>
|
|
@@ -1161,7 +1270,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1161
1270
|
|
|
1162
1271
|
**최소 너비는 `SLayout` 이 보장한다 — 앱이 `min-w-*` 를 직접 주지 않는다.** 창이 최소 너비(`SLAYOUT_MIN_WIDTH`, GNB 포함한 전체 기준)보다 좁아지면 GNB·상단바는 제자리에 남고 **`SPage` 안에만 가로 스크롤이 생긴다.** 문서(브라우저 창)에는 가로 스크롤이 생기지 않는다. 그래서 셸을 감싸는 요소에 `min-width` 나 `overflow-x` 를 걸지 않는다 — 걸면 창 전체가 스크롤되어 GNB 가 화면 밖으로 밀린다.
|
|
1163
1272
|
|
|
1164
|
-
**셸의 `SPage` 는 모든 페이지가 공유하므로,
|
|
1273
|
+
**셸의 `SPage` 는 모든 페이지가 공유하므로, 페이지마다 달라지는 것은 페이지가 셸에 알려야 한다.** 위처럼 프레임 컴포넌트가 `scrollEndSpacing` · `contentHeight` 를 받아 그대로 넘기고, 페이지네이션이 있는 목록 페이지만 `contentHeight="fill"` 을 준다 (§4-2). 나머지 페이지는 넘기지 않으면 기본값(`auto`, 스크롤 끝 여백 켬)이 적용된다.
|
|
1165
1274
|
|
|
1166
1275
|
**상단바 배치는 `header` 가 정한다.** 요소 순서가 달라지므로 슬롯을 채우기 전에 어느 쪽인지부터 정한다.
|
|
1167
1276
|
|
|
@@ -1183,7 +1292,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1183
1292
|
topContent={
|
|
1184
1293
|
/* 남는 폭 전체를 받는다 — 왼쪽은 그대로, 오른쪽 끝은 ml-auto */
|
|
1185
1294
|
<div className="flex w-full items-center gap-sd-8">
|
|
1186
|
-
<
|
|
1295
|
+
<SSearchInput value={keyword} onValueChange={setKeyword} onSearch={runSearch} placeholder="통합 검색" />
|
|
1187
1296
|
<SButton size="sm" color="neutral" outline label="내 계정" className="ml-auto" onClick={openAccount} />
|
|
1188
1297
|
</div>
|
|
1189
1298
|
}
|
|
@@ -1200,7 +1309,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1200
1309
|
{/* 접히면 menuTop·menuFooter 가 함께 빠지므로, 폴드 레일에 남길 것만 foldedTop 으로 따로 준다 */}
|
|
1201
1310
|
<SGnb
|
|
1202
1311
|
items={MENU} value={current} onValueChange={navigate} useRail
|
|
1203
|
-
menuTop={<
|
|
1312
|
+
menuTop={<SSearchInput value={keyword} onValueChange={setKeyword} placeholder="메뉴 검색" />}
|
|
1204
1313
|
menuFooter={<AccountRow />}
|
|
1205
1314
|
foldedTop={<SGhostButton icon="search" size="sm" ariaLabel="메뉴 검색" onClick={openSearch} />}
|
|
1206
1315
|
/>
|
|
@@ -1219,7 +1328,9 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1219
1328
|
- **페이지 제목 줄에는 이 페이지의 주요 액션을 두지 않는다.** 부가적인 것만 `header.slot` 에 `SButton size="sm"` 으로 온다 (§4-1 "페이지 헤더 사용 규칙").
|
|
1220
1329
|
- **주요 액션(등록 등)은 `STableBar` 의 `rightActions`** 에 둔다.
|
|
1221
1330
|
- **선택 상태 액션(선택 삭제 등)은 `STableBar` 의 `actions`** 에 둔다. `actions` 슬롯은 건수 요약이 있으면 앞에 구분선(`SDivider`)을 **자동으로** 넣으므로 직접 구분선을 만들지 않는다.
|
|
1222
|
-
- **페이지네이션이 있으면
|
|
1331
|
+
- **페이지네이션이 있으면 본문이 남은 높이를 채우게 한다** — `AppShell` 에 `contentHeight="fill"` 을 넘긴다. 페이지가 통째로 스크롤되면 페이지네이션이 화면 밖으로 밀려 "여기서 끝"이 읽히지 않는다. `fill` 이면 **표만 자기 안에서 스크롤하고 페이지네이션은 하단에 고정**된다.
|
|
1332
|
+
- 본문 루트에 `h-full min-h-0` 으로 세로 축을 잇고, 남은 높이를 먹을 `STable` 에 `min-h-0 flex-1` 을 준다. 이 사슬이 하나라도 끊기면 표가 높이를 못 잡는다.
|
|
1333
|
+
- `fill` 에서는 페이지가 스크롤하지 않으므로 **`scrollEndSpacing` 은 무시된다** — 따로 끄지 않는다 (§2-2).
|
|
1223
1334
|
|
|
1224
1335
|
```tsx
|
|
1225
1336
|
import {
|
|
@@ -1267,9 +1378,10 @@ export default function ProductListPage() {
|
|
|
1267
1378
|
// 이 페이지의 주요 액션이 아니라 부가 액션 — slot 은 sm 버튼으로만 채운다
|
|
1268
1379
|
slot: <SButton size="sm" color="neutral" outline label="이용 가이드" onClick={openGuide} />,
|
|
1269
1380
|
}}
|
|
1270
|
-
|
|
1381
|
+
contentHeight="fill" // 표가 남은 높이를 채우고 페이지네이션이 하단에 고정된다
|
|
1271
1382
|
>
|
|
1272
|
-
|
|
1383
|
+
{/* h-full min-h-0 → STable 의 min-h-0 flex-1 로 세로 축이 이어진다 */}
|
|
1384
|
+
<div className="flex h-full min-h-0 flex-col gap-sd-12">
|
|
1273
1385
|
{/* 필터 — search 를 켜면 우측에 검색 패널이 붙는다 */}
|
|
1274
1386
|
<SKeyValueTable
|
|
1275
1387
|
fields={filterFields}
|
|
@@ -1294,7 +1406,9 @@ export default function ProductListPage() {
|
|
|
1294
1406
|
}
|
|
1295
1407
|
/>
|
|
1296
1408
|
|
|
1409
|
+
{/* 남은 높이를 채우고 본문만 스크롤한다 — 페이지네이션 바는 표 안에서 하단 고정 */}
|
|
1297
1410
|
<STable
|
|
1411
|
+
className="min-h-0 flex-1"
|
|
1298
1412
|
columns={columns}
|
|
1299
1413
|
rows={rows}
|
|
1300
1414
|
rowKey="id"
|
|
@@ -1380,7 +1494,7 @@ export default function ProductCreatePage() {
|
|
|
1380
1494
|
|
|
1381
1495
|
- 조회 값은 `type: 'text'` 행으로 표시한다. **상태·분류 태그도 별도 영역이 아니라 표의 한 행**으로 넣는다 (`render` 에 `STag`).
|
|
1382
1496
|
- 행이 많아지면 **유형별로 섹션을 나누고, 각 섹션을 `SSectionHeaderCard` 로 감싼다.**
|
|
1383
|
-
|
|
1497
|
+
섹션 제목은 `title` prop 으로, 바디 여백은 `padding` prop 으로 준다.
|
|
1384
1498
|
- **수정·삭제 버튼은 하단에 둔다.** 내용이 짧아 우측 상단에 두는 변형도 있으나 기본은 하단이다.
|
|
1385
1499
|
|
|
1386
1500
|
```tsx
|
|
@@ -1413,24 +1527,18 @@ export default function ProductDetailPage() {
|
|
|
1413
1527
|
// 목록에서 들어온 상세 페이지 — onBack 으로 뒤로가기를 준다
|
|
1414
1528
|
<AppShell header={{ fix: true, title: '클래식 셔츠', onBack: goList }}>
|
|
1415
1529
|
<div className="flex flex-col gap-sd-12">
|
|
1416
|
-
<SSectionHeaderCard>
|
|
1417
|
-
<
|
|
1418
|
-
<SSectionHeaderCard.Body>
|
|
1419
|
-
<SKeyValueTable fields={basicFields} values={product} />
|
|
1420
|
-
</SSectionHeaderCard.Body>
|
|
1530
|
+
<SSectionHeaderCard title="기본 정보" marker thickness="accent">
|
|
1531
|
+
<SKeyValueTable fields={basicFields} values={product} />
|
|
1421
1532
|
</SSectionHeaderCard>
|
|
1422
1533
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
/>
|
|
1431
|
-
<SSectionHeaderCard.Body>
|
|
1432
|
-
<SKeyValueTable fields={priceFields} values={product} />
|
|
1433
|
-
</SSectionHeaderCard.Body>
|
|
1534
|
+
{/* 헤더 우측에 액션이 필요하면 slot 을 쓴다 */}
|
|
1535
|
+
<SSectionHeaderCard
|
|
1536
|
+
title="가격 정보"
|
|
1537
|
+
marker
|
|
1538
|
+
helpText={['부가세 포함 금액입니다.']}
|
|
1539
|
+
slot={<SButton size="sm" color="secondary" label="이력" onClick={openHistory} />}
|
|
1540
|
+
>
|
|
1541
|
+
<SKeyValueTable fields={priceFields} values={product} />
|
|
1434
1542
|
</SSectionHeaderCard>
|
|
1435
1543
|
|
|
1436
1544
|
{/* 액션은 하단 — 목록(되돌리기)은 왼쪽 끝, 실행 액션은 오른쪽 끝 */}
|
|
@@ -1479,8 +1587,8 @@ export default function ProductDetailPage() {
|
|
|
1479
1587
|
- [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
|
|
1480
1588
|
- [ ] `SPage`·`SPopup` 의 기본 패딩을 `p-sd-*` 로 덮어쓰지 않았는가, 블록·섹션 **간격**이 `gap-sd-12` 인가 (`gap-sd-16`/`gap-sd-24` ❌ — 24 는 안쪽 여백에만 열린다)
|
|
1481
1589
|
- [ ] 섹션·패널의 안쪽 **여백**이 §2-2 판정과 맞는가 (덩어리 두 종류 → 16 / 세 종류 이상 → 24, 서면 16)
|
|
1482
|
-
- [ ] `SSectionHeaderCard
|
|
1483
|
-
- [ ] 자체 스크롤하는 패널의 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` 이 있는가, 페이지네이션 있는 목록에서 `
|
|
1590
|
+
- [ ] `SSectionHeaderCard` 의 여백을 `p-sd-*` 가 아니라 `padding` prop 으로 줬는가
|
|
1591
|
+
- [ ] 자체 스크롤하는 패널의 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` 이 있는가, 페이지네이션 있는 목록에서 `contentHeight="fill"` 을 넘기고 `h-full min-h-0` → `STable` 의 `min-h-0 flex-1` 사슬을 이었는가
|
|
1484
1592
|
- [ ] 같은 컴포넌트를 나열할 때 §2-2 그룹 간격을 썼는가 (체크박스 가로 `gap-sd-24` 등)
|
|
1485
1593
|
- [ ] 페이지가 §4의 표준 골격에서 시작했는가
|
|
1486
1594
|
- [ ] `header.fix` 가 프로젝트 전체와 같은 값인가 (다른 페이지와 다르게 섞어 쓰지 않았는가, §4-1)
|
package/README.md
CHANGED
|
@@ -186,6 +186,7 @@ export default [
|
|
|
186
186
|
| `sellmate/component-group-gap` | warn | 같은 컴포넌트를 나열할 때의 그룹 간격 — 배열 방향에 따라 값이 다르다(체크박스 가로 24 / 세로 8) |
|
|
187
187
|
| `sellmate/table-numeric-align` | warn | 수량 컬럼(금액·수량 등)에 `align: 'right'` 누락 — **`--fix` 로 자동 교정** |
|
|
188
188
|
| `sellmate/require-locale-number` | warn | 수량 컬럼의 `toLocaleString()` 누락 — 세 자리 콤마 |
|
|
189
|
+
| `sellmate/field-width-grade` | warn | 필드 폭이 `maxLength`(스키마 상한)와 어긋남 — 등급 미지정 · 등급 밖 폭 · 상한 대비 과부족 |
|
|
189
190
|
| `sellmate/no-arbitrary-class` | **off** | 토큰이 있는 속성(색·타이포·간격·모서리)의 임의 값 — `text-[14px]`, `bg-[#eee]`. 앱 고유 화면에는 정당한 사용이 많아 기본값은 끕니다 |
|
|
190
191
|
|
|
191
192
|
`className` 뿐 아니라 `cn()`/`clsx()` 인자, 템플릿 리터럴, 객체 키 안까지 검사합니다.
|
|
@@ -237,6 +238,68 @@ export default [
|
|
|
237
238
|
'sellmate/table-numeric-align': ['error', { allow: ['rank'] }],
|
|
238
239
|
```
|
|
239
240
|
|
|
241
|
+
### 필드 폭 등급 (`field-width-grade`)
|
|
242
|
+
|
|
243
|
+
필드 너비는 **`maxLength`(= 스키마 상한)로 정합니다.** 상한이 등급 안에 들어오면 그 등급으로 고정하고,
|
|
244
|
+
등급 상한(`xl`)을 넘거나 상한이 아예 없으면 행 전체(`width="100%"` 또는 생략)로 둡니다.
|
|
245
|
+
|
|
246
|
+
| 등급 | 폭 | |
|
|
247
|
+
| ---- | ----- | ------------------------ |
|
|
248
|
+
| `xs` | 80px | 숫자 필드 전용 |
|
|
249
|
+
| `sm` | 120px | |
|
|
250
|
+
| `md` | 160px | |
|
|
251
|
+
| `lg` | 240px | |
|
|
252
|
+
| `xl` | 480px | 정책상 상한 |
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// 상한이 등급 안에 들어온다 → 그 등급으로 고정
|
|
256
|
+
<SInput name="code" label="코드" maxLength={10} width={160} />
|
|
257
|
+
|
|
258
|
+
// 상한이 xl 로도 안 담긴다 → 행 전체
|
|
259
|
+
<SInput name="desc" label="설명" maxLength={100} width="100%" />
|
|
260
|
+
|
|
261
|
+
// 숫자 필드의 상한은 maxLength 가 아니라 max 다 (천단위 콤마도 폭을 먹는다)
|
|
262
|
+
<SNumberInput name="qty" label="수량" max={99} width={80} />
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
대상은 `SInput` · `SNumberInput` · `SBarcodeInput` 입니다. `STextarea` 는 여러 줄로 접혀 상한이 폭을
|
|
266
|
+
정하지 않고, 셀렉트 계열은 상한 개념이 없어 목록 최장값으로 폭을 정하며, 날짜·시간 계열은 컴포넌트가
|
|
267
|
+
자체 상한을 갖습니다.
|
|
268
|
+
|
|
269
|
+
**상한도 폭도 변수로 넘기면 검사하지 않습니다.** 정적으로 알 수 없는 것을 위반으로 보고하지 않습니다.
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
<SInput maxLength={LIMIT} width={fieldWidth} /> // 통과
|
|
273
|
+
<SInput maxLength={10} {...rest} /> // 통과 (spread 안에 width 가 있을 수 있다)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### 글자수 환산
|
|
277
|
+
|
|
278
|
+
등급이 몇 글자를 담는지는 토큰 값(글꼴 크기 · 좌우 패딩 · 테두리 · 스테퍼)에서 계산합니다.
|
|
279
|
+
한글은 전각이라 영숫자의 약 두 배를 먹으므로 **두 기준 사이에는 아무것도 보고하지 않습니다** —
|
|
280
|
+
영숫자로 채워도 넘칠 때만 "좁다", 한글로 채워도 남을 때만 "넓다" 로 판정합니다.
|
|
281
|
+
|
|
282
|
+
| 등급 | `SInput` (sm) | `SInput` (md) | `SNumberInput` (sm) | `SNumberInput` (sm, `useButton`) |
|
|
283
|
+
| ---- | ------------- | ------------- | ------------------- | -------------------------------- |
|
|
284
|
+
| `xs` | — | — | 영숫자 8 | 영숫자 2 |
|
|
285
|
+
| `sm` | 영숫자 14 / 한글 7 | 영숫자 11 / 한글 6 | 영숫자 14 | 영숫자 8 |
|
|
286
|
+
| `md` | 영숫자 20 / 한글 11 | 영숫자 16 / 한글 9 | 영숫자 20 | 영숫자 14 |
|
|
287
|
+
| `lg` | 영숫자 32 / 한글 17 | 영숫자 26 / 한글 14 | 영숫자 32 | 영숫자 26 |
|
|
288
|
+
| `xl` | 영숫자 68 / 한글 37 | 영숫자 57 / 한글 31 | 영숫자 68 | 영숫자 62 |
|
|
289
|
+
|
|
290
|
+
`SNumberInput` 은 `useButton` 여부로 글자 자리가 크게 갈립니다 — 스테퍼가 없으면 좌우 패딩이
|
|
291
|
+
두 배가 되고(기본값), 있으면 버튼 두 개와 간격이 폭을 먹습니다. 규칙은 JSX 의 `useButton` 을 읽어
|
|
292
|
+
둘을 구분합니다.
|
|
293
|
+
|
|
294
|
+
값이 팀 기준과 다르면 옵션으로 덮습니다.
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
'sellmate/field-width-grade': ['warn', {
|
|
298
|
+
grades: { sm: 120, md: 160, lg: 240, xl: 480 },
|
|
299
|
+
charRatio: { narrow: 0.55, wide: 1 },
|
|
300
|
+
}],
|
|
301
|
+
```
|
|
302
|
+
|
|
240
303
|
### 더 엄격하게 / 더 느슨하게
|
|
241
304
|
|
|
242
305
|
`configs.strict` 는 전 규칙을 error 로 올리고, `<ul>` `<ol>` `<li>` `<svg>` `<label>` 까지 검사하며, 간격 유틸리티에 `sd-` 접두를 강제합니다(`requirePrefix`). 디자인 시스템 규칙을 처음부터 전면 적용하는 신규 프로젝트용입니다.
|
|
@@ -10,6 +10,30 @@
|
|
|
10
10
|
|------|------|---------|-------------|
|
|
11
11
|
| `color?` | `SBadgeColor` | `'blue'` | 뱃지 색상 |
|
|
12
12
|
|
|
13
|
+
## Types
|
|
14
|
+
|
|
15
|
+
### SBadgeColor
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
export type SBadgeColor = (typeof BADGE_COLORS)[number];
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### BADGE_COLORS
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export const BADGE_COLORS = [
|
|
25
|
+
'red',
|
|
26
|
+
'orange',
|
|
27
|
+
'yellow',
|
|
28
|
+
'green',
|
|
29
|
+
'lightblue',
|
|
30
|
+
'blue',
|
|
31
|
+
'darkblue',
|
|
32
|
+
'indigo',
|
|
33
|
+
'grey',
|
|
34
|
+
] as const;
|
|
35
|
+
```
|
|
36
|
+
|
|
13
37
|
## Dependencies
|
|
14
38
|
|
|
15
39
|
### Used by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HTMLAttributes } from 'react';
|
|
2
|
-
export declare const BADGE_COLORS: readonly ["red", "orange", "yellow", "green", "blue", "darkblue", "indigo", "grey"];
|
|
2
|
+
export declare const BADGE_COLORS: readonly ["red", "orange", "yellow", "green", "lightblue", "blue", "darkblue", "indigo", "grey"];
|
|
3
3
|
export type SBadgeColor = (typeof BADGE_COLORS)[number];
|
|
4
4
|
export interface SBadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
5
|
/** 뱃지 색상 */
|
|
@@ -15,6 +15,40 @@
|
|
|
15
15
|
| `rightIcon?` | `SIconName` | — | 레이블 오른쪽 아이콘 |
|
|
16
16
|
| `label?` | `string` | — | 버튼 텍스트 (문자열만 — 아이콘은 icon/rightIcon 사용) |
|
|
17
17
|
|
|
18
|
+
## Types
|
|
19
|
+
|
|
20
|
+
### SButtonColor
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
export type SButtonColor = (typeof BUTTON_COLORS)[number];
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### SButtonSize
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export type SButtonSize = (typeof BUTTON_SIZES)[number];
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### BUTTON_COLORS
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
/**
|
|
36
|
+
* SButton 색상/사이즈 설정 — sd-button(component.button 토큰) 충실 포팅.
|
|
37
|
+
* Stencil `name`(예: primary_sm)의 preset을 color + outline(boolean) + size 로 분리.
|
|
38
|
+
* - primary / danger : solid·outline 모두 지원
|
|
39
|
+
* - secondary : solid 전용 (outline 스타일 없음 → outline 무시)
|
|
40
|
+
* - neutral : 흰 배경 고정, outline 은 회색 테두리만 추가(solid = 테두리 없는 흰 버튼)
|
|
41
|
+
* 색상은 theme.css의 `--cmp-button-*` CSS 변수를 참조한다.
|
|
42
|
+
*/
|
|
43
|
+
export const BUTTON_COLORS = ['primary', 'secondary', 'neutral', 'danger'] as const;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### BUTTON_SIZES
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
export const BUTTON_SIZES = ['xs', 'sm', 'md', 'lg'] as const;
|
|
50
|
+
```
|
|
51
|
+
|
|
18
52
|
## Dependencies
|
|
19
53
|
|
|
20
54
|
### Used by
|