@gunjo/ui 0.0.1-alpha.1 → 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.ja.md +90 -0
- package/README.md +52 -91
- package/package.json +47 -6
- package/src/components/display/Accordion.tsx +185 -0
- package/src/components/display/AccordionGroup.tsx +155 -0
- package/src/components/display/ActionDataTable.tsx +413 -0
- package/src/components/display/ActivityTimelineCard.tsx +483 -0
- package/src/components/display/AnalyticsCard.tsx +167 -0
- package/src/components/display/AssetCard.tsx +242 -0
- package/src/components/display/AssetGrid.tsx +164 -0
- package/src/components/display/Avatar.tsx +127 -0
- package/src/components/display/AvatarGroup.tsx +131 -0
- package/src/components/{atoms → display}/Badge.tsx +3 -3
- package/src/components/display/BarChart.tsx +247 -0
- package/src/components/{molecules → display}/Card.tsx +1 -1
- package/src/components/display/Carousel.tsx +593 -0
- package/src/components/display/ChartLegend.tsx +124 -0
- package/src/components/display/ChatMessage.tsx +382 -0
- package/src/components/display/ChoroplethMap.tsx +613 -0
- package/src/components/display/Code.tsx +42 -0
- package/src/components/display/CodeBlock.tsx +338 -0
- package/src/components/display/ColorSwatch.tsx +71 -0
- package/src/components/display/ConcentricProgressCard.tsx +545 -0
- package/src/components/display/DataTable.tsx +522 -0
- package/src/components/display/DistributionBar.tsx +102 -0
- package/src/components/display/DocNote.tsx +36 -0
- package/src/components/display/DonutChart.tsx +257 -0
- package/src/components/display/EmptyState.tsx +44 -0
- package/src/components/display/FileTree.tsx +180 -0
- package/src/components/display/GaugeChart.tsx +219 -0
- package/src/components/display/HeatmapChart.tsx +266 -0
- package/src/components/display/Icon.tsx +66 -0
- package/src/components/display/ImagePreview.tsx +140 -0
- package/src/components/{atoms → display}/Img.tsx +46 -12
- package/src/components/display/LabeledDonutCard.tsx +475 -0
- package/src/components/display/LineChart.tsx +464 -0
- package/src/components/{molecules → display}/List.tsx +20 -13
- package/src/components/display/MarkdownRenderer.tsx +157 -0
- package/src/components/display/MetadataList.tsx +81 -0
- package/src/components/display/MiniDistributionBarCard.tsx +314 -0
- package/src/components/display/PieChart.tsx +234 -0
- package/src/components/display/QuadrantMatrix.tsx +330 -0
- package/src/components/display/RadarChart.tsx +335 -0
- package/src/components/display/RadialBarChart.tsx +264 -0
- package/src/components/display/RetentionCohortCard.tsx +350 -0
- package/src/components/display/RibbonChart.tsx +618 -0
- package/src/components/display/SearchableAccordion.tsx +270 -0
- package/src/components/display/SegmentTimelineCard.tsx +452 -0
- package/src/components/display/SegmentedGaugeCard.tsx +607 -0
- package/src/components/display/Spacer.tsx +51 -0
- package/src/components/display/SparklineChart.tsx +394 -0
- package/src/components/display/StackedBarChart.tsx +393 -0
- package/src/components/display/Statistic.tsx +70 -0
- package/src/components/{molecules → display}/Table.tsx +22 -7
- package/src/components/display/Tag.tsx +80 -0
- package/src/components/display/TagEditor.tsx +141 -0
- package/src/components/display/Timeline.tsx +121 -0
- package/src/components/{atoms → display}/ToolPill.tsx +42 -18
- package/src/components/display/TreeView.tsx +226 -0
- package/src/components/display/chart-tooltip.tsx +423 -0
- package/src/components/display/chart-utils.ts +71 -0
- package/src/components/display/circular-chart-utils.ts +147 -0
- package/src/components/display/generated/default-variant-keys.ts +90 -0
- package/src/components/display/generated/variant-keys.ts +169 -0
- package/src/components/{atoms → feedback}/Alert.tsx +12 -5
- package/src/components/feedback/Banner.tsx +90 -0
- package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
- package/src/components/feedback/ProgressWidget.tsx +44 -0
- package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
- package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
- package/src/components/feedback/StatusScreen.tsx +148 -0
- package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
- package/src/components/feedback/Toast.tsx +108 -0
- package/src/components/feedback/ToastProvider.tsx +78 -0
- package/src/components/feedback/generated/default-variant-keys.ts +16 -0
- package/src/components/feedback/generated/variant-keys.ts +21 -0
- package/src/components/generated/component-manifest.ts +1568 -454
- package/src/components/generated/component-style-hints.ts +1958 -718
- package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
- package/src/components/inputs/Calendar.tsx +212 -0
- package/src/components/inputs/ChatComposer.tsx +75 -0
- package/src/components/inputs/ChatInput.tsx +528 -0
- package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
- package/src/components/inputs/Combobox.tsx +175 -0
- package/src/components/inputs/CopyButton.tsx +187 -0
- package/src/components/inputs/DatePicker.tsx +519 -0
- package/src/components/inputs/DateRangePicker.tsx +878 -0
- package/src/components/inputs/EditableField.tsx +182 -0
- package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
- package/src/components/inputs/FilterButton.tsx +163 -0
- package/src/components/{molecules → inputs}/Form.tsx +20 -3
- package/src/components/{atoms → inputs}/Input.tsx +2 -0
- package/src/components/inputs/InputOTP.tsx +75 -0
- package/src/components/inputs/Mention.tsx +279 -0
- package/src/components/inputs/NumberInput.tsx +109 -0
- package/src/components/inputs/PasswordGroup.tsx +138 -0
- package/src/components/inputs/PasswordInput.tsx +74 -0
- package/src/components/inputs/PasswordRequirementList.tsx +96 -0
- package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
- package/src/components/inputs/PhoneInput.tsx +99 -0
- package/src/components/inputs/PostalCodeInput.tsx +98 -0
- package/src/components/inputs/RangeSlider.tsx +129 -0
- package/src/components/inputs/SearchInput.tsx +76 -0
- package/src/components/inputs/Select.tsx +39 -0
- package/src/components/{atoms → inputs}/Slider.tsx +18 -5
- package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
- package/src/components/{atoms → inputs}/Switch.tsx +15 -4
- package/src/components/inputs/TagInput.tsx +114 -0
- package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
- package/src/components/inputs/TimePicker.tsx +150 -0
- package/src/components/inputs/Toggle.tsx +48 -0
- package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
- package/src/components/inputs/TooltipButton.tsx +148 -0
- package/src/components/inputs/VoiceInputButton.tsx +317 -0
- package/src/components/inputs/calendar-holidays.ts +56 -0
- package/src/components/inputs/generated/default-variant-keys.ts +32 -0
- package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
- package/src/components/layout/AspectRatio.tsx +12 -0
- package/src/components/layout/AssetInspectorPanel.tsx +416 -0
- package/src/components/layout/Cluster.tsx +56 -0
- package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
- package/src/components/layout/Container.tsx +43 -0
- package/src/components/layout/DeviceFrame.tsx +227 -0
- package/src/components/layout/Grid.tsx +65 -0
- package/src/components/layout/HStack.tsx +73 -0
- package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
- package/src/components/layout/MarqueeFrame.tsx +158 -0
- package/src/components/layout/Resizable.tsx +94 -0
- package/src/components/layout/ScrollArea.tsx +71 -0
- package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
- package/src/components/layout/VStack.tsx +69 -0
- package/src/components/layout/generated/default-variant-keys.ts +16 -0
- package/src/components/layout/generated/variant-keys.ts +21 -0
- package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
- package/src/components/navigation/Command.tsx +266 -0
- package/src/components/navigation/CommandPalette.tsx +83 -0
- package/src/components/navigation/DocumentPager.tsx +171 -0
- package/src/components/navigation/Footer.tsx +88 -0
- package/src/components/navigation/Header.tsx +80 -0
- package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
- package/src/components/navigation/NavigationMenu.tsx +128 -0
- package/src/components/navigation/PageAside.tsx +84 -0
- package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
- package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
- package/src/components/navigation/Sidebar.tsx +223 -0
- package/src/components/navigation/SidebarItem.tsx +160 -0
- package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
- package/src/components/navigation/TextLink.tsx +71 -0
- package/src/components/navigation/generated/default-variant-keys.ts +12 -0
- package/src/components/navigation/generated/variant-keys.ts +13 -0
- package/src/components/overlay/AIChatInput.tsx +5 -0
- package/src/components/overlay/AIChatMessage.tsx +6 -0
- package/src/components/overlay/AlertDialog.tsx +145 -0
- package/src/components/overlay/ChatPanel.tsx +180 -0
- package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
- package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
- package/src/components/overlay/Drawer.tsx +131 -0
- package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
- package/src/components/overlay/FloatingPanel.tsx +90 -0
- package/src/components/overlay/HoverCard.tsx +36 -0
- package/src/components/overlay/MediaLightbox.tsx +403 -0
- package/src/components/overlay/MediaPickerDialog.tsx +198 -0
- package/src/components/overlay/Modal.tsx +103 -0
- package/src/components/overlay/OnboardingFlow.tsx +172 -0
- package/src/components/overlay/Popover.tsx +36 -0
- package/src/components/overlay/ShareModal.tsx +324 -0
- package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
- package/src/components/overlay/Tooltip.tsx +130 -0
- package/src/components/overlay/generated/default-variant-keys.ts +14 -0
- package/src/components/overlay/generated/variant-keys.ts +17 -0
- package/src/components/patterns/BlogTemplate.tsx +46 -0
- package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
- package/src/components/patterns/DocsTemplate.tsx +41 -0
- package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
- package/src/components/patterns/OnboardingTemplate.tsx +32 -0
- package/src/components/patterns/PricingTemplate.tsx +106 -0
- package/src/globals.css +173 -22
- package/src/index.ts +177 -76
- package/tailwind-theme-extend.cjs +48 -3
- package/design/atoms-metadata.json +0 -82
- package/design/molecules-metadata.json +0 -130
- package/design/organisms-metadata.json +0 -38
- package/design/templates-metadata.json +0 -38
- package/src/components/atoms/Avatar.tsx +0 -57
- package/src/components/atoms/Select.tsx +0 -28
- package/src/components/atoms/generated/default-variant-keys.ts +0 -36
- package/src/components/molecules/AIChatInput.tsx +0 -140
- package/src/components/molecules/AIChatMessage.tsx +0 -109
- package/src/components/molecules/Accordion.tsx +0 -99
- package/src/components/molecules/Calendar.tsx +0 -60
- package/src/components/molecules/Carousel.tsx +0 -261
- package/src/components/molecules/Command.tsx +0 -152
- package/src/components/molecules/FilterButton.tsx +0 -133
- package/src/components/molecules/HoverCard.tsx +0 -29
- package/src/components/molecules/Modal.tsx +0 -66
- package/src/components/molecules/Popover.tsx +0 -31
- package/src/components/molecules/ProgressWidget.tsx +0 -40
- package/src/components/molecules/Resizable.tsx +0 -47
- package/src/components/molecules/ScrollArea.tsx +0 -48
- package/src/components/molecules/SidebarItem.tsx +0 -134
- package/src/components/molecules/Toast.tsx +0 -57
- package/src/components/molecules/Tooltip.tsx +0 -30
- package/src/components/molecules/generated/default-variant-keys.ts +0 -22
- package/src/components/molecules/generated/variant-keys.ts +0 -33
- package/src/components/organisms/CommandPalette.tsx +0 -58
- package/src/components/organisms/FloatingPanel.tsx +0 -46
- package/src/components/organisms/ShareModal.tsx +0 -182
- package/src/components/organisms/ToastProvider.tsx +0 -49
- /package/src/components/{atoms → display}/Kbd.tsx +0 -0
- /package/src/components/{atoms → display}/Separator.tsx +0 -0
- /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
- /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
- /package/src/components/{atoms → inputs}/Button.tsx +0 -0
- /package/src/components/{atoms → inputs}/Label.tsx +0 -0
- /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
- /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
- /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { IconX as X, IconCopy as Copy, IconGlobe as Globe, IconLock as Lock, IconCheck as Check, IconExternalLink as ExternalLink } from "@tabler/icons-react";
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import { Button } from '../inputs/Button';
|
|
7
|
+
import { Switch } from '../inputs/Switch';
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './Tooltip';
|
|
9
|
+
|
|
10
|
+
export interface ShareData {
|
|
11
|
+
isPublic: boolean;
|
|
12
|
+
token?: string;
|
|
13
|
+
accessCount?: number;
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ShareableItem {
|
|
18
|
+
id: string;
|
|
19
|
+
share?: ShareData;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ShareModalProps {
|
|
24
|
+
isOpen: boolean;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
item: ShareableItem;
|
|
27
|
+
onUpdate: (id: string, updates: Partial<ShareableItem>) => void;
|
|
28
|
+
apiEndpoint?: string; // Allow customizing API endpoint
|
|
29
|
+
onToggleShare?: (id: string, enable: boolean) => ShareData | Promise<ShareData>;
|
|
30
|
+
onCopyShareUrl?: (url: string) => void | Promise<void>;
|
|
31
|
+
onOpenShareUrl?: (url: string) => void | Promise<void>;
|
|
32
|
+
portalContainer?: HTMLElement | null;
|
|
33
|
+
labels?: {
|
|
34
|
+
title?: string;
|
|
35
|
+
publicLink?: string;
|
|
36
|
+
publicDescription?: string;
|
|
37
|
+
privateDescription?: string;
|
|
38
|
+
publicUrl?: string;
|
|
39
|
+
sharingDisabled?: string;
|
|
40
|
+
accessCount?: string;
|
|
41
|
+
token?: string;
|
|
42
|
+
copy?: string;
|
|
43
|
+
copied?: string;
|
|
44
|
+
copyFailed?: string;
|
|
45
|
+
open?: string;
|
|
46
|
+
openPreview?: string;
|
|
47
|
+
close?: string;
|
|
48
|
+
enablePublicLink?: string;
|
|
49
|
+
disablePublicLink?: string;
|
|
50
|
+
sharingDisabledReason?: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function writeClipboardText(text: string) {
|
|
55
|
+
if (navigator.clipboard?.writeText) {
|
|
56
|
+
await navigator.clipboard.writeText(text);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const textarea = document.createElement("textarea");
|
|
61
|
+
textarea.value = text;
|
|
62
|
+
textarea.setAttribute("readonly", "");
|
|
63
|
+
textarea.style.position = "fixed";
|
|
64
|
+
textarea.style.left = "-9999px";
|
|
65
|
+
document.body.appendChild(textarea);
|
|
66
|
+
textarea.select();
|
|
67
|
+
const copied = document.execCommand("copy");
|
|
68
|
+
document.body.removeChild(textarea);
|
|
69
|
+
|
|
70
|
+
if (!copied) {
|
|
71
|
+
throw new Error("Failed to copy share URL");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const ShareModal = ({
|
|
76
|
+
isOpen,
|
|
77
|
+
onClose,
|
|
78
|
+
item,
|
|
79
|
+
onUpdate,
|
|
80
|
+
apiEndpoint = '/api/share',
|
|
81
|
+
onToggleShare,
|
|
82
|
+
onCopyShareUrl,
|
|
83
|
+
onOpenShareUrl,
|
|
84
|
+
portalContainer,
|
|
85
|
+
labels,
|
|
86
|
+
}: ShareModalProps) => {
|
|
87
|
+
const titleId = React.useId();
|
|
88
|
+
const [isPublic, setIsPublic] = useState(item.share?.isPublic || false);
|
|
89
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
90
|
+
const [copied, setCopied] = useState(false);
|
|
91
|
+
const [shareData, setShareData] = useState<ShareData | undefined>(item.share);
|
|
92
|
+
const [mounted, setMounted] = useState(false);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setMounted(true);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setIsPublic(item.share?.isPublic || false);
|
|
100
|
+
setShareData(item.share);
|
|
101
|
+
}, [item.id, item.share]);
|
|
102
|
+
|
|
103
|
+
if (!isOpen || !mounted) return null;
|
|
104
|
+
|
|
105
|
+
const handleToggleShare = async () => {
|
|
106
|
+
setIsLoading(true);
|
|
107
|
+
try {
|
|
108
|
+
const nextIsPublic = !isPublic;
|
|
109
|
+
const nextShare = onToggleShare
|
|
110
|
+
? await onToggleShare(item.id, nextIsPublic)
|
|
111
|
+
: (await fetch(apiEndpoint, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: { 'Content-Type': 'application/json' },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
id: item.id,
|
|
116
|
+
enable: nextIsPublic
|
|
117
|
+
})
|
|
118
|
+
}).then((res) => res.ok ? res.json() : Promise.reject(new Error("Failed to toggle share")))).share;
|
|
119
|
+
|
|
120
|
+
setIsPublic(nextIsPublic);
|
|
121
|
+
setShareData(nextShare);
|
|
122
|
+
onUpdate(item.id, { share: nextShare });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Failed to toggle share', error);
|
|
125
|
+
} finally {
|
|
126
|
+
setIsLoading(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const shareUrl = shareData?.token
|
|
131
|
+
? `${window.location.origin}/share/${shareData.token}`
|
|
132
|
+
: '';
|
|
133
|
+
|
|
134
|
+
const handleCopy = async () => {
|
|
135
|
+
if (!shareUrl) return;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
if (onCopyShareUrl) {
|
|
139
|
+
await onCopyShareUrl(shareUrl);
|
|
140
|
+
} else {
|
|
141
|
+
await writeClipboardText(shareUrl);
|
|
142
|
+
}
|
|
143
|
+
setCopied(true);
|
|
144
|
+
setTimeout(() => setCopied(false), 2000);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Failed to copy share URL', error);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleOpen = async () => {
|
|
151
|
+
if (!shareUrl) return;
|
|
152
|
+
|
|
153
|
+
if (onOpenShareUrl) {
|
|
154
|
+
await onOpenShareUrl(shareUrl);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
window.open(shareUrl, "_blank", "noopener,noreferrer");
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return createPortal(
|
|
162
|
+
<div
|
|
163
|
+
className={cn(
|
|
164
|
+
"fixed inset-0 z-50 flex items-center justify-center bg-overlay/50 backdrop-blur-sm animate-in fade-in duration-200",
|
|
165
|
+
portalContainer && "absolute"
|
|
166
|
+
)}
|
|
167
|
+
onClick={(e) => { e.stopPropagation(); onClose(); }}
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
className="mx-4 w-[448px] max-w-md max-w-[calc(100%-2rem)] overflow-hidden rounded-xl border border-border bg-background shadow-2xl animate-in zoom-in-95 duration-200"
|
|
171
|
+
role="dialog"
|
|
172
|
+
aria-modal="true"
|
|
173
|
+
aria-labelledby={titleId}
|
|
174
|
+
onClick={e => e.stopPropagation()}
|
|
175
|
+
>
|
|
176
|
+
{/* Header */}
|
|
177
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border bg-muted/50">
|
|
178
|
+
<h3 id={titleId} className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
179
|
+
<Globe size={16} className="text-primary" />
|
|
180
|
+
{labels?.title ?? "Share Image"}
|
|
181
|
+
</h3>
|
|
182
|
+
<TooltipProvider>
|
|
183
|
+
<Tooltip>
|
|
184
|
+
<TooltipTrigger asChild>
|
|
185
|
+
<Button
|
|
186
|
+
type="button"
|
|
187
|
+
size="icon"
|
|
188
|
+
variant="ghost"
|
|
189
|
+
onClick={onClose}
|
|
190
|
+
className="h-8 w-8 text-muted-foreground"
|
|
191
|
+
aria-label={labels?.close ?? "Close"}
|
|
192
|
+
>
|
|
193
|
+
<X size={18} />
|
|
194
|
+
</Button>
|
|
195
|
+
</TooltipTrigger>
|
|
196
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
197
|
+
{labels?.close ?? "Close"}
|
|
198
|
+
</TooltipContent>
|
|
199
|
+
</Tooltip>
|
|
200
|
+
</TooltipProvider>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
{/* Content */}
|
|
204
|
+
<div className="p-6 space-y-6">
|
|
205
|
+
|
|
206
|
+
{/* Toggle Switch */}
|
|
207
|
+
<div className="flex items-center justify-between">
|
|
208
|
+
<div className="space-y-1">
|
|
209
|
+
<div className="text-sm font-medium text-foreground">{labels?.publicLink ?? "Public Link"}</div>
|
|
210
|
+
<div className="text-xs text-muted-foreground">
|
|
211
|
+
{isPublic
|
|
212
|
+
? labels?.publicDescription ?? "Anyone with the link can view this image."
|
|
213
|
+
: labels?.privateDescription ?? "Only you can view this image."}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<TooltipProvider>
|
|
218
|
+
<Tooltip>
|
|
219
|
+
<TooltipTrigger asChild>
|
|
220
|
+
<Switch
|
|
221
|
+
checked={isPublic}
|
|
222
|
+
onCheckedChange={handleToggleShare}
|
|
223
|
+
disabled={isLoading}
|
|
224
|
+
aria-label={
|
|
225
|
+
isPublic
|
|
226
|
+
? labels?.disablePublicLink ?? "Turn public link off"
|
|
227
|
+
: labels?.enablePublicLink ?? "Turn public link on"
|
|
228
|
+
}
|
|
229
|
+
/>
|
|
230
|
+
</TooltipTrigger>
|
|
231
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
232
|
+
{isPublic
|
|
233
|
+
? labels?.disablePublicLink ?? "Turn public link off"
|
|
234
|
+
: labels?.enablePublicLink ?? "Turn public link on"}
|
|
235
|
+
</TooltipContent>
|
|
236
|
+
</Tooltip>
|
|
237
|
+
</TooltipProvider>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{/* URL Display */}
|
|
241
|
+
{isPublic && shareData && (
|
|
242
|
+
<div className="space-y-2 animate-in slide-in-from-top-2 duration-200">
|
|
243
|
+
<label className="text-xs font-medium text-muted-foreground">{labels?.publicUrl ?? "Public URL"}</label>
|
|
244
|
+
<div className="flex items-center gap-2">
|
|
245
|
+
<div className="flex-1 bg-muted border border-border rounded-md px-3 py-2 text-xs text-muted-foreground font-mono truncate select-all">
|
|
246
|
+
{shareUrl}
|
|
247
|
+
</div>
|
|
248
|
+
<TooltipProvider>
|
|
249
|
+
<Tooltip>
|
|
250
|
+
<TooltipTrigger asChild>
|
|
251
|
+
<Button
|
|
252
|
+
type="button"
|
|
253
|
+
size="icon"
|
|
254
|
+
variant="outline"
|
|
255
|
+
onClick={handleCopy}
|
|
256
|
+
className="h-9 w-9 shrink-0 text-muted-foreground"
|
|
257
|
+
aria-label={labels?.copy ?? "Copy to clipboard"}
|
|
258
|
+
>
|
|
259
|
+
{copied ? <Check size={16} className="text-primary" /> : <Copy size={16} />}
|
|
260
|
+
</Button>
|
|
261
|
+
</TooltipTrigger>
|
|
262
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
263
|
+
{copied ? labels?.copied ?? "Copied" : labels?.copy ?? "Copy to clipboard"}
|
|
264
|
+
</TooltipContent>
|
|
265
|
+
</Tooltip>
|
|
266
|
+
<Tooltip>
|
|
267
|
+
<TooltipTrigger asChild>
|
|
268
|
+
<Button
|
|
269
|
+
type="button"
|
|
270
|
+
size="icon"
|
|
271
|
+
variant="outline"
|
|
272
|
+
onClick={handleOpen}
|
|
273
|
+
className="h-9 w-9 shrink-0 text-muted-foreground"
|
|
274
|
+
aria-label={labels?.open ?? "Open in new tab"}
|
|
275
|
+
>
|
|
276
|
+
<ExternalLink size={16} />
|
|
277
|
+
</Button>
|
|
278
|
+
</TooltipTrigger>
|
|
279
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
280
|
+
{labels?.openPreview ?? labels?.open ?? "Open in new tab"}
|
|
281
|
+
</TooltipContent>
|
|
282
|
+
</Tooltip>
|
|
283
|
+
</TooltipProvider>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
{/* Stats */}
|
|
287
|
+
<div className="flex items-center gap-4 pt-2">
|
|
288
|
+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
289
|
+
<Globe size={12} />
|
|
290
|
+
<span>{labels?.accessCount ?? "Access Count"}: <span className="text-foreground font-mono">{shareData.accessCount || 0}</span></span>
|
|
291
|
+
</div>
|
|
292
|
+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
293
|
+
<Lock size={12} />
|
|
294
|
+
<span>{labels?.token ?? "Token"}: <span className="text-foreground font-mono">{shareData.token?.slice(0, 8)}...</span></span>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{!isPublic && (
|
|
301
|
+
<TooltipProvider>
|
|
302
|
+
<Tooltip>
|
|
303
|
+
<TooltipTrigger asChild>
|
|
304
|
+
<div
|
|
305
|
+
className="flex cursor-help flex-col items-center justify-center space-y-2 rounded-lg border-2 border-dashed border-border bg-muted/50 py-4 text-muted-foreground"
|
|
306
|
+
tabIndex={0}
|
|
307
|
+
>
|
|
308
|
+
<Lock size={24} />
|
|
309
|
+
<span className="text-xs">{labels?.sharingDisabled ?? "Sharing is disabled"}</span>
|
|
310
|
+
</div>
|
|
311
|
+
</TooltipTrigger>
|
|
312
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
313
|
+
{labels?.sharingDisabledReason ?? "Turn on the public link switch to create a share URL."}
|
|
314
|
+
</TooltipContent>
|
|
315
|
+
</Tooltip>
|
|
316
|
+
</TooltipProvider>
|
|
317
|
+
)}
|
|
318
|
+
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>,
|
|
322
|
+
portalContainer ?? document.body
|
|
323
|
+
);
|
|
324
|
+
};
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
|
5
5
|
import { type VariantProps, cva } from "class-variance-authority"
|
|
6
|
-
import { X } from "
|
|
6
|
+
import { IconX as X } from "@tabler/icons-react";
|
|
7
7
|
|
|
8
8
|
import { cn } from "../../lib/utils"
|
|
9
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./Tooltip"
|
|
9
10
|
|
|
10
11
|
const Sheet = SheetPrimitive.Root
|
|
11
12
|
|
|
@@ -51,27 +52,83 @@ const sheetVariants = cva(
|
|
|
51
52
|
|
|
52
53
|
interface SheetContentProps
|
|
53
54
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
54
|
-
VariantProps<typeof sheetVariants> {
|
|
55
|
+
VariantProps<typeof sheetVariants> {
|
|
56
|
+
portalContainer?: HTMLElement | null
|
|
57
|
+
overlayClassName?: string
|
|
58
|
+
closeLabel?: string
|
|
59
|
+
}
|
|
55
60
|
|
|
56
61
|
const SheetContent = React.forwardRef<
|
|
57
62
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
58
63
|
SheetContentProps
|
|
59
|
-
>(({ side = "right", className, children, ...props }, ref) =>
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
>(({ side = "right", className, children, portalContainer, overlayClassName, closeLabel = "Close", onOpenAutoFocus, ...props }, ref) => {
|
|
65
|
+
const contentRef = React.useRef<React.ElementRef<typeof SheetPrimitive.Content> | null>(null)
|
|
66
|
+
|
|
67
|
+
const setRefs = React.useCallback(
|
|
68
|
+
(node: React.ElementRef<typeof SheetPrimitive.Content> | null) => {
|
|
69
|
+
contentRef.current = node
|
|
70
|
+
if (typeof ref === "function") {
|
|
71
|
+
ref(node)
|
|
72
|
+
} else if (ref) {
|
|
73
|
+
ref.current = node
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[ref]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const handleOpenAutoFocus = React.useCallback(
|
|
80
|
+
(event: Event) => {
|
|
81
|
+
onOpenAutoFocus?.(event)
|
|
82
|
+
if (event.defaultPrevented) return
|
|
83
|
+
|
|
84
|
+
event.preventDefault()
|
|
85
|
+
requestAnimationFrame(() => {
|
|
86
|
+
contentRef.current?.focus({ preventScroll: true })
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
[onOpenAutoFocus]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<SheetPortal container={portalContainer ?? undefined}>
|
|
94
|
+
<SheetOverlay className={cn(portalContainer && "absolute", overlayClassName)} />
|
|
95
|
+
<SheetPrimitive.Content
|
|
96
|
+
ref={setRefs}
|
|
97
|
+
tabIndex={-1}
|
|
98
|
+
onOpenAutoFocus={handleOpenAutoFocus}
|
|
99
|
+
className={cn(
|
|
100
|
+
sheetVariants({ side }),
|
|
101
|
+
portalContainer && "absolute",
|
|
102
|
+
portalContainer && "data-[state=open]:!transform-none",
|
|
103
|
+
portalContainer && side === "right" && "right-0 top-0 h-full",
|
|
104
|
+
portalContainer && side === "left" && "left-0 top-0 h-full",
|
|
105
|
+
portalContainer && side === "top" && "left-0 right-0 top-0",
|
|
106
|
+
portalContainer && side === "bottom" && "bottom-0 left-0 right-0",
|
|
107
|
+
className
|
|
108
|
+
)}
|
|
109
|
+
{...props}
|
|
110
|
+
>
|
|
111
|
+
<TooltipProvider>
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger asChild>
|
|
114
|
+
<SheetPrimitive.Close
|
|
115
|
+
className="absolute right-4 top-4 rounded-sm p-1 opacity-70 ring-offset-background transition-opacity hover:bg-muted hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"
|
|
116
|
+
aria-label={closeLabel}
|
|
117
|
+
>
|
|
118
|
+
<X className="h-4 w-4" />
|
|
119
|
+
<span className="sr-only">{closeLabel}</span>
|
|
120
|
+
</SheetPrimitive.Close>
|
|
121
|
+
</TooltipTrigger>
|
|
122
|
+
<TooltipContent portalContainer={portalContainer}>
|
|
123
|
+
{closeLabel}
|
|
124
|
+
</TooltipContent>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
</TooltipProvider>
|
|
127
|
+
{children}
|
|
128
|
+
</SheetPrimitive.Content>
|
|
129
|
+
</SheetPortal>
|
|
130
|
+
)
|
|
131
|
+
})
|
|
75
132
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
|
76
133
|
|
|
77
134
|
const SheetHeader = ({
|
|
@@ -94,7 +151,7 @@ const SheetFooter = ({
|
|
|
94
151
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
95
152
|
<div
|
|
96
153
|
className={cn(
|
|
97
|
-
"flex flex-
|
|
154
|
+
"flex flex-row flex-wrap justify-end gap-2 sm:space-x-2",
|
|
98
155
|
className
|
|
99
156
|
)}
|
|
100
157
|
{...props}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
type TooltipProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root> & {
|
|
11
|
+
/**
|
|
12
|
+
* Opens the tooltip for a short time on touch/pen press.
|
|
13
|
+
* This keeps disabled reasons and icon-only feedback available on mobile.
|
|
14
|
+
*/
|
|
15
|
+
openOnPress?: boolean
|
|
16
|
+
pressOpenDuration?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type TooltipInteractionContextValue = {
|
|
20
|
+
openFromPress: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TooltipInteractionContext = React.createContext<TooltipInteractionContextValue | null>(null)
|
|
24
|
+
|
|
25
|
+
const Tooltip = ({
|
|
26
|
+
children,
|
|
27
|
+
open,
|
|
28
|
+
defaultOpen,
|
|
29
|
+
onOpenChange,
|
|
30
|
+
openOnPress = true,
|
|
31
|
+
pressOpenDuration = 2200,
|
|
32
|
+
...props
|
|
33
|
+
}: TooltipProps) => {
|
|
34
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false)
|
|
35
|
+
const closeTimerRef = React.useRef<number | null>(null)
|
|
36
|
+
const isControlled = open !== undefined
|
|
37
|
+
const resolvedOpen = isControlled ? open : internalOpen
|
|
38
|
+
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
return () => {
|
|
41
|
+
if (closeTimerRef.current !== null) {
|
|
42
|
+
window.clearTimeout(closeTimerRef.current)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
const setOpen = React.useCallback(
|
|
48
|
+
(nextOpen: boolean) => {
|
|
49
|
+
if (!isControlled) {
|
|
50
|
+
setInternalOpen(nextOpen)
|
|
51
|
+
}
|
|
52
|
+
onOpenChange?.(nextOpen)
|
|
53
|
+
},
|
|
54
|
+
[isControlled, onOpenChange]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const openFromPress = React.useCallback(() => {
|
|
58
|
+
if (!openOnPress) return
|
|
59
|
+
|
|
60
|
+
if (closeTimerRef.current !== null) {
|
|
61
|
+
window.clearTimeout(closeTimerRef.current)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setOpen(true)
|
|
65
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
66
|
+
setOpen(false)
|
|
67
|
+
closeTimerRef.current = null
|
|
68
|
+
}, pressOpenDuration)
|
|
69
|
+
}, [openOnPress, pressOpenDuration, setOpen])
|
|
70
|
+
|
|
71
|
+
const contextValue = React.useMemo(() => ({ openFromPress }), [openFromPress])
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<TooltipInteractionContext.Provider value={contextValue}>
|
|
75
|
+
<TooltipPrimitive.Root
|
|
76
|
+
open={resolvedOpen}
|
|
77
|
+
onOpenChange={setOpen}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</TooltipPrimitive.Root>
|
|
82
|
+
</TooltipInteractionContext.Provider>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const TooltipTrigger = React.forwardRef<
|
|
87
|
+
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
|
88
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
|
89
|
+
>(({ onPointerDown, ...props }, ref) => {
|
|
90
|
+
const interaction = React.useContext(TooltipInteractionContext)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<TooltipPrimitive.Trigger
|
|
94
|
+
ref={ref}
|
|
95
|
+
onPointerDown={(event) => {
|
|
96
|
+
onPointerDown?.(event)
|
|
97
|
+
if (event.defaultPrevented) return
|
|
98
|
+
if (event.pointerType === "touch" || event.pointerType === "pen") {
|
|
99
|
+
interaction?.openFromPress()
|
|
100
|
+
}
|
|
101
|
+
}}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName
|
|
107
|
+
|
|
108
|
+
type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
|
|
109
|
+
portalContainer?: HTMLElement | null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const TooltipContent = React.forwardRef<
|
|
113
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
114
|
+
TooltipContentProps
|
|
115
|
+
>(({ className, sideOffset = 4, portalContainer, ...props }, ref) => (
|
|
116
|
+
<TooltipPrimitive.Portal container={portalContainer ?? undefined}>
|
|
117
|
+
<TooltipPrimitive.Content
|
|
118
|
+
ref={ref}
|
|
119
|
+
sideOffset={sideOffset}
|
|
120
|
+
className={cn(
|
|
121
|
+
"pointer-events-none z-50 w-fit max-w-xs overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-center text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
</TooltipPrimitive.Portal>
|
|
127
|
+
))
|
|
128
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
129
|
+
|
|
130
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
import type { ChatPanelVariantKey, MediaLightboxVariantKey, MediaPickerDialogVariantKey } from "./variant-keys";
|
|
5
|
+
|
|
6
|
+
export const chatPanelDefaultVariantKey: ChatPanelVariantKey = "default";
|
|
7
|
+
export const mediaLightboxDefaultVariantKey: MediaLightboxVariantKey = "default";
|
|
8
|
+
export const mediaPickerDialogDefaultVariantKey: MediaPickerDialogVariantKey = "default";
|
|
9
|
+
|
|
10
|
+
export const overlayDefaultVariantKeys = {
|
|
11
|
+
chatPanel: chatPanelDefaultVariantKey,
|
|
12
|
+
mediaLightbox: mediaLightboxDefaultVariantKey,
|
|
13
|
+
mediaPickerDialog: mediaPickerDialogDefaultVariantKey,
|
|
14
|
+
} as const;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
export const chatPanelVariantKeys = ["compact", "default"] as const;
|
|
5
|
+
export type ChatPanelVariantKey = (typeof chatPanelVariantKeys)[number];
|
|
6
|
+
|
|
7
|
+
export const mediaLightboxVariantKeys = ["compact", "default"] as const;
|
|
8
|
+
export type MediaLightboxVariantKey = (typeof mediaLightboxVariantKeys)[number];
|
|
9
|
+
|
|
10
|
+
export const mediaPickerDialogVariantKeys = ["compact", "default"] as const;
|
|
11
|
+
export type MediaPickerDialogVariantKey = (typeof mediaPickerDialogVariantKeys)[number];
|
|
12
|
+
|
|
13
|
+
export const overlayVariantKeys = {
|
|
14
|
+
chatPanel: chatPanelVariantKeys,
|
|
15
|
+
mediaLightbox: mediaLightboxVariantKeys,
|
|
16
|
+
mediaPickerDialog: mediaPickerDialogVariantKeys,
|
|
17
|
+
} as const;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
export interface BlogTemplateProps
|
|
6
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
7
|
+
category?: React.ReactNode
|
|
8
|
+
title: React.ReactNode
|
|
9
|
+
/** Author / read time / date row. */
|
|
10
|
+
meta?: React.ReactNode
|
|
11
|
+
hero?: React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BlogTemplate = React.forwardRef<HTMLElement, BlogTemplateProps>(
|
|
15
|
+
(
|
|
16
|
+
{ className, category, title, meta, hero, children, ...props },
|
|
17
|
+
ref
|
|
18
|
+
) => (
|
|
19
|
+
<article
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn("mx-auto flex w-full max-w-3xl flex-col gap-6 px-6 py-12", className)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{category ? (
|
|
25
|
+
<p className="text-sm font-medium uppercase tracking-wide text-muted-foreground">
|
|
26
|
+
{category}
|
|
27
|
+
</p>
|
|
28
|
+
) : null}
|
|
29
|
+
<h1 className="text-4xl font-bold tracking-tight md:text-5xl">{title}</h1>
|
|
30
|
+
{meta ? (
|
|
31
|
+
<div className="text-sm text-muted-foreground">{meta}</div>
|
|
32
|
+
) : null}
|
|
33
|
+
{hero ? (
|
|
34
|
+
<div className="overflow-hidden rounded-lg border border-border">
|
|
35
|
+
{hero}
|
|
36
|
+
</div>
|
|
37
|
+
) : null}
|
|
38
|
+
<div className="prose prose-sm max-w-none text-foreground prose-headings:text-foreground prose-strong:text-foreground prose-a:text-foreground">
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
</article>
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
BlogTemplate.displayName = "BlogTemplate"
|
|
45
|
+
|
|
46
|
+
export { BlogTemplate }
|
|
@@ -17,13 +17,13 @@ export function DashboardTemplate({
|
|
|
17
17
|
return (
|
|
18
18
|
<div className={cn("flex flex-col w-[1280px] h-[720px] min-h-screen w-full", className)} {...props}>
|
|
19
19
|
{header && <div className="border-b">{header}</div>}
|
|
20
|
-
<div className="flex flex-1">
|
|
20
|
+
<div className="flex flex-1 min-h-0">
|
|
21
21
|
{sidebar && (
|
|
22
22
|
<aside className="hidden border-r w-64 md:block flex-shrink-0">
|
|
23
23
|
{sidebar}
|
|
24
24
|
</aside>
|
|
25
25
|
)}
|
|
26
|
-
<main className="flex-1 overflow-auto bg-muted/50">
|
|
26
|
+
<main className="flex-1 min-h-0 overflow-auto bg-muted/50">
|
|
27
27
|
<div className="container mx-auto py-6 space-y-8 px-4 md:px-6">
|
|
28
28
|
{children}
|
|
29
29
|
</div>
|