@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,67 @@
|
|
|
1
|
+
# Flag
|
|
2
|
+
|
|
3
|
+
> 라벨 & 배지 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
국가를 나타내는 작은 3:2 국기. Windows에선 국기 이모지가 "KR" 글자로 보여서 SVG로 그려요. 모서리를 살짝 둥글리고 옅은 안쪽 윤곽을 둬 흰 국기(한국 · 일본)도 흰 바탕에서 보여요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Flag } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 숫자만 받는 칸(금액 · 전화번호 · 카드 번호)은 `useDigitInput`을 Input에 펼친다: 글자는 아예 안 들어가고, 쉼표 · 하이픈은 `format`으로 붙이고, 커서는 같은 숫자 뒤에 머물고, 구분자 위 Backspace는 앞 숫자를 지운다. `type="number"`는 쓰지 않는다. 전화번호는 `PhoneInput`(칸 안 `Select bare` 국가 선택 + 국기 `Flag`, 목록은 `anchorRef`로 칸 전체 폭). 국기는 이모지 대신 `Flag`(Windows에서 이모지 국기는 글자로 나온다).
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### Flag
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `code` (필수) | `'KR' \| 'US' \| 'JP' \| 'CN' \| 'VN' \| 'GB' \| 'DE' \| 'FR'` | | ISO 국가 코드. |
|
|
22
|
+
| `size` | `number` | `20` | 너비(px). |
|
|
23
|
+
| `label` | `string` | | 화면 읽기용 이름. 없으면 장식으로 숨겨요. |
|
|
24
|
+
|
|
25
|
+
## 예제
|
|
26
|
+
|
|
27
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
28
|
+
|
|
29
|
+
### 국가와 크기
|
|
30
|
+
|
|
31
|
+
size는 너비(px), 높이는 늘 2/3. 옆에 국가 이름이 없으면 label을 주세요.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { Flag, Inline, Stack, Text, type FlagCode } from '@iamfiro/design'
|
|
35
|
+
|
|
36
|
+
const COUNTRIES: [FlagCode, string][] = [
|
|
37
|
+
['KR', '대한민국'],
|
|
38
|
+
['US', '미국'],
|
|
39
|
+
['JP', '일본'],
|
|
40
|
+
['CN', '중국'],
|
|
41
|
+
['VN', '베트남'],
|
|
42
|
+
['GB', '영국'],
|
|
43
|
+
['DE', '독일'],
|
|
44
|
+
['FR', '프랑스'],
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
export default function Example() {
|
|
48
|
+
return (
|
|
49
|
+
<Stack gap={5}>
|
|
50
|
+
<Inline gap={5}>
|
|
51
|
+
{COUNTRIES.map(([code, name]) => (
|
|
52
|
+
<Inline key={code} gap={2}>
|
|
53
|
+
<Flag code={code} />
|
|
54
|
+
<Text variant="body-sm">{name}</Text>
|
|
55
|
+
</Inline>
|
|
56
|
+
))}
|
|
57
|
+
</Inline>
|
|
58
|
+
<Inline gap={4} align="end">
|
|
59
|
+
<Flag code="KR" size={16} label="대한민국" />
|
|
60
|
+
<Flag code="KR" size={24} label="대한민국" />
|
|
61
|
+
<Flag code="KR" size={36} label="대한민국" />
|
|
62
|
+
<Flag code="KR" size={54} label="대한민국" />
|
|
63
|
+
</Inline>
|
|
64
|
+
</Stack>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# FunnelChart
|
|
2
|
+
|
|
3
|
+
> 차트 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
단계별 전환을 보여주는 퍼널 차트. 단계마다 값에 비례한 막대와 값 · 전환율을 적고, 앞 단계에서 빠져나간 만큼은 옅은 빗금 영역으로 남겨요. 막대는 차례로 자라고 데이터가 바뀌면 새 길이로 미끄러지며, 마우스 · 터치 · ↑/↓ 키로 단계의 값 · 전환율 · 이탈을 Card 툴팁에 보여줘요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { FunnelChart, type FunnelStep } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 두 값의 관계는 `ScatterChart`(가장 가까운 점이 커지고 나머지는 흐려짐, 범례 클릭 = 분류 숨김 · 보임은 제자리에서 줄며 사라지기), 단계 전환은 `FunnelChart`(빠진 만큼은 앞 단계 색의 옅은 빗금, 한 색조 단계는 `gradient` = `--chart-seq-*`).
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### FunnelChart
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `data` (필수) | `FunnelStep[]` | | { key, label, value, color? } 단계 목록(순서대로). |
|
|
22
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | 가로 = 단계마다 한 줄(폭이 값), 세로 = 단계마다 한 열(높이가 값). |
|
|
23
|
+
| `calculateFrom` | `'first' \| 'previous'` | `'first'` | 단계 옆 전환율의 기준. 툴팁에는 두 기준이 모두 나와요. |
|
|
24
|
+
| `color` | `string` | `'--chart-seq-1'` | 막대 색. |
|
|
25
|
+
| `gradient` | `boolean` | `false` | 단계마다 --chart-seq-1부터 옅어지는 한 색조. |
|
|
26
|
+
| `showDropOff` | `boolean` | `true` | 앞 단계에서 빠진 만큼의 빗금 영역. |
|
|
27
|
+
| `height` | `number` | `260` | 세로 방향의 높이(px). 가로는 단계 수 × barSize. |
|
|
28
|
+
| `barSize` | `number` | `32` | 가로 방향 막대 두께(px). |
|
|
29
|
+
| `formatValue` | `(value, step) => string` | `'1,234'` | 값 형식. |
|
|
30
|
+
| `formatPercent` | `(ratio) => string` | `'31.4%'` | 전환율 · 이탈률 형식. |
|
|
31
|
+
| `legend` | `boolean` | `true` | 아래 범례(전환 · 이탈). |
|
|
32
|
+
| `activeIndex / defaultActiveIndex / onActiveIndexChange` | `number \| null` | | 하이라이트한 단계(제어/비제어). |
|
|
33
|
+
| `aria-label` | `string` | `'퍼널 차트'` | 차트 이름. |
|
|
34
|
+
|
|
35
|
+
### funnelStats
|
|
36
|
+
|
|
37
|
+
(data, calculateFrom) => FunnelStepStats[]. 단계마다 fromFirst · fromPrevious · conversion · drop · dropRate를 계산해요. 표나 요약 문구를 차트와 같은 숫자로 맞출 때 써요.
|
|
38
|
+
|
|
39
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| `data` (필수) | `FunnelStep[]` | | 단계 목록. |
|
|
42
|
+
| `calculateFrom` (필수) | `'first' \| 'previous'` | | conversion에 담을 기준. |
|
|
43
|
+
|
|
44
|
+
## 예제
|
|
45
|
+
|
|
46
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
47
|
+
|
|
48
|
+
### 가입 퍼널
|
|
49
|
+
|
|
50
|
+
방문 → 가입 → 활성화 → 결제. calculateFrom을 바꾸면 오른쪽 전환율이 첫 단계 대비와 이전 단계 대비 사이에서 바뀌어요.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useState } from 'react'
|
|
54
|
+
import { FunnelChart, type FunnelCalculateFrom, type FunnelStep, SegmentedControl, Stack } from '@iamfiro/design'
|
|
55
|
+
|
|
56
|
+
const STEPS: FunnelStep[] = [
|
|
57
|
+
{ key: 'visit', label: '방문', value: 12480 },
|
|
58
|
+
{ key: 'signup', label: '가입', value: 3920 },
|
|
59
|
+
{ key: 'activate', label: '활성화', value: 2140 },
|
|
60
|
+
{ key: 'pay', label: '결제', value: 610 },
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
export default function Example() {
|
|
64
|
+
const [from, setFrom] = useState<FunnelCalculateFrom>('first')
|
|
65
|
+
return (
|
|
66
|
+
<Stack gap={5} style={{ width: '100%', maxWidth: 560 }}>
|
|
67
|
+
<SegmentedControl
|
|
68
|
+
ariaLabel="전환율 기준"
|
|
69
|
+
value={from}
|
|
70
|
+
onChange={setFrom}
|
|
71
|
+
options={[
|
|
72
|
+
{ value: 'first', label: '첫 단계 대비' },
|
|
73
|
+
{ value: 'previous', label: '이전 단계 대비' },
|
|
74
|
+
]}
|
|
75
|
+
/>
|
|
76
|
+
<FunnelChart data={STEPS} calculateFrom={from} formatValue={(v) => `${v.toLocaleString('ko-KR')}명`} aria-label="가입 퍼널" />
|
|
77
|
+
</Stack>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 세로 · 그라데이션
|
|
83
|
+
|
|
84
|
+
orientation="vertical"은 단계를 열로 세우고, gradient는 --chart-seq-* 한 색조로 단계마다 옅어져요. 기간을 바꾸면 막대가 새 높이로 움직여요.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { useState } from 'react'
|
|
88
|
+
import { FunnelChart, type FunnelStep, SegmentedControl, Stack } from '@iamfiro/design'
|
|
89
|
+
|
|
90
|
+
type Month = 'sep' | 'oct'
|
|
91
|
+
|
|
92
|
+
const DATA: Record<Month, FunnelStep[]> = {
|
|
93
|
+
sep: [
|
|
94
|
+
{ key: 'visit', label: '방문', value: 10200 },
|
|
95
|
+
{ key: 'signup', label: '가입', value: 2860 },
|
|
96
|
+
{ key: 'activate', label: '활성화', value: 1320 },
|
|
97
|
+
{ key: 'pay', label: '결제', value: 340 },
|
|
98
|
+
],
|
|
99
|
+
oct: [
|
|
100
|
+
{ key: 'visit', label: '방문', value: 12480 },
|
|
101
|
+
{ key: 'signup', label: '가입', value: 3920 },
|
|
102
|
+
{ key: 'activate', label: '활성화', value: 2140 },
|
|
103
|
+
{ key: 'pay', label: '결제', value: 610 },
|
|
104
|
+
],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default function Example() {
|
|
108
|
+
const [month, setMonth] = useState<Month>('oct')
|
|
109
|
+
return (
|
|
110
|
+
<Stack gap={5} style={{ width: '100%', maxWidth: 560 }}>
|
|
111
|
+
<SegmentedControl
|
|
112
|
+
ariaLabel="기간"
|
|
113
|
+
value={month}
|
|
114
|
+
onChange={setMonth}
|
|
115
|
+
options={[
|
|
116
|
+
{ value: 'sep', label: '9월' },
|
|
117
|
+
{ value: 'oct', label: '10월' },
|
|
118
|
+
]}
|
|
119
|
+
/>
|
|
120
|
+
<FunnelChart
|
|
121
|
+
data={DATA[month]}
|
|
122
|
+
orientation="vertical"
|
|
123
|
+
gradient
|
|
124
|
+
calculateFrom="previous"
|
|
125
|
+
formatValue={(v) => v.toLocaleString('ko-KR')}
|
|
126
|
+
aria-label={`${month === 'sep' ? '9월' : '10월'} 가입 퍼널`}
|
|
127
|
+
/>
|
|
128
|
+
</Stack>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Highlight
|
|
2
|
+
|
|
3
|
+
> 텍스트 & 코드 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
글 속 일부를 연한 상태색 배경으로 표시해요(warning · accent · danger). note를 주면 그 글자에 붙은 메모 말풍선(Popover)이 생겨요: 상태색 아이콘 + 오버라인 제목, 본문, 출처 칩. 메모가 있는 표시는 Tab으로 포커스할 수 있고, aria-describedby가 항상 있는 메모 사본을 가리켜 말풍선이 닫혀 있어도 설명이 읽혀요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Highlight } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 민감한 값은 `MaskedText`(양 끝만, 점 한 묶음이라 길이를 드러내지 않음, 화면 읽기는 "가려진 값"), 글 속 강조 + 메모 말풍선은 `Highlight note`, 첨부 파일 링크는 `FileChip`(확장자는 줄이지 않음).
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### Highlight
|
|
18
|
+
|
|
19
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `children` (필수) | `ReactNode` | | 표시할 글자(또는 MaskedText). |
|
|
22
|
+
| `tone` | `'warning' \| 'accent' \| 'danger'` | `'warning'` | 배경 · 글자 · 메모 제목 색. |
|
|
23
|
+
| `note` | `ReactNode` | | 메모 본문. 없으면 단순 표시. |
|
|
24
|
+
| `noteTitle` | `ReactNode` | | 메모의 오버라인 제목("확인 필요"). |
|
|
25
|
+
| `noteIcon` | `ReactNode` | | 제목 앞 아이콘. 기본은 tone별(경고 삼각형 · 오류 원 · 정보). |
|
|
26
|
+
| `noteFooter` | `ReactNode` | | 본문 아래 작은 줄. 보통 출처 Badge. |
|
|
27
|
+
| `openOn` | `'hover' \| 'always'` | `'hover'` | hover: 호버 · 포커스 · 누르면 열림 / always: 항상 열림. |
|
|
28
|
+
| `open / onOpenChange` | `boolean / (open) => void` | | 열림 상태를 밖에서 제어할 때(hover). |
|
|
29
|
+
| `defaultOpen` | `boolean` | `false` | 처음 열림 상태(비제어). |
|
|
30
|
+
| `noteWidth` | `number` | `280` | 메모 폭(px). |
|
|
31
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | 표시 기준 메모의 가로 정렬. |
|
|
32
|
+
|
|
33
|
+
## 예제
|
|
34
|
+
|
|
35
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
36
|
+
|
|
37
|
+
### 송금 검토 (항상 열림)
|
|
38
|
+
|
|
39
|
+
openOn="always": 검토 화면처럼 메모가 처음부터 값 아래에 붙어 있어요. 값 자리에는 MaskedText를 그대로 넣었어요.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Badge, MaskedText, Stack, Text, DescriptionItem, DescriptionList, FileChip, Highlight, toast } from '@iamfiro/design'
|
|
43
|
+
|
|
44
|
+
export default function Example() {
|
|
45
|
+
return (
|
|
46
|
+
<Stack gap={4} align="start" style={{ width: '100%', maxWidth: 420 }}>
|
|
47
|
+
<Stack gap={1}>
|
|
48
|
+
<Text variant="heading-sm">한빛물류 INV-2207 송금</Text>
|
|
49
|
+
<Text variant="body-sm" tone="secondary">
|
|
50
|
+
금액과 품목은 발주서 PO-118과 같아요. 계좌 정보가 지난 여섯 번의 송금과 달라요.
|
|
51
|
+
</Text>
|
|
52
|
+
</Stack>
|
|
53
|
+
<DescriptionList labelStyle="muted" size="sm" columns="repeat(3, max-content)" gap={8} aria-label="송금 정보">
|
|
54
|
+
<DescriptionItem label="금액">₩12,480,000</DescriptionItem>
|
|
55
|
+
<DescriptionItem label="지급일">10월 24일</DescriptionItem>
|
|
56
|
+
<DescriptionItem label="IBAN">
|
|
57
|
+
<Highlight
|
|
58
|
+
openOn="always"
|
|
59
|
+
noteTitle="확인 필요"
|
|
60
|
+
note="이틀 전 메일로 바뀐 새 계좌예요. 송금 전에 전화로 확인하세요."
|
|
61
|
+
noteFooter={<Badge size="sm">billing@hanbit-logis.co.kr 메일</Badge>}
|
|
62
|
+
>
|
|
63
|
+
<MaskedText label="IBAN" value="DE89 3704 0044 0532 0130 00" />
|
|
64
|
+
</Highlight>
|
|
65
|
+
</DescriptionItem>
|
|
66
|
+
</DescriptionList>
|
|
67
|
+
<FileChip name="INV-2207.pdf" size={248_000} onClick={() => toast('INV-2207.pdf 미리보기를 열었어요')} />
|
|
68
|
+
</Stack>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 호버 · 포커스로 열기
|
|
74
|
+
|
|
75
|
+
기본 openOn="hover": 포인터가 잠깐(200ms) 머물면 열리고 300ms 유예 뒤 닫혀요. Tab 포커스로 바로 열리고 Esc로 닫혀요. 터치에서는 누르면 열리고 바깥을 누르면 닫혀요.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { Badge, Text, Highlight } from '@iamfiro/design'
|
|
79
|
+
|
|
80
|
+
export default function Example() {
|
|
81
|
+
return (
|
|
82
|
+
<Text variant="body-md" style={{ maxWidth: 460 }}>
|
|
83
|
+
이번 배포는{' '}
|
|
84
|
+
<Highlight tone="accent" noteTitle="참고" note="결제 페이지의 쿠폰 처리만 바뀌었어요. 나머지 화면은 그대로예요." noteFooter={<Badge size="sm">이도현 · 2분 전</Badge>}>
|
|
85
|
+
쿠폰 처리 변경
|
|
86
|
+
</Highlight>
|
|
87
|
+
을 포함하고,{' '}
|
|
88
|
+
<Highlight tone="danger" noteTitle="실패" note="결제 테스트 412개 중 1개가 시간 초과로 실패했어요. 다시 실행하면 통과했어요.">
|
|
89
|
+
테스트 1건 실패
|
|
90
|
+
</Highlight>
|
|
91
|
+
가 있었어요.
|
|
92
|
+
</Text>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 색 · 메모 없는 표시
|
|
98
|
+
|
|
99
|
+
note가 없으면 포커스되지 않는 단순한 <mark>예요. 열린 동안은 배경이 한 단계 진해져요.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { Stack, Text, Highlight } from '@iamfiro/design'
|
|
103
|
+
|
|
104
|
+
export default function Example() {
|
|
105
|
+
return (
|
|
106
|
+
<Stack gap={2}>
|
|
107
|
+
<Text variant="body-md">
|
|
108
|
+
지급일이 <Highlight>10월 24일</Highlight>로 하루 앞당겨졌어요.
|
|
109
|
+
</Text>
|
|
110
|
+
<Text variant="body-md">
|
|
111
|
+
새 담당자는 <Highlight tone="accent">김민지</Highlight> 님이에요.
|
|
112
|
+
</Text>
|
|
113
|
+
<Text variant="body-md">
|
|
114
|
+
미납 금액 <Highlight tone="danger">₩1,134,545</Highlight>이 남아 있어요.
|
|
115
|
+
</Text>
|
|
116
|
+
</Stack>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
```
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# HoverCard
|
|
2
|
+
|
|
3
|
+
> 메뉴 & 오버레이 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
이름·링크·저장소 위에 잠깐 머물면 뜨는 풍부한 미리보기. Popover openOn="hover" 위에 만들어서 위치 잡기·뒤집기·퇴장 애니메이션을 그대로 써요. 포인터가 200ms 머물러야 열리고, 벗어나도 300ms 유예가 있어 카드로 옮겨 가는 동안 닫히지 않아요. 키보드 포커스로는 바로 열리고, 터치에서는 첫 탭이 카드를 열어요. 꼭 필요한 정보나 유일한 동작은 여기에만 두지 마세요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { HoverCard } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### HoverCard
|
|
14
|
+
|
|
15
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `trigger` (필수) | `ReactNode` | | 머무는 대상(Link, Button, 이름). |
|
|
18
|
+
| `media` | `ReactNode` | | 맨 위 미디어(AspectRatio 이미지 등). |
|
|
19
|
+
| `leading` | `ReactNode` | | 제목 앞 Avatar · IconTile · 로고. |
|
|
20
|
+
| `title` | `ReactNode` | | 제목(한 줄). 글자면 카드의 접근성 이름이 돼요. |
|
|
21
|
+
| `subtitle` | `ReactNode` | | 제목 아래 옅은 한 줄(역할, 도메인). |
|
|
22
|
+
| `description` | `ReactNode` | | 옅은 설명. 세 줄까지 보여요. |
|
|
23
|
+
| `meta` | `Array<{ icon?, label }> \| ReactNode` | | 작은 사실들의 줄(위치, 별 수). 배열이면 아이콘 + 글자 칩으로 그려요. |
|
|
24
|
+
| `children` | `ReactNode` | | 구조 아래 자유 내용. |
|
|
25
|
+
| `actions` | `ReactNode` | | 맨 아래 버튼 줄. |
|
|
26
|
+
| `loading` | `boolean` | `false` | 자리 표시 모양(아바타 원 + 줄). |
|
|
27
|
+
| `open / onOpenChange` | `boolean / (open) => void` | | 열림 상태를 밖에서 제어할 때. |
|
|
28
|
+
| `defaultOpen` | `boolean` | `false` | 처음 열림 상태(비제어). |
|
|
29
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | 트리거 기준 가로 정렬. |
|
|
30
|
+
| `width` | `number \| string` | `320` | 카드 폭. |
|
|
31
|
+
| `touch` | `'tap' \| 'none'` | `'tap'` | tap: 터치의 첫 탭은 트리거를 실행하지 않고 카드를 열어요(두 번째 탭은 그대로). none: 터치에선 평소처럼 동작하고 카드는 마우스·키보드 전용. |
|
|
32
|
+
| `aria-label` | `string` | | 카드 이름. 생략하면 글자 title. |
|
|
33
|
+
| `className` | `string` | | 카드 패널에 붙는 클래스. |
|
|
34
|
+
|
|
35
|
+
## 예제
|
|
36
|
+
|
|
37
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
38
|
+
|
|
39
|
+
### 사람
|
|
40
|
+
|
|
41
|
+
leading(Avatar) · title · subtitle · description · meta · actions로 구조를 채우면 간격과 정렬이 맞춰져요. meta 아이콘은 14px로 넘겨요.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useState } from 'react'
|
|
45
|
+
import { Clock, MapPin, UserPlus } from 'lucide-react'
|
|
46
|
+
import { Avatar, Button, Link, Text, HoverCard } from '@iamfiro/design'
|
|
47
|
+
|
|
48
|
+
export default function Example() {
|
|
49
|
+
const [following, setFollowing] = useState(false)
|
|
50
|
+
return (
|
|
51
|
+
<Text as="p" variant="body-md" style={{ maxWidth: 420 }}>
|
|
52
|
+
버튼 여백 수정은{' '}
|
|
53
|
+
<HoverCard
|
|
54
|
+
trigger={
|
|
55
|
+
<Link variant="inherit" href="#" onClick={(e) => e.preventDefault()}>
|
|
56
|
+
@김민지
|
|
57
|
+
</Link>
|
|
58
|
+
}
|
|
59
|
+
leading={<Avatar name="김민지" size={40} online tooltip={false} />}
|
|
60
|
+
title="김민지"
|
|
61
|
+
subtitle="디자인 시스템 팀 · 리드"
|
|
62
|
+
description="Lumen 컴포넌트와 토큰을 관리해요. 오후 6시 이후엔 답이 늦을 수 있어요."
|
|
63
|
+
meta={[
|
|
64
|
+
{ icon: <MapPin size={14} />, label: '서울' },
|
|
65
|
+
{ icon: <Clock size={14} />, label: '현지 시간 오후 3:42' },
|
|
66
|
+
]}
|
|
67
|
+
actions={
|
|
68
|
+
<Button size="sm" variant={following ? 'secondary' : 'primary'} icon={following ? undefined : <UserPlus />} onClick={() => setFollowing(!following)}>
|
|
69
|
+
{following ? '팔로우 중' : '팔로우'}
|
|
70
|
+
</Button>
|
|
71
|
+
}
|
|
72
|
+
/>{' '}
|
|
73
|
+
님이 검토하고 있어요.
|
|
74
|
+
</Text>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 링크 미리보기
|
|
80
|
+
|
|
81
|
+
media는 카드 맨 위에 들어가요(AspectRatio). 터치에서는 첫 탭에 미리보기가 열리고 두 번째 탭이 링크를 따라가요.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { BookOpen } from 'lucide-react'
|
|
85
|
+
import { AspectRatio, IconTile, Inline, Link, Text, HoverCard } from '@iamfiro/design'
|
|
86
|
+
|
|
87
|
+
export default function Example() {
|
|
88
|
+
return (
|
|
89
|
+
<Text as="p" variant="body-md" style={{ maxWidth: 420 }}>
|
|
90
|
+
자세한 규칙은{' '}
|
|
91
|
+
<HoverCard
|
|
92
|
+
width={300}
|
|
93
|
+
trigger={
|
|
94
|
+
<Link href="#" onClick={(e) => e.preventDefault()}>
|
|
95
|
+
컴포넌트 개발 가이드
|
|
96
|
+
</Link>
|
|
97
|
+
}
|
|
98
|
+
media={
|
|
99
|
+
<AspectRatio ratio="1.91" radius="md" background="subtle">
|
|
100
|
+
<Inline justify="center" style={{ height: '100%' }}>
|
|
101
|
+
<IconTile size={48} tone="surface">
|
|
102
|
+
<BookOpen />
|
|
103
|
+
</IconTile>
|
|
104
|
+
</Inline>
|
|
105
|
+
</AspectRatio>
|
|
106
|
+
}
|
|
107
|
+
title="컴포넌트 개발 가이드"
|
|
108
|
+
subtitle="wiki.lumen.app"
|
|
109
|
+
description="받은 디자인 피드백을 규칙으로 정리한 문서예요. 바탕·색·테두리, 형태, 애니메이션, 인터랙션 규칙과 체크리스트가 있어요."
|
|
110
|
+
meta={[{ label: '10월 25일 수정' }, { label: '6분 읽기' }]}
|
|
111
|
+
/>
|
|
112
|
+
를 보세요. 터치 기기에서는 첫 번째 탭에 미리보기가 열리고, 한 번 더 누르면 링크로 이동해요.
|
|
113
|
+
</Text>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 저장소
|
|
119
|
+
|
|
120
|
+
meta 줄에 별·포크·이슈 수를, children에 언어·상태 배지를 넣었어요.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { CircleDot, GitFork, Star } from 'lucide-react'
|
|
124
|
+
import { Badge, BrandIcon, Button, IconTile, Inline, HoverCard } from '@iamfiro/design'
|
|
125
|
+
|
|
126
|
+
export default function Example() {
|
|
127
|
+
return (
|
|
128
|
+
<Inline gap={2}>
|
|
129
|
+
<HoverCard
|
|
130
|
+
trigger={
|
|
131
|
+
<Button variant="secondary" size="sm" icon={<BrandIcon name="github" />}>
|
|
132
|
+
lumen/design-system
|
|
133
|
+
</Button>
|
|
134
|
+
}
|
|
135
|
+
leading={
|
|
136
|
+
<IconTile size={40} tone="surface">
|
|
137
|
+
<BrandIcon name="github" />
|
|
138
|
+
</IconTile>
|
|
139
|
+
}
|
|
140
|
+
title="lumen/design-system"
|
|
141
|
+
subtitle="공개 저장소 · 2시간 전 업데이트"
|
|
142
|
+
description="Vite + React + TypeScript로 만든 Lumen 디자인 시스템. 토큰, 기본 요소, 조합 컴포넌트와 문서 사이트가 들어 있어요."
|
|
143
|
+
meta={[
|
|
144
|
+
{ icon: <Star size={14} />, label: '1.2천' },
|
|
145
|
+
{ icon: <GitFork size={14} />, label: '84' },
|
|
146
|
+
{ icon: <CircleDot size={14} />, label: '이슈 12' },
|
|
147
|
+
]}
|
|
148
|
+
>
|
|
149
|
+
<Inline gap={1}>
|
|
150
|
+
<Badge size="sm">TypeScript</Badge>
|
|
151
|
+
<Badge size="sm">CSS</Badge>
|
|
152
|
+
<Badge size="sm" tone="success" dot>
|
|
153
|
+
빌드 통과
|
|
154
|
+
</Badge>
|
|
155
|
+
</Inline>
|
|
156
|
+
</HoverCard>
|
|
157
|
+
</Inline>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 불러오는 중
|
|
163
|
+
|
|
164
|
+
열릴 때 데이터를 가져오면 loading으로 자리 표시 모양을 보여 줘요. open · onOpenChange로 열린 순간을 알 수 있어요.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { useEffect, useState } from 'react'
|
|
168
|
+
import { Avatar, Badge, Button, Inline, HoverCard } from '@iamfiro/design'
|
|
169
|
+
|
|
170
|
+
interface Profile {
|
|
171
|
+
name: string
|
|
172
|
+
role: string
|
|
173
|
+
bio: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// A pretend API: the profile arrives a moment after the card opens.
|
|
177
|
+
const fetchProfile = (name: string) =>
|
|
178
|
+
new Promise<Profile>((resolve) =>
|
|
179
|
+
setTimeout(() => resolve({ name, role: '프론트엔드 개발 · 플랫폼 팀', bio: '배포 파이프라인과 사내 도구를 만들어요. 이번 주는 재택 근무 중이에요.' }), 900),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
export default function Example() {
|
|
183
|
+
const [open, setOpen] = useState(false)
|
|
184
|
+
const [profile, setProfile] = useState<Profile | null>(null)
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (!open || profile) return
|
|
188
|
+
let alive = true
|
|
189
|
+
fetchProfile('이도현').then((p) => alive && setProfile(p))
|
|
190
|
+
return () => {
|
|
191
|
+
alive = false
|
|
192
|
+
}
|
|
193
|
+
}, [open, profile])
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Inline gap={3}>
|
|
197
|
+
<HoverCard
|
|
198
|
+
open={open}
|
|
199
|
+
onOpenChange={setOpen}
|
|
200
|
+
loading={!profile}
|
|
201
|
+
aria-label="이도현 프로필"
|
|
202
|
+
trigger={
|
|
203
|
+
<Button variant="ghost" size="sm" icon={<Avatar name="이도현" size={20} tooltip={false} />}>
|
|
204
|
+
이도현
|
|
205
|
+
</Button>
|
|
206
|
+
}
|
|
207
|
+
leading={<Avatar name="이도현" size={40} tooltip={false} />}
|
|
208
|
+
title={profile?.name}
|
|
209
|
+
subtitle={profile?.role}
|
|
210
|
+
description={profile?.bio}
|
|
211
|
+
>
|
|
212
|
+
<Inline gap={1}>
|
|
213
|
+
<Badge size="sm" tone="info">
|
|
214
|
+
재택
|
|
215
|
+
</Badge>
|
|
216
|
+
</Inline>
|
|
217
|
+
</HoverCard>
|
|
218
|
+
<Button size="sm" variant="ghost" onClick={() => setProfile(null)}>
|
|
219
|
+
다시 불러오기
|
|
220
|
+
</Button>
|
|
221
|
+
</Inline>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 자유 내용
|
|
227
|
+
|
|
228
|
+
구조 슬롯과 함께 children에 진행률·아바타 묶음처럼 아무 내용이나 넣을 수 있어요. align으로 트리거 기준 정렬을 바꿔요.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { Users } from 'lucide-react'
|
|
232
|
+
import { AvatarGroup, Button, IconTile, Progress, Stack, Text, HoverCard } from '@iamfiro/design'
|
|
233
|
+
|
|
234
|
+
export default function Example() {
|
|
235
|
+
return (
|
|
236
|
+
<HoverCard
|
|
237
|
+
align="center"
|
|
238
|
+
trigger={
|
|
239
|
+
<Button variant="secondary" icon={<Users />}>
|
|
240
|
+
디자인 시스템 팀
|
|
241
|
+
</Button>
|
|
242
|
+
}
|
|
243
|
+
leading={
|
|
244
|
+
<IconTile size={40} tone="accent">
|
|
245
|
+
<Users />
|
|
246
|
+
</IconTile>
|
|
247
|
+
}
|
|
248
|
+
title="디자인 시스템 팀"
|
|
249
|
+
subtitle="멤버 6명 · #디자인-시스템"
|
|
250
|
+
actions={
|
|
251
|
+
<>
|
|
252
|
+
<Button size="sm" variant="primary">
|
|
253
|
+
팀 페이지
|
|
254
|
+
</Button>
|
|
255
|
+
<Button size="sm" variant="ghost">
|
|
256
|
+
메시지
|
|
257
|
+
</Button>
|
|
258
|
+
</>
|
|
259
|
+
}
|
|
260
|
+
>
|
|
261
|
+
<Stack gap={2}>
|
|
262
|
+
<Text variant="label-sm" tone="secondary">
|
|
263
|
+
스프린트 14 · 남은 기간 3일
|
|
264
|
+
</Text>
|
|
265
|
+
<Progress value={68} aria-label="스프린트 진행률" />
|
|
266
|
+
<AvatarGroup names={['김민지', '이도현', '박서준', '최유나', '정하늘', '한지우']} size={24} max={4} />
|
|
267
|
+
</Stack>
|
|
268
|
+
</HoverCard>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
```
|