@conscience-technology/ctds 0.0.0-stage → 0.1.1
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 +128 -2
- package/assets/components/AppFrame/AppFrame.module.css +177 -0
- package/assets/components/AppFrame/AppFrame.tsx +93 -0
- package/assets/components/AppFrame/index.ts +6 -0
- package/assets/components/AttachmentChip/AttachmentChip.module.css +71 -0
- package/assets/components/AttachmentChip/AttachmentChip.tsx +74 -0
- package/assets/components/AttachmentChip/index.ts +2 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.module.css +107 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.tsx +91 -0
- package/assets/components/AttachmentThumb/index.ts +2 -0
- package/assets/components/Avatar/Avatar.module.css +167 -0
- package/assets/components/Avatar/Avatar.tsx +228 -0
- package/assets/components/Avatar/index.ts +2 -0
- package/assets/components/AvatarUpload/AvatarUpload.module.css +130 -0
- package/assets/components/AvatarUpload/AvatarUpload.tsx +139 -0
- package/assets/components/AvatarUpload/index.ts +2 -0
- package/assets/components/Badge/Badge.module.css +76 -0
- package/assets/components/Badge/Badge.tsx +68 -0
- package/assets/components/Badge/index.ts +2 -0
- package/assets/components/Breadcrumb/Breadcrumb.module.css +213 -0
- package/assets/components/Breadcrumb/Breadcrumb.tsx +279 -0
- package/assets/components/Breadcrumb/index.ts +8 -0
- package/assets/components/Button/Button.module.css +216 -0
- package/assets/components/Button/Button.tsx +149 -0
- package/assets/components/Button/index.ts +2 -0
- package/assets/components/Callout/Callout.module.css +146 -0
- package/assets/components/Callout/Callout.tsx +138 -0
- package/assets/components/Callout/index.ts +2 -0
- package/assets/components/ChatInput/ChatInput.module.css +254 -0
- package/assets/components/ChatInput/ChatInput.tsx +322 -0
- package/assets/components/ChatInput/ModelSelector.tsx +40 -0
- package/assets/components/ChatInput/SendButton.tsx +41 -0
- package/assets/components/ChatInput/index.ts +10 -0
- package/assets/components/Checkbox/Checkbox.module.css +66 -0
- package/assets/components/Checkbox/Checkbox.tsx +36 -0
- package/assets/components/Checkbox/index.ts +2 -0
- package/assets/components/Chip/Chip.module.css +128 -0
- package/assets/components/Chip/Chip.tsx +103 -0
- package/assets/components/Chip/index.ts +2 -0
- package/assets/components/ChoiceCard/ChoiceCard.module.css +142 -0
- package/assets/components/ChoiceCard/ChoiceCard.tsx +132 -0
- package/assets/components/ChoiceCard/index.ts +2 -0
- package/assets/components/ContextMenu/ContextMenu.module.css +102 -0
- package/assets/components/ContextMenu/ContextMenu.tsx +392 -0
- package/assets/components/ContextMenu/index.ts +9 -0
- package/assets/components/DatePicker/Calendar.tsx +234 -0
- package/assets/components/DatePicker/DESIGN_NOTES.md +106 -0
- package/assets/components/DatePicker/DESIGN_NOTES_TIME.md +125 -0
- package/assets/components/DatePicker/DatePicker.module.css +295 -0
- package/assets/components/DatePicker/DatePicker.tsx +387 -0
- package/assets/components/DatePicker/TimeSelector.tsx +69 -0
- package/assets/components/DatePicker/index.ts +8 -0
- package/assets/components/DatePicker/presets.ts +80 -0
- package/assets/components/DatePicker/utils.ts +148 -0
- package/assets/components/Disclosure/Disclosure.module.css +73 -0
- package/assets/components/Disclosure/Disclosure.tsx +129 -0
- package/assets/components/Disclosure/index.ts +6 -0
- package/assets/components/Drawer/Drawer.module.css +257 -0
- package/assets/components/Drawer/Drawer.tsx +126 -0
- package/assets/components/Drawer/index.ts +7 -0
- package/assets/components/EmptyState/EmptyState.module.css +102 -0
- package/assets/components/EmptyState/EmptyState.tsx +80 -0
- package/assets/components/EmptyState/index.ts +2 -0
- package/assets/components/FileIcon/FileIcon.module.css +66 -0
- package/assets/components/FileIcon/FileIcon.tsx +84 -0
- package/assets/components/FileIcon/fileExtMap.ts +112 -0
- package/assets/components/FileIcon/index.ts +9 -0
- package/assets/components/FilterButton/FilterButton.module.css +130 -0
- package/assets/components/FilterButton/FilterButton.tsx +94 -0
- package/assets/components/FilterButton/index.ts +2 -0
- package/assets/components/GalleryItem/GalleryItem.module.css +220 -0
- package/assets/components/GalleryItem/GalleryItem.tsx +163 -0
- package/assets/components/GalleryItem/index.ts +7 -0
- package/assets/components/Icon/Icon.tsx +616 -0
- package/assets/components/Icon/context.tsx +39 -0
- package/assets/components/Icon/index.ts +4 -0
- package/assets/components/IconButton/IconButton.module.css +109 -0
- package/assets/components/IconButton/IconButton.tsx +110 -0
- package/assets/components/IconButton/index.ts +2 -0
- package/assets/components/Input/Input.module.css +338 -0
- package/assets/components/Input/Input.tsx +371 -0
- package/assets/components/Input/index.ts +2 -0
- package/assets/components/ListItem/ListItem.module.css +318 -0
- package/assets/components/ListItem/ListItem.tsx +262 -0
- package/assets/components/ListItem/index.ts +9 -0
- package/assets/components/MenuItem/MenuItem.module.css +437 -0
- package/assets/components/MenuItem/MenuItem.tsx +529 -0
- package/assets/components/MenuItem/index.ts +8 -0
- package/assets/components/MetricCard/MetricCard.module.css +153 -0
- package/assets/components/MetricCard/MetricCard.tsx +140 -0
- package/assets/components/MetricCard/index.ts +2 -0
- package/assets/components/Modal/Modal.module.css +175 -0
- package/assets/components/Modal/Modal.tsx +98 -0
- package/assets/components/Modal/index.ts +2 -0
- package/assets/components/Onboarding/DocsIndex.tsx +82 -0
- package/assets/components/Onboarding/LayoutBuilder.tsx +160 -0
- package/assets/components/Onboarding/Onboarding.module.css +1394 -0
- package/assets/components/Onboarding/OnboardingAxisPanel.tsx +515 -0
- package/assets/components/Onboarding/OnboardingPage.tsx +1193 -0
- package/assets/components/Onboarding/PresetsGallery.tsx +65 -0
- package/assets/components/Onboarding/actions.ts +250 -0
- package/assets/components/Onboarding/index.ts +8 -0
- package/assets/components/Onboarding/palette.css +40 -0
- package/assets/components/Onboarding/wizardData.ts +265 -0
- package/assets/components/Pagination/Pagination.module.css +82 -0
- package/assets/components/Pagination/Pagination.tsx +248 -0
- package/assets/components/Pagination/index.ts +6 -0
- package/assets/components/ProgressBar/ProgressBar.module.css +64 -0
- package/assets/components/ProgressBar/ProgressBar.tsx +54 -0
- package/assets/components/ProgressBar/index.ts +2 -0
- package/assets/components/QuotationBar/QuotationBar.module.css +61 -0
- package/assets/components/QuotationBar/QuotationBar.tsx +48 -0
- package/assets/components/QuotationBar/index.ts +2 -0
- package/assets/components/Radio/Radio.module.css +63 -0
- package/assets/components/Radio/Radio.tsx +33 -0
- package/assets/components/Radio/index.ts +2 -0
- package/assets/components/Select/Select.module.css +151 -0
- package/assets/components/Select/Select.tsx +425 -0
- package/assets/components/Select/index.ts +10 -0
- package/assets/components/SelectableCard/SelectableCard.module.css +177 -0
- package/assets/components/SelectableCard/SelectableCard.tsx +83 -0
- package/assets/components/SelectableCard/index.ts +2 -0
- package/assets/components/Shortcut/Shortcut.module.css +56 -0
- package/assets/components/Shortcut/Shortcut.tsx +156 -0
- package/assets/components/Shortcut/index.ts +2 -0
- package/assets/components/Sidebar/Sidebar.module.css +242 -0
- package/assets/components/Sidebar/Sidebar.tsx +264 -0
- package/assets/components/Sidebar/index.ts +7 -0
- package/assets/components/SidebarSplit/SidebarSplit.module.css +126 -0
- package/assets/components/SidebarSplit/SidebarSplit.tsx +222 -0
- package/assets/components/SidebarSplit/index.ts +6 -0
- package/assets/components/Slider/Slider.module.css +120 -0
- package/assets/components/Slider/Slider.tsx +88 -0
- package/assets/components/Slider/index.ts +2 -0
- package/assets/components/SplitButton/SplitButton.module.css +190 -0
- package/assets/components/SplitButton/SplitButton.tsx +145 -0
- package/assets/components/SplitButton/index.ts +6 -0
- package/assets/components/Stepper/Stepper.module.css +182 -0
- package/assets/components/Stepper/Stepper.tsx +185 -0
- package/assets/components/Stepper/index.ts +2 -0
- package/assets/components/Switch/Switch.module.css +116 -0
- package/assets/components/Switch/Switch.tsx +58 -0
- package/assets/components/Switch/index.ts +2 -0
- package/assets/components/TableCell/TableCell.module.css +137 -0
- package/assets/components/TableCell/TableCellAvatar.tsx +42 -0
- package/assets/components/TableCell/TableCellBadge.tsx +23 -0
- package/assets/components/TableCell/TableCellIcon.tsx +39 -0
- package/assets/components/TableCell/TableCellImage.tsx +49 -0
- package/assets/components/TableCell/TableCellLink.tsx +33 -0
- package/assets/components/TableCell/TableCellMeter.tsx +55 -0
- package/assets/components/TableCell/TableCellNumber.tsx +43 -0
- package/assets/components/TableCell/TableCellText.tsx +30 -0
- package/assets/components/TableCell/index.ts +23 -0
- package/assets/components/TableHeader/TableHeader.module.css +287 -0
- package/assets/components/TableHeader/TableHeader.tsx +590 -0
- package/assets/components/TableHeader/index.ts +9 -0
- package/assets/components/TableRow/TableRow.module.css +290 -0
- package/assets/components/TableRow/TableRow.tsx +253 -0
- package/assets/components/TableRow/index.ts +8 -0
- package/assets/components/TableRowEdit/TableRowEdit.module.css +126 -0
- package/assets/components/TableRowEdit/TableRowEdit.tsx +247 -0
- package/assets/components/TableRowEdit/index.ts +2 -0
- package/assets/components/TableRowSelect/TableRowSelect.module.css +23 -0
- package/assets/components/TableRowSelect/TableRowSelect.tsx +175 -0
- package/assets/components/TableRowSelect/index.ts +2 -0
- package/assets/components/Tabs/Tabs.module.css +234 -0
- package/assets/components/Tabs/Tabs.tsx +426 -0
- package/assets/components/Tabs/index.ts +9 -0
- package/assets/components/Textarea/Textarea.module.css +318 -0
- package/assets/components/Textarea/Textarea.tsx +426 -0
- package/assets/components/Textarea/index.ts +7 -0
- package/assets/components/ThemeController.tsx +271 -0
- package/assets/components/TimePicker/TimePicker.module.css +317 -0
- package/assets/components/TimePicker/TimePicker.tsx +821 -0
- package/assets/components/TimePicker/index.ts +8 -0
- package/assets/components/TimePicker/utils.ts +115 -0
- package/assets/components/Toast/Toast.module.css +264 -0
- package/assets/components/Toast/Toast.tsx +243 -0
- package/assets/components/Toast/index.ts +15 -0
- package/assets/components/Toolbar/Toolbar.module.css +72 -0
- package/assets/components/Toolbar/Toolbar.tsx +75 -0
- package/assets/components/Toolbar/index.ts +2 -0
- package/assets/components/Tooltip/Tooltip.module.css +42 -0
- package/assets/components/Tooltip/Tooltip.tsx +332 -0
- package/assets/components/Tooltip/index.ts +2 -0
- package/assets/components/Topbar/Topbar.module.css +26 -0
- package/assets/components/Topbar/Topbar.tsx +47 -0
- package/assets/components/Topbar/index.ts +2 -0
- package/assets/components/_cn.ts +3 -0
- package/assets/components/_color.ts +48 -0
- package/assets/components/_data.ts +659 -0
- package/assets/components/_utils.css +19 -0
- package/assets/components/axisOptions.ts +24 -0
- package/assets/components/tokens.css +742 -0
- package/assets/prompts/GUIDE.md +251 -0
- package/assets/prompts/OVERRIDE.md +170 -0
- package/assets/prompts/PRINCIPLES.md +130 -0
- package/assets/prompts/axes/density.md +38 -0
- package/assets/prompts/axes/font.md +36 -0
- package/assets/prompts/axes/hue.md +36 -0
- package/assets/prompts/axes/icon.md +48 -0
- package/assets/prompts/axes/mode.md +50 -0
- package/assets/prompts/axes/motion.md +77 -0
- package/assets/prompts/axes/palette.md +35 -0
- package/assets/prompts/axes/shape.md +33 -0
- package/assets/prompts/axes/voice.md +54 -0
- package/assets/prompts/components/README.md +84 -0
- package/assets/prompts/components/app-frame.md +94 -0
- package/assets/prompts/components/attachment-chip.md +46 -0
- package/assets/prompts/components/attachment-thumb.md +49 -0
- package/assets/prompts/components/avatar.md +86 -0
- package/assets/prompts/components/badge.md +104 -0
- package/assets/prompts/components/breadcrumb.md +77 -0
- package/assets/prompts/components/button.md +99 -0
- package/assets/prompts/components/callout.md +81 -0
- package/assets/prompts/components/chat-input.md +84 -0
- package/assets/prompts/components/checkbox.md +46 -0
- package/assets/prompts/components/chip.md +84 -0
- package/assets/prompts/components/choice-card.md +127 -0
- package/assets/prompts/components/context-menu.md +145 -0
- package/assets/prompts/components/date-picker.md +104 -0
- package/assets/prompts/components/drawer.md +152 -0
- package/assets/prompts/components/empty-state.md +69 -0
- package/assets/prompts/components/file-icon.md +73 -0
- package/assets/prompts/components/filter-button.md +42 -0
- package/assets/prompts/components/form-field.md +74 -0
- package/assets/prompts/components/gallery-item.md +158 -0
- package/assets/prompts/components/icon-button.md +82 -0
- package/assets/prompts/components/icon.md +68 -0
- package/assets/prompts/components/input.md +95 -0
- package/assets/prompts/components/list-item.md +235 -0
- package/assets/prompts/components/menu-item.md +234 -0
- package/assets/prompts/components/metric-card.md +132 -0
- package/assets/prompts/components/modal.md +131 -0
- package/assets/prompts/components/pagination.md +92 -0
- package/assets/prompts/components/progress-bar.md +61 -0
- package/assets/prompts/components/quotation-bar.md +39 -0
- package/assets/prompts/components/radio.md +41 -0
- package/assets/prompts/components/select.md +104 -0
- package/assets/prompts/components/selectable-card.md +67 -0
- package/assets/prompts/components/shortcut.md +76 -0
- package/assets/prompts/components/sidebar.md +107 -0
- package/assets/prompts/components/slider.md +89 -0
- package/assets/prompts/components/split-button.md +71 -0
- package/assets/prompts/components/stepper.md +93 -0
- package/assets/prompts/components/switch.md +86 -0
- package/assets/prompts/components/table-cell.md +87 -0
- package/assets/prompts/components/table-header.md +157 -0
- package/assets/prompts/components/table-row-edit.md +129 -0
- package/assets/prompts/components/table-row-select.md +82 -0
- package/assets/prompts/components/table-row.md +156 -0
- package/assets/prompts/components/tabs.md +122 -0
- package/assets/prompts/components/textarea.md +105 -0
- package/assets/prompts/components/toast.md +149 -0
- package/assets/prompts/components/tooltip.md +71 -0
- package/assets/prompts/components/topbar.md +148 -0
- package/assets/prompts/primitives/emerge.md +43 -0
- package/assets/prompts/primitives/fade.md +31 -0
- package/assets/prompts/primitives/scale.md +32 -0
- package/assets/prompts/primitives/scroll.md +75 -0
- package/assets/prompts/primitives/slide.md +31 -0
- package/assets/prompts/themes/_SCHEMA.md +135 -0
- package/assets/prompts/themes/_TEMPLATE.md +73 -0
- package/assets/prompts/themes/expressive.md +118 -0
- package/assets/prompts/themes/pure.md +87 -0
- package/assets/prompts/themes/spare.md +73 -0
- package/assets/prompts/tokens/breakpoint.md +29 -0
- package/assets/prompts/tokens/color.md +78 -0
- package/assets/prompts/tokens/radius.md +33 -0
- package/assets/prompts/tokens/shadow.md +35 -0
- package/assets/prompts/tokens/spacing.md +86 -0
- package/assets/prompts/tokens/typography.md +76 -0
- package/assets/prompts/tokens/z-index.md +35 -0
- package/assets/public/expressive.png +0 -0
- package/assets/public/pure.png +0 -0
- package/assets/public/spare.png +0 -0
- package/assets/skill/SKILL.md +36 -0
- package/bin/ctds.js +76 -0
- package/package.json +35 -4
- package/src/commands/init.js +215 -0
- package/src/commands/update.js +275 -0
- package/src/utils/assets.js +78 -0
- package/src/utils/deps.js +51 -0
- package/src/utils/detect.js +47 -0
- package/src/utils/diff.js +52 -0
- package/src/utils/hash.js +16 -0
- package/src/utils/log.js +41 -0
- package/src/utils/meta.js +73 -0
- package/src/utils/prompt.js +28 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# ChoiceCard
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
페이지 중앙에서 시각적으로 큰 선택지를 나열하거나(온보딩·유저 타입·플랜 선택), 아이콘·설명이 있는 네비게이션 카드가 필요할 때(문서 지도·설정 라우트). 세로 정렬(vertical, 기본)은 가로 그리드에 2~4개 나란히, 가로 정렬(horizontal)은 아이콘+텍스트가 좌측정렬된 리스트/그리드. radio/checkbox 모드는 native `<input>`을 label 안에 숨겨 시맨틱과 폼 전송을 유지하고, link 모드는 Next Link로 렌더되어 선택 상태 없이 네비게이션만 담당.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- **selection은 페이지 중앙 큰 선택지 전용**: 폼 안의 옵션 나열이면 [[components/selectable-card]] (가로 리스트) 또는 [[components/radio]]. radio/checkbox 모드의 ChoiceCard는 선택 자체가 페이지 목적일 때.
|
|
8
|
+
- **link 모드는 아이콘+설명이 있는 네비게이션**: 문서 지도·설정 하위 라우트 진입처럼 다음 페이지에 어떤 내용이 있는지 미리 보여줘야 할 때. 단순 메뉴라면 [[components/menu-item]], 아이콘 없는 네비 리스트는 [[components/list-item]].
|
|
9
|
+
- **그리드 레이아웃**: vertical은 `repeat(3, 1fr)` 표준 리듬, horizontal은 `repeat(auto-fill, minmax(240px, 1fr))` 로 2열씩 쌓이는 리듬이 자연스럽다. 옵션이 5개 이상인 selection은 [[components/selectable-card]] 세로 리스트로 전환.
|
|
10
|
+
- **같은 그룹은 같은 `name`**: radio/checkbox는 `role="radiogroup"` 래퍼 권장. link 모드는 `name` 불필요.
|
|
11
|
+
- **icon slot은 ReactNode까지 허용**: 기본은 `icon="palette"` 같은 IconName 문자열. 필요하면 fill variant·커스텀 색 Icon·`<img>`·로고 svg를 직접 넘길 수 있다. 생략하면 아이콘 영역 자체가 사라져 텍스트만.
|
|
12
|
+
- **아이콘·설명 둘 다 선택**: 둘 다 있으면 가장 풍부, 하나만 있어도 레이아웃 유지. horizontal은 아이콘 생략해도 좌측정렬이라 어색하지 않지만, vertical은 아이콘이 없으면 플레인한 라벨 카드가 된다.
|
|
13
|
+
- **상단 잘림 방지**: hover 시 `translateY(-1px)`. 부모 컨테이너의 `padding-top` 최소 `space-1` 또는 `overflow: visible` 유지. 컴포넌트가 `margin-top: var(--hairline)`로 1차 방어.
|
|
14
|
+
|
|
15
|
+
### Don't
|
|
16
|
+
- 선택 인디케이터(dot/check)를 카드에 그리지 않는다 — 카드 전체가 인디케이터(경계 accent 승격 + 아이콘 색 반전).
|
|
17
|
+
- title/description 밖의 커스텀 콘텐츠(입력 필드, CTA 버튼) 금지 — 카드 안에 넣지 말고 별도 카드/스텝으로 분리.
|
|
18
|
+
- vertical 레이아웃을 세로 스택으로 여러 개 쌓지 않는다. 세로 리스트는 [[components/selectable-card]] 또는 horizontal 레이아웃의 용도.
|
|
19
|
+
- link 모드에 radio/checkbox 상태 흉내 금지 — 네비게이션은 눌리면 떠나는 거라서 "선택됨" 개념이 없다. 현재 페이지 하이라이트가 필요하면 [[components/menu-item]].
|
|
20
|
+
- 커스텀 ReactNode icon slot에 큰 이미지 그대로 넣지 않는다 — slot 폭이 커지면 세로 리듬이 깨짐. 자체 width/height를 아이콘 크기 수준(~24~32px)으로 맞춘 뒤 넘긴다.
|
|
21
|
+
|
|
22
|
+
### Fallback
|
|
23
|
+
`mode` 미지정 시 `radio`, `layout` 미지정 시 `vertical`. link 모드는 `href` 필수.
|
|
24
|
+
|
|
25
|
+
### 관련
|
|
26
|
+
- [[components/selectable-card]] — 가로 정렬(좌측 아이콘/인디케이터 + 우측 텍스트). 세로 리스트·폼 안 옵션용.
|
|
27
|
+
- [[components/radio]] — 텍스트 한 줄 단일 선택.
|
|
28
|
+
- [[components/checkbox]] — 다중 선택 인디케이터.
|
|
29
|
+
- [[tokens/color]] — checked 상태의 `--selected-fg` 토큰.
|
|
30
|
+
- [[axes/icon]] — 아이콘이 화면 위계에서 강한 시각 요소이므로 Icon 축 값이 카드 인상을 좌우.
|
|
31
|
+
|
|
32
|
+
### Props 시그니처
|
|
33
|
+
```tsx
|
|
34
|
+
<ChoiceCard
|
|
35
|
+
mode="radio | checkbox | link" // default: radio
|
|
36
|
+
layout="vertical | horizontal" // default: vertical
|
|
37
|
+
icon={IconName | ReactNode} // optional. 문자열이면 기본 Icon, 노드면 그대로 slot
|
|
38
|
+
title={ReactNode} // required
|
|
39
|
+
description={ReactNode}
|
|
40
|
+
// mode="radio" | "checkbox":
|
|
41
|
+
name / value / checked / defaultChecked / onChange / disabled ...
|
|
42
|
+
// mode="link":
|
|
43
|
+
href="/path" // required when mode="link"
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Variant 매트릭스
|
|
48
|
+
| mode | 선택 정책 | 용도 |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `radio` | 그룹 내 단일 | 온보딩 유저 타입, 플랜 선택 등 하나만 고르는 경우 (기본) |
|
|
51
|
+
| `checkbox` | 다중 | 관심사·기능 세트 다중 선택 (예: "관심 있는 주제를 모두 골라주세요") |
|
|
52
|
+
| `link` | 없음 (Next Link) | 아이콘+설명이 있는 네비게이션 — 문서 지도, 설정 섹션 입구 |
|
|
53
|
+
|
|
54
|
+
| layout | 정렬 | 용도 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `vertical` | 아이콘 위·텍스트 아래 가운데 | 페이지 중앙 큰 선택지 (기본) |
|
|
57
|
+
| `horizontal` | 아이콘 왼쪽·텍스트 오른쪽 좌측정렬 | 리스트형 네비게이션, 밀도 높은 그리드 |
|
|
58
|
+
|
|
59
|
+
**금지 조합**
|
|
60
|
+
- vertical layout + 5개 이상 세로 스택 → 리스트형([[components/selectable-card]])으로 전환.
|
|
61
|
+
- 한 그리드 안에서 아이콘 있는 카드 + 없는 카드 섞기 금지 — 중심축이 흔들림. 전부 있거나 전부 없거나.
|
|
62
|
+
- link 모드 + radio/checkbox 상태 흉내 금지.
|
|
63
|
+
|
|
64
|
+
### 조합 예시
|
|
65
|
+
|
|
66
|
+
**radio + vertical — 온보딩 유저 타입 선택**
|
|
67
|
+
```tsx
|
|
68
|
+
<div
|
|
69
|
+
role="radiogroup"
|
|
70
|
+
style={{
|
|
71
|
+
display: "grid",
|
|
72
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
73
|
+
gap: "var(--space-4)",
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<ChoiceCard
|
|
77
|
+
name="user-type"
|
|
78
|
+
value="designer"
|
|
79
|
+
icon="palette"
|
|
80
|
+
title="디자이너"
|
|
81
|
+
description="UI/UX 작업이 주 업무"
|
|
82
|
+
defaultChecked
|
|
83
|
+
/>
|
|
84
|
+
<ChoiceCard
|
|
85
|
+
name="user-type"
|
|
86
|
+
value="developer"
|
|
87
|
+
icon="code"
|
|
88
|
+
title="개발자"
|
|
89
|
+
description="코드를 작성하고 배포"
|
|
90
|
+
/>
|
|
91
|
+
<ChoiceCard
|
|
92
|
+
name="user-type"
|
|
93
|
+
value="pm"
|
|
94
|
+
icon="clipboard"
|
|
95
|
+
title="PM"
|
|
96
|
+
description="제품과 팀을 조율"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**link + horizontal — 문서 지도**
|
|
102
|
+
```tsx
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
display: "grid",
|
|
106
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))",
|
|
107
|
+
gap: "var(--space-3)",
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<ChoiceCard
|
|
111
|
+
mode="link"
|
|
112
|
+
layout="horizontal"
|
|
113
|
+
href="/getting-started/architecture"
|
|
114
|
+
icon="stack"
|
|
115
|
+
title="전체 구조"
|
|
116
|
+
description="템플릿·축·토큰·컴포넌트가 어떻게 연결되나."
|
|
117
|
+
/>
|
|
118
|
+
<ChoiceCard
|
|
119
|
+
mode="link"
|
|
120
|
+
layout="horizontal"
|
|
121
|
+
href="/tokens/color"
|
|
122
|
+
icon="palette"
|
|
123
|
+
title="토큰"
|
|
124
|
+
description="color·spacing·radius·shadow·motion·typography 전체 값."
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# ContextMenu
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
우클릭·롱프레스로 열리는 **한 객체에 대한 2차 액션 메뉴**. 컨텐츠 상단 툴바나 상단 nav에 놓지 못한 부가 기능(잘라내기/복사/이름 변경/삭제/공유 등)이 여기 모인다. 시각·상태·슬롯은 [[components/menu-item]]이 이미 갖고 있으므로 ContextMenu는 그 위에 얹은 **얇은 어댑터** — 열림/닫힘·포커스 트랩·화살표 네비게이션·서브메뉴 안전지연·뷰포트 flip만 담당한다. Radix `@radix-ui/react-context-menu`를 감싼 compound 컴포넌트.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- **트리거는 객체를 감싼다**: 파일 아이템·캔버스 셀·리스트 행처럼 "이 메뉴가 걸리는 객체"의 시각 경계와 트리거 영역이 정확히 일치해야 한다. 애매하면 사용자가 어디에 대한 메뉴인지 모른다.
|
|
8
|
+
- **2차 액션만**: 핵심 CTA는 페이지 상단·인라인 버튼에. 컨텍스트 메뉴는 자주 쓰지 않지만 있을 때 반가운 옵션 자리.
|
|
9
|
+
- **논리 그룹화**: 관련된 액션끼리 묶고 `Separator`로 나눈다. 파괴적 액션은 항상 맨 아래 그룹, `variant="destructive"`.
|
|
10
|
+
- **키보드 접근 보장**: Radix가 화살표·Home/End·타이핑 서치·Esc·Tab을 이미 처리한다. 자체 키 핸들러를 얹지 말 것 — 충돌 원인.
|
|
11
|
+
- **선택 후 자동 닫힘 활용**: `onSelect`가 발화하면 Radix가 자동으로 닫는다. 계속 열려 있어야 하면 `e.preventDefault()` (예: 다중 체크박스 토글).
|
|
12
|
+
- **상태 있는 아이템**: 토글은 `CheckboxItem`, 배타 선택은 `RadioGroup` + `RadioItem`. 인디케이터(체크마크·dot)는 컴포넌트가 자동으로 그린다.
|
|
13
|
+
- **중첩은 `Sub` 트리오**: `Sub` / `SubTrigger` / `SubContent`. SubTrigger는 자동으로 `hasSubmenu` caret이 붙는다.
|
|
14
|
+
- **트리거 아이콘 힌트**: 리스트 행의 케밥(⋮)·미트볼(⋯) 아이콘으로 "메뉴가 있음"을 사전에 암시하면 발견성이 크게 오른다.
|
|
15
|
+
- **`asChild`로 임의 요소에 위임**: `<ContextMenu.Trigger asChild>`로 카드·이미지·행 어느 것이든 트리거로 만든다.
|
|
16
|
+
- **단축키는 실제 심볼**: `⌘K` · `⇧⌘P` · `⌫`. uppercase 금지 — [[feedback_no_uppercase]].
|
|
17
|
+
- **아이콘은 액션 의미와 정확히 일치할 때만 붙인다.** 아이콘 슬롯은 스캐닝 속도를 높이는 힌트인데 의미 어긋난 아이콘은 오히려 라벨 해석을 방해한다. 유효한 아이콘 이름은 [[web/components/Icon/Icon.tsx]]에 정의된 세트에서만 골라야 한다 — 없는 이름을 상상으로 지어내지 말 것(그 순간 렌더 실패). **적절한 매치가 없으면 아이콘을 붙이지 않고 라벨만 둔다.** 예: 잘라내기/붙여넣기는 우리 세트에 정확한 매치가 없으므로 라벨 + 단축키만으로 표현. 리스트 안에서 아이콘 있는 행과 없는 행이 섞여도 괜찮다 — 억지 아이콘보다 낫다.
|
|
18
|
+
- 자주 쓰이는 안전 매핑: 열기 → `arrow-square-out` (외부로 나가는 화살표) · 문서 아이템 → `article` · 이름 변경/편집 → `pencil-simple` · 잘라내기 → `scissors` · 복사 → `copy` · 붙여넣기 → `clipboard-text` · 복제 → `copy-simple` (겹친 문서 = 같은 걸 하나 더) · 태그 → `tag` · 정보 → `info` · 삭제 → `trash` · 공유 → `share-network` (연결된 노드) · 방송/알림 뉘앙스 → `broadcast` · 이메일 → `envelope` · 메시지 → `chat-text` · 설정 → `gear` · 검색 → `search` · 즐겨찾기 → `star` · 새로 만들기 → `article` (문서) 또는 라벨만.
|
|
19
|
+
- **라벨은 짧게 — 한 줄에 자연스레 읽히는 길이(대략 한글 8–12자 · 영문 12–20자 이내).** ContextMenu 폭은 표준 224px로 고정되어 있고, 넘칠 땐 최대 한 스텝(+48px)만 늘어나며 그 뒤엔 잘린다. "선택한 항목을 휴지통으로 이동하고 30일 후 영구 삭제" 같은 문장형은 금지 — 동사 위주로 압축하고 세부 조건은 확인 다이얼로그로 넘긴다. 이 규칙은 매우 강한 권장 사항이며, 라벨을 짧게 만들 수 없으면 그 액션은 컨텍스트 메뉴 자리가 아니라는 신호다.
|
|
20
|
+
|
|
21
|
+
### Don't
|
|
22
|
+
- 핵심 플로우를 컨텍스트 메뉴 안으로 숨기지 말 것. "우클릭해야만 보이는" CTA는 발견 실패로 이어진다.
|
|
23
|
+
- 한 메뉴에 12개 이상 아이템 나열 금지 — 스캔이 무너지고 실수 클릭이 급증한다. 넘치면 `Sub`로 접기.
|
|
24
|
+
- **긴 라벨을 두 줄로 감싸지 말 것.** ContextMenu는 `white-space: nowrap` + 표준 폭(224px) 고정이 시각 계약. 두 줄 라벨은 리듬을 무너뜨리고 아이템 높이가 들쭉날쭉해진다. 짧게 못 쓰겠으면 라벨 자체를 재설계할 것.
|
|
25
|
+
- 서로 다른 객체에 대한 액션을 한 메뉴에 섞지 말 것 — 컨텍스트 = 하나의 대상.
|
|
26
|
+
- 시각을 위해 root · content에 hard-coded 색·radius·shadow를 얹지 말 것. 모두 토큰 경유 ([[PRINCIPLES]] §6, [[feedback_no_hardcoded_values]]).
|
|
27
|
+
- Radix의 포지셔닝을 뒤흔들지 말 것 (Content에 `position: fixed`/`transform` 강제 금지). `collisionPadding`만 조절.
|
|
28
|
+
- 트리거 위에서 `onContextMenu`를 자체 처리하지 말 것 — Radix가 이미 preventDefault로 브라우저 기본 메뉴를 눌러준다.
|
|
29
|
+
|
|
30
|
+
### Fallback
|
|
31
|
+
size 애매하면 `md`. Item variant 애매하면 `default`. 액션이 파괴적인지 확실치 않으면 destructive 대신 default + 확인 다이얼로그([[components/alert-dialog]]).
|
|
32
|
+
|
|
33
|
+
### 관련
|
|
34
|
+
- [[components/menu-item]] — 시각·슬롯·상태 규칙의 원본. ContextMenu는 이 위에 얇게 얹은 어댑터.
|
|
35
|
+
- [[components/dropdown-menu]] — 트리거만 다름(클릭 vs 우클릭). Content 이하는 거의 동일 패턴 공유.
|
|
36
|
+
- [[components/icon-button]] — 케밥(⋮)/미트볼(⋯) 트리거 아이콘. 발견성 힌트.
|
|
37
|
+
- [[components/alert-dialog]] — 파괴적 액션의 최종 확인은 여기.
|
|
38
|
+
- [[axes/shape]] · [[axes/density]] — Content radius·아이템 min-height가 이 축에 반응.
|
|
39
|
+
- [[tokens/color]] — `--bg` · `--border` · `--shadow-lg` · `--hover` 등이 시각을 만든다.
|
|
40
|
+
|
|
41
|
+
### Props 시그니처
|
|
42
|
+
```tsx
|
|
43
|
+
<ContextMenu> // Radix Root — open/onOpenChange 등 전부 통과
|
|
44
|
+
<ContextMenu.Trigger asChild> // 우클릭·롱프레스 감지 영역
|
|
45
|
+
{trigger}
|
|
46
|
+
</ContextMenu.Trigger>
|
|
47
|
+
<ContextMenu.Content
|
|
48
|
+
size="sm | md" // default: md — 하위 아이템 기본 size
|
|
49
|
+
collisionPadding={number} // default: 8
|
|
50
|
+
>
|
|
51
|
+
<ContextMenu.Label>...</ContextMenu.Label>
|
|
52
|
+
<ContextMenu.Item
|
|
53
|
+
size="sm | md"
|
|
54
|
+
variant="default | destructive"
|
|
55
|
+
leadingActions={MenuItemAction[]}
|
|
56
|
+
trailingActions={MenuItemAction[]}
|
|
57
|
+
shortcut="⌘C"
|
|
58
|
+
disabled={boolean}
|
|
59
|
+
onSelect={(e) => void} // 발화 후 자동 닫힘 — e.preventDefault()로 유지
|
|
60
|
+
>
|
|
61
|
+
...
|
|
62
|
+
</ContextMenu.Item>
|
|
63
|
+
<ContextMenu.CheckboxItem
|
|
64
|
+
checked={boolean | "indeterminate"}
|
|
65
|
+
onCheckedChange={(v) => void}
|
|
66
|
+
/>
|
|
67
|
+
<ContextMenu.RadioGroup value onValueChange>
|
|
68
|
+
<ContextMenu.RadioItem value="..." />
|
|
69
|
+
</ContextMenu.RadioGroup>
|
|
70
|
+
<ContextMenu.Sub>
|
|
71
|
+
<ContextMenu.SubTrigger>...</ContextMenu.SubTrigger>
|
|
72
|
+
<ContextMenu.SubContent>...</ContextMenu.SubContent>
|
|
73
|
+
</ContextMenu.Sub>
|
|
74
|
+
<ContextMenu.Separator />
|
|
75
|
+
</ContextMenu.Content>
|
|
76
|
+
</ContextMenu>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 상태 매트릭스
|
|
80
|
+
| 상태 | 시각 |
|
|
81
|
+
|---|---|
|
|
82
|
+
| 열림 · 닫힘 | Content in/out은 전역 유틸 `.m-float` 담당 (open: emerge-from-* + base + emphasized, close: recede-to-* + fast + ease-in). 자체 애니메이션 재정의 금지. |
|
|
83
|
+
| 아이템 highlight (마우스·키보드) | `--hover` 배경, `fg-strong` — MenuItem 규칙 그대로 |
|
|
84
|
+
| checked (Checkbox) | leading 맨 앞에 checkmark 자동 삽입 |
|
|
85
|
+
| checked (Radio) | leading 맨 앞에 dot 인디케이터, fill=currentColor |
|
|
86
|
+
| disabled | `fg-disabled`, 커서 not-allowed, 키보드 순환에서 자동 제외 |
|
|
87
|
+
| destructive · hover | 배경 destructive 8%, 텍스트·아이콘 destructive |
|
|
88
|
+
|
|
89
|
+
### Size 가이드
|
|
90
|
+
| size | 언제 |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `sm` | 밀도 높은 관리 UI · Command Palette 조합 · 우클릭이 잦은 캔버스 |
|
|
93
|
+
| `md` | **기본.** 일반 컨텍스트 메뉴 |
|
|
94
|
+
|
|
95
|
+
### 조합 예시
|
|
96
|
+
```tsx
|
|
97
|
+
// 캔버스 셀 우클릭 — 잘라내기/복사/붙여넣기 + 삭제
|
|
98
|
+
<ContextMenu>
|
|
99
|
+
<ContextMenu.Trigger asChild>
|
|
100
|
+
<Cell />
|
|
101
|
+
</ContextMenu.Trigger>
|
|
102
|
+
<ContextMenu.Content>
|
|
103
|
+
<ContextMenu.Item shortcut="⌘X" onSelect={cut}>잘라내기</ContextMenu.Item>
|
|
104
|
+
<ContextMenu.Item shortcut="⌘C" onSelect={copy}>복사</ContextMenu.Item>
|
|
105
|
+
<ContextMenu.Item shortcut="⌘V" onSelect={paste}>붙여넣기</ContextMenu.Item>
|
|
106
|
+
<ContextMenu.Separator />
|
|
107
|
+
<ContextMenu.Item variant="destructive" shortcut="⌫" onSelect={remove}>삭제</ContextMenu.Item>
|
|
108
|
+
</ContextMenu.Content>
|
|
109
|
+
</ContextMenu>
|
|
110
|
+
|
|
111
|
+
// 뷰 옵션 — Label + Checkbox + Radio
|
|
112
|
+
<ContextMenu.Content>
|
|
113
|
+
<ContextMenu.Label>보기</ContextMenu.Label>
|
|
114
|
+
<ContextMenu.CheckboxItem checked={grid} onCheckedChange={setGrid} shortcut="⌘G">
|
|
115
|
+
그리드 표시
|
|
116
|
+
</ContextMenu.CheckboxItem>
|
|
117
|
+
<ContextMenu.Separator />
|
|
118
|
+
<ContextMenu.Label>배치</ContextMenu.Label>
|
|
119
|
+
<ContextMenu.RadioGroup value={view} onValueChange={setView}>
|
|
120
|
+
<ContextMenu.RadioItem value="list">리스트</ContextMenu.RadioItem>
|
|
121
|
+
<ContextMenu.RadioItem value="grid">그리드</ContextMenu.RadioItem>
|
|
122
|
+
</ContextMenu.RadioGroup>
|
|
123
|
+
</ContextMenu.Content>
|
|
124
|
+
|
|
125
|
+
// 중첩 서브메뉴 — 공유 대상 세분화
|
|
126
|
+
<ContextMenu.Content>
|
|
127
|
+
<ContextMenu.Item onSelect={open}>열기</ContextMenu.Item>
|
|
128
|
+
<ContextMenu.Sub>
|
|
129
|
+
<ContextMenu.SubTrigger leadingActions={[<Icon name="arrow-right" />]}>공유</ContextMenu.SubTrigger>
|
|
130
|
+
<ContextMenu.SubContent>
|
|
131
|
+
<ContextMenu.Item onSelect={mail}>이메일로</ContextMenu.Item>
|
|
132
|
+
<ContextMenu.Item onSelect={link}>링크 복사</ContextMenu.Item>
|
|
133
|
+
</ContextMenu.SubContent>
|
|
134
|
+
</ContextMenu.Sub>
|
|
135
|
+
</ContextMenu.Content>
|
|
136
|
+
|
|
137
|
+
// 다중 토글 유지 — onSelect에서 preventDefault로 열림 유지
|
|
138
|
+
<ContextMenu.CheckboxItem
|
|
139
|
+
checked={pinned}
|
|
140
|
+
onCheckedChange={setPinned}
|
|
141
|
+
onSelect={(e) => e.preventDefault()}
|
|
142
|
+
>
|
|
143
|
+
고정
|
|
144
|
+
</ContextMenu.CheckboxItem>
|
|
145
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Date Picker
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
|
|
5
|
+
날짜·기간·시간 선택을 **하나의 컴포넌트**로 커버한다. props 조합으로 유즈케이스를 조립한다 — 별도의 `DateRangePicker` / `TimePicker` 컴포넌트를 만들지 않는다. 컴포넌트 분화보다 prop 표면이 예측 가능성이 높다.
|
|
6
|
+
|
|
7
|
+
### Do
|
|
8
|
+
- `mode` 하나로 단일/기간을 결정한다 — `mode="single"` 또는 `mode="range"`.
|
|
9
|
+
- `range`는 기본 `months=2` (양옆 두 달). 좁은 자리는 `months=1`로 명시.
|
|
10
|
+
- 시간이 필요하면 `withTime` 활성 + `timeMode`로 UI 방식 지정.
|
|
11
|
+
- 자주 쓰는 기간이 있으면 `withPresets`로 사이드바 노출. 기본 프리셋은 mode에 맞게 자동 선택됨.
|
|
12
|
+
- 값은 항상 통제형(controlled). `value`/`rangeValue` + `onChange`/`onRangeChange`로 다룬다.
|
|
13
|
+
- 대시보드 사이드바처럼 항상 노출해야 하면 `inline`을 켜서 Popover 없이 렌더링.
|
|
14
|
+
|
|
15
|
+
### Don't
|
|
16
|
+
- 시간만 단독으로 필요한 화면에 이 컴포넌트를 쓰지 않는다 — 날짜가 없이는 의미 없음. 순수 시간 입력은 별도 `TimePicker` (미구현) 몫.
|
|
17
|
+
- `mode="single"`에 `months=2`를 함께 쓰지 않는다 — 시각적 낭비.
|
|
18
|
+
- `timeMode="grid30"`은 30분 슬롯 예약/미팅 UI가 아닌 경우 피한다 — 세로로 길어져 Popover가 뷰포트를 밀어낸다.
|
|
19
|
+
- 하드코딩된 min/max width를 CSS로 덮어쓰지 않는다. 크기 조정은 `size` prop 경유.
|
|
20
|
+
|
|
21
|
+
### Fallback
|
|
22
|
+
mode가 명확하지 않으면 `single`. 시간 필요 여부가 명확하지 않으면 `withTime` 꺼두고 필요할 때 켠다. timeMode가 애매하면 `dropdown` (범용성 최고).
|
|
23
|
+
|
|
24
|
+
### 관련
|
|
25
|
+
- [[components/input]] — 트리거로 `<Input readOnly role="combobox">`를 재사용. 별도 트리거 스타일 없음.
|
|
26
|
+
- [[components/form-field]] — 라벨/에러 필요 시 DatePicker를 FormField로 감쌀 수 있음.
|
|
27
|
+
- [[axes/density]] — 트리거·달력 셀 높이가 `--control-h-sm/md/lg` 경유로 자동 반응.
|
|
28
|
+
- [[axes/motion]] — Popover in/out은 전역 유틸 `.m-float` 하나로 통일. 자체 keyframe 만들지 않는다. 자세히는 `axes/motion.md` §플로팅 서페이스 In/Out.
|
|
29
|
+
- [[axes/shape]] — 트리거·Popover의 radius가 Shape 축에 반응.
|
|
30
|
+
- [[tokens/color]] — `--primary`(선택), `--hover`(범위 하이라이트), `--border`(경계) 경유.
|
|
31
|
+
- [[components/popover]] — 트리거+오버레이 패턴 공유 (Radix Popover 기반).
|
|
32
|
+
|
|
33
|
+
### Props 시그니처
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<DatePicker
|
|
37
|
+
mode="single | range"
|
|
38
|
+
months={1 | 2} // range의 기본은 2
|
|
39
|
+
value={Date | null} // single
|
|
40
|
+
rangeValue={{ from: Date | null, to: Date | null }} // range
|
|
41
|
+
onChange={(d) => void} // single
|
|
42
|
+
onRangeChange={({ from, to }) => void} // range
|
|
43
|
+
|
|
44
|
+
withTime={boolean}
|
|
45
|
+
timeMode="grid30 | dropdown | input" // default: "dropdown"
|
|
46
|
+
minuteStep={5 | 10 | 15 | 30 | 60} // dropdown 전용
|
|
47
|
+
|
|
48
|
+
withPresets={boolean}
|
|
49
|
+
presets={SinglePreset[] | RangePreset[]} // mode에 맞춘 타입
|
|
50
|
+
|
|
51
|
+
minDate={Date} maxDate={Date}
|
|
52
|
+
weekStartsOn={0..6} // default: 0 (일)
|
|
53
|
+
|
|
54
|
+
size="sm | md | lg"
|
|
55
|
+
disabled={boolean}
|
|
56
|
+
inline={boolean} // Popover 없이 인라인
|
|
57
|
+
trigger={ReactNode} // asChild 커스텀 트리거
|
|
58
|
+
placeholder={string}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Variant 매트릭스
|
|
63
|
+
|
|
64
|
+
`variant`가 아니라 **prop 조합**으로 형태가 결정된다. 자주 쓰는 조합:
|
|
65
|
+
|
|
66
|
+
| 조합 | 용도 | 금지 조합 |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `mode="single"` | 하나의 날짜 선택 (생일, 만료일) | `months=2` |
|
|
69
|
+
| `mode="single"` + `withTime` | 예약 시각, 게시 예약 | — |
|
|
70
|
+
| `mode="range"` + `months=1` | 좁은 자리의 짧은 기간 | — |
|
|
71
|
+
| `mode="range"` + `months=2` (기본) | 대시보드 조회 기간, 장기 일정 | — |
|
|
72
|
+
| `mode="range"` + `withPresets` | 애널리틱스/리포트 조회 필터 | — |
|
|
73
|
+
| `mode="range"` + `withTime` + `timeMode="grid30"` | 회의실 예약 (30분 슬롯) | 뷰포트가 좁을 때 |
|
|
74
|
+
| `inline` | 사이드바에 상시 노출된 달력 | `trigger` |
|
|
75
|
+
|
|
76
|
+
**timeMode 선택:**
|
|
77
|
+
- `grid30` — 예약/미팅. 30분 단위 그리드. 세로로 길어지므로 공간 여유 확인.
|
|
78
|
+
- `dropdown` — 범용. 시/분 각각의 select. 밀도 좋음.
|
|
79
|
+
- `input` — HH:MM 직접 타이핑. 자유도 최고, 실수 여지도 최고.
|
|
80
|
+
|
|
81
|
+
### 조합 예시
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// 대시보드 조회 필터: 기간 + 퀵메뉴 + 두 달 뷰
|
|
85
|
+
<DatePicker
|
|
86
|
+
mode="range"
|
|
87
|
+
months={2}
|
|
88
|
+
withPresets
|
|
89
|
+
rangeValue={period}
|
|
90
|
+
onRangeChange={setPeriod}
|
|
91
|
+
maxDate={new Date()}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
// 예약 시스템: 단일 날짜 + 30분 슬롯
|
|
95
|
+
<DatePicker
|
|
96
|
+
value={slot}
|
|
97
|
+
onChange={setSlot}
|
|
98
|
+
withTime
|
|
99
|
+
timeMode="grid30"
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
// 인라인 위젯: 사이드바에 상시 노출
|
|
103
|
+
<DatePicker inline value={date} onChange={setDate} />
|
|
104
|
+
```
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Drawer
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
화면 가장자리에서 밀고 들어오는 패널. **흐름을 끊지 않고** 상세 보기·편집·설정·필터처럼 **본문과 연관된 보조 작업**을 띄울 때. 결정 하나에 집중시키는 중앙 자리는 [[components/modal]], 가벼운 미리보기는 [[components/popover]].
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- **본문과 연결된 보조 작업**에 쓴다 — 선택한 항목의 상세·편집, 필터 그룹, 설정 패널, 알림 센터.
|
|
8
|
+
- `title` 필수. 명사형 짧게(상세 보기 · 알림 · 필터). 질문형은 `description`으로.
|
|
9
|
+
- 설명 문구는 `description` prop. 헤더 영역 안에서 title 바로 아래 바짝 붙어 하나의 "소개" 묶음으로 읽힘. `children`에 `<p>`로 설명 넣지 않는다.
|
|
10
|
+
- children은 자유 ReactNode — Modal보다 세로 길이에 여유가 있어 긴 폼·리스트·탭도 OK. 세로 스크롤은 body가 담당.
|
|
11
|
+
- `footer`는 액션 Button. **우측 정렬 · 최대 2~3개**. 순서는 **좌: outline(취소/닫기), 우: primary(저장/적용)**.
|
|
12
|
+
- 방향(`side`)은 **용도에 맞게 하나**로 고정: 상세·편집·설정은 `right`, 네비게이션·목록은 `left`, 모바일 액션/필터 바텀시트는 `bottom`, 알림·커맨드는 `top`.
|
|
13
|
+
- `backdrop="scrim"`(기본)은 모달형 — 뒤 영역 비활성 + 바깥 클릭 닫힘. `locked`은 저장·업로드 중 실수 방지. `none`은 사이드 패널 상주형(뒤 영역 계속 조작) — 알림 센터·보조 참고 패널.
|
|
14
|
+
- dismiss는 **헤더 우측 X · ESC 키** 두 가지. 둘 다 기본 on.
|
|
15
|
+
- primary Button은 `md`가 기본 — Drawer 안 Input도 `md`로 맞춰 수직 리듬 유지.
|
|
16
|
+
|
|
17
|
+
### Don't
|
|
18
|
+
- Modal 자리에 Drawer 금지 — 결정 하나에 집중시키는 자리는 Modal. Drawer는 흐름을 **잇는** 보조 패널.
|
|
19
|
+
- Drawer 안에 Drawer 금지 — ESC가 어느 쪽을 닫는지 모호해지고 side가 같으면 겹침.
|
|
20
|
+
- 세로가 짧은 짧은 알림·메시지에 Drawer 금지. 그건 [[components/toast]] 자리.
|
|
21
|
+
- footer에 primary 2개 금지 — 무엇이 "다음"인지 모호.
|
|
22
|
+
- 사이즈 조절한다고 `width` 인라인 override 금지. `size` prop만 사용. 더 넓게 필요하면 그건 페이지 자리.
|
|
23
|
+
- title을 `text-transform: uppercase`로 꾸미지 않는다.
|
|
24
|
+
- `side` 아무 방향에나 열지 않는다 — 한 제품/맥락 안에서 **용도별로 하나의 방향**만 쓴다. 같은 페이지에서 right/left/bottom을 섞으면 멀미.
|
|
25
|
+
|
|
26
|
+
### Fallback
|
|
27
|
+
- 결정이 끝나야 다음 흐름이 가능하면 → [[components/modal]].
|
|
28
|
+
- 짧은 알림이면 → [[components/toast]].
|
|
29
|
+
- 가볍게 미리보기면 → [[components/popover]].
|
|
30
|
+
- 세로가 짧고 상단 커맨드 느낌이면 → `side="top"`.
|
|
31
|
+
- 애매하면 `side="right"`, `size="sm"`, `backdrop="scrim"`.
|
|
32
|
+
|
|
33
|
+
### 관련
|
|
34
|
+
- [[components/modal]] — 흐름 중단·결정 집중. Drawer는 흐름 유지.
|
|
35
|
+
- [[components/toast]] — 휘발성 알림.
|
|
36
|
+
- [[components/button]] — footer 액션. 파괴적 확인은 `destructive` variant.
|
|
37
|
+
- [[primitives/slide]] — 가장자리 등장 애니. `--motion-distance-scale`로 축 스왑.
|
|
38
|
+
- [[primitives/fade]] — overlay fade 조합.
|
|
39
|
+
- [[axes/motion]] — slide 거리·duration 스왑.
|
|
40
|
+
- [[tokens/z-index]] — overlay는 `z-overlay`, content는 `z-modal`.
|
|
41
|
+
|
|
42
|
+
### Props 시그니처
|
|
43
|
+
```tsx
|
|
44
|
+
<Drawer
|
|
45
|
+
open={boolean}
|
|
46
|
+
onOpenChange={(open: boolean) => void}
|
|
47
|
+
title={ReactNode} // required
|
|
48
|
+
description={ReactNode} // 헤더 영역 안, title 바로 아래 한두 줄
|
|
49
|
+
titleSrOnly={boolean} // 시각적 숨김
|
|
50
|
+
footer={ReactNode} // Button들을 넘김. 없으면 footer 미렌더
|
|
51
|
+
dismissible={boolean} // default: true (헤더 X 버튼)
|
|
52
|
+
dismissLabel={string} // X 버튼 스크린리더 라벨
|
|
53
|
+
disableEscape={boolean} // ESC 닫힘 차단
|
|
54
|
+
side="right" | "left" | "top" | "bottom" // default: right
|
|
55
|
+
size="sm" | "md" | "lg" // default: sm
|
|
56
|
+
backdrop="scrim" | "locked" | "none" // default: scrim
|
|
57
|
+
>
|
|
58
|
+
{children} // 자유 ReactNode
|
|
59
|
+
</Drawer>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Variant 매트릭스
|
|
63
|
+
|
|
64
|
+
| prop | 값 | 결과 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `side` | `right`(기본) | 우측 끝에서 밀고 들어옴. 상세·편집·설정 — 가장 자주 쓰는 자리. |
|
|
67
|
+
| | `left` | 좌측에서. 네비게이션·탐색·목록 — 모바일 사이드바 느낌. |
|
|
68
|
+
| | `top` | 상단에서 내려옴. 알림·커맨드 팔레트 — 드문 자리. |
|
|
69
|
+
| | `bottom` | 하단에서 올라옴. 모바일 액션 시트·필터 바텀시트. |
|
|
70
|
+
| `size` | 아래 **Size 가이드** 참조 | right·left는 가로 폭을, top·bottom은 세로 높이를 결정. |
|
|
71
|
+
| `backdrop` | `scrim`(기본) | dim O · 바깥 클릭 닫힘. 일반 보조 작업. |
|
|
72
|
+
| | `locked` | dim O · 바깥 클릭 무시. 저장·업로드 중 실수 방지. ESC도 막고 싶으면 `disableEscape`. |
|
|
73
|
+
| | `none` | dim 없음 · 뒤 영역 계속 조작 가능 (non-modal). 알림 센터·상주 참고 패널. |
|
|
74
|
+
| `dismissible` | `true`(기본) / `false` | 헤더 X 노출/제거. |
|
|
75
|
+
| `disableEscape` | `false`(기본) / `true` | ESC 닫힘 활성/차단. |
|
|
76
|
+
| `titleSrOnly` | `false`(기본) / `true` | 헤더 title 시각적 숨김 (스크린리더엔 유지). |
|
|
77
|
+
|
|
78
|
+
### Size 가이드
|
|
79
|
+
| size | right/left (width) | top/bottom (height) | 언제 |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `sm` | 352 | 288 | **기본.** 상세 보기·알림 센터·간단한 편집. 데이터·개발자 도구일수록 유지. |
|
|
82
|
+
| `md` | 440 | 400 | 폼이 길거나 리스트 항목이 많을 때. |
|
|
83
|
+
| `lg` | 576 | 512 | 복잡한 편집·탭 포함 보조 뷰. 이보다 넓어야 하면 페이지 자리. |
|
|
84
|
+
|
|
85
|
+
**금지 조합**
|
|
86
|
+
- `backdrop="none"` + `size="lg"` — 뒤 영역을 거의 가려놓고 non-modal 흉내. `locked` 또는 `scrim`으로.
|
|
87
|
+
- `dismissible=false` + `disableEscape=true` + `footer` 없음 — 사용자가 Drawer를 닫을 방법이 전혀 없음.
|
|
88
|
+
- 같은 페이지에서 서로 다른 `side`의 Drawer 혼용 — 방향 통일.
|
|
89
|
+
|
|
90
|
+
### 조합 예시
|
|
91
|
+
```tsx
|
|
92
|
+
// 가장 기본 — 우측 상세 보기
|
|
93
|
+
<Drawer
|
|
94
|
+
open={open}
|
|
95
|
+
onOpenChange={setOpen}
|
|
96
|
+
title="주문 상세"
|
|
97
|
+
description="주문번호 #28491"
|
|
98
|
+
dismissLabel="닫기"
|
|
99
|
+
footer={
|
|
100
|
+
<>
|
|
101
|
+
<Button variant="outline" onClick={() => setOpen(false)}>닫기</Button>
|
|
102
|
+
<Button variant="primary" onClick={save}>저장</Button>
|
|
103
|
+
</>
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
<DescriptionList>…</DescriptionList>
|
|
107
|
+
</Drawer>
|
|
108
|
+
|
|
109
|
+
// 알림 센터 — non-modal, 뒤 계속 작업
|
|
110
|
+
<Drawer
|
|
111
|
+
open={open}
|
|
112
|
+
onOpenChange={setOpen}
|
|
113
|
+
side="right"
|
|
114
|
+
size="sm"
|
|
115
|
+
backdrop="none"
|
|
116
|
+
title="알림"
|
|
117
|
+
dismissLabel="닫기"
|
|
118
|
+
>
|
|
119
|
+
<List>…</List>
|
|
120
|
+
</Drawer>
|
|
121
|
+
|
|
122
|
+
// 모바일 필터 바텀시트
|
|
123
|
+
<Drawer
|
|
124
|
+
open={open}
|
|
125
|
+
onOpenChange={setOpen}
|
|
126
|
+
side="bottom"
|
|
127
|
+
size="md"
|
|
128
|
+
title="필터"
|
|
129
|
+
dismissLabel="닫기"
|
|
130
|
+
footer={
|
|
131
|
+
<>
|
|
132
|
+
<Button variant="outline" onClick={reset}>초기화</Button>
|
|
133
|
+
<Button variant="primary" onClick={apply}>적용</Button>
|
|
134
|
+
</>
|
|
135
|
+
}
|
|
136
|
+
>
|
|
137
|
+
<FilterGroup>…</FilterGroup>
|
|
138
|
+
</Drawer>
|
|
139
|
+
|
|
140
|
+
// 저장 중 실수 방지
|
|
141
|
+
<Drawer
|
|
142
|
+
open={saving}
|
|
143
|
+
onOpenChange={() => {}}
|
|
144
|
+
title="업로드 중"
|
|
145
|
+
description="파일을 서버로 전송하는 중입니다."
|
|
146
|
+
backdrop="locked"
|
|
147
|
+
dismissible={false}
|
|
148
|
+
disableEscape
|
|
149
|
+
>
|
|
150
|
+
<ProgressBar value={progress} />
|
|
151
|
+
</Drawer>
|
|
152
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# EmptyState
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
리스트·테이블·대시보드 영역이 비었을 때 그 영역을 통째로 채우는 자리채움. 세로 중앙정렬된 아이콘/일러스트 + 타이틀 + 설명 + 액션 조합. 사용자가 "로드가 실패했나?"를 묻기 전에 **"비어있다 + 다음 행동"**을 즉시 보여준다. 소형 안내 카드(사이드바 등 공간 일부에 상주)인 [[components/callout]]과 달리, EmptyState는 **빈 컨테이너 전체**를 차지해 다른 UI 없이 혼자 선다.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- 리스트/테이블/카드 그리드 **첫 데이터 없음**, **검색/필터 결과 0개**, **권한 없음·접근 불가**에 쓴다.
|
|
8
|
+
- 반드시 **다음 행동**을 제시한다 — Button 1개(새로 만들기·필터 초기화 등). action 없는 EmptyState는 "막다른 골목"이므로 지양한다.
|
|
9
|
+
- `size`로 영역 크기에 맞춘다. 작은 패널·사이드 리스트는 `sm`(기본), 메인 콘텐츠 영역은 `md`. 페이지 전체 자리채움도 `md`를 쓴다 — 과한 비율 금지.
|
|
10
|
+
- 시각 요소는 `illustration` slot(ReactNode)이 우선. 없으면 `icon`(IconName). 둘 다 생략하면 텍스트만 — 전환 상태(검색 중, 로딩 직후)에서만 허용.
|
|
11
|
+
- 액션은 [[components/button]] slot. **Button 1개**, `variant="outline"` + `size="sm"` 조합이 디폴트. 자리채움 공간이라 primary로 강조할 필요 없음 — outline이 조용하고 어떤 톤의 EmptyState에서도 어긋나지 않는다.
|
|
12
|
+
|
|
13
|
+
### Don't
|
|
14
|
+
- 카드/패널 **옆자리**에 작게 끼워넣지 않는다 — [[components/callout]] 영역. EmptyState는 컨테이너 전체를 차지하는 자리채움.
|
|
15
|
+
- 임시 피드백(요청 완료, 저장 성공)에 쓰지 않는다 — Toast.
|
|
16
|
+
- 타이틀을 "No data"처럼 상태만 서술하지 않는다. **원인 또는 해결** 중 하나를 담는다 (예: "아직 추가된 멤버가 없어요", "검색 결과가 없어요 — 필터를 바꿔보세요").
|
|
17
|
+
- `illustration` slot에 큰 외부 이미지(200px+)를 넣지 않는다 — 자체 크기를 size에 맞게 조절해서 넘긴다 (`sm`≈48~64px, `md`≈72~96px 안팎).
|
|
18
|
+
- 액션에 destructive variant 금지 — 빈 상태의 primary CTA는 생성/탐색이지 삭제가 아님.
|
|
19
|
+
- 액션 Button `size`에 `md`/`lg` 금지. EmptyState 내부 Button은 전부 `sm`.
|
|
20
|
+
|
|
21
|
+
### Fallback
|
|
22
|
+
`size` 애매하면 `sm`. 시각 요소 애매하면 `icon`만. 액션은 primary 1개로 시작.
|
|
23
|
+
|
|
24
|
+
### 관련
|
|
25
|
+
- [[components/callout]] — 소형 안내 카드. 공간 일부에 상주하는 "곁다리". EmptyState는 전체 자리채움.
|
|
26
|
+
- [[components/button]] — actions 슬롯의 실제 요소. size `sm`/`md`만 허용.
|
|
27
|
+
- [[axes/icon]] — icon slot은 Icon 컴포넌트 경유 강제.
|
|
28
|
+
- [[axes/density]] — size가 영역 비례지만 외곽 padding은 `--space-scale`로 함께 스왑.
|
|
29
|
+
|
|
30
|
+
### Props 시그니처
|
|
31
|
+
```tsx
|
|
32
|
+
<EmptyState
|
|
33
|
+
size="sm | md" // default: sm
|
|
34
|
+
illustration={ReactNode} // 시각 요소 우선순위 1
|
|
35
|
+
icon={IconName} // 시각 요소 우선순위 2 (illustration 없을 때만)
|
|
36
|
+
title={ReactNode} // required
|
|
37
|
+
description={ReactNode}
|
|
38
|
+
actions={ReactNode}
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Variant 매트릭스
|
|
43
|
+
단일 축 — `size`만. 색/강조는 변하지 않는다 (빈 상태는 신호가 아니라 자리채움).
|
|
44
|
+
|
|
45
|
+
| size | 용도 | 시각 요소 권장 크기 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `sm` | 패널·사이드 리스트·좁은 카드 안 | icon ≈24px 또는 48~64px 일러스트 |
|
|
48
|
+
| `md` | 메인 콘텐츠 영역·페이지 자리채움 | icon ≈32px 또는 72~96px 일러스트 |
|
|
49
|
+
|
|
50
|
+
**금지 조합**
|
|
51
|
+
- 액션 Button `variant="destructive"` — 빈 상태 CTA는 삭제가 아님.
|
|
52
|
+
- 액션 Button `size="md"`/`"lg"` — EmptyState 내부는 전부 `sm`.
|
|
53
|
+
- 액션 Button 2개 이상 — EmptyState는 Button 1개만. 복수 액션이 필요하면 Toolbar/Callout 영역.
|
|
54
|
+
- `illustration`과 `icon` 동시 지정 — illustration이 있으면 icon은 무시(병치 금지). 사용자 혼란 방지.
|
|
55
|
+
|
|
56
|
+
### 조합 예시
|
|
57
|
+
```tsx
|
|
58
|
+
<EmptyState
|
|
59
|
+
size="md"
|
|
60
|
+
icon="envelope"
|
|
61
|
+
title="아직 받은 메시지가 없어요"
|
|
62
|
+
description="팀원을 초대하거나 첫 메시지를 보내보세요."
|
|
63
|
+
actions={
|
|
64
|
+
<Button variant="outline" size="sm" leftIcon={<Icon name="plus" />}>
|
|
65
|
+
새 메시지
|
|
66
|
+
</Button>
|
|
67
|
+
}
|
|
68
|
+
/>
|
|
69
|
+
```
|