@idbrnd/design-system 1.8.0 → 1.10.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 +177 -15
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +27 -0
- package/dist/components/Table/Table.d.ts +1 -1
- package/dist/components/Table/Table.types.d.ts +31 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2510 -2289
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @idbrnd/design-system
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|

|
|
5
5
|

|
|
6
6
|

|
|
@@ -68,6 +68,7 @@ export default function App() {
|
|
|
68
68
|
- [ChipGroup](#chipgroup)
|
|
69
69
|
- [FilterChip](#filterchip)
|
|
70
70
|
- [FilterBar](#filterbar)
|
|
71
|
+
- [SegmentedControl](#segmentedcontrol)
|
|
71
72
|
- [테이블](#테이블)
|
|
72
73
|
- [TableContainer](#tablecontainer)
|
|
73
74
|
- [Table](#table)
|
|
@@ -1118,6 +1119,103 @@ import { FilterBar, FilterChip } from "@idbrnd/design-system";
|
|
|
1118
1119
|
|
|
1119
1120
|
---
|
|
1120
1121
|
|
|
1122
|
+
## SegmentedControl
|
|
1123
|
+
|
|
1124
|
+
탭 형태로 옵션을 전환하는 컴포넌트입니다. WAI-ARIA `tablist` 패턴을 따르며 키보드(Arrow, Home, End)로 탐색할 수 있습니다.
|
|
1125
|
+
|
|
1126
|
+
```tsx
|
|
1127
|
+
import { useState } from "react";
|
|
1128
|
+
import { SegmentedControl } from "@idbrnd/design-system";
|
|
1129
|
+
import type { SegmentedControlItem } from "@idbrnd/design-system";
|
|
1130
|
+
|
|
1131
|
+
const items: SegmentedControlItem[] = [
|
|
1132
|
+
{ value: "day", label: "일" },
|
|
1133
|
+
{ value: "week", label: "주" },
|
|
1134
|
+
{ value: "month", label: "월" },
|
|
1135
|
+
];
|
|
1136
|
+
|
|
1137
|
+
function Example() {
|
|
1138
|
+
const [period, setPeriod] = useState("day");
|
|
1139
|
+
|
|
1140
|
+
return <SegmentedControl items={items} value={period} onChange={setPeriod} />;
|
|
1141
|
+
}
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
**outlined 타입 + large 크기**
|
|
1145
|
+
|
|
1146
|
+
```tsx
|
|
1147
|
+
<SegmentedControl
|
|
1148
|
+
type="outlined"
|
|
1149
|
+
size="large"
|
|
1150
|
+
items={items}
|
|
1151
|
+
value={period}
|
|
1152
|
+
onChange={setPeriod}
|
|
1153
|
+
/>
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
**아이콘 + 텍스트 (`icon=true`)**
|
|
1157
|
+
|
|
1158
|
+
```tsx
|
|
1159
|
+
<SegmentedControl
|
|
1160
|
+
icon
|
|
1161
|
+
items={[
|
|
1162
|
+
{ value: "list", label: "목록", icon: <ListIcon /> },
|
|
1163
|
+
{ value: "grid", label: "그리드", icon: <GridIcon /> },
|
|
1164
|
+
{ value: "map", label: "지도", icon: <MapIcon /> },
|
|
1165
|
+
]}
|
|
1166
|
+
value={view}
|
|
1167
|
+
onChange={setView}
|
|
1168
|
+
/>
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
**아이콘 전용 (`variant="icon"`)**
|
|
1172
|
+
|
|
1173
|
+
```tsx
|
|
1174
|
+
<SegmentedControl
|
|
1175
|
+
variant="icon"
|
|
1176
|
+
label="뷰 전환"
|
|
1177
|
+
items={[
|
|
1178
|
+
{ value: "list", icon: <ListIcon />, ariaLabel: "목록 보기" },
|
|
1179
|
+
{ value: "grid", icon: <GridIcon />, ariaLabel: "그리드 보기" },
|
|
1180
|
+
{ value: "map", icon: <MapIcon />, ariaLabel: "지도 보기" },
|
|
1181
|
+
]}
|
|
1182
|
+
value={view}
|
|
1183
|
+
onChange={setView}
|
|
1184
|
+
/>
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
1188
|
+
| ------------- | ------------------------------------ | ---------- | ------------------------------------------------- |
|
|
1189
|
+
| `items` | `SegmentedControlItem[]` | — | **(필수)** 세그먼트 목록 |
|
|
1190
|
+
| `value` | `string` | — | **(필수)** 현재 선택된 세그먼트 값 |
|
|
1191
|
+
| `onChange` | `(value: string) => void` | — | 세그먼트 변경 콜백 |
|
|
1192
|
+
| `variant` | `"basic"` \| `"icon"` | `"basic"` | `"icon"`: 아이콘 전용 정사각형 세그먼트 |
|
|
1193
|
+
| `type` | `"fill"` \| `"outlined"` | `"fill"` | 컴포넌트 외형 |
|
|
1194
|
+
| `size` | `"small"` \| `"medium"` \| `"large"` | `"medium"` | 크기. small: 32px, medium: 40px, large: 48px |
|
|
1195
|
+
| `icon` | `boolean` | `false` | `variant="basic"`에서 아이콘을 텍스트와 함께 표시 |
|
|
1196
|
+
| `label` | `string` | — | 스크린 리더용 그룹 레이블 (`aria-label`) |
|
|
1197
|
+
| `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
|
|
1198
|
+
|
|
1199
|
+
**`SegmentedControlItem`**
|
|
1200
|
+
|
|
1201
|
+
| 필드 | 타입 | 설명 |
|
|
1202
|
+
| ----------- | ----------- | --------------------------------------------- |
|
|
1203
|
+
| `value` | `string` | **(필수)** 세그먼트 고유 값 |
|
|
1204
|
+
| `label` | `ReactNode` | 텍스트 라벨 (`variant="basic"`에서 사용) |
|
|
1205
|
+
| `icon` | `ReactNode` | 아이콘 (`icon=true` 또는 `variant="icon"`) |
|
|
1206
|
+
| `disabled` | `boolean` | 비활성화 |
|
|
1207
|
+
| `ariaLabel` | `string` | `variant="icon"` 시 스크린 리더용 버튼 레이블 |
|
|
1208
|
+
|
|
1209
|
+
유의사항:
|
|
1210
|
+
|
|
1211
|
+
- `variant="icon"` 사용 시 각 item에 `ariaLabel`을 반드시 지정해야 합니다. 미지정 시 개발 모드에서 경고가 출력됩니다.
|
|
1212
|
+
- `variant="basic"`에서 `icon=true`이면 세그먼트 왼쪽에 아이콘, 나머지 공간에 텍스트가 배치됩니다.
|
|
1213
|
+
- `value`에 해당하는 item이 없으면 슬라이딩 인디케이터가 숨겨지고, 첫 번째 활성 항목에 키보드 포커스가 유지됩니다.
|
|
1214
|
+
- 키보드 ArrowLeft/Right(또는 Up/Down)로 세그먼트 이동, Home/End로 첫/끝 이동을 지원합니다.
|
|
1215
|
+
- 비활성화된 세그먼트는 키보드 이동에서 건너뜁니다.
|
|
1216
|
+
|
|
1217
|
+
---
|
|
1218
|
+
|
|
1121
1219
|
## 테이블
|
|
1122
1220
|
|
|
1123
1221
|
`TableContainer`, `Table`, `Pagination` 3개의 컴포넌트로 구성됩니다. 내부적으로 [TanStack Table v8](https://tanstack.com/table/v8)을 사용하므로 `@tanstack/react-table` 패키지가 필요합니다.
|
|
@@ -1211,22 +1309,29 @@ const table = useReactTable({
|
|
|
1211
1309
|
emptyTitle="데이터가 없습니다"
|
|
1212
1310
|
emptyDescription="새 항목을 추가해 주세요"
|
|
1213
1311
|
/>
|
|
1312
|
+
|
|
1313
|
+
// 최대 행 수 제한 (초과 시 tbody 세로 스크롤, 헤더 고정)
|
|
1314
|
+
<Table table={table} maxRows={10} />
|
|
1214
1315
|
```
|
|
1215
1316
|
|
|
1216
|
-
| Prop
|
|
1217
|
-
|
|
|
1218
|
-
| `table`
|
|
1219
|
-
| `variant`
|
|
1220
|
-
| `selectable`
|
|
1221
|
-
| `interactive`
|
|
1222
|
-
| `onRowClick`
|
|
1223
|
-
| `loading`
|
|
1224
|
-
| `skeletonRowCount`
|
|
1225
|
-
| `emptyContent`
|
|
1226
|
-
| `emptyTitle`
|
|
1227
|
-
| `emptyDescription`
|
|
1228
|
-
| `customStyle`
|
|
1229
|
-
| `className`
|
|
1317
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
1318
|
+
| ------------------------ | -------------------------------- | ---------- | --------------------------------------------------------------------------- |
|
|
1319
|
+
| `table` | `Table<TData>` | — | `useReactTable()`로 생성한 TanStack Table 인스턴스 (필수) |
|
|
1320
|
+
| `variant` | `"normal"` \| `"content"` | `"normal"` | 테이블 스타일 변형 |
|
|
1321
|
+
| `selectable` | `boolean` | `false` | 첫 번째 컬럼에 체크박스 표시. `enableRowSelection` 설정 필요 |
|
|
1322
|
+
| `interactive` | `boolean` | `false` | 행 hover/focus/pressed 인터랙션 활성화 |
|
|
1323
|
+
| `onRowClick` | `(row: Row<TData>) => void` | — | 행 클릭 콜백. 제공 시 체크박스 없이도 행에 인터랙션 효과 적용 |
|
|
1324
|
+
| `loading` | `boolean` | `false` | 로딩 스켈레톤 표시 |
|
|
1325
|
+
| `skeletonRowCount` | `number` | `5` | 로딩 스켈레톤 행 수 |
|
|
1326
|
+
| `emptyContent` | `ReactNode` | — | 데이터 없을 때 표시할 커스텀 콘텐츠 (전체 오버라이드) |
|
|
1327
|
+
| `emptyTitle` | `string` | — | 빈 상태 제목 |
|
|
1328
|
+
| `emptyDescription` | `string` | — | 빈 상태 설명 |
|
|
1329
|
+
| `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
|
|
1330
|
+
| `className` | `string` | — | 추가 클래스명 |
|
|
1331
|
+
| `maxRows` | `number` | — | 표시할 최대 행 수. 초과 시 세로 스크롤, 헤더는 항상 고정 |
|
|
1332
|
+
| `accordion` | `boolean` | `false` | 아코디언 토글 컬럼 활성화. `canExpand`·`renderAccordionContent`와 함께 사용 |
|
|
1333
|
+
| `canExpand` | `(row: Row<TData>) => boolean` | — | 쉐브론을 표시할 행 결정. 미제공 시 모든 행에 쉐브론 표시 |
|
|
1334
|
+
| `renderAccordionContent` | `(row: Row<TData>) => ReactNode` | — | 펼쳐진 행의 콘텐츠. 최초 열릴 때 1회 호출 후 DOM에 유지 |
|
|
1230
1335
|
|
|
1231
1336
|
**단일 행 선택**
|
|
1232
1337
|
|
|
@@ -1251,6 +1356,55 @@ const table = useReactTable({
|
|
|
1251
1356
|
});
|
|
1252
1357
|
```
|
|
1253
1358
|
|
|
1359
|
+
**아코디언 (행 펼치기)**
|
|
1360
|
+
|
|
1361
|
+
쉐브론 버튼 클릭 시 행 하단에 콘텐츠를 슬라이드 애니메이션으로 펼칩니다.
|
|
1362
|
+
|
|
1363
|
+
```tsx
|
|
1364
|
+
const table = useReactTable({
|
|
1365
|
+
data,
|
|
1366
|
+
columns,
|
|
1367
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1368
|
+
getRowId: (row) => String(row.id), // 정렬·필터 사용 시 필수
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
<Table
|
|
1372
|
+
table={table}
|
|
1373
|
+
accordion
|
|
1374
|
+
canExpand={(row) => !!row.original.hasDetail} // 쉐브론 표시 여부를 가볍게 판단
|
|
1375
|
+
renderAccordionContent={(
|
|
1376
|
+
row, // 실제 콘텐츠는 펼쳐질 때만 렌더
|
|
1377
|
+
) => <DetailPanel data={row.original} />}
|
|
1378
|
+
/>;
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
- `canExpand`로 쉐브론 표시 여부를 결정하고, `renderAccordionContent`는 행이 최초로 펼쳐질 때 한 번만 호출됩니다. 이후 닫혀도 DOM에 유지되어 재렌더 없이 부드럽게 닫힙니다.
|
|
1382
|
+
- 쉐브론 버튼은 키보드 Tab+Enter 동작과 `aria-expanded` / `aria-controls`를 지원합니다.
|
|
1383
|
+
|
|
1384
|
+
**중첩 테이블 패턴**
|
|
1385
|
+
|
|
1386
|
+
```tsx
|
|
1387
|
+
<Table
|
|
1388
|
+
table={parentTable}
|
|
1389
|
+
accordion
|
|
1390
|
+
canExpand={(row) => row.original.orderCount > 0}
|
|
1391
|
+
renderAccordionContent={(row) => <OrderTable userId={row.original.id} />}
|
|
1392
|
+
/>;
|
|
1393
|
+
|
|
1394
|
+
function OrderTable({ userId }: { userId: number }) {
|
|
1395
|
+
const table = useReactTable({
|
|
1396
|
+
data: getOrders(userId),
|
|
1397
|
+
columns,
|
|
1398
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1399
|
+
});
|
|
1400
|
+
return (
|
|
1401
|
+
<TableContainer variant="content">
|
|
1402
|
+
<Table table={table} variant="content" maxRows={6} />
|
|
1403
|
+
</TableContainer>
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1254
1408
|
---
|
|
1255
1409
|
|
|
1256
1410
|
### Pagination
|
|
@@ -1449,6 +1603,13 @@ import type {
|
|
|
1449
1603
|
DropdownType,
|
|
1450
1604
|
DropdownAlign,
|
|
1451
1605
|
|
|
1606
|
+
// SegmentedControl
|
|
1607
|
+
SegmentedControlProps,
|
|
1608
|
+
SegmentedControlVariant,
|
|
1609
|
+
SegmentedControlType,
|
|
1610
|
+
SegmentedControlSize,
|
|
1611
|
+
SegmentedControlItem,
|
|
1612
|
+
|
|
1452
1613
|
// Table
|
|
1453
1614
|
TableProps,
|
|
1454
1615
|
TableVariant,
|
|
@@ -1470,5 +1631,6 @@ import type {
|
|
|
1470
1631
|
- `Table`은 `@tanstack/react-table`의 `useReactTable()`로 생성한 인스턴스를 필수로 받습니다. TanStack Table을 별도 설치해야 합니다.
|
|
1471
1632
|
- `TableContainer`의 `variant`는 `Table`의 `variant`와 반드시 동일하게 맞춰야 레이아웃이 올바르게 적용됩니다.
|
|
1472
1633
|
- `Table`에서 `selectable` 사용 시 `useReactTable`에 `enableRowSelection`, `state.rowSelection`, `onRowSelectionChange`를 함께 설정해야 합니다.
|
|
1634
|
+
- `Table`에서 `accordion` 사용 시 정렬·필터 기능과 함께 쓴다면 `useReactTable`에 `getRowId: (row) => String(row.id)`를 설정해야 합니다. 기본값(배열 인덱스)은 정렬 변경 시 엉뚱한 행이 펼쳐진 상태가 됩니다.
|
|
1473
1635
|
- `Pagination`에서 서버사이드 페이지네이션을 사용할 경우 `pageCount`를 서버 응답 기준으로 직접 계산해 전달해야 합니다.
|
|
1474
1636
|
- CSS 변수/타이포그래피를 포함한 스타일을 위해 `@idbrnd/design-system/style.css` import를 권장합니다.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
export type SegmentedControlVariant = "basic" | "icon";
|
|
3
|
+
export type SegmentedControlType = "fill" | "outlined";
|
|
4
|
+
export type SegmentedControlSize = "small" | "medium" | "large";
|
|
5
|
+
export interface SegmentedControlItem {
|
|
6
|
+
value: string;
|
|
7
|
+
label?: ReactNode;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** variant="icon"일 때 스크린 리더용 레이블 */
|
|
11
|
+
ariaLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SegmentedControlProps {
|
|
14
|
+
variant?: SegmentedControlVariant;
|
|
15
|
+
type?: SegmentedControlType;
|
|
16
|
+
size?: SegmentedControlSize;
|
|
17
|
+
/** basic variant에서 icon을 함께 표시할지 여부 */
|
|
18
|
+
icon?: boolean;
|
|
19
|
+
/** 스크린 리더용 그룹 레이블 */
|
|
20
|
+
label?: string;
|
|
21
|
+
items: SegmentedControlItem[];
|
|
22
|
+
value: string;
|
|
23
|
+
onChange?: (value: string) => void;
|
|
24
|
+
customStyle?: CSSProperties;
|
|
25
|
+
}
|
|
26
|
+
declare function SegmentedControl({ variant, type, size, icon, label, items, value, onChange, customStyle, }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export default SegmentedControl;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TableProps, TableVariant } from "./Table.types";
|
|
2
2
|
export type { TableVariant };
|
|
3
3
|
export type { TableProps };
|
|
4
|
-
declare function Table<TData>({ table, variant, selectable, interactive, loading, skeletonRowCount, emptyContent, emptyTitle, emptyDescription, customStyle, className, onRowClick, }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
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
5
|
export default Table;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
import type { Row, Table } from "@tanstack/react-table";
|
|
3
3
|
export type TableVariant = "normal" | "content";
|
|
4
|
-
|
|
4
|
+
interface BaseTableProps<TData> {
|
|
5
5
|
/** useReactTable()로 생성한 TanStack Table 인스턴스 */
|
|
6
6
|
table: Table<TData>;
|
|
7
7
|
/** 테이블 스타일 변형
|
|
@@ -35,4 +35,34 @@ export interface TableProps<TData> {
|
|
|
35
35
|
customStyle?: CSSProperties;
|
|
36
36
|
/** 추가 클래스명 */
|
|
37
37
|
className?: string;
|
|
38
|
+
/** 표시할 최대 행 수. 초과 시 세로 스크롤 발생. 헤더는 항상 고정 */
|
|
39
|
+
maxRows?: number;
|
|
38
40
|
}
|
|
41
|
+
type AccordionProps<TData> = {
|
|
42
|
+
accordion?: false;
|
|
43
|
+
canExpand?: never;
|
|
44
|
+
renderAccordionContent?: never;
|
|
45
|
+
} | {
|
|
46
|
+
/** 행마다 아코디언 토글(쉐브론) 컬럼을 활성화할지 여부.
|
|
47
|
+
* `renderAccordionContent`를 함께 제공해야 동작합니다.
|
|
48
|
+
*/
|
|
49
|
+
accordion: true;
|
|
50
|
+
/** 행마다 쉐브론(토글)을 표시할지 결정하는 함수.
|
|
51
|
+
* 미제공 시 모든 행에 토글 표시.
|
|
52
|
+
* renderAccordionContent 호출을 펼쳐진 행으로만 제한하므로
|
|
53
|
+
* 콘텐츠 생성 비용이 클 때 반드시 제공하세요.
|
|
54
|
+
*/
|
|
55
|
+
canExpand?: (row: Row<TData>) => boolean;
|
|
56
|
+
/** 확장된 행에 렌더링할 콘텐츠.
|
|
57
|
+
* 행이 펼쳐질 때 해당 row를 인수로 받아 ReactNode를 반환.
|
|
58
|
+
*
|
|
59
|
+
* **주의 — row.id 안정성**: TanStack Table은 기본적으로 배열 인덱스를
|
|
60
|
+
* row.id로 사용합니다. 정렬·필터 변경 시 같은 id가 다른 데이터를 가리켜
|
|
61
|
+
* 엉뚱한 행이 열린 상태가 됩니다.
|
|
62
|
+
* accordion 사용 시 useReactTable에 반드시
|
|
63
|
+
* `getRowId: (originalRow) => originalRow.id` 를 설정하세요.
|
|
64
|
+
*/
|
|
65
|
+
renderAccordionContent: (row: Row<TData>) => ReactNode;
|
|
66
|
+
};
|
|
67
|
+
export type TableProps<TData> = BaseTableProps<TData> & AccordionProps<TData>;
|
|
68
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export type { FilterChipProps } from "./components/Chip/FilterChip/FilterChip";
|
|
|
54
54
|
export type { FilterBarProps, FilterBarSize, } from "./components/Chip/FilterBar/FilterBar";
|
|
55
55
|
export { default as Tooltip } from "./components/Tooltip/Tooltip";
|
|
56
56
|
export type { TooltipProps, TooltipPosition, TooltipVariant, TooltipTrigger, TooltipContent, } from "./components/Tooltip/Tooltip.types";
|
|
57
|
+
export { default as SegmentedControl } from "./components/SegmentedControl/SegmentedControl";
|
|
58
|
+
export type { SegmentedControlProps, SegmentedControlVariant, SegmentedControlType, SegmentedControlSize, SegmentedControlItem, } from "./components/SegmentedControl/SegmentedControl";
|
|
57
59
|
export { default as Table } from "./components/Table/Table";
|
|
58
60
|
export type { TableProps, TableVariant, } from "./components/Table/Table.types";
|
|
59
61
|
export { default as TableContainer } from "./components/Table/TableContainer";
|