sellmate-design-system-react 9.0.0-beta.50 → 9.0.0-beta.52
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 +105 -37
- package/dist/components/SBarChart/barChart.config.d.ts +41 -14
- package/dist/components/SLoginCard/sellmate-wordmark.d.ts +2 -1
- package/dist/components/SScrollArea/README.md +1 -0
- package/dist/components/SScrollArea/SScrollArea.d.ts +17 -0
- package/dist/components/SSelect/select.config.d.ts +1 -1
- package/dist/components/SSystemActionButton/README.md +12 -7
- package/dist/components/SSystemActionButton/SSystemActionButton.d.ts +15 -9
- package/dist/components/STable/README.md +6 -1
- package/dist/components/STable/STable.d.ts +61 -1
- package/dist/components/STableBar/README.md +1 -0
- package/dist/components/STableBar/STableBar.d.ts +8 -1
- package/dist/index.cjs +692 -384
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +692 -384
- package/dist/index.js.map +1 -1
- package/dist/lib/chart/tooltip.d.ts +12 -4
- package/dist/lib/chart/use-element-size.d.ts +22 -0
- package/dist/lib/overlay-scrollbar.d.ts +32 -0
- package/dist/{components/SSelect → lib}/row-metrics.d.ts +1 -0
- package/dist/llms-full.txt +125 -45
- package/dist/llms.txt +105 -37
- package/dist/styles.css +17 -20
- package/dist/theme.css +8 -4
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -197,7 +197,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
197
197
|
| `SKeyValueTable` 값 셀 | 인라인 · 요소 | 블록 |
|
|
198
198
|
| `SListItem` | 인라인 | 블록 · 요소 |
|
|
199
199
|
|
|
200
|
-
- **블록을 `div` 로 감싸지 않는다.** 감싸면 페이지 스택에서 빠져나가 `gap-sd-12` 리듬이 끊긴다. 여러 블록을 묶어야 하면 그건 섹션이므로 `SSectionHeaderCard` 다.
|
|
200
|
+
- **블록을 `div` 로 감싸지 않는다.** 감싸면 페이지 스택에서 빠져나가 `gap-sd-12` 리듬이 끊긴다. 여러 블록을 묶어야 하면 그건 섹션이므로 `SSectionHeaderCard` 다. **예외는 툴바+표 하나뿐이다** — 둘은 한 블록이라 붙여야 하므로 간격 없는 `div` 로 묶는다(바로 아래).
|
|
201
201
|
- **요소를 페이지에 직접 놓지 않는다.** 하단 액션 버튼들처럼 블록이 없는 자리는 `div` 로 한 줄을 만들어 그 `div` 가 블록이 된다 (§4-3·§4-4).
|
|
202
202
|
- **레이어는 어디서 띄워도 된다.** `body` 로 portal 되므로 셸 안에 넣을 필요가 없고, 넣어도 레이아웃이 바뀌지 않는다 (§4-1).
|
|
203
203
|
|
|
@@ -219,7 +219,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
219
219
|
1. 페이지 제목 `SPageHeader` (SGnb 와 SPage 사이. 가이드·매뉴얼 링크는 slot, 액션 버튼은 오지 않는다 §4-2)
|
|
220
220
|
2. 상시 안내 SCallout
|
|
221
221
|
3. 필터 SKeyValueTable · SChipFilter (§3-7-11)
|
|
222
|
-
4. 툴바 STableBar (건수 요약 + 액션)
|
|
222
|
+
4. 툴바 STableBar (제목 slot + 건수 요약 + 액션)
|
|
223
223
|
5. 본문 STable · 섹션 카드들 · SList …
|
|
224
224
|
6. 페이지네이션 SPagination (STable 이 pagination prop 으로 직접 그린다)
|
|
225
225
|
7. 하단 액션 되돌리기 왼쪽 · 실행 오른쪽 (§4-3)
|
|
@@ -227,6 +227,26 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
227
227
|
|
|
228
228
|
간격은 층마다 다르다 — 블록 ↔ 블록은 `gap-sd-12`, 요소 ↔ 요소는 `gap-sd-8` 이 기본이고, 같은 컴포넌트를 나열할 때는 컴포넌트별 그룹 간격이 따로 있다. 전부 §2-2 에 있다.
|
|
229
229
|
|
|
230
|
+
#### 툴바(4)와 본문 표(5)는 한 블록이다 — 붙인다
|
|
231
|
+
|
|
232
|
+
`STableBar` 는 자기 아래 표의 건수와 액션을 말한다. **둘 사이를 벌리면 툴바가 어느 표의 것인지 끊긴다.** 위 순서에서 4·5 만은 따로 선 블록이 아니라 한 덩어리이므로, 간격 없는 `div` 로 묶고 그 `div` 가 블록 스택의 한 칸이 된다.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
<div className="flex min-h-0 flex-1 flex-col"> {/* 툴바+표 = 한 블록 */}
|
|
236
|
+
<STableBar className="border-b-0" title="주문 목록" … />
|
|
237
|
+
<STable className="min-h-0 flex-1" … />
|
|
238
|
+
</div>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**표의 이름은 `STableBar` 의 `title` 슬롯에 넣는다.** 이름만 적자고 그 위에 `SSectionHeaderCard`
|
|
242
|
+
를 따로 세우지 않는다 — 블록이 한 칸 늘어 툴바와 표가 멀어지고, 위에서 막은 "둘 사이를 벌리지
|
|
243
|
+
않는다" 를 스스로 어기게 된다. 섹션 카드는 표 말고도 여러 블록을 함께 담을 때 쓴다.
|
|
244
|
+
|
|
245
|
+
- **맞닿는 가로선은 표가 그린다 — 툴바에 `border-b-0` 을 준다.** 둘 다 1px 테두리를 가진 상자라, 그냥 붙이면 그 변만 1px+1px=2px 로 두꺼워진다. 표에는 아무것도 주지 않는다.
|
|
246
|
+
- **`gap-sd-12` 는 이 덩어리 바깥에만 걸린다.** 필터 ↔ (툴바+표) 는 여느 블록 사이와 같다.
|
|
247
|
+
- **`min-h-0 flex-1` 사슬은 이 `div` 를 한 칸 더 지난다** — 감싸는 `div` 에도 붙여야 표가 남은 높이를 잡는다(§4-2).
|
|
248
|
+
- 툴바를 표 없이 단독으로 쓰는 자리라면 `border-b-0` 을 주지 않는다. 그때는 사방이 닫힌 제 상자다.
|
|
249
|
+
|
|
230
250
|
### 2-1. 타이포그래피 프리셋
|
|
231
251
|
|
|
232
252
|
역할(role) → 크기 → 굵기 순으로 조합된 클래스가 이미 준비되어 있다.
|
|
@@ -287,7 +307,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
287
307
|
| 상황 | 값 |
|
|
288
308
|
| --- | --- |
|
|
289
309
|
| **페이지 콘텐츠 패딩** | **`SPage` 가 `--cmp-pageBody-padding-default` 로 이미 넣는다.** 직접 주지 않는다 (덮어쓰면 토큰이 바뀌어도 안 따라간다). `SPage` 밖에서 같은 패딩이 필요하면 `p-sd-16` |
|
|
290
|
-
| **섹션 ↔ 섹션**, **블록 ↔ 블록** (
|
|
310
|
+
| **섹션 ↔ 섹션**, **블록 ↔ 블록** (헤더·필터·본문 사이) | **`gap-sd-12`** |
|
|
311
|
+
| **툴바 ↔ 그 표** (`STableBar` ↔ `STable`) | **간격 없음 — 붙인다.** 둘은 한 블록이다 (§2-0) |
|
|
291
312
|
| 요소 ↔ 요소 | **기본 `gap-sd-8`** (`--sys-space-stack-gap-element-normal`) · 타이트 `gap-sd-4`(`-tight`) · 여유 `gap-sd-12`(`-relaxed`) / `gap-sd-16`(`-wide`) |
|
|
292
313
|
| 제목 ↔ 설명 텍스트 | **수직 배치 `gap-sd-4`**(`--sys-space-stack-gap-text-normal`, 타이트 `gap-sd-2`) · **가로 배치 `gap-sd-8`**(`-relaxed`) |
|
|
293
314
|
|
|
@@ -415,8 +436,11 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
415
436
|
```tsx
|
|
416
437
|
<SPage contentHeight="fill">
|
|
417
438
|
<div className="flex h-full min-h-0 flex-col gap-sd-12">
|
|
418
|
-
|
|
419
|
-
<
|
|
439
|
+
{/* 툴바+표 = 한 블록. 사이를 벌리지 않는다 (§2-0) */}
|
|
440
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
441
|
+
<STableBar className="border-b-0" … />
|
|
442
|
+
<STable className="min-h-0 flex-1" pagination={…} />
|
|
443
|
+
</div>
|
|
420
444
|
</div>
|
|
421
445
|
</SPage>
|
|
422
446
|
```
|
|
@@ -543,7 +567,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
543
567
|
| 날짜별 일정을 한 달치 격자로 펼쳐 보여준다 | `SCalendarBoard` | §3-7-4 |
|
|
544
568
|
| 항목끼리 크기를 눈으로 견주게 한다 (기간별 추이·채널별 비중) | `SBarChart` | 정확한 값을 읽어야 하면 `STable` — 그래프는 "어느 쪽이 큰가" 를 답하지 "얼마인가" 를 답하지 않는다 |
|
|
545
569
|
| 항목의 크기와 **그 안의 구성**을 함께 보여준다 (주문 상태별 내역, 유입 경로별 몫) | `SBarChart stacked` | 계열끼리 견주는 것이 목적이면 `stacked` 없이 나란히 세운다. 더해서 뜻이 없는 값(비율·평균)은 쌓지 않는다 — 합계 라벨이 거짓말이 된다 |
|
|
546
|
-
| 표 위에 건수 요약과 액션을 얹는다 | `STableBar` | §4-2 |
|
|
570
|
+
| 표 위에 건수 요약과 액션을 얹는다 | `STableBar` | 표의 이름도 여기 `title` 슬롯에 넣는다 — 제목만으로 블록을 따로 세우지 않는다. §4-2 |
|
|
547
571
|
| 상태·분류를 라벨로 찍는다 | `STag` | §3-1 |
|
|
548
572
|
| 색 점만으로 상태를 찍는다 | `SBadge` | §3-1 |
|
|
549
573
|
| 아이콘을 넣는다 | `SIcon` | |
|
|
@@ -1052,6 +1076,39 @@ const columns: STableColumn[] = [
|
|
|
1052
1076
|
|
|
1053
1077
|
**값을 반드시 채워야 하는 열에는 `column.required`** 를 준다 — 라벨 뒤에 `*` 가 붙는다. `SKeyValueTable` 의 `field.required` 와 같은 것이다. **읽기 전용 열에 붙이지 않는다** — 표시만 있고 채울 방법이 없어 사용자가 막힌다.
|
|
1054
1078
|
|
|
1079
|
+
#### 고를 수 없는 행은 `isRowSelectable` 로 잠근다
|
|
1080
|
+
|
|
1081
|
+
`selectable` 을 켠 표에서 **일부 행만 대상이 될 수 있을 때** 쓴다 — 실패 0건인 차수처럼 자리는 보여야 하지만 고를 수는 없는 행이다.
|
|
1082
|
+
|
|
1083
|
+
```tsx
|
|
1084
|
+
<STable selectable isRowSelectable={row => row.failedCount > 0} … />
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
- **체크박스를 직접 잠그려 하지 않는다.** 색·커서·hover 가 한 벌로 움직이므로 밖에서 속성만 바꾸면 "잠기지 않았는데 멀쩡해 보이는" 상태가 된다. 이 prop 하나로 셋이 함께 잡힌다.
|
|
1088
|
+
- **전체 선택과 Shift 구간 선택의 셈에서도 빠진다.** 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 못 가고 한 방향이 된다. DS 가 이걸 처리하므로 직접 보정하지 않는다.
|
|
1089
|
+
- **아예 대상이 아닌 행이라면 `rows` 에서 거르는 편이 낫다.** 잠긴 행이 잔뜩 섞이면 무엇을 고를 수 있는지가 오히려 안 읽힌다. 고를 수 있는 행이 한 줄도 없으면 헤더까지 잠기는데, 그 상태라면 `selectable` 을 켤 자리가 아니다.
|
|
1090
|
+
- 잠금은 그리는 시점의 판정이라, **이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다** — 제어 상태를 말없이 바꾸지 않기 때문이다. 헤더의 전체 해제로는 걷힌다.
|
|
1091
|
+
|
|
1092
|
+
#### 표 본문의 스크롤에 닿아야 할 때
|
|
1093
|
+
|
|
1094
|
+
표는 자기 안에서 스크롤한다(§2-2). 그 스크롤을 **읽거나 되돌려야** 하면 두 가지가 있다.
|
|
1095
|
+
|
|
1096
|
+
- **`onScroll`** — 본문이 스크롤될 때 온다. 실제 스크롤에만 울리므로 "사용자가 스크롤했다"는 신호로 쓸 수 있다. `onVirtualUpdate` 는 그리는 창이 바뀔 때만 오므로 그 사이 스크롤은 알 수 없다.
|
|
1097
|
+
- **`ref.current.getScrollElement()`** — 본문의 스크롤 컨테이너. 위치를 되돌리거나 스크롤 여지를 잴 때 쓴다.
|
|
1098
|
+
|
|
1099
|
+
**내부 마크업을 겨냥한 선택자로 스크롤 컨테이너를 찾지 않는다.** `querySelector` 로 `overflow` 가 걸린 조상을 거슬러 올라가는 코드는 DS 내부 구조에 기대는 것이라, 표 안쪽이 바뀌는 순간 조용히 어긋난다.
|
|
1100
|
+
|
|
1101
|
+
##### 위치를 저장했다 되돌릴 때는 픽셀이 아니라 행 번호로 한다
|
|
1102
|
+
|
|
1103
|
+
```tsx
|
|
1104
|
+
const row = tableRef.current.getFirstVisibleRow(); // 저장
|
|
1105
|
+
tableRef.current.scrollToRow(row); // 복원
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
`virtualScroll` 에서 `scrollTop` 은 **실측이 쌓이며 좌표계째 움직이는 값**이다. 저장할 때의 픽셀은 실측된 좌표계의 값인데 되돌릴 때는 아직 아무것도 안 잰 추정 좌표계로 읽히므로, 같은 픽셀이 다른 행을 가리킨다. `rowHeight` 가 실제와 멀수록 크게 벌어진다.
|
|
1109
|
+
|
|
1110
|
+
`scrollToRow` 는 옮긴 뒤에도 실측이 정착할 때까지 그 행을 다시 겨냥하므로 `rowHeight` 가 빗나가도 제자리에 선다. 가상 스크롤을 쓰지 않는 표는 좌표계가 하나뿐이라 픽셀로 해도 되지만, **두 경우를 갈라 쓰기보다 행 번호로 통일하는 편이 낫다.**
|
|
1111
|
+
|
|
1055
1112
|
### 3-5. 버튼류
|
|
1056
1113
|
|
|
1057
1114
|
| 상황 | 사용 |
|
|
@@ -1210,8 +1267,8 @@ const columns: STableColumn[] = [
|
|
|
1210
1267
|
| `type` | 선행 아이콘 | 라벨 | 우측 | 언제 |
|
|
1211
1268
|
| --- | --- | --- | --- | --- |
|
|
1212
1269
|
| `account` | `user` 고정 | `label` | 없음 | 로그인한 계정을 보여줄 때. 누르면 **언제나** 계정 리스트박스가 뜬다 — `option` 필수 (§3-5-8) |
|
|
1213
|
-
| `select` | `option.icon` (
|
|
1214
|
-
| `switch` | `option.icon` (
|
|
1270
|
+
| `select` | `option.icon` (선택) | `label` | 화살표 | 눌러서 하위 화면으로 들어갈 때 |
|
|
1271
|
+
| `switch` | `option.icon` (선택) | `label` | `option.text` + 새로고침 | 지금 붙어 있는 대상을 갈아 끼울 때 |
|
|
1215
1272
|
| `subSelect` | `option.icon` (필수) | `label` | 지금 값 + 화살표 | 값을 보여주고 **눌러서 바꿀** 때. 누르면 언제나 메뉴가 뜬다 — `option.options` 필수 |
|
|
1216
1273
|
| `status` | 없음 | 상태 태그(고른 칸의 `color`·`label`) | 화살표 | 상태를 보여주고 **눌러서 바꿀** 때. 누르면 언제나 메뉴가 뜬다 — `option.options` 필수 |
|
|
1217
1274
|
|
|
@@ -1219,7 +1276,7 @@ const columns: STableColumn[] = [
|
|
|
1219
1276
|
- **GNB 의 system 자리(맨 아래 판 · 전폭 상단바)에 놓는 것이라면 이 버튼을 직접 배치하지 말고 `SGnbSystem` 의 `actions` 에 넘긴다** (§4-1). 패널의 배경·구분선·도메인 태그·알림·계정까지 한 벌로 그려주므로, 조각을 손으로 조립할 이유가 없다. **단 `type="account"` 는 `actions` 가 받지 않는다** — 계정 행은 패널에 하나뿐이고 자리도 맨 아래로 정해져 있어 `account` prop 이 따로 있다 (§3-5-8).
|
|
1220
1277
|
- **`color` 는 놓이는 표면을 따라간다** — 흰 면이면 `light`, 오션블루 면이면 `dark`, 네이비 면이면 `darker`. 배경과 다른 색을 고르면 버튼만 떠 보인다.
|
|
1221
1278
|
- **상태는 default·hover 둘뿐이다.** 토큰에 selected 가 없으므로 "지금 열려 있는 항목"을 이 버튼으로 표시하지 않는다. 선택 상태가 필요한 자리면 `SGnb` 메뉴나 `SListItem`(§3-7-6) 이다.
|
|
1222
|
-
- **선행
|
|
1279
|
+
- **선행 아이콘은 타입이 정한다.** `account` 는 `user` 고정, `status` 는 태그가 자리를 차지해 없고, `subSelect` 는 `option.icon` 이 필수다. **`select` · `switch` 만 선택이다** — 주지 않으면(`option` 을 통째로 생략해도 된다) 아이콘 프레임 없이 라벨부터 시작한다. **한 판 안에서는 아이콘 유무를 통일한다** — 섞으면 라벨 시작점이 행마다 어긋나 세로 정렬이 깨진다.
|
|
1223
1280
|
- **`account` 의 `option` 만 성격이 다르다** — 아이콘이 아니라 **누르면 뜨는 계정 리스트박스의 내용**이고, **필수다** (§3-5-8). 눌러도 아무것도 뜨지 않는 계정 행은 없다 — 계정 화면으로 넘기는 것은 이 행이 아니라 패널의 `accountSetting` 이 맡는다.
|
|
1224
1281
|
- **우측 아이콘은 `type` 이 정하므로 밖에서 바꾸지 않는다.** 화살표가 필요한데 문구가 없으면 `select`, 문구가 함께 필요하면 `subSelect` 다.
|
|
1225
1282
|
- **`subSelect` · `status` 는 언제나 값 고르기 메뉴의 트리거다** — 지금 값이 적혀 있는 행이라 누르면 그 값을 바꾸는 메뉴가 뜬다. 그래서 `option.options` 가 **필수**이고, 메뉴는 **행 오른쪽으로**, 행 위쪽 끝에 맞춰 펼쳐진다 — 이 행은 판 안에 세로로 쌓이는 자리라 아래로 펴면 바로 다음 행을 덮는다. 고르면 `onChange(value)` 가 돌며 닫힌다. **메뉴를 손으로 만들지 않는다** — 뜨는 자리·닫히는 시점이 어긋난다. 계정 패널의 언어 변경 행(§3-5-8)도 같은 메뉴이지만, 그쪽은 판 안에 갇혀 있어 우측 값 아래로 편다.
|
|
@@ -1936,7 +1993,9 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1936
1993
|
|
|
1937
1994
|
### 4-2. 목록 페이지 (필터 + 테이블)
|
|
1938
1995
|
|
|
1939
|
-
구조: **페이지 헤더(`AppShell` 의 `header` prop, 가이드 등 부가 액션은 slot) → 필터(`SKeyValueTable`) → `STableBar`
|
|
1996
|
+
구조: **페이지 헤더(`AppShell` 의 `header` prop, 가이드 등 부가 액션은 slot) → 필터(`SKeyValueTable`) → (`STableBar` + `STable`)**
|
|
1997
|
+
|
|
1998
|
+
**뒤의 둘은 한 블록이라 붙여 쓴다** — 툴바는 자기 아래 표의 건수와 액션을 말하므로 사이를 벌리지 않는다. 묶는 방법과 맞닿는 변 처리는 §2-0 에 있다.
|
|
1940
1999
|
|
|
1941
2000
|
필터 자리는 `SChipFilter` 로 바꿔 놓을 수 있다 — 조건 후보가 많아 쓸 것만 골라 붙이게 하는 화면이면 그쪽이다 (§3-7-11). 나머지 골격은 같다.
|
|
1942
2001
|
|
|
@@ -1946,7 +2005,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
|
|
|
1946
2005
|
- **주요 액션(등록 등)은 `STableBar` 의 `rightActions`** 에 둔다.
|
|
1947
2006
|
- **선택 상태 액션(선택 삭제 등)은 `STableBar` 의 `actions`** 에 둔다. `actions` 슬롯은 건수 요약이 있으면 앞에 구분선(`SDivider`)을 **자동으로** 넣으므로 직접 구분선을 만들지 않는다.
|
|
1948
2007
|
- **본문이 남은 높이를 채우게 한다** — `AppShell` 에 `contentHeight="fill"` 을 넘긴다(§2-2 표준). 페이지가 통째로 스크롤되면 페이지네이션이 화면 밖으로 밀려 "여기서 끝"이 읽히지 않는다. `fill` 이면 **표만 자기 안에서 스크롤하고 페이지네이션은 하단에 고정**된다.
|
|
1949
|
-
- 본문 루트에 `h-full min-h-0` 으로 세로 축을 잇고, 남은 높이를 먹을 `STable` 에 `min-h-0 flex-1` 을 준다. 이 사슬이 하나라도 끊기면 표가 높이를 못 잡는다.
|
|
2008
|
+
- 본문 루트에 `h-full min-h-0` 으로 세로 축을 잇고, 남은 높이를 먹을 `STable` 에 `min-h-0 flex-1` 을 준다. 이 사슬이 하나라도 끊기면 표가 높이를 못 잡는다. **툴바+표를 묶은 `div` 도 사슬의 한 칸이라 거기에도 `min-h-0 flex-1` 이 필요하다** — 빠뜨리기 가장 쉬운 자리다.
|
|
1950
2009
|
- `fill` 에서는 페이지가 스크롤하지 않으므로 **`scrollEndSpacing` 은 무시된다** — 따로 끄지 않는다 (§2-2).
|
|
1951
2010
|
- **정렬 가능한 컬럼은 `sortable` 로 준다.** 정렬 상태(`sort`)는 이 페이지가 들고 `onSortChange` 로 받는다 — 조회 조건이라 URL 에 실려야 한다 (§3-4).
|
|
1952
2011
|
|
|
@@ -2010,33 +2069,38 @@ export default function ProductListPage() {
|
|
|
2010
2069
|
onSearch={fetchList}
|
|
2011
2070
|
/>
|
|
2012
2071
|
|
|
2013
|
-
{/*
|
|
2014
|
-
<
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2072
|
+
{/* 툴바+표 = 한 블록. 사이를 벌리지 않는다 (§2-0) */}
|
|
2073
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
2074
|
+
{/* 툴바 — 좌: 건수 + (구분선 자동) + 선택 액션 / 우: 주요 액션
|
|
2075
|
+
맞닿는 가로선은 표가 그리므로 아래 테두리는 지운다 */}
|
|
2076
|
+
<STableBar
|
|
2077
|
+
className="border-b-0"
|
|
2078
|
+
total={total}
|
|
2079
|
+
selected={selected.length}
|
|
2080
|
+
actions={
|
|
2081
|
+
/* 선택 항목 단위 파괴 액션 → danger outline (§3-5-3) */
|
|
2082
|
+
<SButton size="sm" color="danger" outline label="선택 삭제"
|
|
2083
|
+
disabled={!selected.length} onClick={removeSelected} />
|
|
2084
|
+
}
|
|
2085
|
+
rightActions={
|
|
2086
|
+
/* 이 페이지의 유일한 primary 채움 (§3-5-1) */
|
|
2087
|
+
<SButton size="sm" label="상품 등록" onClick={goCreate} />
|
|
2088
|
+
}
|
|
2089
|
+
/>
|
|
2027
2090
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2091
|
+
{/* 남은 높이를 채우고 본문만 스크롤한다 — 페이지네이션 바는 표 안에서 하단 고정 */}
|
|
2092
|
+
<STable
|
|
2093
|
+
className="min-h-0 flex-1"
|
|
2094
|
+
columns={columns}
|
|
2095
|
+
rows={rows}
|
|
2096
|
+
rowKey="id"
|
|
2097
|
+
selectable
|
|
2098
|
+
selected={selected}
|
|
2099
|
+
onSelectedChange={setSelected}
|
|
2100
|
+
pagination={{ currentPage, lastPage }}
|
|
2101
|
+
loading={loading}
|
|
2102
|
+
/>
|
|
2103
|
+
</div>
|
|
2040
2104
|
</div>
|
|
2041
2105
|
</AppShell>
|
|
2042
2106
|
);
|
|
@@ -2328,6 +2392,7 @@ export default function ProductDetailPage() {
|
|
|
2328
2392
|
- [ ] 필터·폼·상세 정보를 `SKeyValueTable` 로 만들었는가 (컨트롤을 `div` 로 나열하지 않았는가). 필터를 `SChipFilter` 로 했다면 §3-7-11 의 판정을 거쳤는가
|
|
2329
2393
|
- [ ] 섹션 구분에 `SSectionHeaderCard` 를 썼는가 (직접 만든 카드가 아니라)
|
|
2330
2394
|
- [ ] 목록의 주요 액션이 `STableBar` 의 `rightActions` 에 있는가 (페이지 제목 줄이 아니라)
|
|
2395
|
+
- [ ] `STableBar` 와 그 표를 **붙였는가** — 사이에 `gap-sd-12` 를 넣지 않았고, 툴바에 `border-b-0` 을 줬는가 (§2-0 — 안 주면 맞닿는 변만 2px 로 보인다)
|
|
2331
2396
|
- [ ] 목록 페이지 표에 `dense` 로 밀도 토글을 띄웠는가, 그 값을 `onDenseChange` 결과로 되돌려 넣지는 않았는가 (§4-2 — 되돌려 넣으면 넓게 본 순간 토글이 사라진다)
|
|
2332
2397
|
- [ ] 상태 표시에 `STag size="sm"` 을 썼는가
|
|
2333
2398
|
- [ ] 테이블에서 양을 나타내는 컬럼(금액·수량·개수 등)이 전부 `align: 'right'` 인가
|
|
@@ -2336,6 +2401,9 @@ export default function ProductDetailPage() {
|
|
|
2336
2401
|
- [ ] **모든 컬럼에 폭을 명시**했는가, px 로만 줬는가 (`%`·`clamp()` ❌), `autoWidth` 는 스페이서 열 하나뿐인가 (§3-4)
|
|
2337
2402
|
- [ ] 컨트롤(`STag`·`SButton`·`SSelect`·`SInput` …)이 들어가는 컬럼이 `resizable` 이면 `minWidth` 도 줬는가 (§3-4 — 폭이 모자라면 요소가 잘려 못 쓴다)
|
|
2338
2403
|
- [ ] 정렬 가능한 열에 `sortable` 을 줬는가 (`renderHeader` 로 직접 만들지 않았는가), 정렬 상태를 페이지가 들고 있는가 (§3-4)
|
|
2404
|
+
- [ ] 일부 행만 고를 수 있는 표라면 `isRowSelectable` 로 잠갔는가 — 체크박스를 밖에서 직접 잠그거나 전체 선택을 손으로 보정하지 않았는가 (§3-4)
|
|
2405
|
+
- [ ] 표 본문의 스크롤이 필요하면 `onScroll` · `ref.getScrollElement()` 를 썼는가 — 내부 마크업을 겨냥한 선택자로 스크롤 컨테이너를 찾지 않았는가 (§3-4)
|
|
2406
|
+
- [ ] 스크롤 위치를 저장했다 되돌린다면 **행 번호**(`getFirstVisibleRow` → `scrollToRow`)로 했는가, `scrollTop` 픽셀을 저장하지는 않았는가 (§3-4 — 가상 스크롤에서는 좌표계가 실측으로 움직여 다른 행이 나온다)
|
|
2339
2407
|
- [ ] `editable` · `navigable` 표식을 켠 열이 **셀에서도 실제로 그렇게 동작하는가** (입력 컨트롤 · 링크가 있는가), 표식을 붙인 열의 폭을 함께 넓혔는가 (§3-4)
|
|
2340
2408
|
- [ ] 금액·수량 등 양을 나타내는 숫자에 빠짐없이 `toLocaleString()` 을 썼는가 (번호·코드는 제외)
|
|
2341
2409
|
- [ ] 날짜를 `YYYY-MM-DD` 로 표기했는가 (`toLocaleDateString()` ❌, §1-4)
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SBarChart 의 치수·색 상수와 막대 배치 계산.
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ 아래 네 값은 원래 `--cmp-chart-bar-*` 로 와야 하지만, `tokens/token.json` 의
|
|
5
|
-
* `chart.bar` 노드가 그룹인데 `"type": "other"` 를 달고 있어 생성기가 그 아래를 통째로
|
|
6
|
-
* 잘라 버린다(자식 하나의 이름이 `value` 라 leaf 로 오인된다). 토큰이 고쳐지면
|
|
7
|
-
* 아래 TODO 를 따라 교체한다.
|
|
8
3
|
*/
|
|
9
4
|
/**
|
|
10
5
|
* 막대 두께 하한(px) — 시안 `bar width min : 24`.
|
|
@@ -20,20 +15,49 @@ export declare const BAR_GAP = 6;
|
|
|
20
15
|
export declare const GROUP_PADDING_MIN = 8;
|
|
21
16
|
/** 막대 묶음 좌우 여백의 상한(px) — 시안 `bar group paddingX max : 24`. */
|
|
22
17
|
export declare const GROUP_PADDING_MAX = 24;
|
|
18
|
+
/**
|
|
19
|
+
* 항목 축 양끝에 두는 여백(px).
|
|
20
|
+
*
|
|
21
|
+
* 카테고리가 나눠 갖는 자리(band) 바깥에 따로 붙는다 — 첫 막대와 마지막 막대가
|
|
22
|
+
* 차트 경계에 붙어 잘린 것처럼 보이지 않게 한다. 세로 막대는 좌우, 가로 막대는
|
|
23
|
+
* 위아래에 생긴다.
|
|
24
|
+
*/
|
|
25
|
+
export declare const AXIS_EDGE_PADDING = 24;
|
|
23
26
|
/** 막대 끝과 값 라벨 사이 간격(px) — 시안 실측. */
|
|
24
27
|
export declare const VALUE_LABEL_GAP = 6;
|
|
25
28
|
/**
|
|
26
|
-
* 막대 모서리 반경(px). SVG 경로를 좌표로 계산하므로 `var(--radius
|
|
27
|
-
* **숫자**여야 한다 — 시안의 clipPath 도 같은 이유로 8 을 좌표에
|
|
28
|
-
*
|
|
29
|
+
* 막대 모서리 반경(px). SVG 경로를 좌표로 계산하므로 `var(--cmp-chart-bar-radius)`
|
|
30
|
+
* 문자열이 아니라 **숫자**여야 한다 — 시안의 clipPath 도 같은 이유로 8 을 좌표에
|
|
31
|
+
* 박아 두고 있다. 토큰(`chart.bar.radius`)과 같은 값을 손으로 맞춰 둔 유일한 자리다.
|
|
29
32
|
*/
|
|
30
33
|
export declare const BAR_RADIUS = 8;
|
|
31
|
-
|
|
32
|
-
export declare const
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
export declare const BAR_OPACITY_DEFAULT = "var(--cmp-chart-bar-opacity-default)";
|
|
35
|
+
export declare const BAR_OPACITY_DIMMED = "var(--cmp-chart-bar-opacity-dimmed)";
|
|
36
|
+
/**
|
|
37
|
+
* 호버한 항목의 막대 — 완전히 불투명하다.
|
|
38
|
+
*
|
|
39
|
+
* `--opacity-*` 스케일에 100% 가 없다. "투명하지 않음"은 값이 아니라 값의 부재라
|
|
40
|
+
* 디자인 토큰으로 두지 않는 쪽이 맞으므로, 여기서는 토큰 대신 1 을 쓴다.
|
|
41
|
+
*/
|
|
42
|
+
export declare const BAR_OPACITY_HOVER = 1;
|
|
43
|
+
export declare const BAR_DATA_LABEL_COLOR = "var(--cmp-chart-bar-dataLabel-color-default)";
|
|
44
|
+
/**
|
|
45
|
+
* 값 라벨의 타이포 — 호버한 항목만 굵고 크게 뜬다.
|
|
46
|
+
*
|
|
47
|
+
* 타이포 토큰은 `--cmp-*` 변수로 나오지 않고 `.typo-*` 유틸로만 나오므로,
|
|
48
|
+
* `chart.bar.dataLabel.typography.default`(`body.sm.medium`) ·
|
|
49
|
+
* `.hover`(`body.md.bold`) 에 대응하는 클래스를 여기서 짝지어 둔다.
|
|
50
|
+
*/
|
|
51
|
+
export declare const BAR_DATA_LABEL_TYPO = "typo-body-sm-medium";
|
|
52
|
+
export declare const BAR_DATA_LABEL_TYPO_HOVER = "typo-body-md-bold";
|
|
53
|
+
/**
|
|
54
|
+
* 흐려지고 또렷해지는 전환 — 막대와 값 라벨에 같이 건다.
|
|
55
|
+
*
|
|
56
|
+
* 둘의 속도가 다르면 막대는 이미 물러났는데 숫자만 뒤늦게 따라와 어긋나 보인다.
|
|
57
|
+
* 전환 대상은 불투명도뿐이다. 짚은 항목의 라벨이 커지는 것(`typo-*` 교체)은
|
|
58
|
+
* 글자가 늘어나는 중간 상태가 오히려 읽기 나빠 즉시 바꾼다.
|
|
59
|
+
*/
|
|
60
|
+
export declare const BAR_HOVER_TRANSITION = "transition-opacity duration-200 ease-out motion-reduce:transition-none";
|
|
37
61
|
export interface BandLayout {
|
|
38
62
|
/** 카테고리 하나가 차지하는 폭(px) */
|
|
39
63
|
band: number;
|
|
@@ -61,5 +85,8 @@ export interface BandLayout {
|
|
|
61
85
|
*
|
|
62
86
|
* 자리가 최소보다 좁으면 `t=0` 에서 멈춘다 — 더 얇아지는 대신 호출 쪽이 스크롤을
|
|
63
87
|
* 만든다. 반대로 자리가 남으면 `t=1` 에서 멈추고 남는 폭은 `offset` 으로 가운데 모은다.
|
|
88
|
+
*
|
|
89
|
+
* 양끝 `AXIS_EDGE_PADDING` 은 band 가 나눠 갖기 전에 먼저 떼어 둔다 — band 안의
|
|
90
|
+
* `padding` 과 달리 카테고리 수·두께와 무관하게 항상 그대로 남아야 한다.
|
|
64
91
|
*/
|
|
65
92
|
export declare function bandLayout(size: number, categoryCount: number, seriesCount: number, barSizeMax?: number): BandLayout;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* (가장 가까운 `--color-primary-90` 도 `#002B5E` 로 다른 색이다), 있더라도 로고 색은
|
|
7
7
|
* 테마를 따라 바뀌면 안 되는 값이다. `scripts/check-hardcoded.mjs` 의 ALLOW 에 등록해 둔다.
|
|
8
8
|
*
|
|
9
|
-
* 원본 비율은 276
|
|
9
|
+
* 원본 비율은 276:40 이다. 기본 크기는 높이 32px 이고 폭(220.8px)은 그 비율에서 나온다.
|
|
10
|
+
* 크기를 바꾸려면 `className` 으로 높이만 주고 폭은 비율에 맡긴다.
|
|
10
11
|
*/
|
|
11
12
|
export declare function SellmateWordmark({ className }: {
|
|
12
13
|
className?: string;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| `bordered?` | `boolean` | `false` | true면 스크롤바와 콘텐츠 사이에 1px 구분선을 그린다. 컨테이너 테두리가 아니다. |
|
|
14
14
|
| `maxHeight?` | `string` | — | viewport 최대 높이 (예: '400px'). 비우면 부모 크기를 따른다. |
|
|
15
15
|
| `maxWidth?` | `string` | — | viewport 최대 너비 (예: '480px'). 비우면 부모 크기를 따른다. |
|
|
16
|
+
| `overlayScrollbar?` | `boolean` | `false` | true면 네이티브 스크롤바를 숨기고 영역 위에 오버레이 스크롤바를 얹는다. 스크롤바가 레이아웃 폭을 차지하지 않아 스크롤이 생겨도 안쪽 콘텐츠 폭이 줄지 않는다. **막대는 스크롤하는 동안에만 보인다.** 멎으면 페이드로 사라진다 — 자리를 차지하지 않는 막대는 늘 떠 있을 이유도 없다. 끌고 있는 동안에는 사라지지 않는다. (`SPage` 의 같은 이름 prop 은 막대를 항상 보인다. 페이지는 스크롤이 늘 있는 자리라 "여기는 스크롤된다"는 신호를 남겨 둔 쪽이다.) **켜면 구조가 한 겹 늘어난다.** `className`·`style`·`maxHeight`·`maxWidth` 가 바깥 상자로 가고 스크롤 요소가 그 안을 채운다 — 막대를 얹을 기준 상자가 필요해서다. 크기·자리를 잡는 클래스(`h-*`·`flex-1`·`min-h-0`)는 그대로 듣지만, 패딩은 스크롤 요소가 아니라 바깥 상자에 걸리므로 콘텐츠가 그 여백 아래로 흘러 들어가지 않는다. `background`·`bordered` 는 네이티브 트랙을 꾸미는 값이라 이때 무시된다. |
|
|
16
17
|
|
|
17
18
|
## Types
|
|
18
19
|
|
|
@@ -11,6 +11,23 @@ export interface SScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
11
11
|
maxHeight?: string;
|
|
12
12
|
/** viewport 최대 너비 (예: '480px'). 비우면 부모 크기를 따른다. */
|
|
13
13
|
maxWidth?: string;
|
|
14
|
+
/**
|
|
15
|
+
* true면 네이티브 스크롤바를 숨기고 영역 위에 오버레이 스크롤바를 얹는다.
|
|
16
|
+
* 스크롤바가 레이아웃 폭을 차지하지 않아 스크롤이 생겨도 안쪽 콘텐츠 폭이 줄지 않는다.
|
|
17
|
+
*
|
|
18
|
+
* **막대는 스크롤하는 동안에만 보인다.** 멎으면 페이드로 사라진다 — 자리를 차지하지
|
|
19
|
+
* 않는 막대는 늘 떠 있을 이유도 없다. 끌고 있는 동안에는 사라지지 않는다.
|
|
20
|
+
* (`SPage` 의 같은 이름 prop 은 막대를 항상 보인다. 페이지는 스크롤이 늘 있는 자리라
|
|
21
|
+
* "여기는 스크롤된다"는 신호를 남겨 둔 쪽이다.)
|
|
22
|
+
*
|
|
23
|
+
* **켜면 구조가 한 겹 늘어난다.** `className`·`style`·`maxHeight`·`maxWidth` 가 바깥
|
|
24
|
+
* 상자로 가고 스크롤 요소가 그 안을 채운다 — 막대를 얹을 기준 상자가 필요해서다.
|
|
25
|
+
* 크기·자리를 잡는 클래스(`h-*`·`flex-1`·`min-h-0`)는 그대로 듣지만, 패딩은 스크롤
|
|
26
|
+
* 요소가 아니라 바깥 상자에 걸리므로 콘텐츠가 그 여백 아래로 흘러 들어가지 않는다.
|
|
27
|
+
*
|
|
28
|
+
* `background`·`bordered` 는 네이티브 트랙을 꾸미는 값이라 이때 무시된다.
|
|
29
|
+
*/
|
|
30
|
+
overlayScrollbar?: boolean;
|
|
14
31
|
}
|
|
15
32
|
/**
|
|
16
33
|
* SScrollArea — sd-scroll-area 포팅.
|
|
@@ -32,7 +32,7 @@ export declare const extractText: (html: string) => string;
|
|
|
32
32
|
/**
|
|
33
33
|
* 아직 한 번도 그려지지 않은 행의 **추정** 높이(px). 리프 옵션은 세로 패딩 4px + 행간 20px
|
|
34
34
|
* 이라 보통 이 값이지만, 그룹 헤더나 큰 글씨가 섞인 라벨은 다르다 — 화면에 나온 행은
|
|
35
|
-
* 실측으로 덮이므로(`row-metrics.ts`) 이 값은 처음 스크롤바 길이를 잡는 출발점일 뿐이다.
|
|
35
|
+
* 실측으로 덮이므로(`lib/row-metrics.ts`) 이 값은 처음 스크롤바 길이를 잡는 출발점일 뿐이다.
|
|
36
36
|
*/
|
|
37
37
|
export declare const OPTION_ROW_HEIGHT = 28;
|
|
38
38
|
/**
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|------|------|---------|-------------|
|
|
30
30
|
| `type` | `'account' \| 'select' \| 'switch' \| 'subSelect' \| 'status'` | — | 계정 행 |
|
|
31
31
|
| `label?` | `string` | — | 라벨 텍스트 |
|
|
32
|
-
| `option
|
|
32
|
+
| `option?` | `<union 5종>` | — | 누르면 뜨는 계정 리스트박스. **필수다** — 계정 행은 늘 이 패널의 트리거이고, 눌러도 아무것도 뜨지 않는 계정 행은 없다. 아이콘은 `user` 로 고정이라 여기서 받지 않는다. |
|
|
33
33
|
|
|
34
34
|
### SSystemActionButtonSelect
|
|
35
35
|
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|------|------|---------|-------------|
|
|
40
40
|
| `type` | `'select'` | — | 화살표만 붙는 행 |
|
|
41
41
|
| `label?` | `string` | — | 라벨 텍스트 |
|
|
42
|
-
| `option
|
|
42
|
+
| `option?` | `SSystemActionButtonIconOption` | — | 선행 아이콘. 아이콘 없이 라벨만 두는 행이면 통째로 생략한다 |
|
|
43
43
|
|
|
44
44
|
### SSystemActionButtonStatus
|
|
45
45
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
|------|------|---------|-------------|
|
|
70
70
|
| `type` | `'switch'` | — | 우측에 보조 조작 문구가 붙는 행 |
|
|
71
71
|
| `label?` | `string` | — | 라벨 텍스트 |
|
|
72
|
-
| `option
|
|
72
|
+
| `option?` | `SSystemActionButtonTextOption` | — | 선행 아이콘 + 우측 액션 텍스트. 둘 다 두지 않으면 통째로 생략한다 |
|
|
73
73
|
|
|
74
74
|
### SSystemActionButtonText
|
|
75
75
|
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
|------|------|---------|-------------|
|
|
80
80
|
| `type` | `'switch' \| 'subSelect'` | — | 우측에 보조 조작 문구가 붙는 행 |
|
|
81
81
|
| `label?` | `string` | — | 라벨 텍스트 |
|
|
82
|
-
| `option
|
|
82
|
+
| `option?` | `SSystemActionButtonTextOption \| SSystemActionButtonSubSelectOption` | — | 선행 아이콘 + 우측 액션 텍스트. 둘 다 두지 않으면 통째로 생략한다 |
|
|
83
83
|
|
|
84
84
|
## Types
|
|
85
85
|
|
|
@@ -117,8 +117,8 @@ export type SSystemActionButtonColor = (typeof SYSTEM_ACTION_BUTTON_COLORS)[numb
|
|
|
117
117
|
```ts
|
|
118
118
|
/** 선행 아이콘만 받는 타입(`select`)의 option */
|
|
119
119
|
export interface SSystemActionButtonIconOption {
|
|
120
|
-
/** 선행
|
|
121
|
-
icon
|
|
120
|
+
/** 선행 아이콘명. 주지 않으면 아이콘 프레임 없이 라벨부터 시작한다 */
|
|
121
|
+
icon?: SIconName;
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
@@ -152,6 +152,11 @@ export interface SSystemActionButtonStatusOption {
|
|
|
152
152
|
* 아무것도 뜨지 않으면 그것은 `select`(눌러 들어가는 행)이지 `subSelect` 가 아니다.
|
|
153
153
|
*/
|
|
154
154
|
export interface SSystemActionButtonSubSelectOption extends SSystemActionButtonIconOption {
|
|
155
|
+
/**
|
|
156
|
+
* 선행 아이콘. `select` · `switch` 와 달리 **필수다** — 우측이 지금 값에 쓰이는 행이라
|
|
157
|
+
* 라벨 왼쪽까지 비면 무엇에 대한 값인지 가리키는 것이 라벨 글자 하나뿐이 된다.
|
|
158
|
+
*/
|
|
159
|
+
icon: SIconName;
|
|
155
160
|
/**
|
|
156
161
|
* 지금 값. `options` 에서 같은 `value` 를 찾아 우측에 그 `label` 로 적는다
|
|
157
162
|
* (못 찾으면 `value` 가 그대로 남는다).
|
|
@@ -167,7 +172,7 @@ export interface SSystemActionButtonSubSelectOption extends SSystemActionButtonI
|
|
|
167
172
|
### SSystemActionButtonTextOption
|
|
168
173
|
|
|
169
174
|
```ts
|
|
170
|
-
/** 우측 액션 텍스트까지 받는 타입(`switch`
|
|
175
|
+
/** 우측 액션 텍스트까지 받는 타입(`switch`)의 option */
|
|
171
176
|
export interface SSystemActionButtonTextOption extends SSystemActionButtonIconOption {
|
|
172
177
|
/** 우측 액션 텍스트 */
|
|
173
178
|
text?: string;
|
|
@@ -5,10 +5,10 @@ import { type STagColor } from '../STag';
|
|
|
5
5
|
import { type SSystemActionButtonColor } from './systemActionButton.config';
|
|
6
6
|
/** 선행 아이콘만 받는 타입(`select`)의 option */
|
|
7
7
|
export interface SSystemActionButtonIconOption {
|
|
8
|
-
/** 선행
|
|
9
|
-
icon
|
|
8
|
+
/** 선행 아이콘명. 주지 않으면 아이콘 프레임 없이 라벨부터 시작한다 */
|
|
9
|
+
icon?: SIconName;
|
|
10
10
|
}
|
|
11
|
-
/** 우측 액션 텍스트까지 받는 타입(`switch`
|
|
11
|
+
/** 우측 액션 텍스트까지 받는 타입(`switch`)의 option */
|
|
12
12
|
export interface SSystemActionButtonTextOption extends SSystemActionButtonIconOption {
|
|
13
13
|
/** 우측 액션 텍스트 */
|
|
14
14
|
text?: string;
|
|
@@ -27,6 +27,11 @@ export interface SSystemActionButtonMenuOption {
|
|
|
27
27
|
* 아무것도 뜨지 않으면 그것은 `select`(눌러 들어가는 행)이지 `subSelect` 가 아니다.
|
|
28
28
|
*/
|
|
29
29
|
export interface SSystemActionButtonSubSelectOption extends SSystemActionButtonIconOption {
|
|
30
|
+
/**
|
|
31
|
+
* 선행 아이콘. `select` · `switch` 와 달리 **필수다** — 우측이 지금 값에 쓰이는 행이라
|
|
32
|
+
* 라벨 왼쪽까지 비면 무엇에 대한 값인지 가리키는 것이 라벨 글자 하나뿐이 된다.
|
|
33
|
+
*/
|
|
34
|
+
icon: SIconName;
|
|
30
35
|
/**
|
|
31
36
|
* 지금 값. `options` 에서 같은 `value` 를 찾아 우측에 그 `label` 로 적는다
|
|
32
37
|
* (못 찾으면 `value` 가 그대로 남는다).
|
|
@@ -100,8 +105,8 @@ export interface SSystemActionButtonSelectProps extends SSystemActionButtonBaseP
|
|
|
100
105
|
type: 'select';
|
|
101
106
|
/** 라벨 텍스트 */
|
|
102
107
|
label?: string;
|
|
103
|
-
/** 선행 아이콘 */
|
|
104
|
-
option
|
|
108
|
+
/** 선행 아이콘. 아이콘 없이 라벨만 두는 행이면 통째로 생략한다 */
|
|
109
|
+
option?: SSystemActionButtonIconOption;
|
|
105
110
|
}
|
|
106
111
|
/** 우측에 보조 조작 문구가 붙는 행 — 지금 붙어 있는 대상을 갈아 끼운다 */
|
|
107
112
|
export interface SSystemActionButtonSwitchProps extends SSystemActionButtonBaseProps {
|
|
@@ -109,8 +114,8 @@ export interface SSystemActionButtonSwitchProps extends SSystemActionButtonBaseP
|
|
|
109
114
|
type: 'switch';
|
|
110
115
|
/** 라벨 텍스트 */
|
|
111
116
|
label?: string;
|
|
112
|
-
/** 선행 아이콘 + 우측 액션
|
|
113
|
-
option
|
|
117
|
+
/** 선행 아이콘 + 우측 액션 텍스트. 둘 다 두지 않으면 통째로 생략한다 */
|
|
118
|
+
option?: SSystemActionButtonTextOption;
|
|
114
119
|
}
|
|
115
120
|
/** 우측에 지금 선택된 값이 붙는 행 — `options` 를 주면 행 오른쪽으로 메뉴가 펼쳐진다 */
|
|
116
121
|
export interface SSystemActionButtonSubSelectProps extends SSystemActionButtonBaseProps {
|
|
@@ -141,8 +146,9 @@ export type SSystemActionButtonProps = SSystemActionButtonAccountProps | SSystem
|
|
|
141
146
|
*
|
|
142
147
|
* 아이콘 프레임 + 라벨 + 우측 액션이 한 행에 붙고, 행 전체가 버튼이다. 무엇이 그려지는지는
|
|
143
148
|
* `type` 이 정하고(`account` `switch` `select` `subSelect` `status`), 타입마다 다른 값만
|
|
144
|
-
* `option` 으로 받는다. 선행 아이콘은
|
|
145
|
-
*
|
|
149
|
+
* `option` 으로 받는다. 선행 아이콘은 `account` 가 `user` 고정, `status` 는 태그가 자리를 차지해
|
|
150
|
+
* 없고, `subSelect` 는 `option.icon` 이 필수다. **`select` · `switch` 만 아이콘이 선택이다** —
|
|
151
|
+
* 주지 않으면 아이콘 프레임 없이 라벨부터 시작한다(`option` 자체를 생략해도 된다).
|
|
146
152
|
*
|
|
147
153
|
* `account` 는 **언제나 계정 리스트박스(`SAccountListBox`)의 트리거**다 — 눌러도 아무것도 뜨지
|
|
148
154
|
* 않는 계정 행은 없으므로 `option` 이 필수이고, 누르면 이메일·이름·권한과 계정 설정·언어 변경·
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| `rows?` | `SRow[]` | `[]` | |
|
|
14
14
|
| `rowKey?` | `string` | `'id'` | 행 식별 필드 |
|
|
15
15
|
| `selectable?` | `boolean` | `false` | 행 선택 체크박스 |
|
|
16
|
+
| `isRowSelectable?` | `(row: SRow) => boolean` | — | 이 행을 고를 수 있는가. 주지 않으면 모든 행을 고를 수 있다. 고를 수 없는 행은 체크박스가 잠기고, **전체 선택·Shift 구간 선택의 셈에서도 빠진다** — 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 가지 못한다. 목록에서 지워 버리는 것과 다르다. 실패 0건인 차수처럼 **자리는 보여야 하지만 대상이 될 수는 없는 행**에 쓴다. 아예 대상이 아니라면 `rows` 에서 거르는 편이 낫다. 잠금은 그리기 시점의 판정일 뿐이라, 이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다(제어 상태를 말없이 바꾸지 않는다). 다만 헤더의 전체 해제로는 걷어낼 수 있다. |
|
|
16
17
|
| `selected?` | `SRow[]` | `[]` | |
|
|
17
18
|
| `sort?` | `STableSort \| null` | `null` | 정렬 상태 (controlled). `null`·미지정이면 정렬 없음. 컴포넌트는 정렬 상태를 갖지 않는다 — 서버 정렬이면 이 값이 곧 조회 조건이고, 뒤로가기·새로고침·링크 공유로 복원돼야 하므로 진실은 URL·store 쪽에 있어야 한다. 행을 실제로 정렬하는 것도 소비 앱 몫이다 (`STable` 은 받은 순서대로 그린다). |
|
|
18
19
|
| `resizable?` | `boolean` | `false` | 컬럼 너비 조절 |
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
| `showRowsPerPageSelect?` | `boolean` | `false` | 페이지당 행 수 셀렉트 표시 |
|
|
32
33
|
| `rowsPerPageOption?` | `SSelectOption[]` | `DEFAULT_ROWS_PER_PAGE_OPTION` | |
|
|
33
34
|
| `virtualScroll?` | `boolean` | `false` | 가상 스크롤 |
|
|
34
|
-
| `rowHeight?` | `number` | — |
|
|
35
|
+
| `rowHeight?` | `number` | — | 행 높이의 **출발점**(px). 미지정이면 밀도에 따라 44 / 32. 행마다 높이가 달라도 된다 — 화면에 나온 행은 실제 높이를 재서 덮으므로, 이 값은 **아직 안 그려진 행의 추정치**이자 행의 최소 높이일 뿐이다. 실측이 쌓일수록 뒤쪽 추정도 그 평균으로 수렴한다. 그래서 높이가 제각각인 표에서 "가장 흔한 높이"를 넣어 두면 충분하다. 값이 실제와 멀수록 아직 안 그려진 구간의 스크롤바 길이만 덜 정확할 뿐, 그리는 위치는 어긋나지 않는다. |
|
|
35
36
|
| `virtualBuffer?` | `number` | `5` | |
|
|
36
37
|
| `virtualEndThreshold?` | `number` | `10` | |
|
|
37
38
|
| `tableId?` | `string` | — | 테이블 식별자 (없으면 자동 생성) |
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
| `onDenseChange` | `(dense: boolean) => void` | 밀도 변경 (하단 바의 밀도 토글을 눌렀을 때). 표시는 테이블이 알아서 바꾸므로 받지 않아도 되고, 사용자가 고른 밀도를 다음 방문까지 기억해 두려는(로컬 저장 등) 페이지만 받으면 된다. |
|
|
48
49
|
| `onPageChange` | `(page: number) => void` | |
|
|
49
50
|
| `onRowsPerPageChange` | `(perPage: number) => void` | |
|
|
51
|
+
| `onScroll` | `(event: ReactUIEvent<HTMLDivElement>) => void` | 본문이 스크롤될 때. **실제 스크롤에만 울린다** — 행 수·컬럼 폭·로딩이 바뀌어 내부에서 위치를 다시 잴 때는 오지 않는다. `onVirtualUpdate` 는 그리는 창이 바뀔 때만 오므로 그 사이 스크롤을 알 수 없다. 스크롤 위치를 읽어 두었다 되돌리는 것처럼 **위치 자체가 필요하면** 이쪽을 쓴다. |
|
|
50
52
|
| `onVirtualUpdate` | `(range: { from: number; to: number }) => void` | |
|
|
51
53
|
| `onVirtualReachEnd` | `(info: { from: number; to: number; rowCount: number; threshold: number; }) =>…` | |
|
|
52
54
|
| `onRowClick` | `(row: SRow) => void` | 행 클릭 |
|
|
@@ -59,6 +61,9 @@
|
|
|
59
61
|
| `getIsAllChecked` | `(rows: SRow[]) => boolean \| null` | 주어진 행들의 전체 선택 상태 (true/false/null=indeterminate) |
|
|
60
62
|
| `getPaginationInfo` | `<union 2종>` | 내부 페이지네이션 정보 (internalPagination 아니면 null) |
|
|
61
63
|
| `getStickyStyle` | `(colIdx: number) => CSSProperties` | 컬럼 sticky 스타일 (CSS 변수 + 고정폭) |
|
|
64
|
+
| `getScrollElement` | `() => HTMLDivElement \| null` | 본문의 세로 스크롤 컨테이너. 본문이 슬롯으로 대체됐거나 아직 안 그려졌으면 `null`. 스크롤 위치를 읽고 되돌리거나, 표 안에서 스크롤 여지를 재야 할 때 쓴다 — **DOM 을 뒤져 컨테이너를 찾지 않는다.** 내부 구조는 예고 없이 바뀐다. ⚠️ `virtualScroll` 에서 **위치를 픽셀로 저장했다 되돌리는 것은 미덥지 않다.** 다시 그릴 때 아직 안 그려진 행은 추정 높이로 잡히므로, 실측이 쌓이기 전의 같은 `scrollTop` 이 다른 행을 가리킨다. 그 경우엔 위치가 아니라 **보고 있던 행**을 기억해 두는 편이 맞다. |
|
|
65
|
+
| `getFirstVisibleRow` | `() => number` | 지금 본문 맨 위에 걸린 행의 인덱스 (`rows` 기준). 행이 없으면 `0`. **스크롤 위치를 저장할 때는 픽셀이 아니라 이 값을 저장한다** — `virtualScroll` 에서 픽셀은 좌표계가 실측으로 움직이는 값이라, 되돌릴 때 다른 행을 가리킨다. |
|
|
66
|
+
| `scrollToRow` | `(index: number) => void` | 그 행이 본문 맨 위에 오도록 스크롤한다 (`rows` 기준 인덱스, 범위를 벗어나면 잘린다). `virtualScroll` 에서는 **실측이 정착할 때까지 그 행을 계속 다시 겨냥한다** — 옮기는 시점의 좌표는 아직 추정치라 한 번만 옮기면 몇 행씩 밀린다. 사용자가 직접 스크롤하면 겨냥을 놓는다. 페이지네이션을 쓰는 표에서는 **지금 페이지에 그려진 행만** 닿는다 — 다른 페이지의 행이라면 페이지를 먼저 옮긴다. |
|
|
62
67
|
| `setRowCount` | `(count: number) => void` | 가상 스크롤 총 행 수 설정 |
|
|
63
68
|
| `updateRowSelect` | `(row: SRow) => void` | 단일 행 선택 토글 (sd-table updateRowSelect) — onSelectedChange 발생 |
|
|
64
69
|
| `toggleSelectAll` | `(checked: boolean, rows: SRow[]) => void` | 주어진 행들 전체 선택/해제 (sd-table toggleSelectAll) — onSelectedChange 발생 |
|