@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,166 @@
|
|
|
1
|
+
# Skeleton
|
|
2
|
+
|
|
3
|
+
> 진행 표시 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
내용이 들어올 자리를 미리 보여 주는 회색 자리 표시. 실제 레이아웃과 같은 크기로 그려 두면 내용이 들어올 때 화면이 덜컹거리지 않아요. SkeletonGroup으로 감싸면 로딩이 끝날 때 실제 내용으로 부드럽게 바뀌어요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Skeleton, SkeletonText, SkeletonCircle, SkeletonGroup } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### Skeleton
|
|
14
|
+
|
|
15
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `width` | `number \| string` | `'100%'` | 너비. 숫자는 px. |
|
|
18
|
+
| `height` | `number \| string` | `16` | 높이. 숫자는 px. |
|
|
19
|
+
| `radius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'full' \| number` | `'sm'` | 모서리(0 / 6 / 10 / 16px / 원형). |
|
|
20
|
+
| `animation` | `'shimmer' \| 'pulse' \| 'none'` | `'shimmer'` | 움직임. shimmer는 빛이 지나가고, pulse는 깜빡여요. |
|
|
21
|
+
| `className / style` | `string / CSSProperties` | | 블록에 적용돼요. |
|
|
22
|
+
|
|
23
|
+
### SkeletonText
|
|
24
|
+
|
|
25
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `lines` | `number` | `3` | 줄 수. |
|
|
28
|
+
| `lastLineWidth` | `string` | `'62%'` | 마지막 줄 너비(2줄 이상일 때). 문단처럼 보이게 해요. |
|
|
29
|
+
| `variant` | `TextVariant` | `'body-md'` | 맞출 글자 역할. 줄 높이가 실제 Text와 같아져요. |
|
|
30
|
+
| `animation` | `'shimmer' \| 'pulse' \| 'none'` | `'shimmer'` | 움직임. shimmer는 빛이 지나가고, pulse는 깜빡여요. |
|
|
31
|
+
| `className` | `string` | | 루트에 붙일 클래스. |
|
|
32
|
+
|
|
33
|
+
### SkeletonCircle
|
|
34
|
+
|
|
35
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| `size` | `number` | `36` | 지름(px). |
|
|
38
|
+
| `animation` | `'shimmer' \| 'pulse' \| 'none'` | `'shimmer'` | 움직임. shimmer는 빛이 지나가고, pulse는 깜빡여요. |
|
|
39
|
+
|
|
40
|
+
### SkeletonGroup
|
|
41
|
+
|
|
42
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `loading` (필수) | `boolean` | | true면 fallback, false면 children을 보여 줘요. |
|
|
45
|
+
| `fallback` (필수) | `ReactNode` | | 로딩 중에 보여 줄 스켈레톤. |
|
|
46
|
+
| `children` (필수) | `ReactNode` | | 실제 내용. |
|
|
47
|
+
| `label` | `string` | `'불러오는 중'` | 로딩 중 스크린 리더가 읽는 이름. |
|
|
48
|
+
| `className` | `string` | | 루트에 붙일 클래스. |
|
|
49
|
+
|
|
50
|
+
## 예제
|
|
51
|
+
|
|
52
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
53
|
+
|
|
54
|
+
### 기본 모양
|
|
55
|
+
|
|
56
|
+
Skeleton은 크기와 모서리를 정하는 블록, SkeletonCircle은 아바타 자리, SkeletonText는 글자 역할(variant)의 줄 높이에 맞춘 문단이에요.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Skeleton, SkeletonCircle, SkeletonText } from '@iamfiro/design'
|
|
60
|
+
|
|
61
|
+
export default function Example() {
|
|
62
|
+
return (
|
|
63
|
+
<div style={{ display: 'grid', gap: 20, width: 360 }}>
|
|
64
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
65
|
+
<SkeletonCircle size={40} />
|
|
66
|
+
<div style={{ display: 'grid', gap: 8, flex: 1 }}>
|
|
67
|
+
<Skeleton width="40%" height={14} />
|
|
68
|
+
<Skeleton width="65%" height={12} />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<Skeleton height={180} radius="lg" />
|
|
72
|
+
<div style={{ display: 'grid', gap: 6 }}>
|
|
73
|
+
<div style={{ width: '70%' }}>
|
|
74
|
+
<SkeletonText variant="heading-sm" lines={1} />
|
|
75
|
+
</div>
|
|
76
|
+
<SkeletonText lines={3} lastLineWidth="45%" />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 애니메이션
|
|
84
|
+
|
|
85
|
+
여러 줄이면 줄마다 조금씩 늦게 시작해서 물결처럼 보여요.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { Text, SkeletonCircle, SkeletonText, type SkeletonAnimation } from '@iamfiro/design'
|
|
89
|
+
|
|
90
|
+
const ANIMATIONS: Array<[SkeletonAnimation, string]> = [
|
|
91
|
+
['shimmer', '빛이 지나가요 (기본)'],
|
|
92
|
+
['pulse', '천천히 깜빡여요'],
|
|
93
|
+
['none', '움직이지 않아요'],
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
export default function Example() {
|
|
97
|
+
return (
|
|
98
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 28, width: '100%', maxWidth: 640 }}>
|
|
99
|
+
{ANIMATIONS.map(([animation, label]) => (
|
|
100
|
+
<div key={animation} style={{ display: 'grid', gap: 12 }}>
|
|
101
|
+
<SkeletonCircle size={32} animation={animation} />
|
|
102
|
+
<SkeletonText lines={3} animation={animation} />
|
|
103
|
+
<Text variant="caption" tone="tertiary">
|
|
104
|
+
{animation} · {label}
|
|
105
|
+
</Text>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 실제 내용으로 바꾸기
|
|
114
|
+
|
|
115
|
+
SkeletonGroup은 loading 동안 fallback을 보여 주고, 끝나면 children을 서서히 나타내요. 로딩 중에는 aria-busy가 붙어요.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { useState } from 'react'
|
|
119
|
+
import { FolderKanban } from 'lucide-react'
|
|
120
|
+
import { AvatarGroup, Switch, Card, CardBody, CardFooter, CardHeader, Skeleton, SkeletonGroup, SkeletonText } from '@iamfiro/design'
|
|
121
|
+
|
|
122
|
+
function ProjectCardSkeleton() {
|
|
123
|
+
return (
|
|
124
|
+
<Card style={{ width: 360 }}>
|
|
125
|
+
<CardHeader
|
|
126
|
+
icon={<Skeleton width={40} height={40} radius="md" />}
|
|
127
|
+
title={
|
|
128
|
+
<div style={{ width: '55%' }}>
|
|
129
|
+
<SkeletonText variant="heading-sm" lines={1} />
|
|
130
|
+
</div>
|
|
131
|
+
}
|
|
132
|
+
description={
|
|
133
|
+
<div style={{ width: '80%' }}>
|
|
134
|
+
<SkeletonText variant="body-sm" lines={1} />
|
|
135
|
+
</div>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
<CardBody>
|
|
139
|
+
<SkeletonText lines={2} />
|
|
140
|
+
</CardBody>
|
|
141
|
+
<CardFooter align="start">
|
|
142
|
+
<Skeleton width={84} height={24} radius="full" />
|
|
143
|
+
</CardFooter>
|
|
144
|
+
</Card>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default function Example() {
|
|
149
|
+
const [loading, setLoading] = useState(true)
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div style={{ display: 'grid', justifyItems: 'center', gap: 20 }}>
|
|
153
|
+
<Switch label="불러오는 중" checked={loading} onChange={(e) => setLoading(e.target.checked)} />
|
|
154
|
+
<SkeletonGroup loading={loading} fallback={<ProjectCardSkeleton />}>
|
|
155
|
+
<Card style={{ width: 360 }}>
|
|
156
|
+
<CardHeader icon={<FolderKanban />} title="웹사이트 리뉴얼" description="김민지 외 3명 · 2분 전 수정" />
|
|
157
|
+
<CardBody>새 랜딩 페이지 시안을 검토하고 있어요. 금요일까지 피드백을 남겨 주세요.</CardBody>
|
|
158
|
+
<CardFooter align="start">
|
|
159
|
+
<AvatarGroup names={['김민지', '이도현', '박서연', '최준호']} />
|
|
160
|
+
</CardFooter>
|
|
161
|
+
</Card>
|
|
162
|
+
</SkeletonGroup>
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
```
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Slider
|
|
2
|
+
|
|
3
|
+
> 선택 컨트롤 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
끌어서 값을 고르는 슬라이더. 숫자 하나를 주면 단일 값, [시작, 끝]을 주면 범위가 돼요. 끄는 동안은 미리보기(onValueChange), 손을 놓을 때 확정(onValueCommit)하고, 끄는 중에 Esc를 누르면 원래 값으로 돌아가요.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Slider } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### Slider
|
|
14
|
+
|
|
15
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `value` | `number \| [number, number]` | | 값(제어). 튜플이면 범위 슬라이더. |
|
|
18
|
+
| `defaultValue` | `number \| [number, number]` | `min` | 처음 값(비제어). 범위는 반드시 튜플로 주세요. |
|
|
19
|
+
| `onValueChange` | `(value) => void` | | 끄는 동안·키 입력마다 호출(미리보기). |
|
|
20
|
+
| `onValueCommit` | `(value) => void` | | 손을 놓을 때 / 키를 누를 때 한 번 호출(확정). Esc로 취소하면 호출되지 않아요. |
|
|
21
|
+
| `min` | `number` | `0` | 최솟값. |
|
|
22
|
+
| `max` | `number` | `100` | 최댓값. |
|
|
23
|
+
| `step` | `number` | `1` | 단위. 소수(0.1 등)도 돼요. |
|
|
24
|
+
| `minDistance` | `number` | `0` | 범위 전용. 두 손잡이 사이 최소 간격. |
|
|
25
|
+
| `marks` | `Array<number \| { value: number; label?: ReactNode }>` | | 트랙 아래 눈금. label이 있으면 글자도 표시. |
|
|
26
|
+
| `formatValue` | `(value: number) => string` | `String` | 말풍선과 스크린 리더(aria-valuetext)에 쓰는 표시 형식. |
|
|
27
|
+
| `showValue` | `'auto' \| 'always' \| 'never'` | `'auto'` | 값 말풍선: 조작할 때만 / 항상 / 안 보임. |
|
|
28
|
+
| `size` | `'sm' \| 'md'` | `'md'` | 손잡이 18 / 14px. |
|
|
29
|
+
| `disabled` | `boolean` | | 비활성. |
|
|
30
|
+
| `name` | `string` | | 폼 제출용 hidden input 이름. 범위는 name[0], name[1]. |
|
|
31
|
+
| `aria-label` | `string` | | 손잡이 이름. |
|
|
32
|
+
| `getThumbLabel` | `(index: number) => string` | | 범위에서 손잡이별 이름. 예: (i) => (i === 0 ? '최소 가격' : '최대 가격'). |
|
|
33
|
+
|
|
34
|
+
## 예제
|
|
35
|
+
|
|
36
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
37
|
+
|
|
38
|
+
### 기본
|
|
39
|
+
|
|
40
|
+
트랙 어디를 눌러도 그 위치로 옮겨가요. 끄는 동안 위에 값 말풍선이 뜨고, formatValue로 단위를 붙여요. ←→ 한 칸, PageUp/PageDown 크게, Home/End 끝으로.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useState } from 'react'
|
|
44
|
+
import { Volume1, Volume2 } from 'lucide-react'
|
|
45
|
+
import { Slider } from '@iamfiro/design'
|
|
46
|
+
|
|
47
|
+
export default function Example() {
|
|
48
|
+
const [volume, setVolume] = useState(60)
|
|
49
|
+
return (
|
|
50
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, width: 340 }}>
|
|
51
|
+
<Volume1 size={18} color="var(--text-3)" />
|
|
52
|
+
<div style={{ flex: 1 }}>
|
|
53
|
+
<Slider value={volume} onValueChange={setVolume} formatValue={(v) => `${v}%`} aria-label="볼륨" />
|
|
54
|
+
</div>
|
|
55
|
+
<Volume2 size={18} color="var(--text-3)" />
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 범위
|
|
62
|
+
|
|
63
|
+
눌린 위치에서 가까운 손잡이가 움직여요. minDistance로 두 손잡이 사이 최소 간격을, getThumbLabel로 손잡이별 이름을 정해요.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { useState } from 'react'
|
|
67
|
+
import { Text, Slider } from '@iamfiro/design'
|
|
68
|
+
|
|
69
|
+
const won = (v: number) => (v >= 10000 ? `${(v / 10000).toLocaleString('ko-KR')}만원` : `${v.toLocaleString('ko-KR')}원`)
|
|
70
|
+
|
|
71
|
+
export default function Example() {
|
|
72
|
+
const [price, setPrice] = useState<[number, number]>([100000, 350000])
|
|
73
|
+
return (
|
|
74
|
+
<div style={{ display: 'grid', gap: 12, width: 360 }}>
|
|
75
|
+
<Text variant="label-md" numeric>
|
|
76
|
+
가격 {won(price[0])} – {won(price[1])}
|
|
77
|
+
</Text>
|
|
78
|
+
<Slider
|
|
79
|
+
value={price}
|
|
80
|
+
onValueChange={setPrice}
|
|
81
|
+
min={0}
|
|
82
|
+
max={500000}
|
|
83
|
+
step={10000}
|
|
84
|
+
minDistance={50000}
|
|
85
|
+
formatValue={won}
|
|
86
|
+
getThumbLabel={(i) => (i === 0 ? '최소 가격' : '최대 가격')}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 눈금
|
|
94
|
+
|
|
95
|
+
marks에 label을 넣으면 트랙 아래에 글자가 찍혀요. 숫자만 넣으면 눈금만 그려요. showValue로 말풍선을 항상 / 끄기 할 수 있어요.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useState } from 'react'
|
|
99
|
+
import { Slider } from '@iamfiro/design'
|
|
100
|
+
|
|
101
|
+
const QUALITY = ['낮음', '보통', '높음', '최고']
|
|
102
|
+
|
|
103
|
+
export default function Example() {
|
|
104
|
+
const [quality, setQuality] = useState(2)
|
|
105
|
+
return (
|
|
106
|
+
<div style={{ display: 'grid', gap: 32, width: 360 }}>
|
|
107
|
+
<Slider
|
|
108
|
+
value={quality}
|
|
109
|
+
onValueChange={setQuality}
|
|
110
|
+
min={0}
|
|
111
|
+
max={3}
|
|
112
|
+
marks={QUALITY.map((label, value) => ({ value, label }))}
|
|
113
|
+
formatValue={(v) => QUALITY[v]}
|
|
114
|
+
showValue="never"
|
|
115
|
+
aria-label="화질"
|
|
116
|
+
/>
|
|
117
|
+
<Slider defaultValue={40} step={20} marks={[0, 20, 40, 60, 80, 100]} formatValue={(v) => `${v}%`} showValue="always" aria-label="투명도" />
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 미리보기와 확정
|
|
124
|
+
|
|
125
|
+
저장이나 서버 요청은 onValueCommit에서 하세요. 끄는 중 Esc를 누르면 미리보기가 취소되고 아무것도 확정되지 않아요.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useState } from 'react'
|
|
129
|
+
import { Text, Slider } from '@iamfiro/design'
|
|
130
|
+
|
|
131
|
+
export default function Example() {
|
|
132
|
+
const [preview, setPreview] = useState(120)
|
|
133
|
+
const [saved, setSaved] = useState(120)
|
|
134
|
+
return (
|
|
135
|
+
<div style={{ display: 'grid', gap: 12, width: 360 }}>
|
|
136
|
+
<Slider
|
|
137
|
+
value={preview}
|
|
138
|
+
onValueChange={setPreview}
|
|
139
|
+
onValueCommit={setSaved}
|
|
140
|
+
min={30}
|
|
141
|
+
max={300}
|
|
142
|
+
step={10}
|
|
143
|
+
formatValue={(v) => `${v}분`}
|
|
144
|
+
aria-label="자동 잠금 시간"
|
|
145
|
+
/>
|
|
146
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
147
|
+
<Text variant="body-sm" tone="secondary" numeric>
|
|
148
|
+
미리보기 {preview}분
|
|
149
|
+
</Text>
|
|
150
|
+
<Text variant="body-sm" weight="semibold" numeric>
|
|
151
|
+
저장됨 {saved}분
|
|
152
|
+
</Text>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 크기와 비활성
|
|
160
|
+
|
|
161
|
+
size sm / md, disabled.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { Field, Slider } from '@iamfiro/design'
|
|
165
|
+
|
|
166
|
+
export default function Example() {
|
|
167
|
+
return (
|
|
168
|
+
<div style={{ display: 'grid', gap: 20, width: 340 }}>
|
|
169
|
+
<Field label="작게 (sm)">
|
|
170
|
+
<Slider size="sm" defaultValue={40} aria-label="밝기" />
|
|
171
|
+
</Field>
|
|
172
|
+
<Field label="작게 (sm) · 범위">
|
|
173
|
+
<Slider size="sm" defaultValue={[20, 70]} getThumbLabel={(i) => (i === 0 ? '최소 온도' : '최대 온도')} />
|
|
174
|
+
</Field>
|
|
175
|
+
<Field label="비활성">
|
|
176
|
+
<Slider defaultValue={30} disabled aria-label="비활성 슬라이더" />
|
|
177
|
+
</Field>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# SparkChart
|
|
2
|
+
|
|
3
|
+
> 차트 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
|
|
4
|
+
|
|
5
|
+
축 없이 흐름만 보여 주는 아주 작은 차트. 표의 칸이나 KPI 카드에 넣어요. SparkLine(선) · SparkArea(선 + 옅은 그라데이션) · Sparkbar(막대)가 같은 props를 쓰고, SparkChart type으로 하나를 골라요. 24–40px 높이에서도 또렷하게 실제 픽셀 크기로 그리며, 마우스·터치·←/→로 십자선 점과 값 카드를 띄워요. tone="trend"는 처음 값 대비 마지막 값의 방향으로 색을 정해요(Delta처럼 higherIs로 좋고 나쁨).
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SparkArea, SparkChart, SparkLine } from '@iamfiro/design'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 디자인 규칙
|
|
12
|
+
|
|
13
|
+
- 표 칸 · KPI 카드의 작은 추이는 `SparkLine` · `SparkArea` · `Sparkbar`(같은 props, 하나로 고르면 `SparkChart type`). 축 없음, 실제 픽셀 크기로 그려 24–40px에서도 또렷하게, 값 범위는 0이 아니라 최솟값–최댓값. 색은 `tone="trend"` + `higherIs`(Delta와 같은 규칙), 마지막 값은 `endDot`. 긴 표에선 `tooltip={false}`로 이벤트 없는 정적 그림(`role="img"`). KPI 카드는 Card 안 `Stat` + `Delta` + `SparkArea`.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
### SparkLine · SparkArea
|
|
18
|
+
|
|
19
|
+
SparkArea는 SparkLine props + fill.
|
|
20
|
+
|
|
21
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| `data` (필수) | `(number \| null)[]` | | 한 칸에 값 하나(오래된 것부터). null이면 선이 끊겨요. |
|
|
24
|
+
| `height` | `number` | `32` | 높이(px). 24–40을 권장해요. |
|
|
25
|
+
| `width` | `number \| string` | `'100%'` | 너비. 표 칸에선 숫자로 고정해요. |
|
|
26
|
+
| `curve` | `'linear' \| 'smooth'` | `'smooth'` | 직선 또는 튀어 오르지 않는 단조 곡선. |
|
|
27
|
+
| `tone` | `'accent' \| 'neutral' \| 'trend'` | `'accent'` | trend는 방향(마지막 값 − 처음 값)에 따라 초록 / 주황. |
|
|
28
|
+
| `higherIs` | `'better' \| 'worse' \| 'neutral'` | `'better'` | trend에서 오르는 게 좋은지. 시간 · 비용 · 오류는 worse. |
|
|
29
|
+
| `worseTone` | `'warning' \| 'danger'` | `'warning'` | 나빠졌을 때 색. |
|
|
30
|
+
| `color` | `string` | | 직접 정한 색(CSS 변수 '--chart-2' 또는 색). tone보다 우선. |
|
|
31
|
+
| `min / max` | `number` | | 값 범위. 기본은 데이터의 최솟값 · 최댓값(모양을 보여 주는 차트라 0부터가 아니에요). |
|
|
32
|
+
| `endDot` | `boolean` | | 마지막 값에 점. |
|
|
33
|
+
| `fill` | `'gradient' \| 'solid'` | `'gradient'` | SparkArea 채움. |
|
|
34
|
+
| `tooltip` | `boolean` | `true` | 십자선 점 + 값 카드. false면 이벤트 · 포커스 없는 정적 그림. |
|
|
35
|
+
| `formatLabel` | `(index) => string` | `'3일'` | 툴팁 제목. |
|
|
36
|
+
| `valueLabel` | `string` | `'값'` | 툴팁 행 이름. |
|
|
37
|
+
| `formatValue` | `(value) => string` | `'1,234'` | 툴팁 값 형식. |
|
|
38
|
+
| `activeIndex / defaultActiveIndex / onActiveIndexChange` | `number \| null` | | 고른 칸(제어/비제어). |
|
|
39
|
+
| `aria-label` | `string` | `'추이'` | 이름. 고른 값은 aria-live로 읽어요. |
|
|
40
|
+
|
|
41
|
+
### SparkChart
|
|
42
|
+
|
|
43
|
+
위 props 전부 + type. bar는 Sparkbar로 그려요(Sparkbar도 tone · higherIs · worseTone을 받아요).
|
|
44
|
+
|
|
45
|
+
| prop | 타입 | 기본값 | 설명 |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `type` | `'line' \| 'area' \| 'bar'` | `'line'` | 그릴 모양. |
|
|
48
|
+
|
|
49
|
+
## 예제
|
|
50
|
+
|
|
51
|
+
예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
|
|
52
|
+
|
|
53
|
+
### KPI 카드
|
|
54
|
+
|
|
55
|
+
Card 안에 Stat + Delta + SparkArea. 응답 시간처럼 낮을수록 좋은 지표는 higherIs="worse"로 내려가는 선이 초록이 돼요. endDot은 마지막 값에 점을 찍어요.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Delta, Grid, Stack, Card, CardBody, SparkArea, Stat } from '@iamfiro/design'
|
|
59
|
+
|
|
60
|
+
const DAYS = ['10월 12일', '10월 13일', '10월 14일', '10월 15일', '10월 16일', '10월 17일', '10월 18일', '10월 19일', '10월 20일', '10월 21일', '10월 22일', '10월 23일', '10월 24일', '10월 25일']
|
|
61
|
+
|
|
62
|
+
const KPIS = [
|
|
63
|
+
{
|
|
64
|
+
label: '매출',
|
|
65
|
+
value: '₩4,820만',
|
|
66
|
+
delta: 12.4,
|
|
67
|
+
higherIs: 'better' as const,
|
|
68
|
+
unit: '만 원',
|
|
69
|
+
data: [312, 298, 330, 341, 325, 352, 368, 344, 371, 389, 402, 396, 418, 432],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: '활성 사용자',
|
|
73
|
+
value: '18,240',
|
|
74
|
+
delta: -3.1,
|
|
75
|
+
higherIs: 'better' as const,
|
|
76
|
+
unit: '명',
|
|
77
|
+
data: [19400, 19120, 19310, 18980, 18760, 18900, 18640, 18520, 18710, 18450, 18390, 18300, 18260, 18240],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: '평균 응답 시간',
|
|
81
|
+
value: '182ms',
|
|
82
|
+
delta: -8.6,
|
|
83
|
+
higherIs: 'worse' as const,
|
|
84
|
+
unit: 'ms',
|
|
85
|
+
data: [214, 220, 205, 211, 198, 201, 195, 190, 193, 188, 186, 191, 184, 182],
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
export default function Example() {
|
|
90
|
+
return (
|
|
91
|
+
<Grid minItemWidth={200} gap={3} style={{ width: '100%' }}>
|
|
92
|
+
{KPIS.map((k) => (
|
|
93
|
+
<Card key={k.label}>
|
|
94
|
+
<CardBody>
|
|
95
|
+
<Stack gap={3}>
|
|
96
|
+
<Stat label={k.label} value={k.value} delta={<Delta value={k.delta} format={(n) => `${n}%`} higherIs={k.higherIs} size="sm" />} />
|
|
97
|
+
<SparkArea
|
|
98
|
+
data={k.data}
|
|
99
|
+
height={40}
|
|
100
|
+
tone="trend"
|
|
101
|
+
higherIs={k.higherIs}
|
|
102
|
+
endDot
|
|
103
|
+
formatLabel={(i) => DAYS[i]}
|
|
104
|
+
valueLabel={k.label}
|
|
105
|
+
formatValue={(v) => `${v.toLocaleString('ko-KR')}${k.unit}`}
|
|
106
|
+
aria-label={`${k.label} 최근 14일`}
|
|
107
|
+
/>
|
|
108
|
+
</Stack>
|
|
109
|
+
</CardBody>
|
|
110
|
+
</Card>
|
|
111
|
+
))}
|
|
112
|
+
</Grid>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 표 안의 추이
|
|
118
|
+
|
|
119
|
+
Table 칸에 width를 정한 SparkLine. null은 선이 끊긴 칸이에요. 칸 위에서 마우스를 움직이면 그날 값이 떠요.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { Delta, Text, SparkLine, Table, type TableColumn } from '@iamfiro/design'
|
|
123
|
+
|
|
124
|
+
interface Page {
|
|
125
|
+
id: string
|
|
126
|
+
path: string
|
|
127
|
+
views: number
|
|
128
|
+
change: number
|
|
129
|
+
/** 최근 14일 조회수 */
|
|
130
|
+
trend: Array<number | null>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const PAGES: Page[] = [
|
|
134
|
+
{ id: 'p1', path: '/요금제', views: 12840, change: 18.2, trend: [620, 640, 610, 700, 760, 720, 810, 840, 860, 900, 950, 930, 1010, 1080] },
|
|
135
|
+
{ id: 'p2', path: '/시작하기', views: 9310, change: 4.1, trend: [640, 660, 650, 630, 670, 690, 660, 680, 700, 690, 710, 700, 720, 730] },
|
|
136
|
+
{ id: 'p3', path: '/블로그/출시-노트', views: 6120, change: -22.5, trend: [880, 820, 760, 640, 600, 520, 480, 430, 410, 380, 350, 330, 310, 290] },
|
|
137
|
+
{ id: 'p4', path: '/고객-사례', views: 3470, change: 0, trend: [240, 260, 230, 250, 270, 240, 250, 260, 240, 250, 255, 245, 250, 248] },
|
|
138
|
+
{ id: 'p5', path: '/문의', views: 1980, change: 9.7, trend: [110, 120, null, null, 130, 135, 140, 128, 150, 145, 155, 160, 158, 170] },
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
const COLUMNS: TableColumn<Page>[] = [
|
|
142
|
+
{ key: 'path', header: '페이지', render: (p) => <Text variant="label-md">{p.path}</Text> },
|
|
143
|
+
{ key: 'views', header: '조회수', numeric: true, render: (p) => p.views.toLocaleString('ko-KR') },
|
|
144
|
+
{
|
|
145
|
+
key: 'trend',
|
|
146
|
+
header: '최근 14일',
|
|
147
|
+
width: 120,
|
|
148
|
+
render: (p) => (
|
|
149
|
+
<SparkLine
|
|
150
|
+
data={p.trend}
|
|
151
|
+
width={112}
|
|
152
|
+
height={28}
|
|
153
|
+
tone="trend"
|
|
154
|
+
formatLabel={(i) => `10월 ${12 + i}일`}
|
|
155
|
+
valueLabel="조회수"
|
|
156
|
+
formatValue={(v) => `${v.toLocaleString('ko-KR')}회`}
|
|
157
|
+
aria-label={`${p.path} 최근 14일 조회수`}
|
|
158
|
+
/>
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
{ key: 'change', header: '변화', align: 'end', render: (p) => <Delta value={p.change} format={(n) => `${n}%`} size="sm" /> },
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
export default function Example() {
|
|
165
|
+
return <Table caption="많이 본 페이지" columns={COLUMNS} rows={PAGES} rowKey={(p) => p.id} style={{ width: '100%', maxWidth: 640 }} />
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 세 가지 type
|
|
170
|
+
|
|
171
|
+
SparkChart type="line" | "area" | "bar". 데이터를 바꾸면 선은 새 모양으로, 막대는 새 높이로 미끄러져요.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import { useState } from 'react'
|
|
175
|
+
import { Button, Grid, Inline, Stack, Text, SparkChart } from '@iamfiro/design'
|
|
176
|
+
|
|
177
|
+
const THIS_WEEK = [32, 41, 38, 52, 47, 61, 58, 66, 72, 69, 80, 84]
|
|
178
|
+
const LAST_WEEK = [28, 30, 35, 31, 40, 38, 44, 41, 39, 46, 43, 50]
|
|
179
|
+
|
|
180
|
+
const TYPES = [
|
|
181
|
+
{ type: 'line', label: '선 (line)' },
|
|
182
|
+
{ type: 'area', label: '영역 (area)' },
|
|
183
|
+
{ type: 'bar', label: '막대 (bar)' },
|
|
184
|
+
] as const
|
|
185
|
+
|
|
186
|
+
export default function Example() {
|
|
187
|
+
const [week, setWeek] = useState<'this' | 'last'>('this')
|
|
188
|
+
const data = week === 'this' ? THIS_WEEK : LAST_WEEK
|
|
189
|
+
return (
|
|
190
|
+
<Stack gap={5} style={{ width: '100%', maxWidth: 560 }}>
|
|
191
|
+
<Grid columns={3} gap={6}>
|
|
192
|
+
{TYPES.map((t) => (
|
|
193
|
+
<Stack key={t.type} gap={2}>
|
|
194
|
+
<Text variant="caption" tone="tertiary">
|
|
195
|
+
{t.label}
|
|
196
|
+
</Text>
|
|
197
|
+
<SparkChart
|
|
198
|
+
type={t.type}
|
|
199
|
+
data={data}
|
|
200
|
+
height={36}
|
|
201
|
+
tone="accent"
|
|
202
|
+
formatLabel={(i) => `${i * 2}시`}
|
|
203
|
+
valueLabel="주문"
|
|
204
|
+
formatValue={(v) => `${v}건`}
|
|
205
|
+
aria-label={`시간대별 주문 · ${t.label}`}
|
|
206
|
+
/>
|
|
207
|
+
</Stack>
|
|
208
|
+
))}
|
|
209
|
+
</Grid>
|
|
210
|
+
<Inline gap={2}>
|
|
211
|
+
<Button size="sm" variant={week === 'last' ? 'primary' : 'secondary'} onClick={() => setWeek('last')}>
|
|
212
|
+
지난주
|
|
213
|
+
</Button>
|
|
214
|
+
<Button size="sm" variant={week === 'this' ? 'primary' : 'secondary'} onClick={() => setWeek('this')}>
|
|
215
|
+
이번 주
|
|
216
|
+
</Button>
|
|
217
|
+
</Inline>
|
|
218
|
+
</Stack>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 추세 색 · 정적
|
|
224
|
+
|
|
225
|
+
tone="trend" + higherIs · worseTone. tooltip={false}면 이벤트 없이 그림만 그려서(role="img") 긴 표에서도 가벼워요. fill="solid"는 한 겹의 옅은 색.
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { Inline, Stack, Text, SparkArea, SparkLine } from '@iamfiro/design'
|
|
229
|
+
|
|
230
|
+
const UP = [12, 14, 13, 17, 16, 19, 22, 21, 25, 27]
|
|
231
|
+
const DOWN = [27, 25, 26, 22, 21, 19, 18, 15, 14, 12]
|
|
232
|
+
|
|
233
|
+
const ROWS = [
|
|
234
|
+
{ label: '가입 · 늘수록 좋음', data: UP, higherIs: 'better' as const },
|
|
235
|
+
{ label: '이탈 · 줄수록 좋음', data: DOWN, higherIs: 'worse' as const },
|
|
236
|
+
{ label: '오류 · 늘면 위험', data: UP, higherIs: 'worse' as const, worseTone: 'danger' as const },
|
|
237
|
+
{ label: '방문 · 줄었음', data: DOWN, higherIs: 'better' as const },
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
export default function Example() {
|
|
241
|
+
return (
|
|
242
|
+
<Stack gap={4} style={{ width: '100%', maxWidth: 420 }}>
|
|
243
|
+
{ROWS.map((r) => (
|
|
244
|
+
<Inline key={r.label} justify="between" gap={4} wrap={false}>
|
|
245
|
+
<Text variant="body-sm" tone="secondary">
|
|
246
|
+
{r.label}
|
|
247
|
+
</Text>
|
|
248
|
+
<Inline gap={4} wrap={false}>
|
|
249
|
+
<SparkLine data={r.data} width={96} height={24} tone="trend" higherIs={r.higherIs} worseTone={r.worseTone} endDot tooltip={false} aria-label={r.label} />
|
|
250
|
+
<SparkArea data={r.data} width={96} height={24} tone="trend" higherIs={r.higherIs} worseTone={r.worseTone} curve="linear" fill="solid" tooltip={false} aria-label={r.label} />
|
|
251
|
+
</Inline>
|
|
252
|
+
</Inline>
|
|
253
|
+
))}
|
|
254
|
+
</Stack>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
```
|