sellmate-design-system-react 9.0.0-beta.17 → 9.0.0-beta.18

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
@@ -865,6 +865,7 @@ const columns: STableColumn[] = [
865
865
  - **값과 헤더를 따로 계산해 큰 쪽을 쓴다.** `contentType: 'control'` 은 `<td>` 에만 적용되고 `<th>` 는 항상 텍스트 패딩이라, 짧은 컨트롤 + 긴 헤더 조합에서 헤더가 잘린다.
866
866
  - 값에 따라 폭이 달라지는 요소(`STag` 라벨, 라벨 있는 버튼)는 **가장 긴 값** 기준으로 잡는다. `판매중` 에 맞춰두면 `판매중지 요청` 에서 잘린다.
867
867
  - **정렬 가능한 헤더(`sortable`)는 아이콘 버튼 + 간격만큼 `+20px` 더 든다.** `helpText` 를 함께 달면 그만큼 또 더한다.
868
+ - **`editable` · `navigable` 표식과 `required` 표시(`*`) 도 각각 폭을 먹는다.** 헤더에 붙는 것이 늘수록 **라벨이 먼저 잘리므로**, 붙인 열은 폭을 함께 넓힌다.
868
869
  - **계산값은 픽셀 단위까지 맞추면 어긋난다** — 서브픽셀 반올림 때문이다. 여유 8px 을 얹고 8 단위로 올림한다.
869
870
  - 좌우 패딩은 `STable` 이 토큰으로 넣으므로 직접 주지 않는다. 그만큼을 뺀 나머지가 요소 몫이라는 점만 계산에 넣는다.
870
871
 
@@ -973,6 +974,33 @@ const columns: STableColumn[] = [
973
974
  - 긴 문장을 넣는 자리가 아니다. 한 줄에 한 가지 사실만 담고, 그 이상은 페이지 상단 안내(`SCallout`)로 뺀다.
974
975
  - 아이콘도 폭을 먹는다 — 헤더 폭 계산에 넣는다.
975
976
 
977
+ #### 헤더에 붙는 것들의 순서 · 열을 어떻게 다루는지 알리는 표식
978
+
979
+ 헤더 라벨 뒤에 붙는 것은 네 가지고, **순서는 `STable` 이 고정한다.** 소비 앱이 바꾸는 것이 아니다.
980
+
981
+ ```text
982
+ 라벨 [helpText ?] [editable] [navigable] [required *] [sortable 정렬버튼]
983
+ ```
984
+
985
+ **`editable` 은 값을 직접 고칠 수 있는 열, `navigable` 은 눌러서 다른 화면으로 넘어가는 열에 준다.** 둘 다 **표식일 뿐 버튼이 아니다** — 아이콘·크기·색은 컴포넌트가 고정하고 클릭은 받지 않는다.
986
+
987
+ ```tsx
988
+ const columns: STableColumn[] = [
989
+ { name: 'name', label: '상품명', field: 'name', width: 200,
990
+ navigable: true,
991
+ render: (row: SRow) => <STextLink label={row.name} onClick={() => goDetail(row.id)} /> },
992
+ { name: 'stock', label: '재고', field: 'stock', width: 160, contentType: 'control',
993
+ editable: true, required: true,
994
+ render: (row: SRow) => <SNumberInput value={row.stock} width="100%" /> },
995
+ ];
996
+ ```
997
+
998
+ - **실제로 그렇게 동작하는 열에만 켠다.** 표식만 켜고 셀은 텍스트 그대로 두면, 고칠 수 있다고 해 놓고 고칠 방법이 없고 넘어갈 수 있다고 해 놓고 누를 것이 없다. `editable` 이면 셀에 입력 컨트롤이, `navigable` 이면 셀에 링크·클릭이 있어야 한다.
999
+ - **표식으로 동작을 대신하지 않는다.** 고치는 UI 도 넘어가는 동작도 셀(`column.render`) 몫이다.
1000
+ - **`renderHeader` 로 헤더를 통째로 교체하면 표식도 `required` 도 그려지지 않는다** — 헤더 전체가 소비 앱 책임이 되므로 순서·크기·색을 손으로 맞추게 된다.
1001
+
1002
+ **값을 반드시 채워야 하는 열에는 `column.required`** 를 준다 — 라벨 뒤에 `*` 가 붙는다. `SKeyValueTable` 의 `field.required` 와 같은 것이다. **읽기 전용 열에 붙이지 않는다** — 표시만 있고 채울 방법이 없어 사용자가 막힌다.
1003
+
976
1004
  ### 3-5. 버튼류
977
1005
 
978
1006
  | 상황 | 사용 |
@@ -1222,6 +1250,22 @@ const columns: STableColumn[] = [
1222
1250
 
1223
1251
  `SDateRangePicker` 가 한 등급씩 위인 것은 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 로 두 배가 넘기 때문이다. 같은 이유로 `STimeRangePicker` 의 오전/오후 모드도 sm 에서 한 등급 위를 쓴다 — 그 모드의 최소 폭이 md 등급을 이미 넘어, 그대로 두면 하한이 상한을 넘어 상한이 무력해진다.
1224
1252
 
1253
+ `SDatePicker` 만 이 상한을 `maxWidth` 로 덮을 수 있다 — 등급을 주면 그 등급이 상한이 되고, `width="100%" maxWidth="100%"` 면 부모 폭을 그대로 채운다. **폭이 이미 좁게 정해진 자리(팝오버·좁은 카드)에서만 쓴다.** 폼·표 행에서는 쓰지 않는다 — 거기서 상한을 풀면 4~10글자짜리 값이 행 전체를 차지한다.
1254
+
1255
+ ##### 값을 지울 수 있게 하려면 `clearable`
1256
+
1257
+ `SSelect` · `SDatePicker` · `SDateRangePicker` · `STimePicker` · `STimeRangePicker` 가 같은 규칙으로 갖는다. 값이 있을 때만 지우기 버튼이 나타나고, 누르면 **`onValueChange` 로 `null` 이 온다** (빈 문자열이 아니다). 받는 쪽 상태도 `null` 을 담을 수 있어야 한다.
1258
+
1259
+ ```tsx
1260
+ const [from, setFrom] = useState<string | null>(null);
1261
+
1262
+ <SDatePicker label="시작일" clearable value={from} onValueChange={setFrom} />;
1263
+ ```
1264
+
1265
+ - **조회 조건(필터)에는 켠다.** 한 번 고른 날짜를 되돌릴 방법이 없으면 전체 조회로 돌아가려고 새로고침하게 된다.
1266
+ - **필수 입력 필드에는 켜지 않는다.** 지우면 다시 고르기 전까지 폼이 통과하지 못한다 — 지울 수 있어야 하는 값이면 애초에 필수가 아니다.
1267
+ - `disabled` 이면 지우기 버튼도 함께 사라진다. 끈 필드를 지울 수 있으면 안 되기 때문이다.
1268
+
1225
1269
  #### 3-7-5. SField 를 직접 쓰는 경우
1226
1270
 
1227
1271
  **거의 없다.** `SInput`·`SNumberInput`·`STextarea`·`SSelect`·날짜/시간 피커는 이미 내부에서 `SField` 를 쓰고 있어서 `label`·`required`·에러 표시를 자기 prop 으로 받는다. 그 위에 `SField` 를 한 겹 더 감싸면 라벨이 두 번 나온다.
@@ -1584,6 +1628,29 @@ export default function ProductListPage() {
1584
1628
  }
1585
1629
  ```
1586
1630
 
1631
+ #### 한 화면에 더 많은 행을 — `dense` 와 밀도 토글
1632
+
1633
+ 행 높이를 줄이는 것은 `dense` 다. 세로 여백만 줄고 좌우 패딩은 그대로라, 값이 잘리지 않으면서 한 화면에 들어가는 행 수가 늘어난다.
1634
+
1635
+ **어느 쪽이 편한지는 화면이 아니라 사용자가 안다.** 그래서 목록 페이지는 밀도를 고정하지 말고 `useDensityToggle` 로 고를 수 있게 둔다 — 페이지네이션 바 우측(rows per page 셀렉트 왼쪽)에 `좁게 보기` · `넓게 보기` 링크가 붙는다.
1636
+
1637
+ ```tsx
1638
+ // 사용자가 고른 밀도는 다음 방문에도 남는 것이 자연스럽다 — 저장은 페이지 몫이다
1639
+ const [dense, setDense] = useState(() => loadPref('list.dense', true));
1640
+
1641
+ <STable
1642
+ dense={dense}
1643
+ onDenseChange={next => { setDense(next); savePref('list.dense', next); }}
1644
+ useDensityToggle
1645
+ useRowsPerPageSelect
1646
+ pagination={{ currentPage, lastPage }}
1647
+ />;
1648
+ ```
1649
+
1650
+ - **밀도는 `STable` 이 갖지 않는다.** `dense` 가 곧 현재 상태이고, `onDenseChange` 없이 `useDensityToggle` 만 켜면 눌러도 아무 일도 일어나지 않는다.
1651
+ - **토글은 페이지네이션이 있을 때만 나타난다** — 사는 곳이 그 바이기 때문이다. 페이지네이션 없는 표에서 밀도를 고르게 하려면 `STableBar` 쪽에 직접 둔다.
1652
+ - 라벨과 아이콘은 현재 상태가 아니라 **누르면 되는 상태**를 가리킨다. `dense` 면 `넓게 보기` 다.
1653
+
1587
1654
  ### 4-3. 폼 페이지 (등록/수정)
1588
1655
 
1589
1656
  구조: **페이지 제목(`AppShell` 의 `header` prop) → `SForm` + `SKeyValueTable` → 하단 버튼**
@@ -1790,6 +1857,7 @@ export default function ProductDetailPage() {
1790
1857
  - [ ] 필터·폼·상세 정보를 `SKeyValueTable` 로 만들었는가 (컨트롤을 `div` 로 나열하지 않았는가). 필터를 `SChipFilter` 로 했다면 §3-7-11 의 판정을 거쳤는가
1791
1858
  - [ ] 섹션 구분에 `SSectionHeaderCard` 를 썼는가 (직접 만든 카드가 아니라)
1792
1859
  - [ ] 목록의 주요 액션이 `STableBar` 의 `rightActions` 에 있는가 (페이지 제목 줄이 아니라)
1860
+ - [ ] 목록 페이지 표에 `useDensityToggle` 로 밀도를 고를 수 있게 뒀는가, `onDenseChange` 를 함께 줬는가 (§4-2 — 핸들러 없이 켜면 눌러도 아무 일도 없다)
1793
1861
  - [ ] 상태 표시에 `STag size="sm"` 을 썼는가
1794
1862
  - [ ] 테이블에서 양을 나타내는 컬럼(금액·수량·개수 등)이 전부 `align: 'right'` 인가
1795
1863
  - [ ] 번호·코드·전화번호·일자 컬럼에 `align: 'center'` 를 **명시**했는가 (생략하면 좌측이 된다)
@@ -1797,9 +1865,11 @@ export default function ProductDetailPage() {
1797
1865
  - [ ] **모든 컬럼에 폭을 명시**했는가, px 로만 줬는가 (`%`·`clamp()` ❌), `autoWidth` 는 스페이서 열 하나뿐인가 (§3-4)
1798
1866
  - [ ] 컨트롤(`STag`·`SButton`·`SSelect`·`SInput` …)이 들어가는 컬럼이 `resizable` 이면 `minWidth` 도 줬는가 (§3-4 — 폭이 모자라면 요소가 잘려 못 쓴다)
1799
1867
  - [ ] 정렬 가능한 열에 `sortable` 을 줬는가 (`renderHeader` 로 직접 만들지 않았는가), 정렬 상태를 페이지가 들고 있는가 (§3-4)
1868
+ - [ ] `editable` · `navigable` 표식을 켠 열이 **셀에서도 실제로 그렇게 동작하는가** (입력 컨트롤 · 링크가 있는가), 표식을 붙인 열의 폭을 함께 넓혔는가 (§3-4)
1800
1869
  - [ ] 금액·수량 등 양을 나타내는 숫자에 빠짐없이 `toLocaleString()` 을 썼는가 (번호·코드는 제외)
1801
1870
  - [ ] 날짜를 `YYYY-MM-DD` 로 표기했는가 (`toLocaleDateString()` ❌, §1-4)
1802
1871
  - [ ] 필드 폭을 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`)으로 줬는가 — px 직접 지정 ❌ (§4-3)
1872
+ - [ ] 조회 조건으로 쓰는 셀렉트·날짜·시간 피커에 `clearable` 을 줬는가, 그 상태가 `null` 을 담을 수 있는가 (§3-7-4 — 필수 입력 필드에는 켜지 않는다)
1803
1873
  - [ ] `SKeyValueTable` 의 짧은 행에 `tdColSpan` 을 주어 전체 열 수를 채웠는가 (§4-3 — 안 채우면 그 구간의 행 구분선이 끊긴다)
1804
1874
  - [ ] 하단 버튼이 양끝 분리(`justify-between`)이고, 되돌리기가 왼쪽 끝 · 실행이 오른쪽 끝인가
1805
1875
  - [ ] 페이지에 `color="primary"` 채움 버튼이 **1개뿐**인가 (`danger` 채움도 1개, `SDropdownButton` 포함)
@@ -24,7 +24,7 @@
24
24
  |-------|------|-------------|
25
25
  | `onValueChange` | `(value: SChipFilterValueMap) => void` | 전체 값 변경 — 편집 중인 값이 바뀔 때마다(선택할 때마다) 호출된다. 실제 검색 실행은 onSearch를 쓴다 |
26
26
  | `onFilterChange` | `(detail: SChipFilterChangeDetail) => void` | 개별 필터 값 변경 |
27
- | `onSearch` | `(value: SChipFilterValueMap) => void` | 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom, clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. dirty 체크가 기본 적용되어 있어 — 마지막으로 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고 닫는 경우) 호출되지 않는다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔 onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음) |
27
+ | `onSearch` | `(value: SChipFilterValueMap) => void` | 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom, clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. keyword 필터에서 Enter 로 키워드를 추가할 때도 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다. dirty 체크가 기본 적용되어 있어 — 마지막으로 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고 닫는 경우) 호출되지 않는다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔 onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음) |
28
28
  | `onActiveKeysChange` | `(keys: string[]) => void` | 노출 필터 key 변경(칩 추가·제거) — 비제어 방식에서도 참고용으로 호출된다. activeKeys를 직접 제어할 때는 이 값을 그대로 activeKeys에 반영해야 한다 |
29
29
  | `onReset` | `() => void` | "검색 초기화" 클릭 — 모든 필드가 기본값(또는 null)으로 리셋된 뒤 호출된다 |
30
30
  | `onAddFilter` | `(key: string) => void` | "필터 추가" 목록에서 항목을 골랐을 때 — activeKeys를 직접 제어 중이면 이 콜백에서 (또는 onActiveKeysChange에서) key를 activeKeys에 추가해줘야 칩이 실제로 나타난다. activeKeys를 넘기지 않았다면(비제어) 별도 처리 없이도 컴포넌트가 알아서 칩을 노출한다 |
@@ -138,6 +138,11 @@ export interface SChipFilterKeywordField extends SChipFilterOptionsField {
138
138
  type: 'keyword';
139
139
  /** 입력 placeholder */
140
140
  placeholder?: string;
141
+ /** 입력 방식. 기본 'tag' — Enter 로 하나씩 추가한다.
142
+ * 'csv' 는 쉼표도 구분자로 인정해, 쉼표를 치거나 쉼표가 섞인 텍스트를 붙여넣으면 그 자리에서
143
+ * 여러 개로 쪼개져 목록에 쌓인다(엑셀에서 복사한 코드 목록을 한 번에 넣는 용도).
144
+ * 쌓이는 목록도 값 형태도 'tag' 와 같다 — 구분자만 늘어난다. */
145
+ input?: SChipFilterKeywordInput;
141
146
  /** 검색조건(포함/일치) 토글 표시 여부 */
142
147
  matchModes?: boolean;
143
148
  /** matchModes 활성 시 "미포함"까지 포함해 3개(포함/일치/미포함)로 노출할지.
@@ -180,6 +185,7 @@ export interface SChipFilterCustomField extends SChipFilterFieldBase {
180
185
  render?: (ctx: {
181
186
  value: SChipFilterValue;
182
187
  disabled?: boolean;
188
+ /** 속한 그룹이 rule을 위반하는 동안 true — 직접 그린 노드에도 경고 표시를 맞추라는 신호다 */
183
189
  warning?: boolean;
184
190
  onValueChange: (value: SChipFilterValue) => void;
185
191
  }) => ReactNode;
@@ -229,6 +235,13 @@ export interface SChipFilterOptionsField extends SChipFilterFieldBase {
229
235
  }
230
236
  ```
231
237
 
238
+ ### SChipFilterKeywordInput
239
+
240
+ ```ts
241
+ /** keyword 필드의 입력 방식 — 무엇을 키워드 하나의 끝으로 볼지 */
242
+ export type SChipFilterKeywordInput = 'tag' | 'csv';
243
+ ```
244
+
232
245
  ### SChipFilterPresetsField
233
246
 
234
247
  ```ts
@@ -264,8 +277,6 @@ export interface SChipFilterFieldBase {
264
277
  * 마운트(또는 "필터 추가"로 활성화) 시 이 값이 자동으로 채워진다. required인데 지정하지 않으면
265
278
  * 타입별 내장 기본값(single: 첫 번째 옵션, date: 오늘 날짜)을 대신 쓴다 */
266
279
  defaultValue?: SChipFilterValue;
267
- /** 경고 테두리 표시. 바 전체를 잠그는 SChipFilterProps.disabled와 달리 이 필터에만 적용된다 */
268
- warning?: boolean;
269
280
  /** 이 필터만 비활성. 바에 남아 있되 팝오버가 열리지 않고 clearable도 눌리지 않는다.
270
281
  * 바 전체를 잠그려면 SChipFilterProps.disabled를 쓴다 — 둘은 OR로 합쳐진다 */
271
282
  disabled?: boolean;
@@ -54,8 +54,6 @@ export interface SChipFilterFieldBase {
54
54
  * 마운트(또는 "필터 추가"로 활성화) 시 이 값이 자동으로 채워진다. required인데 지정하지 않으면
55
55
  * 타입별 내장 기본값(single: 첫 번째 옵션, date: 오늘 날짜)을 대신 쓴다 */
56
56
  defaultValue?: SChipFilterValue;
57
- /** 경고 테두리 표시. 바 전체를 잠그는 SChipFilterProps.disabled와 달리 이 필터에만 적용된다 */
58
- warning?: boolean;
59
57
  /** 이 필터만 비활성. 바에 남아 있되 팝오버가 열리지 않고 clearable도 눌리지 않는다.
60
58
  * 바 전체를 잠그려면 SChipFilterProps.disabled를 쓴다 — 둘은 OR로 합쳐진다 */
61
59
  disabled?: boolean;
@@ -83,11 +81,18 @@ export interface SChipFilterSingleField extends SChipFilterOptionsField {
83
81
  export interface SChipFilterMultiField extends SChipFilterOptionsField {
84
82
  type: 'multi';
85
83
  }
84
+ /** keyword 필드의 입력 방식 — 무엇을 키워드 하나의 끝으로 볼지 */
85
+ export type SChipFilterKeywordInput = 'tag' | 'csv';
86
86
  /** 키워드를 입력해 누적한다. options는 입력 중 후보로만 뜬다 */
87
87
  export interface SChipFilterKeywordField extends SChipFilterOptionsField {
88
88
  type: 'keyword';
89
89
  /** 입력 placeholder */
90
90
  placeholder?: string;
91
+ /** 입력 방식. 기본 'tag' — Enter 로 하나씩 추가한다.
92
+ * 'csv' 는 쉼표도 구분자로 인정해, 쉼표를 치거나 쉼표가 섞인 텍스트를 붙여넣으면 그 자리에서
93
+ * 여러 개로 쪼개져 목록에 쌓인다(엑셀에서 복사한 코드 목록을 한 번에 넣는 용도).
94
+ * 쌓이는 목록도 값 형태도 'tag' 와 같다 — 구분자만 늘어난다. */
95
+ input?: SChipFilterKeywordInput;
91
96
  /** 검색조건(포함/일치) 토글 표시 여부 */
92
97
  matchModes?: boolean;
93
98
  /** matchModes 활성 시 "미포함"까지 포함해 3개(포함/일치/미포함)로 노출할지.
@@ -115,6 +120,7 @@ export interface SChipFilterCustomField extends SChipFilterFieldBase {
115
120
  render?: (ctx: {
116
121
  value: SChipFilterValue;
117
122
  disabled?: boolean;
123
+ /** 속한 그룹이 rule을 위반하는 동안 true — 직접 그린 노드에도 경고 표시를 맞추라는 신호다 */
118
124
  warning?: boolean;
119
125
  onValueChange: (value: SChipFilterValue) => void;
120
126
  }) => ReactNode;
@@ -173,7 +179,8 @@ export interface SChipFilterProps {
173
179
  onFilterChange?: (detail: SChipFilterChangeDetail) => void;
174
180
  /** 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른
175
181
  * 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom,
176
- * clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. dirty 체크가 기본 적용되어 있어 마지막으로
182
+ * clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. keyword 필터에서 Enter 키워드를 추가할 때도
183
+ * 그 즉시 호출된다 — 팝오버는 열린 채라 키워드를 이어서 더 넣을 수 있고, 넣을 때마다 조회가 갱신된다. dirty 체크가 기본 적용되어 있어 — 마지막으로
177
184
  * 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고
178
185
  * 닫는 경우) 호출되지 않는다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔
179
186
  * onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상
@@ -1 +1 @@
1
- export { SChipFilter, type SChipFilterChangeDetail, type SChipFilterCustomField, type SChipFilterDateField, type SChipFilterDatePreset, type SChipFilterField, type SChipFilterFieldBase, type SChipFilterGroup, type SChipFilterGroupRule, type SChipFilterHandle, type SChipFilterKeywordField, type SChipFilterKeywordValue, type SChipFilterMatchMode, type SChipFilterMultiField, type SChipFilterOption, type SChipFilterOptionValue, type SChipFilterOptionsField, type SChipFilterPeriodField, type SChipFilterPresetsField, type SChipFilterProps, type SChipFilterSingleField, type SChipFilterType, type SChipFilterValue, type SChipFilterValueMap, } from './SChipFilter';
1
+ export { SChipFilter, type SChipFilterChangeDetail, type SChipFilterCustomField, type SChipFilterDateField, type SChipFilterDatePreset, type SChipFilterField, type SChipFilterFieldBase, type SChipFilterGroup, type SChipFilterGroupRule, type SChipFilterHandle, type SChipFilterKeywordField, type SChipFilterKeywordInput, type SChipFilterKeywordValue, type SChipFilterMatchMode, type SChipFilterMultiField, type SChipFilterOption, type SChipFilterOptionValue, type SChipFilterOptionsField, type SChipFilterPeriodField, type SChipFilterPresetsField, type SChipFilterProps, type SChipFilterSingleField, type SChipFilterType, type SChipFilterValue, type SChipFilterValueMap, } from './SChipFilter';
@@ -33,7 +33,9 @@
33
33
  | `placeholder?` | `string` | — | |
34
34
  | `selectable?` | `[string, string]` | — | 선택 가능 범위 [시작, 종료] |
35
35
  | `disabled?` | `boolean` | `false` | |
36
+ | `clearable?` | `boolean` | `false` | 선택값 지우기 버튼. 값이 있을 때만 나타나고, 누르면 `onValueChange` 로 `null` 이 온다. **필수 입력 필드에는 켜지 않는다** — 지우면 다시 고르기 전까지 폼이 통과하지 못한다. `STimePicker` · `STimeRangePicker` · `SSelect` 의 `clearable` 과 같은 규칙이다. |
36
37
  | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
38
+ | `maxWidth?` | `SFieldWidth` | — | 컨트롤 최대 너비 — 폭 등급 · 숫자=px · CSS 길이. 지정하지 않으면 값 길이(`YYYY-MM-DD`)에 맞춘 내장 상한(`size='md'` → `lg`, `'sm'` → `md`)이 걸려, `width="100%"` 를 받아도 행 전체로 늘어나지 않는다. 팝오버처럼 폭이 이미 좁게 정해진 자리에서 그 폭을 그대로 채워야 하면 `width="100%"` 와 함께 `maxWidth="100%"` 를 준다. `0` 은 상한을 아예 걸지 않는다 — 부모보다 넓어지는 것까지 허용해야 할 때만 쓴다. |
37
39
  | `name?` | `string` | — | |
38
40
  | `rules?` | `Rule[]` | — | |
39
41
  | `status?` | `SFieldStatus` | — | |
@@ -55,7 +57,7 @@
55
57
 
56
58
  | Event | Type | Description |
57
59
  |-------|------|-------------|
58
- | `onValueChange` | `(value: string) => void` | 선택 변경 (sdUpdate) |
60
+ | `onValueChange` | `(value: string \| null) => void` | 선택 변경 (sdUpdate). `clearable` 로 지우면 `null` 이 온다 |
59
61
  | `onViewChange` | `(view: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
60
62
 
61
63
  ### SDatePickerYearListbox
@@ -101,6 +103,7 @@ export type SDatePickerSize = SFieldSize;
101
103
 
102
104
  - [SCalendar](../SCalendar)
103
105
  - [SField](../SField)
106
+ - [SGhostButton](../SGhostButton)
104
107
  - [SIcon](../SIcon)
105
108
 
106
109
  ### Graph
@@ -109,6 +112,7 @@ export type SDatePickerSize = SFieldSize;
109
112
  graph TD;
110
113
  SDatePicker --> SCalendar
111
114
  SDatePicker --> SField
115
+ SDatePicker --> SGhostButton
112
116
  SDatePicker --> SIcon
113
117
  SChipFilter --> SDatePicker
114
118
  SKeyValueTable --> SDatePicker
@@ -10,8 +10,8 @@ export type SDatePickerMode = 'date' | 'month' | 'year';
10
10
  export interface SDatePickerProps {
11
11
  /** 선택 값 (date: YYYY-MM-DD, month: YYYY-MM, year: YYYY) */
12
12
  value?: string | null;
13
- /** 선택 변경 (sdUpdate) */
14
- onValueChange?: (value: string) => void;
13
+ /** 선택 변경 (sdUpdate). `clearable` 로 지우면 `null` 이 온다 */
14
+ onValueChange?: (value: string | null) => void;
15
15
  /** 보이는 연·월 변경 (sdViewChange) */
16
16
  onViewChange?: (view: {
17
17
  year: number;
@@ -24,12 +24,29 @@ export interface SDatePickerProps {
24
24
  /** 선택 가능 범위 [시작, 종료] */
25
25
  selectable?: [string, string];
26
26
  disabled?: boolean;
27
+ /**
28
+ * 선택값 지우기 버튼. 값이 있을 때만 나타나고, 누르면 `onValueChange` 로 `null` 이 온다.
29
+ *
30
+ * **필수 입력 필드에는 켜지 않는다** — 지우면 다시 고르기 전까지 폼이 통과하지 못한다.
31
+ * `STimePicker` · `STimeRangePicker` · `SSelect` 의 `clearable` 과 같은 규칙이다.
32
+ */
33
+ clearable?: boolean;
27
34
  /**
28
35
  * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
29
36
  * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
30
37
  * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
31
38
  */
32
39
  width?: SFieldWidth;
40
+ /**
41
+ * 컨트롤 최대 너비 — 폭 등급 · 숫자=px · CSS 길이. 지정하지 않으면 값 길이(`YYYY-MM-DD`)에 맞춘
42
+ * 내장 상한(`size='md'` → `lg`, `'sm'` → `md`)이 걸려, `width="100%"` 를 받아도 행 전체로
43
+ * 늘어나지 않는다.
44
+ *
45
+ * 팝오버처럼 폭이 이미 좁게 정해진 자리에서 그 폭을 그대로 채워야 하면 `width="100%"` 와 함께
46
+ * `maxWidth="100%"` 를 준다. `0` 은 상한을 아예 걸지 않는다 — 부모보다 넓어지는 것까지 허용해야
47
+ * 할 때만 쓴다.
48
+ */
49
+ maxWidth?: SFieldWidth;
33
50
  name?: string;
34
51
  rules?: Rule[];
35
52
  status?: SFieldStatus;
@@ -78,4 +95,4 @@ export interface SDatePickerMonthListboxProps {
78
95
  * 자체 트리거/팝오버 없이 연도+월 리스트만 필요한 커스텀 조합에 재사용한다. */
79
96
  export declare function SDatePickerMonthListbox({ value, anchorYear, selectable, onValueChange, onMonthSelect, className, style, }: SDatePickerMonthListboxProps): import("react").JSX.Element;
80
97
  /** SDatePicker — sd-date-picker 포팅. SField + SPopover(SCalendar). */
81
- export declare function SDatePicker({ value, onValueChange, onViewChange, mode, size, placeholder, selectable, disabled, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDatePickerProps): import("react").JSX.Element;
98
+ export declare function SDatePicker({ value, onValueChange, onViewChange, mode, size, placeholder, selectable, disabled, clearable, width, maxWidth, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDatePickerProps): import("react").JSX.Element;
@@ -15,6 +15,7 @@
15
15
  | `maxRange?` | `number` | — | 최대 선택 일수 |
16
16
  | `useTimePicker?` | `boolean` | `false` | 시간(시:분) 선택 푸터 사용 여부. true이면 value는 "YYYY-MM-DD HH:mm" 형식이 되고, 캘린더 하단에 시작·종료 시간 입력이 표시된다. |
17
17
  | `disabled?` | `boolean` | `false` | |
18
+ | `clearable?` | `boolean` | `false` | 선택값 지우기 버튼. 값이 있을 때만 나타나고, 누르면 `onValueChange` 로 `null` 이 온다. **필수 입력 필드에는 켜지 않는다** — 지우면 다시 고르기 전까지 폼이 통과하지 못한다. `SDatePicker` · `STimePicker` · `SSelect` 의 `clearable` 과 같은 규칙이다. |
18
19
  | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
19
20
  | `name?` | `string` | — | |
20
21
  | `rules?` | `Rule[]` | — | |
@@ -37,7 +38,7 @@
37
38
 
38
39
  | Event | Type | Description |
39
40
  |-------|------|-------------|
40
- | `onValueChange` | `(range: SDateRangeValue) => void` | 선택 변경 (sdUpdate) — [start, end] |
41
+ | `onValueChange` | `(range: SDateRangeValue) => void` | 선택 변경 (sdUpdate) — [start, end]. `clearable` 로 지우면 `null` 이 온다 |
41
42
  | `onViewChange` | `(view: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
42
43
 
43
44
  ### SRangeCalendar
@@ -9,7 +9,7 @@ export type SDateRangeValue = [string, string] | null;
9
9
  export type SDateRangePickerSize = SFieldSize;
10
10
  export interface SDateRangePickerProps {
11
11
  value?: SDateRangeValue;
12
- /** 선택 변경 (sdUpdate) — [start, end] */
12
+ /** 선택 변경 (sdUpdate) — [start, end]. `clearable` 로 지우면 `null` 이 온다 */
13
13
  onValueChange?: (range: SDateRangeValue) => void;
14
14
  /** 보이는 연·월 변경 (sdViewChange) */
15
15
  onViewChange?: (view: {
@@ -27,6 +27,13 @@ export interface SDateRangePickerProps {
27
27
  */
28
28
  useTimePicker?: boolean;
29
29
  disabled?: boolean;
30
+ /**
31
+ * 선택값 지우기 버튼. 값이 있을 때만 나타나고, 누르면 `onValueChange` 로 `null` 이 온다.
32
+ *
33
+ * **필수 입력 필드에는 켜지 않는다** — 지우면 다시 고르기 전까지 폼이 통과하지 못한다.
34
+ * `SDatePicker` · `STimePicker` · `SSelect` 의 `clearable` 과 같은 규칙이다.
35
+ */
36
+ clearable?: boolean;
30
37
  /**
31
38
  * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
32
39
  * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
@@ -67,4 +74,4 @@ export interface SRangeCalendarProps {
67
74
  * 캘린더 그리드만 필요한 커스텀 조합(예: 다른 트리거에 팝오버로 얹는 경우)에 재사용한다. */
68
75
  export declare function RangeCalendar({ value, selectable, maxRange, useTimePicker, onSelect, onPendingStartChange, onViewChange, }: SRangeCalendarProps): import("react").JSX.Element;
69
76
  /** SDateRangePicker — sd-date-range-picker 포팅. SField + SPopover(범위 캘린더). */
70
- export declare function SDateRangePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, maxRange, useTimePicker, disabled, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDateRangePickerProps): import("react").JSX.Element;
77
+ export declare function SDateRangePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, maxRange, useTimePicker, disabled, clearable, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDateRangePickerProps): import("react").JSX.Element;
@@ -17,6 +17,7 @@
17
17
  - [SCalendar](../SCalendar)
18
18
  - [SDateRangePicker](../SDateRangePicker)
19
19
  - [SList](../SList)
20
+ - [STable](../STable)
20
21
  - [STableBar](../STableBar)
21
22
 
22
23
  ### Graph
@@ -26,6 +27,7 @@ graph TD;
26
27
  SCalendar --> SDivider
27
28
  SDateRangePicker --> SDivider
28
29
  SList --> SDivider
30
+ STable --> SDivider
29
31
  STableBar --> SDivider
30
32
  style SDivider fill:#f9f,stroke:#333,stroke-width:4px
31
33
  ```
@@ -49,6 +49,7 @@ export type SGhostButtonIntent = 'default' | 'danger' | 'action' | 'subAction' |
49
49
  - [SCalendar](../SCalendar)
50
50
  - [SChip](../SChip)
51
51
  - [SChipFilter](../SChipFilter)
52
+ - [SDatePicker](../SDatePicker)
52
53
  - [SDateRangePicker](../SDateRangePicker)
53
54
  - [SFilePicker](../SFilePicker)
54
55
  - [SGnb](../SGnb)
@@ -81,6 +82,7 @@ graph TD;
81
82
  SCalendar --> SGhostButton
82
83
  SChip --> SGhostButton
83
84
  SChipFilter --> SGhostButton
85
+ SDatePicker --> SGhostButton
84
86
  SDateRangePicker --> SGhostButton
85
87
  SFilePicker --> SGhostButton
86
88
  SGnb --> SGhostButton
@@ -17,6 +17,6 @@ export interface SIconProps {
17
17
  }
18
18
  /**
19
19
  * SIcon — sd-icon 포팅.
20
- * 94개 SVG(icons.gen.tsx)를 currentColor 기반으로 렌더한다.
20
+ * icons.gen.tsx 의 SVG 를 currentColor 기반으로 렌더한다.
21
21
  */
22
22
  export declare const SIcon: import("react").ForwardRefExoticComponent<SIconProps & import("react").RefAttributes<HTMLSpanElement>>;
@@ -39,6 +39,7 @@ export declare const ICONS: {
39
39
  readonly clockFill: (p: IconRenderProps) => import("react").JSX.Element;
40
40
  readonly clockOutline: (p: IconRenderProps) => import("react").JSX.Element;
41
41
  readonly close: (p: IconRenderProps) => import("react").JSX.Element;
42
+ readonly company: (p: IconRenderProps) => import("react").JSX.Element;
42
43
  readonly copy: (p: IconRenderProps) => import("react").JSX.Element;
43
44
  readonly coupon: (p: IconRenderProps) => import("react").JSX.Element;
44
45
  readonly dashboard: (p: IconRenderProps) => import("react").JSX.Element;
@@ -76,6 +77,7 @@ export declare const ICONS: {
76
77
  readonly reset: (p: IconRenderProps) => import("react").JSX.Element;
77
78
  readonly robot: (p: IconRenderProps) => import("react").JSX.Element;
78
79
  readonly row: (p: IconRenderProps) => import("react").JSX.Element;
80
+ readonly rowDense: (p: IconRenderProps) => import("react").JSX.Element;
79
81
  readonly save: (p: IconRenderProps) => import("react").JSX.Element;
80
82
  readonly search: (p: IconRenderProps) => import("react").JSX.Element;
81
83
  readonly send: (p: IconRenderProps) => import("react").JSX.Element;
@@ -24,11 +24,12 @@
24
24
  | `noDataLabel?` | `string` | `'데이터가 없습니다.'` | |
25
25
  | `noDataSlot?` | `ReactNode` | — | 데이터가 없을 때 body 영역 전체를 대체하는 슬롯. 지정하면 `noDataLabel` 대신 이 콘텐츠가 헤더 아래 영역을 채우며, 버튼 등 인터랙션도 동작한다. |
26
26
  | `isLoading?` | `boolean` | `false` | |
27
- | `dense?` | `boolean` | `false` | |
27
+ | `dense?` | `boolean` | `false` | 행 높이를 좁게 (세로 여백만 줄인다 — 좌우 패딩은 그대로) |
28
28
  | `noHover?` | `boolean` | `false` | true면 행에 마우스를 올려도 hover 배경(grey_05)을 표시하지 않는다 |
29
29
  | `pagination?` | `STablePagination` | — | 페이지네이션 (있으면 하단 표시) |
30
30
  | `useInternalPagination?` | `boolean` | `false` | 테이블 내부에서 페이지네이션을 직접 관리 (rows를 내부 슬라이싱) |
31
31
  | `useRowsPerPageSelect?` | `boolean` | `false` | 페이지당 행 수 셀렉트 표시 |
32
+ | `useDensityToggle?` | `boolean` | `false` | 페이지네이션 바에 밀도 토글(`좁게 보기` · `넓게 보기`) 표시. **페이지네이션이 있을 때만 나타난다** — 토글이 사는 곳이 그 바이기 때문이다. `onDenseChange` 와 함께 준다. 밀도는 컴포넌트가 갖지 않으므로, 핸들러 없이 켜면 눌러도 아무 일도 일어나지 않는다. |
32
33
  | `rowsPerPageOption?` | `SSelectOption[]` | `DEFAULT_ROWS_PER_PAGE_OPTION` | |
33
34
  | `useVirtualScroll?` | `boolean` | `false` | 가상 스크롤 |
34
35
  | `rowHeight?` | `number` | — | |
@@ -44,6 +45,7 @@
44
45
  |-------|------|-------------|
45
46
  | `onSelectedChange` | `(rows: SRow[]) => void` | |
46
47
  | `onSortChange` | `(sort: STableSort \| null) => void` | 정렬 헤더 클릭 (`asc → desc → 해제` 3단). 해제되면 `null` 이 온다. 다중 정렬은 1차 안에서 지원하지 않는다. |
48
+ | `onDenseChange` | `(dense: boolean) => void` | 밀도 변경 (`useDensityToggle` 로 띄운 토글을 눌렀을 때). 컴포넌트는 밀도 상태를 갖지 않는다 — `dense` 가 곧 현재 상태이고, 그 진실은 페이지에 있다. 사용자가 고른 밀도를 다음 방문까지 기억해 두는 것(로컬 저장 등)도 페이지 몫이다. |
47
49
  | `onPageChange` | `(page: number) => void` | |
48
50
  | `onRowsPerPageChange` | `(perPage: number) => void` | |
49
51
  | `onVirtualUpdate` | `(range: { from: number; to: number }) => void` | |
@@ -126,10 +128,12 @@ export type STableColumnField = string | ((row: SRow) => any);
126
128
 
127
129
  - [SCheckbox](../SCheckbox)
128
130
  - [SCircleProgress](../SCircleProgress)
131
+ - [SDivider](../SDivider)
129
132
  - [SGhostButton](../SGhostButton)
130
133
  - [SIcon](../SIcon)
131
134
  - [SPagination](../SPagination)
132
135
  - [SSelect](../SSelect)
136
+ - [STextLink](../STextLink)
133
137
  - [STooltip](../STooltip)
134
138
 
135
139
  ### Graph
@@ -138,10 +142,12 @@ export type STableColumnField = string | ((row: SRow) => any);
138
142
  graph TD;
139
143
  STable --> SCheckbox
140
144
  STable --> SCircleProgress
145
+ STable --> SDivider
141
146
  STable --> SGhostButton
142
147
  STable --> SIcon
143
148
  STable --> SPagination
144
149
  STable --> SSelect
150
+ STable --> STextLink
145
151
  STable --> STooltip
146
152
  style STable fill:#f9f,stroke:#333,stroke-width:4px
147
153
  ```
@@ -1,6 +1,4 @@
1
1
  import { type CSSProperties, type ReactNode } from 'react';
2
- import { type SColor } from '../../lib/color';
3
- import { type SIconName } from '../SIcon';
4
2
  import { type SSelectOption } from '../SSelect';
5
3
  export type SRow = Record<string, any>;
6
4
  /** `STableColumn.renderCell`에 전달되는 컨텍스트 */
@@ -53,9 +51,6 @@ interface STableColumnBase {
53
51
  /** 표시 포맷 */
54
52
  format?: (value: any, row: SRow) => string;
55
53
  visible?: boolean;
56
- icon?: SIconName;
57
- /** 헤더 아이콘 색상. 팔레트 키(`grey_65`, `red_95` …) 또는 임의 CSS 색상 */
58
- iconColor?: SColor;
59
54
  /**
60
55
  * 헤더 라벨 뒤 도움말 툴팁 (각 항목이 한 줄). `SKeyValueTable` 의 `field.helpText` 와 같은 역할 —
61
56
  * 라벨만으로 뜻이 통하지 않는 컬럼의 산출 기준·단위를 설명한다.
@@ -63,6 +58,30 @@ interface STableColumnBase {
63
58
  * `renderHeader` 로 헤더를 통째로 교체하면 그리지 않는다 (헤더 전체가 소비 앱 책임).
64
59
  */
65
60
  helpText?: string[];
61
+ /**
62
+ * 라벨 뒤 필수 표시(`*`). `SKeyValueTable` 의 `field.required` 와 같은 규칙 —
63
+ * 그 열의 값을 반드시 채워야 하는 입력 열에만 쓴다.
64
+ *
65
+ * `renderHeader` 로 헤더를 통째로 교체하면 그리지 않는다 (헤더 전체가 소비 앱 책임).
66
+ */
67
+ required?: boolean;
68
+ /**
69
+ * 셀 값을 직접 고칠 수 있는 열. 라벨 뒤에 `pageEdit` 표식이 붙는다.
70
+ *
71
+ * **표식일 뿐 버튼이 아니다** — 실제로 고치는 UI 는 셀(`render`)이 그린다. 표식만 켜 두면
72
+ * 고칠 수 있다고 해 놓고 고칠 방법이 없는 열이 된다.
73
+ *
74
+ * `renderHeader` 로 헤더를 통째로 교체하면 그리지 않는다 (헤더 전체가 소비 앱 책임).
75
+ */
76
+ editable?: boolean;
77
+ /**
78
+ * 셀을 눌러 다른 화면으로 넘어가는 열. 라벨 뒤에 `pageMove` 표식이 붙는다.
79
+ *
80
+ * **표식일 뿐 버튼이 아니다** — 넘어가는 것은 셀(`render`)의 링크·클릭이 한다.
81
+ *
82
+ * `renderHeader` 로 헤더를 통째로 교체하면 그리지 않는다 (헤더 전체가 소비 앱 책임).
83
+ */
84
+ navigable?: boolean;
66
85
  /**
67
86
  * 정렬 가능한 열인지. 켜면 헤더 라벨 뒤에 정렬 버튼이 붙는다 — 누르는 곳은 그 버튼뿐이고
68
87
  * 헤더 셀 전체가 클릭 영역이 되지는 않는다.
@@ -199,7 +218,14 @@ export interface STableProps {
199
218
  */
200
219
  noDataSlot?: ReactNode;
201
220
  isLoading?: boolean;
221
+ /** 행 높이를 좁게 (세로 여백만 줄인다 — 좌우 패딩은 그대로) */
202
222
  dense?: boolean;
223
+ /**
224
+ * 밀도 변경 (`useDensityToggle` 로 띄운 토글을 눌렀을 때). 컴포넌트는 밀도 상태를 갖지 않는다 —
225
+ * `dense` 가 곧 현재 상태이고, 그 진실은 페이지에 있다. 사용자가 고른 밀도를 다음 방문까지
226
+ * 기억해 두는 것(로컬 저장 등)도 페이지 몫이다.
227
+ */
228
+ onDenseChange?: (dense: boolean) => void;
203
229
  /** true면 행에 마우스를 올려도 hover 배경(grey_05)을 표시하지 않는다 */
204
230
  noHover?: boolean;
205
231
  /** 페이지네이션 (있으면 하단 표시) */
@@ -209,6 +235,14 @@ export interface STableProps {
209
235
  useInternalPagination?: boolean;
210
236
  /** 페이지당 행 수 셀렉트 표시 */
211
237
  useRowsPerPageSelect?: boolean;
238
+ /**
239
+ * 페이지네이션 바에 밀도 토글(`좁게 보기` · `넓게 보기`) 표시.
240
+ *
241
+ * **페이지네이션이 있을 때만 나타난다** — 토글이 사는 곳이 그 바이기 때문이다.
242
+ * `onDenseChange` 와 함께 준다. 밀도는 컴포넌트가 갖지 않으므로, 핸들러 없이 켜면 눌러도
243
+ * 아무 일도 일어나지 않는다.
244
+ */
245
+ useDensityToggle?: boolean;
212
246
  rowsPerPageOption?: SSelectOption[];
213
247
  onRowsPerPageChange?: (perPage: number) => void;
214
248
  /** 가상 스크롤 */
@@ -11,6 +11,7 @@
11
11
  | `label?` | `string` | `''` | 레이블 |
12
12
  | `icon?` | `SIconName` | — | 좌측 아이콘 |
13
13
  | `iconColor?` | `SColor` | — | 좌측 아이콘 색상. 팔레트 키(`grey_65`, `red_95` …) 또는 임의 CSS 색상 |
14
+ | `iconRotate?` | `0 \| 90 \| 180 \| 270` | — | 좌측 아이콘 회전 각도. 같은 아이콘을 방향만 바꿔 쓸 때 (`SIcon` 의 `rotate` 로 그대로 간다) |
14
15
  | `labelClass?` | `string` | — | 레이블 span에 추가할 클래스 |
15
16
  | `rightArrow?` | `STextLinkArrow` | `'none'` | 우측 화살표 |
16
17
  | `underline?` | `boolean` | `false` | 밑줄 여부 |
@@ -45,6 +46,7 @@ export type STextLinkSize = 'sm' | 'md' | 'lg';
45
46
  - [SChipFilter](../SChipFilter)
46
47
  - [SChipInput](../SChipInput)
47
48
  - [SPopover](../SPopover)
49
+ - [STable](../STable)
48
50
 
49
51
  ### Depends on
50
52
 
@@ -60,5 +62,6 @@ graph TD;
60
62
  SChipFilter --> STextLink
61
63
  SChipInput --> STextLink
62
64
  SPopover --> STextLink
65
+ STable --> STextLink
63
66
  style STextLink fill:#f9f,stroke:#333,stroke-width:4px
64
67
  ```
@@ -10,6 +10,8 @@ export interface STextLinkProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'o
10
10
  icon?: SIconName;
11
11
  /** 좌측 아이콘 색상. 팔레트 키(`grey_65`, `red_95` …) 또는 임의 CSS 색상 */
12
12
  iconColor?: SColor;
13
+ /** 좌측 아이콘 회전 각도. 같은 아이콘을 방향만 바꿔 쓸 때 (`SIcon` 의 `rotate` 로 그대로 간다) */
14
+ iconRotate?: 0 | 90 | 180 | 270;
13
15
  /** 레이블 span에 추가할 클래스 */
14
16
  labelClass?: string;
15
17
  /** 우측 화살표 */