sellmate-design-system-react 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +48 -8
- package/dist/components/SPopup/README.md +2 -1
- package/dist/components/SPopup/SPopup.d.ts +2 -0
- package/dist/index.cjs +11 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +50 -9
- package/dist/llms.txt +48 -8
- package/dist/styles.css +16 -3
- package/dist/theme.css +1 -0
- package/package.json +1 -1
package/dist/llms-full.txt
CHANGED
|
@@ -269,7 +269,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
269
269
|
|
|
270
270
|
| 스크롤 종류 | 어떻게 |
|
|
271
271
|
| --- | --- |
|
|
272
|
-
| **패널 자체 스크롤** (좌측 목록, 중앙 대화 등) | 그 패널 안쪽 하단에 `pb-
|
|
272
|
+
| **패널 자체 스크롤** (좌측 목록, 중앙 대화 등) | 그 패널 안쪽 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` — `SPage` 와 같은 토큰이라 값이 바뀌어도 함께 따라간다 |
|
|
273
273
|
| **페이지 단위 스크롤** | **`SPage` 가 넣는다. 직접 주지 않는다** |
|
|
274
274
|
|
|
275
275
|
`SPage` 는 기본으로 넣으므로 **아무것도 하지 않으면 맞다.** 끄는 경우는 하나뿐이다 — **페이지네이션이 붙은 테이블.** 페이지네이션이 이미 "여기서 끝"을 알려주므로 `scrollEndSpacing={false}` 로 끈다 (§4-2 목록 페이지).
|
|
@@ -413,8 +413,9 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
413
413
|
|
|
414
414
|
- 작은 창이 아니라 **화면 하나가 통째로** 들어간다 — 검색 필터·테이블·페이지네이션이 그대로 있는 목록(`엑셀 파일 관리`), 헤더 카드·탭이 있는 상세(`이동 오더 상세`).
|
|
415
415
|
- 상세를 팝업으로 여는 이유는 **목록을 떠나지 않고 여러 건을 번갈아 보기** 위해서다. 단 상세가 **항상** 팝업인 것은 아니고, 데이터 양이 많을 때 팝업을 쓴다.
|
|
416
|
-
- 구조는 헤더(제목 중앙) +
|
|
417
|
-
-
|
|
416
|
+
- 구조는 헤더(제목 중앙) + 본문이고 **푸터는 기본으로 없다**. 조회만 하는 팝업은 그대로 두고, **확정할 작업이 있을 때만 `useFooter` 로 푸터를 켜서 `submitButton` 에 `저장`** 을 둔다.
|
|
417
|
+
- **본문 패딩은 `SPopup` 이 토큰으로 넣는다. 직접 주지 않는다** (`p-sd-*` 로 덮어쓰면 토큰이 바뀌어도 안 따라간다). 표를 가장자리까지 채우는 등 콘텐츠가 여백을 직접 다뤄야 할 때만 `noPadding` 으로 끈다.
|
|
418
|
+
- 그 밖에는 팝업 안도 일반 페이지와 같은 규칙(§4)을 따른다: 블록 간격 `gap-sd-12`, 표는 `SKeyValueTable` / `STable`.
|
|
418
419
|
|
|
419
420
|
```tsx
|
|
420
421
|
// 1) 목록에서 별도 창을 연다 — 창 크기 = 콘텐츠 크기
|
|
@@ -429,8 +430,9 @@ function openDetailPopup(orderId: string) {
|
|
|
429
430
|
// 2) 그 라우트의 루트에 SPopup 을 둔다 (조회만 → 푸터 없음)
|
|
430
431
|
export default function TransferOrderPopupPage() {
|
|
431
432
|
return (
|
|
432
|
-
<SPopup popupTitle="이동 오더 상세"
|
|
433
|
-
|
|
433
|
+
<SPopup popupTitle="이동 오더 상세">
|
|
434
|
+
{/* 본문 패딩은 SPopup 이 토큰으로 넣는다 — p-sd-* 로 덮어쓰지 않는다 */}
|
|
435
|
+
<div className="flex flex-col gap-sd-12">
|
|
434
436
|
<SSectionHeaderCard>…</SSectionHeaderCard>
|
|
435
437
|
<STabs value={tab} tabs={TABS} onValueChange={setTab} />
|
|
436
438
|
<STable columns={columns} rows={rows} rowKey="id" />
|
|
@@ -491,7 +493,9 @@ SModal.create({ component: OrderModal, componentProps: { orderId } })
|
|
|
491
493
|
|
|
492
494
|
작성 중인 내용이 있을 때 닫기를 시도하면 이탈 안내를 띄우는 것은 **소비 앱 몫**이다. 디자인 시스템은 dirty 상태를 알지 못하므로 백드롭·ESC 를 일괄 차단할 뿐이다. 안내가 필요하면 앱이 자체 dirty 판정 후 `SModal.confirm` 으로 띄운다.
|
|
493
495
|
|
|
494
|
-
### 3-4. 테이블 컬럼
|
|
496
|
+
### 3-4. 테이블 컬럼 — 정렬과 너비
|
|
497
|
+
|
|
498
|
+
#### 정렬
|
|
495
499
|
|
|
496
500
|
**값의 크기를 비교하는 숫자 컬럼은 예외 없이 오른쪽 정렬한다** (`align: 'right'`).
|
|
497
501
|
자릿수가 세로로 맞아야 값의 크기를 눈으로 비교할 수 있기 때문이다.
|
|
@@ -535,6 +539,41 @@ const columns: STableColumn[] = [
|
|
|
535
539
|
|
|
536
540
|
- `SKeyValueTable` 의 값 셀도 같은 기준을 따른다.
|
|
537
541
|
|
|
542
|
+
#### 컨트롤이 들어가는 컬럼은 너비를 명시한다
|
|
543
|
+
|
|
544
|
+
컬럼 폭은 `width` 로 **고정**되고, `<td>` 는 그 폭을 넘는 내용을 잘라낸다(`overflow: hidden`). 텍스트라면 말줄임으로 끝나지만, 셀에 `STag` · `SButton` · `SGhostButton` · `SSelect` · `SInput` · `SNumberInput` 처럼 **자기 폭을 가진 요소**를 넣으면 요소 자체가 잘려 **누르거나 읽거나 입력할 수 없게 된다.** `width` 를 생략해도 내용에 맞춰 늘어나지 않고 `STable` 의 기본 폭이 될 뿐이므로, 컨트롤이 들어가는 컬럼은 폭을 직접 판단해서 준다.
|
|
545
|
+
|
|
546
|
+
- 기준은 **요소가 온전히 보이는 폭 + 셀 좌우 패딩**이다. 좌우 패딩은 `STable` 이 토큰으로 넣으므로(직접 주지 않는다) 그만큼을 뺀 나머지가 요소 몫이라는 점을 계산에 넣는다.
|
|
547
|
+
- 요소가 둘 이상이면 요소 폭의 합에 **`gap` 까지** 더한다 (행 내부 인라인 액션 간격은 `gap-sd-4` 고정, §3-5-5).
|
|
548
|
+
- 값에 따라 폭이 달라지는 요소(`STag` 라벨, 라벨 있는 버튼)는 **가장 긴 값** 기준으로 잡는다. `판매중` 에 맞춰두면 `판매중지 요청` 에서 잘린다.
|
|
549
|
+
- `SSelect` · `SInput` 처럼 셀 폭을 채우는 컨트롤은 **컬럼 폭이 곧 컨트롤 폭**이다. 실제 선택값·입력값이 말줄임 없이 읽히는 폭인지 확인한다.
|
|
550
|
+
- 폭을 넉넉히 줄 수 없는 자리는 폭을 줄이는 게 아니라 **요소를 바꾼다** — 라벨 버튼 대신 아이콘만 있는 `SGhostButton`, `size="xs"` (§3-5-2, §3-5-5).
|
|
551
|
+
- **`autoWidth` 는 해법이 아니다.** 내용에 맞춰 늘어나는 게 아니라 고정폭 컬럼들이 가져가고 **남은 폭을 나눠 갖는 것**이라, 테이블이 좁으면 역시 잘린다. 컨트롤 컬럼은 `width` 로 직접 확보한다.
|
|
552
|
+
|
|
553
|
+
**`resizable` 테이블이면 `minWidth` 를 함께 준다.** resize 하한 기본값은 어떤 컨트롤도 담지 못할 만큼 작아, 사용자가 끝까지 끌면 그대로 잘린다. `width` 를 정한 근거와 같은 값을 하한으로 둔다 — 텍스트 컬럼과 달리 여기서는 더 줄일 여지가 없다.
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
const columns: STableColumn[] = [
|
|
557
|
+
// 태그 — 가장 긴 라벨 기준
|
|
558
|
+
{ name: 'status', label: '상태', field: 'status', width: '120px', minWidth: 120, align: 'center',
|
|
559
|
+
render: (row: SRow) => <STag size="sm" color="green" label={row.statusLabel} /> },
|
|
560
|
+
// 셀 안 입력 — 컬럼 폭이 곧 입력 폭
|
|
561
|
+
{ name: 'qty', label: '수량', field: 'qty', width: '100px', minWidth: 100, align: 'right',
|
|
562
|
+
render: (row: SRow) => <SNumberInput value={row.qty} onValueChange={v => setQty(row, v)} /> },
|
|
563
|
+
// 인라인 액션 둘 — 폭 = xs 버튼 2개 + gap-sd-4 + 셀 좌우 패딩
|
|
564
|
+
{ name: 'actions', label: '', field: 'id', width: '84px', minWidth: 84, align: 'center',
|
|
565
|
+
render: (row: SRow) => (
|
|
566
|
+
<div className="flex items-center justify-center gap-sd-4">
|
|
567
|
+
<SGhostButton size="xs" intent="action" icon="edit" ariaLabel="수정" onClick={() => editRow(row)} />
|
|
568
|
+
<SGhostButton size="xs" icon="delete" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
|
|
569
|
+
</div>
|
|
570
|
+
) },
|
|
571
|
+
|
|
572
|
+
// ❌ 컨트롤 컬럼에 width 생략 — 기본 폭에 맡기면 버튼이 잘린다
|
|
573
|
+
{ name: 'move', label: '', field: 'id', render: () => <SButton label="재고 이동" size="xs" /> },
|
|
574
|
+
];
|
|
575
|
+
```
|
|
576
|
+
|
|
538
577
|
#### 값이 없는 셀은 회색 하이픈
|
|
539
578
|
|
|
540
579
|
셀을 **빈칸으로 두지 않는다.** 값이 `null` · `undefined` · 빈 문자열이면 `-` 를 `text-fg-tertiary`(`grey_65`)로 표시한다.
|
|
@@ -1015,10 +1054,10 @@ export default function ProductDetailPage() {
|
|
|
1015
1054
|
- [ ] 간격이 전부 `sd-` 접두 스케일 값인가 (`gap-13`·`gap-sd-13` ❌ → `gap-sd-12` ✅)
|
|
1016
1055
|
- [ ] 본문이 12px(`typo-body-sm-default`)인가 (14px 본문 ❌)
|
|
1017
1056
|
- [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
|
|
1018
|
-
- [ ] `SPage` 의 기본 패딩을 `p-sd-*` 로 덮어쓰지 않았는가, 블록·섹션 **간격**이 `gap-sd-12` 인가 (`gap-sd-16`/`gap-sd-24` ❌ — 24 는 안쪽 여백에만 열린다)
|
|
1057
|
+
- [ ] `SPage`·`SPopup` 의 기본 패딩을 `p-sd-*` 로 덮어쓰지 않았는가, 블록·섹션 **간격**이 `gap-sd-12` 인가 (`gap-sd-16`/`gap-sd-24` ❌ — 24 는 안쪽 여백에만 열린다)
|
|
1019
1058
|
- [ ] 섹션·패널의 안쪽 **여백**이 §2-2 판정과 맞는가 (덩어리 두 종류 → 16 / 세 종류 이상 → 24, 서면 16)
|
|
1020
1059
|
- [ ] `SSectionHeaderCard.Body` 의 여백을 `p-sd-*` 가 아니라 `padding` prop 으로 줬는가
|
|
1021
|
-
- [ ] 자체 스크롤하는 패널의 하단에 `pb-
|
|
1060
|
+
- [ ] 자체 스크롤하는 패널의 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` 이 있는가, 페이지네이션 있는 목록에서 `scrollEndSpacing={false}` 를 넘겼는가
|
|
1022
1061
|
- [ ] 같은 컴포넌트를 나열할 때 §2-2 그룹 간격을 썼는가 (체크박스 가로 `gap-sd-24` 등)
|
|
1023
1062
|
- [ ] 페이지가 §4의 표준 골격에서 시작했는가
|
|
1024
1063
|
- [ ] 앱 셸이나 그 바깥에 `min-width`·`overflow-x` 를 직접 걸지 않았는가 (최소 너비는 `SLayout` 이 보장한다, §4-1)
|
|
@@ -1028,6 +1067,7 @@ export default function ProductDetailPage() {
|
|
|
1028
1067
|
- [ ] 상태 표시에 `STag size="sm"` 을 썼는가
|
|
1029
1068
|
- [ ] 테이블에서 양을 나타내는 컬럼(금액·수량·개수 등)이 전부 `align: 'right'` 인가
|
|
1030
1069
|
- [ ] 번호·코드·전화번호·일자 컬럼에 `align: 'center'` 를 **명시**했는가 (생략하면 좌측이 된다)
|
|
1070
|
+
- [ ] 컨트롤(`STag`·`SButton`·`SSelect`·`SInput` …)이 들어가는 컬럼에 `width` 를 명시했는가, `resizable` 이면 `minWidth` 도 줬는가 (§3-4 — 폭이 모자라면 요소가 잘려 못 쓴다)
|
|
1031
1071
|
- [ ] 금액·수량 등 양을 나타내는 숫자에 빠짐없이 `toLocaleString()` 을 썼는가 (번호·코드는 제외)
|
|
1032
1072
|
- [ ] 하단 버튼이 양끝 분리(`justify-between`)이고, 되돌리기가 왼쪽 끝 · 실행이 오른쪽 끝인가
|
|
1033
1073
|
- [ ] 페이지에 `color="primary"` 채움 버튼이 **1개뿐**인가 (`danger` 채움도 1개, `SDropdownButton` 포함)
|
|
@@ -3265,9 +3305,10 @@ function OrderModal({ open, onOpenChange, onClose, modalRef, orderId }: OrderMod
|
|
|
3265
3305
|
|------|------|---------|-------------|
|
|
3266
3306
|
| `popupTitle?` | `string` | `''` | 헤더 제목 |
|
|
3267
3307
|
| `type?` | `SPopupType` | `'default'` | 타입 (헤더 색상) |
|
|
3268
|
-
| `useFooter?` | `boolean` | `
|
|
3308
|
+
| `useFooter?` | `boolean` | `false` | 하단 footer 표시 여부 |
|
|
3269
3309
|
| `submitButton?` | `SPopupSubmitButton` | — | 확인 버튼 설정 |
|
|
3270
3310
|
| `footerLeft?` | `ReactNode` | — | footer 좌측 영역 |
|
|
3311
|
+
| `noPadding?` | `boolean` | `false` | 본문 기본 패딩을 없앱니다. 표를 가장자리까지 채우는 등 본문이 직접 여백을 다룰 때만 사용합니다. |
|
|
3271
3312
|
| `children?` | `ReactNode` | — | 본문 |
|
|
3272
3313
|
| `className?` | `string` | — | |
|
|
3273
3314
|
| `style?` | `CSSProperties` | — | |
|
package/dist/llms.txt
CHANGED
|
@@ -270,7 +270,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
270
270
|
|
|
271
271
|
| 스크롤 종류 | 어떻게 |
|
|
272
272
|
| --- | --- |
|
|
273
|
-
| **패널 자체 스크롤** (좌측 목록, 중앙 대화 등) | 그 패널 안쪽 하단에 `pb-
|
|
273
|
+
| **패널 자체 스크롤** (좌측 목록, 중앙 대화 등) | 그 패널 안쪽 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` — `SPage` 와 같은 토큰이라 값이 바뀌어도 함께 따라간다 |
|
|
274
274
|
| **페이지 단위 스크롤** | **`SPage` 가 넣는다. 직접 주지 않는다** |
|
|
275
275
|
|
|
276
276
|
`SPage` 는 기본으로 넣으므로 **아무것도 하지 않으면 맞다.** 끄는 경우는 하나뿐이다 — **페이지네이션이 붙은 테이블.** 페이지네이션이 이미 "여기서 끝"을 알려주므로 `scrollEndSpacing={false}` 로 끈다 (§4-2 목록 페이지).
|
|
@@ -414,8 +414,9 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
414
414
|
|
|
415
415
|
- 작은 창이 아니라 **화면 하나가 통째로** 들어간다 — 검색 필터·테이블·페이지네이션이 그대로 있는 목록(`엑셀 파일 관리`), 헤더 카드·탭이 있는 상세(`이동 오더 상세`).
|
|
416
416
|
- 상세를 팝업으로 여는 이유는 **목록을 떠나지 않고 여러 건을 번갈아 보기** 위해서다. 단 상세가 **항상** 팝업인 것은 아니고, 데이터 양이 많을 때 팝업을 쓴다.
|
|
417
|
-
- 구조는 헤더(제목 중앙) +
|
|
418
|
-
-
|
|
417
|
+
- 구조는 헤더(제목 중앙) + 본문이고 **푸터는 기본으로 없다**. 조회만 하는 팝업은 그대로 두고, **확정할 작업이 있을 때만 `useFooter` 로 푸터를 켜서 `submitButton` 에 `저장`** 을 둔다.
|
|
418
|
+
- **본문 패딩은 `SPopup` 이 토큰으로 넣는다. 직접 주지 않는다** (`p-sd-*` 로 덮어쓰면 토큰이 바뀌어도 안 따라간다). 표를 가장자리까지 채우는 등 콘텐츠가 여백을 직접 다뤄야 할 때만 `noPadding` 으로 끈다.
|
|
419
|
+
- 그 밖에는 팝업 안도 일반 페이지와 같은 규칙(§4)을 따른다: 블록 간격 `gap-sd-12`, 표는 `SKeyValueTable` / `STable`.
|
|
419
420
|
|
|
420
421
|
```tsx
|
|
421
422
|
// 1) 목록에서 별도 창을 연다 — 창 크기 = 콘텐츠 크기
|
|
@@ -430,8 +431,9 @@ function openDetailPopup(orderId: string) {
|
|
|
430
431
|
// 2) 그 라우트의 루트에 SPopup 을 둔다 (조회만 → 푸터 없음)
|
|
431
432
|
export default function TransferOrderPopupPage() {
|
|
432
433
|
return (
|
|
433
|
-
<SPopup popupTitle="이동 오더 상세"
|
|
434
|
-
|
|
434
|
+
<SPopup popupTitle="이동 오더 상세">
|
|
435
|
+
{/* 본문 패딩은 SPopup 이 토큰으로 넣는다 — p-sd-* 로 덮어쓰지 않는다 */}
|
|
436
|
+
<div className="flex flex-col gap-sd-12">
|
|
435
437
|
<SSectionHeaderCard>…</SSectionHeaderCard>
|
|
436
438
|
<STabs value={tab} tabs={TABS} onValueChange={setTab} />
|
|
437
439
|
<STable columns={columns} rows={rows} rowKey="id" />
|
|
@@ -492,7 +494,9 @@ SModal.create({ component: OrderModal, componentProps: { orderId } })
|
|
|
492
494
|
|
|
493
495
|
작성 중인 내용이 있을 때 닫기를 시도하면 이탈 안내를 띄우는 것은 **소비 앱 몫**이다. 디자인 시스템은 dirty 상태를 알지 못하므로 백드롭·ESC 를 일괄 차단할 뿐이다. 안내가 필요하면 앱이 자체 dirty 판정 후 `SModal.confirm` 으로 띄운다.
|
|
494
496
|
|
|
495
|
-
### 3-4. 테이블 컬럼
|
|
497
|
+
### 3-4. 테이블 컬럼 — 정렬과 너비
|
|
498
|
+
|
|
499
|
+
#### 정렬
|
|
496
500
|
|
|
497
501
|
**값의 크기를 비교하는 숫자 컬럼은 예외 없이 오른쪽 정렬한다** (`align: 'right'`).
|
|
498
502
|
자릿수가 세로로 맞아야 값의 크기를 눈으로 비교할 수 있기 때문이다.
|
|
@@ -536,6 +540,41 @@ const columns: STableColumn[] = [
|
|
|
536
540
|
|
|
537
541
|
- `SKeyValueTable` 의 값 셀도 같은 기준을 따른다.
|
|
538
542
|
|
|
543
|
+
#### 컨트롤이 들어가는 컬럼은 너비를 명시한다
|
|
544
|
+
|
|
545
|
+
컬럼 폭은 `width` 로 **고정**되고, `<td>` 는 그 폭을 넘는 내용을 잘라낸다(`overflow: hidden`). 텍스트라면 말줄임으로 끝나지만, 셀에 `STag` · `SButton` · `SGhostButton` · `SSelect` · `SInput` · `SNumberInput` 처럼 **자기 폭을 가진 요소**를 넣으면 요소 자체가 잘려 **누르거나 읽거나 입력할 수 없게 된다.** `width` 를 생략해도 내용에 맞춰 늘어나지 않고 `STable` 의 기본 폭이 될 뿐이므로, 컨트롤이 들어가는 컬럼은 폭을 직접 판단해서 준다.
|
|
546
|
+
|
|
547
|
+
- 기준은 **요소가 온전히 보이는 폭 + 셀 좌우 패딩**이다. 좌우 패딩은 `STable` 이 토큰으로 넣으므로(직접 주지 않는다) 그만큼을 뺀 나머지가 요소 몫이라는 점을 계산에 넣는다.
|
|
548
|
+
- 요소가 둘 이상이면 요소 폭의 합에 **`gap` 까지** 더한다 (행 내부 인라인 액션 간격은 `gap-sd-4` 고정, §3-5-5).
|
|
549
|
+
- 값에 따라 폭이 달라지는 요소(`STag` 라벨, 라벨 있는 버튼)는 **가장 긴 값** 기준으로 잡는다. `판매중` 에 맞춰두면 `판매중지 요청` 에서 잘린다.
|
|
550
|
+
- `SSelect` · `SInput` 처럼 셀 폭을 채우는 컨트롤은 **컬럼 폭이 곧 컨트롤 폭**이다. 실제 선택값·입력값이 말줄임 없이 읽히는 폭인지 확인한다.
|
|
551
|
+
- 폭을 넉넉히 줄 수 없는 자리는 폭을 줄이는 게 아니라 **요소를 바꾼다** — 라벨 버튼 대신 아이콘만 있는 `SGhostButton`, `size="xs"` (§3-5-2, §3-5-5).
|
|
552
|
+
- **`autoWidth` 는 해법이 아니다.** 내용에 맞춰 늘어나는 게 아니라 고정폭 컬럼들이 가져가고 **남은 폭을 나눠 갖는 것**이라, 테이블이 좁으면 역시 잘린다. 컨트롤 컬럼은 `width` 로 직접 확보한다.
|
|
553
|
+
|
|
554
|
+
**`resizable` 테이블이면 `minWidth` 를 함께 준다.** resize 하한 기본값은 어떤 컨트롤도 담지 못할 만큼 작아, 사용자가 끝까지 끌면 그대로 잘린다. `width` 를 정한 근거와 같은 값을 하한으로 둔다 — 텍스트 컬럼과 달리 여기서는 더 줄일 여지가 없다.
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
const columns: STableColumn[] = [
|
|
558
|
+
// 태그 — 가장 긴 라벨 기준
|
|
559
|
+
{ name: 'status', label: '상태', field: 'status', width: '120px', minWidth: 120, align: 'center',
|
|
560
|
+
render: (row: SRow) => <STag size="sm" color="green" label={row.statusLabel} /> },
|
|
561
|
+
// 셀 안 입력 — 컬럼 폭이 곧 입력 폭
|
|
562
|
+
{ name: 'qty', label: '수량', field: 'qty', width: '100px', minWidth: 100, align: 'right',
|
|
563
|
+
render: (row: SRow) => <SNumberInput value={row.qty} onValueChange={v => setQty(row, v)} /> },
|
|
564
|
+
// 인라인 액션 둘 — 폭 = xs 버튼 2개 + gap-sd-4 + 셀 좌우 패딩
|
|
565
|
+
{ name: 'actions', label: '', field: 'id', width: '84px', minWidth: 84, align: 'center',
|
|
566
|
+
render: (row: SRow) => (
|
|
567
|
+
<div className="flex items-center justify-center gap-sd-4">
|
|
568
|
+
<SGhostButton size="xs" intent="action" icon="edit" ariaLabel="수정" onClick={() => editRow(row)} />
|
|
569
|
+
<SGhostButton size="xs" icon="delete" ariaLabel="삭제" onClick={() => confirmRemove(row)} />
|
|
570
|
+
</div>
|
|
571
|
+
) },
|
|
572
|
+
|
|
573
|
+
// ❌ 컨트롤 컬럼에 width 생략 — 기본 폭에 맡기면 버튼이 잘린다
|
|
574
|
+
{ name: 'move', label: '', field: 'id', render: () => <SButton label="재고 이동" size="xs" /> },
|
|
575
|
+
];
|
|
576
|
+
```
|
|
577
|
+
|
|
539
578
|
#### 값이 없는 셀은 회색 하이픈
|
|
540
579
|
|
|
541
580
|
셀을 **빈칸으로 두지 않는다.** 값이 `null` · `undefined` · 빈 문자열이면 `-` 를 `text-fg-tertiary`(`grey_65`)로 표시한다.
|
|
@@ -1016,10 +1055,10 @@ export default function ProductDetailPage() {
|
|
|
1016
1055
|
- [ ] 간격이 전부 `sd-` 접두 스케일 값인가 (`gap-13`·`gap-sd-13` ❌ → `gap-sd-12` ✅)
|
|
1017
1056
|
- [ ] 본문이 12px(`typo-body-sm-default`)인가 (14px 본문 ❌)
|
|
1018
1057
|
- [ ] 텍스트 회색 위계를 순차 적용했는가 (기본 → `text-fg-secondary` → `text-fg-tertiary`, 단계 건너뛰기 ❌)
|
|
1019
|
-
- [ ] `SPage` 의 기본 패딩을 `p-sd-*` 로 덮어쓰지 않았는가, 블록·섹션 **간격**이 `gap-sd-12` 인가 (`gap-sd-16`/`gap-sd-24` ❌ — 24 는 안쪽 여백에만 열린다)
|
|
1058
|
+
- [ ] `SPage`·`SPopup` 의 기본 패딩을 `p-sd-*` 로 덮어쓰지 않았는가, 블록·섹션 **간격**이 `gap-sd-12` 인가 (`gap-sd-16`/`gap-sd-24` ❌ — 24 는 안쪽 여백에만 열린다)
|
|
1020
1059
|
- [ ] 섹션·패널의 안쪽 **여백**이 §2-2 판정과 맞는가 (덩어리 두 종류 → 16 / 세 종류 이상 → 24, 서면 16)
|
|
1021
1060
|
- [ ] `SSectionHeaderCard.Body` 의 여백을 `p-sd-*` 가 아니라 `padding` prop 으로 줬는가
|
|
1022
|
-
- [ ] 자체 스크롤하는 패널의 하단에 `pb-
|
|
1061
|
+
- [ ] 자체 스크롤하는 패널의 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` 이 있는가, 페이지네이션 있는 목록에서 `scrollEndSpacing={false}` 를 넘겼는가
|
|
1023
1062
|
- [ ] 같은 컴포넌트를 나열할 때 §2-2 그룹 간격을 썼는가 (체크박스 가로 `gap-sd-24` 등)
|
|
1024
1063
|
- [ ] 페이지가 §4의 표준 골격에서 시작했는가
|
|
1025
1064
|
- [ ] 앱 셸이나 그 바깥에 `min-width`·`overflow-x` 를 직접 걸지 않았는가 (최소 너비는 `SLayout` 이 보장한다, §4-1)
|
|
@@ -1029,6 +1068,7 @@ export default function ProductDetailPage() {
|
|
|
1029
1068
|
- [ ] 상태 표시에 `STag size="sm"` 을 썼는가
|
|
1030
1069
|
- [ ] 테이블에서 양을 나타내는 컬럼(금액·수량·개수 등)이 전부 `align: 'right'` 인가
|
|
1031
1070
|
- [ ] 번호·코드·전화번호·일자 컬럼에 `align: 'center'` 를 **명시**했는가 (생략하면 좌측이 된다)
|
|
1071
|
+
- [ ] 컨트롤(`STag`·`SButton`·`SSelect`·`SInput` …)이 들어가는 컬럼에 `width` 를 명시했는가, `resizable` 이면 `minWidth` 도 줬는가 (§3-4 — 폭이 모자라면 요소가 잘려 못 쓴다)
|
|
1032
1072
|
- [ ] 금액·수량 등 양을 나타내는 숫자에 빠짐없이 `toLocaleString()` 을 썼는가 (번호·코드는 제외)
|
|
1033
1073
|
- [ ] 하단 버튼이 양끝 분리(`justify-between`)이고, 되돌리기가 왼쪽 끝 · 실행이 오른쪽 끝인가
|
|
1034
1074
|
- [ ] 페이지에 `color="primary"` 채움 버튼이 **1개뿐**인가 (`danger` 채움도 1개, `SDropdownButton` 포함)
|
package/dist/styles.css
CHANGED
|
@@ -703,6 +703,9 @@
|
|
|
703
703
|
.z-\[9000\] {
|
|
704
704
|
z-index: 9000;
|
|
705
705
|
}
|
|
706
|
+
.z-auto {
|
|
707
|
+
z-index: auto;
|
|
708
|
+
}
|
|
706
709
|
.col-start-1 {
|
|
707
710
|
grid-column-start: 1;
|
|
708
711
|
}
|
|
@@ -906,6 +909,9 @@
|
|
|
906
909
|
.h-\[250px\] {
|
|
907
910
|
height: 250px;
|
|
908
911
|
}
|
|
912
|
+
.h-\[280px\] {
|
|
913
|
+
height: 280px;
|
|
914
|
+
}
|
|
909
915
|
.h-\[320px\] {
|
|
910
916
|
height: 320px;
|
|
911
917
|
}
|
|
@@ -1098,6 +1104,9 @@
|
|
|
1098
1104
|
.w-\[328px\] {
|
|
1099
1105
|
width: 328px;
|
|
1100
1106
|
}
|
|
1107
|
+
.w-\[360px\] {
|
|
1108
|
+
width: 360px;
|
|
1109
|
+
}
|
|
1101
1110
|
.w-\[528px\] {
|
|
1102
1111
|
width: 528px;
|
|
1103
1112
|
}
|
|
@@ -1903,6 +1912,9 @@
|
|
|
1903
1912
|
.border-\[\#E1E1E1\] {
|
|
1904
1913
|
border-color: #E1E1E1;
|
|
1905
1914
|
}
|
|
1915
|
+
.border-\[\#e5e7eb\] {
|
|
1916
|
+
border-color: #e5e7eb;
|
|
1917
|
+
}
|
|
1906
1918
|
.border-\[color\:var\(--btn-border\)\] {
|
|
1907
1919
|
border-color: var(--btn-border);
|
|
1908
1920
|
}
|
|
@@ -2524,12 +2536,12 @@
|
|
|
2524
2536
|
.pb-\[var\(--cmp-modal-action-body-paddingBottom\)\] {
|
|
2525
2537
|
padding-bottom: var(--cmp-modal-action-body-paddingBottom);
|
|
2526
2538
|
}
|
|
2539
|
+
.pb-\[var\(--cmp-pageBody-padding-scrollEnd\)\] {
|
|
2540
|
+
padding-bottom: var(--cmp-pageBody-padding-scrollEnd);
|
|
2541
|
+
}
|
|
2527
2542
|
.pb-sd-16 {
|
|
2528
2543
|
padding-bottom: var(--spacing-sd-16);
|
|
2529
2544
|
}
|
|
2530
|
-
.pb-sd-80 {
|
|
2531
|
-
padding-bottom: var(--spacing-sd-80);
|
|
2532
|
-
}
|
|
2533
2545
|
.pl-\(--cmp-guide-contents-depth-paddingLeft\) {
|
|
2534
2546
|
padding-left: var(--cmp-guide-contents-depth-paddingLeft);
|
|
2535
2547
|
}
|
|
@@ -5136,6 +5148,7 @@
|
|
|
5136
5148
|
--cmp-listDraggableItem-accentStripe-color: #64ABFF;
|
|
5137
5149
|
--cmp-listDraggableItem-dragging-content-opacity: 60%;
|
|
5138
5150
|
--cmp-pageBody-padding-default: 16px;
|
|
5151
|
+
--cmp-pageBody-padding-scrollEnd: 80px;
|
|
5139
5152
|
--cmp-sectionHeaderCard-radius: 8px;
|
|
5140
5153
|
--cmp-sectionHeaderCard-border: #E1E1E1;
|
|
5141
5154
|
--cmp-sectionHeaderCard-topBorder-thickness: 2px;
|
package/dist/theme.css
CHANGED
|
@@ -1350,6 +1350,7 @@
|
|
|
1350
1350
|
--cmp-listDraggableItem-accentStripe-color: #64ABFF;
|
|
1351
1351
|
--cmp-listDraggableItem-dragging-content-opacity: 60%;
|
|
1352
1352
|
--cmp-pageBody-padding-default: 16px;
|
|
1353
|
+
--cmp-pageBody-padding-scrollEnd: 80px;
|
|
1353
1354
|
--cmp-sectionHeaderCard-radius: 8px;
|
|
1354
1355
|
--cmp-sectionHeaderCard-border: #E1E1E1;
|
|
1355
1356
|
--cmp-sectionHeaderCard-topBorder-thickness: 2px;
|