@idbrnd/design-system 1.7.4 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @idbrnd/design-system
2
2
 
3
- ![Version](https://img.shields.io/badge/version-1.7.3-4B5FE1?style=flat-square)
3
+ ![Version](https://img.shields.io/badge/version-1.9.0-4B5FE1?style=flat-square)
4
4
  ![React](https://img.shields.io/badge/React-18.3.1-61DAFB?style=flat-square&logo=react&logoColor=white)
5
5
  ![Vite](https://img.shields.io/badge/Vite-7.3.0-646CFF?style=flat-square&logo=vite&logoColor=white)
6
6
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-3178C6?style=flat-square&logo=typescript&logoColor=white)
@@ -68,6 +68,10 @@ export default function App() {
68
68
  - [ChipGroup](#chipgroup)
69
69
  - [FilterChip](#filterchip)
70
70
  - [FilterBar](#filterbar)
71
+ - [테이블](#테이블)
72
+ - [TableContainer](#tablecontainer)
73
+ - [Table](#table)
74
+ - [Pagination](#pagination)
71
75
  - [기타](#기타)
72
76
  - [Spinner](#spinner)
73
77
 
@@ -226,26 +230,26 @@ function InputExample() {
226
230
  }
227
231
  ```
228
232
 
229
- | Prop | 타입 | 기본값 | 설명 |
230
- | ------------------------ | ------------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------------- |
231
- | `type` | `HTMLInputTypeAttribute` | `"text"` | 실제 HTML input 타입 (`text`, `password`, `email` 등) |
232
- | `designType` | `"outline"` \| `"fill"` | `"outline"` | Input 컨테이너 외형 |
233
- | `size` | `"default"` \| `"small"` | `"default"` | Input 높이 |
234
- | `variant` | `"basic"` \| `"error"` \| `"onTyping"` \| `"typed"` \| `"onFocus"` \| `"success"` | 자동 계산 | 수동 상태 지정. `errorMessage`가 있으면 `error`가 우선 적용 |
235
- | `width` | `number \| string` | `"100%"` | 루트 너비 |
236
- | `value` | `string \| number \| readonly string[]` | — | **(필수)** 입력값 |
237
- | `onChange` | `ChangeEventHandler<HTMLInputElement>` | — | 값 변경 핸들러 |
238
- | `heading` | `boolean` | `true` | 상단 라벨 표시 여부 |
239
- | `headingContent` | `ReactNode` | `"Label"` | 상단 라벨 내용 |
240
- | `required` | `boolean` | `false` | 필수 입력 표시 및 native `required` 속성 적용 |
241
- | `description` | `ReactNode \| boolean` | `false` | 하단 안내 문구. `true`면 기본 안내 문구를 사용 |
242
- | `fixedDescriptionHeight` | `boolean` | `true` | helper 영역 높이 고정 여부 |
243
- | `errorMessage` | `ReactNode` | 기본 에러 문구 | 의미 있는 값이 전달되면 에러 상태가 자동 적용되고 해당 메시지가 표시됨 |
244
- | `leadingIcon` | `ReactNode` | — | 입력 필드 좌측 아이콘 |
245
- | `trailingContent` | `ReactNode` | — | 입력 필드 우측 콘텐츠 |
246
- | `readOnly` | `boolean` | `false` | 읽기 전용 |
247
- | `disabled` | `boolean` | `false` | 비활성화 |
248
- | `customStyle` | `CSSProperties` | `{}` | 루트 컨테이너에 적용할 추가 인라인 스타일 |
233
+ | Prop | 타입 | 기본값 | 설명 |
234
+ | ------------------------ | --------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------- |
235
+ | `type` | `HTMLInputTypeAttribute` | `"text"` | 실제 HTML input 타입 (`text`, `password`, `email` 등) |
236
+ | `designType` | `"outline"` \| `"fill"` | `"outline"` | Input 컨테이너 외형 |
237
+ | `size` | `"default"` \| `"small"` | `"default"` | Input 높이 |
238
+ | `variant` | `"basic"` \| `"error"` \| `"onTyping"` \| `"typed"` \| `"onFocus"` \| `"success"` | 자동 계산 | 수동 상태 지정. `errorMessage`가 있으면 `error`가 우선 적용 |
239
+ | `width` | `number \| string` | `"100%"` | 루트 너비 |
240
+ | `value` | `string \| number \| readonly string[]` | — | **(필수)** 입력값 |
241
+ | `onChange` | `ChangeEventHandler<HTMLInputElement>` | — | 값 변경 핸들러 |
242
+ | `heading` | `boolean` | `true` | 상단 라벨 표시 여부 |
243
+ | `headingContent` | `ReactNode` | `"Label"` | 상단 라벨 내용 |
244
+ | `required` | `boolean` | `false` | 필수 입력 표시 및 native `required` 속성 적용 |
245
+ | `description` | `ReactNode \| boolean` | `false` | 하단 안내 문구. `true`면 기본 안내 문구를 사용 |
246
+ | `fixedDescriptionHeight` | `boolean` | `true` | helper 영역 높이 고정 여부 |
247
+ | `errorMessage` | `ReactNode` | 기본 에러 문구 | 의미 있는 값이 전달되면 에러 상태가 자동 적용되고 해당 메시지가 표시됨 |
248
+ | `leadingIcon` | `ReactNode` | — | 입력 필드 좌측 아이콘 |
249
+ | `trailingContent` | `ReactNode` | — | 입력 필드 우측 콘텐츠 |
250
+ | `readOnly` | `boolean` | `false` | 읽기 전용 |
251
+ | `disabled` | `boolean` | `false` | 비활성화 |
252
+ | `customStyle` | `CSSProperties` | `{}` | 루트 컨테이너에 적용할 추가 인라인 스타일 |
249
253
 
250
254
  유의사항:
251
255
 
@@ -264,7 +268,14 @@ function InputExample() {
264
268
  value={value}
265
269
  onChange={onChange}
266
270
  errorMessage={
267
- <span style={{ display: "inline-block", maxWidth: "240px", whiteSpace: "normal", wordBreak: "keep-all" }}>
271
+ <span
272
+ style={{
273
+ display: "inline-block",
274
+ maxWidth: "240px",
275
+ whiteSpace: "normal",
276
+ wordBreak: "keep-all",
277
+ }}
278
+ >
268
279
  긴 에러 문구를 이 케이스에서만 두 줄로
269
280
  <br />
270
281
  보여주고 싶을 때 사용할 수 있습니다.
@@ -285,7 +296,7 @@ function SearchExample() {
285
296
  const [keyword, setKeyword] = useState("");
286
297
  const messages = {
287
298
  minLength: "검색어를 2자 이상 입력해 주세요.",
288
- maxLength: "검색어는 19자 이하로 입력해 주세요."
299
+ maxLength: "검색어는 19자 이하로 입력해 주세요.",
289
300
  };
290
301
  const searchErrorMessage =
291
302
  keyword.length === 1
@@ -306,19 +317,19 @@ function SearchExample() {
306
317
  }
307
318
  ```
308
319
 
309
- | Prop | 타입 | 기본값 | 설명 |
310
- | -------------- | ----------------------------------------------------- | ----------- | -------------------------------------------------------- |
311
- | `value` | `string` | — | **(필수)** 입력값 |
312
- | `onChange` | `ChangeEventHandler` | — | **(필수)** 변경 핸들러 |
313
- | `onSearch` | `(value: string) => void` | — | 검색 실행 콜백 |
314
- | `onClear` | `() => void` | — | 삭제 버튼 클릭 콜백. 미지정 시 기본 동작 사용 |
315
- | `size` | `"default"` \| `"small"` | `"default"` | 크기 |
316
- | `variant` | `"default"` \| `"onTyping"` \| `"typed"` \| `"error"` | — | 상태 변형. `errorMessage`가 있으면 `error`가 우선 적용 |
320
+ | Prop | 타입 | 기본값 | 설명 |
321
+ | -------------- | ----------------------------------------------------- | ----------- | -------------------------------------------------------------------- |
322
+ | `value` | `string` | — | **(필수)** 입력값 |
323
+ | `onChange` | `ChangeEventHandler` | — | **(필수)** 변경 핸들러 |
324
+ | `onSearch` | `(value: string) => void` | — | 검색 실행 콜백 |
325
+ | `onClear` | `() => void` | — | 삭제 버튼 클릭 콜백. 미지정 시 기본 동작 사용 |
326
+ | `size` | `"default"` \| `"small"` | `"default"` | 크기 |
327
+ | `variant` | `"default"` \| `"onTyping"` \| `"typed"` \| `"error"` | — | 상태 변형. `errorMessage`가 있으면 `error`가 우선 적용 |
317
328
  | `errorMessage` | `ReactNode` | — | 의미 있는 값이 전달되면 에러 상태가 자동 적용되고 해당 문구가 표시됨 |
318
- | `maxLength` | `number` | `20` | native `input`의 `maxLength` 속성으로 전달 |
319
- | `heading` | `boolean` | `false` | 상단 heading 표시 여부 |
320
- | `disabled` | `boolean` | `false` | 비활성화 |
321
- | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
329
+ | `maxLength` | `number` | `20` | native `input`의 `maxLength` 속성으로 전달 |
330
+ | `heading` | `boolean` | `false` | 상단 heading 표시 여부 |
331
+ | `disabled` | `boolean` | `false` | 비활성화 |
332
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
322
333
 
323
334
  - `SearchBar`는 UI 컴포넌트로만 동작하며, 길이 검증이나 번역 문구 생성 로직은 포함하지 않습니다.
324
335
  - `errorMessage`가 비거나 내용 없는 JSX 래퍼만 남으면 에러로 처리하지 않고, `variant` prop 또는 기본 자동 상태(`default`/`typed`)로 되돌아갑니다.
@@ -375,7 +386,7 @@ function CheckBoxExample() {
375
386
  | `checked` | `boolean` | — | **(필수)** 체크 상태 |
376
387
  | `onChange` | `(checked: boolean) => void` | — | 변경 핸들러 |
377
388
  | `indeterminate` | `boolean` | `false` | `checked={true}`와 함께 사용 시 대시(–) 아이콘 표시 |
378
- | `children` | `ReactNode` | — | 체크박스 우측에 렌더링할 라벨/콘텐츠 |
389
+ | `children` | `ReactNode` | — | 체크박스 우측에 렌더링할 라벨/콘텐츠 |
379
390
  | `variant` | `"primary"` \| `"assistive"` | `"primary"` | 색상 변형 |
380
391
  | `size` | `"medium"` \| `"small"` | `"medium"` | 크기 |
381
392
  | `density` | `"default"` \| `"compact"` | `"default"` | wrapper 밀도 |
@@ -417,18 +428,18 @@ function RadioGroupExample() {
417
428
  }
418
429
  ```
419
430
 
420
- | Prop | 타입 | 기본값 | 설명 |
421
- | ------------- | ---------------------------- | ----------- | -------------------- |
422
- | `checked` | `boolean` | — | **(필수)** 선택 상태 |
423
- | `onChange` | `(checked: boolean) => void` | — | 변경 핸들러 |
424
- | `name` | `string` | — | input name 속성 |
425
- | `value` | `string` | — | input value 속성 |
431
+ | Prop | 타입 | 기본값 | 설명 |
432
+ | ------------- | ---------------------------- | ----------- | ---------------------------------- |
433
+ | `checked` | `boolean` | — | **(필수)** 선택 상태 |
434
+ | `onChange` | `(checked: boolean) => void` | — | 변경 핸들러 |
435
+ | `name` | `string` | — | input name 속성 |
436
+ | `value` | `string` | — | input value 속성 |
426
437
  | `children` | `ReactNode` | — | 라디오 우측에 렌더링할 라벨/콘텐츠 |
427
- | `variant` | `"primary"` \| `"assistive"` | `"primary"` | 색상 변형 |
428
- | `size` | `"medium"` \| `"small"` | `"medium"` | 크기 |
429
- | `density` | `"default"` \| `"compact"` | `"default"` | wrapper 밀도 |
430
- | `disabled` | `boolean` | `false` | 비활성화 |
431
- | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
438
+ | `variant` | `"primary"` \| `"assistive"` | `"primary"` | 색상 변형 |
439
+ | `size` | `"medium"` \| `"small"` | `"medium"` | 크기 |
440
+ | `density` | `"default"` \| `"compact"` | `"default"` | wrapper 밀도 |
441
+ | `disabled` | `boolean` | `false` | 비활성화 |
442
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
432
443
 
433
444
  유의사항:
434
445
 
@@ -955,16 +966,16 @@ import { Chip } from "@idbrnd/design-system";
955
966
  </Chip>
956
967
  ```
957
968
 
958
- | Prop | Type | Default | Description |
959
- |------|------|---------|-------------|
960
- | `variant` | `"fillWeak" \| "fill" \| "outline" \| "outlineSurface"` | `"outline"` | 칩 스타일 |
961
- | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 크기 |
962
- | `selected` | `boolean` | `false` | 선택 상태 |
963
- | `disabled` | `boolean` | `false` | 비활성화 |
964
- | `leadingSlot` | `ReactNode` | — | 텍스트 왼쪽 콘텐츠 |
965
- | `trailingSlot` | `ReactNode` | — | 텍스트 오른쪽 콘텐츠 |
966
- | `onClick` | `MouseEventHandler` | — | 클릭 핸들러 |
967
- | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
969
+ | Prop | Type | Default | Description |
970
+ | -------------- | ------------------------------------------------------- | ----------- | --------------------------- |
971
+ | `variant` | `"fillWeak" \| "fill" \| "outline" \| "outlineSurface"` | `"outline"` | 칩 스타일 |
972
+ | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 크기 |
973
+ | `selected` | `boolean` | `false` | 선택 상태 |
974
+ | `disabled` | `boolean` | `false` | 비활성화 |
975
+ | `leadingSlot` | `ReactNode` | — | 텍스트 왼쪽 콘텐츠 |
976
+ | `trailingSlot` | `ReactNode` | — | 텍스트 오른쪽 콘텐츠 |
977
+ | `onClick` | `MouseEventHandler` | — | 클릭 핸들러 |
978
+ | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
968
979
 
969
980
  ---
970
981
 
@@ -1005,14 +1016,14 @@ function toggle(prev: DropdownOption[], item: DropdownOption) {
1005
1016
  </ChipGroup>
1006
1017
  ```
1007
1018
 
1008
- | Prop | Type | Default | Description |
1009
- |------|------|---------|-------------|
1010
- | `type` | `"selection" \| "suggestion"` | `"selection"` | `selection`: 줄바꿈, `suggestion`: 가로 드래그 캐러셀 |
1011
- | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 간격 크기 |
1012
- | `gradient` | `boolean` | `false` | `suggestion` 타입에서 스크롤 시 양끝 그라데이션 표시 |
1013
- | `leadingSlot` | `ReactNode` | — | 칩 목록 앞 텍스트/라벨 |
1014
- | `leadingElementSlot` | `ReactNode` | — | 칩 목록 앞 구분선 포함 엘리먼트 |
1015
- | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
1019
+ | Prop | Type | Default | Description |
1020
+ | -------------------- | -------------------------------------------- | ------------- | ----------------------------------------------------- |
1021
+ | `type` | `"selection" \| "suggestion"` | `"selection"` | `selection`: 줄바꿈, `suggestion`: 가로 드래그 캐러셀 |
1022
+ | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 간격 크기 |
1023
+ | `gradient` | `boolean` | `false` | `suggestion` 타입에서 스크롤 시 양끝 그라데이션 표시 |
1024
+ | `leadingSlot` | `ReactNode` | — | 칩 목록 앞 텍스트/라벨 |
1025
+ | `leadingElementSlot` | `ReactNode` | — | 칩 목록 앞 구분선 포함 엘리먼트 |
1026
+ | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
1016
1027
 
1017
1028
  ---
1018
1029
 
@@ -1060,15 +1071,15 @@ const [sort, setSort] = useState<DropdownOption | undefined>();
1060
1071
  </FilterChip>
1061
1072
  ```
1062
1073
 
1063
- | Prop | Type | Default | Description |
1064
- |------|------|---------|-------------|
1065
- | `options` | `DropdownOption[]` | (필수) | 드롭다운 옵션 목록 |
1066
- | `onSelect` | `(option: DropdownOption \| undefined) => void` | (필수) | 선택/해제 콜백. 이미 선택된 옵션 재클릭 시 `undefined` 전달 |
1067
- | `selectedValue` | `string` | — | 현재 선택된 옵션의 `value` |
1068
- | `dropdownType` | `"basic" \| "search"` | `"basic"` | `search`: 검색 입력란 포함 |
1069
- | `dropdownAlign` | `"left" \| "center"` | `"left"` | 드롭다운 정렬 방향 |
1070
- | `dropdownWidth` | `number` | — | 드롭다운 너비 (px) |
1071
- | `showSelectedLabel` | `boolean` | `false` | `children`을 prefix로, 선택값 label을 옆에 함께 표시 |
1074
+ | Prop | Type | Default | Description |
1075
+ | ------------------- | ----------------------------------------------- | --------- | ----------------------------------------------------------- |
1076
+ | `options` | `DropdownOption[]` | (필수) | 드롭다운 옵션 목록 |
1077
+ | `onSelect` | `(option: DropdownOption \| undefined) => void` | (필수) | 선택/해제 콜백. 이미 선택된 옵션 재클릭 시 `undefined` 전달 |
1078
+ | `selectedValue` | `string` | — | 현재 선택된 옵션의 `value` |
1079
+ | `dropdownType` | `"basic" \| "search"` | `"basic"` | `search`: 검색 입력란 포함 |
1080
+ | `dropdownAlign` | `"left" \| "center"` | `"left"` | 드롭다운 정렬 방향 |
1081
+ | `dropdownWidth` | `number` | — | 드롭다운 너비 (px) |
1082
+ | `showSelectedLabel` | `boolean` | `false` | `children`을 prefix로, 선택값 label을 옆에 함께 표시 |
1072
1083
 
1073
1084
  ---
1074
1085
 
@@ -1097,13 +1108,282 @@ import { FilterBar, FilterChip } from "@idbrnd/design-system";
1097
1108
  </FilterBar>
1098
1109
  ```
1099
1110
 
1100
- | Prop | Type | Default | Description |
1101
- |------|------|---------|-------------|
1102
- | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 간격 및 슬롯 폰트 크기 |
1103
- | `verticalPadding` | `boolean` | `false` | `true`: 위아래 8px 패딩, 그라데이션 없음. `false`: 그라데이션 사용 |
1104
- | `leadingElementSlot` | `ReactNode` | — | 칩 목록 앞 구분선 포함 엘리먼트 |
1105
- | `trailingElementSlot` | `ReactNode` | — | 칩 목록 뒤 엘리먼트 (초기화 버튼 등) |
1106
- | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
1111
+ | Prop | Type | Default | Description |
1112
+ | --------------------- | -------------------------------------------- | ---------- | ------------------------------------------------------------------ |
1113
+ | `size` | `"large" \| "medium" \| "small" \| "xsmall"` | `"medium"` | 칩 간격 및 슬롯 폰트 크기 |
1114
+ | `verticalPadding` | `boolean` | `false` | `true`: 위아래 8px 패딩, 그라데이션 없음. `false`: 그라데이션 사용 |
1115
+ | `leadingElementSlot` | `ReactNode` | — | 칩 목록 앞 구분선 포함 엘리먼트 |
1116
+ | `trailingElementSlot` | `ReactNode` | — | 칩 목록 뒤 엘리먼트 (초기화 버튼 등) |
1117
+ | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
1118
+
1119
+ ---
1120
+
1121
+ ## 테이블
1122
+
1123
+ `TableContainer`, `Table`, `Pagination` 3개의 컴포넌트로 구성됩니다. 내부적으로 [TanStack Table v8](https://tanstack.com/table/v8)을 사용하므로 `@tanstack/react-table` 패키지가 필요합니다.
1124
+
1125
+ ```bash
1126
+ npm install @tanstack/react-table
1127
+ ```
1128
+
1129
+ ### TableContainer
1130
+
1131
+ `Table`과 `Pagination`을 감싸는 레이아웃 컨테이너입니다. `variant`를 `Table`과 동일하게 맞춰야 합니다.
1132
+
1133
+ ```tsx
1134
+ import { TableContainer, Table, Pagination } from "@idbrnd/design-system";
1135
+
1136
+ <TableContainer variant="content">
1137
+ <Table table={table} variant="content" />
1138
+ <Pagination ... />
1139
+ </TableContainer>
1140
+ ```
1141
+
1142
+ | Prop | 타입 | 기본값 | 설명 |
1143
+ | ------------- | ------------------------- | ---------- | ------------------------------------------------------------------------ |
1144
+ | `variant` | `"normal"` \| `"content"` | `"normal"` | `Table` variant와 동일하게 설정. `content`는 외곽선 + border-radius 적용 |
1145
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
1146
+ | `className` | `string` | — | 추가 클래스명 |
1147
+ | `children` | `ReactNode` | — | `Table`, `Pagination` 등 |
1148
+
1149
+ ---
1150
+
1151
+ ### Table
1152
+
1153
+ TanStack Table 인스턴스를 받아 렌더링하는 컴포넌트입니다.
1154
+
1155
+ ```tsx
1156
+ import { Table } from "@idbrnd/design-system";
1157
+ import {
1158
+ useReactTable,
1159
+ getCoreRowModel,
1160
+ getSortedRowModel,
1161
+ type SortingState,
1162
+ } from "@tanstack/react-table";
1163
+
1164
+ // 기본 사용
1165
+ const table = useReactTable({
1166
+ data,
1167
+ columns,
1168
+ getCoreRowModel: getCoreRowModel(),
1169
+ });
1170
+
1171
+ <Table table={table} variant="normal" />
1172
+
1173
+ // 정렬
1174
+ const [sorting, setSorting] = useState<SortingState>([]);
1175
+ const table = useReactTable({
1176
+ data,
1177
+ columns,
1178
+ state: { sorting },
1179
+ onSortingChange: setSorting,
1180
+ getCoreRowModel: getCoreRowModel(),
1181
+ getSortedRowModel: getSortedRowModel(),
1182
+ });
1183
+
1184
+ <Table table={table} />
1185
+
1186
+ // 행 선택 (체크박스)
1187
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
1188
+ const table = useReactTable({
1189
+ data,
1190
+ columns,
1191
+ enableRowSelection: true,
1192
+ state: { rowSelection },
1193
+ onRowSelectionChange: setRowSelection,
1194
+ getCoreRowModel: getCoreRowModel(),
1195
+ });
1196
+
1197
+ <Table table={table} selectable />
1198
+
1199
+ // 행 클릭 (체크박스 없이 인터랙션)
1200
+ <Table
1201
+ table={table}
1202
+ onRowClick={(row) => console.log(row.original)}
1203
+ />
1204
+
1205
+ // 로딩 스켈레톤
1206
+ <Table table={table} loading skeletonRowCount={10} />
1207
+
1208
+ // 빈 상태
1209
+ <Table
1210
+ table={table}
1211
+ emptyTitle="데이터가 없습니다"
1212
+ emptyDescription="새 항목을 추가해 주세요"
1213
+ />
1214
+
1215
+ // 최대 행 수 제한 (초과 시 tbody 세로 스크롤, 헤더 고정)
1216
+ <Table table={table} maxRows={10} />
1217
+ ```
1218
+
1219
+ | Prop | 타입 | 기본값 | 설명 |
1220
+ | ------------------------ | -------------------------------- | ---------- | --------------------------------------------------------------------------- |
1221
+ | `table` | `Table<TData>` | — | `useReactTable()`로 생성한 TanStack Table 인스턴스 (필수) |
1222
+ | `variant` | `"normal"` \| `"content"` | `"normal"` | 테이블 스타일 변형 |
1223
+ | `selectable` | `boolean` | `false` | 첫 번째 컬럼에 체크박스 표시. `enableRowSelection` 설정 필요 |
1224
+ | `interactive` | `boolean` | `false` | 행 hover/focus/pressed 인터랙션 활성화 |
1225
+ | `onRowClick` | `(row: Row<TData>) => void` | — | 행 클릭 콜백. 제공 시 체크박스 없이도 행에 인터랙션 효과 적용 |
1226
+ | `loading` | `boolean` | `false` | 로딩 스켈레톤 표시 |
1227
+ | `skeletonRowCount` | `number` | `5` | 로딩 스켈레톤 행 수 |
1228
+ | `emptyContent` | `ReactNode` | — | 데이터 없을 때 표시할 커스텀 콘텐츠 (전체 오버라이드) |
1229
+ | `emptyTitle` | `string` | — | 빈 상태 제목 |
1230
+ | `emptyDescription` | `string` | — | 빈 상태 설명 |
1231
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
1232
+ | `className` | `string` | — | 추가 클래스명 |
1233
+ | `maxRows` | `number` | — | 표시할 최대 행 수. 초과 시 세로 스크롤, 헤더는 항상 고정 |
1234
+ | `accordion` | `boolean` | `false` | 아코디언 토글 컬럼 활성화. `canExpand`·`renderAccordionContent`와 함께 사용 |
1235
+ | `canExpand` | `(row: Row<TData>) => boolean` | — | 쉐브론을 표시할 행 결정. 미제공 시 모든 행에 쉐브론 표시 |
1236
+ | `renderAccordionContent` | `(row: Row<TData>) => ReactNode` | — | 펼쳐진 행의 콘텐츠. 최초 열릴 때 1회 호출 후 DOM에 유지 |
1237
+
1238
+ **단일 행 선택**
1239
+
1240
+ `enableMultiRowSelection: false`와 `onRowSelectionChange` 오버라이드로 구현합니다.
1241
+
1242
+ ```tsx
1243
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
1244
+
1245
+ const table = useReactTable({
1246
+ data,
1247
+ columns,
1248
+ enableRowSelection: true,
1249
+ enableMultiRowSelection: false,
1250
+ state: { rowSelection },
1251
+ onRowSelectionChange: (updater) => {
1252
+ const next =
1253
+ typeof updater === "function" ? updater(rowSelection) : updater;
1254
+ const newlySelected = Object.keys(next).find((id) => !rowSelection[id]);
1255
+ setRowSelection(newlySelected ? { [newlySelected]: true } : {});
1256
+ },
1257
+ getCoreRowModel: getCoreRowModel(),
1258
+ });
1259
+ ```
1260
+
1261
+ **아코디언 (행 펼치기)**
1262
+
1263
+ 쉐브론 버튼 클릭 시 행 하단에 콘텐츠를 슬라이드 애니메이션으로 펼칩니다.
1264
+
1265
+ ```tsx
1266
+ const table = useReactTable({
1267
+ data,
1268
+ columns,
1269
+ getCoreRowModel: getCoreRowModel(),
1270
+ getRowId: (row) => String(row.id), // 정렬·필터 사용 시 필수
1271
+ });
1272
+
1273
+ <Table
1274
+ table={table}
1275
+ accordion
1276
+ canExpand={(row) => !!row.original.hasDetail} // 쉐브론 표시 여부를 가볍게 판단
1277
+ renderAccordionContent={(
1278
+ row, // 실제 콘텐츠는 펼쳐질 때만 렌더
1279
+ ) => <DetailPanel data={row.original} />}
1280
+ />;
1281
+ ```
1282
+
1283
+ - `canExpand`로 쉐브론 표시 여부를 결정하고, `renderAccordionContent`는 행이 최초로 펼쳐질 때 한 번만 호출됩니다. 이후 닫혀도 DOM에 유지되어 재렌더 없이 부드럽게 닫힙니다.
1284
+ - 쉐브론 버튼은 키보드 Tab+Enter 동작과 `aria-expanded` / `aria-controls`를 지원합니다.
1285
+
1286
+ **중첩 테이블 패턴**
1287
+
1288
+ ```tsx
1289
+ <Table
1290
+ table={parentTable}
1291
+ accordion
1292
+ canExpand={(row) => row.original.orderCount > 0}
1293
+ renderAccordionContent={(row) => <OrderTable userId={row.original.id} />}
1294
+ />;
1295
+
1296
+ function OrderTable({ userId }: { userId: number }) {
1297
+ const table = useReactTable({
1298
+ data: getOrders(userId),
1299
+ columns,
1300
+ getCoreRowModel: getCoreRowModel(),
1301
+ });
1302
+ return (
1303
+ <TableContainer variant="content">
1304
+ <Table table={table} variant="content" maxRows={6} />
1305
+ </TableContainer>
1306
+ );
1307
+ }
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ ### Pagination
1313
+
1314
+ ```tsx
1315
+ import { Pagination } from "@idbrnd/design-system";
1316
+ import {
1317
+ useReactTable,
1318
+ getPaginationRowModel,
1319
+ type PaginationState,
1320
+ } from "@tanstack/react-table";
1321
+
1322
+ const [pagination, setPagination] = useState<PaginationState>({
1323
+ pageIndex: 0,
1324
+ pageSize: 10,
1325
+ });
1326
+
1327
+ const table = useReactTable({
1328
+ data,
1329
+ columns,
1330
+ state: { pagination },
1331
+ onPaginationChange: setPagination,
1332
+ getCoreRowModel: getCoreRowModel(),
1333
+ getPaginationRowModel: getPaginationRowModel(),
1334
+ });
1335
+
1336
+ // 기본
1337
+ <Pagination
1338
+ table={table}
1339
+ pageCount={table.getPageCount()}
1340
+ canPreviousPage={table.getCanPreviousPage()}
1341
+ canNextPage={table.getCanNextPage()}
1342
+ onPreviousPage={() => table.previousPage()}
1343
+ onNextPage={() => table.nextPage()}
1344
+ />
1345
+
1346
+ // compact variant + 페이지 이동 입력란
1347
+ <Pagination
1348
+ table={table}
1349
+ pageCount={table.getPageCount()}
1350
+ canPreviousPage={table.getCanPreviousPage()}
1351
+ canNextPage={table.getCanNextPage()}
1352
+ onPreviousPage={() => table.previousPage()}
1353
+ onNextPage={() => table.nextPage()}
1354
+ variant="compact"
1355
+ trailingContent
1356
+ />
1357
+
1358
+ // 첫/마지막 페이지 고정 + 생략 부호
1359
+ <Pagination
1360
+ table={table}
1361
+ pageCount={table.getPageCount()}
1362
+ canPreviousPage={table.getCanPreviousPage()}
1363
+ canNextPage={table.getCanNextPage()}
1364
+ onPreviousPage={() => table.previousPage()}
1365
+ onNextPage={() => table.nextPage()}
1366
+ boundary
1367
+ />
1368
+ ```
1369
+
1370
+ | Prop | 타입 | 기본값 | 설명 |
1371
+ | ----------------- | ------------------------------------------- | --------------- | -------------------------------------------------------------------------- |
1372
+ | `table` | `Table<TData>` | — | TanStack Table 인스턴스 (필수) |
1373
+ | `pageCount` | `number` | — | 전체 페이지 수 (필수). 서버사이드 시 직접 전달 |
1374
+ | `canPreviousPage` | `boolean` | — | 이전 페이지 버튼 활성화 여부 (필수) |
1375
+ | `canNextPage` | `boolean` | — | 다음 페이지 버튼 활성화 여부 (필수) |
1376
+ | `onPreviousPage` | `() => void` | — | 이전 페이지 이동 콜백 (필수) |
1377
+ | `onNextPage` | `() => void` | — | 다음 페이지 이동 콜백 (필수) |
1378
+ | `variant` | `"extended"` \| `"compact"` \| `"minimize"` | `"extended"` | `extended`: 최대 10개, `compact`: 최대 7개, `minimize`: 현재/전체 텍스트만 |
1379
+ | `boundary` | `boolean` | `false` | `false`: 슬라이딩 윈도우, `true`: 첫·끝 페이지 고정 + `...` 생략 |
1380
+ | `trailingContent` | `boolean` | `false` | 우측에 "페이지 이동" 입력란 표시 |
1381
+ | `prevLabel` | `string` | `"이전"` | 이전 버튼 레이블 |
1382
+ | `nextLabel` | `string` | `"다음"` | 다음 버튼 레이블 |
1383
+ | `jumpLabel` | `string` | `"페이지 이동"` | 페이지 이동 입력란 레이블 |
1384
+ | `loading` | `boolean` | `false` | 로딩 스켈레톤 표시 |
1385
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
1386
+ | `className` | `string` | — | 추가 클래스명 |
1107
1387
 
1108
1388
  ---
1109
1389
 
@@ -1224,6 +1504,12 @@ import type {
1224
1504
  DropdownOption,
1225
1505
  DropdownType,
1226
1506
  DropdownAlign,
1507
+
1508
+ // Table
1509
+ TableProps,
1510
+ TableVariant,
1511
+ PaginationProps,
1512
+ PaginationVariant,
1227
1513
  } from "@idbrnd/design-system";
1228
1514
  ```
1229
1515
 
@@ -1237,4 +1523,9 @@ import type {
1237
1523
  - `showToast`, `showSnackbar`는 내부적으로 `document.body`에 포털을 생성하므로 브라우저 환경에서 호출해야 합니다.
1238
1524
  - `Select`와 `Dropdown`의 드롭다운 목록은 `document.body`에 포탈로 렌더링되므로 브라우저 환경에서만 동작합니다.
1239
1525
  - `Select`는 폼 필드용, `Dropdown`은 컨텍스트 메뉴/액션 드롭다운용으로 사용을 권장합니다.
1526
+ - `Table`은 `@tanstack/react-table`의 `useReactTable()`로 생성한 인스턴스를 필수로 받습니다. TanStack Table을 별도 설치해야 합니다.
1527
+ - `TableContainer`의 `variant`는 `Table`의 `variant`와 반드시 동일하게 맞춰야 레이아웃이 올바르게 적용됩니다.
1528
+ - `Table`에서 `selectable` 사용 시 `useReactTable`에 `enableRowSelection`, `state.rowSelection`, `onRowSelectionChange`를 함께 설정해야 합니다.
1529
+ - `Table`에서 `accordion` 사용 시 정렬·필터 기능과 함께 쓴다면 `useReactTable`에 `getRowId: (row) => String(row.id)`를 설정해야 합니다. 기본값(배열 인덱스)은 정렬 변경 시 엉뚱한 행이 펼쳐진 상태가 됩니다.
1530
+ - `Pagination`에서 서버사이드 페이지네이션을 사용할 경우 `pageCount`를 서버 응답 기준으로 직접 계산해 전달해야 합니다.
1240
1531
  - CSS 변수/타이포그래피를 포함한 스타일을 위해 `@idbrnd/design-system/style.css` import를 권장합니다.
@@ -0,0 +1,5 @@
1
+ import type { PaginationProps, PaginationVariant } from "./Pagination.types";
2
+ export type { PaginationVariant };
3
+ export type { PaginationProps };
4
+ declare function Pagination<TData>({ table, pageCount, canPreviousPage, canNextPage, onPreviousPage, onNextPage, variant, boundary, trailingContent, loading, prevLabel, nextLabel, jumpLabel, customStyle, className, }: PaginationProps<TData>): import("react/jsx-runtime").JSX.Element;
5
+ export default Pagination;
@@ -0,0 +1,51 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { Table } from "@tanstack/react-table";
3
+ export type PaginationVariant = "extended" | "compact" | "minimize";
4
+ export interface PaginationProps<TData> {
5
+ /** TanStack Table 인스턴스 */
6
+ table: Table<TData>;
7
+ /** 전체 페이지 수 (서버사이드 페이지네이션 시 직접 전달) */
8
+ pageCount: number;
9
+ /** 이전/다음 버튼 활성화 여부 */
10
+ canPreviousPage: boolean;
11
+ canNextPage: boolean;
12
+ /** 이전 페이지 이동 콜백 */
13
+ onPreviousPage: () => void;
14
+ /** 다음 페이지 이동 콜백 */
15
+ onNextPage: () => void;
16
+ /** 페이지네이션 레이아웃 형태
17
+ * - `extended`: 최대 10개 페이지 번호 노출 (기본값)
18
+ * - `compact`: 최대 7개 페이지 번호 노출
19
+ * - `minimize`: 페이지 번호 대신 "현재/전체" 텍스트만 노출
20
+ * @defaultValue `'extended'`
21
+ */
22
+ variant?: PaginationVariant;
23
+ /** 첫/마지막 페이지 고정 + 생략 부호 처리 여부
24
+ * - `false`: 현재 페이지 중심의 슬라이딩 윈도우
25
+ * - `true`: 첫·끝 페이지를 항상 고정하고 중간을 ... 으로 축약
26
+ * @defaultValue `false`
27
+ */
28
+ boundary?: boolean;
29
+ /** 우측 "페이지 이동" 입력란 표시 여부
30
+ * @defaultValue `false`
31
+ */
32
+ trailingContent?: boolean;
33
+ /** 이전 버튼 레이블
34
+ * @defaultValue `'이전'`
35
+ */
36
+ prevLabel?: string;
37
+ /** 다음 버튼 레이블
38
+ * @defaultValue `'다음'`
39
+ */
40
+ nextLabel?: string;
41
+ /** 페이지 이동 입력란 레이블
42
+ * @defaultValue `'페이지 이동'`
43
+ */
44
+ jumpLabel?: string;
45
+ /** 로딩 스켈레톤 상태 */
46
+ loading?: boolean;
47
+ /** 루트 엘리먼트 인라인 스타일 */
48
+ customStyle?: CSSProperties;
49
+ /** 추가 클래스명 */
50
+ className?: string;
51
+ }
@@ -0,0 +1,5 @@
1
+ import type { TableProps, TableVariant } from "./Table.types";
2
+ export type { TableVariant };
3
+ export type { TableProps };
4
+ declare function Table<TData>({ table, variant, selectable, interactive, loading, skeletonRowCount, emptyContent, emptyTitle, emptyDescription, customStyle, className, onRowClick, accordion, canExpand, renderAccordionContent, maxRows, }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
5
+ export default Table;