sellmate-design-system-react 9.0.0-beta.86 → 9.0.0-beta.88
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 +24 -3
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/STable/README.md +6 -0
- package/dist/components/STable/STable.d.ts +13 -0
- package/dist/components/STree/README.md +0 -1
- package/dist/components/STree/STree.d.ts +0 -2
- package/dist/components/STreeTable/README.md +49 -0
- package/dist/components/STreeTable/STreeTable.d.ts +67 -0
- package/dist/components/STreeTable/STreeTable.examples.d.ts +32 -0
- package/dist/components/STreeTable/index.d.ts +1 -0
- package/dist/index.cjs +193 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +193 -12
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +75 -4
- package/dist/llms.txt +26 -5
- package/dist/styles.css +3 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
| **버튼·링크** | `SButton` `SGhostButton` `SMeatballButton`(⋯ 로 '더 보기' 메뉴를 여는 아이콘 버튼) `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
|
|
43
43
|
| **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
44
44
|
| **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
45
|
-
| **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` `STimeline`(날짜로 묶인 시간순 기록 — 작업 로그·메모 이력) |
|
|
45
|
+
| **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `STreeTable`(행이 부모-자식 계층을 이루는 표 — 펼치고 접는다) `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` `STimeline`(날짜로 묶인 시간순 기록 — 작업 로그·메모 이력) |
|
|
46
46
|
| **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SLauncherListBox`(런처 버튼을 눌러 뜨는 서비스 목록) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
47
47
|
| **내비게이션** | `STabs` `SPagination` `SStepper` |
|
|
48
48
|
| **차트** | `SBarChart`(막대 그래프 — 항목끼리 크기를 견준다. `stacked` 로 항목 안의 구성까지) `SLineChart`(꺾은선 그래프 — 순서가 있는 항목의 추이를 본다. `area` 로 크기까지, `stacked` 로 구성까지) `SPieChart`(원그래프 — 한 시점의 전체를 몇 개의 몫으로 나눈다) `SDonutChart`(도넛 그래프 — 가운데가 빈 원그래프. 구멍에 글자를 두지 않는다) |
|
|
@@ -167,7 +167,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
167
167
|
| 층 | 무엇인가 | 컴포넌트 |
|
|
168
168
|
| --- | --- | --- |
|
|
169
169
|
| **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
|
|
170
|
-
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `STimeline` `SCallout` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
170
|
+
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `STreeTable` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `STimeline` `SCallout` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
171
171
|
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SMeatballButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SChatAttachedFile` `SChatFile` `SImage` `SLinearProgress` `SCircleProgress` `SSkeleton` |
|
|
172
172
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `SLogo` `STextLink` `SChip` |
|
|
173
173
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SLauncherListBox`(런처 버튼에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
@@ -568,6 +568,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
568
568
|
| 대화 중간에 시스템이 알리는 사실을 끼운다 (상담사 배정·대화 종료 등) | `SChatSystemMessage` | 사람이 보낸 말이면 `SChatMessage` |
|
|
569
569
|
| 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
|
|
570
570
|
| 부모-자식 계층을 들여쓰기로 보여준다 | `STree` | §3-7-7 |
|
|
571
|
+
| 계층을 이루는 행들을 여러 열로 보여준다 (카테고리 > 상품 > 옵션) | `STreeTable` | §3-7-7 — 한 줄에 라벨 하나면 `STree` |
|
|
571
572
|
| 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
|
|
572
573
|
| 날짜별 일정을 한 달치 격자로 펼쳐 보여준다 | `SCalendarBoard` | §3-7-4 |
|
|
573
574
|
| 항목끼리 크기를 눈으로 견주게 한다 (채널별 매출·상품별 판매량) | `SBarChart` | 정확한 값을 읽어야 하면 `STable` — 그래프는 "어느 쪽이 큰가" 를 답하지 "얼마인가" 를 답하지 않는다 |
|
|
@@ -1910,6 +1911,7 @@ const [from, setFrom] = useState<string | null>(null);
|
|
|
1910
1911
|
| 항목마다 **시각**이 있고 날짜 머리글로 묶여 위에서 아래로 흐른다 (작업 로그·메모 이력) | `STimeline` |
|
|
1911
1912
|
|
|
1912
1913
|
- **항목 하나의 속성을 나열하는 것은 목록이 아니다.** `라벨: 값` 이 세로로 쌓이는 것은 `SKeyValueTable` 이다 (§4-4).
|
|
1914
|
+
- **행끼리 부모-자식 계층을 이루면 `STable` 이 아니라 `STreeTable` 이다** (§3-7-7). 하위 행을 이름 앞 공백이나 `└` 로 흉내 내지 않는다.
|
|
1913
1915
|
- **`STimeline` 의 레일(점·선)과 머리글·시각 열은 컴포넌트가 그린다.** `groups` 에 데이터만 넘기고, 점·선을 `border-l` 이나 절대 위치 `span` 으로 직접 그리지 않는다 — 선이 묶음 경계와 마지막 줄에서 끊기는 규칙을 컴포넌트가 계산한다. 작성자·편집 버튼 같은 업무 내용은 각 항목의 `content` 에 넣는다. 빈 상태 안내도 `emptyText` 로 넘기고, 불러오는 중에는 `emptyText={null}` 로 빈 안내를 숨긴다.
|
|
1914
1916
|
- `SList` 는 레이아웃만 담당한다. depth 별 단일 펼침이 필요하면 `SExpansionList` 다 (§3-7-7).
|
|
1915
1917
|
- **`SList` 의 자식은 `SListItem` 을 권장한다.** 다른 자식도 그대로 렌더되지만, 펼치는 항목은 `SExpansionList` + `SExpansionItem` 이, 끌어서 순서를 바꾸는 항목은 `SDraggableList` + `SDraggableItem` 이 여닫힘·정렬 동작까지 함께 관리하므로 그쪽을 쓴다 (§3-7-7).
|
|
@@ -1944,13 +1946,32 @@ const [from, setFrom] = useState<string | null>(null);
|
|
|
1944
1946
|
❌ <div onKeyDown={e => { e.stopPropagation(); … }}> {/* 리스트가 이미 걸러 준다 */}
|
|
1945
1947
|
```
|
|
1946
1948
|
|
|
1947
|
-
#### 3-7-7. 펼치는
|
|
1949
|
+
#### 3-7-7. 펼치는 넷 — SExpansionItem vs SExpansionList vs STree vs STreeTable
|
|
1948
1950
|
|
|
1949
1951
|
| 판별 | 사용 |
|
|
1950
1952
|
| --- | --- |
|
|
1951
1953
|
| 항목들이 **서로 독립적으로** 여닫힌다 (여러 개 동시에 열려도 됨) | `SExpansionItem` 단독 |
|
|
1952
1954
|
| **한 번에 하나만** 열려야 한다 (아코디언) | `SExpansionList` + `SExpansionItem` |
|
|
1953
1955
|
| **부모-자식 계층 자체**를 보여줘야 한다 (2단 이상, 연결선) | `STree` |
|
|
1956
|
+
| 계층을 이루는 행마다 **열이 둘 이상**이다 (재고·가격처럼 행끼리 비교하는 값) | `STreeTable` |
|
|
1957
|
+
|
|
1958
|
+
`STreeTable` 은 `STable` 에 계층을 붙인 것이다. 헤더·셀·밀도·정렬 버튼·sticky·가상 스크롤은 `STable` 과 같고, 컬럼 규칙(§3-4)도 그대로 따른다. 다른 점만 적는다.
|
|
1959
|
+
|
|
1960
|
+
- **하위 행은 각 행의 `children` 에 같은 모양으로 넣는다.** 필드 이름이 다르면 `childrenKey` 로 알려 준다. 평평한 목록에 `parentId` 를 달아 넘기지 않는다 — 계층으로 바꿔서 넘긴다.
|
|
1961
|
+
- **펼침 버튼·들여쓰기는 컴포넌트가 그린다.** 기본은 보이는 첫 컬럼이고 `treeColumn` 으로 옮긴다. 그 컬럼은 왼쪽 정렬로 두고, 셀 내용은 `render`·`format` 으로 바꾼다 — `renderCell` 은 펼침 버튼을 넣을 자리가 없어 에러가 난다.
|
|
1962
|
+
- **셀 병합은 가로(`colSpan`)만 한다.** 트리 컬럼이 아닌 컬럼은 `renderCell` 로 `<td>` 를 바꿀 수 있고, 한 행 안에서 끝나는 `colSpan` 은 접고 펴도 그대로다. **`rowSpan` 으로 세로 병합하지 않는다** — 행이 접히면 사라진 행 몫까지 병합이 남아 아래 행의 칸을 덮고, 덮인 행의 칸은 옆 컬럼으로 밀려난다. 부모와 하위 행에 같은 값이 반복되면 부모 행에만 쓰고 하위 행은 비워 둔다.
|
|
1963
|
+
- **행 클릭은 펼침이 아니다.** 펼침 버튼만 펼치고 접는다. 행 클릭(`onRowClick`)은 상세 열기 같은 자기 일을 그대로 한다.
|
|
1964
|
+
- **선택은 계층을 따른다.** 부모를 고르면 접힌 하위 행까지 함께 골라지고, 일부만 골라진 부모는 "일부 선택"으로 그려진다. 하위 행이 모두 골라지면 부모도 `selected` 에 들어간다 — 처리 대상이 잎 행뿐이면 받은 목록에서 하위 행이 없는 행만 걸러 쓴다.
|
|
1965
|
+
- **가로로 스크롤되는 표는 트리 컬럼을 왼쪽에 고정한다**(`stickyColumn={{ left: 1 }}`, 트리 컬럼이 첫 컬럼일 때). 스크롤로 들여쓰기가 밀려나면 어느 행이 누구의 하위인지 읽히지 않는다. 체크박스 열은 `selectable` 이면 늘 고정되므로 개수에 넣지 않는다.
|
|
1966
|
+
- **컬럼 폭 조절(`resizable`)은 `STable` 과 달리 기본으로 켜져 있다.** 폭이 고정이어야 하는 표만 `resizable={false}` 로 끈다.
|
|
1967
|
+
- **트리 컬럼에는 `minWidth` 를 준다.** 폭 조절이 켜져 있으므로, 하한이 없으면 들여쓰기보다 좁게 줄어 깊은 행의 이름이 통째로 가려진다. 값은 가장 깊은 행의 이름이 남을 만큼으로 정한다.
|
|
1968
|
+
- **페이지네이션·드래그 선택은 받지 않는다.** 페이지 경계가 부모와 자식 사이를 자르면 계층이 읽히지 않는다. 행이 많으면 `virtualScroll` 을 쓴다.
|
|
1969
|
+
- **정렬은 형제끼리 한다.** `STable` 처럼 받은 순서대로 그리므로, 정렬한 결과를 넘길 때 계층을 풀어 한 줄로 정렬하지 않는다.
|
|
1970
|
+
|
|
1971
|
+
```tsx
|
|
1972
|
+
✅ <STreeTable<Category> columns={columns} rows={categories} selectable selected={selected} onSelectedChange={setSelected} />
|
|
1973
|
+
❌ <STable rows={flatten(categories)} columns={[{ …, render: row => `${' '.repeat(row.depth)}${row.name}` }]} /> {/* 계층을 공백으로 흉내 내지 않는다 */}
|
|
1974
|
+
```
|
|
1954
1975
|
|
|
1955
1976
|
`SExpansionList` 는 depth 별 **단일 확장**을 관리하는 wrapper 다. 직접 `useState` 로 "열린 항목 하나"를 들고 있지 않는다. 항목 사이 구분선은 여기서도 리스트가 알아서 그린다 — 따로 줄 prop 이 없다 (§3-7-6).
|
|
1956
1977
|
|
|
@@ -70,6 +70,7 @@ export type SGhostButtonIntent =
|
|
|
70
70
|
- [SToast](../SToast)
|
|
71
71
|
- [STooltip](../STooltip)
|
|
72
72
|
- [STree](../STree)
|
|
73
|
+
- [STreeTable](../STreeTable)
|
|
73
74
|
|
|
74
75
|
### Depends on
|
|
75
76
|
|
|
@@ -106,5 +107,6 @@ graph TD;
|
|
|
106
107
|
SToast --> SGhostButton
|
|
107
108
|
STooltip --> SGhostButton
|
|
108
109
|
STree --> SGhostButton
|
|
110
|
+
STreeTable --> SGhostButton
|
|
109
111
|
style SGhostButton fill:#f9f,stroke:#333,stroke-width:4px
|
|
110
112
|
```
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| `dragSelectable?` | `boolean` | `false` | 행을 드래그해서 고른다. 기본 `false`. 고르는 수단만 다를 뿐 `selectable` 과 같은 선택이다 — `selected` · `onSelectedChange` · `isRowSelectable` 을 그대로 쓴다. 체크박스 열은 생기지 않고, 대신 고른 구간이 배경색과 바깥 테두리로 표시된다. **`selectable` 과 함께 켜면 `selectable` 이 이긴다** — 체크박스가 있는 표에서 드래그까지 걸리면 글자를 긁으려던 손이 선택을 갈아치운다. 둘 다 필요해 보이면 체크박스 쪽만 남긴다. 누른 자리가 구간의 기준점이고, 끌어간 자리까지가 구간이다. **새로 끌면 이전 선택은 풀린다** — 기존 선택에 더하려면 `Ctrl`(macOS 는 `Cmd`)을 짚고 끈다. 잠긴 행(`isRowSelectable`)은 구간 안에 있어도 그냥 지나간다. **켜 두면 셀 안의 글자를 긁어 복사할 수 없다** — 끌기가 곧 선택이라 글자 선택과 같은 손짓을 두고 다툰다. 값을 복사해 가는 표에는 켜지 않는다. 셀 안의 버튼·입력은 그대로 눌린다. |
|
|
17
17
|
| `contextMenuItems?` | `STableContextMenuItem[]` | — | 드래그 선택에서 **행을 오른쪽 클릭했을 때** 커서 자리에 뜨는 메뉴의 항목. 주지 않거나 비면 메뉴가 뜨지 않고 브라우저 기본 메뉴가 나온다. **`dragSelectable` 에서만 동작한다** — 체크박스 모드에서는 무시된다. 고르지 않은 행에서 누르면 **그 행만 고른 뒤** 열린다. 메뉴가 다룰 대상과 화면에 칠해진 것이 어긋나지 않게 하기 위함이다. 잠긴 행(`isRowSelectable`) 위에서는 열리지 않는다 — 고를 수 없는 행을 대상으로 삼을 수 없기 때문이다. |
|
|
18
18
|
| `isRowSelectable?` | `(row: TRow) => boolean` | — | 이 행을 고를 수 있는가. 주지 않으면 모든 행을 고를 수 있다. 고를 수 없는 행은 체크박스가 잠기고, **전체 선택·Shift 구간 선택의 셈에서도 빠진다** — 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 가지 못한다. 목록에서 지워 버리는 것과 다르다. 실패 0건인 차수처럼 **자리는 보여야 하지만 대상이 될 수는 없는 행**에 쓴다. 아예 대상이 아니라면 `rows` 에서 거르는 편이 낫다. 잠금은 그리기 시점의 판정일 뿐이라, 이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다(제어 상태를 말없이 바꾸지 않는다). 다만 헤더의 전체 해제로는 걷어낼 수 있다. |
|
|
19
|
+
| `isRowIndeterminate?` | `(row: TRow) => boolean` | — | 이 행의 체크박스를 "일부 선택"(indeterminate)으로 그리는가. 주지 않으면 그리지 않는다. **표시만 바꾼다** — 선택 상태(`selected`)와 클릭 동작은 그대로다. 일부 선택된 행을 누르면 선택되지 않은 행을 누른 것과 같이 `selected` 에 더해진다. 선택된 행(`selected` 에 든 행)은 이 값과 무관하게 체크로 그린다. 하위 행을 가진 행처럼 **"아래가 일부만 골라졌다"를 행 스스로 말해야 하는 표**에 쓴다. 계층 선택을 통째로 맡기려면 `STreeTable` 을 쓴다 — 이 prop 은 그 표가 쓰는 통로다. |
|
|
19
20
|
| `selected?` | `TRow[]` | `[]` | |
|
|
20
21
|
| `sort?` | `STableSort \| null` | `null` | 정렬 상태 (controlled). `null`·미지정이면 정렬 없음. 컴포넌트는 정렬 상태를 갖지 않는다 — 서버 정렬이면 이 값이 곧 조회 조건이고, 뒤로가기·새로고침·링크 공유로 복원돼야 하므로 진실은 URL·store 쪽에 있어야 한다. 행을 실제로 정렬하는 것도 소비 앱 몫이다 (`STable` 은 받은 순서대로 그린다). |
|
|
21
22
|
| `resizable?` | `boolean` | `false` | 컬럼 너비 조절 |
|
|
@@ -169,6 +170,10 @@ export type STableColumnField<TRow extends SRow = SRow> = string | ((row: TRow)
|
|
|
169
170
|
|
|
170
171
|
## Dependencies
|
|
171
172
|
|
|
173
|
+
### Used by
|
|
174
|
+
|
|
175
|
+
- [STreeTable](../STreeTable)
|
|
176
|
+
|
|
172
177
|
### Depends on
|
|
173
178
|
|
|
174
179
|
- [SCheckbox](../SCheckbox)
|
|
@@ -194,5 +199,6 @@ graph TD;
|
|
|
194
199
|
STable --> SSelect
|
|
195
200
|
STable --> STextLink
|
|
196
201
|
STable --> STooltip
|
|
202
|
+
STreeTable --> STable
|
|
197
203
|
style STable fill:#f9f,stroke:#333,stroke-width:4px
|
|
198
204
|
```
|
|
@@ -296,6 +296,19 @@ export interface STableProps<TRow extends SRow = SRow> {
|
|
|
296
296
|
* 않는다(제어 상태를 말없이 바꾸지 않는다). 다만 헤더의 전체 해제로는 걷어낼 수 있다.
|
|
297
297
|
*/
|
|
298
298
|
isRowSelectable?: (row: TRow) => boolean;
|
|
299
|
+
/**
|
|
300
|
+
* 이 행의 체크박스를 "일부 선택"(indeterminate)으로 그리는가. 주지 않으면 그리지 않는다.
|
|
301
|
+
*
|
|
302
|
+
* **표시만 바꾼다** — 선택 상태(`selected`)와 클릭 동작은 그대로다. 일부 선택된 행을 누르면
|
|
303
|
+
* 선택되지 않은 행을 누른 것과 같이 `selected` 에 더해진다. 선택된 행(`selected` 에 든 행)은
|
|
304
|
+
* 이 값과 무관하게 체크로 그린다.
|
|
305
|
+
*
|
|
306
|
+
* 하위 행을 가진 행처럼 **"아래가 일부만 골라졌다"를 행 스스로 말해야 하는 표**에 쓴다.
|
|
307
|
+
* 계층 선택을 통째로 맡기려면 `STreeTable` 을 쓴다 — 이 prop 은 그 표가 쓰는 통로다.
|
|
308
|
+
*
|
|
309
|
+
* @nodemo 사용 예는 STreeTable 의 선택 데모가 보여준다 — 계층 없이 이 prop 만 쓰는 화면은 없다.
|
|
310
|
+
*/
|
|
311
|
+
isRowIndeterminate?: (row: TRow) => boolean;
|
|
299
312
|
selected?: TRow[];
|
|
300
313
|
onSelectedChange?: (rows: TRow[]) => void;
|
|
301
314
|
/**
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
| `showSelectAll?` | `boolean` | `false` | 체크박스 사용 시 최상단 전체 선택 아이템 표시 여부 |
|
|
46
46
|
| `guideline?` | `boolean` | `true` | depth 연결선 표시 여부 |
|
|
47
47
|
| `border?` | `boolean` | `true` | 아이템 하단 border 표시 여부 |
|
|
48
|
-
| `cascadeSelection?` | `boolean` | `true` | 부모 선택 시 하위 노드까지 함께 토글 |
|
|
49
48
|
| `leading?` | `ReactNode \| ((state: STreeItemRenderState) => ReactNode)` | — | 사용자 지정 leading 콘텐츠 |
|
|
50
49
|
| `trailing?` | `ReactNode \| ((state: STreeItemRenderState) => ReactNode)` | — | 사용자 지정 trailing 콘텐츠 |
|
|
51
50
|
|
|
@@ -85,8 +85,6 @@ export interface STreeProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childr
|
|
|
85
85
|
guideline?: boolean;
|
|
86
86
|
/** 아이템 하단 border 표시 여부 */
|
|
87
87
|
border?: boolean;
|
|
88
|
-
/** 부모 선택 시 하위 노드까지 함께 토글 */
|
|
89
|
-
cascadeSelection?: boolean;
|
|
90
88
|
/** 사용자 지정 leading 콘텐츠 */
|
|
91
89
|
leading?: ReactNode | ((state: STreeItemRenderState) => ReactNode);
|
|
92
90
|
/** 사용자 지정 trailing 콘텐츠 */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# STreeTable
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### STreeTable
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `rows?` | `TRow[]` | `[]` | 최상위 행. 하위 행은 각 행의 `childrenKey` 필드에 같은 모양으로 넣는다. 펼친 행만 표에 그려진다. 나머지 prop(`columns` · `selectable` · `sort` · `stickyColumn` · `virtualScroll` …)은 `STable` 과 같다. `STable` 처럼 행을 직접 정렬하지 않고 받은 순서대로 그리므로, 정렬하려면 소비 앱이 **형제끼리** 정렬해 넘긴다. 그리는 행이 펼침에 따라 늘고 줄므로, 컬럼의 `renderCell` 로 **`rowSpan` 세로 병합을 하지 않는다** — 행이 접히면 사라진 행 몫까지 병합이 남아 아래 행의 칸을 덮는다. 한 행 안의 `colSpan` 은 된다. |
|
|
12
|
+
| `childrenKey?` | `string` | `'children'` | 하위 행 배열이 든 필드 |
|
|
13
|
+
| `resizable?` | `boolean` | `true` | 컬럼 폭 조절. **`STable` 과 달리 기본이 `true` 다** — 깊이에 따라 트리 컬럼이 필요로 하는 폭이 달라 화면마다 맞는 폭을 미리 정하기 어렵다. 폭이 고정이어야 하는 표만 `false` 로 끈다. 켜 두면 트리 컬럼에 `minWidth` 를 준다. 하한이 없으면 들여쓰기보다 좁게 줄어 깊은 행의 이름이 통째로 가려진다. |
|
|
14
|
+
| `treeColumn?` | `string` | — | 펼침 버튼과 들여쓰기가 붙는 컬럼의 `name`. 주지 않으면 보이는 첫 컬럼이다. 이 컬럼에는 `renderCell` 을 쓰지 않는다 — `<td>` 를 통째로 바꾸면 펼침 버튼을 넣을 자리가 없다. 셀 내용은 `render` · `format` 으로 바꾼다. 들여쓰기가 왼쪽에서 쌓이므로 `align` 은 왼쪽으로 둔다. |
|
|
15
|
+
| `expandedKeys?` | `STreeTableKey[]` | — | 펼친 행의 키 목록 (controlled) |
|
|
16
|
+
| `defaultExpandedKeys?` | `STreeTableKey[]` | — | 비제어 펼침 초기값. 주지 않으면 하위 행이 있는 최상위 행이 펼쳐진 채로 시작한다 (`STree` 와 같다) |
|
|
17
|
+
| `selected?` | `TRow[]` | `[]` | 고른 행 (controlled). 접혀서 보이지 않는 행도 들어 있을 수 있다. 선택은 계층을 따른다. 부모를 고르면 하위 행이 모두 함께 골라지고, 하위 행이 일부만 골라진 부모는 체크박스가 "일부 선택"으로 그려진다. 하위 행이 모두 골라지면 부모도 골라진 것으로 친다 — `onSelectedChange` 로 오는 목록에 부모가 들어간다. 고를 수 없는 행(`isRowSelectable`)과 그 아래는 함께 골라지지 않는다. |
|
|
18
|
+
|
|
19
|
+
#### Events
|
|
20
|
+
|
|
21
|
+
| Event | Type | Description |
|
|
22
|
+
|-------|------|-------------|
|
|
23
|
+
| `onExpandedChange` | `(expandedKeys: STreeTableKey[], row: TRow) => void` | 펼침 변경. 바뀐 뒤의 키 목록과 누른 행이 온다 |
|
|
24
|
+
| `onSelectedChange` | `(rows: TRow[]) => void` | 선택 변경. 계층 순서(부모 다음에 그 하위 행)대로 정렬된 목록이 온다 |
|
|
25
|
+
|
|
26
|
+
## Types
|
|
27
|
+
|
|
28
|
+
### STreeTableKey
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
/** 행 식별 값 — `rowKey` 필드에 든 값이다 */
|
|
32
|
+
export type STreeTableKey = string | number;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Dependencies
|
|
36
|
+
|
|
37
|
+
### Depends on
|
|
38
|
+
|
|
39
|
+
- [SGhostButton](../SGhostButton)
|
|
40
|
+
- [STable](../STable)
|
|
41
|
+
|
|
42
|
+
### Graph
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
graph TD;
|
|
46
|
+
STreeTable --> SGhostButton
|
|
47
|
+
STreeTable --> STable
|
|
48
|
+
style STreeTable fill:#f9f,stroke:#333,stroke-width:4px
|
|
49
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import { type SRow, type STableProps } from '../STable';
|
|
3
|
+
/** 행 식별 값 — `rowKey` 필드에 든 값이다 */
|
|
4
|
+
export type STreeTableKey = string | number;
|
|
5
|
+
/**
|
|
6
|
+
* STable 에서 받지 않는 prop.
|
|
7
|
+
*
|
|
8
|
+
* - 페이지네이션: 페이지 경계가 부모와 자식 사이를 자르면 계층이 읽히지 않는다.
|
|
9
|
+
* - 드래그 선택·오른쪽 클릭 메뉴: 펼친 순서의 구간은 계층과 어긋난다 — 접힌 자식은 구간에 들지 않는다.
|
|
10
|
+
* - `isRowIndeterminate`: 일부 선택 표시는 STreeTable 이 계층에서 계산한다.
|
|
11
|
+
*/
|
|
12
|
+
type OmittedTableProps = 'rows' | 'selected' | 'onSelectedChange' | 'isRowIndeterminate' | 'pagination' | 'onPageChange' | 'internalPagination' | 'showRowsPerPageSelect' | 'rowsPerPageOption' | 'onRowsPerPageChange' | 'dragSelectable' | 'contextMenuItems' | 'onContextMenuItemClick';
|
|
13
|
+
export interface STreeTableProps<TRow extends SRow = SRow> extends Omit<STableProps<TRow>, OmittedTableProps> {
|
|
14
|
+
/**
|
|
15
|
+
* 최상위 행. 하위 행은 각 행의 `childrenKey` 필드에 같은 모양으로 넣는다.
|
|
16
|
+
*
|
|
17
|
+
* 펼친 행만 표에 그려진다. 나머지 prop(`columns` · `selectable` · `sort` · `stickyColumn` ·
|
|
18
|
+
* `virtualScroll` …)은 `STable` 과 같다. `STable` 처럼 행을 직접 정렬하지 않고 받은 순서대로 그리므로,
|
|
19
|
+
* 정렬하려면 소비 앱이 **형제끼리** 정렬해 넘긴다.
|
|
20
|
+
*
|
|
21
|
+
* 그리는 행이 펼침에 따라 늘고 줄므로, 컬럼의 `renderCell` 로 **`rowSpan` 세로 병합을 하지 않는다** —
|
|
22
|
+
* 행이 접히면 사라진 행 몫까지 병합이 남아 아래 행의 칸을 덮는다. 한 행 안의 `colSpan` 은 된다.
|
|
23
|
+
*/
|
|
24
|
+
rows?: TRow[];
|
|
25
|
+
/** 하위 행 배열이 든 필드 */
|
|
26
|
+
childrenKey?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 컬럼 폭 조절. **`STable` 과 달리 기본이 `true` 다** — 깊이에 따라 트리 컬럼이 필요로 하는 폭이
|
|
29
|
+
* 달라 화면마다 맞는 폭을 미리 정하기 어렵다. 폭이 고정이어야 하는 표만 `false` 로 끈다.
|
|
30
|
+
*
|
|
31
|
+
* 켜 두면 트리 컬럼에 `minWidth` 를 준다. 하한이 없으면 들여쓰기보다 좁게 줄어 깊은 행의 이름이
|
|
32
|
+
* 통째로 가려진다.
|
|
33
|
+
*/
|
|
34
|
+
resizable?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 펼침 버튼과 들여쓰기가 붙는 컬럼의 `name`. 주지 않으면 보이는 첫 컬럼이다.
|
|
37
|
+
*
|
|
38
|
+
* 이 컬럼에는 `renderCell` 을 쓰지 않는다 — `<td>` 를 통째로 바꾸면 펼침 버튼을 넣을 자리가 없다.
|
|
39
|
+
* 셀 내용은 `render` · `format` 으로 바꾼다. 들여쓰기가 왼쪽에서 쌓이므로 `align` 은 왼쪽으로 둔다.
|
|
40
|
+
*/
|
|
41
|
+
treeColumn?: string;
|
|
42
|
+
/** 펼친 행의 키 목록 (controlled) */
|
|
43
|
+
expandedKeys?: STreeTableKey[];
|
|
44
|
+
/** 비제어 펼침 초기값. 주지 않으면 하위 행이 있는 최상위 행이 펼쳐진 채로 시작한다 (`STree` 와 같다) */
|
|
45
|
+
defaultExpandedKeys?: STreeTableKey[];
|
|
46
|
+
/** 펼침 변경. 바뀐 뒤의 키 목록과 누른 행이 온다 */
|
|
47
|
+
onExpandedChange?: (expandedKeys: STreeTableKey[], row: TRow) => void;
|
|
48
|
+
/**
|
|
49
|
+
* 고른 행 (controlled). 접혀서 보이지 않는 행도 들어 있을 수 있다.
|
|
50
|
+
*
|
|
51
|
+
* 선택은 계층을 따른다. 부모를 고르면 하위 행이 모두 함께 골라지고, 하위 행이 일부만 골라진 부모는
|
|
52
|
+
* 체크박스가 "일부 선택"으로 그려진다. 하위 행이 모두 골라지면 부모도 골라진 것으로 친다 —
|
|
53
|
+
* `onSelectedChange` 로 오는 목록에 부모가 들어간다. 고를 수 없는 행(`isRowSelectable`)과 그 아래는
|
|
54
|
+
* 함께 골라지지 않는다.
|
|
55
|
+
*/
|
|
56
|
+
selected?: TRow[];
|
|
57
|
+
/** 선택 변경. 계층 순서(부모 다음에 그 하위 행)대로 정렬된 목록이 온다 */
|
|
58
|
+
onSelectedChange?: (rows: TRow[]) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* STreeTable — `STable` 에 행 계층(펼침·접힘)을 붙인 표.
|
|
62
|
+
*
|
|
63
|
+
* 계층을 펼친 순서의 평평한 행 목록으로 바꿔 `STable` 에 넘기고, 트리 컬럼 셀에 들여쓰기와
|
|
64
|
+
* 펼침 버튼을 넣는다. 그래서 헤더·셀·테두리·밀도·정렬 버튼·sticky·가상 스크롤은 `STable` 과 같다.
|
|
65
|
+
*/
|
|
66
|
+
export declare function STreeTable<TRow extends SRow = SRow>({ rows, columns, rowKey, childrenKey, resizable, treeColumn, expandedKeys, defaultExpandedKeys, onExpandedChange, selectable, isRowSelectable, selected, onSelectedChange, ...rest }: STreeTableProps<TRow>): ReactElement | null;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { STableColumn } from '../STable';
|
|
2
|
+
/** 스토리·playground 가 함께 쓰는 예시 데이터 — 카테고리 > 상품 > 옵션 3단 */
|
|
3
|
+
export interface TreeTableExampleRow {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
code: string;
|
|
7
|
+
stock: number;
|
|
8
|
+
price: number;
|
|
9
|
+
/** 판매 중지 — 선택 잠금 예시에 쓴다 */
|
|
10
|
+
stopped?: boolean;
|
|
11
|
+
children?: TreeTableExampleRow[];
|
|
12
|
+
}
|
|
13
|
+
export declare const treeTableRows: TreeTableExampleRow[];
|
|
14
|
+
export declare const treeTableColumns: STableColumn<TreeTableExampleRow>[];
|
|
15
|
+
/**
|
|
16
|
+
* 가로로 스크롤되는 넓은 표 — sticky 예시에 쓴다.
|
|
17
|
+
* 맨 왼쪽은 트리 컬럼(상품명), 맨 오른쪽은 상태다. 남은 폭을 먹는 스페이서는 두지 않는다 —
|
|
18
|
+
* 스페이서가 끝에 있으면 오른쪽 고정 컬럼이 스페이서가 된다.
|
|
19
|
+
*/
|
|
20
|
+
export declare const treeTableWideColumns: STableColumn<TreeTableExampleRow>[];
|
|
21
|
+
/** 하위 행을 가진 모든 행의 키 — "모두 펼치기" 예시에 쓴다 */
|
|
22
|
+
export declare const treeTableParentKeys: string[];
|
|
23
|
+
/** 깊은 트리 예시 — 창고 > 구역 > 랙 > 단 > 칸, 5단계 로케이션 */
|
|
24
|
+
export interface TreeTableLocationRow {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
qty: number;
|
|
28
|
+
children?: TreeTableLocationRow[];
|
|
29
|
+
}
|
|
30
|
+
export declare const treeTableLocationRows: TreeTableLocationRow[];
|
|
31
|
+
export declare const treeTableLocationParentKeys: string[];
|
|
32
|
+
export declare const treeTableLocationColumns: STableColumn<TreeTableLocationRow>[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { STreeTable, type STreeTableProps, type STreeTableKey } from './STreeTable';
|
package/dist/index.cjs
CHANGED
|
@@ -7596,10 +7596,7 @@ function toSelectedSet(value) {
|
|
|
7596
7596
|
return new Set(value ?? []);
|
|
7597
7597
|
}
|
|
7598
7598
|
var TREE_ALL_NODE = { id: "__all__", label: "\uC804\uCCB4" };
|
|
7599
|
-
function getSelectionState(node, selectedSet
|
|
7600
|
-
if (!cascadeSelection) {
|
|
7601
|
-
return { selected: selectedSet.has(node.id), indeterminate: false };
|
|
7602
|
-
}
|
|
7599
|
+
function getSelectionState(node, selectedSet) {
|
|
7603
7600
|
const selectableChildren = (node.children ?? []).filter(
|
|
7604
7601
|
(child) => collectSelectableIds(child).length > 0
|
|
7605
7602
|
);
|
|
@@ -7607,7 +7604,7 @@ function getSelectionState(node, selectedSet, cascadeSelection) {
|
|
|
7607
7604
|
return { selected: selectedSet.has(node.id), indeterminate: false };
|
|
7608
7605
|
}
|
|
7609
7606
|
const childStates = selectableChildren.map(
|
|
7610
|
-
(child) => getSelectionState(child, selectedSet
|
|
7607
|
+
(child) => getSelectionState(child, selectedSet)
|
|
7611
7608
|
);
|
|
7612
7609
|
const selectedCount = childStates.filter((state2) => state2.selected).length;
|
|
7613
7610
|
return {
|
|
@@ -7878,7 +7875,6 @@ var STreeImpl = /* @__PURE__ */ react.forwardRef(function STree({
|
|
|
7878
7875
|
selectable = true,
|
|
7879
7876
|
size = "sm",
|
|
7880
7877
|
showSelectAll = false,
|
|
7881
|
-
cascadeSelection = true,
|
|
7882
7878
|
guideline = true,
|
|
7883
7879
|
border = true,
|
|
7884
7880
|
leading,
|
|
@@ -7939,8 +7935,8 @@ var STreeImpl = /* @__PURE__ */ react.forwardRef(function STree({
|
|
|
7939
7935
|
};
|
|
7940
7936
|
const updateSelected = (node) => {
|
|
7941
7937
|
if (node.disabled) return;
|
|
7942
|
-
const targetIds =
|
|
7943
|
-
const everySelected = getSelectionState(node, selectedSet
|
|
7938
|
+
const targetIds = collectSelectableIds(node);
|
|
7939
|
+
const everySelected = getSelectionState(node, selectedSet).selected;
|
|
7944
7940
|
const next = new Set(selectedSet);
|
|
7945
7941
|
targetIds.forEach((id) => {
|
|
7946
7942
|
if (everySelected) next.delete(id);
|
|
@@ -8018,7 +8014,7 @@ var STreeImpl = /* @__PURE__ */ react.forwardRef(function STree({
|
|
|
8018
8014
|
const renderNodes = (nodes, depth = 1, ancestorLast = []) => nodes.map((node, index) => {
|
|
8019
8015
|
const isLast = index === nodes.length - 1;
|
|
8020
8016
|
const isExpanded = expandedSet.has(node.id);
|
|
8021
|
-
const { selected, indeterminate } = getSelectionState(node, selectedSet
|
|
8017
|
+
const { selected, indeterminate } = getSelectionState(node, selectedSet);
|
|
8022
8018
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
8023
8019
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8024
8020
|
STreeItem2,
|
|
@@ -16759,6 +16755,7 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
16759
16755
|
contextMenuItems,
|
|
16760
16756
|
onContextMenuItemClick,
|
|
16761
16757
|
isRowSelectable,
|
|
16758
|
+
isRowIndeterminate,
|
|
16762
16759
|
selected = [],
|
|
16763
16760
|
onSelectedChange,
|
|
16764
16761
|
sort = null,
|
|
@@ -17685,7 +17682,7 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17685
17682
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17686
17683
|
SCheckbox,
|
|
17687
17684
|
{
|
|
17688
|
-
value: isSel,
|
|
17685
|
+
value: isSel || !isRowIndeterminate?.(row) ? isSel : null,
|
|
17689
17686
|
disabled: !canSelectRow(row),
|
|
17690
17687
|
onValueChange: () => toggleRow(row, rowOffset + ri)
|
|
17691
17688
|
}
|
|
@@ -17935,6 +17932,190 @@ var STableBar = /* @__PURE__ */ react.forwardRef(function STableBar2({ title, to
|
|
|
17935
17932
|
}
|
|
17936
17933
|
);
|
|
17937
17934
|
});
|
|
17935
|
+
function STreeTable({
|
|
17936
|
+
rows = [],
|
|
17937
|
+
columns = [],
|
|
17938
|
+
rowKey = "id",
|
|
17939
|
+
childrenKey = "children",
|
|
17940
|
+
resizable = true,
|
|
17941
|
+
treeColumn,
|
|
17942
|
+
expandedKeys,
|
|
17943
|
+
defaultExpandedKeys,
|
|
17944
|
+
onExpandedChange,
|
|
17945
|
+
selectable = false,
|
|
17946
|
+
isRowSelectable,
|
|
17947
|
+
selected = [],
|
|
17948
|
+
onSelectedChange,
|
|
17949
|
+
...rest
|
|
17950
|
+
}) {
|
|
17951
|
+
const keyOf = (row) => row[rowKey];
|
|
17952
|
+
const childrenOf = (row) => {
|
|
17953
|
+
const children = row[childrenKey];
|
|
17954
|
+
return Array.isArray(children) ? children : [];
|
|
17955
|
+
};
|
|
17956
|
+
const canSelectRow = (row) => isRowSelectable ? isRowSelectable(row) : true;
|
|
17957
|
+
const entries4 = react.useMemo(() => {
|
|
17958
|
+
const map = /* @__PURE__ */ new Map();
|
|
17959
|
+
const walk = (nodes, depth, parentKey) => {
|
|
17960
|
+
nodes.forEach((row) => {
|
|
17961
|
+
const children = childrenOf(row);
|
|
17962
|
+
map.set(keyOf(row), { row, depth, children, parentKey });
|
|
17963
|
+
walk(children, depth + 1, keyOf(row));
|
|
17964
|
+
});
|
|
17965
|
+
};
|
|
17966
|
+
walk(rows, 0, null);
|
|
17967
|
+
return map;
|
|
17968
|
+
}, [rows, rowKey, childrenKey]);
|
|
17969
|
+
const [innerExpandedKeys, setInnerExpandedKeys] = react.useState(
|
|
17970
|
+
() => defaultExpandedKeys ?? rows.filter((row) => childrenOf(row).length > 0).map(keyOf)
|
|
17971
|
+
);
|
|
17972
|
+
const currentExpandedKeys = expandedKeys ?? innerExpandedKeys;
|
|
17973
|
+
const expandedSet = react.useMemo(() => new Set(currentExpandedKeys), [currentExpandedKeys]);
|
|
17974
|
+
const toggleExpanded = (row) => {
|
|
17975
|
+
const key = keyOf(row);
|
|
17976
|
+
const next = expandedSet.has(key) ? currentExpandedKeys.filter((k) => k !== key) : [...currentExpandedKeys, key];
|
|
17977
|
+
if (expandedKeys === void 0) setInnerExpandedKeys(next);
|
|
17978
|
+
onExpandedChange?.(next, row);
|
|
17979
|
+
};
|
|
17980
|
+
const visibleRows = react.useMemo(() => {
|
|
17981
|
+
const out = [];
|
|
17982
|
+
const walk = (nodes) => {
|
|
17983
|
+
nodes.forEach((row) => {
|
|
17984
|
+
out.push(row);
|
|
17985
|
+
if (expandedSet.has(keyOf(row))) walk(childrenOf(row));
|
|
17986
|
+
});
|
|
17987
|
+
};
|
|
17988
|
+
walk(rows);
|
|
17989
|
+
return out;
|
|
17990
|
+
}, [rows, expandedSet, rowKey, childrenKey]);
|
|
17991
|
+
const collectSelectable = (row) => canSelectRow(row) ? [row, ...childrenOf(row).flatMap(collectSelectable)] : [];
|
|
17992
|
+
const selectionOf = (keys) => {
|
|
17993
|
+
const state2 = /* @__PURE__ */ new Map();
|
|
17994
|
+
const visit = (row) => {
|
|
17995
|
+
const children = childrenOf(row);
|
|
17996
|
+
const childStates = children.map(visit);
|
|
17997
|
+
const counted = childStates.filter((_, i) => canSelectRow(children[i]));
|
|
17998
|
+
const result = counted.length === 0 ? { selected: keys.has(keyOf(row)), indeterminate: false } : {
|
|
17999
|
+
selected: counted.every((s) => s.selected),
|
|
18000
|
+
indeterminate: !counted.every((s) => s.selected) && counted.some((s) => s.selected || s.indeterminate)
|
|
18001
|
+
};
|
|
18002
|
+
state2.set(keyOf(row), result);
|
|
18003
|
+
return result;
|
|
18004
|
+
};
|
|
18005
|
+
rows.forEach(visit);
|
|
18006
|
+
return state2;
|
|
18007
|
+
};
|
|
18008
|
+
const selectionState = react.useMemo(
|
|
18009
|
+
() => selectable ? selectionOf(new Set(selected.map(keyOf))) : null,
|
|
18010
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18011
|
+
[selectable, selected, entries4, isRowSelectable]
|
|
18012
|
+
);
|
|
18013
|
+
const tableSelected = react.useMemo(() => {
|
|
18014
|
+
if (!selectionState) return selected;
|
|
18015
|
+
return [...entries4.values()].filter((entry) => selectionState.get(keyOf(entry.row))?.selected).map((entry) => entry.row);
|
|
18016
|
+
}, [selectionState, selected, entries4]);
|
|
18017
|
+
const handleSelectedChange = (next) => {
|
|
18018
|
+
const prevKeys = new Set(tableSelected.map(keyOf));
|
|
18019
|
+
const nextKeys = new Set(next.map(keyOf));
|
|
18020
|
+
const keys = new Set(prevKeys);
|
|
18021
|
+
prevKeys.forEach((key) => {
|
|
18022
|
+
if (nextKeys.has(key)) return;
|
|
18023
|
+
const entry = entries4.get(key);
|
|
18024
|
+
keys.delete(key);
|
|
18025
|
+
if (entry) collectSelectable(entry.row).forEach((row) => keys.delete(keyOf(row)));
|
|
18026
|
+
});
|
|
18027
|
+
nextKeys.forEach((key) => {
|
|
18028
|
+
if (prevKeys.has(key)) return;
|
|
18029
|
+
const entry = entries4.get(key);
|
|
18030
|
+
keys.add(key);
|
|
18031
|
+
if (entry) collectSelectable(entry.row).forEach((row) => keys.add(keyOf(row)));
|
|
18032
|
+
});
|
|
18033
|
+
const state2 = selectionOf(keys);
|
|
18034
|
+
onSelectedChange?.(
|
|
18035
|
+
[...entries4.values()].filter((entry) => state2.get(keyOf(entry.row))?.selected).map((entry) => entry.row)
|
|
18036
|
+
);
|
|
18037
|
+
};
|
|
18038
|
+
const treeColumnName = treeColumn ?? columns.find((col) => col.visible !== false)?.name;
|
|
18039
|
+
const tableColumns = react.useMemo(
|
|
18040
|
+
() => columns.map((col) => {
|
|
18041
|
+
if (col.name !== treeColumnName) return col;
|
|
18042
|
+
if (col.renderCell) {
|
|
18043
|
+
throw new Error(
|
|
18044
|
+
`[STreeTable] \uD2B8\uB9AC \uCEEC\uB7FC(${col.name})\uC5D0\uB294 renderCell \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. render \uB97C \uC4F0\uC138\uC694.`
|
|
18045
|
+
);
|
|
18046
|
+
}
|
|
18047
|
+
return {
|
|
18048
|
+
...col,
|
|
18049
|
+
render: (row, value) => {
|
|
18050
|
+
const entry = entries4.get(keyOf(row));
|
|
18051
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18052
|
+
TreeCell,
|
|
18053
|
+
{
|
|
18054
|
+
depth: entry?.depth ?? 0,
|
|
18055
|
+
hasChildren: (entry?.children.length ?? 0) > 0,
|
|
18056
|
+
expanded: expandedSet.has(keyOf(row)),
|
|
18057
|
+
onToggle: () => toggleExpanded(row),
|
|
18058
|
+
children: col.render ? col.render(row, value) : value
|
|
18059
|
+
}
|
|
18060
|
+
);
|
|
18061
|
+
}
|
|
18062
|
+
};
|
|
18063
|
+
}),
|
|
18064
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18065
|
+
[columns, treeColumnName, entries4, expandedSet, currentExpandedKeys]
|
|
18066
|
+
);
|
|
18067
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18068
|
+
STable2,
|
|
18069
|
+
{
|
|
18070
|
+
...rest,
|
|
18071
|
+
columns: tableColumns,
|
|
18072
|
+
rows: visibleRows,
|
|
18073
|
+
rowKey,
|
|
18074
|
+
resizable,
|
|
18075
|
+
selectable,
|
|
18076
|
+
isRowSelectable,
|
|
18077
|
+
isRowIndeterminate: selectionState ? (row) => !!selectionState.get(keyOf(row))?.indeterminate : void 0,
|
|
18078
|
+
selected: tableSelected,
|
|
18079
|
+
onSelectedChange: handleSelectedChange
|
|
18080
|
+
}
|
|
18081
|
+
);
|
|
18082
|
+
}
|
|
18083
|
+
function TreeCell({
|
|
18084
|
+
depth,
|
|
18085
|
+
hasChildren,
|
|
18086
|
+
expanded,
|
|
18087
|
+
onToggle,
|
|
18088
|
+
children
|
|
18089
|
+
}) {
|
|
18090
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
18091
|
+
"div",
|
|
18092
|
+
{
|
|
18093
|
+
className: "flex min-w-0 items-center gap-(--cmp-tree-item-contentGap)",
|
|
18094
|
+
style: {
|
|
18095
|
+
paddingLeft: `calc(${depth} * (var(--cmp-ghostButton-xxs-size) + var(--cmp-tree-item-contentGap)))`
|
|
18096
|
+
},
|
|
18097
|
+
children: [
|
|
18098
|
+
hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18099
|
+
SGhostButton,
|
|
18100
|
+
{
|
|
18101
|
+
icon: expanded ? "caretDown" : "caretRight",
|
|
18102
|
+
size: "xxs",
|
|
18103
|
+
ariaLabel: expanded ? "collapse row" : "expand row",
|
|
18104
|
+
ariaExpanded: expanded,
|
|
18105
|
+
onClick: (event) => {
|
|
18106
|
+
event.stopPropagation();
|
|
18107
|
+
onToggle();
|
|
18108
|
+
}
|
|
18109
|
+
}
|
|
18110
|
+
) : (
|
|
18111
|
+
// 하위 행이 없는 행도 버튼 자리를 비워 형제끼리 내용의 시작점을 맞춘다
|
|
18112
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-(--cmp-ghostButton-xxs-size) shrink-0", "aria-hidden": "true" })
|
|
18113
|
+
),
|
|
18114
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children })
|
|
18115
|
+
]
|
|
18116
|
+
}
|
|
18117
|
+
);
|
|
18118
|
+
}
|
|
17938
18119
|
var HIDE_DELAY_MS2 = 100;
|
|
17939
18120
|
function mergeRefs(...refs) {
|
|
17940
18121
|
return (node) => {
|
|
@@ -22944,7 +23125,7 @@ var SForm = /* @__PURE__ */ react.forwardRef(function SForm2({ formClassName, on
|
|
|
22944
23125
|
var SIZE_CONFIG2 = {
|
|
22945
23126
|
xs: { fontSize: 12, lineHeight: 20, radius: "var(--cmp-radioButton-radius-sm)" },
|
|
22946
23127
|
sm: { fontSize: 12, lineHeight: 20, radius: "var(--cmp-radioButton-radius-sm)" },
|
|
22947
|
-
md: { fontSize:
|
|
23128
|
+
md: { fontSize: 14, lineHeight: 24, radius: "var(--cmp-radioButton-radius-md)" }
|
|
22948
23129
|
};
|
|
22949
23130
|
function SRadioButton({
|
|
22950
23131
|
options = [],
|
|
@@ -28783,6 +28964,7 @@ exports.SToggle = SToggle;
|
|
|
28783
28964
|
exports.STooltip = STooltip2;
|
|
28784
28965
|
exports.STree = STree2;
|
|
28785
28966
|
exports.STreeItem = STreeItem2;
|
|
28967
|
+
exports.STreeTable = STreeTable;
|
|
28786
28968
|
exports.SYSTEM_ACTION_BUTTON_COLORS = SYSTEM_ACTION_BUTTON_COLORS;
|
|
28787
28969
|
exports.SYSTEM_ACTION_BUTTON_COLOR_CONFIG = SYSTEM_ACTION_BUTTON_COLOR_CONFIG;
|
|
28788
28970
|
exports.SYSTEM_ACTION_BUTTON_LAYOUT = SYSTEM_ACTION_BUTTON_LAYOUT;
|