@dyrected/admin 2.0.1 → 2.4.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/CHANGELOG.md +29 -0
- package/package.json +4 -4
- package/scripts/prefix-tailwind-precision.py +98 -0
- package/scripts/prefix-tailwind.py +67 -0
- package/src/components/auth/auth-gate.tsx +4 -4
- package/src/components/error-boundary.tsx +4 -4
- package/src/components/forms/fields/block-builder.tsx +24 -24
- package/src/components/forms/fields/date-picker.tsx +7 -7
- package/src/components/forms/fields/json-editor.tsx +5 -5
- package/src/components/forms/fields/media-picker.tsx +39 -39
- package/src/components/forms/fields/multi-select.tsx +12 -12
- package/src/components/forms/fields/radio-field.tsx +8 -8
- package/src/components/forms/fields/relationship-picker.tsx +13 -13
- package/src/components/forms/fields/rich-text-editor.tsx +22 -22
- package/src/components/forms/fields/select-field.tsx +3 -3
- package/src/components/forms/form-engine.tsx +3 -3
- package/src/components/forms/form-field-renderer.tsx +37 -37
- package/src/components/layout/admin-shell.tsx +60 -60
- package/src/components/live-preview/LivePreviewPane.tsx +14 -14
- package/src/components/media/focal-point-picker.tsx +9 -9
- package/src/components/media/media-card.tsx +10 -10
- package/src/components/media/media-grid.tsx +3 -3
- package/src/components/media/media-library-dialog.tsx +105 -105
- package/src/components/ui/badge.tsx +5 -5
- package/src/components/ui/button.tsx +11 -11
- package/src/components/ui/calendar.tsx +36 -36
- package/src/components/ui/card.tsx +6 -6
- package/src/components/ui/checkbox.tsx +3 -3
- package/src/components/ui/command.tsx +12 -12
- package/src/components/ui/data-table.tsx +18 -18
- package/src/components/ui/dialog.tsx +9 -9
- package/src/components/ui/dropdown-menu.tsx +16 -16
- package/src/components/ui/form.tsx +4 -4
- package/src/components/ui/input.tsx +3 -3
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/page-header.tsx +6 -6
- package/src/components/ui/pagination.tsx +6 -6
- package/src/components/ui/popover.tsx +1 -1
- package/src/components/ui/progress.tsx +2 -2
- package/src/components/ui/radio-group.tsx +4 -4
- package/src/components/ui/render-cell.tsx +16 -16
- package/src/components/ui/scroll-area.tsx +6 -6
- package/src/components/ui/select.tsx +14 -14
- package/src/components/ui/separator.tsx +2 -2
- package/src/components/ui/sheet.tsx +13 -13
- package/src/components/ui/sidebar.tsx +60 -60
- package/src/components/ui/skeleton.tsx +1 -1
- package/src/components/ui/sonner.tsx +1 -1
- package/src/components/ui/switch.tsx +2 -2
- package/src/components/ui/table.tsx +7 -7
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/textarea.tsx +1 -1
- package/src/components/ui/toggle.tsx +6 -6
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/index.css +27 -27
- package/src/index.tsx +4 -4
- package/src/lib/utils.ts +7 -3
- package/src/pages/auth/first-user-page.tsx +18 -18
- package/src/pages/auth/login-page.tsx +14 -14
- package/src/pages/collections/edit-page.tsx +37 -37
- package/src/pages/collections/list-page.tsx +23 -23
- package/src/pages/dashboard/dashboard.tsx +49 -49
- package/src/pages/globals/editor-page.tsx +13 -13
- package/src/pages/media/media-page.tsx +106 -106
- package/src/pages/setup/setup-prompt.tsx +48 -48
- package/tailwind.config.ts +1 -0
- package/vite.config.ts +0 -1
|
@@ -48,23 +48,23 @@ function NavItem({
|
|
|
48
48
|
to={to}
|
|
49
49
|
onClick={onClick}
|
|
50
50
|
className={cn(
|
|
51
|
-
"group flex items-center gap-3 rounded-md px-3 py-2 text-[13px] font-medium transition-all duration-150",
|
|
52
|
-
collapsed ? "justify-center px-2" : "",
|
|
51
|
+
"dy-group dy-flex dy-items-center dy-gap-3 dy-rounded-md dy-px-3 dy-py-2 dy-text-[13px] dy-font-medium dy-transition-all dy-duration-150",
|
|
52
|
+
collapsed ? "dy-justify-center dy-px-2" : "",
|
|
53
53
|
active
|
|
54
|
-
? "bg-primary text-primary-foreground"
|
|
55
|
-
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
54
|
+
? "dy-bg-primary dy-text-primary-foreground"
|
|
55
|
+
: "dy-text-muted-foreground hover:dy-bg-accent hover:dy-text-foreground"
|
|
56
56
|
)}
|
|
57
57
|
>
|
|
58
58
|
<Icon
|
|
59
59
|
className={cn(
|
|
60
|
-
"shrink-0 transition-colors",
|
|
61
|
-
collapsed ? "h-[17px] w-[17px]" : "h-[15px] w-[15px]",
|
|
62
|
-
active ? "text-background" : "text-muted-foreground group-hover:text-foreground"
|
|
60
|
+
"dy-shrink-0 dy-transition-colors",
|
|
61
|
+
collapsed ? "dy-h-[17px] dy-w-[17px]" : "dy-h-[15px] dy-w-[15px]",
|
|
62
|
+
active ? "dy-text-background" : "dy-text-muted-foreground dy-group-hover:dy-text-foreground"
|
|
63
63
|
)}
|
|
64
64
|
/>
|
|
65
|
-
{!collapsed && <span className="truncate">{label}</span>}
|
|
65
|
+
{!collapsed && <span className="dy-truncate">{label}</span>}
|
|
66
66
|
{!collapsed && active && (
|
|
67
|
-
<ChevronRight className="ml-auto h-3.5 w-3.5 opacity-50 shrink-0" />
|
|
67
|
+
<ChevronRight className="dy-ml-auto dy-h-3.5 dy-w-3.5 dy-opacity-50 dy-shrink-0" />
|
|
68
68
|
)}
|
|
69
69
|
</Link>
|
|
70
70
|
)
|
|
@@ -88,29 +88,29 @@ function NavGroup({
|
|
|
88
88
|
|
|
89
89
|
if (collapsed) {
|
|
90
90
|
return (
|
|
91
|
-
<div className="space-y-1">
|
|
92
|
-
<div className="my-2 mx-3 h-px bg-border" />
|
|
91
|
+
<div className="dy-space-y-1">
|
|
92
|
+
<div className="dy-my-2 dy-mx-3 dy-h-px dy-bg-border" />
|
|
93
93
|
{children}
|
|
94
94
|
</div>
|
|
95
95
|
)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
return (
|
|
99
|
-
<div className="space-y-1">
|
|
99
|
+
<div className="dy-space-y-1">
|
|
100
100
|
<button
|
|
101
101
|
onClick={() => setExpanded(!expanded)}
|
|
102
|
-
className="flex w-full items-center justify-between px-3 mt-4 mb-1 group"
|
|
102
|
+
className="dy-flex dy-w-full dy-items-center dy-justify-between dy-px-3 dy-mt-4 dy-mb-1 dy-group"
|
|
103
103
|
>
|
|
104
|
-
<span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/40 group-hover:text-muted-foreground/60 transition-colors">
|
|
104
|
+
<span className="dy-text-[10px] dy-font-semibold dy-uppercase dy-tracking-widest dy-text-muted-foreground/40 dy-group-hover:dy-text-muted-foreground/60 dy-transition-colors">
|
|
105
105
|
{label}
|
|
106
106
|
</span>
|
|
107
107
|
{expanded ? (
|
|
108
|
-
<ChevronDown className="h-3 w-3 text-muted-foreground/30 group-hover:text-muted-foreground/50" />
|
|
108
|
+
<ChevronDown className="dy-h-3 dy-w-3 dy-text-muted-foreground/30 dy-group-hover:dy-text-muted-foreground/50" />
|
|
109
109
|
) : (
|
|
110
|
-
<ChevronRight className="h-3 w-3 text-muted-foreground/30 group-hover:text-muted-foreground/50" />
|
|
110
|
+
<ChevronRight className="dy-h-3 dy-w-3 dy-text-muted-foreground/30 dy-group-hover:dy-text-muted-foreground/50" />
|
|
111
111
|
)}
|
|
112
112
|
</button>
|
|
113
|
-
<div className={cn("space-y-0.5 overflow-hidden transition-all duration-200", expanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0")}>
|
|
113
|
+
<div className={cn("dy-space-y-0.5 dy-overflow-hidden dy-transition-all dy-duration-200", expanded ? "dy-max-h-[1000px] dy-opacity-100" : "dy-max-h-0 dy-opacity-0")}>
|
|
114
114
|
{children}
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
@@ -146,42 +146,42 @@ function SidebarInner({
|
|
|
146
146
|
|
|
147
147
|
const groupLabel = (text: string) =>
|
|
148
148
|
!collapsed ? (
|
|
149
|
-
<p className="px-3 mb-1.5 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/50">
|
|
149
|
+
<p className="dy-px-3 dy-mb-1.5 dy-text-[10px] dy-font-semibold dy-uppercase dy-tracking-widest dy-text-muted-foreground/50">
|
|
150
150
|
{text}
|
|
151
151
|
</p>
|
|
152
152
|
) : (
|
|
153
|
-
<div className="my-2 mx-3 h-px bg-border" />
|
|
153
|
+
<div className="dy-my-2 dy-mx-3 dy-h-px dy-bg-border" />
|
|
154
154
|
)
|
|
155
155
|
|
|
156
156
|
const branding = schemas?.admin?.branding;
|
|
157
157
|
|
|
158
158
|
return (
|
|
159
|
-
<div className="flex flex-col min-h-screen admin-ui">
|
|
159
|
+
<div className="dy-flex dy-flex-col dy-min-h-screen dy-admin-ui">
|
|
160
160
|
{/* Logo */}
|
|
161
161
|
<div
|
|
162
162
|
className={cn(
|
|
163
|
-
"flex items-center h-14 shrink-0 transition-all",
|
|
164
|
-
collapsed ? "justify-center px-2" : "gap-2.5 px-4"
|
|
163
|
+
"dy-flex dy-items-center dy-h-14 dy-shrink-0 dy-transition-all",
|
|
164
|
+
collapsed ? "dy-justify-center dy-px-2" : "dy-gap-2.5 dy-px-4"
|
|
165
165
|
)}
|
|
166
166
|
>
|
|
167
167
|
<div>
|
|
168
168
|
{!isEmbedded && (
|
|
169
169
|
<>
|
|
170
170
|
{branding?.logo || branding?.logoMark ? (
|
|
171
|
-
<div className="h-7 w-7 flex items-center justify-center shrink-0">
|
|
171
|
+
<div className="dy-h-7 dy-w-7 dy-flex dy-items-center dy-justify-center dy-shrink-0">
|
|
172
172
|
<img
|
|
173
173
|
src={getMediaUrl(collapsed ? (branding.logoMark || branding.logo) : (branding.logo || branding.logoMark), client?.getBaseUrl() || "")}
|
|
174
174
|
alt="Logo"
|
|
175
|
-
className="max-h-full max-w-full object-contain"
|
|
175
|
+
className="dy-max-h-full dy-max-w-full dy-object-contain"
|
|
176
176
|
/>
|
|
177
177
|
</div>
|
|
178
178
|
) : (
|
|
179
|
-
<div className="h-7 w-auto flex items-center justify-center shrink-0">
|
|
180
|
-
<img src={logo} alt="Dyrected" className="h-8 w-auto" />
|
|
179
|
+
<div className="dy-h-7 dy-w-auto dy-flex dy-items-center dy-justify-center dy-shrink-0">
|
|
180
|
+
<img src={logo} alt="Dyrected" className="dy-h-8 dy-w-auto" />
|
|
181
181
|
</div>
|
|
182
182
|
)}
|
|
183
183
|
{!collapsed && (
|
|
184
|
-
<span className="font-serif text-lg tracking-tight text-foreground flex-1 truncate">
|
|
184
|
+
<span className="dy-font-serif dy-text-lg dy-tracking-tight dy-text-foreground dy-flex-1 dy-truncate">
|
|
185
185
|
{branding?.titleSuffix?.replace(/^- /, '') || ''}
|
|
186
186
|
</span>
|
|
187
187
|
)}
|
|
@@ -194,7 +194,7 @@ function SidebarInner({
|
|
|
194
194
|
|
|
195
195
|
|
|
196
196
|
{/* Nav */}
|
|
197
|
-
<nav className="flex-1 overflow-y-auto py-4 px-2 space-y-4">
|
|
197
|
+
<nav className="dy-flex-1 dy-overflow-y-auto dy-py-4 dy-px-2 dy-space-y-4">
|
|
198
198
|
<div>
|
|
199
199
|
<NavItem
|
|
200
200
|
to="/"
|
|
@@ -226,9 +226,9 @@ function SidebarInner({
|
|
|
226
226
|
{(isLoading || collections.filter((c: any) => !c.upload).length > 0) && (
|
|
227
227
|
<div>
|
|
228
228
|
{isLoading ? (
|
|
229
|
-
<div className="space-y-1 px-1">
|
|
229
|
+
<div className="dy-space-y-1 dy-px-1">
|
|
230
230
|
{[1, 2, 3].map((i) => (
|
|
231
|
-
<div key={i} className={cn("h-8 rounded-md bg-muted/60 animate-pulse", collapsed ? "mx-1" : "mx-2")} />
|
|
231
|
+
<div key={i} className={cn("dy-h-8 dy-rounded-md dy-bg-muted/60 dy-animate-pulse", collapsed ? "dy-mx-1" : "dy-mx-2")} />
|
|
232
232
|
))}
|
|
233
233
|
</div>
|
|
234
234
|
) : (() => {
|
|
@@ -251,13 +251,13 @@ function SidebarInner({
|
|
|
251
251
|
const renderCollectionItem = (col: any) => {
|
|
252
252
|
const isReadOnly = col.access?.read && !col.access?.create && !col.access?.update && !col.access?.delete
|
|
253
253
|
const navLabel = (
|
|
254
|
-
<div className="flex items-center gap-1.5 min-w-0">
|
|
255
|
-
<span className="truncate">{col.labels?.plural ?? col.label ?? col.slug}</span>
|
|
254
|
+
<div className="dy-flex dy-items-center dy-gap-1.5 dy-min-w-0">
|
|
255
|
+
<span className="dy-truncate">{col.labels?.plural ?? col.label ?? col.slug}</span>
|
|
256
256
|
{!collapsed && (
|
|
257
|
-
<div className="flex gap-1 shrink-0">
|
|
258
|
-
{col.auth && <Shield className="h-4 w-4 text-primary/70" />}
|
|
259
|
-
{col.shared && <Share2 className="h-4 w-4 text-purple-500/70" />}
|
|
260
|
-
{isReadOnly && <Lock className="h-4 w-4 text-muted-foreground/40" />}
|
|
257
|
+
<div className="dy-flex dy-gap-1 dy-shrink-0">
|
|
258
|
+
{col.auth && <Shield className="dy-h-4 dy-w-4 dy-text-primary/70" />}
|
|
259
|
+
{col.shared && <Share2 className="dy-h-4 dy-w-4 dy-text-purple-500/70" />}
|
|
260
|
+
{isReadOnly && <Lock className="dy-h-4 dy-w-4 dy-text-muted-foreground/40" />}
|
|
261
261
|
</div>
|
|
262
262
|
)}
|
|
263
263
|
</div>
|
|
@@ -277,7 +277,7 @@ function SidebarInner({
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
return (
|
|
280
|
-
<div className="space-y-1">
|
|
280
|
+
<div className="dy-space-y-1">
|
|
281
281
|
{/* Grouped sections */}
|
|
282
282
|
{Array.from(groups.entries()).map(([groupName, cols]) => (
|
|
283
283
|
<NavGroup key={groupName} label={groupName} collapsed={collapsed} defaultExpanded={true}>
|
|
@@ -301,7 +301,7 @@ function SidebarInner({
|
|
|
301
301
|
{globals.length > 0 && (
|
|
302
302
|
<div>
|
|
303
303
|
{groupLabel("Configuration")}
|
|
304
|
-
<div className="space-y-0.5">
|
|
304
|
+
<div className="dy-space-y-0.5">
|
|
305
305
|
{globals.map((glob: any) => (
|
|
306
306
|
<NavItem
|
|
307
307
|
key={glob.slug}
|
|
@@ -319,7 +319,7 @@ function SidebarInner({
|
|
|
319
319
|
</nav>
|
|
320
320
|
|
|
321
321
|
{/* Footer */}
|
|
322
|
-
<div className="border-t border-border px-2 py-3 shrink-0 space-y-0.5">
|
|
322
|
+
<div className="dy-border-t dy-border-border dy-px-2 dy-py-3 dy-shrink-0 dy-space-y-0.5">
|
|
323
323
|
{/* Integration guide — always visible so embedded users can access the prompt */}
|
|
324
324
|
<NavItem
|
|
325
325
|
to="/setup"
|
|
@@ -335,11 +335,11 @@ function SidebarInner({
|
|
|
335
335
|
onClick={logout}
|
|
336
336
|
title={collapsed ? "Logout" : undefined}
|
|
337
337
|
className={cn(
|
|
338
|
-
"flex w-full items-center gap-3 rounded-md px-3 py-2 text-[13px] font-medium text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
339
|
-
collapsed ? "justify-center px-2" : ""
|
|
338
|
+
"dy-flex dy-w-full dy-items-center dy-gap-3 dy-rounded-md dy-px-3 dy-py-2 dy-text-[13px] dy-font-medium dy-text-muted-foreground dy-transition-colors hover:dy-bg-destructive/10 hover:dy-text-destructive",
|
|
339
|
+
collapsed ? "dy-justify-center dy-px-2" : ""
|
|
340
340
|
)}
|
|
341
341
|
>
|
|
342
|
-
<LogOut className="h-[15px] w-[15px] shrink-0" />
|
|
342
|
+
<LogOut className="dy-h-[15px] dy-w-[15px] dy-shrink-0" />
|
|
343
343
|
{!collapsed && <span>Logout</span>}
|
|
344
344
|
</button>
|
|
345
345
|
)}
|
|
@@ -347,21 +347,21 @@ function SidebarInner({
|
|
|
347
347
|
|
|
348
348
|
{/* Desktop Collapse Toggle at Bottom */}
|
|
349
349
|
{onToggleCollapse && !isEmbedded && (
|
|
350
|
-
<div className="mt-auto p-4 border-t border-border/40">
|
|
350
|
+
<div className="dy-mt-auto dy-p-4 dy-border-t dy-border-border/40">
|
|
351
351
|
<button
|
|
352
352
|
onClick={onToggleCollapse}
|
|
353
353
|
className={cn(
|
|
354
|
-
"w-full flex items-center gap-3 p-2.5 rounded-xl text-muted-foreground/60 hover:text-foreground hover:bg-accent/50 transition-all group/btn",
|
|
355
|
-
collapsed ? "justify-center" : "px-3"
|
|
354
|
+
"dy-w-full dy-flex dy-items-center dy-gap-3 dy-p-2.5 dy-rounded-xl dy-text-muted-foreground/60 hover:dy-text-foreground hover:dy-bg-accent/50 dy-transition-all dy-group/btn",
|
|
355
|
+
collapsed ? "dy-justify-center" : "dy-px-3"
|
|
356
356
|
)}
|
|
357
357
|
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
358
358
|
>
|
|
359
359
|
{collapsed ? (
|
|
360
|
-
<PanelLeftOpen className="h-5 w-5" />
|
|
360
|
+
<PanelLeftOpen className="dy-h-5 dy-w-5" />
|
|
361
361
|
) : (
|
|
362
362
|
<>
|
|
363
|
-
<PanelLeftClose className="h-5 w-5 group-hover/btn
|
|
364
|
-
<span className="text-sm font-medium text-[13px]">Collapse Sidebar</span>
|
|
363
|
+
<PanelLeftClose className="dy-h-5 dy-w-5 dy-group-hover/btn:dy--translate-x-0.5 dy-transition-transform" />
|
|
364
|
+
<span className="dy-text-sm dy-font-medium dy-text-[13px]">Collapse Sidebar</span>
|
|
365
365
|
</>
|
|
366
366
|
)}
|
|
367
367
|
</button>
|
|
@@ -411,12 +411,12 @@ export function AdminShell({
|
|
|
411
411
|
|
|
412
412
|
return (
|
|
413
413
|
<BrandingProvider>
|
|
414
|
-
<div className={cn("flex w-full relative", isEmbedded ? "h-full min-h-[600px]" : "min-h-screen")}>
|
|
414
|
+
<div className={cn("dy-flex dy-w-full dy-relative", isEmbedded ? "dy-h-full dy-min-h-[600px]" : "dy-min-h-screen")}>
|
|
415
415
|
{/* ... existing sidebar and main content ... */}
|
|
416
416
|
<aside
|
|
417
417
|
className={cn(
|
|
418
|
-
"hidden md:flex flex-col shrink-0 h-full border-r border-border bg-background transition-all duration-300 overflow-hidden",
|
|
419
|
-
collapsed ? "w-[56px]" : "w-[220px]"
|
|
418
|
+
"dy-hidden md:dy-flex dy-flex-col dy-shrink-0 dy-h-full dy-border-r dy-border-border dy-bg-background dy-transition-all dy-duration-300 dy-overflow-hidden",
|
|
419
|
+
collapsed ? "dy-w-[56px]" : "dy-w-[220px]"
|
|
420
420
|
)}
|
|
421
421
|
>
|
|
422
422
|
<SidebarInner
|
|
@@ -432,21 +432,21 @@ export function AdminShell({
|
|
|
432
432
|
|
|
433
433
|
{mobileOpen && (
|
|
434
434
|
<div
|
|
435
|
-
className="fixed inset-0 z-30 bg-black/30 md:hidden"
|
|
435
|
+
className="dy-fixed dy-inset-0 dy-z-30 dy-bg-black/30 md:dy-hidden"
|
|
436
436
|
onClick={() => setMobileOpen(false)}
|
|
437
437
|
/>
|
|
438
438
|
)}
|
|
439
439
|
<aside
|
|
440
440
|
className={cn(
|
|
441
|
-
"fixed top-0 left-0 z-40 h-full w-[220px] flex flex-col border-r border-border bg-background transition-transform duration-300 ease-in-out md:hidden",
|
|
442
|
-
mobileOpen ? "translate-x-0" : "
|
|
441
|
+
"dy-fixed dy-top-0 dy-left-0 dy-z-40 dy-h-full dy-w-[220px] dy-flex dy-flex-col dy-border-r dy-border-border dy-bg-background dy-transition-transform dy-duration-300 dy-ease-in-out md:dy-hidden",
|
|
442
|
+
mobileOpen ? "dy-translate-x-0" : "dy--translate-x-full"
|
|
443
443
|
)}
|
|
444
444
|
>
|
|
445
445
|
<button
|
|
446
446
|
onClick={() => setMobileOpen(false)}
|
|
447
|
-
className="absolute top-3.5 right-3 p-1.5 rounded-md text-muted-foreground hover:bg-muted transition-colors"
|
|
447
|
+
className="dy-absolute dy-top-3.5 dy-right-3 dy-p-1.5 dy-rounded-md dy-text-muted-foreground hover:dy-bg-muted dy-transition-colors"
|
|
448
448
|
>
|
|
449
|
-
<X className="h-4 w-4" />
|
|
449
|
+
<X className="dy-h-4 dy-w-4" />
|
|
450
450
|
</button>
|
|
451
451
|
<SidebarInner
|
|
452
452
|
schemas={schemas}
|
|
@@ -459,17 +459,17 @@ export function AdminShell({
|
|
|
459
459
|
/>
|
|
460
460
|
</aside>
|
|
461
461
|
|
|
462
|
-
<main className="flex-1 min-w-0 overflow-auto flex flex-col relative bg-background/50">
|
|
462
|
+
<main className="dy-flex-1 dy-min-w-0 dy-overflow-auto dy-flex dy-flex-col dy-relative dy-bg-background/50">
|
|
463
463
|
{/* Mobile Floating Toggle */}
|
|
464
464
|
<button
|
|
465
465
|
onClick={() => setMobileOpen(true)}
|
|
466
|
-
className="md:hidden fixed bottom-8 right-8 z-50 h-14 w-14 rounded-full bg-gradient-to-br from-primary to-primary/80 text-primary-foreground shadow-[0_8px_30px_rgb(0,0,0,0.3)] flex items-center justify-center transition-all active:scale-90 hover:scale-105 border border-white/20"
|
|
466
|
+
className="md:dy-hidden dy-fixed dy-bottom-8 dy-right-8 dy-z-50 dy-h-14 dy-w-14 dy-rounded-full dy-bg-gradient-to-br dy-from-primary dy-to-primary/80 dy-text-primary-foreground dy-shadow-[0_8px_30px_rgb(0,0,0,0.3)] dy-flex dy-items-center dy-justify-center dy-transition-all active:dy-scale-90 hover:dy-scale-105 dy-border dy-border-white/20"
|
|
467
467
|
aria-label="Open menu"
|
|
468
468
|
>
|
|
469
|
-
<Menu className="h-6 w-6" />
|
|
469
|
+
<Menu className="dy-h-6 dy-w-6" />
|
|
470
470
|
</button>
|
|
471
471
|
|
|
472
|
-
<div className="flex-1 py-6 px-4 lg:py-10 lg:px-6">
|
|
472
|
+
<div className="dy-flex-1 dy-py-6 dy-px-4 lg:dy-py-10 lg:dy-px-6">
|
|
473
473
|
{children}
|
|
474
474
|
</div>
|
|
475
475
|
</main>
|
|
@@ -51,16 +51,16 @@ export function LivePreviewPane({ previewUrl, data, mode = 'postMessage' }: Live
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
<div className="flex flex-col h-full bg-muted/20 border-l border-border/60">
|
|
55
|
-
<div className="flex items-center justify-between px-4 py-2 bg-white border-b border-border/40">
|
|
56
|
-
<div className="flex items-center gap-2">
|
|
54
|
+
<div className="dy-flex dy-flex-col dy-h-full dy-bg-muted/20 dy-border-l dy-border-border/60">
|
|
55
|
+
<div className="dy-flex dy-items-center dy-justify-between dy-px-4 dy-py-2 dy-bg-white dy-border-b dy-border-border/40">
|
|
56
|
+
<div className="dy-flex dy-items-center dy-gap-2">
|
|
57
57
|
<Button
|
|
58
58
|
variant="ghost"
|
|
59
59
|
size="icon"
|
|
60
60
|
className={`h-8 w-8 ${viewMode === 'desktop' ? 'bg-muted' : ''}`}
|
|
61
61
|
onClick={() => setViewMode('desktop')}
|
|
62
62
|
>
|
|
63
|
-
<Monitor className="h-4 w-4" />
|
|
63
|
+
<Monitor className="dy-h-4 dy-w-4" />
|
|
64
64
|
</Button>
|
|
65
65
|
<Button
|
|
66
66
|
variant="ghost"
|
|
@@ -68,12 +68,12 @@ export function LivePreviewPane({ previewUrl, data, mode = 'postMessage' }: Live
|
|
|
68
68
|
className={`h-8 w-8 ${viewMode === 'mobile' ? 'bg-muted' : ''}`}
|
|
69
69
|
onClick={() => setViewMode('mobile')}
|
|
70
70
|
>
|
|
71
|
-
<Smartphone className="h-4 w-4" />
|
|
71
|
+
<Smartphone className="dy-h-4 dy-w-4" />
|
|
72
72
|
</Button>
|
|
73
73
|
|
|
74
74
|
{viewMode === 'desktop' && (
|
|
75
|
-
<div className="flex items-center gap-1 ml-2 pl-2 border-l border-border/40">
|
|
76
|
-
<span className="text-[10px] font-bold text-muted-foreground/50 uppercase tracking-wider mr-1">Zoom</span>
|
|
75
|
+
<div className="dy-flex dy-items-center dy-gap-1 dy-ml-2 dy-pl-2 dy-border-l dy-border-border/40">
|
|
76
|
+
<span className="dy-text-[10px] dy-font-bold dy-text-muted-foreground/50 dy-uppercase dy-tracking-wider dy-mr-1">Zoom</span>
|
|
77
77
|
{[0.50, 0.75, 1.0].map((z) => (
|
|
78
78
|
<Button
|
|
79
79
|
key={z}
|
|
@@ -89,19 +89,19 @@ export function LivePreviewPane({ previewUrl, data, mode = 'postMessage' }: Live
|
|
|
89
89
|
)}
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
|
-
<div className="flex items-center gap-2">
|
|
93
|
-
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={reload}>
|
|
94
|
-
<RotateCcw className="h-3.5 w-3.5" />
|
|
92
|
+
<div className="dy-flex dy-items-center dy-gap-2">
|
|
93
|
+
<Button variant="ghost" size="icon" className="dy-h-8 dy-w-8" onClick={reload}>
|
|
94
|
+
<RotateCcw className="dy-h-3.5 dy-w-3.5" />
|
|
95
95
|
</Button>
|
|
96
|
-
<Button variant="ghost" size="icon" className="h-8 w-8" asChild>
|
|
96
|
+
<Button variant="ghost" size="icon" className="dy-h-8 dy-w-8" asChild>
|
|
97
97
|
<a href={previewUrl} target="_blank" rel="noreferrer">
|
|
98
|
-
<ExternalLink className="h-3.5 w-3.5" />
|
|
98
|
+
<ExternalLink className="dy-h-3.5 dy-w-3.5" />
|
|
99
99
|
</a>
|
|
100
100
|
</Button>
|
|
101
101
|
</div>
|
|
102
102
|
</div>
|
|
103
103
|
|
|
104
|
-
<div className="flex-1 flex items-center justify-center p-0 overflow-hidden bg-muted/5">
|
|
104
|
+
<div className="dy-flex-1 dy-flex dy-items-center dy-justify-center dy-p-0 dy-overflow-hidden dy-bg-muted/5">
|
|
105
105
|
<div
|
|
106
106
|
className={`bg-white shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 overflow-hidden border border-border/40 ${viewMode === 'mobile' ? 'w-[375px] h-[667px]' : 'w-full h-full'
|
|
107
107
|
}`}
|
|
@@ -109,7 +109,7 @@ export function LivePreviewPane({ previewUrl, data, mode = 'postMessage' }: Live
|
|
|
109
109
|
<iframe
|
|
110
110
|
ref={iframeRef}
|
|
111
111
|
src={previewUrl}
|
|
112
|
-
className="border-none transition-transform duration-300"
|
|
112
|
+
className="dy-border-none dy-transition-transform dy-duration-300"
|
|
113
113
|
style={viewMode === 'desktop' ? {
|
|
114
114
|
width: `${100 / zoom}%`,
|
|
115
115
|
height: `${100 / zoom}%`,
|
|
@@ -32,33 +32,33 @@ export function FocalPointPicker({ url, value, onChange, className }: FocalPoint
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className="space-y-2">
|
|
35
|
+
<div className="dy-space-y-2">
|
|
36
36
|
<div
|
|
37
37
|
ref={containerRef}
|
|
38
|
-
className={cn("relative cursor-crosshair overflow-hidden rounded-xl border border-border/40 bg-muted/20 group", className)}
|
|
38
|
+
className={cn("dy-relative dy-cursor-crosshair dy-overflow-hidden dy-rounded-xl dy-border dy-border-border/40 dy-bg-muted/20 dy-group", className)}
|
|
39
39
|
onClick={handleClick}
|
|
40
40
|
>
|
|
41
41
|
<img
|
|
42
42
|
src={url}
|
|
43
43
|
alt="Focal point picker"
|
|
44
|
-
className="w-full h-auto pointer-events-none select-none max-h-[400px] object-contain bg-checkered"
|
|
44
|
+
className="dy-w-full dy-h-auto dy-pointer-events-none dy-select-none dy-max-h-[400px] dy-object-contain dy-bg-checkered"
|
|
45
45
|
/>
|
|
46
46
|
|
|
47
47
|
{/* Focal point indicator */}
|
|
48
48
|
<div
|
|
49
|
-
className="absolute w-8 h-8
|
|
49
|
+
className="dy-absolute dy-w-8 dy-h-8 dy--ml-4 dy--mt-4 dy-border-2 dy-border-white dy-rounded-full dy-shadow-2xl dy-flex dy-items-center dy-justify-center dy-pointer-events-none dy-transition-all dy-duration-200 dy-ease-out"
|
|
50
50
|
style={{ left: `${focalPoint.x}%`, top: `${focalPoint.y}%` }}
|
|
51
51
|
>
|
|
52
|
-
<div className="w-1.5 h-1.5 bg-white rounded-full shadow-sm" />
|
|
53
|
-
<div className="absolute w-full h-px bg-white/40" />
|
|
54
|
-
<div className="absolute h-full w-px bg-white/40" />
|
|
52
|
+
<div className="dy-w-1.5 dy-h-1.5 dy-bg-white dy-rounded-full dy-shadow-sm" />
|
|
53
|
+
<div className="dy-absolute dy-w-full dy-h-px dy-bg-white/40" />
|
|
54
|
+
<div className="dy-absolute dy-h-full dy-w-px dy-bg-white/40" />
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
|
-
<div className="absolute bottom-3 left-3 bg-black/60 backdrop-blur-md px-2.5 py-1 rounded-lg text-[10px] text-white font-bold tracking-widest border border-white/10 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
57
|
+
<div className="dy-absolute dy-bottom-3 dy-left-3 dy-bg-black/60 dy-backdrop-blur-md dy-px-2.5 dy-py-1 dy-rounded-lg dy-text-[10px] dy-text-white dy-font-bold dy-tracking-widest dy-border dy-border-white/10 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
58
58
|
X: {focalPoint.x}% / Y: {focalPoint.y}%
|
|
59
59
|
</div>
|
|
60
60
|
</div>
|
|
61
|
-
<p className="text-[10px] text-muted-foreground font-medium px-1">
|
|
61
|
+
<p className="dy-text-[10px] dy-text-muted-foreground dy-font-medium dy-px-1">
|
|
62
62
|
Click on the image to set the focal point for smart cropping.
|
|
63
63
|
</p>
|
|
64
64
|
</div>
|
|
@@ -14,30 +14,30 @@ export function MediaCard({ item, baseUrl, onDelete, editPath }: MediaCardProps)
|
|
|
14
14
|
const url = getMediaUrl(item, baseUrl)
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<div className="group relative aspect-square rounded-2xl overflow-hidden bg-white border border-border/40 shadow-sm hover:shadow-xl hover:border-primary/20 transition-all duration-300">
|
|
17
|
+
<div className="dy-group dy-relative dy-aspect-square dy-rounded-2xl dy-overflow-hidden dy-bg-white dy-border dy-border-border/40 dy-shadow-sm hover:dy-shadow-xl hover:dy-border-primary/20 dy-transition-all dy-duration-300">
|
|
18
18
|
<img
|
|
19
19
|
src={url}
|
|
20
20
|
alt={item.filename}
|
|
21
|
-
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
21
|
+
className="dy-w-full dy-h-full dy-object-cover dy-transition-transform dy-duration-500 dy-group-hover:dy-scale-110"
|
|
22
22
|
/>
|
|
23
|
-
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-center justify-center gap-3 backdrop-blur-[2px]">
|
|
23
|
+
<div className="dy-absolute dy-inset-0 dy-bg-black/40 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-all dy-duration-300 dy-flex dy-items-center dy-justify-center dy-gap-3 dy-backdrop-blur-[2px]">
|
|
24
24
|
<Link to={editPath}>
|
|
25
|
-
<Button size="icon" variant="secondary" className="h-9 w-9 rounded-full bg-white/90 hover:bg-white text-foreground shadow-lg transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300">
|
|
26
|
-
<Pencil className="h-4 w-4" />
|
|
25
|
+
<Button size="icon" variant="secondary" className="dy-h-9 dy-w-9 dy-rounded-full dy-bg-white/90 hover:dy-bg-white dy-text-foreground dy-shadow-lg dy-transform dy-translate-y-2 dy-group-hover:dy-translate-y-0 dy-transition-transform dy-duration-300">
|
|
26
|
+
<Pencil className="dy-h-4 dy-w-4" />
|
|
27
27
|
</Button>
|
|
28
28
|
</Link>
|
|
29
29
|
<Button
|
|
30
30
|
size="icon"
|
|
31
31
|
variant="destructive"
|
|
32
|
-
className="h-9 w-9 rounded-full bg-destructive/90 hover:bg-destructive shadow-lg transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300 delay-75"
|
|
32
|
+
className="dy-h-9 dy-w-9 dy-rounded-full dy-bg-destructive/90 hover:dy-bg-destructive dy-shadow-lg dy-transform dy-translate-y-2 dy-group-hover:dy-translate-y-0 dy-transition-transform dy-duration-300 dy-delay-75"
|
|
33
33
|
onClick={() => onDelete(item.id)}
|
|
34
34
|
>
|
|
35
|
-
<Trash2 className="h-4 w-4" />
|
|
35
|
+
<Trash2 className="dy-h-4 dy-w-4" />
|
|
36
36
|
</Button>
|
|
37
37
|
</div>
|
|
38
|
-
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
39
|
-
<p className="text-[10px] text-white truncate font-medium">{item.filename}</p>
|
|
40
|
-
<p className="text-[8px] text-white/60 uppercase tracking-wider mt-0.5">{item.mimeType}</p>
|
|
38
|
+
<div className="dy-absolute dy-bottom-0 dy-left-0 dy-right-0 dy-p-3 dy-bg-gradient-to-t dy-from-black/80 dy-via-black/40 dy-to-transparent dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity dy-duration-300">
|
|
39
|
+
<p className="dy-text-[10px] dy-text-white dy-truncate dy-font-medium">{item.filename}</p>
|
|
40
|
+
<p className="dy-text-[8px] dy-text-white/60 dy-uppercase dy-tracking-wider dy-mt-0.5">{item.mimeType}</p>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
43
|
)
|
|
@@ -10,14 +10,14 @@ interface MediaGridProps {
|
|
|
10
10
|
export function MediaGrid({ items, baseUrl, onDelete, slug }: MediaGridProps) {
|
|
11
11
|
if (!items || items.length === 0) {
|
|
12
12
|
return (
|
|
13
|
-
<div className="flex flex-col items-center justify-center h-[300px] border-2 border-dashed border-border/60 rounded-3xl bg-muted/5">
|
|
14
|
-
<p className="text-sm text-muted-foreground font-medium">No media assets found</p>
|
|
13
|
+
<div className="dy-flex dy-flex-col dy-items-center dy-justify-center dy-h-[300px] dy-border-2 dy-border-dashed dy-border-border/60 dy-rounded-3xl dy-bg-muted/5">
|
|
14
|
+
<p className="dy-text-sm dy-text-muted-foreground dy-font-medium">No media assets found</p>
|
|
15
15
|
</div>
|
|
16
16
|
)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
|
|
20
|
+
<div className="dy-grid dy-grid-cols-2 sm:dy-grid-cols-3 md:dy-grid-cols-4 lg:dy-grid-cols-6 dy-gap-6">
|
|
21
21
|
{items.map((item) => (
|
|
22
22
|
<MediaCard
|
|
23
23
|
key={item.id}
|