@nexus-cross/design-system 1.0.13 → 1.1.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/claude-rules/nexus/CLAUDE.md +85 -0
- package/claude-rules/nexus/commands/nexus-audit.md +79 -0
- package/claude-rules/nexus/commands/nexus-component-map.md +85 -0
- package/claude-rules/nexus/commands/nexus-token-check.md +68 -0
- package/claude-rules/nexus/skills/nexus-design-system/SKILL.md +92 -0
- package/cursor-rules/nexus-ui-api.mdc +824 -41
- package/cursor-rules/nexus-ui-decisions.mdc +259 -0
- package/dist/accordion.js +0 -1
- package/dist/accordion.mjs +0 -1
- package/dist/alert.js +0 -1
- package/dist/alert.mjs +0 -1
- package/dist/avatar.js +0 -1
- package/dist/avatar.mjs +0 -1
- package/dist/badge.js +0 -1
- package/dist/badge.mjs +0 -1
- package/dist/breadcrumb.js +0 -1
- package/dist/breadcrumb.mjs +0 -1
- package/dist/button.js +0 -1
- package/dist/button.mjs +0 -1
- package/dist/carousel.js +0 -1
- package/dist/carousel.mjs +0 -1
- package/dist/checkbox.js +0 -1
- package/dist/checkbox.mjs +0 -1
- package/dist/chip.js +0 -1
- package/dist/chip.mjs +0 -1
- package/dist/chunks/chunk-2Z52NPWB.js +78 -0
- package/dist/chunks/chunk-46P52MFM.mjs +56 -0
- package/dist/chunks/{chunk-X3CTJ7TD.js → chunk-4KBFVIKX.js} +41 -11
- package/dist/chunks/chunk-56ZOOQFE.mjs +514 -0
- package/dist/chunks/chunk-5ASTWFJW.js +538 -0
- package/dist/chunks/{chunk-33UFQJIO.mjs → chunk-BJMXZJWO.mjs} +16 -5
- package/dist/chunks/chunk-EILXBLEV.mjs +5 -0
- package/dist/chunks/chunk-G3RLK2HS.js +7 -0
- package/dist/chunks/{chunk-YZV6FWE7.js → chunk-JLDQNDFT.js} +16 -5
- package/dist/chunks/{chunk-K574BYHQ.js → chunk-K3CK7NTP.js} +22 -4
- package/dist/chunks/{chunk-Z4YM7LU3.mjs → chunk-PIGHBDK5.mjs} +22 -4
- package/dist/chunks/{chunk-PEIEVKD5.js → chunk-RCIBLLSF.js} +11 -12
- package/dist/chunks/{chunk-MMCA33FW.mjs → chunk-RSFLNWOM.mjs} +41 -11
- package/dist/chunks/{chunk-K2TBLM3F.mjs → chunk-THBE27U3.mjs} +11 -12
- package/dist/client-only.js +0 -1
- package/dist/client-only.mjs +0 -1
- package/dist/combobox.js +16 -0
- package/dist/combobox.mjs +3 -0
- package/dist/components/Combobox.d.ts +48 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/DataGrid.d.ts +44 -0
- package/dist/components/DataGrid.d.ts.map +1 -0
- package/dist/components/DataList.d.ts +3 -1
- package/dist/components/DataList.d.ts.map +1 -1
- package/dist/components/RadioGroup.d.ts +4 -0
- package/dist/components/RadioGroup.d.ts.map +1 -1
- package/dist/components/Stepper.d.ts.map +1 -1
- package/dist/components/ToggleGroup.d.ts +2 -1
- package/dist/components/ToggleGroup.d.ts.map +1 -1
- package/dist/countdown.js +0 -1
- package/dist/countdown.mjs +0 -1
- package/dist/counter.js +0 -1
- package/dist/counter.mjs +0 -1
- package/dist/data-grid.js +14 -0
- package/dist/data-grid.mjs +5 -0
- package/dist/data-list.js +2 -3
- package/dist/data-list.mjs +1 -2
- package/dist/date-picker.js +0 -1
- package/dist/date-picker.mjs +0 -1
- package/dist/divider.js +0 -1
- package/dist/divider.mjs +0 -1
- package/dist/drawer.js +0 -1
- package/dist/drawer.mjs +0 -1
- package/dist/dropdown-menu.js +0 -1
- package/dist/dropdown-menu.mjs +0 -1
- package/dist/ellipsis.js +0 -1
- package/dist/ellipsis.mjs +0 -1
- package/dist/empty-state.js +0 -1
- package/dist/empty-state.mjs +0 -1
- package/dist/error-boundary.js +0 -1
- package/dist/error-boundary.mjs +0 -1
- package/dist/hooks/useCheckDevice.js +0 -1
- package/dist/hooks/useCheckDevice.mjs +0 -1
- package/dist/hooks/useClickOutside.js +0 -1
- package/dist/hooks/useClickOutside.mjs +0 -1
- package/dist/hooks/useDraggableBottomSheet.js +0 -1
- package/dist/hooks/useDraggableBottomSheet.mjs +0 -1
- package/dist/hooks/useDraggableWindow.js +0 -1
- package/dist/hooks/useDraggableWindow.mjs +0 -1
- package/dist/hooks/useInView.js +0 -1
- package/dist/hooks/useInView.mjs +0 -1
- package/dist/hooks/useModal.js +0 -1
- package/dist/hooks/useModal.mjs +0 -1
- package/dist/image-upload.js +0 -1
- package/dist/image-upload.mjs +0 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -88
- package/dist/index.mjs +12 -12
- package/dist/infinite-scroll.js +0 -1
- package/dist/infinite-scroll.mjs +0 -1
- package/dist/marquee.js +0 -1
- package/dist/marquee.mjs +0 -1
- package/dist/modal/index.js +11 -12
- package/dist/modal/index.mjs +2 -3
- package/dist/number-input.js +0 -1
- package/dist/number-input.mjs +0 -1
- package/dist/nx-image.js +0 -1
- package/dist/nx-image.mjs +0 -1
- package/dist/pagination.js +0 -1
- package/dist/pagination.mjs +0 -1
- package/dist/popover.js +0 -1
- package/dist/popover.mjs +0 -1
- package/dist/price-input.js +0 -1
- package/dist/price-input.mjs +0 -1
- package/dist/progress.js +0 -1
- package/dist/progress.mjs +0 -1
- package/dist/radio-group.js +5 -6
- package/dist/radio-group.mjs +1 -2
- package/dist/schemas/_all.json +308 -117
- package/dist/schemas/accordion.d.ts.map +1 -1
- package/dist/schemas/accordion.json +1 -1
- package/dist/schemas/alert.d.ts.map +1 -1
- package/dist/schemas/alert.json +1 -1
- package/dist/schemas/avatar.d.ts.map +1 -1
- package/dist/schemas/avatar.json +1 -1
- package/dist/schemas/badge.d.ts.map +1 -1
- package/dist/schemas/badge.json +1 -1
- package/dist/schemas/breadcrumb.d.ts.map +1 -1
- package/dist/schemas/breadcrumb.json +1 -1
- package/dist/schemas/button.d.ts.map +1 -1
- package/dist/schemas/button.json +1 -1
- package/dist/schemas/carousel.d.ts.map +1 -1
- package/dist/schemas/carousel.json +1 -1
- package/dist/schemas/checkBox.json +1 -1
- package/dist/schemas/checkbox.d.ts.map +1 -1
- package/dist/schemas/chip.d.ts.map +1 -1
- package/dist/schemas/chip.json +1 -1
- package/dist/schemas/client-only.d.ts.map +1 -1
- package/dist/schemas/clientOnly.json +1 -1
- package/dist/schemas/combobox.d.ts +85 -0
- package/dist/schemas/combobox.d.ts.map +1 -0
- package/dist/schemas/combobox.json +98 -0
- package/dist/schemas/comboboxOption.json +30 -0
- package/dist/schemas/countdown.d.ts.map +1 -1
- package/dist/schemas/countdown.json +1 -1
- package/dist/schemas/counter.d.ts.map +1 -1
- package/dist/schemas/counter.json +1 -1
- package/dist/schemas/data-grid.d.ts +74 -0
- package/dist/schemas/data-grid.d.ts.map +1 -0
- package/dist/schemas/data-list.d.ts.map +1 -1
- package/dist/schemas/dataGrid.json +102 -0
- package/dist/schemas/dataList.json +1 -1
- package/dist/schemas/date-picker.d.ts.map +1 -1
- package/dist/schemas/datePicker.json +1 -1
- package/dist/schemas/divider.d.ts.map +1 -1
- package/dist/schemas/divider.json +1 -1
- package/dist/schemas/drawer.d.ts.map +1 -1
- package/dist/schemas/drawer.json +1 -1
- package/dist/schemas/dropdown-menu.d.ts.map +1 -1
- package/dist/schemas/dropdownMenu.json +1 -1
- package/dist/schemas/ellipsis.d.ts.map +1 -1
- package/dist/schemas/ellipsis.json +1 -1
- package/dist/schemas/empty-state.d.ts.map +1 -1
- package/dist/schemas/emptyState.json +1 -1
- package/dist/schemas/error-boundary.d.ts.map +1 -1
- package/dist/schemas/errorBoundary.json +1 -1
- package/dist/schemas/image-upload.d.ts.map +1 -1
- package/dist/schemas/imageUpload.json +1 -1
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/infinite-scroll.d.ts.map +1 -1
- package/dist/schemas/infiniteScroll.json +1 -1
- package/dist/schemas/marquee.d.ts.map +1 -1
- package/dist/schemas/marquee.json +1 -1
- package/dist/schemas/modal.d.ts.map +1 -1
- package/dist/schemas/modalTemplate.json +1 -1
- package/dist/schemas/number-input.d.ts.map +1 -1
- package/dist/schemas/numberInput.json +1 -1
- package/dist/schemas/nx-image.d.ts.map +1 -1
- package/dist/schemas/nxImage.json +1 -1
- package/dist/schemas/pagination.d.ts.map +1 -1
- package/dist/schemas/pagination.json +1 -1
- package/dist/schemas/popover.d.ts.map +1 -1
- package/dist/schemas/popover.json +1 -1
- package/dist/schemas/price-input.d.ts.map +1 -1
- package/dist/schemas/priceInput.json +1 -1
- package/dist/schemas/progress.d.ts.map +1 -1
- package/dist/schemas/progress.json +1 -1
- package/dist/schemas/radio-group.d.ts +9 -0
- package/dist/schemas/radio-group.d.ts.map +1 -1
- package/dist/schemas/radioGroup.json +10 -1
- package/dist/schemas/radioItem.json +11 -0
- package/dist/schemas/select.d.ts.map +1 -1
- package/dist/schemas/select.json +1 -1
- package/dist/schemas/skeleton.d.ts.map +1 -1
- package/dist/schemas/skeleton.json +1 -1
- package/dist/schemas/slider.d.ts.map +1 -1
- package/dist/schemas/slider.json +1 -1
- package/dist/schemas/spinner.d.ts.map +1 -1
- package/dist/schemas/spinner.json +1 -1
- package/dist/schemas/stepper.d.ts.map +1 -1
- package/dist/schemas/stepper.json +1 -1
- package/dist/schemas/switch.d.ts.map +1 -1
- package/dist/schemas/switch.json +1 -1
- package/dist/schemas/tab.d.ts.map +1 -1
- package/dist/schemas/tab.json +1 -1
- package/dist/schemas/table.d.ts.map +1 -1
- package/dist/schemas/table.json +1 -1
- package/dist/schemas/tableRow.json +1 -1
- package/dist/schemas/tag-input.d.ts.map +1 -1
- package/dist/schemas/tagInput.json +1 -1
- package/dist/schemas/tdColumn.json +1 -1
- package/dist/schemas/text-area.d.ts.map +1 -1
- package/dist/schemas/text-input.d.ts +2 -2
- package/dist/schemas/text-input.d.ts.map +1 -1
- package/dist/schemas/textArea.json +1 -1
- package/dist/schemas/textInput.json +1 -1
- package/dist/schemas/toast.d.ts.map +1 -1
- package/dist/schemas/toastOptions.json +1 -1
- package/dist/schemas/toaster.json +1 -1
- package/dist/schemas/toggle-group.d.ts +6 -3
- package/dist/schemas/toggle-group.d.ts.map +1 -1
- package/dist/schemas/toggleGroup.json +9 -3
- package/dist/schemas/tooltip.d.ts.map +1 -1
- package/dist/schemas/tooltip.json +1 -1
- package/dist/schemas/virtual-scroll.d.ts.map +1 -1
- package/dist/schemas/virtualGrid.json +1 -1
- package/dist/schemas/virtualList.json +1 -1
- package/dist/schemas.js +867 -66
- package/dist/schemas.mjs +865 -66
- package/dist/select.js +0 -1
- package/dist/select.mjs +0 -1
- package/dist/skeleton.js +0 -1
- package/dist/skeleton.mjs +0 -1
- package/dist/slider.js +0 -1
- package/dist/slider.mjs +0 -1
- package/dist/spinner.js +0 -1
- package/dist/spinner.mjs +0 -1
- package/dist/stepper.js +3 -4
- package/dist/stepper.mjs +1 -2
- package/dist/styles/.generated/built.d.ts +1 -1
- package/dist/styles/.generated/built.d.ts.map +1 -1
- package/dist/styles/layer.js +2 -3
- package/dist/styles/layer.mjs +1 -2
- package/dist/styles.css +554 -51
- package/dist/styles.js +2 -3
- package/dist/styles.layered.css +554 -51
- package/dist/styles.mjs +1 -2
- package/dist/switch.js +0 -1
- package/dist/switch.mjs +0 -1
- package/dist/tab.js +0 -1
- package/dist/tab.mjs +0 -1
- package/dist/table.js +0 -1
- package/dist/table.mjs +0 -1
- package/dist/tag-input.js +0 -1
- package/dist/tag-input.mjs +0 -1
- package/dist/text-area.js +0 -1
- package/dist/text-area.mjs +0 -1
- package/dist/text-input.js +0 -1
- package/dist/text-input.mjs +0 -1
- package/dist/toast.js +0 -1
- package/dist/toast.mjs +0 -1
- package/dist/toggle-group.js +3 -4
- package/dist/toggle-group.mjs +1 -2
- package/dist/tooltip.js +0 -1
- package/dist/tooltip.mjs +0 -1
- package/dist/utils/cn.js +0 -1
- package/dist/utils/cn.mjs +0 -1
- package/dist/utils/scroll.js +0 -1
- package/dist/utils/scroll.mjs +0 -1
- package/dist/virtual-scroll.js +0 -1
- package/dist/virtual-scroll.mjs +0 -1
- package/package.json +14 -8
- package/scripts/setup-cursor-rules.cjs +164 -27
- package/dist/chunks/chunk-22ULI3BF.js +0 -21
- package/dist/chunks/chunk-6ECGMUT6.mjs +0 -5
- package/dist/chunks/chunk-CVYXRSXT.mjs +0 -8
- package/dist/chunks/chunk-I252NERB.mjs +0 -21
- package/dist/chunks/chunk-JNMCYWGY.js +0 -10
- package/dist/chunks/chunk-V35IEPRL.js +0 -7
- package/dist/components/ThemeProvider.d.ts +0 -25
- package/dist/components/ThemeProvider.d.ts.map +0 -1
- package/dist/schemas/theme-provider.d.ts +0 -36
- package/dist/schemas/theme-provider.d.ts.map +0 -1
- package/dist/schemas/themeProvider.json +0 -65
- package/dist/theme-provider.js +0 -15
- package/dist/theme-provider.mjs +0 -2
- package/dist/chunks/{chunk-CWMLTXOH.mjs → chunk-5ZVPTIL3.mjs} +1 -1
- package/dist/chunks/{chunk-HFBTS42N.js → chunk-7F4SOLAC.js} +1 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "@nexus-cross/design-system component selection guide — when to use which component, anti-patterns, and decision matrices for ambiguous cases (Combobox vs Select, Drawer vs Modal, Tooltip vs Popover, etc.)"
|
|
3
|
+
globs: "**/*.tsx,**/*.jsx"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# NEXUS Component Selection Guide
|
|
8
|
+
|
|
9
|
+
UI 작업 시 어떤 컴포넌트를 골라야 할지 결정하는 가이드. **`ui-components.md`로 1차 매핑 → 모호하면 이 문서 참조.**
|
|
10
|
+
|
|
11
|
+
## Selection vs. Action
|
|
12
|
+
|
|
13
|
+
옵션 중 **값을 고르는** 컴포넌트와 **액션을 트리거하는** 컴포넌트는 다릅니다.
|
|
14
|
+
|
|
15
|
+
| 사용자 의도 | 컴포넌트 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 1개 값 선택 (form field) | Select / Combobox / RadioGroup |
|
|
18
|
+
| 여러 값 선택 (form field) | CheckBox / Combobox(multiple) / TagInput |
|
|
19
|
+
| 액션 메뉴 (저장/삭제/공유 등) | DropdownMenu |
|
|
20
|
+
| 두 상태 토글 (on/off) | Switch |
|
|
21
|
+
| n개 중 하나 선택 (즉시 적용) | ToggleGroup (single) |
|
|
22
|
+
| n개 중 여러개 선택 (즉시 적용) | ToggleGroup (multiple) |
|
|
23
|
+
|
|
24
|
+
> **핵심 차이**: form field는 "값"을 보유하고 submit 시 전송. DropdownMenu는 클릭 시 함수가 실행되는 액션. Switch/ToggleGroup은 즉시 상태 변경 (저장 버튼 없음).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Single-select 입력: Select vs Combobox vs RadioGroup vs ToggleGroup
|
|
29
|
+
|
|
30
|
+
| 상황 | 컴포넌트 | 이유 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| 옵션 ≤ 4개, 모두 보여주고 싶음 | **RadioGroup** 또는 **ToggleGroup** | 한눈에 비교, 클릭 1회 |
|
|
33
|
+
| 옵션 ≤ 7개, 공간 절약 필요 | **Select** | 가벼움, 빠른 마운트 |
|
|
34
|
+
| 옵션 ≥ 7개 또는 라벨이 긺 | **Combobox** | 검색·필터·키보드 탐색 |
|
|
35
|
+
| 옵션이 서버에서 비동기로 옴 | **Combobox** (`loading`, `onSearch`) | async 지원 |
|
|
36
|
+
| 옵션이 동적 (사용자가 추가 가능) | **Combobox** + `creatable` 패턴 | 자유 입력 |
|
|
37
|
+
| 시각적 비교가 핵심 (정렬·뷰모드 등) | **ToggleGroup** | 즉시 적용 + 시각 피드백 |
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// ❌ 옵션 20개에 Select
|
|
41
|
+
<Select><SelectItem>...</SelectItem>...</Select>
|
|
42
|
+
|
|
43
|
+
// ✅ 옵션 20개에 Combobox
|
|
44
|
+
<Combobox options={options} placeholder="검색하여 선택" />
|
|
45
|
+
|
|
46
|
+
// ❌ 옵션 3개에 Select
|
|
47
|
+
<Select><SelectItem>S</SelectItem><SelectItem>M</SelectItem><SelectItem>L</SelectItem></Select>
|
|
48
|
+
|
|
49
|
+
// ✅ 옵션 3개에 RadioGroup 또는 ToggleGroup
|
|
50
|
+
<RadioGroup><RadioItem value="S">S</RadioItem>...</RadioGroup>
|
|
51
|
+
<ToggleGroup type="single" items={[{value:'S',label:'S'},...]} />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Multi-select 입력: CheckBox vs Combobox(multiple) vs TagInput vs ToggleGroup(multiple)
|
|
57
|
+
|
|
58
|
+
| 상황 | 컴포넌트 | 이유 |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| 옵션 ≤ 7개 모두 보여주기 | **CheckBox** 그룹 | 한눈에 체크 상태 비교 |
|
|
61
|
+
| 옵션 ≥ 7개 검색 필요 | **Combobox** (`multiple`) | 칩 + 검색 |
|
|
62
|
+
| 사용자가 자유 입력 (이메일·태그 등) | **TagInput** | enter로 토큰 생성 |
|
|
63
|
+
| 즉시 필터 적용 (검색 페이지의 카테고리 필터) | **ToggleGroup** (`multiple`) | 토글 UI |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 오버레이: Modal vs Drawer vs Popover vs Tooltip vs DropdownMenu
|
|
68
|
+
|
|
69
|
+
| 상황 | 컴포넌트 | 이유 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 사용자 결정 강제 (확인/삭제/저장) | **Modal** | 백드롭 + focus trap |
|
|
72
|
+
| 부수 정보 또는 부가 작업 | **Drawer** | 메인 콘텐츠 가림 적음 |
|
|
73
|
+
| trigger 옆에 작은 컨텍스트 정보 | **Popover** | 위치 anchor, 클릭 dismiss |
|
|
74
|
+
| trigger hover 시 짧은 힌트 | **Tooltip** | hover/focus만, 클릭 X |
|
|
75
|
+
| 액션 메뉴 (저장/삭제/공유) | **DropdownMenu** | role=menu, 화살표 키 탐색 |
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// ❌ 단순 도움말에 Modal
|
|
79
|
+
<Modal title="도움말">짧은 설명...</Modal>
|
|
80
|
+
|
|
81
|
+
// ✅ 단순 도움말에 Tooltip
|
|
82
|
+
<Tooltip content="짧은 설명...">
|
|
83
|
+
<button><HelpIcon /></button>
|
|
84
|
+
</Tooltip>
|
|
85
|
+
|
|
86
|
+
// ❌ 액션 메뉴에 Popover 안에 button 직접 작성
|
|
87
|
+
<Popover><button onClick={save}>저장</button>...</Popover>
|
|
88
|
+
|
|
89
|
+
// ✅ 액션 메뉴는 DropdownMenu (a11y 자동)
|
|
90
|
+
<DropdownMenu items={[{label:'저장', onSelect: save}, ...]} />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**판단 기준**:
|
|
94
|
+
- "사용자가 메인 작업으로부터 잠깐 분리되어야 하는가?" → Modal
|
|
95
|
+
- "사용자가 사이드 패널에서 보조 작업을 하는가?" → Drawer
|
|
96
|
+
- "trigger 가까이에 작은 보조 UI가 필요한가?" → Popover
|
|
97
|
+
- "텍스트만 잠깐 보여주면 되는가?" → Tooltip
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 알림: toast vs Alert vs Modal vs EmptyState
|
|
102
|
+
|
|
103
|
+
| 상황 | 컴포넌트 | 이유 |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| 일시적 피드백 (저장 완료, 복사됨 등) | **toast()** | 자동 dismiss, non-blocking |
|
|
106
|
+
| 영구적 인라인 경고 (form 위 에러 요약) | **Alert** | 페이지 영역에 고정 |
|
|
107
|
+
| 파괴적 액션 확인 ("정말 삭제?") | **modal.confirm()** | blocking, 명확한 결정 강제 |
|
|
108
|
+
| 데이터 0개 상태 (목록·표) | **EmptyState** | 다음 액션 유도 |
|
|
109
|
+
| 페이지 전체 에러 | **EmptyState** (variant: error) | 회복 액션 제공 |
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// ❌ "저장됨" 알림에 Alert
|
|
113
|
+
<Alert>저장되었습니다</Alert>
|
|
114
|
+
|
|
115
|
+
// ✅ "저장됨"은 toast
|
|
116
|
+
toast.success('저장되었습니다');
|
|
117
|
+
|
|
118
|
+
// ❌ 삭제 확인에 toast (사용자가 못 보고 지나칠 수 있음)
|
|
119
|
+
toast('삭제하시겠습니까?', { action: { ... }});
|
|
120
|
+
|
|
121
|
+
// ✅ 삭제 확인은 modal.confirm
|
|
122
|
+
const ok = await modal.confirm({ title: '정말 삭제하시겠습니까?', danger: true });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 진행 상태: Spinner vs Skeleton vs Progress
|
|
128
|
+
|
|
129
|
+
| 상황 | 컴포넌트 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| 짧은 로딩 (< 1초), 결과만 기다림 | **Spinner** |
|
|
132
|
+
| 긴 로딩, 레이아웃 미리 보여주기 | **Skeleton** (구조 그대로) |
|
|
133
|
+
| 진행률을 알 수 있음 (업로드·동기화) | **Progress** |
|
|
134
|
+
| 무한 스트림 더 불러오기 | (UI 없음) **InfiniteScroll** + 하단 Spinner |
|
|
135
|
+
|
|
136
|
+
> Skeleton은 항상 **실제 컴포넌트와 동일한 형태**여야 함. 다른 모양이면 의미 없음.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 리스트: DataList vs DataGrid vs VirtualList vs Carousel vs InfiniteScroll
|
|
141
|
+
|
|
142
|
+
| 상황 | 컴포넌트 |
|
|
143
|
+
|---|---|
|
|
144
|
+
| 단순 카드/항목 그리드 | **DataList** (간단), **DataGrid** (정렬·필터) |
|
|
145
|
+
| 항목 수 ≥ 100, 모두 같은 높이 | **VirtualList** |
|
|
146
|
+
| 가로 슬라이드 (배너·갤러리) | **Carousel** |
|
|
147
|
+
| 무한 스크롤 페이지네이션 | **InfiniteScroll** |
|
|
148
|
+
| 페이지 단위 명시적 이동 | **Pagination** |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 입력: TextInput vs TextArea vs NumberInput vs PriceInput vs DatePicker
|
|
153
|
+
|
|
154
|
+
| 데이터 타입 | 컴포넌트 |
|
|
155
|
+
|---|---|
|
|
156
|
+
| 짧은 텍스트 (이름·이메일·검색) | **TextInput** |
|
|
157
|
+
| 긴 텍스트 (메모·설명) | **TextArea** |
|
|
158
|
+
| 정수·소수 (수량·점수) | **NumberInput** |
|
|
159
|
+
| 통화 (천 단위 콤마·원/달러) | **PriceInput** |
|
|
160
|
+
| 날짜·기간 | **DatePicker** |
|
|
161
|
+
| 파일 (이미지) | **ImageUpload** |
|
|
162
|
+
|
|
163
|
+
> ❌ `TextInput type="number"` 금지. `NumberInput` 쓸 것 (단위·step·키보드 ↑↓ 자동).
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Tab vs ToggleGroup vs RadioGroup
|
|
168
|
+
|
|
169
|
+
세 개 다 "n개 중 하나 선택"인데 의미가 다릅니다.
|
|
170
|
+
|
|
171
|
+
| 컴포넌트 | 의미 | 예시 |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| **Tab** | 영역 전환 (콘텐츠 스왑) | 설정 페이지의 General/Security/Billing |
|
|
174
|
+
| **ToggleGroup** | 즉시 적용되는 옵션 | 정렬: 최신순/인기순, 뷰: 그리드/리스트 |
|
|
175
|
+
| **RadioGroup** | form 필드의 단일 선택 | 결제 방법: 카드/계좌이체 |
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
// ❌ 페이지 영역 전환에 RadioGroup
|
|
179
|
+
<RadioGroup onValueChange={setView}>...</RadioGroup>
|
|
180
|
+
{view === 'general' && <GeneralPanel />}
|
|
181
|
+
|
|
182
|
+
// ✅ 페이지 영역 전환은 Tab
|
|
183
|
+
<Tab.Root>
|
|
184
|
+
<Tab.List>
|
|
185
|
+
<Tab.Trigger value="general">General</Tab.Trigger>
|
|
186
|
+
...
|
|
187
|
+
</Tab.List>
|
|
188
|
+
<Tab.Content value="general"><GeneralPanel /></Tab.Content>
|
|
189
|
+
</Tab.Root>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Anti-patterns 자주 발견
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// ❌ raw 버튼 + 임의 색상
|
|
198
|
+
<button className="bg-blue-500 text-white px-4 py-2">저장</button>
|
|
199
|
+
// ✅
|
|
200
|
+
<Button semantic="primary">저장</Button>
|
|
201
|
+
|
|
202
|
+
// ❌ raw input + 라벨 따로
|
|
203
|
+
<label>이메일<input type="email" /></label>
|
|
204
|
+
// ✅
|
|
205
|
+
<TextInput type="email" label="이메일" />
|
|
206
|
+
|
|
207
|
+
// ❌ Tooltip 안에 클릭 가능한 콘텐츠
|
|
208
|
+
<Tooltip content={<button onClick={...}>액션</button>}>...</Tooltip>
|
|
209
|
+
// ✅ 클릭 가능 콘텐츠는 Popover 또는 DropdownMenu
|
|
210
|
+
<Popover content={<Button onClick={...}>액션</Button>}>...</Popover>
|
|
211
|
+
|
|
212
|
+
// ❌ 토글 스위치에 CheckBox
|
|
213
|
+
<CheckBox label="알림 받기" />
|
|
214
|
+
// ✅ 즉시 적용되는 binary 설정은 Switch
|
|
215
|
+
<Switch label="알림 받기" />
|
|
216
|
+
|
|
217
|
+
// ❌ 동일 form 안에 비슷한 모양 컴포넌트 혼용
|
|
218
|
+
<RadioGroup>...</RadioGroup>
|
|
219
|
+
<ToggleGroup type="single">...</ToggleGroup>
|
|
220
|
+
// ✅ form field 통일 — 같은 form은 Radio 또는 Select 한 가지로
|
|
221
|
+
|
|
222
|
+
// ❌ 직접 만든 dropdown
|
|
223
|
+
<div onClick={() => setOpen(!open)}>{open && <div>...</div>}</div>
|
|
224
|
+
// ✅
|
|
225
|
+
<DropdownMenu items={...} />
|
|
226
|
+
|
|
227
|
+
// ❌ !important로 NEXUS 스타일 강제 변경
|
|
228
|
+
<Button className="!bg-red-500">...</Button>
|
|
229
|
+
// ✅ semantic prop 사용
|
|
230
|
+
<Button semantic="danger">...</Button>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 결정 트리 (가장 자주 묻는 케이스)
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
"드롭다운이 필요해"
|
|
239
|
+
├─ 값 선택? ─ 옵션 ≤7? ─ Select
|
|
240
|
+
│ ─ 옵션 >7? ─ Combobox
|
|
241
|
+
└─ 액션 메뉴? ─ DropdownMenu
|
|
242
|
+
|
|
243
|
+
"체크 같은 거 필요해"
|
|
244
|
+
├─ 즉시 적용? ─ Switch (1개) / ToggleGroup (n개)
|
|
245
|
+
├─ form field 다중 선택? ─ CheckBox 그룹
|
|
246
|
+
└─ form field 단일 선택? ─ RadioGroup
|
|
247
|
+
|
|
248
|
+
"팝업 띄워줘"
|
|
249
|
+
├─ 결정 강제? ─ Modal
|
|
250
|
+
├─ 보조 작업? ─ Drawer
|
|
251
|
+
├─ 짧은 힌트? ─ Tooltip
|
|
252
|
+
├─ trigger 옆 정보? ─ Popover
|
|
253
|
+
└─ 액션 메뉴? ─ DropdownMenu
|
|
254
|
+
|
|
255
|
+
"알림 보여줘"
|
|
256
|
+
├─ 일시적? ─ toast()
|
|
257
|
+
├─ 인라인 영구? ─ Alert
|
|
258
|
+
└─ 확인 필요? ─ modal.confirm()
|
|
259
|
+
```
|
package/dist/accordion.js
CHANGED
package/dist/accordion.mjs
CHANGED
package/dist/alert.js
CHANGED
package/dist/alert.mjs
CHANGED
package/dist/avatar.js
CHANGED
package/dist/avatar.mjs
CHANGED
package/dist/badge.js
CHANGED
package/dist/badge.mjs
CHANGED
package/dist/breadcrumb.js
CHANGED
package/dist/breadcrumb.mjs
CHANGED
package/dist/button.js
CHANGED
package/dist/button.mjs
CHANGED
package/dist/carousel.js
CHANGED
package/dist/carousel.mjs
CHANGED
package/dist/checkbox.js
CHANGED
package/dist/checkbox.mjs
CHANGED
package/dist/chip.js
CHANGED
package/dist/chip.mjs
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkRCIBLLSF_js = require('./chunk-RCIBLLSF.js');
|
|
4
|
+
var chunkCZC76ZD5_js = require('./chunk-CZC76ZD5.js');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
27
|
+
|
|
28
|
+
var BREAKPOINT_VAR_MAP = {
|
|
29
|
+
base: "--nexus-dg-cols",
|
|
30
|
+
sm: "--nexus-dg-cols-sm",
|
|
31
|
+
md: "--nexus-dg-cols-md",
|
|
32
|
+
lg: "--nexus-dg-cols-lg",
|
|
33
|
+
xl: "--nexus-dg-cols-xl",
|
|
34
|
+
"2xl": "--nexus-dg-cols-2xl"
|
|
35
|
+
};
|
|
36
|
+
function buildColumnsStyle(columns) {
|
|
37
|
+
const style = {};
|
|
38
|
+
if (typeof columns === "number") {
|
|
39
|
+
style[BREAKPOINT_VAR_MAP.base] = columns;
|
|
40
|
+
return style;
|
|
41
|
+
}
|
|
42
|
+
for (const key of Object.keys(BREAKPOINT_VAR_MAP)) {
|
|
43
|
+
const value = columns[key];
|
|
44
|
+
if (typeof value === "number") {
|
|
45
|
+
style[BREAKPOINT_VAR_MAP[key]] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return style;
|
|
49
|
+
}
|
|
50
|
+
function DataGrid({
|
|
51
|
+
list,
|
|
52
|
+
columns,
|
|
53
|
+
gap,
|
|
54
|
+
className,
|
|
55
|
+
...rest
|
|
56
|
+
}) {
|
|
57
|
+
const gridStyle = React__namespace.useMemo(() => {
|
|
58
|
+
const style = {
|
|
59
|
+
...buildColumnsStyle(columns)
|
|
60
|
+
};
|
|
61
|
+
if (gap !== void 0) {
|
|
62
|
+
style["--nexus-dg-gap"] = typeof gap === "number" ? `${gap}px` : gap;
|
|
63
|
+
}
|
|
64
|
+
return style;
|
|
65
|
+
}, [columns, gap]);
|
|
66
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
67
|
+
chunkRCIBLLSF_js.DataList,
|
|
68
|
+
{
|
|
69
|
+
list,
|
|
70
|
+
className: chunkCZC76ZD5_js.cn("nexus-data-grid", className),
|
|
71
|
+
style: gridStyle,
|
|
72
|
+
...rest
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
DataGrid.displayName = "DataGrid";
|
|
77
|
+
|
|
78
|
+
exports.DataGrid = DataGrid;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DataList } from './chunk-THBE27U3.mjs';
|
|
2
|
+
import { cn } from './chunk-MCKOWMLS.mjs';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var BREAKPOINT_VAR_MAP = {
|
|
7
|
+
base: "--nexus-dg-cols",
|
|
8
|
+
sm: "--nexus-dg-cols-sm",
|
|
9
|
+
md: "--nexus-dg-cols-md",
|
|
10
|
+
lg: "--nexus-dg-cols-lg",
|
|
11
|
+
xl: "--nexus-dg-cols-xl",
|
|
12
|
+
"2xl": "--nexus-dg-cols-2xl"
|
|
13
|
+
};
|
|
14
|
+
function buildColumnsStyle(columns) {
|
|
15
|
+
const style = {};
|
|
16
|
+
if (typeof columns === "number") {
|
|
17
|
+
style[BREAKPOINT_VAR_MAP.base] = columns;
|
|
18
|
+
return style;
|
|
19
|
+
}
|
|
20
|
+
for (const key of Object.keys(BREAKPOINT_VAR_MAP)) {
|
|
21
|
+
const value = columns[key];
|
|
22
|
+
if (typeof value === "number") {
|
|
23
|
+
style[BREAKPOINT_VAR_MAP[key]] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return style;
|
|
27
|
+
}
|
|
28
|
+
function DataGrid({
|
|
29
|
+
list,
|
|
30
|
+
columns,
|
|
31
|
+
gap,
|
|
32
|
+
className,
|
|
33
|
+
...rest
|
|
34
|
+
}) {
|
|
35
|
+
const gridStyle = React.useMemo(() => {
|
|
36
|
+
const style = {
|
|
37
|
+
...buildColumnsStyle(columns)
|
|
38
|
+
};
|
|
39
|
+
if (gap !== void 0) {
|
|
40
|
+
style["--nexus-dg-gap"] = typeof gap === "number" ? `${gap}px` : gap;
|
|
41
|
+
}
|
|
42
|
+
return style;
|
|
43
|
+
}, [columns, gap]);
|
|
44
|
+
return /* @__PURE__ */ jsx(
|
|
45
|
+
DataList,
|
|
46
|
+
{
|
|
47
|
+
list,
|
|
48
|
+
className: cn("nexus-data-grid", className),
|
|
49
|
+
style: gridStyle,
|
|
50
|
+
...rest
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
DataGrid.displayName = "DataGrid";
|
|
55
|
+
|
|
56
|
+
export { DataGrid };
|
|
@@ -41,10 +41,38 @@ var stepperVariants = classVarianceAuthority.cva("nexus-stepper", {
|
|
|
41
41
|
size: "md"
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
|
-
var CheckIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
44
|
+
var CheckIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
45
|
+
"svg",
|
|
46
|
+
{
|
|
47
|
+
className,
|
|
48
|
+
viewBox: "0 0 14 14",
|
|
49
|
+
fill: "none",
|
|
50
|
+
stroke: "currentColor",
|
|
51
|
+
strokeWidth: "2",
|
|
52
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.5 7.5l3 3 6-6", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
53
|
+
}
|
|
54
|
+
);
|
|
45
55
|
var Stepper = React__namespace.forwardRef(
|
|
46
|
-
({
|
|
56
|
+
({
|
|
57
|
+
className,
|
|
58
|
+
orientation,
|
|
59
|
+
size,
|
|
60
|
+
steps,
|
|
61
|
+
current = 0,
|
|
62
|
+
status = "process",
|
|
63
|
+
...props
|
|
64
|
+
}, ref) => {
|
|
47
65
|
const isHorizontal = orientation !== "vertical";
|
|
66
|
+
const prevCurrentRef = React__namespace.useRef(current);
|
|
67
|
+
const [animatingIdx, setAnimatingIdx] = React__namespace.useState(null);
|
|
68
|
+
React__namespace.useEffect(() => {
|
|
69
|
+
if (prevCurrentRef.current !== current) {
|
|
70
|
+
setAnimatingIdx(current);
|
|
71
|
+
prevCurrentRef.current = current;
|
|
72
|
+
const timer = setTimeout(() => setAnimatingIdx(null), 600);
|
|
73
|
+
return () => clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
}, [current]);
|
|
48
76
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
49
77
|
"div",
|
|
50
78
|
{
|
|
@@ -56,12 +84,15 @@ var Stepper = React__namespace.forwardRef(
|
|
|
56
84
|
const state = i < current ? "completed" : i === current ? status === "error" ? "error" : "active" : "pending";
|
|
57
85
|
const isFirst = i === 0;
|
|
58
86
|
const isLast = i === steps.length - 1;
|
|
59
|
-
const
|
|
60
|
-
const nextCompleted = i < current;
|
|
87
|
+
const isAnimating = animatingIdx === i;
|
|
61
88
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
62
89
|
"div",
|
|
63
90
|
{
|
|
64
|
-
className: chunkCZC76ZD5_js.cn(
|
|
91
|
+
className: chunkCZC76ZD5_js.cn(
|
|
92
|
+
"nexus-stepper__step",
|
|
93
|
+
`nexus-stepper__step--${state}`,
|
|
94
|
+
isAnimating && "nexus-stepper__step--animating"
|
|
95
|
+
),
|
|
65
96
|
children: [
|
|
66
97
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-stepper__indicator-wrap", children: isHorizontal ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
67
98
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -69,8 +100,7 @@ var Stepper = React__namespace.forwardRef(
|
|
|
69
100
|
{
|
|
70
101
|
className: chunkCZC76ZD5_js.cn(
|
|
71
102
|
"nexus-stepper__connector",
|
|
72
|
-
isFirst && "nexus-stepper__connector--hidden"
|
|
73
|
-
prevCompleted && "nexus-stepper__connector--completed"
|
|
103
|
+
isFirst && "nexus-stepper__connector--hidden"
|
|
74
104
|
)
|
|
75
105
|
}
|
|
76
106
|
),
|
|
@@ -80,14 +110,14 @@ var Stepper = React__namespace.forwardRef(
|
|
|
80
110
|
{
|
|
81
111
|
className: chunkCZC76ZD5_js.cn(
|
|
82
112
|
"nexus-stepper__connector",
|
|
83
|
-
isLast && "nexus-stepper__connector--hidden"
|
|
84
|
-
nextCompleted && "nexus-stepper__connector--completed"
|
|
113
|
+
isLast && "nexus-stepper__connector--hidden"
|
|
85
114
|
)
|
|
86
115
|
}
|
|
87
116
|
)
|
|
88
117
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
89
|
-
!isFirst && /* @__PURE__ */ jsxRuntime.jsx("div", { className:
|
|
90
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-stepper__indicator", children: state === "completed" ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "nexus-stepper__check" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: i + 1 }) })
|
|
118
|
+
!isFirst && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-stepper__connector" }),
|
|
119
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-stepper__indicator", children: state === "completed" ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "nexus-stepper__check" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: i + 1 }) }),
|
|
120
|
+
!isLast && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nexus-stepper__connector" })
|
|
91
121
|
] }) }),
|
|
92
122
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nexus-stepper__content", children: [
|
|
93
123
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-stepper__label", children: step.label }),
|