sellmate-design-system-react 9.0.0-beta.55 → 9.0.0-beta.56

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 CHANGED
@@ -605,6 +605,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
605
605
 
606
606
  **`SScrollArea` 로 페이지 본문 전체를 감싸지 않는다.** 페이지 스크롤은 `SPage` 의 `<main>` 몫이다 — 감싸면 스크롤바가 본문 패딩 안쪽으로 들어와 페이지 가장자리에서 떨어져 그려진다 (§2-2).
607
607
 
608
+ **`SScrollArea` 에는 상한을 정해 준다 — `maxHeight` 를 주거나, 부모가 높이를 정한 자리에 놓는다.** 둘 다 없으면 영역이 내용만큼 자라 스크롤이 생기지 않는다. 남은 높이를 쓰는 자리라면 `maxHeight` 가 아니라 `className="min-h-0 flex-1"` 로 사슬을 잇는다 — 픽셀로 박으면 창 높이가 바뀔 때 따라가지 못한다.
609
+
608
610
  #### E. 다른 곳으로 이동시킨다
609
611
 
610
612
  | 하려는 일 | 컴포넌트 | 갈림 |
@@ -621,7 +623,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
621
623
  | --- | --- | --- |
622
624
  | 실행 여부만 확정받는다 | `SModal.confirm()` | §3-3-1 |
623
625
  | 모달 안에서 작성·선택하게 한다 | `SActionModal` + `SModal.create()` | §3-3-1 |
624
- | 띄우는 것 자체가 하나의 화면이다 | `SPopup` | §3-3-1 |
626
+ | 띄우는 것 자체가 하나의 화면이다 | `SPopup` | §3-3-1 · 골격은 §4-7 |
625
627
  | 화면 옆에서 밀려 나오는 작업 패널을 연다 | `SDrawer` | §3-3-5 |
626
628
  | 확인 다이얼로그를 화면에 직접 배치한다 | `SConfirmModal` | §3-3-4 |
627
629
  | 클릭하면 상호작용 가능한 작은 콘텐츠를 띄운다 | `SPopover` | §3-3 |
@@ -773,30 +775,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
773
775
  - **본문 패딩은 `SPopup` 이 토큰으로 넣는다. 직접 주지 않는다** (`p-sd-*` 로 덮어쓰면 토큰이 바뀌어도 안 따라간다). 표를 가장자리까지 채우는 등 콘텐츠가 여백을 직접 다뤄야 할 때만 `padding="none"` 으로 끈다.
774
776
  - 그 밖에는 팝업 안도 일반 페이지와 같은 규칙(§4)을 따른다: 블록 간격 `gap-sd-12`, 표는 `SKeyValueTable` / `STable`.
775
777
 
776
- ```tsx
777
- // 1) 목록에서 별도 창을 연다 — 창 크기 = 콘텐츠 크기
778
- function openDetailPopup(orderId: string) {
779
- window.open(
780
- `${window.location.origin}/popup/transfer-orders/${orderId}`,
781
- `transfer-order-${orderId}`,
782
- 'width=1200, height=800, toolbar=no, menubar=no, location=no, resizable=no',
783
- );
784
- }
785
-
786
- // 2) 그 라우트의 루트에 SPopup 을 둔다 (조회만 → 푸터 없음)
787
- export default function TransferOrderPopupPage() {
788
- return (
789
- <SPopup popupTitle="이동 오더 상세">
790
- {/* 본문 패딩은 SPopup 이 토큰으로 넣는다 — p-sd-* 로 덮어쓰지 않는다 */}
791
- <div className="flex flex-col gap-sd-12">
792
- <SSectionHeaderCard>…</SSectionHeaderCard>
793
- <STabs value={tab} tabs={TABS} onValueChange={setTab} />
794
- <STable columns={columns} rows={rows} rowKey="id" />
795
- </div>
796
- </SPopup>
797
- );
798
- }
799
- ```
778
+ **창을 여는 코드와 그 라우트의 골격은 §4-7 에 있다.** 창 높이를 `SPopup` 까지 잇는 한 줄이 빠지면 헤더·푸터가 창 밖으로 밀려나므로, 팝업 라우트는 반드시 §4-7 골격에서 시작한다.
800
779
 
801
780
  #### 3-3-4. 모달 만드는 법
802
781
 
@@ -1475,6 +1454,7 @@ tableRef.current.scrollToRow(row); // 복원
1475
1454
  - 크기 단위는 `unit` 이 정한다. 기본 `'%'` 는 창이 바뀌어도 비율을 유지하고, `'px'` 는 폭을 유지한다. **사이드바처럼 폭이 고정돼야 하는 자리는 `'px'`**, 화면을 비율로 나누는 자리는 기본값 그대로 둔다.
1476
1455
  - 본문이 읽을 수 없을 만큼 좁아지지 않도록 `limits={[최소, 최대]}` 를 준다. 생략하면 `'%'` 는 `[10, 90]`, `'px'` 는 `[50, Infinity]`.
1477
1456
  - 각 패널은 넘치는 만큼 **스스로 스크롤한다.** 패널 안에 `SScrollArea` 를 겹쳐 넣지 않는다.
1457
+ - **높이는 놓는 자리가 준다.** `SSplitter` 는 부모를 채우기만 하므로, 부모 높이가 `auto` 면 패널이 내용 높이로 자라 스크롤이 생기지 않는다 (`vertical` 은 위아래 비율 자체가 무의미해진다). 페이지 본문의 남은 높이를 쓰려면 `contentHeight="fill"` 에 스택을 `min-h-0 flex-1` 로 이어 준다 — `SCalendarBoard`·`STable` 과 같은 사슬이다 (§2-2).
1478
1458
  - 모델은 항상 **첫 패널**(`SSplitter.Before`) 크기다. 사이드가 기준인 화면이면 사이드를 `Before` 에 둔다.
1479
1459
  - 앱 셸의 GNB 폭은 `SGnb` 가 소유한다. `SLayout`/`SGnb` 를 `SSplitter` 로 감싸지 않는다 — **GNB 폭을 끌 수 있게 하려면 `SGnb` 에 `resizable` 을 준다**(§4-1).
1480
1460
 
@@ -1929,6 +1909,17 @@ export default function AppShell({
1929
1909
  }
1930
1910
  ```
1931
1911
 
1912
+ **셸을 마운트하는 자리가 뷰포트 높이를 준다 — 앱 루트에 `h-screen` 을 둔다.** `SLayout` 은 부모 높이를 채우도록만 되어 있어서, 마운트 지점의 높이가 `auto` 면 100% 가 풀려 셸이 내용 높이로 줄어든다. 그러면 GNB 가 화면 바닥까지 내려오지 않고, `SPage` 안에서 일어나야 할 스크롤이 문서(브라우저 창) 스크롤이 되어 `contentHeight="fill"` 도 최소 너비의 가로 스크롤 규칙도 함께 무너진다. `html`·`body` 에 전역 CSS 를 걸 필요는 없다 — 루트 한 겹이면 된다.
1913
+
1914
+ ```tsx
1915
+ // 앱 진입점 (main.tsx) — 셸이 창 높이를 받는 자리는 여기 하나다
1916
+ createRoot(document.getElementById('root')!).render(
1917
+ <div className="h-screen">
1918
+ <AppShell header={{ variant: 'bar', title: '주문 목록' }}>…</AppShell>
1919
+ </div>,
1920
+ );
1921
+ ```
1922
+
1932
1923
  **페이지는 `AppShell` 을 직접 호출하며 자기 `header` 를 넘긴다** — 셸은 앱에 하나뿐이므로, 페이지 제목이 페이지마다 다르다는 사실은 이렇게 프레임 컴포넌트를 통해 흘려보낸다(§4-2·§4-3·§4-4 참고).
1933
1924
 
1934
1925
  **자식 순서는 `SGnb` → `SPageHeader` → `SPage` 다.** `SLayout` 은 `SPageHeader` 자식을 보면 **그 자식부터 뒤를** 하나의 페이지 열로 묶어 헤더를 페이지 위에 고정한다 — 스크롤도 페이지 패딩도 그 아래 `SPage` 안에서만 일어난다. 그래서 순서가 규칙이다: 헤더를 `SGnb` 앞에 두면 GNB 까지 페이지 열로 딸려 들어가고, `SPage` 의 `children` 안에 넣으면 본문 패딩 안으로 들어가 스크롤과 함께 밀려 올라간다.
@@ -2445,7 +2436,7 @@ export default function ProductDetailPage() {
2445
2436
 
2446
2437
  ### 4-6. 로그인 화면 — SLoginCard
2447
2438
 
2448
- 앱 셸이 아직 없는 유일한 화면이다. `SLayout`·`SGnb`·`SPage` 가 없고, 회색 바탕 위에 카드 하나만 선다.
2439
+ 로그인 전이라 앱 셸이 아직 없다. `SLayout`·`SGnb`·`SPage` 가 없고, 회색 바탕 위에 카드 하나만 선다. (앱 셸 없이 서는 화면은 이것과 팝업 라우트(§4-7) 둘뿐이다.)
2449
2440
 
2450
2441
  ```tsx
2451
2442
  <div className="flex h-screen items-stretch justify-center bg-(--sys-color-bg-neutralLight) p-sd-24">
@@ -2487,6 +2478,66 @@ export default function ProductDetailPage() {
2487
2478
 
2488
2479
  **로그인 카드 옆에 다른 블록을 두지 않는다.** 이 화면에서 할 일은 로그인 하나이고, 옆에 공지·배너가 붙는 순간 그 뜻이 깨진다. 안내가 필요하면 `description` 이나 `footerNote` 로 넣는다.
2489
2480
 
2481
+ ### 4-7. 팝업 창 라우트 — SPopup
2482
+
2483
+ 앱 셸 없이 서는 또 하나의 화면이다. 별도 브라우저 창으로 열리는 전용 라우트이므로 `SLayout`·`SGnb`·`SPage` 가 없고, 창을 통째로 `SPopup` 하나가 채운다. 무엇을 팝업으로 열지는 §3-3-1·§3-3-3 에서 고르고, 여기서는 그 라우트의 골격만 다룬다.
2484
+
2485
+ ```tsx
2486
+ // 1) 목록에서 별도 창을 연다 — 창 크기 = 콘텐츠 크기
2487
+ function openDetailPopup(orderId: string) {
2488
+ window.open(
2489
+ `${window.location.origin}/popup/transfer-orders/${orderId}`,
2490
+ `transfer-order-${orderId}`,
2491
+ 'width=1200, height=800, toolbar=no, menubar=no, location=no, resizable=no',
2492
+ );
2493
+ }
2494
+
2495
+ // 2) 그 라우트의 루트는 SPopup 하나다 (조회만 → 푸터 없음)
2496
+ export default function TransferOrderPopupPage() {
2497
+ return (
2498
+ // h-screen 으로 창 높이를 잡는다 — 이게 없으면 본문이 창 밖으로 자란다
2499
+ <SPopup className="h-screen" popupTitle="이동 오더 상세">
2500
+ {/* 본문 패딩은 SPopup 이 토큰으로 넣는다 — p-sd-* 로 덮어쓰지 않는다 */}
2501
+ <div className="flex flex-col gap-sd-12">
2502
+ <SSectionHeaderCard>…</SSectionHeaderCard>
2503
+ <STabs value={tab} tabs={TABS} onValueChange={setTab} />
2504
+ <STable columns={columns} rows={rows} rowKey="id" />
2505
+ </div>
2506
+ </SPopup>
2507
+ );
2508
+ }
2509
+ ```
2510
+
2511
+ **높이는 라우트가 준다 — `SPopup` 에 `h-screen` 을 준다.** `SPopup` 자체는 부모 높이를 채우도록만 되어 있어서, 부모(라우트 루트·`body`)가 높이를 정해 주지 않으면 100% 가 풀려 본문이 창 밖으로 자란다. 그러면 헤더·푸터가 위아래로 밀려 창 안에 보이지 않는다. `h-screen` 이면 `html`·`body`·마운트 루트에 전역 CSS 를 걸지 않고도 창 높이가 바로 들어온다. (`body` 여백은 0 이어야 한다 — Tailwind preflight 가 이미 0 으로 만든다.)
2512
+
2513
+ **창 높이를 고정으로 가정하지 않는다.** `window.open` 의 `resizable=no` 는 브라우저가 무시하는 경우가 많아 사용자가 창을 늘리고 줄일 수 있고, 화면 해상도에 따라 처음 열리는 높이도 요청값과 달라진다. `h-screen` 으로 이어 두면 본문의 가용 높이가 창과 함께 변하고, 넘칠 때만 **본문만** 스크롤한다 — 헤더와 푸터는 자리에 남는다. 높이를 `px` 로 박거나 리사이즈를 JS 로 따라가지 않는다.
2514
+
2515
+ **표를 담으면 본문이 아니라 표가 스크롤한다.** 본문이 통째로 스크롤되면 표 헤더와 페이지네이션 바가 위아래로 밀려 사라진다. `SPopup` 의 본문은 이미 남은 높이를 잡고 있으므로, 본문 루트에 `h-full min-h-0` 으로 세로 축을 잇고 표에 `min-h-0 flex-1` 을 준다. **툴바+표를 묶은 `div` 도 사슬의 한 칸이라 거기에도 `min-h-0 flex-1` 이 필요하다** (§4-2 목록 페이지와 같은 사슬이다 — 한 칸만 끊겨도 표가 높이를 못 잡는데 실패가 조용하다).
2516
+
2517
+ ```tsx
2518
+ <SPopup
2519
+ className="h-screen"
2520
+ popupTitle="엑셀 파일 관리"
2521
+ showFooter // 확정할 작업이 있을 때만 (§3-3-3)
2522
+ submitButton={{ label: '저장' }}
2523
+ onSubmit={save}
2524
+ >
2525
+ <div className="flex h-full min-h-0 flex-col gap-sd-12">
2526
+ <SKeyValueTable fields={filterFields} values={filters} search
2527
+ onChange={({ values }) => setFilters(values)} onSearch={fetchList} />
2528
+
2529
+ {/* 툴바+표 = 한 블록. 이 div 가 사슬의 한 칸이다 (§2-0) */}
2530
+ <div className="flex min-h-0 flex-1 flex-col">
2531
+ <STableBar className="border-b-0" total={total} />
2532
+ {/* 표만 자기 안에서 스크롤한다 — 필터·툴바·푸터는 늘 보인다 */}
2533
+ <STable className="min-h-0 flex-1" columns={columns} rows={rows} rowKey="id"
2534
+ pagination={{ currentPage, lastPage }} />
2535
+ </div>
2536
+ </div>
2537
+ </SPopup>
2538
+ ```
2539
+
2540
+ 조회만 하고 표가 없는 팝업은 이 사슬이 필요 없다 — 본문이 통째로 스크롤되어도 헤더는 고정이고 밀려날 푸터가 없다.
2490
2541
 
2491
2542
  ---
2492
2543
 
@@ -2506,6 +2557,8 @@ export default function ProductDetailPage() {
2506
2557
  - [ ] `SSectionHeaderCard` 의 여백을 `p-sd-*` 가 아니라 `padding` prop 으로 줬는가
2507
2558
  - [ ] 페이지에 `contentHeight="fill"` 을 넘겼는가 (§2-2 표준 — 페이지 스크롤을 쓰는 화면에서만 `auto` + `scrollEndSpacing`)
2508
2559
  - [ ] `fill` 을 쓴 블록에서 **자식까지 `min-h-0 flex-1` 이 끊기지 않았는가** (한 군데만 끊겨도 자식이 높이를 못 잡는데 실패가 조용하다)
2560
+ - [ ] 셸을 마운트하는 앱 루트가 뷰포트 높이인가 (§4-1 — `h-screen` 이 없으면 GNB 가 바닥까지 안 오고 페이지 스크롤이 문서 스크롤이 된다)
2561
+ - [ ] 팝업 라우트라면 `SPopup` 에 `h-screen` 을 줬는가 (§4-7 — 없으면 본문이 창 밖으로 자라 헤더·푸터가 밀려난다), 표를 담았다면 표만 스크롤하는가
2509
2562
  - [ ] 자체 스크롤하는 패널의 하단에 `pb-[var(--cmp-pageBody-padding-scrollEnd)]` 이 있는가
2510
2563
  - [ ] 한 페이지에 섹션 카드가 둘 이상이면 `marker` 를 켰는가, 하나뿐이면 껐는가 (§4-5)
2511
2564
  - [ ] 같은 컴포넌트를 나열할 때 §2-2 그룹 간격을 썼는가 (체크박스 가로 `gap-sd-24` 등)
@@ -6,7 +6,12 @@ export declare const ICONS: {
6
6
  readonly account: (p: IconRenderProps) => import("react").JSX.Element;
7
7
  readonly add: (p: IconRenderProps) => import("react").JSX.Element;
8
8
  readonly alert: (p: IconRenderProps) => import("react").JSX.Element;
9
+ readonly alignCenter: (p: IconRenderProps) => import("react").JSX.Element;
9
10
  readonly alignKorean: (p: IconRenderProps) => import("react").JSX.Element;
11
+ readonly alignLeft: (p: IconRenderProps) => import("react").JSX.Element;
12
+ readonly alignMiddle: (p: IconRenderProps) => import("react").JSX.Element;
13
+ readonly alignRight: (p: IconRenderProps) => import("react").JSX.Element;
14
+ readonly alignTop: (p: IconRenderProps) => import("react").JSX.Element;
10
15
  readonly archive: (p: IconRenderProps) => import("react").JSX.Element;
11
16
  readonly arrowDown: (p: IconRenderProps) => import("react").JSX.Element;
12
17
  readonly arrowLeft: (p: IconRenderProps) => import("react").JSX.Element;