@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,73 @@
|
|
|
1
|
+
# File Icon
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
파일 확장자별 **색·라벨·아이콘**을 담은 둥근 사각 뱃지. [[components/chat-input]] 첨부·[[components/message-bubble]] 파일 인용에서 공용. 포맷 식별이 핵심이라 **브랜드 자산(Adobe/Office)은 상용 색을 그대로** — 토큰으로 추상화하면 포맷을 못 알아봄. CTDS 하드코딩 금지의 명시적 예외 ([[feedback/no-hardcoded-values]]).
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- 확장자만 넘기면 끝. `ext="psd"` / `ext=".psd"` / `ext="report.psd"` 전부 OK.
|
|
8
|
+
- Mode 축은 자동 반응 — `others`·`zip`·`mp3` 세 그룹만 L/D pair, 브랜드 파일은 모드 무관 고정.
|
|
9
|
+
- 사이즈는 접점 크기에 맞춤: 리스트/칩 `sm`(16), 기본 `md`(24), 그리드/히어로 `lg`(32).
|
|
10
|
+
- 모르는 확장자는 자동으로 `others` 폴백 — 애플리케이션에서 매핑 추가할 필요 없음.
|
|
11
|
+
- 이미지(png/jpg/gif/webp) 첨부는 [[components/attachment-thumb]]로 썸네일 렌더 — FileIcon을 쓰지 않음.
|
|
12
|
+
|
|
13
|
+
### Don't
|
|
14
|
+
- `label` prop을 자동 생성(`ext.toUpperCase().slice(0,2)`)으로 만들지 않는다 — 레퍼런스는 수동 라벨(`Ai`/`Ps`/`X`/`P`/`W`/`Pd`).
|
|
15
|
+
- 브랜드 색을 CTDS 토큰(`--bg-brand` 등)으로 치환하지 않는다 — 포맷 식별 정보가 날아감.
|
|
16
|
+
- 아이콘(file/file-zip/music-note)을 Icon 컴포넌트 경유로 바꾸지 않는다 — Icon 축(family) 전환에 영향받으면 자산 일관성이 깨짐.
|
|
17
|
+
- 포커스 가능하게 만들지 않는다 — 장식 요소. 상호작용은 감싸는 chip/button이 책임.
|
|
18
|
+
|
|
19
|
+
### Fallback
|
|
20
|
+
size 애매하면 `md`, radius 애매하면 `auto`(Shape 축 따라감). 확장자 모르면 `txt`도 OK — 어차피 `others` 폴백.
|
|
21
|
+
|
|
22
|
+
### 관련
|
|
23
|
+
- [[components/attachment-chip]] — FileIcon + 파일명 + X. 가장 흔한 소비처
|
|
24
|
+
- [[components/attachment-thumb]] — 이미지 첨부(FileIcon 대신)
|
|
25
|
+
- [[components/chat-input]] / [[components/message-bubble]] — 상위 소비처
|
|
26
|
+
- [[axes/shape]] — radius=auto일 때 Shape 축 따라감
|
|
27
|
+
- [[axes/mode]] — 제너릭(others/zip/mp3) 색이 Mode 축 반응
|
|
28
|
+
|
|
29
|
+
### Props 시그니처
|
|
30
|
+
```tsx
|
|
31
|
+
<FileIcon
|
|
32
|
+
ext="psd | ai | pdf | xls | ppt | doc | zip | mp3 | ..." // 필수
|
|
33
|
+
size="sm | md | lg" // default: md (16 / 24 / 32)
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 비율 (Shape 축 반응 + soft 캡)
|
|
38
|
+
- radius: sm/md=`--radius-checkbox` / lg=`--radius-xs` — 한 단계씩 내려 soft 과도 방지
|
|
39
|
+
- `label font-size / size` = 0.53, weight=semibold
|
|
40
|
+
- `glyph / size` = 0.60
|
|
41
|
+
|
|
42
|
+
> **왜 한 단계 내림**: Shape soft에서 `--radius-xs=10px`를 24px 박스에 쓰면 42%로 너무 둥글어 "뱃지"가 아니라 "알약"으로 보임. [[project-radius-checkbox]] 캡 패턴을 md까지 확장 — Shape 축 반응은 유지하되 soft의 과도만 눌러줌.
|
|
43
|
+
|
|
44
|
+
### 색 매트릭스
|
|
45
|
+
| 그룹 | 모드 | bg | fg | 콘텐츠 |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| **ai** (Illustrator) | 고정 | `#2E0402` | `#F09E38` | 라벨 `Ai` |
|
|
48
|
+
| **psd** (Photoshop) | 고정 | `#081D34` | `#57A5F8` | 라벨 `Ps` |
|
|
49
|
+
| **xls, xlsx** (Excel) | 고정 | `#3B804C` | `#FFFFFF` | 라벨 `X` |
|
|
50
|
+
| **ppt, pptx** (PowerPoint) | 고정 | `#B84C2C` | `#FFFFFF` | 라벨 `P` |
|
|
51
|
+
| **doc, docx** (Word) | 고정 | `#3044C7` | `#FFFFFF` | 라벨 `W` |
|
|
52
|
+
| **pdf** | 고정 | `#EA336C` | `#FFFFFF` | 라벨 `Pd` |
|
|
53
|
+
| **others** (fallback) | Light | `#EDEAB0` | `#B2AA54` | 아이콘 `file` |
|
|
54
|
+
| | Dark | `#363229` | `#A19B5A` | |
|
|
55
|
+
| **zip** · rar · 7z · tar · gz | Light | `#EDEAB0` | `#B2AA54` | 아이콘 `file-zip` |
|
|
56
|
+
| | Dark | `#363229` | `#A19B59` | |
|
|
57
|
+
| **mp3** · wav · flac · m4a · aac · ogg | Light | `#BEFDA8` | `#4B9C2D` | 아이콘 `music-note` |
|
|
58
|
+
| | Dark | `#254924` | `#BDFCA8` | |
|
|
59
|
+
|
|
60
|
+
### 조합 예시
|
|
61
|
+
```tsx
|
|
62
|
+
// 가장 흔한 — AttachmentChip 안
|
|
63
|
+
<AttachmentChip filename="report.pdf" /> // 내부에서 <FileIcon ext="pdf" size="md" />
|
|
64
|
+
|
|
65
|
+
// 리스트 셀 (sm)
|
|
66
|
+
<FileIcon ext=".psd" size="sm" />
|
|
67
|
+
|
|
68
|
+
// 그리드 (lg)
|
|
69
|
+
<FileIcon ext="song.mp3" size="lg" />
|
|
70
|
+
|
|
71
|
+
// 모르는 확장자 — 자동 others
|
|
72
|
+
<FileIcon ext="unknown" />
|
|
73
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Filter Button
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
리스트·테이블 상단에서 결과를 좁힐 때 쓰는 **토글 컨트롤**. 액션 실행([[components/button]])이 아니라 “적용 중” 상태 유지가 목적이라 별도 컴포넌트. Hue/Palette와 무관하게 항상 명도 대비(fg↔bg 반전)로 강조 → 어느 테마에서도 “선택됨”이 확실히 읽힌다.
|
|
5
|
+
|
|
6
|
+
`role="button"` + `aria-pressed`로 순수 토글. 여러 개를 조합해도 되고 하나만 켜도 되지만 “체크박스/라디오 시맨틱”이 필요한 폼 UI는 [[components/checkbox]]/[[components/radio]]를 직접 쓴다.
|
|
7
|
+
|
|
8
|
+
count는 [[components/badge]]로 위임 — unselected는 `variant="soft"`, selected(반전 상태)에서는 `variant="inverse"`.
|
|
9
|
+
|
|
10
|
+
### Do
|
|
11
|
+
- **필터 바 전용**: 리스트·테이블 상단, 카드 그리드 상단의 “결과 좁히기”. 폼 안에 두면 [[components/checkbox]]/[[components/radio]]가 맞다.
|
|
12
|
+
- **count는 미리 알려줄 수 있을 때만**: 서버가 매칭 개수를 이미 알고 있는 경우에만 표시. lazy 계산은 UX상 어색.
|
|
13
|
+
|
|
14
|
+
### Don't
|
|
15
|
+
- Chip(정보 라벨)로 오해되게 두지 않는다 — FilterButton은 언제나 **인터랙션 컨트롤**, `<button>` 시맨틱.
|
|
16
|
+
- 폼 시맨틱(체크박스·라디오)이 필요한 곳에 대체품으로 쓰지 않는다 — 시맨틱이 다르다.
|
|
17
|
+
|
|
18
|
+
### Fallback
|
|
19
|
+
size 애매하면 `md`.
|
|
20
|
+
|
|
21
|
+
### 관련
|
|
22
|
+
- [[components/checkbox]] · [[components/radio]] — 폼 안이라면 이 둘을 직접 쓴다.
|
|
23
|
+
- [[components/badge]] — count 슬롯이 Badge로 위임. `soft` ↔ `inverse` 자동 스왑.
|
|
24
|
+
- [[components/button]] — 시각적 형제. 액션 실행용은 이쪽.
|
|
25
|
+
|
|
26
|
+
### Props 시그니처
|
|
27
|
+
```tsx
|
|
28
|
+
<FilterButton
|
|
29
|
+
size="sm | md" // default: md
|
|
30
|
+
selected={boolean}
|
|
31
|
+
onToggle={(next: boolean) => void}
|
|
32
|
+
leftIcon={ReactNode}
|
|
33
|
+
count={number} // Badge로 위임
|
|
34
|
+
disabled={boolean}
|
|
35
|
+
>{children}</FilterButton>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 상태 매트릭스
|
|
39
|
+
| state | 시각 |
|
|
40
|
+
|---|---|
|
|
41
|
+
| unselected | outline (fg-muted 텍스트) |
|
|
42
|
+
| selected | **반전** (fg 배경 + bg 텍스트) |
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# FormField
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
|
|
5
|
+
라벨 · 설명 · 에러 · required 별표를 담당하는 폼 필드 래퍼. 자식 입력 컨트롤([[components/input]] · [[components/date-picker]] · Select 등)의 접근성 속성(`id` · `aria-describedby` · `aria-invalid` · `required`)을 자동으로 연결한다. 입력 컨트롤 자체는 이 속성들을 직접 관리하지 않는다.
|
|
6
|
+
|
|
7
|
+
### Do
|
|
8
|
+
- 라벨이 필요한 모든 폼 컨트롤은 `FormField`로 감싼다.
|
|
9
|
+
- `FormField.Label`은 항상 첫 번째 자식. 그 다음 `Description`, 컨트롤, `Error` 순.
|
|
10
|
+
- `invalid`는 FormField에 준다 — 자식 컨트롤이 자동으로 `aria-invalid`를 받는다. 컨트롤에도 `invalid`를 넘겨 시각 피드백을 켜준다.
|
|
11
|
+
- `required`는 FormField에 준다 — 라벨에 별표(*)가 자동으로 붙고 컨트롤에 `required` 속성이 전파된다.
|
|
12
|
+
- 에러 문구는 반드시 `FormField.Error` 컴포넌트로 넣는다. 임의의 `<p>`나 텍스트로 두지 않는다 — `role="alert"`과 aria 연결이 깨진다.
|
|
13
|
+
|
|
14
|
+
### Don't
|
|
15
|
+
- 한 FormField 안에 두 개 이상의 컨트롤을 두지 않는다. 라벨/에러 대응이 1:1이 아니게 됨.
|
|
16
|
+
- `Description`을 에러 메시지로 쓰지 않는다. Description은 상시 힌트, Error는 검증 실패 시 표시.
|
|
17
|
+
- `Label`의 `htmlFor`를 수동 지정하지 않는다 — FormField가 자동 생성한 id와 어긋난다.
|
|
18
|
+
|
|
19
|
+
### Fallback
|
|
20
|
+
`required` 여부가 애매하면 `false`. 필수 입력이 확정되면 켠다. `invalid`는 상위 폼 상태에서 파생된 값을 넘긴다 (컴포넌트가 스스로 판단하지 않음).
|
|
21
|
+
|
|
22
|
+
### 관련
|
|
23
|
+
- [[components/input]] — 가장 자주 조합되는 자식.
|
|
24
|
+
- [[components/date-picker]] — 트리거 Input이 FormField의 자식이 될 수 있음.
|
|
25
|
+
- [[tokens/color]] — `--destructive`(에러 · required 별표), `--fg-muted`(설명).
|
|
26
|
+
- [[tokens/typography]] — 라벨은 `--text-sm` · `medium`, 설명/에러는 `--text-xs`.
|
|
27
|
+
|
|
28
|
+
### Props 시그니처
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<FormField
|
|
32
|
+
invalid={boolean}
|
|
33
|
+
disabled={boolean}
|
|
34
|
+
required={boolean}
|
|
35
|
+
id={string} // 없으면 자동 생성
|
|
36
|
+
>
|
|
37
|
+
<FormField.Label>...</FormField.Label>
|
|
38
|
+
<FormField.Description>...</FormField.Description>
|
|
39
|
+
<Input ... /> // 자식 컨트롤
|
|
40
|
+
<FormField.Error>...</FormField.Error>
|
|
41
|
+
</FormField>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**하위:**
|
|
45
|
+
- `FormField.Label` — `htmlFor` 자동. `required=true`면 별표 자동.
|
|
46
|
+
- `FormField.Description` — 상시 힌트. `id` 자동 → 컨트롤의 `aria-describedby`에 연결.
|
|
47
|
+
- `FormField.Error` — `invalid=true`일 때만 렌더링. `role="alert"` 포함.
|
|
48
|
+
|
|
49
|
+
### Variant 매트릭스
|
|
50
|
+
|
|
51
|
+
FormField 자체는 variant가 없다. 자식 컨트롤의 variant를 따른다.
|
|
52
|
+
|
|
53
|
+
| 상태 | 시각 반응 |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `required` | 라벨에 destructive 색 별표(*) |
|
|
56
|
+
| `invalid` | 자식 컨트롤에 `aria-invalid` 전파 + Error 노출 |
|
|
57
|
+
| `disabled` | 자식 컨트롤에 `disabled` 전파 |
|
|
58
|
+
|
|
59
|
+
### 조합 예시
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<FormField required invalid={!!emailError}>
|
|
63
|
+
<FormField.Label>이메일</FormField.Label>
|
|
64
|
+
<FormField.Description>업무용 이메일을 입력해주세요.</FormField.Description>
|
|
65
|
+
<Input
|
|
66
|
+
type="email"
|
|
67
|
+
value={email}
|
|
68
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
69
|
+
invalid={!!emailError}
|
|
70
|
+
leftIcon={<MailIcon />}
|
|
71
|
+
/>
|
|
72
|
+
<FormField.Error>{emailError}</FormField.Error>
|
|
73
|
+
</FormField>
|
|
74
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# GalleryItem
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
갤러리뷰 그리드에서 썸네일을 전면에 세우고 그 아래 title·description·meta·actions를 서술하는 콘텐츠 카드. 미디어가 주인공이고 텍스트는 꼬리표. 선택 카드가 아니라 "보여주고 열어가는" 카드 — 선택 상태는 가지지 않는다. 반복 레코드를 비교·정렬해야 하면 [[components/table-row]], 숫자 하나만 전달하면 [[components/metric-card]], 옵션 중 하나를 고르는 자리면 [[components/choice-card]].
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- **media 필수**: 썸네일이 없으면 이 컴포넌트가 아니다. 자산이 아직 없으면 placeholder라도 넣어 자리를 지킨다.
|
|
8
|
+
- **layout 선택 기준**: `vertical`은 썸네일을 전면에 세우는 **그리드 갤러리** 용(이미지 비중 ↑). `horizontal`은 썸네일 옆에 title·description·meta가 길게 서술되는 **세로 리스트** 용(텍스트 비중 ↑). 블로그 아카이브·검색 결과·세션 목록처럼 "읽는 콘텐츠"는 horizontal.
|
|
9
|
+
- **그리드로 배치(vertical)**: `display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: var(--space-4)` 가 기본 리듬. 열 수는 Density 축에 따라 자동 조절되게 min 폭만 고정.
|
|
10
|
+
- **세로 스택(horizontal)**: `display: flex; flex-direction: column; gap: var(--space-6)` 로 쌓는다. 카드끼리 붙지 않도록 gap 최소 `space-6`.
|
|
11
|
+
- **aspect 통일**: 한 리스트/그리드 안의 카드는 전부 같은 `aspect`. 16/9 가로형이 기본, 세로 포스터형 콘텐츠(책/영상 커버)만 3/4.
|
|
12
|
+
- **actionPosition 선택 기준**: 호버 전 보여야 하는 상시 액션(공개·북마크 토글 등)이면 `overlay`, 카드 전체가 링크일 때 보조 액션(⋯ 메뉴)이면 `footer`. 둘 다 쓰면 overlay/footer 중 하나로만 통일.
|
|
13
|
+
- **meta는 muted 한 줄**: [[components/badge]](카테고리·상태 라벨) + inline 날짜/카운트 텍스트 조합. 두 줄이 되면 description으로 승격.
|
|
14
|
+
- **href 또는 onClick이 있을 때만 hover elevation 활성**: 정적 카드는 떠오르지 않는다. elevation은 썸네일에만 적용 — body에는 그림자/톤 변화 없음.
|
|
15
|
+
|
|
16
|
+
### Don't
|
|
17
|
+
- **meta에 [[components/chip]] 사용 금지**. Chip은 검색 필터·다중 선택 전용(사용자가 제거하는 값). 카드 카테고리·태그 라벨은 전부 Badge (`variant="soft"`).
|
|
18
|
+
- **meta 토큰 사이 가운뎃점(`·`·`•`·`|`) 금지**. 저자·날짜·좋아요·댓글 등을 나열할 때 구분자는 짧고 연한 세로 hairline. 가운뎃점은 글자처럼 읽혀 라인에 시각 노이즈를 더함. 전역 룰.
|
|
19
|
+
- 카드 전체에 테두리·배경 chrome 추가 금지. 썸네일 radius 외의 경계는 두지 않는다. 분위기 있는 그리드가 필요해도 카드 chrome이 아니라 **그리드 간격·행 구분**으로 해결.
|
|
20
|
+
- 선택 상태(selected·checked) 흉내 금지 — border accent나 ring 승격 금지. 선택이 필요하면 다른 카드.
|
|
21
|
+
- media 위에 title/description 글자 overlay 금지. 글자가 사진 위에 겹치는 레이아웃은 썸네일 가독성과 축 스왑을 둘 다 깬다. overlay 슬롯은 액션 전용.
|
|
22
|
+
- actions에 full-width Button 금지. IconButton 또는 Button(`size="sm"`, `variant="ghost|outline"`)만. actions가 primary CTA가 되는 순간 이건 카드가 아니라 다른 컴포넌트.
|
|
23
|
+
- aspect 값 바깥의 임의 `aspect-ratio` inline 금지. 네 값(`16/9`·`4/3`·`1/1`·`3/4`) 외가 필요하면 그리드 설계가 틀렸을 가능성.
|
|
24
|
+
|
|
25
|
+
### Fallback
|
|
26
|
+
- `layout` 미지정 시 `vertical`.
|
|
27
|
+
- `aspect` 미지정 시 `16/9`.
|
|
28
|
+
- `actionPosition` 미지정 시 `footer`.
|
|
29
|
+
- `href`/`onClick`이 둘 다 없으면 hover elevation 비활성 — 정적 카드.
|
|
30
|
+
|
|
31
|
+
### 관련
|
|
32
|
+
- [[components/choice-card]] — 선택이 화면 주 액션인 카드. GalleryItem은 선택 상태 없음.
|
|
33
|
+
- [[components/selectable-card]] — 폼 안 옵션 리스트형 선택 카드.
|
|
34
|
+
- [[components/metric-card]] — 숫자 하나 전달. 미디어 없음.
|
|
35
|
+
- [[components/table-row]] — 반복 레코드 비교·정렬이 필요하면 테이블.
|
|
36
|
+
- [[components/empty-state]] — 그리드가 비었을 때. GalleryItem 그리드 안이 아니라 영역 전체를 대체.
|
|
37
|
+
- [[tokens/radius]] — 썸네일에 `radius-md` 토큰.
|
|
38
|
+
- [[axes/density]] — 그리드 열 수가 density 따라 자동 변동.
|
|
39
|
+
|
|
40
|
+
### Props 시그니처
|
|
41
|
+
```tsx
|
|
42
|
+
<GalleryItem
|
|
43
|
+
media={ReactNode} // required — 썸네일 슬롯 (img/video/svg)
|
|
44
|
+
layout="vertical | horizontal" // default: vertical
|
|
45
|
+
aspect="16/9 | 4/3 | 1/1 | 3/4" // default: 16/9
|
|
46
|
+
title={ReactNode} // required
|
|
47
|
+
description={ReactNode}
|
|
48
|
+
meta={ReactNode} // Badge·날짜 자유 조합. muted
|
|
49
|
+
actions={ReactNode} // IconButton 또는 Button sm ghost/outline
|
|
50
|
+
actionPosition="footer | overlay" // default: footer
|
|
51
|
+
href={string} // 있으면 <a>로 렌더. hover elevation 활성
|
|
52
|
+
disabled={boolean}
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Variant 매트릭스
|
|
57
|
+
|
|
58
|
+
**layout × 전형 용례**
|
|
59
|
+
|
|
60
|
+
| layout | 전형 | 썸네일 폭 | title 크기 |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `vertical` | 그리드 갤러리, 포스터·블로그 커버 | 100% | `text-sm` |
|
|
63
|
+
| `horizontal` | 세로 리스트, 블로그 아카이브, 검색 결과 | 고정(~16rem) | `text-lg` |
|
|
64
|
+
|
|
65
|
+
**aspect × 콘텐츠 성격**
|
|
66
|
+
|
|
67
|
+
| aspect | 전형 콘텐츠 |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `16/9` | 영상 썸네일, 블로그 커버, 와이드 스크린샷 (기본) |
|
|
70
|
+
| `4/3` | 제품 사진, 일반 이미지 |
|
|
71
|
+
| `1/1` | 아바타형·앨범 커버·프로덕트 그리드 |
|
|
72
|
+
| `3/4` | 책/영화 포스터, 세로 모바일 캡처 |
|
|
73
|
+
|
|
74
|
+
**금지 조합**
|
|
75
|
+
- `layout="horizontal"` + `aspect="3/4"` — 세로 포스터가 왼쪽에 길쭉하게 서면 리스트 높이가 들쭉날쭉해지고 title row 정렬이 깨짐. horizontal에서는 가로형(`16/9` · `4/3`)만.
|
|
76
|
+
- `layout="horizontal"` + 그리드 배치 — horizontal은 세로 스택 전용. 그리드로 쌓으면 각 셀 내부가 좁아져 media 고정 폭이 body를 밀어버림.
|
|
77
|
+
- `actionPosition="overlay"` + 반투명 overlay 배경 추가 — media 위에 글자 겹치는 레이아웃 유도. overlay는 액션 아이콘 자체만.
|
|
78
|
+
- `aspect="1/1"` + title 3줄 이상 — 정사각 카드에 긴 텍스트는 밸런스 깨짐. 1줄 title이면 1/1, 2줄까지면 4/3 이상.
|
|
79
|
+
|
|
80
|
+
### 조합 예시
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// 블로그/뉴스 — 와이드 썸네일 + 메타 + ⋯ 메뉴
|
|
84
|
+
<section
|
|
85
|
+
style={{
|
|
86
|
+
display: "grid",
|
|
87
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(16rem, 1fr))",
|
|
88
|
+
gap: "var(--space-4)",
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{posts.map((p) => (
|
|
92
|
+
<GalleryItem
|
|
93
|
+
key={p.id}
|
|
94
|
+
href={`/posts/${p.id}`}
|
|
95
|
+
media={<img src={p.thumb} alt="" />}
|
|
96
|
+
aspect="16/9"
|
|
97
|
+
title={p.title}
|
|
98
|
+
description={p.excerpt}
|
|
99
|
+
meta={
|
|
100
|
+
<>
|
|
101
|
+
<Badge variant="soft">{p.category}</Badge>
|
|
102
|
+
<span>{p.date}</span>
|
|
103
|
+
</>
|
|
104
|
+
}
|
|
105
|
+
actions={
|
|
106
|
+
<IconButton
|
|
107
|
+
size="sm"
|
|
108
|
+
variant="ghost"
|
|
109
|
+
icon={<Icon name="dots-three" />}
|
|
110
|
+
aria-label="더보기"
|
|
111
|
+
onClick={(e) => { e.preventDefault(); openMenu(p.id); }}
|
|
112
|
+
/>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
))}
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
// 포스터 그리드 — 세로형 + overlay 저장 토글
|
|
119
|
+
<GalleryItem
|
|
120
|
+
href={`/movies/${m.id}`}
|
|
121
|
+
media={<img src={m.poster} alt="" />}
|
|
122
|
+
aspect="3/4"
|
|
123
|
+
title={m.title}
|
|
124
|
+
meta={<Badge variant="soft" status="info">{m.year}</Badge>}
|
|
125
|
+
actionPosition="overlay"
|
|
126
|
+
actions={
|
|
127
|
+
<IconButton
|
|
128
|
+
size="sm"
|
|
129
|
+
variant="ghost"
|
|
130
|
+
icon={<Icon name="star" />}
|
|
131
|
+
aria-label="저장"
|
|
132
|
+
/>
|
|
133
|
+
}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
// 블로그 아카이브 — horizontal 리스트
|
|
137
|
+
<section style={{ display: "flex", flexDirection: "column", gap: "var(--space-6)" }}>
|
|
138
|
+
{posts.map((p) => (
|
|
139
|
+
<GalleryItem
|
|
140
|
+
key={p.id}
|
|
141
|
+
href={`/posts/${p.id}`}
|
|
142
|
+
layout="horizontal"
|
|
143
|
+
media={<img src={p.thumb} alt="" />}
|
|
144
|
+
title={p.title}
|
|
145
|
+
description={p.excerpt}
|
|
146
|
+
meta={
|
|
147
|
+
<>
|
|
148
|
+
<span>{p.author}</span>
|
|
149
|
+
<span className={styles.sep} aria-hidden />
|
|
150
|
+
<span>{p.date}</span>
|
|
151
|
+
<span className={styles.sep} aria-hidden />
|
|
152
|
+
<span>♥ {p.likes}</span>
|
|
153
|
+
</>
|
|
154
|
+
}
|
|
155
|
+
/>
|
|
156
|
+
))}
|
|
157
|
+
</section>
|
|
158
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# IconButton
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
아이콘만 있는 버튼. **좁은 자리의 액션 전용** — 툴바, 테이블 행 액션, 플로팅 액션, Input의 trailing 등. [[components/button]]의 icon-only 모드와 **용도 중복**이지만 IconButton은 **크기 체계가 한 단계 작음** (Button sm = IconButton md). 좁은 자리의 밀도용. `aria-label` 필수.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- `aria-label` 반드시 지정. 아이콘은 글자가 아님.
|
|
8
|
+
- 툴바·테이블 행 action: `size="md"` + `variant="ghost"`가 표준.
|
|
9
|
+
- 플로팅 액션: `size="lg"` + `variant="secondary"`.
|
|
10
|
+
- `trailingIcon`을 넣으면 자동 **dual 모드**(정사각 → 가로형 2-슬롯). caret-down 등 보조 아이콘 자리.
|
|
11
|
+
- `asChild`로 `<a>` 또는 Next `<Link>` 위임 가능 (이땐 아이콘·trailingIcon은 자식이 담당).
|
|
12
|
+
|
|
13
|
+
### Don't
|
|
14
|
+
- 라벨이 필요하면 쓰지 않는다 — [[components/button]] icon-only 모드로.
|
|
15
|
+
- Button과 같은 size 이름이라도 실제 크기 다르니 **같은 툴바에서 섞을 땐 매핑 확인**: IconButton md(24) ↔ Button sm(28), IconButton lg(32) ↔ Button md(32).
|
|
16
|
+
- aria-label 생략 금지. 아이콘 의미가 자명해도 스크린리더엔 안 보임.
|
|
17
|
+
- 색을 variant로 override하지 않는다 — destructive 외의 상태색은 Badge/Callout 쪽.
|
|
18
|
+
|
|
19
|
+
### Fallback
|
|
20
|
+
size 애매하면 `md`, variant 애매하면 `ghost`.
|
|
21
|
+
|
|
22
|
+
### 관련
|
|
23
|
+
- [[components/button]] — 라벨 있는 버튼 또는 Button의 icon-only 모드 (신규 권장)
|
|
24
|
+
- [[components/icon]] — 안에 들어가는 아이콘
|
|
25
|
+
- [[components/toolbar]] — 가장 많이 쓰는 자리
|
|
26
|
+
- [[components/tooltip]] — aria-label 보충 설명 (hover 시 라벨 노출)
|
|
27
|
+
- [[axes/density]] — `--iconbtn-h-*`가 Density에 반응
|
|
28
|
+
- [[axes/shape]] — radius가 Shape 축에 반응
|
|
29
|
+
|
|
30
|
+
### Props 시그니처
|
|
31
|
+
```tsx
|
|
32
|
+
<IconButton
|
|
33
|
+
icon={<Icon name="..." />} // asChild=true면 무시
|
|
34
|
+
trailingIcon={<Icon name="caret-down" />} // 있으면 dual 모드
|
|
35
|
+
size="sm | md | lg" // default: md (Button sm 상당)
|
|
36
|
+
variant="secondary | outline | ghost | destructive" // default: ghost
|
|
37
|
+
asChild={boolean} // Radix Slot — <a>/<Link> 위임
|
|
38
|
+
aria-label="필수"
|
|
39
|
+
disabled={boolean}
|
|
40
|
+
onClick={(e) => void}
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Size × Button 매핑
|
|
45
|
+
| IconButton size | 높이 | Button 대응 | 쓰임 |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `sm` | 20 | — (xs 수준) | 인라인·초밀도. 거의 안 씀 |
|
|
48
|
+
| `md` | 24 | Button `sm`(32)과 짝 | **기본** — 툴바·테이블 행·Input trailing |
|
|
49
|
+
| `lg` | 32 | Button `md`(36)과 짝 | 플로팅 액션·주 액션 |
|
|
50
|
+
|
|
51
|
+
### Variant 매트릭스
|
|
52
|
+
| variant | 시각 | 쓰임 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `secondary` | primary 8% 배경 + primary 아이콘 | 강조가 필요한 플로팅 |
|
|
55
|
+
| `outline` | 투명 배경 + border | 툴바 그룹의 그룹핑 |
|
|
56
|
+
| `ghost` | 완전 투명, hover만 반응 | **기본** — 밀도 자리 |
|
|
57
|
+
| `destructive` | 평상시 ghost, hover 시 destructive 톤 | 삭제·제거 |
|
|
58
|
+
|
|
59
|
+
### 조합 예시
|
|
60
|
+
```tsx
|
|
61
|
+
// 툴바 — ghost md 나열
|
|
62
|
+
<Toolbar>
|
|
63
|
+
<IconButton icon={<Icon name="pencil-simple" />} aria-label="수정" />
|
|
64
|
+
<IconButton icon={<Icon name="copy" />} aria-label="복제" />
|
|
65
|
+
<IconButton icon={<Icon name="trash" />} aria-label="삭제" variant="destructive" />
|
|
66
|
+
</Toolbar>
|
|
67
|
+
|
|
68
|
+
// Dual 모드 — 드롭다운 트리거
|
|
69
|
+
<IconButton
|
|
70
|
+
icon={<Icon name="user" />}
|
|
71
|
+
trailingIcon={<Icon name="caret-down" />}
|
|
72
|
+
aria-label="사용자 메뉴"
|
|
73
|
+
variant="outline"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
// asChild — Next Link 위임
|
|
77
|
+
<IconButton asChild size="md" variant="ghost" aria-label="깃허브">
|
|
78
|
+
<a href="https://github.com/..." target="_blank" rel="noreferrer">
|
|
79
|
+
<Icon name="github-logo" />
|
|
80
|
+
</a>
|
|
81
|
+
</IconButton>
|
|
82
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Icon
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
의미 기반 아이콘 렌더러. `name`으로 **의미 슬롯**을 지정하고, 실제 SVG는 [[axes/icon]]의 Family(phosphor/solar/lucide)가 결정. **한 페이지 = 한 Family** 원칙. family는 `<html data-icon-family>` 축에서 자동 상속. Weight(regular/fill)는 축이 아니라 prop — phosphor·solar만 실제 반응, lucide는 regular 고정.
|
|
5
|
+
|
|
6
|
+
### Do
|
|
7
|
+
- 항상 `name`으로 의미 지정: `<Icon name="check" />`.
|
|
8
|
+
- 라벨 없는 아이콘 쓰임엔 반드시 `aria-label` 또는 Tooltip (sender 쪽에서).
|
|
9
|
+
- `size`는 number(px). 컴포넌트 안에서 자동 상속되므로 대부분 지정 안 함.
|
|
10
|
+
- 활성/선택 상태 표시엔 `weight="fill"` (phosphor/solar만).
|
|
11
|
+
- 축 override가 필요한 특수 자리(브랜드 로고 아이콘 등)에만 `family` prop.
|
|
12
|
+
|
|
13
|
+
### Don't
|
|
14
|
+
- 인라인 `<svg>` 로 아이콘 그리지 않는다 — 반드시 Icon 컴포넌트. 브랜드 로고·Tooltip tail은 예외.
|
|
15
|
+
- `name`에 Icon.tsx 유니온 밖 문자열 금지 — 유령 아이콘은 **family별 placeholder 사각형**으로 렌더됨 (설계된 폴백).
|
|
16
|
+
- 한 페이지에서 여러 family 섞지 않는다. 축 override를 과용하면 CTDS 지문이 깨짐.
|
|
17
|
+
- Lucide에서 `weight="fill"` 요구 금지 — 원본이 fill 미제공, regular로 폴백.
|
|
18
|
+
- 아이콘 크기를 CSS로 override 금지. `size` prop 또는 컨테이너의 `--icon-size-*` 토큰 경유.
|
|
19
|
+
|
|
20
|
+
### Fallback
|
|
21
|
+
family는 축 상속(기본 `phosphor`), weight는 축 상속(기본 `regular`), size 애매하면 `20`.
|
|
22
|
+
|
|
23
|
+
### 관련
|
|
24
|
+
- [[axes/icon]] — Family 축 (data-icon-family). Weight는 축 아님.
|
|
25
|
+
- [[components/icon-button]] · [[components/button]] — 아이콘 담는 가장 흔한 자리
|
|
26
|
+
- [[components/menu-item]] · [[components/badge]] · [[components/chip]] — leadingActions/leftIcon 자리
|
|
27
|
+
- [[tokens/color]] — `--icon-color`가 Palette 축에 반응
|
|
28
|
+
|
|
29
|
+
### Props 시그니처
|
|
30
|
+
```tsx
|
|
31
|
+
<Icon
|
|
32
|
+
name={IconName} // Icon.tsx 유니온 (phosphor + solar + lucide 합집합)
|
|
33
|
+
family="phosphor | solar | lucide" // default: 축 상속
|
|
34
|
+
weight="regular | fill" // default: 축 상속 (phosphor/solar만 반응)
|
|
35
|
+
size={number | string} // default: 20
|
|
36
|
+
// 그 외 SVG attributes 전부 통과
|
|
37
|
+
/>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Family별 지원
|
|
41
|
+
| Family | Weight 반응 | 스타일 특징 | 쓰임 |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `phosphor` | regular / fill | 다재다능, 모던, 선·면 균형 | **기본** |
|
|
44
|
+
| `solar` | Linear(regular) / Bold(fill) | 두꺼운 선·단단한 느낌 | 모바일·터치 |
|
|
45
|
+
| `lucide` | regular만 (fill 요청 시 regular) | 얇은 Feather 계승, 미니멀 | 미니멀·docs |
|
|
46
|
+
|
|
47
|
+
### 유령 이름 처리
|
|
48
|
+
- `name="존재하지-않는-슬롯"` → **family별 placeholder 사각형**이 자동 렌더. 레이아웃 공간 유지.
|
|
49
|
+
- 축 전환 시 "이 아이콘은 이 family에 없다"가 시각적으로 드러나는 설계된 폴백.
|
|
50
|
+
|
|
51
|
+
### 조합 예시
|
|
52
|
+
```tsx
|
|
53
|
+
// 기본 — 축 상속
|
|
54
|
+
<Icon name="check" />
|
|
55
|
+
<Icon name="warning" size={16} />
|
|
56
|
+
|
|
57
|
+
// 활성 상태 fill
|
|
58
|
+
<Icon name="heart" weight={isLiked ? "fill" : "regular"} />
|
|
59
|
+
|
|
60
|
+
// Family override — 로고 자리처럼 특수한 지점만
|
|
61
|
+
<Icon name="github-logo" family="phosphor" size={24} />
|
|
62
|
+
|
|
63
|
+
// Button 안 — 자동 크기 상속 (ICON_SIZE 매핑)
|
|
64
|
+
<Button leftIcon={<Icon name="plus" />}>추가</Button>
|
|
65
|
+
|
|
66
|
+
// MenuItem leadingActions
|
|
67
|
+
<MenuItem leadingActions={[<Icon name="user" />]}>프로필</MenuItem>
|
|
68
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Input
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
|
|
5
|
+
폼 입력의 최소 단위. 사용자가 한 줄의 텍스트/숫자/비밀번호 등을 입력하는 컨트롤. 라벨·설명·에러는 이 컴포넌트가 아니라 [[components/form-field]]가 담당한다 — Input은 **입력 자체**에만 책임을 진다.
|
|
6
|
+
|
|
7
|
+
### Do
|
|
8
|
+
- **variant는 `line`을 먼저 쓴다.** 모달·폼·대시보드 어디든 기본값. 밑줄 하나로 입력 표식이 끝나 시각 무게가 가장 가볍고 주변 레이아웃을 흐트러뜨리지 않음. `outline`은 폼이 독립 블록(가입/로그인 전용 페이지 등)으로 자리할 때, `filled`는 관리자·엑셀 대체 UI 등 밀도가 승부처일 때만.
|
|
9
|
+
- 라벨이 필요한 실제 폼에서는 항상 `FormField`로 감싼다. Input을 라벨 없이 단독으로 두는 것은 검색창 · Popover 트리거 · 인라인 필터에 한한다.
|
|
10
|
+
- 통화 · 단위 · 도메인처럼 사용자가 편집하지 않는 고정 문자열은 `prefix` / `suffix`로 넣는다. placeholder에 넣지 않는다.
|
|
11
|
+
- 아이콘은 시맨틱 힌트(`leftIcon`=검색 돋보기, 메일 봉투 등). 인터랙션이 있으면 `rightAction` 슬롯으로 실제 버튼을 넣는다.
|
|
12
|
+
- 값을 지울 수 있어야 하면 `clearable`. 비밀번호는 `passwordToggle` 을 켠다.
|
|
13
|
+
- 검증 실패 시 `invalid` 활성 + `FormField.Error`로 사유를 알린다. `invalid`만 켜고 에러 문구를 안 주면 사용자가 이유를 모른다.
|
|
14
|
+
- 다른 컴포넌트(DatePicker · Combobox · Select)가 트리거로 Input을 재사용할 때는 `readOnly` + `role="combobox"` 조합. 새 스타일을 만들지 않는다.
|
|
15
|
+
|
|
16
|
+
### Don't
|
|
17
|
+
- `leftIcon`에 클릭 액션을 붙이지 않는다. 아이콘은 항상 `aria-hidden`. 액션이 필요하면 `rightAction`.
|
|
18
|
+
- placeholder를 라벨 대신 쓰지 않는다 — 값이 들어가면 힌트가 사라진다. 접근성 실패.
|
|
19
|
+
- `variant="filled"`와 `invalid`를 조합해서 배경까지 destructive 계열로 물들이지 않는다. 보더/링만 destructive.
|
|
20
|
+
- 사이즈 조정용으로 CSS로 height를 덮어쓰지 않는다. `size` prop 경유.
|
|
21
|
+
|
|
22
|
+
### Fallback
|
|
23
|
+
variant가 애매하면 **`line`**(기본 권장). 독립 폼 블록이면 `outline`, 밀도 승부면 `filled`. size가 애매하면 `md`. 폼 전체가 일관된 밀도를 갖도록 한 화면에서 size·variant를 섞지 않는다.
|
|
24
|
+
|
|
25
|
+
### 관련
|
|
26
|
+
- [[components/form-field]] — 라벨 · 설명 · 에러 · required 별표를 담당. Input의 접근성 짝.
|
|
27
|
+
- [[components/date-picker]] — Input을 readOnly 트리거로 재사용.
|
|
28
|
+
- [[axes/density]] — `--control-h-sm/md/lg`가 size에 매핑.
|
|
29
|
+
- [[axes/shape]] — 컨테이너 radius가 Shape 축에 반응.
|
|
30
|
+
- [[tokens/color]] — `--primary`(focus), `--destructive`(invalid), `--bg-subtle`(filled/readOnly).
|
|
31
|
+
|
|
32
|
+
### Props 시그니처
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Input
|
|
36
|
+
size="sm | md | lg" // default: md
|
|
37
|
+
variant="line | outline | filled" // default: outline (문서 권장 기본: line)
|
|
38
|
+
|
|
39
|
+
value={string} defaultValue={string}
|
|
40
|
+
onChange={(e) => void}
|
|
41
|
+
type="text | email | password | number | ..."
|
|
42
|
+
placeholder={string}
|
|
43
|
+
|
|
44
|
+
invalid={boolean}
|
|
45
|
+
disabled={boolean}
|
|
46
|
+
readOnly={boolean}
|
|
47
|
+
fullWidth={boolean}
|
|
48
|
+
|
|
49
|
+
leftIcon={ReactNode} rightIcon={ReactNode} // 시각용
|
|
50
|
+
prefix={ReactNode} suffix={ReactNode} // 짧은 텍스트
|
|
51
|
+
|
|
52
|
+
clearable={boolean} onClear={() => void}
|
|
53
|
+
passwordToggle={boolean} // type="password"에만 유효
|
|
54
|
+
rightAction={ReactNode} // 커스텀 우측 액션(버튼 등)
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Variant 매트릭스
|
|
59
|
+
|
|
60
|
+
| variant | 용도 | 금지 조합 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `line` | **권장 기본값.** 모달·폼·대시보드 어디든. 밑줄만으로 입력 자리를 알리고 주변 레이아웃을 흐트러뜨리지 않음 | — |
|
|
63
|
+
| `outline` | 독립 폼 블록(가입/로그인 전용 페이지 등) | — |
|
|
64
|
+
| `filled` | 밀도 높은 관리자·대시보드. 시각 무게가 가벼움 | `invalid`와 결합 시 배경까지 물들이지 말 것 |
|
|
65
|
+
|
|
66
|
+
**상태 조합:**
|
|
67
|
+
|
|
68
|
+
| 상태 | 시각 반응 | 접근성 |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `disabled` | 반투명 + cursor:not-allowed | 포커스 불가 |
|
|
71
|
+
| `readOnly` | 배경만 살짝 눌림(bg-subtle) | 포커스 가능, 편집 불가 |
|
|
72
|
+
| `invalid` | destructive 보더/링 | `aria-invalid="true"` |
|
|
73
|
+
|
|
74
|
+
### 조합 예시
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// 로그인 폼: 라벨/에러 자동 연결
|
|
78
|
+
<FormField required invalid={!!error}>
|
|
79
|
+
<FormField.Label>이메일</FormField.Label>
|
|
80
|
+
<Input
|
|
81
|
+
type="email"
|
|
82
|
+
value={email}
|
|
83
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
84
|
+
leftIcon={<MailIcon />}
|
|
85
|
+
invalid={!!error}
|
|
86
|
+
/>
|
|
87
|
+
<FormField.Error>{error}</FormField.Error>
|
|
88
|
+
</FormField>
|
|
89
|
+
|
|
90
|
+
// 검색: 라벨 없이 단독
|
|
91
|
+
<Input placeholder="검색" leftIcon={<SearchIcon />} clearable value={q} onChange={..} />
|
|
92
|
+
|
|
93
|
+
// 금액: prefix/suffix
|
|
94
|
+
<Input prefix="$" suffix="USD" type="number" placeholder="0.00" />
|
|
95
|
+
```
|