@conscience-technology/ctds 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/README.md +128 -2
- package/assets/components/AppFrame/AppFrame.module.css +177 -0
- package/assets/components/AppFrame/AppFrame.tsx +93 -0
- package/assets/components/AppFrame/index.ts +6 -0
- package/assets/components/AttachmentChip/AttachmentChip.module.css +71 -0
- package/assets/components/AttachmentChip/AttachmentChip.tsx +74 -0
- package/assets/components/AttachmentChip/index.ts +2 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.module.css +107 -0
- package/assets/components/AttachmentThumb/AttachmentThumb.tsx +91 -0
- package/assets/components/AttachmentThumb/index.ts +2 -0
- package/assets/components/Avatar/Avatar.module.css +167 -0
- package/assets/components/Avatar/Avatar.tsx +228 -0
- package/assets/components/Avatar/index.ts +2 -0
- package/assets/components/AvatarUpload/AvatarUpload.module.css +130 -0
- package/assets/components/AvatarUpload/AvatarUpload.tsx +139 -0
- package/assets/components/AvatarUpload/index.ts +2 -0
- package/assets/components/Badge/Badge.module.css +76 -0
- package/assets/components/Badge/Badge.tsx +68 -0
- package/assets/components/Badge/index.ts +2 -0
- package/assets/components/Breadcrumb/Breadcrumb.module.css +213 -0
- package/assets/components/Breadcrumb/Breadcrumb.tsx +279 -0
- package/assets/components/Breadcrumb/index.ts +8 -0
- package/assets/components/Button/Button.module.css +216 -0
- package/assets/components/Button/Button.tsx +149 -0
- package/assets/components/Button/index.ts +2 -0
- package/assets/components/Callout/Callout.module.css +146 -0
- package/assets/components/Callout/Callout.tsx +138 -0
- package/assets/components/Callout/index.ts +2 -0
- package/assets/components/ChatInput/ChatInput.module.css +241 -0
- package/assets/components/ChatInput/ChatInput.tsx +322 -0
- package/assets/components/ChatInput/ModelSelector.tsx +40 -0
- package/assets/components/ChatInput/SendButton.tsx +41 -0
- package/assets/components/ChatInput/index.ts +10 -0
- package/assets/components/Checkbox/Checkbox.module.css +66 -0
- package/assets/components/Checkbox/Checkbox.tsx +36 -0
- package/assets/components/Checkbox/index.ts +2 -0
- package/assets/components/Chip/Chip.module.css +128 -0
- package/assets/components/Chip/Chip.tsx +103 -0
- package/assets/components/Chip/index.ts +2 -0
- package/assets/components/ChoiceCard/ChoiceCard.module.css +142 -0
- package/assets/components/ChoiceCard/ChoiceCard.tsx +132 -0
- package/assets/components/ChoiceCard/index.ts +2 -0
- package/assets/components/ContextMenu/ContextMenu.module.css +102 -0
- package/assets/components/ContextMenu/ContextMenu.tsx +392 -0
- package/assets/components/ContextMenu/index.ts +9 -0
- package/assets/components/DatePicker/Calendar.tsx +234 -0
- package/assets/components/DatePicker/DESIGN_NOTES.md +106 -0
- package/assets/components/DatePicker/DESIGN_NOTES_TIME.md +125 -0
- package/assets/components/DatePicker/DatePicker.module.css +295 -0
- package/assets/components/DatePicker/DatePicker.tsx +387 -0
- package/assets/components/DatePicker/TimeSelector.tsx +69 -0
- package/assets/components/DatePicker/index.ts +8 -0
- package/assets/components/DatePicker/presets.ts +80 -0
- package/assets/components/DatePicker/utils.ts +148 -0
- package/assets/components/Disclosure/Disclosure.module.css +73 -0
- package/assets/components/Disclosure/Disclosure.tsx +129 -0
- package/assets/components/Disclosure/index.ts +6 -0
- package/assets/components/Drawer/Drawer.module.css +257 -0
- package/assets/components/Drawer/Drawer.tsx +126 -0
- package/assets/components/Drawer/index.ts +7 -0
- package/assets/components/EmptyState/EmptyState.module.css +102 -0
- package/assets/components/EmptyState/EmptyState.tsx +80 -0
- package/assets/components/EmptyState/index.ts +2 -0
- package/assets/components/FileIcon/FileIcon.module.css +66 -0
- package/assets/components/FileIcon/FileIcon.tsx +84 -0
- package/assets/components/FileIcon/fileExtMap.ts +112 -0
- package/assets/components/FileIcon/index.ts +9 -0
- package/assets/components/FilterButton/FilterButton.module.css +130 -0
- package/assets/components/FilterButton/FilterButton.tsx +94 -0
- package/assets/components/FilterButton/index.ts +2 -0
- package/assets/components/GalleryItem/GalleryItem.module.css +220 -0
- package/assets/components/GalleryItem/GalleryItem.tsx +163 -0
- package/assets/components/GalleryItem/index.ts +7 -0
- package/assets/components/Icon/Icon.tsx +616 -0
- package/assets/components/Icon/context.tsx +39 -0
- package/assets/components/Icon/index.ts +4 -0
- package/assets/components/IconButton/IconButton.module.css +109 -0
- package/assets/components/IconButton/IconButton.tsx +110 -0
- package/assets/components/IconButton/index.ts +2 -0
- package/assets/components/Input/Input.module.css +338 -0
- package/assets/components/Input/Input.tsx +371 -0
- package/assets/components/Input/index.ts +2 -0
- package/assets/components/ListItem/ListItem.module.css +318 -0
- package/assets/components/ListItem/ListItem.tsx +262 -0
- package/assets/components/ListItem/index.ts +9 -0
- package/assets/components/MenuItem/MenuItem.module.css +437 -0
- package/assets/components/MenuItem/MenuItem.tsx +529 -0
- package/assets/components/MenuItem/index.ts +8 -0
- package/assets/components/MetricCard/MetricCard.module.css +153 -0
- package/assets/components/MetricCard/MetricCard.tsx +140 -0
- package/assets/components/MetricCard/index.ts +2 -0
- package/assets/components/Modal/Modal.module.css +175 -0
- package/assets/components/Modal/Modal.tsx +98 -0
- package/assets/components/Modal/index.ts +2 -0
- package/assets/components/Onboarding/DocsIndex.tsx +82 -0
- package/assets/components/Onboarding/LayoutBuilder.tsx +160 -0
- package/assets/components/Onboarding/Onboarding.module.css +1394 -0
- package/assets/components/Onboarding/OnboardingAxisPanel.tsx +515 -0
- package/assets/components/Onboarding/OnboardingPage.tsx +1193 -0
- package/assets/components/Onboarding/PresetsGallery.tsx +65 -0
- package/assets/components/Onboarding/actions.ts +250 -0
- package/assets/components/Onboarding/index.ts +8 -0
- package/assets/components/Onboarding/palette.css +40 -0
- package/assets/components/Onboarding/wizardData.ts +265 -0
- package/assets/components/Pagination/Pagination.module.css +82 -0
- package/assets/components/Pagination/Pagination.tsx +248 -0
- package/assets/components/Pagination/index.ts +6 -0
- package/assets/components/ProgressBar/ProgressBar.module.css +64 -0
- package/assets/components/ProgressBar/ProgressBar.tsx +54 -0
- package/assets/components/ProgressBar/index.ts +2 -0
- package/assets/components/QuotationBar/QuotationBar.module.css +61 -0
- package/assets/components/QuotationBar/QuotationBar.tsx +48 -0
- package/assets/components/QuotationBar/index.ts +2 -0
- package/assets/components/Radio/Radio.module.css +63 -0
- package/assets/components/Radio/Radio.tsx +33 -0
- package/assets/components/Radio/index.ts +2 -0
- package/assets/components/Select/Select.module.css +151 -0
- package/assets/components/Select/Select.tsx +425 -0
- package/assets/components/Select/index.ts +10 -0
- package/assets/components/SelectableCard/SelectableCard.module.css +177 -0
- package/assets/components/SelectableCard/SelectableCard.tsx +83 -0
- package/assets/components/SelectableCard/index.ts +2 -0
- package/assets/components/Shortcut/Shortcut.module.css +56 -0
- package/assets/components/Shortcut/Shortcut.tsx +156 -0
- package/assets/components/Shortcut/index.ts +2 -0
- package/assets/components/Sidebar/Sidebar.module.css +242 -0
- package/assets/components/Sidebar/Sidebar.tsx +264 -0
- package/assets/components/Sidebar/index.ts +7 -0
- package/assets/components/SidebarSplit/SidebarSplit.module.css +126 -0
- package/assets/components/SidebarSplit/SidebarSplit.tsx +222 -0
- package/assets/components/SidebarSplit/index.ts +6 -0
- package/assets/components/Slider/Slider.module.css +120 -0
- package/assets/components/Slider/Slider.tsx +88 -0
- package/assets/components/Slider/index.ts +2 -0
- package/assets/components/SplitButton/SplitButton.module.css +190 -0
- package/assets/components/SplitButton/SplitButton.tsx +145 -0
- package/assets/components/SplitButton/index.ts +6 -0
- package/assets/components/Stepper/Stepper.module.css +182 -0
- package/assets/components/Stepper/Stepper.tsx +185 -0
- package/assets/components/Stepper/index.ts +2 -0
- package/assets/components/Switch/Switch.module.css +116 -0
- package/assets/components/Switch/Switch.tsx +58 -0
- package/assets/components/Switch/index.ts +2 -0
- package/assets/components/TableCell/TableCell.module.css +137 -0
- package/assets/components/TableCell/TableCellAvatar.tsx +42 -0
- package/assets/components/TableCell/TableCellBadge.tsx +23 -0
- package/assets/components/TableCell/TableCellIcon.tsx +39 -0
- package/assets/components/TableCell/TableCellImage.tsx +49 -0
- package/assets/components/TableCell/TableCellLink.tsx +33 -0
- package/assets/components/TableCell/TableCellMeter.tsx +55 -0
- package/assets/components/TableCell/TableCellNumber.tsx +43 -0
- package/assets/components/TableCell/TableCellText.tsx +30 -0
- package/assets/components/TableCell/index.ts +23 -0
- package/assets/components/TableHeader/TableHeader.module.css +287 -0
- package/assets/components/TableHeader/TableHeader.tsx +590 -0
- package/assets/components/TableHeader/index.ts +9 -0
- package/assets/components/TableRow/TableRow.module.css +290 -0
- package/assets/components/TableRow/TableRow.tsx +253 -0
- package/assets/components/TableRow/index.ts +8 -0
- package/assets/components/TableRowEdit/TableRowEdit.module.css +126 -0
- package/assets/components/TableRowEdit/TableRowEdit.tsx +247 -0
- package/assets/components/TableRowEdit/index.ts +2 -0
- package/assets/components/TableRowSelect/TableRowSelect.module.css +23 -0
- package/assets/components/TableRowSelect/TableRowSelect.tsx +175 -0
- package/assets/components/TableRowSelect/index.ts +2 -0
- package/assets/components/Tabs/Tabs.module.css +234 -0
- package/assets/components/Tabs/Tabs.tsx +426 -0
- package/assets/components/Tabs/index.ts +9 -0
- package/assets/components/Textarea/Textarea.module.css +318 -0
- package/assets/components/Textarea/Textarea.tsx +426 -0
- package/assets/components/Textarea/index.ts +7 -0
- package/assets/components/ThemeController.tsx +271 -0
- package/assets/components/TimePicker/TimePicker.module.css +317 -0
- package/assets/components/TimePicker/TimePicker.tsx +821 -0
- package/assets/components/TimePicker/index.ts +8 -0
- package/assets/components/TimePicker/utils.ts +115 -0
- package/assets/components/Toast/Toast.module.css +264 -0
- package/assets/components/Toast/Toast.tsx +243 -0
- package/assets/components/Toast/index.ts +15 -0
- package/assets/components/Toolbar/Toolbar.module.css +72 -0
- package/assets/components/Toolbar/Toolbar.tsx +75 -0
- package/assets/components/Toolbar/index.ts +2 -0
- package/assets/components/Tooltip/Tooltip.module.css +42 -0
- package/assets/components/Tooltip/Tooltip.tsx +332 -0
- package/assets/components/Tooltip/index.ts +2 -0
- package/assets/components/Topbar/Topbar.module.css +26 -0
- package/assets/components/Topbar/Topbar.tsx +47 -0
- package/assets/components/Topbar/index.ts +2 -0
- package/assets/components/_cn.ts +3 -0
- package/assets/components/_color.ts +48 -0
- package/assets/components/_data.ts +659 -0
- package/assets/components/_utils.css +19 -0
- package/assets/components/axisOptions.ts +24 -0
- package/assets/components/tokens.css +742 -0
- package/assets/prompts/GUIDE.md +251 -0
- package/assets/prompts/OVERRIDE.md +170 -0
- package/assets/prompts/PRINCIPLES.md +130 -0
- package/assets/prompts/axes/density.md +38 -0
- package/assets/prompts/axes/font.md +36 -0
- package/assets/prompts/axes/hue.md +36 -0
- package/assets/prompts/axes/icon.md +48 -0
- package/assets/prompts/axes/mode.md +50 -0
- package/assets/prompts/axes/motion.md +77 -0
- package/assets/prompts/axes/palette.md +35 -0
- package/assets/prompts/axes/shape.md +33 -0
- package/assets/prompts/axes/voice.md +54 -0
- package/assets/prompts/components/README.md +84 -0
- package/assets/prompts/components/app-frame.md +94 -0
- package/assets/prompts/components/attachment-chip.md +46 -0
- package/assets/prompts/components/attachment-thumb.md +49 -0
- package/assets/prompts/components/avatar.md +86 -0
- package/assets/prompts/components/badge.md +104 -0
- package/assets/prompts/components/breadcrumb.md +77 -0
- package/assets/prompts/components/button.md +99 -0
- package/assets/prompts/components/callout.md +81 -0
- package/assets/prompts/components/chat-input.md +84 -0
- package/assets/prompts/components/checkbox.md +46 -0
- package/assets/prompts/components/chip.md +84 -0
- package/assets/prompts/components/choice-card.md +127 -0
- package/assets/prompts/components/context-menu.md +145 -0
- package/assets/prompts/components/date-picker.md +104 -0
- package/assets/prompts/components/drawer.md +152 -0
- package/assets/prompts/components/empty-state.md +69 -0
- package/assets/prompts/components/file-icon.md +73 -0
- package/assets/prompts/components/filter-button.md +42 -0
- package/assets/prompts/components/form-field.md +74 -0
- package/assets/prompts/components/gallery-item.md +158 -0
- package/assets/prompts/components/icon-button.md +82 -0
- package/assets/prompts/components/icon.md +68 -0
- package/assets/prompts/components/input.md +95 -0
- package/assets/prompts/components/list-item.md +235 -0
- package/assets/prompts/components/menu-item.md +234 -0
- package/assets/prompts/components/metric-card.md +132 -0
- package/assets/prompts/components/modal.md +131 -0
- package/assets/prompts/components/pagination.md +92 -0
- package/assets/prompts/components/progress-bar.md +61 -0
- package/assets/prompts/components/quotation-bar.md +39 -0
- package/assets/prompts/components/radio.md +41 -0
- package/assets/prompts/components/select.md +104 -0
- package/assets/prompts/components/selectable-card.md +67 -0
- package/assets/prompts/components/shortcut.md +76 -0
- package/assets/prompts/components/sidebar.md +107 -0
- package/assets/prompts/components/slider.md +89 -0
- package/assets/prompts/components/split-button.md +71 -0
- package/assets/prompts/components/stepper.md +93 -0
- package/assets/prompts/components/switch.md +86 -0
- package/assets/prompts/components/table-cell.md +87 -0
- package/assets/prompts/components/table-header.md +157 -0
- package/assets/prompts/components/table-row-edit.md +129 -0
- package/assets/prompts/components/table-row-select.md +82 -0
- package/assets/prompts/components/table-row.md +156 -0
- package/assets/prompts/components/tabs.md +122 -0
- package/assets/prompts/components/textarea.md +105 -0
- package/assets/prompts/components/toast.md +149 -0
- package/assets/prompts/components/tooltip.md +71 -0
- package/assets/prompts/components/topbar.md +148 -0
- package/assets/prompts/primitives/emerge.md +43 -0
- package/assets/prompts/primitives/fade.md +31 -0
- package/assets/prompts/primitives/scale.md +32 -0
- package/assets/prompts/primitives/scroll.md +75 -0
- package/assets/prompts/primitives/slide.md +31 -0
- package/assets/prompts/themes/_SCHEMA.md +135 -0
- package/assets/prompts/themes/_TEMPLATE.md +73 -0
- package/assets/prompts/themes/expressive.md +118 -0
- package/assets/prompts/themes/pure.md +87 -0
- package/assets/prompts/themes/spare.md +73 -0
- package/assets/prompts/tokens/breakpoint.md +29 -0
- package/assets/prompts/tokens/color.md +78 -0
- package/assets/prompts/tokens/radius.md +33 -0
- package/assets/prompts/tokens/shadow.md +35 -0
- package/assets/prompts/tokens/spacing.md +86 -0
- package/assets/prompts/tokens/typography.md +76 -0
- package/assets/prompts/tokens/z-index.md +35 -0
- package/assets/public/expressive.png +0 -0
- package/assets/public/pure.png +0 -0
- package/assets/public/spare.png +0 -0
- package/assets/skill/SKILL.md +36 -0
- package/bin/ctds.js +76 -0
- package/package.json +35 -4
- package/src/commands/init.js +215 -0
- package/src/commands/update.js +275 -0
- package/src/utils/assets.js +78 -0
- package/src/utils/deps.js +51 -0
- package/src/utils/detect.js +47 -0
- package/src/utils/diff.js +52 -0
- package/src/utils/hash.js +16 -0
- package/src/utils/log.js +41 -0
- package/src/utils/meta.js +73 -0
- package/src/utils/prompt.js +28 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
forwardRef,
|
|
5
|
+
type AnchorHTMLAttributes,
|
|
6
|
+
type HTMLAttributes,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
type Ref,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { Icon, type IconName } from "../Icon";
|
|
12
|
+
|
|
13
|
+
import styles from "./MetricCard.module.css";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* MetricCard — 대시보드 상단 KPI/Stat 카드.
|
|
17
|
+
* label + value + 선택적 아이콘 + trend 매핑된 inline delta + 선택적 보조 설명.
|
|
18
|
+
*
|
|
19
|
+
* trend prop을 주면 delta는 trend-up/trend-down/minus 아이콘 + 숫자 inline으로 자동 생성.
|
|
20
|
+
* delta를 직접 ReactNode로 주면 수동 모드, trend 매핑은 무시된다.
|
|
21
|
+
*/
|
|
22
|
+
export type MetricCardVariant = "line" | "soft" | "bare";
|
|
23
|
+
export type MetricCardTrend = "up" | "down" | "flat";
|
|
24
|
+
|
|
25
|
+
type Common = {
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
value: ReactNode;
|
|
28
|
+
icon?: IconName;
|
|
29
|
+
trend?: MetricCardTrend;
|
|
30
|
+
deltaLabel?: ReactNode;
|
|
31
|
+
delta?: ReactNode;
|
|
32
|
+
description?: ReactNode;
|
|
33
|
+
variant?: MetricCardVariant;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type DivProps = Common &
|
|
37
|
+
Omit<HTMLAttributes<HTMLDivElement>, keyof Common> & {
|
|
38
|
+
href?: undefined;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type AnchorProps = Common &
|
|
42
|
+
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof Common> & {
|
|
43
|
+
href: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type MetricCardProps = DivProps | AnchorProps;
|
|
47
|
+
|
|
48
|
+
const variantClass: Record<MetricCardVariant, string> = {
|
|
49
|
+
line: styles.line,
|
|
50
|
+
soft: styles.soft,
|
|
51
|
+
bare: styles.bare,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const trendIcon: Record<MetricCardTrend, IconName> = {
|
|
55
|
+
up: "trend-up",
|
|
56
|
+
down: "trend-down",
|
|
57
|
+
flat: "minus",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const MetricCard = forwardRef<HTMLElement, MetricCardProps>((props, ref) => {
|
|
61
|
+
const {
|
|
62
|
+
className,
|
|
63
|
+
label,
|
|
64
|
+
value,
|
|
65
|
+
icon,
|
|
66
|
+
trend,
|
|
67
|
+
deltaLabel,
|
|
68
|
+
delta,
|
|
69
|
+
description,
|
|
70
|
+
variant = "line",
|
|
71
|
+
...rest
|
|
72
|
+
} = props as Common & { className?: string } & Record<string, unknown>;
|
|
73
|
+
|
|
74
|
+
const isLink = typeof (props as AnchorProps).href === "string";
|
|
75
|
+
|
|
76
|
+
const rootClass = [
|
|
77
|
+
styles.root,
|
|
78
|
+
variantClass[variant],
|
|
79
|
+
isLink ? styles.interactive : "",
|
|
80
|
+
className ?? "",
|
|
81
|
+
]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join(" ");
|
|
84
|
+
|
|
85
|
+
const resolvedDelta =
|
|
86
|
+
delta ??
|
|
87
|
+
(trend && deltaLabel !== undefined ? (
|
|
88
|
+
<span className={styles.delta} data-trend={trend}>
|
|
89
|
+
<span className={styles.deltaIcon} aria-hidden>
|
|
90
|
+
<Icon name={trendIcon[trend]} />
|
|
91
|
+
</span>
|
|
92
|
+
<span>{deltaLabel}</span>
|
|
93
|
+
</span>
|
|
94
|
+
) : null);
|
|
95
|
+
|
|
96
|
+
const content = (
|
|
97
|
+
<>
|
|
98
|
+
<div className={styles.head}>
|
|
99
|
+
{icon && (
|
|
100
|
+
<span className={styles.icon} aria-hidden>
|
|
101
|
+
<Icon name={icon} />
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
<span className={styles.label}>{label}</span>
|
|
105
|
+
</div>
|
|
106
|
+
<div className={styles.valueRow}>
|
|
107
|
+
<span className={styles.value}>{value}</span>
|
|
108
|
+
{resolvedDelta}
|
|
109
|
+
</div>
|
|
110
|
+
{description && <div className={styles.description}>{description}</div>}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (isLink) {
|
|
115
|
+
return (
|
|
116
|
+
<a
|
|
117
|
+
ref={ref as Ref<HTMLAnchorElement>}
|
|
118
|
+
className={rootClass}
|
|
119
|
+
{...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}
|
|
120
|
+
href={(props as AnchorProps).href}
|
|
121
|
+
>
|
|
122
|
+
{content}
|
|
123
|
+
</a>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
ref={ref as Ref<HTMLDivElement>}
|
|
130
|
+
className={rootClass}
|
|
131
|
+
{...(rest as HTMLAttributes<HTMLDivElement>)}
|
|
132
|
+
>
|
|
133
|
+
{content}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
MetricCard.displayName = "MetricCard";
|
|
139
|
+
|
|
140
|
+
export { MetricCard };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/* Modal ─────────────────────────────────────────────────────────
|
|
2
|
+
흐름을 끊는 중앙 다이얼로그. 결정·입력·확인의 자리.
|
|
3
|
+
구조: [header: title · close] · [body: 자유 children] · [footer: actions]
|
|
4
|
+
원사이즈(560) · dismiss는 X + ESC만 (오버레이 클릭 ❌). */
|
|
5
|
+
|
|
6
|
+
/* Overlay ───────────────────────────────────────────────────── */
|
|
7
|
+
.overlay {
|
|
8
|
+
position: fixed;
|
|
9
|
+
inset: 0;
|
|
10
|
+
z-index: var(--z-overlay);
|
|
11
|
+
background: var(--scrim);
|
|
12
|
+
animation: modal-overlay-in var(--duration-base) var(--ease-out);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.overlay[data-state="closed"] {
|
|
16
|
+
animation: modal-overlay-out var(--duration-fast) var(--ease-in);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Content ───────────────────────────────────────────────────── */
|
|
20
|
+
.content {
|
|
21
|
+
position: fixed;
|
|
22
|
+
top: 50%;
|
|
23
|
+
left: 50%;
|
|
24
|
+
z-index: var(--z-modal);
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
/* 440 = space-2(8) * 55. 모달은 좁고 집중된 자리 — 긴 폼은 페이지 자리. */
|
|
28
|
+
width: calc(100vw - var(--space-8));
|
|
29
|
+
max-width: calc(var(--space-2) * 55);
|
|
30
|
+
/* 세로는 뷰포트 - 좌우 여백과 동일 비율로 캡. body 스크롤 가능. */
|
|
31
|
+
max-height: calc(100vh - var(--space-8));
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
border: var(--hairline) solid var(--border);
|
|
34
|
+
border-radius: var(--radius-lg);
|
|
35
|
+
box-shadow: var(--shadow-xl);
|
|
36
|
+
font-family: var(--font-sans);
|
|
37
|
+
color: var(--fg);
|
|
38
|
+
outline: none;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
animation: modal-content-in var(--duration-slow) var(--ease-out);
|
|
41
|
+
transform: translate(-50%, -50%);
|
|
42
|
+
will-change: transform, opacity;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.content[data-state="closed"] {
|
|
46
|
+
animation: modal-content-out var(--duration-base) var(--ease-in);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Header ────────────────────────────────────────────────────── */
|
|
50
|
+
.header {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: flex-start;
|
|
53
|
+
gap: var(--space-2);
|
|
54
|
+
/* 상단은 넉넉히, header 자체 bottom은 0 — description↔footer 간격은 footer top padding으로 분담. */
|
|
55
|
+
padding: var(--space-5) var(--space-5) 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* title + description을 하나의 묶음으로 감싸 좌측 flex 열에 둠. */
|
|
59
|
+
.headerText {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
/* description은 title에 바짝 — 둘은 하나의 "소개" 묶음. */
|
|
65
|
+
gap: var(--space-1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.title {
|
|
69
|
+
margin: 0;
|
|
70
|
+
font-size: var(--text-sm);
|
|
71
|
+
line-height: var(--lh-sm);
|
|
72
|
+
letter-spacing: var(--type-sm-tracking);
|
|
73
|
+
font-weight: var(--font-weight-semibold);
|
|
74
|
+
color: var(--fg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.description {
|
|
78
|
+
margin: 0;
|
|
79
|
+
font-size: var(--text-xs);
|
|
80
|
+
line-height: var(--lh-xs);
|
|
81
|
+
color: var(--fg-muted);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.close {
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
color: var(--fg-muted);
|
|
87
|
+
/* title 첫 줄 line-height 중앙에 X 중심 맞춤 (Toast/Callout과 동일 패턴). */
|
|
88
|
+
margin-top: calc((var(--lh-sm) - var(--iconbtn-h-md)) / 2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Body ──────────────────────────────────────────────────────── */
|
|
92
|
+
/* 스크롤 컨테이너와 콘텐츠 트랙을 2단으로 분리 —
|
|
93
|
+
바깥(.body)은 overflow만 담당, 안쪽(.bodyInner)이 좌우 padding을 깐다.
|
|
94
|
+
스크롤바가 콘텐츠를 가리지 않고, 트랙 여백 규칙(bodyInner padding) 유지. */
|
|
95
|
+
.body {
|
|
96
|
+
flex: 1 1 auto;
|
|
97
|
+
min-height: 0;
|
|
98
|
+
overflow-y: auto;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.bodyInner {
|
|
102
|
+
/* children은 "description과 분리된 자유 콘텐츠 영역" — 여유 간격. */
|
|
103
|
+
padding: var(--space-4) var(--space-5) 0;
|
|
104
|
+
color: var(--fg);
|
|
105
|
+
font-size: var(--text-sm);
|
|
106
|
+
line-height: var(--lh-sm);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* footer가 없을 땐 body에 하단 여백을 돌려줌. */
|
|
110
|
+
.body:last-child .bodyInner {
|
|
111
|
+
padding-bottom: var(--space-5);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Footer ────────────────────────────────────────────────────── */
|
|
115
|
+
/* 액션 Button은 우측으로 몰린다 — justify가 아니라 첫 자식 margin-left:auto로 밀어
|
|
116
|
+
스크롤 컨테이너 아니지만 코드 일관성 위해 우측 정렬은 margin 패턴 사용. */
|
|
117
|
+
.footer {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: var(--space-1);
|
|
121
|
+
/* header bottom이 0이므로 footer top이 header↔footer 전체 간격을 담당. */
|
|
122
|
+
padding: var(--space-5) var(--space-5) var(--space-5);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.footer > :first-child {
|
|
126
|
+
margin-left: auto;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Animations ────────────────────────────────────────────────── */
|
|
130
|
+
@keyframes modal-overlay-in {
|
|
131
|
+
from { opacity: 0; }
|
|
132
|
+
to { opacity: 1; }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@keyframes modal-overlay-out {
|
|
136
|
+
from { opacity: 1; }
|
|
137
|
+
to { opacity: 0; }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@keyframes modal-content-in {
|
|
141
|
+
from {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
transform: translate(-50%, -50%) scale(var(--modal-scale-from, 0.96));
|
|
144
|
+
}
|
|
145
|
+
to {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
transform: translate(-50%, -50%) scale(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@keyframes modal-content-out {
|
|
152
|
+
from {
|
|
153
|
+
opacity: 1;
|
|
154
|
+
transform: translate(-50%, -50%) scale(1);
|
|
155
|
+
}
|
|
156
|
+
to {
|
|
157
|
+
opacity: 0;
|
|
158
|
+
transform: translate(-50%, -50%) scale(var(--modal-scale-from, 0.96));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Motion 축 스왑 — scale 프리미티브 스펙(0.94/0.96/0.98) 참조. */
|
|
163
|
+
:root[data-motion="bouncy"] .content { --modal-scale-from: 0.94; }
|
|
164
|
+
:root[data-motion="standard"] .content { --modal-scale-from: 0.96; }
|
|
165
|
+
:root[data-motion="calm"] .content { --modal-scale-from: 0.98; }
|
|
166
|
+
:root[data-motion="none"] .content { --modal-scale-from: 1; }
|
|
167
|
+
|
|
168
|
+
@media (prefers-reduced-motion: reduce) {
|
|
169
|
+
.overlay,
|
|
170
|
+
.overlay[data-state="closed"],
|
|
171
|
+
.content,
|
|
172
|
+
.content[data-state="closed"] {
|
|
173
|
+
animation: none;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as RadixDialog from "@radix-ui/react-dialog";
|
|
4
|
+
import { forwardRef, type ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { Icon } from "../Icon";
|
|
7
|
+
import { IconButton } from "../IconButton";
|
|
8
|
+
|
|
9
|
+
import styles from "./Modal.module.css";
|
|
10
|
+
|
|
11
|
+
export interface ModalProps {
|
|
12
|
+
open: boolean;
|
|
13
|
+
onOpenChange: (open: boolean) => void;
|
|
14
|
+
/** 헤더 좌측 타이틀. 접근성상 필수 — 숨길 땐 titleSrOnly. */
|
|
15
|
+
title: ReactNode;
|
|
16
|
+
/** 타이틀 바로 아래 한두 줄 설명. 헤더 영역 안에서 title과 바짝 붙음. 긴 폼이면 children 쪽으로. */
|
|
17
|
+
description?: ReactNode;
|
|
18
|
+
/** 타이틀을 시각적으로 숨기고 스크린리더에만 노출. 일러스트 중심 모달 등. */
|
|
19
|
+
titleSrOnly?: boolean;
|
|
20
|
+
/** 바디 콘텐츠. input/image/list 등 자유. 헤더(title + description)와 분리된 아래 영역. */
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
/** 하단 액션 영역. Button들을 넘김. 없으면 footer 자체가 렌더되지 않음. */
|
|
23
|
+
footer?: ReactNode;
|
|
24
|
+
/** 헤더 우측 X 버튼. 기본 true. */
|
|
25
|
+
dismissible?: boolean;
|
|
26
|
+
/** 스크린리더용 X 버튼 라벨. i18n 호출부 책임. */
|
|
27
|
+
dismissLabel?: string;
|
|
28
|
+
/** ESC 키 닫힘 차단. 중요한 확인/저장 중엔 true. */
|
|
29
|
+
disableEscape?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Modal = forwardRef<HTMLDivElement, ModalProps>(function Modal(
|
|
33
|
+
{
|
|
34
|
+
open,
|
|
35
|
+
onOpenChange,
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
titleSrOnly,
|
|
39
|
+
children,
|
|
40
|
+
footer,
|
|
41
|
+
dismissible = true,
|
|
42
|
+
dismissLabel,
|
|
43
|
+
disableEscape,
|
|
44
|
+
},
|
|
45
|
+
ref,
|
|
46
|
+
) {
|
|
47
|
+
return (
|
|
48
|
+
<RadixDialog.Root open={open} onOpenChange={onOpenChange}>
|
|
49
|
+
<RadixDialog.Portal>
|
|
50
|
+
<RadixDialog.Overlay className={styles.overlay} />
|
|
51
|
+
<RadixDialog.Content
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={styles.content}
|
|
54
|
+
onEscapeKeyDown={(e) => {
|
|
55
|
+
if (disableEscape) e.preventDefault();
|
|
56
|
+
}}
|
|
57
|
+
onPointerDownOutside={(e) => {
|
|
58
|
+
// 오버레이 클릭 닫기는 비활성 (실수 유발 방지).
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<div className={styles.header}>
|
|
63
|
+
<div className={styles.headerText}>
|
|
64
|
+
<RadixDialog.Title
|
|
65
|
+
className={[styles.title, titleSrOnly && "visually-hidden"]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(" ")}
|
|
68
|
+
>
|
|
69
|
+
{title}
|
|
70
|
+
</RadixDialog.Title>
|
|
71
|
+
{description && (
|
|
72
|
+
<RadixDialog.Description className={styles.description}>
|
|
73
|
+
{description}
|
|
74
|
+
</RadixDialog.Description>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
{dismissible && (
|
|
78
|
+
<RadixDialog.Close asChild>
|
|
79
|
+
<IconButton
|
|
80
|
+
className={styles.close}
|
|
81
|
+
variant="ghost"
|
|
82
|
+
icon={<Icon name="x" />}
|
|
83
|
+
aria-label={dismissLabel}
|
|
84
|
+
/>
|
|
85
|
+
</RadixDialog.Close>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
{children !== undefined && children !== null && (
|
|
89
|
+
<div className={styles.body}>
|
|
90
|
+
<div className={styles.bodyInner}>{children}</div>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
{footer && <div className={styles.footer}>{footer}</div>}
|
|
94
|
+
</RadixDialog.Content>
|
|
95
|
+
</RadixDialog.Portal>
|
|
96
|
+
</RadixDialog.Root>
|
|
97
|
+
);
|
|
98
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { Button } from "../Button";
|
|
6
|
+
import { components, componentSlugs } from "../_data";
|
|
7
|
+
import styles from "./Onboarding.module.css";
|
|
8
|
+
|
|
9
|
+
export function DocsIndex() {
|
|
10
|
+
const [copied, setCopied] = useState<string | null>(null);
|
|
11
|
+
const [error, setError] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const copyPrompt = async (name: string, slug: string) => {
|
|
14
|
+
setError(null);
|
|
15
|
+
try {
|
|
16
|
+
const promptSlug = slug.split("/").pop() ?? slug;
|
|
17
|
+
const res = await fetch(`/api/prompt/${promptSlug}`);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
setError(name);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const md = await res.text();
|
|
23
|
+
await navigator.clipboard.writeText(md);
|
|
24
|
+
setCopied(name);
|
|
25
|
+
setTimeout(() => setCopied(null), 1600);
|
|
26
|
+
} catch {
|
|
27
|
+
setError(name);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={styles.panel}>
|
|
33
|
+
{components.map((cat) => (
|
|
34
|
+
<div key={cat.id} className={styles.docsCategory}>
|
|
35
|
+
<span className={styles.docsCategoryTitle}>{cat.title}</span>
|
|
36
|
+
<div className={styles.docsGrid}>
|
|
37
|
+
{cat.items.map((name) => {
|
|
38
|
+
const slug = componentSlugs[name];
|
|
39
|
+
const hasDoc = Boolean(slug);
|
|
40
|
+
return (
|
|
41
|
+
<div key={name} className={styles.docsCard}>
|
|
42
|
+
<span className={styles.docsCardName}>{name}</span>
|
|
43
|
+
<div className={styles.docsCardActions}>
|
|
44
|
+
{hasDoc ? (
|
|
45
|
+
<Button variant="outline" size="sm" asChild>
|
|
46
|
+
<Link
|
|
47
|
+
href={`/components/${slug}`}
|
|
48
|
+
target="_blank"
|
|
49
|
+
rel="noopener"
|
|
50
|
+
>
|
|
51
|
+
문서 열기
|
|
52
|
+
</Link>
|
|
53
|
+
</Button>
|
|
54
|
+
) : (
|
|
55
|
+
<Button variant="outline" size="sm" disabled>
|
|
56
|
+
문서 없음
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
<Button
|
|
60
|
+
variant="ghost"
|
|
61
|
+
size="sm"
|
|
62
|
+
disabled={!hasDoc}
|
|
63
|
+
onClick={() => hasDoc && copyPrompt(name, slug)}
|
|
64
|
+
>
|
|
65
|
+
프롬프트 복사
|
|
66
|
+
</Button>
|
|
67
|
+
{copied === name && (
|
|
68
|
+
<span className={styles.docsBadge}>복사됨</span>
|
|
69
|
+
)}
|
|
70
|
+
{error === name && (
|
|
71
|
+
<span className={styles.docsBadge}>실패</span>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @next/next/no-img-element */
|
|
4
|
+
|
|
5
|
+
import { useState, type ChangeEvent } from "react";
|
|
6
|
+
import styles from "./Onboarding.module.css";
|
|
7
|
+
|
|
8
|
+
type SidebarMode = "left" | "right" | "dual" | "none";
|
|
9
|
+
type LogoKind = "symbol" | "combination";
|
|
10
|
+
|
|
11
|
+
type LayoutPreset = {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
sidebar: SidebarMode;
|
|
15
|
+
topbar: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LAYOUT_PRESETS: LayoutPreset[] = [
|
|
19
|
+
{ id: "sidebar-only", label: "사이드바만", sidebar: "left", topbar: false },
|
|
20
|
+
{ id: "topbar-only", label: "탑바만", sidebar: "none", topbar: true },
|
|
21
|
+
{ id: "sidebar-topbar", label: "사이드바 + 탑바", sidebar: "left", topbar: true },
|
|
22
|
+
{ id: "dual-sidebar", label: "양쪽 사이드바", sidebar: "dual", topbar: true },
|
|
23
|
+
{ id: "none", label: "레이아웃 없음", sidebar: "none", topbar: false },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function LayoutBuilder() {
|
|
27
|
+
const [presetId, setPresetId] = useState<string>("sidebar-topbar");
|
|
28
|
+
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
|
29
|
+
const [logoKind, setLogoKind] = useState<LogoKind>("symbol");
|
|
30
|
+
const [serviceName, setServiceName] = useState("Acme");
|
|
31
|
+
|
|
32
|
+
const preset =
|
|
33
|
+
LAYOUT_PRESETS.find((p) => p.id === presetId) ?? LAYOUT_PRESETS[0];
|
|
34
|
+
|
|
35
|
+
const handleFile = (e: ChangeEvent<HTMLInputElement>) => {
|
|
36
|
+
const file = e.target.files?.[0];
|
|
37
|
+
if (!file) return;
|
|
38
|
+
const url = URL.createObjectURL(file);
|
|
39
|
+
setLogoUrl(url);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className={styles.layoutGrid}>
|
|
44
|
+
<div className={styles.layoutOptions}>
|
|
45
|
+
<div className={styles.optionGroup}>
|
|
46
|
+
<span className={styles.optionGroupLabel}>레이아웃 프리셋</span>
|
|
47
|
+
<div className={styles.optionList}>
|
|
48
|
+
{LAYOUT_PRESETS.map((p) => (
|
|
49
|
+
<button
|
|
50
|
+
key={p.id}
|
|
51
|
+
type="button"
|
|
52
|
+
className={styles.optionItem}
|
|
53
|
+
data-active={p.id === presetId || undefined}
|
|
54
|
+
onClick={() => setPresetId(p.id)}
|
|
55
|
+
>
|
|
56
|
+
{p.label}
|
|
57
|
+
</button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className={styles.optionGroup}>
|
|
63
|
+
<span className={styles.optionGroupLabel}>로고 업로드</span>
|
|
64
|
+
<input
|
|
65
|
+
type="file"
|
|
66
|
+
accept="image/*"
|
|
67
|
+
onChange={handleFile}
|
|
68
|
+
className={styles.fieldControl}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className={styles.optionGroup}>
|
|
73
|
+
<span className={styles.optionGroupLabel}>로고 타입</span>
|
|
74
|
+
<div className={styles.optionList}>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
className={styles.optionItem}
|
|
78
|
+
data-active={logoKind === "symbol" || undefined}
|
|
79
|
+
onClick={() => setLogoKind("symbol")}
|
|
80
|
+
>
|
|
81
|
+
심볼 (파비콘 + 서비스명)
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className={styles.optionItem}
|
|
86
|
+
data-active={logoKind === "combination" || undefined}
|
|
87
|
+
onClick={() => setLogoKind("combination")}
|
|
88
|
+
>
|
|
89
|
+
콤비네이션 (로고 이미지만)
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{logoKind === "symbol" && (
|
|
95
|
+
<div className={styles.field}>
|
|
96
|
+
<label className={styles.fieldLabel} htmlFor="onboarding-service">
|
|
97
|
+
서비스 이름
|
|
98
|
+
</label>
|
|
99
|
+
<input
|
|
100
|
+
id="onboarding-service"
|
|
101
|
+
type="text"
|
|
102
|
+
value={serviceName}
|
|
103
|
+
onChange={(e) => setServiceName(e.target.value)}
|
|
104
|
+
className={styles.fieldControl}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div
|
|
111
|
+
className={styles.mini}
|
|
112
|
+
data-sidebar={preset.sidebar}
|
|
113
|
+
data-topbar={preset.topbar}
|
|
114
|
+
>
|
|
115
|
+
{preset.topbar && (
|
|
116
|
+
<div className={styles.miniTopbar}>
|
|
117
|
+
{logoUrl && logoKind === "symbol" && (
|
|
118
|
+
<>
|
|
119
|
+
<img
|
|
120
|
+
src={logoUrl}
|
|
121
|
+
alt=""
|
|
122
|
+
className={styles.miniBrandSymbol}
|
|
123
|
+
/>
|
|
124
|
+
<span className={styles.miniBrand}>{serviceName}</span>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
{logoUrl && logoKind === "combination" && (
|
|
128
|
+
<img src={logoUrl} alt={serviceName} className={styles.miniBrandImg} />
|
|
129
|
+
)}
|
|
130
|
+
{!logoUrl && <span className={styles.miniBrand}>{serviceName}</span>}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{(preset.sidebar === "left" || preset.sidebar === "dual") && (
|
|
135
|
+
<aside className={styles.miniSidebar}>
|
|
136
|
+
<span className={styles.miniNavItem} data-active>
|
|
137
|
+
Inbox
|
|
138
|
+
</span>
|
|
139
|
+
<span className={styles.miniNavItem}>Issues</span>
|
|
140
|
+
<span className={styles.miniNavItem}>Projects</span>
|
|
141
|
+
<span className={styles.miniNavItem}>Docs</span>
|
|
142
|
+
</aside>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
<div className={styles.miniBody}>
|
|
146
|
+
{preset.sidebar === "none" && preset.topbar === false
|
|
147
|
+
? "레이아웃 없음 — 순수 콘텐츠"
|
|
148
|
+
: "여기에 페이지 콘텐츠가 위치합니다."}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{preset.sidebar === "dual" && (
|
|
152
|
+
<aside className={`${styles.miniSidebar} ${styles.miniSidebarRight}`}>
|
|
153
|
+
<span className={styles.miniNavItem}>Details</span>
|
|
154
|
+
<span className={styles.miniNavItem}>Activity</span>
|
|
155
|
+
</aside>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|