@idbrnd/design-system 1.10.0 → 1.12.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.10.0-4B5FE1?style=flat-square)
3
+ ![Version](https://img.shields.io/badge/version-1.12.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)
@@ -69,6 +69,7 @@ export default function App() {
69
69
  - [FilterChip](#filterchip)
70
70
  - [FilterBar](#filterbar)
71
71
  - [SegmentedControl](#segmentedcontrol)
72
+ - [Tab](#tab)
72
73
  - [테이블](#테이블)
73
74
  - [TableContainer](#tablecontainer)
74
75
  - [Table](#table)
@@ -681,26 +682,38 @@ function SelectExample() {
681
682
  />
682
683
  ```
683
684
 
684
- | Prop | 타입 | 기본값 | 설명 |
685
- | ------------------ | -------------------------------------------------------- | ----------------- | ------------------------------------------------------------- |
686
- | `options` | `DropdownOption[]` | `[]` | 드롭다운에 표시할 옵션 배열. `{ label, value, icon? }` 형태 |
687
- | `selectedValue` | `DropdownOption \| null` | — | 현재 선택된 옵션 |
688
- | `onSelect` | `(option: DropdownOption) => void` | — | 옵션 선택 시 호출되는 콜백 |
689
- | `variant` | `"default"` \| `"choosing"` \| `"error"` \| `"selected"` | `"default"` | 트리거 상태. `choosing`/`selected`는 자동 적용 |
690
- | `size` | `"large"` \| `"medium"` \| `"small"` | `"medium"` | 트리거 높이. large: 44px, medium: 40px, small: 36px |
691
- | `heading` | `string` | — | 상단 라벨 텍스트. 미지정 시 라벨 미표시 |
692
- | `required` | `boolean` | `false` | 라벨 우측 `*` 표시 여부 |
693
- | `leadingContent` | `ReactNode` | — | 트리거 텍스트 좌측에 표시할 아이콘 등 |
694
- | `description` | `ReactNode` | — | 하단 안내 문구 |
695
- | `placeholder` | `ReactNode` | `"선택해주세요."` | 미선택 시 표시할 콘텐츠. 문자열 또는 JSX 전달 가능 |
696
- | `disabled` | `boolean` | `false` | 비활성화 |
697
- | `type` | `"basic"` \| `"search"` | `"basic"` | `"search"` 시 드롭다운 상단에 검색 입력란 표시, 실시간 필터링 |
698
- | `align` | `"left"` \| `"center"` | `"left"` | 옵션 텍스트 정렬 |
699
- | `content` | `boolean` | `false` | `true` 시 옵션의 `icon`이 라벨 좌측에 표시 |
700
- | `buttonWidth` | `number` | — | 트리거 버튼 너비(px). 미지정 시 부모 너비를 따름 |
701
- | `descriptionWidth` | `number` | — | 하단 안내 문구 너비(px). 미지정 시 부모 너비를 따름 |
702
- | `textColor` | `string` | — | 옵션 라벨 텍스트 색상 커스터마이징 (예: `"#6366f1"`) |
703
- | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
685
+ **특정 옵션 비활성화**
686
+
687
+ ```tsx
688
+ const options: DropdownOption[] = [
689
+ { label: "사용 가능", value: "a" },
690
+ { label: "선택 불가", value: "b", disabled: true },
691
+ { label: "사용 가능", value: "c" },
692
+ ];
693
+
694
+ <Select options={options} selectedValue={selected} onSelect={setSelected} />;
695
+ ```
696
+
697
+ | Prop | 타입 | 기본값 | 설명 |
698
+ | ------------------ | -------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------- |
699
+ | `options` | `DropdownOption[]` | `[]` | 드롭다운에 표시할 옵션 배열. `{ label, value, icon?, disabled? }` 형태 |
700
+ | `selectedValue` | `DropdownOption \| null` | — | 현재 선택된 옵션 |
701
+ | `onSelect` | `(option: DropdownOption) => void` | — | 옵션 선택 시 호출되는 콜백 |
702
+ | `variant` | `"default"` \| `"choosing"` \| `"error"` \| `"selected"` | `"default"` | 트리거 상태. `choosing`/`selected`는 자동 적용 |
703
+ | `size` | `"large"` \| `"medium"` \| `"small"` | `"medium"` | 트리거 높이. large: 44px, medium: 40px, small: 36px |
704
+ | `heading` | `string` | — | 상단 라벨 텍스트. 미지정 시 라벨 미표시 |
705
+ | `required` | `boolean` | `false` | 라벨 우측 `*` 표시 여부 |
706
+ | `leadingContent` | `ReactNode` | — | 트리거 텍스트 좌측에 표시할 아이콘 등 |
707
+ | `description` | `ReactNode` | — | 하단 안내 문구 |
708
+ | `placeholder` | `ReactNode` | `"선택해주세요."` | 미선택 시 표시할 콘텐츠. 문자열 또는 JSX 전달 가능 |
709
+ | `disabled` | `boolean` | `false` | 비활성화 |
710
+ | `type` | `"basic"` \| `"search"` | `"basic"` | `"search"` 시 드롭다운 상단에 검색 입력란 표시, 실시간 필터링 |
711
+ | `align` | `"left"` \| `"center"` | `"left"` | 옵션 텍스트 정렬 |
712
+ | `content` | `boolean` | `false` | `true` 시 옵션의 `icon`이 라벨 좌측에 표시 |
713
+ | `buttonWidth` | `number` | — | 트리거 버튼 너비(px). 미지정 시 부모 너비를 따름 |
714
+ | `descriptionWidth` | `number` | — | 하단 안내 문구 너비(px). 미지정 시 부모 너비를 따름 |
715
+ | `textColor` | `string` | — | 옵션 라벨 텍스트 색상 커스터마이징 (예: `"#6366f1"`) |
716
+ | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
704
717
 
705
718
  유의사항:
706
719
 
@@ -711,6 +724,7 @@ function SelectExample() {
711
724
  - 긴 옵션 라벨은 말줄임 처리되며, hover 시 툴팁으로 전체 텍스트가 표시됩니다. 트리거 버튼의 선택된 값도 동일하게 동작합니다.
712
725
  - `placeholder`에 문자열 대신 JSX를 전달하면 커스텀 플레이스홀더를 렌더링할 수 있습니다.
713
726
  - 옵션이 비어있을 때: `type="search"`에서 검색어가 있으면 "검색 결과가 없습니다.", 그 외에는 `-`가 표시됩니다.
727
+ - `options` 항목에 `disabled: true`를 지정하면 해당 옵션이 클릭 불가 상태로 표시됩니다. `type="search"`에서도 검색 결과에 포함되어 존재를 알릴 수 있습니다.
714
728
 
715
729
  ---
716
730
 
@@ -778,19 +792,32 @@ function SearchDropdown() {
778
792
  </Dropdown>
779
793
  ```
780
794
 
781
- | Prop | 타입 | 기본값 | 설명 |
782
- | --------------- | ---------------------------------- | --------- | --------------------------------------------------------------- |
783
- | `children` | `ReactNode` | — | **(필수)** 드롭다운 트리거 엘리먼트 |
784
- | `options` | `DropdownOption[]` | `[]` | 드롭다운에 표시할 옵션 배열. `{ label, value, icon? }` 형태 |
785
- | `onSelect` | `(option: DropdownOption) => void` | — | **(필수)** 옵션 선택 시 호출되는 콜백 |
786
- | `selectedValue` | `string` | — | 현재 선택된 옵션의 `value`. 선택 스타일 및 `aria-selected` 적용 |
787
- | `type` | `"basic"` \| `"search"` | `"basic"` | `"search"` 시 드롭다운 상단에 검색 입력란 표시, 실시간 필터링 |
788
- | `align` | `"left"` \| `"center"` | `"left"` | 옵션 텍스트 정렬 |
789
- | `content` | `boolean` | `false` | `true` 시 옵션의 `icon`이 라벨 좌측에 표시 |
790
- | `width` | `number` | — | 드롭다운 목록 너비(px). 미지정 시 트리거 너비에 맞춤 |
791
- | `textColor` | `string` | — | 옵션 라벨 텍스트 색상 커스터마이징 (예: `"#6366f1"`) |
792
- | `disabled` | `boolean` | `false` | `true` 시 트리거 클릭이 무시됨 |
793
- | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
795
+ **특정 옵션 비활성화**
796
+
797
+ ```tsx
798
+ const options: DropdownOption[] = [
799
+ { label: "수정", value: "edit" },
800
+ { label: "삭제 (권한 없음)", value: "delete", disabled: true },
801
+ ];
802
+
803
+ <Dropdown options={options} onSelect={handleSelect}>
804
+ <OutlineButton variant="assistive">메뉴 열기</OutlineButton>
805
+ </Dropdown>;
806
+ ```
807
+
808
+ | Prop | 타입 | 기본값 | 설명 |
809
+ | --------------- | ---------------------------------- | --------- | ---------------------------------------------------------------------- |
810
+ | `children` | `ReactNode` | — | **(필수)** 드롭다운 트리거 엘리먼트 |
811
+ | `options` | `DropdownOption[]` | `[]` | 드롭다운에 표시할 옵션 배열. `{ label, value, icon?, disabled? }` 형태 |
812
+ | `onSelect` | `(option: DropdownOption) => void` | — | **(필수)** 옵션 선택 시 호출되는 콜백 |
813
+ | `selectedValue` | `string` | — | 현재 선택된 옵션의 `value`. 선택 스타일 및 `aria-selected` 적용 |
814
+ | `type` | `"basic"` \| `"search"` | `"basic"` | `"search"` 시 드롭다운 상단에 검색 입력란 표시, 실시간 필터링 |
815
+ | `align` | `"left"` \| `"center"` | `"left"` | 옵션 텍스트 정렬 |
816
+ | `content` | `boolean` | `false` | `true` 시 옵션의 `icon`이 라벨 좌측에 표시 |
817
+ | `width` | `number` | — | 드롭다운 목록 너비(px). 미지정 시 트리거 너비에 맞춤 |
818
+ | `textColor` | `string` | — | 옵션 라벨 텍스트 색상 커스터마이징 (예: `"#6366f1"`) |
819
+ | `disabled` | `boolean` | `false` | `true` 시 트리거 클릭이 무시됨 |
820
+ | `customStyle` | `CSSProperties` | — | 루트 엘리먼트 인라인 스타일 |
794
821
 
795
822
  유의사항:
796
823
 
@@ -799,6 +826,18 @@ function SearchDropdown() {
799
826
  - 드롭다운 목록은 `document.body`에 포탈로 렌더링됩니다.
800
827
  - 긴 옵션 라벨은 말줄임 처리되며, hover 시 툴팁으로 전체 텍스트가 표시됩니다.
801
828
  - 열림/닫힘 시 fade + scale 애니메이션이 적용됩니다.
829
+ - `options` 항목에 `disabled: true`를 지정하면 해당 옵션이 클릭 불가 상태로 표시됩니다.
830
+
831
+ **`DropdownOption`**
832
+
833
+ | 필드 | 타입 | 설명 |
834
+ | ---------- | ----------- | ------------------------------------------------------------ |
835
+ | `label` | `string` | **(필수)** 옵션에 표시되는 텍스트 |
836
+ | `value` | `string` | **(필수)** 옵션의 고유 식별 값 |
837
+ | `icon` | `ReactNode` | 옵션 라벨 좌측 아이콘. `content={true}`일 때만 렌더링됩니다. |
838
+ | `disabled` | `boolean` | `true`이면 클릭이 차단되고 비활성 스타일이 적용됩니다. |
839
+
840
+ ---
802
841
 
803
842
  ### Select vs Dropdown 비교
804
843
 
@@ -1216,6 +1255,104 @@ function Example() {
1216
1255
 
1217
1256
  ---
1218
1257
 
1258
+ ## Tab
1259
+
1260
+ `value` + `onChange`를 받는 controlled 탭 컴포넌트입니다. WAI-ARIA `tablist` 패턴을 따르며 키보드(ArrowLeft/Right, Home, End)로 탐색할 수 있습니다. 탭이 뷰포트를 초과하면 드래그 가능한 캐러셀이 자동으로 활성화됩니다.
1261
+
1262
+ ```tsx
1263
+ import { useState } from "react";
1264
+ import { Tab } from "@idbrnd/design-system";
1265
+ import type { TabItem } from "@idbrnd/design-system";
1266
+
1267
+ const items: TabItem[] = [
1268
+ { value: "all", label: "전체" },
1269
+ { value: "active", label: "진행 중" },
1270
+ { value: "done", label: "완료" },
1271
+ ];
1272
+
1273
+ function TabExample() {
1274
+ const [tab, setTab] = useState("all");
1275
+
1276
+ return <Tab items={items} value={tab} onChange={setTab} />;
1277
+ }
1278
+ ```
1279
+
1280
+ **뱃지 + 숫자 뱃지**
1281
+
1282
+ ```tsx
1283
+ const items: TabItem[] = [
1284
+ { value: "inbox", label: "받은 메시지" },
1285
+ { value: "unread", label: "안읽은 메시지", badge: true },
1286
+ { value: "sent", label: "보낸 메시지", numberBadge: 12 },
1287
+ ];
1288
+
1289
+ <Tab items={items} value={tab} onChange={setTab} />;
1290
+ ```
1291
+
1292
+ **fill 모드 (균등 분배)**
1293
+
1294
+ ```tsx
1295
+ <Tab items={items} value={tab} onChange={setTab} resize="fill" />
1296
+ ```
1297
+
1298
+ **왼쪽 패딩 + trailing 아이콘 버튼**
1299
+
1300
+ ```tsx
1301
+ <Tab
1302
+ items={items}
1303
+ value={tab}
1304
+ onChange={setTab}
1305
+ horizontalPadding
1306
+ trailingIcon={
1307
+ <button type="button" onClick={handleAdd}>
1308
+ <PlusIcon />
1309
+ </button>
1310
+ }
1311
+ />
1312
+ ```
1313
+
1314
+ **비활성화 탭 포함**
1315
+
1316
+ ```tsx
1317
+ const items: TabItem[] = [
1318
+ { value: "a", label: "사용 가능" },
1319
+ { value: "b", label: "비활성화", disabled: true },
1320
+ { value: "c", label: "사용 가능" },
1321
+ ];
1322
+ ```
1323
+
1324
+ | Prop | 타입 | 기본값 | 설명 |
1325
+ | ------------------- | ------------------------------------ | ---------- | ---------------------------------------------------------------------- |
1326
+ | `items` | `TabItem[]` | — | **(필수)** 탭 목록 |
1327
+ | `value` | `string \| number` | — | **(필수)** 현재 선택된 탭 값 |
1328
+ | `onChange` | `(value: string \| number) => void` | — | 탭 변경 콜백 |
1329
+ | `size` | `"large"` \| `"medium"` \| `"small"` | `"medium"` | 크기. 폰트 기준: large = heading-1, medium = heading-2, small = body-1 |
1330
+ | `resize` | `"hug"` \| `"fill"` | `"hug"` | `hug`: 탭이 자연 폭, `fill`: 탭이 컨테이너를 균등 분배 |
1331
+ | `horizontalPadding` | `boolean` | `false` | `true` 시 왼쪽 20px 패딩 적용 |
1332
+ | `trailingIcon` | `ReactNode` | — | 탭 목록 오른쪽에 고정 노출되는 아이콘 슬롯. `resize="hug"`일 때만 적용 |
1333
+ | `label` | `string` | — | 스크린 리더용 그룹 레이블 (`aria-label`) |
1334
+ | `customStyle` | `CSSProperties` | — | 추가 인라인 스타일 |
1335
+
1336
+ **`TabItem`**
1337
+
1338
+ | 필드 | 타입 | 설명 |
1339
+ | ------------- | ------------------ | ------------------------------------------------------------------------------------- |
1340
+ | `value` | `string \| number` | **(필수)** 탭 고유 값 |
1341
+ | `label` | `ReactNode` | **(필수)** 탭 레이블 |
1342
+ | `badge` | `boolean` | `true` 시 레이블 우측 상단에 빨간 점(dot) 뱃지 표시 |
1343
+ | `numberBadge` | `number` | 양수일 때 레이블 우측에 숫자 뱃지 표시. 999 초과 시 "999+"로 클리핑. `badge`보다 우선 |
1344
+ | `disabled` | `boolean` | 비활성화. 키보드 탐색에서도 건너뜀 |
1345
+
1346
+ 유의사항:
1347
+
1348
+ - 탭이 뷰포트를 초과하면 드래그 가능한 캐러셀이 자동으로 활성화되고, 선택된 탭으로 자동 스크롤됩니다.
1349
+ - `resize="fill"` 모드에서도 탭 개수가 많아 초과하면 캐러셀이 활성화됩니다.
1350
+ - `trailingIcon`은 `resize="hug"`일 때만 렌더링됩니다.
1351
+ - `value`에 해당하는 item이 없으면 슬라이딩 인디케이터가 숨겨지고, 첫 번째 활성 탭에 키보드 포커스가 유지됩니다.
1352
+ - `numberBadge={0}` 또는 음수이면 뱃지가 표시되지 않습니다.
1353
+
1354
+ ---
1355
+
1219
1356
  ## 테이블
1220
1357
 
1221
1358
  `TableContainer`, `Table`, `Pagination` 3개의 컴포넌트로 구성됩니다. 내부적으로 [TanStack Table v8](https://tanstack.com/table/v8)을 사용하므로 `@tanstack/react-table` 패키지가 필요합니다.
@@ -1610,6 +1747,12 @@ import type {
1610
1747
  SegmentedControlSize,
1611
1748
  SegmentedControlItem,
1612
1749
 
1750
+ // Tab
1751
+ TabProps,
1752
+ TabItem,
1753
+ TabSize,
1754
+ TabResize,
1755
+
1613
1756
  // Table
1614
1757
  TableProps,
1615
1758
  TableVariant,
@@ -8,6 +8,8 @@ export interface DropdownOption {
8
8
  value: string;
9
9
  /** 옵션 라벨 좌측에 표시할 아이콘. `content={true}`일 때만 렌더링됩니다. */
10
10
  icon?: ReactNode;
11
+ /** 비활성화 여부. `true`이면 클릭이 차단되고 비활성 스타일이 적용됩니다. */
12
+ disabled?: boolean;
11
13
  }
12
14
  export interface DropdownProps {
13
15
  /** 드롭다운 트리거 엘리먼트.
@@ -40,7 +42,7 @@ export interface DropdownProps {
40
42
  */
41
43
  content?: boolean;
42
44
  /** 드롭다운에 표시할 옵션 아이템 배열.
43
- * 각 아이템은 `{ label, value, icon? }` 형태입니다.
45
+ * 각 아이템은 `{ label, value, icon?, disabled? }` 형태입니다.
44
46
  */
45
47
  options: DropdownOption[];
46
48
  /** 옵션 항목 선택 시 호출되는 콜백.
@@ -1,3 +1,3 @@
1
- import type { DropdownMenuProps } from './DropdownMenu.types';
1
+ import type { DropdownMenuProps } from "./DropdownMenu.types";
2
2
  declare function DropdownMenu({ listboxId, type, align, content, options, selectedValue, onSelect, textColor, closing, placement, }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
3
3
  export default DropdownMenu;
@@ -25,7 +25,7 @@ export interface DropdownMenuProps {
25
25
  */
26
26
  content?: boolean;
27
27
  /** 드롭다운에 표시할 옵션 아이템 배열.
28
- * 각 아이템은 `{ label, value, icon? }` 형태입니다.
28
+ * 각 아이템은 `{ label, value, icon?, disabled? }` 형태입니다.
29
29
  */
30
30
  options: DropdownOption[];
31
31
  /** 현재 선택된 옵션의 `value`.
@@ -0,0 +1,23 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ export type TabSize = "large" | "medium" | "small";
3
+ export type TabResize = "hug" | "fill";
4
+ export interface TabItem {
5
+ value: string | number;
6
+ label: ReactNode;
7
+ badge?: boolean;
8
+ numberBadge?: number;
9
+ disabled?: boolean;
10
+ }
11
+ export interface TabProps {
12
+ resize?: TabResize;
13
+ size?: TabSize;
14
+ items: TabItem[];
15
+ value: string | number;
16
+ onChange?: (value: string | number) => void;
17
+ horizontalPadding?: boolean;
18
+ trailingIcon?: ReactNode;
19
+ label?: string;
20
+ customStyle?: CSSProperties;
21
+ }
22
+ declare function Tab({ items, value, onChange, size, resize, horizontalPadding, trailingIcon, label, customStyle, }: TabProps): import("react/jsx-runtime").JSX.Element;
23
+ export default Tab;
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 Tab } from "./components/Tab/Tab";
58
+ export type { TabProps, TabItem, TabSize, TabResize, } from "./components/Tab/Tab";
57
59
  export { default as SegmentedControl } from "./components/SegmentedControl/SegmentedControl";
58
60
  export type { SegmentedControlProps, SegmentedControlVariant, SegmentedControlType, SegmentedControlSize, SegmentedControlItem, } from "./components/SegmentedControl/SegmentedControl";
59
61
  export { default as Table } from "./components/Table/Table";