sellmate-design-system-react 9.0.0-beta.94 → 9.0.0-beta.97

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
@@ -572,6 +572,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
572
572
  | 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
573
573
  | 부모-자식 계층을 들여쓰기로 보여준다 | `STree` | §3-7-7 — 폼 값으로 드롭다운에서 고르는 것이면 `SSelect` `type="tree"` |
574
574
  | 계층을 이루는 행들을 여러 열로 보여준다 (카테고리 > 상품 > 옵션) | `STreeTable` | §3-7-7 — 한 줄에 라벨 하나면 `STree` |
575
+ | 표의 행마다 그 건의 상세를 펼쳐 보여준다 (한 행 = 한 건) | `STable` `renderExpansion` | §3-7-7 — 행끼리 계층이면 `STreeTable` |
575
576
  | 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
576
577
  | 날짜별 일정을 한 달치 격자로 펼쳐 보여준다 | `SCalendarBoard` | §3-7-4 |
577
578
  | 항목끼리 크기를 눈으로 견주게 한다 (채널별 매출·상품별 판매량) | `SBarChart` | 정확한 값을 읽어야 하면 `STable` — 그래프는 "어느 쪽이 큰가" 를 답하지 "얼마인가" 를 답하지 않는다 |
@@ -1093,6 +1094,7 @@ const columns: STableColumn<Order>[] = [
1093
1094
  - **컨트롤이 들어가는 컬럼에는 `contentType: 'control'` 을 함께 준다.** 좌우 패딩이 텍스트용(넓게)에서 컨트롤용(좁게)으로 바뀌어, 같은 컬럼 폭에서도 요소가 쓸 폭이 넓어진다. 기본값은 `text` 다.
1094
1095
  - 요소가 둘 이상이면 요소 폭의 합에 **`gap` 까지** 더한다 (행 내부 인라인 액션 간격은 `gap-sd-4` 고정, §3-5-5).
1095
1096
  - `SSelect` · `SInput` 처럼 셀 폭을 채우는 컨트롤은 **컬럼 폭이 곧 컨트롤 폭**이다. 실제 선택값·입력값이 말줄임 없이 읽히는 폭인지 확인한다.
1097
+ - **셀 안 컨트롤을 누른 것은 행 클릭(`onRowClick`)·더블클릭이 아니다.** 버튼·링크·입력과 선택 체크박스 열은 그 컨트롤의 동작만 일으킨다 — 셀이나 컨트롤에 `stopPropagation` 을 직접 넣지 않는다.
1096
1098
  - 폭을 넉넉히 줄 수 없는 자리는 폭을 줄이는 게 아니라 **요소를 바꾼다** — 라벨 버튼 대신 아이콘만 있는 `SGhostButton`, `size="xs"` (§3-5-2, §3-5-5).
1097
1099
 
1098
1100
  ```tsx
@@ -1935,7 +1937,8 @@ const [from, setFrom] = useState<string | null>(null);
1935
1937
  직접 쓰는 경우는 하나뿐이다 — **디자인 시스템에 없는 컨트롤**에 다른 필드와 똑같은 라벨·필수·에러 모양을 붙일 때.
1936
1938
 
1937
1939
  - **라벨은 `controlId` 로 컨트롤에 잇는다.** 컨트롤에 `id` 를 주고 같은 값을 `controlId` 로 넘기면 라벨이 그 컨트롤의 이름이 되고, 라벨을 누르면 컨트롤에 포커스가 간다. 컨트롤이 `input`·`textarea`·`select`·`button` 이 아니라 `role` 로 만든 요소면 `aria-labelledby={fieldLabelId(id)}` 를 직접 건다.
1938
- - **라벨이 보이는 입력에 `aria-label` 을 다시 적지 않는다.** DS 입력 컴포넌트는 `label` 을 알아서 컨트롤에 잇는다. 화면의 라벨과 읽히는 이름을 두 번 적으면 어긋난다. `aria-label`(`SCheckbox`·`SChipInput` 은 `ariaLabel`)은 **라벨 글자 없이 서는 컨트롤**에만 준다 — 표의 행 선택 체크박스처럼.
1940
+ - **라벨이 보이는 입력에 `aria-label` 을 다시 적지 않는다.** DS 입력 컴포넌트는 `label` 을 알아서 컨트롤에 잇는다. 화면의 라벨과 읽히는 이름을 두 번 적으면 어긋난다. `aria-label`(`SCheckbox`·`SChipInput`·`SSelect` 는 `ariaLabel`)은 **라벨 글자 없이 서는 컨트롤**에만 준다 — 목록 위 정렬 셀렉트처럼. 라벨 없이 서는 `SSelect` 에 `ariaLabel` 을 빠뜨리면 고른 값만 읽혀 무엇을 고르는 칸인지 알 수 없다.
1941
+ - **`selectable` 표에는 `rowSelectLabel` 을 준다.** 행 체크박스는 그 행을 가리키는 글자로 이름이 붙어야 한다(`row => \`${row.orderNo} 선택\``). 주지 않으면 모든 행이 "행 선택" 으로 읽혀 구분되지 않는다. 헤더는 기본 "전체 선택" 이고, 무엇의 전체인지 밝혀야 하면 `selectAllLabel` 로 바꾼다. `STreeTable` 도 같다.
1939
1942
 
1940
1943
  ```tsx
1941
1944
  ✅ <SField label="색상" controlId={id}><input id={id} type="color" /></SField>
@@ -1991,7 +1994,7 @@ const [from, setFrom] = useState<string | null>(null);
1991
1994
  ❌ <div onKeyDown={e => { e.stopPropagation(); … }}> {/* 리스트가 이미 걸러 준다 */}
1992
1995
  ```
1993
1996
 
1994
- #### 3-7-7. 펼치는 넷 — SExpansionItem vs SExpansionList vs STree vs STreeTable
1997
+ #### 3-7-7. 펼치는 다섯 — SExpansionItem vs SExpansionList vs STree vs STreeTable vs STable 펼침
1995
1998
 
1996
1999
  | 판별 | 사용 |
1997
2000
  | --- | --- |
@@ -1999,12 +2002,14 @@ const [from, setFrom] = useState<string | null>(null);
1999
2002
  | **한 번에 하나만** 열려야 한다 (아코디언) | `SExpansionList` + `SExpansionItem` |
2000
2003
  | **부모-자식 계층 자체**를 보여줘야 한다 (2단 이상, 연결선) | `STree` |
2001
2004
  | 계층을 이루는 행마다 **열이 둘 이상**이다 (재고·가격처럼 행끼리 비교하는 값) | `STreeTable` |
2005
+ | 표의 **행마다 그 건의 상세**를 펼친다 (한 행 = 한 건, 상세는 열로 나뉘지 않는 내용) | `STable` + `renderExpansion` |
2002
2006
 
2003
2007
  `STreeTable` 은 `STable` 에 계층을 붙인 것이다. 헤더·셀·밀도·정렬 버튼·sticky·가상 스크롤은 `STable` 과 같고, 컬럼 규칙(§3-4)도 그대로 따른다. 다른 점만 적는다.
2004
2008
 
2005
2009
  - **하위 행은 각 행의 `children` 에 같은 모양으로 넣는다.** 필드 이름이 다르면 `childrenKey` 로 알려 준다. 평평한 목록에 `parentId` 를 달아 넘기지 않는다 — 계층으로 바꿔서 넘긴다.
2006
2010
  - **펼침 버튼·들여쓰기는 컴포넌트가 그린다.** 기본은 보이는 첫 컬럼이고 `treeColumn` 으로 옮긴다. 그 컬럼은 왼쪽 정렬로 두고, 셀 내용은 `render`·`format` 으로 바꾼다 — `renderCell` 은 펼침 버튼을 넣을 자리가 없어 에러가 난다.
2007
2011
  - **셀 병합은 가로(`colSpan`)만 한다.** 트리 컬럼이 아닌 컬럼은 `renderCell` 로 `<td>` 를 바꿀 수 있고, 한 행 안에서 끝나는 `colSpan` 은 접고 펴도 그대로다. **`rowSpan` 으로 세로 병합하지 않는다** — 행이 접히면 사라진 행 몫까지 병합이 남아 아래 행의 칸을 덮고, 덮인 행의 칸은 옆 컬럼으로 밀려난다. 부모와 하위 행에 같은 값이 반복되면 부모 행에만 쓰고 하위 행은 비워 둔다.
2012
+ - **"한 행 = 한 건, 펼치면 그 건의 상세" 는 `STreeTable` 이 아니다.** 상세를 가짜 하위 행으로 끼워 넣지 않고 `STable` 의 `renderExpansion` 을 쓴다 (§3-7-7 아래).
2008
2013
  - **펼침 영역은 트리 컬럼 셀 전체다.** 하위 행이 있는 행은 펼침 버튼뿐 아니라 그 셀 어디를 눌러도 접고 편다. 다른 셀을 누르면 행 클릭(`onRowClick`)이다 — 트리 컬럼 셀을 누른 것은 행 클릭으로 가지 않는다.
2009
2014
  - **펼침·접힘 모션은 컴포넌트가 넣는다.** 하위 행이 부모 아래에서 서랍처럼 펼쳐지고 접히며, 아래 행들도 함께 내려가고 올라온다. 모션을 줄인 환경에서는 꺼진다. 행(`rowClassName`)에 애니메이션·`transform`·`clip-path` 를 직접 주지 않는다 — 컴포넌트가 행의 위치를 움직이는 것과 겹친다. `onExpandedChange` 는 모션을 기다리지 않고 누른 즉시 온다.
2010
2015
  - **트리 컬럼에 링크·버튼을 넣어도 된다.** 셀 안의 링크·버튼·입력을 누르면 그 동작만 일어나고 펼침은 일어나지 않는다 — `stopPropagation` 을 직접 넣지 않는다. 상품명을 상세 링크로 거는 것처럼 쓴다.
@@ -2020,6 +2025,20 @@ const [from, setFrom] = useState<string | null>(null);
2020
2025
  ❌ <STable rows={flatten(categories)} columns={[{ …, render: row => `${' '.repeat(row.depth)}${row.name}` }]} /> {/* 계층을 공백으로 흉내 내지 않는다 */}
2021
2026
  ```
2022
2027
 
2028
+ `STable` 의 행 펼침(`renderExpansion`)은 행마다 **그 건의 상세 영역** 하나를 연다. 계층이 아니므로 `STreeTable` 을 쓰지 않는다.
2029
+
2030
+ - **상세 영역은 행이 아니다.** 선택·체크박스·잠금 표시·행 클릭·hover 와 무관하고 체크박스 열까지 덮는 표 전체 폭 한 칸이다. 상세를 가짜 하위 행으로 만들어 `isRowSelectable` 로 잠그지 않는다.
2031
+ - **펼침 버튼은 컴포넌트가 그린다.** 기본은 보이는 첫 컬럼이고 `expansionColumn` 으로 옮긴다. 그 셀 전체가 펼침 영역이며 행 클릭(`onRowClick`)으로 가지 않는다. 펼침 버튼만 담는 좁은 컬럼을 따로 세우지 않고, 그 컬럼에는 `renderCell` 을 쓰지 않는다.
2032
+ - **여러 행을 함께 펼칠 수 있다.** 행 클릭으로도 펴야 하면 `expandedKeys`·`onExpandedChange` 로 들고 `onRowClick` 에서 키를 넣고 뺀다.
2033
+ - **펼침·접힘 모션은 컴포넌트가 넣는다.** `STreeTable` 하위 행과 같은 서랍 움직임이고 모션을 줄인 환경에서는 꺼진다. 행(`rowClassName`)이나 상세 내용에 펼침 애니메이션·`transform` 을 직접 주지 않는다.
2034
+ - **상세의 여백은 컴포넌트가 넣는다.** 셀과 같은 좌우·위아래 여백이 이미 들어가 있으므로 내용에 바깥 패딩을 직접 주지 않는다.
2035
+ - `renderCell` 의 `rowSpan` 세로 병합과 함께 쓰지 않는다 — 펼친 상세가 병합 사이에 끼어든다.
2036
+
2037
+ ```tsx
2038
+ ✅ <STable<Report> columns={columns} rows={reports} renderExpansion={report => <ReportDetail report={report} />} />
2039
+ ❌ <STreeTable rows={reports.map(r => ({ ...r, children: [{ id: `${r.id}:detail`, detail: r }] }))} … /> {/* 상세를 가짜 하위 행으로 만들지 않는다 */}
2040
+ ```
2041
+
2023
2042
  `SExpansionList` 는 depth 별 **단일 확장**을 관리하는 wrapper 다. 직접 `useState` 로 "열린 항목 하나"를 들고 있지 않는다. 항목 사이 구분선은 여기서도 리스트가 알아서 그린다 — 따로 줄 prop 이 없다 (§3-7-6).
2024
2043
 
2025
2044
  ##### 펼침 ≠ 선택
@@ -33,6 +33,7 @@
33
33
  | `serverSearch?` | `boolean` | `false` | 검색을 서버로 넘긴다 — 내부 필터링을 하지 않고 `options` 를 받은 그대로 보여준다. `onSearchChange` 로 온 검색어에 맞는 목록을 소비 앱이 다시 내려줘야 한다. 켜면 검색바가 옵션 수와 무관하게 항상 나온다 — 검색 결과가 줄었다고 검색바가 사라지면 검색어를 지울 수단이 없어지기 때문이다. |
34
34
  | `searchDebounce?` | `number` | `DEFAULT_SEARCH_DEBOUNCE` | `serverSearch` 에서 검색어를 서버로 넘기기 전 기다리는 시간(ms) |
35
35
  | `label?` | `string` | — | |
36
+ | `ariaLabel?` | `string` | — | 스크린리더용 접근성 레이블. **`label` 이 없을 때만 준다** — 표의 셀이나 필터 줄처럼 라벨 글자 없이 서는 셀렉트는 이것이 없으면 고른 값만 읽혀 무엇을 고르는 칸인지 알 수 없다. 라벨처럼 고른 값 앞에 이어 읽힌다("정렬 최신순"). `label` 이 있으면 그 글자가 이미 이름이 되므로 이 값은 쓰지 않는다. |
36
37
  | `labelPosition?` | `SFieldLabelPosition` | — | 레이블 위치 — `top` 이면 라벨이 컨트롤 위에 선다 (SField 로 그대로 넘어간다). |
37
38
  | `subLabel?` | `string` | — | 보조 레이블 — 레이블 오른쪽 같은 줄에 붙는다 (SField 로 그대로 넘어간다). |
38
39
  | `labelWidth?` | `number \| string` | — | |
@@ -152,6 +152,14 @@ export interface SSelectProps<TValue extends SSelectChangeValue = SSelectChangeV
152
152
  /** `serverSearch` 에서 검색어를 서버로 넘기기 전 기다리는 시간(ms) */
153
153
  searchDebounce?: number;
154
154
  label?: string;
155
+ /**
156
+ * 스크린리더용 접근성 레이블. **`label` 이 없을 때만 준다** — 표의 셀이나 필터 줄처럼 라벨 글자 없이
157
+ * 서는 셀렉트는 이것이 없으면 고른 값만 읽혀 무엇을 고르는 칸인지 알 수 없다. 라벨처럼 고른 값 앞에
158
+ * 이어 읽힌다("정렬 최신순").
159
+ *
160
+ * `label` 이 있으면 그 글자가 이미 이름이 되므로 이 값은 쓰지 않는다.
161
+ */
162
+ ariaLabel?: string;
155
163
  /**
156
164
  * 레이블 위치 — `top` 이면 라벨이 컨트롤 위에 선다 (SField 로 그대로 넘어간다).
157
165
  *
@@ -13,6 +13,8 @@
13
13
  | `rows?` | `TRow[]` | `[]` | |
14
14
  | `rowKey?` | `string` | `'id'` | 행 식별 필드 |
15
15
  | `selectable?` | `boolean` | `false` | 행 선택 체크박스 |
16
+ | `rowSelectLabel?` | `(row: TRow) => string` | — | 행 선택 체크박스의 접근성 이름 — 화면 낭독기가 어느 행의 체크박스인지 읽게 한다. 그 행을 가리키는 글자(주문번호·상품명 등)로 만든다: `row => \`${row.orderNo} 선택\``. 주지 않으면 모든 행이 `행 선택` 으로 읽혀 행끼리 구분되지 않는다. |
17
+ | `selectAllLabel?` | `string` | `'전체 선택'` | 헤더 전체 선택 체크박스의 접근성 이름 |
16
18
  | `dragSelectable?` | `boolean` | `false` | 행을 드래그해서 고른다. 기본 `false`. 고르는 수단만 다를 뿐 `selectable` 과 같은 선택이다 — `selected` · `onSelectedChange` · `isRowSelectable` 을 그대로 쓴다. 체크박스 열은 생기지 않고, 대신 고른 구간이 배경색과 바깥 테두리로 표시된다. **`selectable` 과 함께 켜면 `selectable` 이 이긴다** — 체크박스가 있는 표에서 드래그까지 걸리면 글자를 긁으려던 손이 선택을 갈아치운다. 둘 다 필요해 보이면 체크박스 쪽만 남긴다. 누른 자리가 구간의 기준점이고, 끌어간 자리까지가 구간이다. **새로 끌면 이전 선택은 풀린다** — 기존 선택에 더하려면 `Ctrl`(macOS 는 `Cmd`)을 짚고 끈다. 잠긴 행(`isRowSelectable`)은 구간 안에 있어도 그냥 지나간다. **켜 두면 셀 안의 글자를 긁어 복사할 수 없다** — 끌기가 곧 선택이라 글자 선택과 같은 손짓을 두고 다툰다. 값을 복사해 가는 표에는 켜지 않는다. 셀 안의 버튼·입력은 그대로 눌린다. |
17
19
  | `contextMenuItems?` | `STableContextMenuItem[]` | — | 드래그 선택에서 **행을 오른쪽 클릭했을 때** 커서 자리에 뜨는 메뉴의 항목. 주지 않거나 비면 메뉴가 뜨지 않고 브라우저 기본 메뉴가 나온다. **`dragSelectable` 에서만 동작한다** — 체크박스 모드에서는 무시된다. 고르지 않은 행에서 누르면 **그 행만 고른 뒤** 열린다. 메뉴가 다룰 대상과 화면에 칠해진 것이 어긋나지 않게 하기 위함이다. 잠긴 행(`isRowSelectable`) 위에서는 열리지 않는다 — 고를 수 없는 행을 대상으로 삼을 수 없기 때문이다. |
18
20
  | `isRowSelectable?` | `(row: TRow) => boolean` | — | 이 행을 고를 수 있는가. 주지 않으면 모든 행을 고를 수 있다. 고를 수 없는 행은 체크박스가 잠기고, **전체 선택·Shift 구간 선택의 셈에서도 빠진다** — 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 가지 못한다. 목록에서 지워 버리는 것과 다르다. 실패 0건인 차수처럼 **자리는 보여야 하지만 대상이 될 수는 없는 행**에 쓴다. 아예 대상이 아니라면 `rows` 에서 거르는 편이 낫다. 잠금은 그리기 시점의 판정일 뿐이라, 이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다(제어 상태를 말없이 바꾸지 않는다). 다만 헤더의 전체 해제로는 걷어낼 수 있다. |
@@ -40,6 +42,10 @@
40
42
  | `virtualBuffer?` | `number` | `5` | |
41
43
  | `virtualEndThreshold?` | `number` | `10` | |
42
44
  | `tableId?` | `string` | — | 테이블 식별자 (없으면 자동 생성) |
45
+ | `renderExpansion?` | `(row: TRow) => ReactNode` | — | 행마다 펼치는 **상세 영역**. 주면 펼침 컬럼(`expansionColumn`)에 펼침 버튼이 붙고, 펼친 행 바로 아래에 돌려준 내용이 표 전체 폭으로 그려진다. 여러 행을 함께 펼칠 수 있다. "한 행 = 한 건, 펼치면 그 건의 상세" 인 표에 쓴다. 행이 부모-자식 계층을 이루면 `STreeTable` 이다. - 상세 영역은 **행이 아니다** — 선택·체크박스·잠금 표시·행 클릭·hover 와 무관하고, 행 수에 세지 않는다. - 체크박스 열까지 덮는 한 칸이고, 가로 스크롤이 있어도 보이는 폭에 머문다. 줄바꿈을 허용한다. - 행 높이를 늘리므로 `renderCell` 의 `rowSpan` 세로 병합과 함께 쓰지 않는다. - 가상 스크롤에서는 상세 영역까지 그 행의 높이로 잰다. - 펼침·접힘 모션은 컴포넌트가 넣는다 — 상세가 행 아래에서 서랍처럼 열리고 아래 행들이 함께 움직인다 (`STreeTable` 과 같다). 모션을 줄인 환경에서는 꺼진다. `onExpandedChange` 는 모션을 기다리지 않는다. |
46
+ | `expansionColumn?` | `string` | — | 펼침 버튼이 붙는 컬럼의 `name`. 주지 않으면 보이는 첫 컬럼이다. **이 셀 전체가 펼침 영역**이다 — 어디를 눌러도 접고 펴고, 행 클릭(`onRowClick`)으로는 가지 않는다. 셀 안에 넣은 링크·버튼·입력은 자기 동작만 한다. 이 컬럼에는 `renderCell` 을 쓰지 않는다 — 셀 내용은 `render` · `format` 으로 바꾼다 (`STreeTable` 의 `treeColumn` 과 같다). |
47
+ | `expandedKeys?` | `STableKey[]` | — | 펼친 행의 키 목록 (controlled) |
48
+ | `defaultExpandedKeys?` | `STableKey[]` | — | 비제어 펼침 초기값. 주지 않으면 모두 접힌 채로 시작한다 |
43
49
  | `className?` | `string` | — | |
44
50
  | `style?` | `CSSProperties` | — | |
45
51
 
@@ -56,8 +62,9 @@
56
62
  | `onScroll` | `(event: ReactUIEvent<HTMLDivElement>) => void` | 본문이 스크롤될 때. **실제 스크롤에만 울린다** — 행 수·컬럼 폭·로딩이 바뀌어 내부에서 위치를 다시 잴 때는 오지 않는다. `onVirtualUpdate` 는 그리는 창이 바뀔 때만 오므로 그 사이 스크롤을 알 수 없다. 스크롤 위치를 읽어 두었다 되돌리는 것처럼 **위치 자체가 필요하면** 이쪽을 쓴다. |
57
63
  | `onVirtualUpdate` | `(range: { from: number; to: number }) => void` | |
58
64
  | `onVirtualReachEnd` | `(info: { from: number; to: number; rowCount: number; threshold: number; }) =>…` | |
59
- | `onRowClick` | `(row: TRow) => void` | 행 클릭 |
65
+ | `onRowClick` | `(row: TRow) => void` | 행 클릭. 선택 체크박스 열과 셀 안의 버튼·링크·입력을 누른 것은 행 클릭이 아니다 — 그 컨트롤의 동작만 일어난다. 셀에 버튼을 두고 `stopPropagation` 을 직접 넣지 않는다. |
60
66
  | `onRowDoubleClick` | `(row: TRow) => void` | 행 더블클릭. 목록에서 상세를 여는 것처럼 **한 번 누르는 것과 다른 일**을 걸 때 쓴다. 더블클릭도 클릭 두 번이라 `onRowClick` 과 함께 주면 **`onRowClick` 이 두 번 먼저 울린다** — 둘을 함께 쓰려면 `onRowClick` 은 두 번 울려도 결과가 같은 일(행 강조 등)이어야 한다. 선택 체크박스 열과 셀 안의 버튼·링크·입력 위에서 한 더블클릭은 행 더블클릭이 아니다 — 체크박스를 빠르게 두 번 누르거나 입력의 글자를 더블클릭으로 고르다 상세가 열리지 않게 한다. |
67
+ | `onExpandedChange` | `(expandedKeys: STableKey[], row: TRow) => void` | 펼침 변경. 바뀐 뒤의 키 목록과 누른 행이 온다 |
61
68
 
62
69
  #### Methods (ref)
63
70
 
@@ -162,6 +169,13 @@ export interface STablePagination {
162
169
  }
163
170
  ```
164
171
 
172
+ ### STableKey
173
+
174
+ ```ts
175
+ /** 행 식별 값 — `rowKey` 필드에 든 값이다 */
176
+ export type STableKey = string | number;
177
+ ```
178
+
165
179
  ### STableColumnField
166
180
 
167
181
  ```ts
@@ -9,6 +9,8 @@ import { type SSelectOption } from '../SSelect';
9
9
  * 예전처럼 동작한다 — 기본값을 `unknown` 으로 바꾸면 `row.statusLabel` 같은 코드가 전부 깨진다.
10
10
  */
11
11
  export type SRow = Record<string, any>;
12
+ /** 행 식별 값 — `rowKey` 필드에 든 값이다 */
13
+ export type STableKey = string | number;
12
14
  /**
13
15
  * 셀 값(`format`·`render` 가 받는 값)의 타입.
14
16
  *
@@ -250,6 +252,15 @@ export interface STableProps<TRow extends SRow = SRow> {
250
252
  rowKey?: string;
251
253
  /** 행 선택 체크박스 */
252
254
  selectable?: boolean;
255
+ /**
256
+ * 행 선택 체크박스의 접근성 이름 — 화면 낭독기가 어느 행의 체크박스인지 읽게 한다.
257
+ * 그 행을 가리키는 글자(주문번호·상품명 등)로 만든다: `row => \`${row.orderNo} 선택\``.
258
+ *
259
+ * 주지 않으면 모든 행이 `행 선택` 으로 읽혀 행끼리 구분되지 않는다.
260
+ */
261
+ rowSelectLabel?: (row: TRow) => string;
262
+ /** 헤더 전체 선택 체크박스의 접근성 이름 */
263
+ selectAllLabel?: string;
253
264
  /**
254
265
  * 행을 드래그해서 고른다. 기본 `false`.
255
266
  *
@@ -421,7 +432,12 @@ export interface STableProps<TRow extends SRow = SRow> {
421
432
  }) => void;
422
433
  /** 테이블 식별자 (없으면 자동 생성) */
423
434
  tableId?: string;
424
- /** 행 클릭 */
435
+ /**
436
+ * 행 클릭.
437
+ *
438
+ * 선택 체크박스 열과 셀 안의 버튼·링크·입력을 누른 것은 행 클릭이 아니다 — 그 컨트롤의 동작만
439
+ * 일어난다. 셀에 버튼을 두고 `stopPropagation` 을 직접 넣지 않는다.
440
+ */
425
441
  onRowClick?: (row: TRow) => void;
426
442
  /**
427
443
  * 행 더블클릭. 목록에서 상세를 여는 것처럼 **한 번 누르는 것과 다른 일**을 걸 때 쓴다.
@@ -433,6 +449,34 @@ export interface STableProps<TRow extends SRow = SRow> {
433
449
  * 체크박스를 빠르게 두 번 누르거나 입력의 글자를 더블클릭으로 고르다 상세가 열리지 않게 한다.
434
450
  */
435
451
  onRowDoubleClick?: (row: TRow) => void;
452
+ /**
453
+ * 행마다 펼치는 **상세 영역**. 주면 펼침 컬럼(`expansionColumn`)에 펼침 버튼이 붙고, 펼친 행 바로 아래에
454
+ * 돌려준 내용이 표 전체 폭으로 그려진다. 여러 행을 함께 펼칠 수 있다.
455
+ *
456
+ * "한 행 = 한 건, 펼치면 그 건의 상세" 인 표에 쓴다. 행이 부모-자식 계층을 이루면 `STreeTable` 이다.
457
+ *
458
+ * - 상세 영역은 **행이 아니다** — 선택·체크박스·잠금 표시·행 클릭·hover 와 무관하고, 행 수에 세지 않는다.
459
+ * - 체크박스 열까지 덮는 한 칸이고, 가로 스크롤이 있어도 보이는 폭에 머문다. 줄바꿈을 허용한다.
460
+ * - 행 높이를 늘리므로 `renderCell` 의 `rowSpan` 세로 병합과 함께 쓰지 않는다.
461
+ * - 가상 스크롤에서는 상세 영역까지 그 행의 높이로 잰다.
462
+ * - 펼침·접힘 모션은 컴포넌트가 넣는다 — 상세가 행 아래에서 서랍처럼 열리고 아래 행들이 함께 움직인다
463
+ * (`STreeTable` 과 같다). 모션을 줄인 환경에서는 꺼진다. `onExpandedChange` 는 모션을 기다리지 않는다.
464
+ */
465
+ renderExpansion?: (row: TRow) => ReactNode;
466
+ /**
467
+ * 펼침 버튼이 붙는 컬럼의 `name`. 주지 않으면 보이는 첫 컬럼이다.
468
+ *
469
+ * **이 셀 전체가 펼침 영역**이다 — 어디를 눌러도 접고 펴고, 행 클릭(`onRowClick`)으로는 가지 않는다.
470
+ * 셀 안에 넣은 링크·버튼·입력은 자기 동작만 한다. 이 컬럼에는 `renderCell` 을 쓰지 않는다 — 셀 내용은
471
+ * `render` · `format` 으로 바꾼다 (`STreeTable` 의 `treeColumn` 과 같다).
472
+ */
473
+ expansionColumn?: string;
474
+ /** 펼친 행의 키 목록 (controlled) */
475
+ expandedKeys?: STableKey[];
476
+ /** 비제어 펼침 초기값. 주지 않으면 모두 접힌 채로 시작한다 */
477
+ defaultExpandedKeys?: STableKey[];
478
+ /** 펼침 변경. 바뀐 뒤의 키 목록과 누른 행이 온다 */
479
+ onExpandedChange?: (expandedKeys: STableKey[], row: TRow) => void;
436
480
  className?: string;
437
481
  style?: CSSProperties;
438
482
  }
@@ -1 +1 @@
1
- export { STable, type STableProps, type STableColumn, type STableColumnField, type STableSort, type STableHeaderGroup, type STableCellContext, type STableCellValue, type STablePagination, type STableStickyColumn, type STableHandle, type SRow, } from './STable';
1
+ export { STable, type STableProps, type STableColumn, type STableColumnField, type STableSort, type STableHeaderGroup, type STableCellContext, type STableCellValue, type STablePagination, type STableStickyColumn, type STableHandle, type STableKey, type SRow, } from './STable';
@@ -9,7 +9,7 @@ export type STreeTableKey = string | number;
9
9
  * - 드래그 선택·오른쪽 클릭 메뉴: 펼친 순서의 구간은 계층과 어긋난다 — 접힌 자식은 구간에 들지 않는다.
10
10
  * - `isRowIndeterminate`: 일부 선택 표시는 STreeTable 이 계층에서 계산한다.
11
11
  */
12
- type OmittedTableProps = 'rows' | 'selected' | 'onSelectedChange' | 'isRowIndeterminate' | 'pagination' | 'onPageChange' | 'internalPagination' | 'showRowsPerPageSelect' | 'rowsPerPageOption' | 'onRowsPerPageChange' | 'dragSelectable' | 'contextMenuItems' | 'onContextMenuItemClick';
12
+ type OmittedTableProps = 'rows' | 'selected' | 'onSelectedChange' | 'isRowIndeterminate' | 'pagination' | 'onPageChange' | 'internalPagination' | 'showRowsPerPageSelect' | 'rowsPerPageOption' | 'onRowsPerPageChange' | 'dragSelectable' | 'contextMenuItems' | 'onContextMenuItemClick' | 'renderExpansion' | 'expansionColumn' | 'expandedKeys' | 'defaultExpandedKeys' | 'onExpandedChange';
13
13
  export interface STreeTableProps<TRow extends SRow = SRow> extends Omit<STableProps<TRow>, OmittedTableProps> {
14
14
  /**
15
15
  * 최상위 행. 하위 행은 각 행의 `childrenKey` 필드에 같은 모양으로 넣는다.