@gunjo/ui 0.0.1-alpha.0 → 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.
Files changed (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +90 -0
  3. package/README.md +52 -91
  4. package/package.json +47 -6
  5. package/src/components/display/Accordion.tsx +185 -0
  6. package/src/components/display/AccordionGroup.tsx +155 -0
  7. package/src/components/display/ActionDataTable.tsx +413 -0
  8. package/src/components/display/ActivityTimelineCard.tsx +483 -0
  9. package/src/components/display/AnalyticsCard.tsx +167 -0
  10. package/src/components/display/AssetCard.tsx +242 -0
  11. package/src/components/display/AssetGrid.tsx +164 -0
  12. package/src/components/display/Avatar.tsx +127 -0
  13. package/src/components/display/AvatarGroup.tsx +131 -0
  14. package/src/components/{atoms → display}/Badge.tsx +3 -3
  15. package/src/components/display/BarChart.tsx +247 -0
  16. package/src/components/{molecules → display}/Card.tsx +1 -1
  17. package/src/components/display/Carousel.tsx +593 -0
  18. package/src/components/display/ChartLegend.tsx +124 -0
  19. package/src/components/display/ChatMessage.tsx +382 -0
  20. package/src/components/display/ChoroplethMap.tsx +613 -0
  21. package/src/components/display/Code.tsx +42 -0
  22. package/src/components/display/CodeBlock.tsx +338 -0
  23. package/src/components/display/ColorSwatch.tsx +71 -0
  24. package/src/components/display/ConcentricProgressCard.tsx +545 -0
  25. package/src/components/display/DataTable.tsx +522 -0
  26. package/src/components/display/DistributionBar.tsx +102 -0
  27. package/src/components/display/DocNote.tsx +36 -0
  28. package/src/components/display/DonutChart.tsx +257 -0
  29. package/src/components/display/EmptyState.tsx +44 -0
  30. package/src/components/display/FileTree.tsx +180 -0
  31. package/src/components/display/GaugeChart.tsx +219 -0
  32. package/src/components/display/HeatmapChart.tsx +266 -0
  33. package/src/components/display/Icon.tsx +66 -0
  34. package/src/components/display/ImagePreview.tsx +140 -0
  35. package/src/components/{atoms → display}/Img.tsx +46 -12
  36. package/src/components/display/LabeledDonutCard.tsx +475 -0
  37. package/src/components/display/LineChart.tsx +464 -0
  38. package/src/components/{molecules → display}/List.tsx +20 -13
  39. package/src/components/display/MarkdownRenderer.tsx +157 -0
  40. package/src/components/display/MetadataList.tsx +81 -0
  41. package/src/components/display/MiniDistributionBarCard.tsx +314 -0
  42. package/src/components/display/PieChart.tsx +234 -0
  43. package/src/components/display/QuadrantMatrix.tsx +330 -0
  44. package/src/components/display/RadarChart.tsx +335 -0
  45. package/src/components/display/RadialBarChart.tsx +264 -0
  46. package/src/components/display/RetentionCohortCard.tsx +350 -0
  47. package/src/components/display/RibbonChart.tsx +618 -0
  48. package/src/components/display/SearchableAccordion.tsx +270 -0
  49. package/src/components/display/SegmentTimelineCard.tsx +452 -0
  50. package/src/components/display/SegmentedGaugeCard.tsx +607 -0
  51. package/src/components/display/Spacer.tsx +51 -0
  52. package/src/components/display/SparklineChart.tsx +394 -0
  53. package/src/components/display/StackedBarChart.tsx +393 -0
  54. package/src/components/display/Statistic.tsx +70 -0
  55. package/src/components/{molecules → display}/Table.tsx +22 -7
  56. package/src/components/display/Tag.tsx +80 -0
  57. package/src/components/display/TagEditor.tsx +141 -0
  58. package/src/components/display/Timeline.tsx +121 -0
  59. package/src/components/{atoms → display}/ToolPill.tsx +42 -18
  60. package/src/components/display/TreeView.tsx +226 -0
  61. package/src/components/display/chart-tooltip.tsx +423 -0
  62. package/src/components/display/chart-utils.ts +71 -0
  63. package/src/components/display/circular-chart-utils.ts +147 -0
  64. package/src/components/display/generated/default-variant-keys.ts +90 -0
  65. package/src/components/display/generated/variant-keys.ts +169 -0
  66. package/src/components/{atoms → feedback}/Alert.tsx +12 -5
  67. package/src/components/feedback/Banner.tsx +90 -0
  68. package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
  69. package/src/components/feedback/ProgressWidget.tsx +44 -0
  70. package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
  71. package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
  72. package/src/components/feedback/StatusScreen.tsx +148 -0
  73. package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
  74. package/src/components/feedback/Toast.tsx +108 -0
  75. package/src/components/feedback/ToastProvider.tsx +78 -0
  76. package/src/components/feedback/generated/default-variant-keys.ts +16 -0
  77. package/src/components/feedback/generated/variant-keys.ts +21 -0
  78. package/src/components/generated/component-manifest.ts +1568 -454
  79. package/src/components/generated/component-style-hints.ts +1958 -718
  80. package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
  81. package/src/components/inputs/Calendar.tsx +212 -0
  82. package/src/components/inputs/ChatComposer.tsx +75 -0
  83. package/src/components/inputs/ChatInput.tsx +528 -0
  84. package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
  85. package/src/components/inputs/Combobox.tsx +175 -0
  86. package/src/components/inputs/CopyButton.tsx +187 -0
  87. package/src/components/inputs/DatePicker.tsx +519 -0
  88. package/src/components/inputs/DateRangePicker.tsx +878 -0
  89. package/src/components/inputs/EditableField.tsx +182 -0
  90. package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
  91. package/src/components/inputs/FilterButton.tsx +163 -0
  92. package/src/components/{molecules → inputs}/Form.tsx +20 -3
  93. package/src/components/{atoms → inputs}/Input.tsx +2 -0
  94. package/src/components/inputs/InputOTP.tsx +75 -0
  95. package/src/components/inputs/Mention.tsx +279 -0
  96. package/src/components/inputs/NumberInput.tsx +109 -0
  97. package/src/components/inputs/PasswordGroup.tsx +138 -0
  98. package/src/components/inputs/PasswordInput.tsx +74 -0
  99. package/src/components/inputs/PasswordRequirementList.tsx +96 -0
  100. package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
  101. package/src/components/inputs/PhoneInput.tsx +99 -0
  102. package/src/components/inputs/PostalCodeInput.tsx +98 -0
  103. package/src/components/inputs/RangeSlider.tsx +129 -0
  104. package/src/components/inputs/SearchInput.tsx +76 -0
  105. package/src/components/inputs/Select.tsx +39 -0
  106. package/src/components/{atoms → inputs}/Slider.tsx +18 -5
  107. package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
  108. package/src/components/{atoms → inputs}/Switch.tsx +15 -4
  109. package/src/components/inputs/TagInput.tsx +114 -0
  110. package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
  111. package/src/components/inputs/TimePicker.tsx +150 -0
  112. package/src/components/inputs/Toggle.tsx +48 -0
  113. package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
  114. package/src/components/inputs/TooltipButton.tsx +148 -0
  115. package/src/components/inputs/VoiceInputButton.tsx +317 -0
  116. package/src/components/inputs/calendar-holidays.ts +56 -0
  117. package/src/components/inputs/generated/default-variant-keys.ts +32 -0
  118. package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
  119. package/src/components/layout/AspectRatio.tsx +12 -0
  120. package/src/components/layout/AssetInspectorPanel.tsx +416 -0
  121. package/src/components/layout/Cluster.tsx +56 -0
  122. package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
  123. package/src/components/layout/Container.tsx +43 -0
  124. package/src/components/layout/DeviceFrame.tsx +227 -0
  125. package/src/components/layout/Grid.tsx +65 -0
  126. package/src/components/layout/HStack.tsx +73 -0
  127. package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
  128. package/src/components/layout/MarqueeFrame.tsx +158 -0
  129. package/src/components/layout/Resizable.tsx +94 -0
  130. package/src/components/layout/ScrollArea.tsx +71 -0
  131. package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
  132. package/src/components/layout/VStack.tsx +69 -0
  133. package/src/components/layout/generated/default-variant-keys.ts +16 -0
  134. package/src/components/layout/generated/variant-keys.ts +21 -0
  135. package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
  136. package/src/components/navigation/Command.tsx +266 -0
  137. package/src/components/navigation/CommandPalette.tsx +83 -0
  138. package/src/components/navigation/DocumentPager.tsx +171 -0
  139. package/src/components/navigation/Footer.tsx +88 -0
  140. package/src/components/navigation/Header.tsx +80 -0
  141. package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
  142. package/src/components/navigation/NavigationMenu.tsx +128 -0
  143. package/src/components/navigation/PageAside.tsx +84 -0
  144. package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
  145. package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
  146. package/src/components/navigation/Sidebar.tsx +223 -0
  147. package/src/components/navigation/SidebarItem.tsx +160 -0
  148. package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
  149. package/src/components/navigation/TextLink.tsx +71 -0
  150. package/src/components/navigation/generated/default-variant-keys.ts +12 -0
  151. package/src/components/navigation/generated/variant-keys.ts +13 -0
  152. package/src/components/overlay/AIChatInput.tsx +5 -0
  153. package/src/components/overlay/AIChatMessage.tsx +6 -0
  154. package/src/components/overlay/AlertDialog.tsx +145 -0
  155. package/src/components/overlay/ChatPanel.tsx +180 -0
  156. package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
  157. package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
  158. package/src/components/overlay/Drawer.tsx +131 -0
  159. package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
  160. package/src/components/overlay/FloatingPanel.tsx +90 -0
  161. package/src/components/overlay/HoverCard.tsx +36 -0
  162. package/src/components/overlay/MediaLightbox.tsx +403 -0
  163. package/src/components/overlay/MediaPickerDialog.tsx +198 -0
  164. package/src/components/overlay/Modal.tsx +103 -0
  165. package/src/components/overlay/OnboardingFlow.tsx +172 -0
  166. package/src/components/overlay/Popover.tsx +36 -0
  167. package/src/components/overlay/ShareModal.tsx +324 -0
  168. package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
  169. package/src/components/overlay/Tooltip.tsx +130 -0
  170. package/src/components/overlay/generated/default-variant-keys.ts +14 -0
  171. package/src/components/overlay/generated/variant-keys.ts +17 -0
  172. package/src/components/patterns/BlogTemplate.tsx +46 -0
  173. package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
  174. package/src/components/patterns/DocsTemplate.tsx +41 -0
  175. package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
  176. package/src/components/patterns/OnboardingTemplate.tsx +32 -0
  177. package/src/components/patterns/PricingTemplate.tsx +106 -0
  178. package/src/globals.css +173 -22
  179. package/src/index.ts +177 -76
  180. package/tailwind-theme-extend.cjs +48 -3
  181. package/design/atoms-metadata.json +0 -82
  182. package/design/molecules-metadata.json +0 -130
  183. package/design/organisms-metadata.json +0 -38
  184. package/design/templates-metadata.json +0 -38
  185. package/src/components/atoms/Avatar.tsx +0 -57
  186. package/src/components/atoms/Select.tsx +0 -28
  187. package/src/components/atoms/generated/default-variant-keys.ts +0 -36
  188. package/src/components/molecules/AIChatInput.tsx +0 -140
  189. package/src/components/molecules/AIChatMessage.tsx +0 -109
  190. package/src/components/molecules/Accordion.tsx +0 -99
  191. package/src/components/molecules/Calendar.tsx +0 -60
  192. package/src/components/molecules/Carousel.tsx +0 -261
  193. package/src/components/molecules/Command.tsx +0 -152
  194. package/src/components/molecules/FilterButton.tsx +0 -133
  195. package/src/components/molecules/HoverCard.tsx +0 -29
  196. package/src/components/molecules/Modal.tsx +0 -66
  197. package/src/components/molecules/Popover.tsx +0 -31
  198. package/src/components/molecules/ProgressWidget.tsx +0 -40
  199. package/src/components/molecules/Resizable.tsx +0 -47
  200. package/src/components/molecules/ScrollArea.tsx +0 -48
  201. package/src/components/molecules/SidebarItem.tsx +0 -134
  202. package/src/components/molecules/Toast.tsx +0 -57
  203. package/src/components/molecules/Tooltip.tsx +0 -30
  204. package/src/components/molecules/generated/default-variant-keys.ts +0 -22
  205. package/src/components/molecules/generated/variant-keys.ts +0 -33
  206. package/src/components/organisms/CommandPalette.tsx +0 -58
  207. package/src/components/organisms/FloatingPanel.tsx +0 -46
  208. package/src/components/organisms/ShareModal.tsx +0 -182
  209. package/src/components/organisms/ToastProvider.tsx +0 -49
  210. /package/src/components/{atoms → display}/Kbd.tsx +0 -0
  211. /package/src/components/{atoms → display}/Separator.tsx +0 -0
  212. /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
  213. /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
  214. /package/src/components/{atoms → inputs}/Button.tsx +0 -0
  215. /package/src/components/{atoms → inputs}/Label.tsx +0 -0
  216. /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
  217. /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
  218. /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
  219. /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
  220. /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
  221. /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
  222. /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
  223. /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
  224. /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 "lucide-react"
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
- <SheetPortal>
61
- <SheetOverlay />
62
- <SheetPrimitive.Content
63
- ref={ref}
64
- className={cn(sheetVariants({ side }), className)}
65
- {...props}
66
- >
67
- <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity 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">
68
- <X className="h-4 w-4" />
69
- <span className="sr-only">Close</span>
70
- </SheetPrimitive.Close>
71
- {children}
72
- </SheetPrimitive.Content>
73
- </SheetPortal>
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-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
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>