@iamfiro/design 0.0.0-stage → 0.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/AGENTS.md +81 -0
- package/README.md +15 -2
- package/agents/INDEX.md +156 -0
- package/agents/accordion/AGENTS.md +279 -0
- package/agents/action-bar/AGENTS.md +90 -0
- package/agents/alert/AGENTS.md +212 -0
- package/agents/alert-dialog/AGENTS.md +193 -0
- package/agents/app-shell/AGENTS.md +409 -0
- package/agents/area-chart/AGENTS.md +183 -0
- package/agents/aspect-ratio/AGENTS.md +143 -0
- package/agents/avatar/AGENTS.md +130 -0
- package/agents/badge/AGENTS.md +217 -0
- package/agents/bar-chart/AGENTS.md +386 -0
- package/agents/bar-list/AGENTS.md +223 -0
- package/agents/brand-icon/AGENTS.md +82 -0
- package/agents/breadcrumb/AGENTS.md +133 -0
- package/agents/button/AGENTS.md +195 -0
- package/agents/card/AGENTS.md +141 -0
- package/agents/card-frame/AGENTS.md +227 -0
- package/agents/chat-bubble/AGENTS.md +266 -0
- package/agents/chat-composer/AGENTS.md +251 -0
- package/agents/checkbox/AGENTS.md +113 -0
- package/agents/code-block/AGENTS.md +144 -0
- package/agents/color-dot/AGENTS.md +120 -0
- package/agents/color-picker/AGENTS.md +224 -0
- package/agents/colors/AGENTS.md +168 -0
- package/agents/combobox/AGENTS.md +218 -0
- package/agents/command-menu/AGENTS.md +169 -0
- package/agents/comment/AGENTS.md +206 -0
- package/agents/context-menu/AGENTS.md +303 -0
- package/agents/copy-button/AGENTS.md +84 -0
- package/agents/count-badge/AGENTS.md +115 -0
- package/agents/countdown/AGENTS.md +101 -0
- package/agents/data-grid/AGENTS.md +460 -0
- package/agents/date-picker/AGENTS.md +197 -0
- package/agents/delta/AGENTS.md +114 -0
- package/agents/description-list/AGENTS.md +251 -0
- package/agents/dialog/AGENTS.md +391 -0
- package/agents/drawer/AGENTS.md +471 -0
- package/agents/dropdown-menu/AGENTS.md +322 -0
- package/agents/dropzone/AGENTS.md +116 -0
- package/agents/elapsed/AGENTS.md +160 -0
- package/agents/empty-state/AGENTS.md +203 -0
- package/agents/file-chip/AGENTS.md +79 -0
- package/agents/file-icon/AGENTS.md +66 -0
- package/agents/file-upload/AGENTS.md +168 -0
- package/agents/filter-chips/AGENTS.md +167 -0
- package/agents/flag/AGENTS.md +67 -0
- package/agents/funnel-chart/AGENTS.md +131 -0
- package/agents/highlight/AGENTS.md +119 -0
- package/agents/hover-card/AGENTS.md +271 -0
- package/agents/icon-stack/AGENTS.md +120 -0
- package/agents/icon-tile/AGENTS.md +94 -0
- package/agents/inline-diff/AGENTS.md +211 -0
- package/agents/input/AGENTS.md +261 -0
- package/agents/kanban/AGENTS.md +352 -0
- package/agents/kbd/AGENTS.md +160 -0
- package/agents/layout/AGENTS.md +351 -0
- package/agents/line-chart/AGENTS.md +393 -0
- package/agents/link/AGENTS.md +53 -0
- package/agents/list/AGENTS.md +392 -0
- package/agents/marker/AGENTS.md +142 -0
- package/agents/masked-text/AGENTS.md +127 -0
- package/agents/menubar/AGENTS.md +276 -0
- package/agents/multi-select/AGENTS.md +159 -0
- package/agents/navigation-menu/AGENTS.md +315 -0
- package/agents/notice-stack/AGENTS.md +101 -0
- package/agents/number-stepper/AGENTS.md +96 -0
- package/agents/otp-input/AGENTS.md +162 -0
- package/agents/pagination/AGENTS.md +189 -0
- package/agents/phone-input/AGENTS.md +131 -0
- package/agents/pie-chart/AGENTS.md +243 -0
- package/agents/popover/AGENTS.md +190 -0
- package/agents/progress/AGENTS.md +273 -0
- package/agents/progress-tracker/AGENTS.md +305 -0
- package/agents/rating/AGENTS.md +165 -0
- package/agents/relative-time/AGENTS.md +103 -0
- package/agents/resizable/AGENTS.md +390 -0
- package/agents/scaled-preview/AGENTS.md +111 -0
- package/agents/scatter-chart/AGENTS.md +160 -0
- package/agents/scroll-area/AGENTS.md +271 -0
- package/agents/search-input/AGENTS.md +105 -0
- package/agents/segmented-control/AGENTS.md +153 -0
- package/agents/select/AGENTS.md +159 -0
- package/agents/selector-button/AGENTS.md +122 -0
- package/agents/sidebar/AGENTS.md +201 -0
- package/agents/skeleton/AGENTS.md +166 -0
- package/agents/slider/AGENTS.md +181 -0
- package/agents/spark-chart/AGENTS.md +257 -0
- package/agents/sparkbar/AGENTS.md +92 -0
- package/agents/spinner/AGENTS.md +123 -0
- package/agents/split-view/AGENTS.md +290 -0
- package/agents/stat/AGENTS.md +138 -0
- package/agents/status-icon/AGENTS.md +243 -0
- package/agents/step-list/AGENTS.md +313 -0
- package/agents/table/AGENTS.md +286 -0
- package/agents/tabs/AGENTS.md +305 -0
- package/agents/tag-input/AGENTS.md +246 -0
- package/agents/time-picker/AGENTS.md +228 -0
- package/agents/timeline/AGENTS.md +1018 -0
- package/agents/toast/AGENTS.md +266 -0
- package/agents/toggle-group/AGENTS.md +216 -0
- package/agents/toolbar/AGENTS.md +277 -0
- package/agents/tooltip/AGENTS.md +154 -0
- package/agents/tracker/AGENTS.md +158 -0
- package/agents/tree-diagram/AGENTS.md +260 -0
- package/agents/tree-view/AGENTS.md +329 -0
- package/agents/typography/AGENTS.md +396 -0
- package/lib/index.js +17914 -0
- package/lib/style.css +2 -0
- package/lib/types/components/Accordion/Accordion.d.ts +57 -0
- package/lib/types/components/ActionBar/ActionBar.d.ts +24 -0
- package/lib/types/components/Alert/Alert.d.ts +36 -0
- package/lib/types/components/AppShell/AppShell.d.ts +38 -0
- package/lib/types/components/AreaChart/AreaChart.d.ts +17 -0
- package/lib/types/components/BarChart/BarChart.d.ts +57 -0
- package/lib/types/components/BarList/BarList.d.ts +56 -0
- package/lib/types/components/Breadcrumb/Breadcrumb.d.ts +15 -0
- package/lib/types/components/Calendar/Calendar.d.ts +34 -0
- package/lib/types/components/Calendar/DatePicker.d.ts +45 -0
- package/lib/types/components/Calendar/date.d.ts +32 -0
- package/lib/types/components/Card/Card.d.ts +64 -0
- package/lib/types/components/CardFrame/CardFrame.d.ts +15 -0
- package/lib/types/components/Chart/ChartParts.d.ts +38 -0
- package/lib/types/components/Chart/chart.d.ts +46 -0
- package/lib/types/components/Chart/useChartInteraction.d.ts +39 -0
- package/lib/types/components/ChatBubble/ChatBubble.d.ts +61 -0
- package/lib/types/components/ChatBubble/formatChatTime.d.ts +2 -0
- package/lib/types/components/ChatComposer/ChatComposer.d.ts +42 -0
- package/lib/types/components/CodeBlock/CodeBlock.d.ts +18 -0
- package/lib/types/components/CodeBlock/highlight.d.ts +10 -0
- package/lib/types/components/ColorPicker/ColorPicker.d.ts +61 -0
- package/lib/types/components/ColorPicker/color.d.ts +48 -0
- package/lib/types/components/ColorPicker/presets.d.ts +7 -0
- package/lib/types/components/Combobox/Combobox.d.ts +48 -0
- package/lib/types/components/CommandMenu/CommandMenu.d.ts +51 -0
- package/lib/types/components/Comment/Comment.d.ts +45 -0
- package/lib/types/components/ContextMenu/ContextMenu.d.ts +40 -0
- package/lib/types/components/DataGrid/DataGrid.d.ts +43 -0
- package/lib/types/components/DataGrid/GridPopover.d.ts +43 -0
- package/lib/types/components/DataGrid/anchor.d.ts +5 -0
- package/lib/types/components/DataGrid/model.d.ts +61 -0
- package/lib/types/components/DataGrid/panels.d.ts +63 -0
- package/lib/types/components/DataGrid/typeMeta.d.ts +6 -0
- package/lib/types/components/DataGrid/types.d.ts +77 -0
- package/lib/types/components/DescriptionList/DescriptionList.d.ts +77 -0
- package/lib/types/components/Dialog/AlertDialog.d.ts +26 -0
- package/lib/types/components/Dialog/Dialog.d.ts +42 -0
- package/lib/types/components/Dialog/modal.d.ts +38 -0
- package/lib/types/components/Drawer/Drawer.d.ts +62 -0
- package/lib/types/components/DropdownMenu/DropdownMenu.d.ts +97 -0
- package/lib/types/components/DropdownMenu/menuCore.d.ts +23 -0
- package/lib/types/components/Dropzone/Dropzone.d.ts +30 -0
- package/lib/types/components/EmptyState/EmptyState.d.ts +26 -0
- package/lib/types/components/FileChip/FileChip.d.ts +32 -0
- package/lib/types/components/FileUpload/FileUpload.d.ts +52 -0
- package/lib/types/components/FilterChips/FilterChips.d.ts +37 -0
- package/lib/types/components/FunnelChart/FunnelChart.d.ts +38 -0
- package/lib/types/components/FunnelChart/funnel.d.ts +25 -0
- package/lib/types/components/Highlight/Highlight.d.ts +35 -0
- package/lib/types/components/HoverCard/HoverCard.d.ts +47 -0
- package/lib/types/components/InlineDiff/InlineDiff.d.ts +36 -0
- package/lib/types/components/InlineDiff/diff.d.ts +42 -0
- package/lib/types/components/InlineDiff/useDiffReview.d.ts +57 -0
- package/lib/types/components/Kanban/Kanban.d.ts +64 -0
- package/lib/types/components/Kanban/KanbanCard.d.ts +35 -0
- package/lib/types/components/Kanban/move.d.ts +25 -0
- package/lib/types/components/LineChart/LineChart.d.ts +85 -0
- package/lib/types/components/List/List.d.ts +62 -0
- package/lib/types/components/Listbox/Listbox.d.ts +60 -0
- package/lib/types/components/Marker/Marker.d.ts +28 -0
- package/lib/types/components/Menubar/Menubar.d.ts +50 -0
- package/lib/types/components/MultiSelect/MultiSelect.d.ts +38 -0
- package/lib/types/components/NavigationMenu/NavigationMenu.d.ts +101 -0
- package/lib/types/components/NoticeStack/NoticeStack.d.ts +41 -0
- package/lib/types/components/NumberStepper/NumberStepper.d.ts +32 -0
- package/lib/types/components/OtpInput/OtpInput.d.ts +31 -0
- package/lib/types/components/Pagination/Pagination.d.ts +54 -0
- package/lib/types/components/Pagination/paginationRange.d.ts +7 -0
- package/lib/types/components/PhoneInput/PhoneInput.d.ts +47 -0
- package/lib/types/components/PieChart/PieChart.d.ts +67 -0
- package/lib/types/components/PieChart/arc.d.ts +26 -0
- package/lib/types/components/PieChart/useArcTween.d.ts +15 -0
- package/lib/types/components/Popover/Popover.d.ts +28 -0
- package/lib/types/components/ProgressTracker/ProgressTracker.d.ts +64 -0
- package/lib/types/components/Rating/Rating.d.ts +42 -0
- package/lib/types/components/Resizable/Resizable.d.ts +50 -0
- package/lib/types/components/Resizable/layout.d.ts +31 -0
- package/lib/types/components/ScaledPreview/ScaledPreview.d.ts +38 -0
- package/lib/types/components/ScatterChart/ScatterChart.d.ts +81 -0
- package/lib/types/components/ScatterChart/scatter.d.ts +14 -0
- package/lib/types/components/ScrollArea/ScrollArea.d.ts +34 -0
- package/lib/types/components/Select/Select.d.ts +39 -0
- package/lib/types/components/Sidebar/Sidebar.d.ts +39 -0
- package/lib/types/components/Skeleton/Skeleton.d.ts +47 -0
- package/lib/types/components/Slider/Slider.d.ts +36 -0
- package/lib/types/components/SparkChart/SparkChart.d.ts +55 -0
- package/lib/types/components/SparkChart/spark.d.ts +16 -0
- package/lib/types/components/Sparkbar/Sparkbar.d.ts +39 -0
- package/lib/types/components/SplitView/SplitView.d.ts +31 -0
- package/lib/types/components/Stat/Stat.d.ts +58 -0
- package/lib/types/components/StepList/StepList.d.ts +61 -0
- package/lib/types/components/Table/Table.d.ts +39 -0
- package/lib/types/components/Tabs/Tabs.d.ts +47 -0
- package/lib/types/components/TagInput/TagInput.d.ts +46 -0
- package/lib/types/components/TimePicker/TimePicker.d.ts +43 -0
- package/lib/types/components/TimePicker/time.d.ts +15 -0
- package/lib/types/components/Timeline/Timeline.d.ts +154 -0
- package/lib/types/components/Timeline/scale.d.ts +22 -0
- package/lib/types/components/Toast/Toaster.d.ts +19 -0
- package/lib/types/components/Toast/index.d.ts +4 -0
- package/lib/types/components/Toast/store.d.ts +75 -0
- package/lib/types/components/ToggleGroup/ToggleGroup.d.ts +28 -0
- package/lib/types/components/Toolbar/Toolbar.d.ts +70 -0
- package/lib/types/components/Tracker/Tracker.d.ts +43 -0
- package/lib/types/components/TreeDiagram/TreeDiagram.d.ts +68 -0
- package/lib/types/components/TreeView/TreeView.d.ts +107 -0
- package/lib/types/components/index.d.ts +186 -0
- package/lib/types/hooks/index.d.ts +17 -0
- package/lib/types/hooks/timing.d.ts +29 -0
- package/lib/types/hooks/useControllableState.d.ts +5 -0
- package/lib/types/hooks/useCountdown.d.ts +19 -0
- package/lib/types/hooks/useDigitInput.d.ts +24 -0
- package/lib/types/hooks/useDismiss.d.ts +3 -0
- package/lib/types/hooks/useElapsed.d.ts +28 -0
- package/lib/types/hooks/useFloating.d.ts +21 -0
- package/lib/types/hooks/useFocusTrap.d.ts +7 -0
- package/lib/types/hooks/usePresence.d.ts +8 -0
- package/lib/types/hooks/usePressToOpen.d.ts +20 -0
- package/lib/types/hooks/useScrollLock.d.ts +2 -0
- package/lib/types/hooks/useSelection.d.ts +29 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/primitives/AspectRatio.d.ts +14 -0
- package/lib/types/primitives/BrandIcon.d.ts +12 -0
- package/lib/types/primitives/ColorDot.d.ts +20 -0
- package/lib/types/primitives/CopyButton.d.ts +12 -0
- package/lib/types/primitives/CountBadge.d.ts +28 -0
- package/lib/types/primitives/Countdown.d.ts +21 -0
- package/lib/types/primitives/Delta.d.ts +33 -0
- package/lib/types/primitives/Elapsed.d.ts +23 -0
- package/lib/types/primitives/FileIcon.d.ts +17 -0
- package/lib/types/primitives/Flag.d.ts +15 -0
- package/lib/types/primitives/IconStack.d.ts +27 -0
- package/lib/types/primitives/IconTile.d.ts +24 -0
- package/lib/types/primitives/Kbd.d.ts +19 -0
- package/lib/types/primitives/Layout.d.ts +64 -0
- package/lib/types/primitives/Link.d.ts +12 -0
- package/lib/types/primitives/MaskedText.d.ts +28 -0
- package/lib/types/primitives/Progress.d.ts +52 -0
- package/lib/types/primitives/RelativeTime.d.ts +22 -0
- package/lib/types/primitives/SearchInput.d.ts +8 -0
- package/lib/types/primitives/SelectorButton.d.ts +23 -0
- package/lib/types/primitives/Spinner.d.ts +15 -0
- package/lib/types/primitives/StatusIcon.d.ts +40 -0
- package/lib/types/primitives/Swatch.d.ts +11 -0
- package/lib/types/primitives/Tooltip.d.ts +17 -0
- package/lib/types/primitives/Typography.d.ts +48 -0
- package/lib/types/primitives/formatCountdown.d.ts +2 -0
- package/lib/types/primitives/formatElapsed.d.ts +8 -0
- package/lib/types/primitives/formatRelativeTime.d.ts +16 -0
- package/lib/types/primitives/hotkey.d.ts +10 -0
- package/lib/types/primitives/index.d.ts +188 -0
- package/lib/types/primitives/maskValue.d.ts +24 -0
- package/package.json +54 -4
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# IconStack
|
|
2
|
+
|
|
3
|
+
> 아이콘 & 아바타 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
작은 아이콘 · 로고 2–3개를 겹쳐 한 줄로 보여 줘요. 작업 설명 앞에 "어떤 앱이 관련됐는지"를 붙일 때 좋아요. AvatarGroup도 이 컴포넌트로 만들어져 있어요. 아이콘끼리는 테두리가 아니라 바탕색 링으로 잘라 구분해요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { IconStack } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 겹친 작은 아이콘 · 로고는 `IconStack`(AvatarGroup도 이것으로). 겹침은 테두리가 아니라 바탕색 링으로 자른다.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### IconStack
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `icons` (필수) | `ReactNode[]` | | 앞에서부터 보일 아이콘 · 로고 · 아바타. 크기는 각 아이콘에서 정해요. |
|
|
22
|
+
| `size` | `number` | `20` | 항목 하나의 지름(px). |
|
|
23
|
+
| `max` | `number` | `3` | 보여 줄 개수. 나머지는 +N. |
|
|
24
|
+
| `overlap` | `number` | `size / 3` | 앞 항목 밑으로 들어가는 폭(px). |
|
|
25
|
+
| `shape` | `'circle' \| 'square'` | `'circle'` | 항목 모양. |
|
|
26
|
+
| `tile` | `boolean` | | 아이콘 밑에 흰 타일 + 옅은 그림자. |
|
|
27
|
+
| `renderOverflow` | `(hidden: number) => ReactNode` | | +N 항목을 직접 그려요(AvatarGroup은 Avatar로). |
|
|
28
|
+
| `label` | `string` | | 묶음 전체의 이름. 없으면 장식으로 취급해 스크린 리더에서 숨겨요. |
|
|
29
|
+
| `className` | `string` | | 추가 클래스. |
|
|
30
|
+
| `style` | `CSSProperties` | | 인라인 스타일. |
|
|
31
|
+
|
|
32
|
+
## 예제
|
|
33
|
+
|
|
34
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
35
|
+
|
|
36
|
+
### 기본
|
|
37
|
+
|
|
38
|
+
tile은 로고마다 흰 타일을 깔고, max를 넘는 아이콘은 +N으로 묶어요. BrandIcon의 size는 직접 정해요(타일 크기의 70% 정도).
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { BrandIcon, IconStack, Inline } from '@iamfiro/design'
|
|
42
|
+
|
|
43
|
+
export default function Example() {
|
|
44
|
+
return (
|
|
45
|
+
<Inline gap={8}>
|
|
46
|
+
<IconStack tile icons={[<BrandIcon key="slack" name="slack" size={14} />, <BrandIcon key="github" name="github" size={14} />]} label="Slack, GitHub" />
|
|
47
|
+
<IconStack
|
|
48
|
+
tile
|
|
49
|
+
icons={(['figma', 'slack', 'gmail'] as const).map((n) => (
|
|
50
|
+
<BrandIcon key={n} name={n} size={14} />
|
|
51
|
+
))}
|
|
52
|
+
label="Figma, Slack, Gmail"
|
|
53
|
+
/>
|
|
54
|
+
<IconStack
|
|
55
|
+
tile
|
|
56
|
+
size={24}
|
|
57
|
+
max={2}
|
|
58
|
+
icons={(['gmail', 'clickup', 'figma', 'github', 'slack'] as const).map((n) => (
|
|
59
|
+
<BrandIcon key={n} name={n} size={16} />
|
|
60
|
+
))}
|
|
61
|
+
label="Gmail, ClickUp 외 3개"
|
|
62
|
+
/>
|
|
63
|
+
</Inline>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 작업 행
|
|
69
|
+
|
|
70
|
+
ListItem의 leading에 넣어 관련 앱을 보여 줘요. label을 주면 스크린 리더가 앱 이름을 읽어요.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Badge, BrandIcon, IconStack, type BrandName, List, ListItem } from '@iamfiro/design'
|
|
74
|
+
|
|
75
|
+
const TASKS: Array<{ id: string; title: string; meta: string; apps: BrandName[]; names: string }> = [
|
|
76
|
+
{ id: 'design', title: '디자인 리뷰 코멘트 정리', meta: '김민지', apps: ['figma', 'slack'], names: 'Figma, Slack' },
|
|
77
|
+
{ id: 'release', title: '릴리스 노트 메일 보내기', meta: '이도현', apps: ['github', 'gmail'], names: 'GitHub, Gmail' },
|
|
78
|
+
{ id: 'sprint', title: '스프린트 보드 업데이트', meta: '최서연', apps: ['clickup', 'slack', 'github', 'figma'], names: 'ClickUp, Slack, GitHub, Figma' },
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
export default function Example() {
|
|
82
|
+
return (
|
|
83
|
+
<List label="오늘 할 일" style={{ width: '100%', maxWidth: 460 }}>
|
|
84
|
+
{TASKS.map((t) => (
|
|
85
|
+
<ListItem
|
|
86
|
+
key={t.id}
|
|
87
|
+
leading={<IconStack tile max={2} icons={t.apps.map((a) => <BrandIcon key={a} name={a} size={14} />)} label={t.names} />}
|
|
88
|
+
title={t.title}
|
|
89
|
+
meta={t.meta}
|
|
90
|
+
trailing={t.id === 'release' ? <Badge tone="warning" size="sm">오늘 마감</Badge> : undefined}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</List>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 모양 · 크기 · 겹침
|
|
99
|
+
|
|
100
|
+
shape="square"는 앱 아이콘처럼 둥근 사각형. overlap으로 겹치는 정도를 조절해요.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { FileText, Image as ImageIcon, Table2 } from 'lucide-react'
|
|
104
|
+
import { Avatar, BrandIcon, IconStack, Inline } from '@iamfiro/design'
|
|
105
|
+
|
|
106
|
+
export default function Example() {
|
|
107
|
+
return (
|
|
108
|
+
<Inline gap={8}>
|
|
109
|
+
<IconStack
|
|
110
|
+
tile
|
|
111
|
+
shape="square"
|
|
112
|
+
size={28}
|
|
113
|
+
icons={[<BrandIcon key="figma" name="figma" size={18} />, <BrandIcon key="github" name="github" size={18} />, <BrandIcon key="slack" name="slack" size={18} />]}
|
|
114
|
+
/>
|
|
115
|
+
<IconStack tile size={24} overlap={4} icons={[<FileText key="doc" size={13} />, <Table2 key="sheet" size={13} />, <ImageIcon key="image" size={13} />]} />
|
|
116
|
+
<IconStack size={32} overlap={10} icons={['김민지', '이도현', '최서연', '정우진'].map((n) => <Avatar key={n} name={n} size={32} />)} />
|
|
117
|
+
</Inline>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# IconTile
|
|
2
|
+
|
|
3
|
+
> 아이콘 & 아바타 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
아이콘이나 글자 하나를 담는 작은 채움 타일. 도구 · 사이트 · 문서처럼 사람이 아닌 것의 목록 행 앞(ListItem leading)에 써요. 사람은 Avatar로. 기본은 작은 회색 채움이고, 상태 색은 tone으로 골라요. 로고나 여러 색 아이콘은 회색이 탁해 보이니 tone="surface"(흰 타일 + 옅은 그림자)로.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { IconTile } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 행 앞 타일: 사람은 `Avatar`, 도구·사이트·문서는 `IconTile`(회색 채움 + 아이콘/글자). 선택된 것을 보여 주고 메뉴로 바꾸는 버튼(워크스페이스·계정 전환, 드롭다운 제목)은 `SelectorButton`을 `DropdownMenu` trigger에.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### IconTile
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `children` (필수) | `ReactNode` | | 아이콘 또는 짧은 글자. |
|
|
22
|
+
| `size` | `number` | `28` | 한 변 길이(px). |
|
|
23
|
+
| `tone` | `'neutral' \| 'surface' \| 'accent' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | neutral은 회색 채움, surface는 옅은 그림자로 띄운 흰 타일(로고 · 여러 색 아이콘용), 나머지는 연한 상태색. |
|
|
24
|
+
| `round` | `boolean` | | 둥근 모양. |
|
|
25
|
+
| `aria-label` | `string` | | 주면 role="img"로 읽혀요. 없으면 장식으로 숨겨요. |
|
|
26
|
+
| `className / style` | `string / CSSProperties` | | 바깥 요소에 전달. |
|
|
27
|
+
|
|
28
|
+
## 예제
|
|
29
|
+
|
|
30
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
31
|
+
|
|
32
|
+
### 모양 · 톤 · 크기
|
|
33
|
+
|
|
34
|
+
아이콘은 타일 크기의 약 절반으로 맞춰져요. 글자는 한 글자(또는 두 글자)까지.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { FileText, Globe, ImageIcon, Sparkles, TriangleAlert } from 'lucide-react'
|
|
38
|
+
import { IconTile, Inline, Stack, Text } from '@iamfiro/design'
|
|
39
|
+
|
|
40
|
+
export default function Example() {
|
|
41
|
+
return (
|
|
42
|
+
<Stack gap={5}>
|
|
43
|
+
<Stack gap={2}>
|
|
44
|
+
<Text variant="caption" tone="tertiary">
|
|
45
|
+
아이콘 · 글자
|
|
46
|
+
</Text>
|
|
47
|
+
<Inline gap={3}>
|
|
48
|
+
<IconTile>
|
|
49
|
+
<FileText />
|
|
50
|
+
</IconTile>
|
|
51
|
+
<IconTile>
|
|
52
|
+
<Globe />
|
|
53
|
+
</IconTile>
|
|
54
|
+
<IconTile>문</IconTile>
|
|
55
|
+
<IconTile>N</IconTile>
|
|
56
|
+
<IconTile round>
|
|
57
|
+
<ImageIcon />
|
|
58
|
+
</IconTile>
|
|
59
|
+
</Inline>
|
|
60
|
+
</Stack>
|
|
61
|
+
<Stack gap={2}>
|
|
62
|
+
<Text variant="caption" tone="tertiary">
|
|
63
|
+
tone
|
|
64
|
+
</Text>
|
|
65
|
+
<Inline gap={3}>
|
|
66
|
+
<IconTile tone="surface">
|
|
67
|
+
<Globe />
|
|
68
|
+
</IconTile>
|
|
69
|
+
<IconTile tone="accent">
|
|
70
|
+
<Sparkles />
|
|
71
|
+
</IconTile>
|
|
72
|
+
<IconTile tone="success">완</IconTile>
|
|
73
|
+
<IconTile tone="warning">
|
|
74
|
+
<TriangleAlert />
|
|
75
|
+
</IconTile>
|
|
76
|
+
<IconTile tone="danger">!</IconTile>
|
|
77
|
+
</Inline>
|
|
78
|
+
</Stack>
|
|
79
|
+
<Stack gap={2}>
|
|
80
|
+
<Text variant="caption" tone="tertiary">
|
|
81
|
+
size 20 · 28 · 36 · 44
|
|
82
|
+
</Text>
|
|
83
|
+
<Inline gap={3}>
|
|
84
|
+
{[20, 28, 36, 44].map((s) => (
|
|
85
|
+
<IconTile key={s} size={s}>
|
|
86
|
+
<FileText />
|
|
87
|
+
</IconTile>
|
|
88
|
+
))}
|
|
89
|
+
</Inline>
|
|
90
|
+
</Stack>
|
|
91
|
+
</Stack>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# InlineDiff
|
|
2
|
+
|
|
3
|
+
> 텍스트 & 코드 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
글 속 변경 내용(추적된 변경)을 그대로 보여 줘요. 지운 글자는 빨간 취소선 + 연한 빨간 배경, 넣은 글자는 초록 + 연한 초록 배경이고, 진짜 <del> · <ins>에 화면 읽기용 "삭제" · "추가"가 붙어요. 바로 이어진 삭제 + 추가는 한 변경으로 묶여요. reviewable이면 변경마다 눌러 수락 · 거절하는 작은 팝오버가 열리고, 수락하면 새 글로 · 거절하면 원래 글로 폭을 접으며 자연스럽게 정리돼요. 툴바(남은 개수, 이전 · 다음, 모두 수락)는 useDiffReview가 돌려주는 같은 상태로 조립해요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { InlineDiff, applyDiffDecisions, diffWords, useDiffReview } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 글 속 변경 내용(추적된 변경)은 `InlineDiff`: 삭제 = 빨간 취소선 + `--danger-soft`, 추가 = 초록 + `--success-soft`, 진짜 `<del>` · `<ins>` + 화면 읽기 "삭제" · "추가". 검토(수락 · 거절)는 `reviewable`, 툴바(남은 개수 `CountBadge variant="soft" tone="success"`, 이전 · 다음 `Button iconOnly`, 모두 거절 · 모두 수락)는 `useDiffReview` 상태를 함께 써서 조립한다. 검토 카드는 `CardFrame header`(회색 판 위 아이콘 + 문서 이름) 안에 `Card raised` — 회색 Box를 카드 안 제목줄로 넣으면 입력창처럼 보인다. 본문 줄 간격은 넉넉하게(위아래 줄의 색 배경이 닿지 않게). 모두 수락 · 거절은 확인 대신 `toast.undo`. 두 문자열 비교는 `diffWords`, 결과 글은 `applyDiffDecisions`.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### InlineDiff
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `changes` | `DiffSegment[]` | | { type: 'equal' \| 'insert' \| 'delete', text, changeId? } 목록. changeId가 없으면 바로 이어진 삭제 · 추가를 한 변경으로 묶어요. |
|
|
22
|
+
| `before / after` | `string` | | changes 대신 두 문자열. diffWords로 단어 단위 비교. |
|
|
23
|
+
| `review` | `DiffReview` | | useDiffReview 결과. 툴바와 상태를 나눌 때. 주면 segments · decisions를 여기서 읽어요. |
|
|
24
|
+
| `reviewable` | `boolean` | `review가 있으면 true` | 변경을 눌러 수락 · 거절(팝오버). 화살표 · Home/End로 이동, Enter · Space로 열기. |
|
|
25
|
+
| `decisions` | `DiffDecisions` | | { [changeId]: 'accepted' \| 'rejected' } 제어 값. 없는 id는 대기. |
|
|
26
|
+
| `defaultDecisions` | `DiffDecisions` | | 비제어 초기값. |
|
|
27
|
+
| `onDecisionsChange` | `(decisions) => void` | | 결정이 바뀔 때. |
|
|
28
|
+
| `variant` | `TextVariant` | `'body-md'` | 글자 역할. |
|
|
29
|
+
| `as` | `'p' \| 'div' \| 'span'` | `'p'` | 루트 요소. |
|
|
30
|
+
| `aria-label` | `string` | | 본문 이름. |
|
|
31
|
+
|
|
32
|
+
### useDiffReview
|
|
33
|
+
|
|
34
|
+
옵션은 changes / before · after / decisions · defaultDecisions · onDecisionsChange / advance(기본 true: 결정 뒤 다음 남은 변경으로).
|
|
35
|
+
|
|
36
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `remaining · total` | `number` | | 남은(대기) 변경 수 · 전체 변경 수. |
|
|
39
|
+
| `current · currentIndex` | `string \| null · number` | | 현재 변경 id와 순서. |
|
|
40
|
+
| `next() · prev()` | `() => void` | | 다음 · 이전 남은 변경으로(모두 정했으면 전체에서), 끝에서 처음으로 돌아요. 스크롤해 보여 줘요. |
|
|
41
|
+
| `goTo(id) · setCurrent(id)` | `(id) => void` | | 현재 변경 바꾸기(goTo는 스크롤까지). |
|
|
42
|
+
| `accept(id?) · reject(id?)` | `(id?) => void` | | 하나 결정. 기본은 현재 변경. |
|
|
43
|
+
| `decide(id, decision | null)` | `function` | | null이면 대기로 되돌려요. |
|
|
44
|
+
| `acceptAll() · rejectAll()` | `() => void` | | 남은 변경 전부. 이미 정한 건 그대로. |
|
|
45
|
+
| `setDecisions · reset` | `function` | | 결정 통째로 바꾸기(실행 취소) · 모두 대기로. |
|
|
46
|
+
| `decisions · changes · segments · text` | `—` | | 결정 맵, 변경 목록({ id, deleted, inserted }), 묶인 조각, 결정이 반영된 글. |
|
|
47
|
+
|
|
48
|
+
### diff 도우미
|
|
49
|
+
|
|
50
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| `diffWords(before, after)` | `DiffSegment[]` | | 단어 · 공백 · 문장부호 토큰의 LCS. 변경 사이의 공백은 변경에 합쳐 한 번의 삭제 + 추가로 정리해요. |
|
|
53
|
+
| `applyDiffDecisions(segments, decisions, pending?)` | `string` | | 결정이 반영된 글. 대기 중인 변경은 pending('after' 기본 \| 'before'). |
|
|
54
|
+
| `resolveDiffSegments · listDiffChanges` | `function` | | changeId 묶기 · 변경 목록 만들기. |
|
|
55
|
+
|
|
56
|
+
## 예제
|
|
57
|
+
|
|
58
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
59
|
+
|
|
60
|
+
### 검토 카드 (툴바 조립)
|
|
61
|
+
|
|
62
|
+
CardFrame(회색 판 + 위 캡션) 안에 Card raised를 두고, useDiffReview로 만든 상태를 InlineDiff review와 툴바가 함께 써요. CountBadge soft = 남은 개수, Button iconOnly = 이전 · 다음(현재 변경을 스크롤해 보여 주고 파란 링으로 표시), 모두 거절 · 모두 수락은 확인 대신 바로 실행 + 실행 취소 토스트. 하나를 수락 · 거절하면 다음 남은 변경으로 넘어가요.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { Check, ChevronLeft, ChevronRight, FileText } from 'lucide-react'
|
|
66
|
+
import { Badge, Button, CountBadge, Inline, Stack, Text, Card, CardBody, CardFrame, InlineDiff, toast, useDiffReview, type DiffDecision, type DiffSegment } from '@iamfiro/design'
|
|
67
|
+
|
|
68
|
+
// A delete right before an insert (with no changeId) is grouped into one change automatically.
|
|
69
|
+
const changes: DiffSegment[] = [
|
|
70
|
+
{ type: 'equal', text: '3분기 매출은 전년 대비 ' },
|
|
71
|
+
{ type: 'delete', text: '18%' },
|
|
72
|
+
{ type: 'insert', text: '21%' },
|
|
73
|
+
{ type: 'equal', text: ' 늘었어요. 신규 계약은 ' },
|
|
74
|
+
{ type: 'delete', text: '2분기와 같은 수준에 머물렀고' },
|
|
75
|
+
{ type: 'insert', text: '2분기보다 소폭 줄었고' },
|
|
76
|
+
{ type: 'equal', text: ', 해지율은 ' },
|
|
77
|
+
{ type: 'delete', text: '2.4%' },
|
|
78
|
+
{ type: 'insert', text: '1.9%' },
|
|
79
|
+
{ type: 'equal', text: '로 낮아졌어요. ' },
|
|
80
|
+
{ type: 'insert', text: '환율 영향을 빼면 성장률은 19%예요. ' },
|
|
81
|
+
{ type: 'equal', text: '4분기에는 ' },
|
|
82
|
+
{ type: 'delete', text: '신제품 출시' },
|
|
83
|
+
{ type: 'insert', text: '기존 고객 확대' },
|
|
84
|
+
{ type: 'equal', text: '에 집중할 계획이에요.' },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
export default function Example() {
|
|
88
|
+
const review = useDiffReview({ changes })
|
|
89
|
+
const done = review.remaining === 0
|
|
90
|
+
|
|
91
|
+
// Reversible, so no confirm dialog: decide right away and offer 실행 취소.
|
|
92
|
+
const decideAll = (decision: DiffDecision) => {
|
|
93
|
+
const previous = review.decisions
|
|
94
|
+
const count = review.remaining
|
|
95
|
+
if (decision === 'accepted') review.acceptAll()
|
|
96
|
+
else review.rejectAll()
|
|
97
|
+
toast.undo(`${count}곳을 ${decision === 'accepted' ? '수락' : '거절'}했어요`, { onUndo: () => review.setDecisions(previous) })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<CardFrame
|
|
102
|
+
style={{ width: '100%', maxWidth: 460 }}
|
|
103
|
+
header={
|
|
104
|
+
<Inline gap={2} wrap={false}>
|
|
105
|
+
<FileText size={14} aria-hidden />
|
|
106
|
+
<Text variant="label-sm" tone="tertiary" truncate>
|
|
107
|
+
이사회 메모 – 3분기 · 매출
|
|
108
|
+
</Text>
|
|
109
|
+
</Inline>
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<Card variant="raised" padding="sm">
|
|
113
|
+
<CardBody>
|
|
114
|
+
<Stack gap={3}>
|
|
115
|
+
<Inline gap={2} justify="between">
|
|
116
|
+
<Inline gap={1} wrap={false}>
|
|
117
|
+
{done ? (
|
|
118
|
+
<Badge tone="success" size="sm" icon={<Check />}>
|
|
119
|
+
검토 완료
|
|
120
|
+
</Badge>
|
|
121
|
+
) : (
|
|
122
|
+
<span aria-live="polite">
|
|
123
|
+
<CountBadge count={review.remaining} variant="soft" tone="success" label={`남은 변경 ${review.remaining}곳`} />
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
<Button size="sm" variant="ghost" iconOnly icon={<ChevronLeft />} aria-label="이전 변경" onClick={review.prev} disabled={done} />
|
|
127
|
+
<Button size="sm" variant="ghost" iconOnly icon={<ChevronRight />} aria-label="다음 변경" onClick={review.next} disabled={done} />
|
|
128
|
+
</Inline>
|
|
129
|
+
<Inline gap={2} wrap={false}>
|
|
130
|
+
{done ? (
|
|
131
|
+
<Button size="sm" variant="ghost" onClick={review.reset}>
|
|
132
|
+
처음부터 다시
|
|
133
|
+
</Button>
|
|
134
|
+
) : (
|
|
135
|
+
<>
|
|
136
|
+
<Button size="sm" variant="secondary" onClick={() => decideAll('rejected')}>
|
|
137
|
+
모두 거절
|
|
138
|
+
</Button>
|
|
139
|
+
<Button size="sm" variant="primary" onClick={() => decideAll('accepted')}>
|
|
140
|
+
모두 수락
|
|
141
|
+
</Button>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
</Inline>
|
|
145
|
+
</Inline>
|
|
146
|
+
<InlineDiff review={review} variant="body-md" aria-label="이사회 메모 본문" />
|
|
147
|
+
</Stack>
|
|
148
|
+
</CardBody>
|
|
149
|
+
</Card>
|
|
150
|
+
</CardFrame>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 읽기 전용
|
|
156
|
+
|
|
157
|
+
reviewable 없이 changes만 주면 보여 주기만 해요. changeId로 삭제와 추가를 직접 짝지을 수 있어요(추가만 있는 변경도 가능).
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { Stack, Text, InlineDiff, type DiffSegment } from '@iamfiro/design'
|
|
161
|
+
|
|
162
|
+
// changeId pairs a delete with its insert explicitly.
|
|
163
|
+
const changes: DiffSegment[] = [
|
|
164
|
+
{ type: 'equal', text: '제3조(계약 기간) 이 계약의 기간은 체결일로부터 ' },
|
|
165
|
+
{ type: 'delete', text: '1년', changeId: 'term' },
|
|
166
|
+
{ type: 'insert', text: '2년', changeId: 'term' },
|
|
167
|
+
{ type: 'equal', text: '으로 하며, 어느 한쪽이 만료 ' },
|
|
168
|
+
{ type: 'delete', text: '30일', changeId: 'notice' },
|
|
169
|
+
{ type: 'insert', text: '60일', changeId: 'notice' },
|
|
170
|
+
{ type: 'equal', text: ' 전까지 서면으로 해지 의사를 알리지 않으면 같은 조건으로 ' },
|
|
171
|
+
{ type: 'insert', text: '1년씩 ', changeId: 'renew' },
|
|
172
|
+
{ type: 'equal', text: '자동 연장한다.' },
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
export default function Example() {
|
|
176
|
+
return (
|
|
177
|
+
<Stack gap={2} style={{ width: '100%', maxWidth: 440 }}>
|
|
178
|
+
<Text variant="label-sm" tone="secondary">
|
|
179
|
+
공급 계약서 · 이도현 님이 10월 25일에 고침
|
|
180
|
+
</Text>
|
|
181
|
+
<InlineDiff changes={changes} aria-label="제3조 변경 내용" />
|
|
182
|
+
</Stack>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### before / after 문자열
|
|
188
|
+
|
|
189
|
+
두 문자열을 주면 단어 단위로 비교해요(공백 · 한글 단어를 그대로 지키는 LCS). 결정은 decisions + onDecisionsChange로 제어하고, applyDiffDecisions로 결정이 반영된 글을 얻어요.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { useState } from 'react'
|
|
193
|
+
import { Box, Stack, DescriptionSection, InlineDiff, applyDiffDecisions, diffWords, type DiffDecisions } from '@iamfiro/design'
|
|
194
|
+
|
|
195
|
+
const before = '회의는 다음주 월요일 오전 10시에 진행 됩니다. 참석 여부를 금요일 까지 알려주세요.'
|
|
196
|
+
const after = '회의는 다음 주 월요일 오전 10시에 진행됩니다. 참석 여부는 금요일까지 알려 주세요.'
|
|
197
|
+
// The same word diff InlineDiff makes from before / after, for the result preview below.
|
|
198
|
+
const segments = diffWords(before, after)
|
|
199
|
+
|
|
200
|
+
export default function Example() {
|
|
201
|
+
const [decisions, setDecisions] = useState<DiffDecisions>({})
|
|
202
|
+
return (
|
|
203
|
+
<Stack gap={3} style={{ width: '100%', maxWidth: 440 }}>
|
|
204
|
+
<InlineDiff before={before} after={after} reviewable decisions={decisions} onDecisionsChange={setDecisions} aria-label="맞춤법 교정" />
|
|
205
|
+
<Box background="subtle" radius="md" padding={3}>
|
|
206
|
+
<DescriptionSection title="결과 (아직 고르지 않은 곳은 원문)">{applyDiffDecisions(segments, decisions, 'before')}</DescriptionSection>
|
|
207
|
+
</Box>
|
|
208
|
+
</Stack>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
```
|