@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +4 -4
  3. package/scripts/prefix-tailwind-precision.py +98 -0
  4. package/scripts/prefix-tailwind.py +67 -0
  5. package/src/components/auth/auth-gate.tsx +4 -4
  6. package/src/components/error-boundary.tsx +4 -4
  7. package/src/components/forms/fields/block-builder.tsx +24 -24
  8. package/src/components/forms/fields/date-picker.tsx +7 -7
  9. package/src/components/forms/fields/json-editor.tsx +5 -5
  10. package/src/components/forms/fields/media-picker.tsx +39 -39
  11. package/src/components/forms/fields/multi-select.tsx +12 -12
  12. package/src/components/forms/fields/radio-field.tsx +8 -8
  13. package/src/components/forms/fields/relationship-picker.tsx +13 -13
  14. package/src/components/forms/fields/rich-text-editor.tsx +22 -22
  15. package/src/components/forms/fields/select-field.tsx +3 -3
  16. package/src/components/forms/form-engine.tsx +3 -3
  17. package/src/components/forms/form-field-renderer.tsx +37 -37
  18. package/src/components/layout/admin-shell.tsx +60 -60
  19. package/src/components/live-preview/LivePreviewPane.tsx +14 -14
  20. package/src/components/media/focal-point-picker.tsx +9 -9
  21. package/src/components/media/media-card.tsx +10 -10
  22. package/src/components/media/media-grid.tsx +3 -3
  23. package/src/components/media/media-library-dialog.tsx +105 -105
  24. package/src/components/ui/badge.tsx +5 -5
  25. package/src/components/ui/button.tsx +11 -11
  26. package/src/components/ui/calendar.tsx +36 -36
  27. package/src/components/ui/card.tsx +6 -6
  28. package/src/components/ui/checkbox.tsx +3 -3
  29. package/src/components/ui/command.tsx +12 -12
  30. package/src/components/ui/data-table.tsx +18 -18
  31. package/src/components/ui/dialog.tsx +9 -9
  32. package/src/components/ui/dropdown-menu.tsx +16 -16
  33. package/src/components/ui/form.tsx +4 -4
  34. package/src/components/ui/input.tsx +3 -3
  35. package/src/components/ui/label.tsx +1 -1
  36. package/src/components/ui/page-header.tsx +6 -6
  37. package/src/components/ui/pagination.tsx +6 -6
  38. package/src/components/ui/popover.tsx +1 -1
  39. package/src/components/ui/progress.tsx +2 -2
  40. package/src/components/ui/radio-group.tsx +4 -4
  41. package/src/components/ui/render-cell.tsx +16 -16
  42. package/src/components/ui/scroll-area.tsx +6 -6
  43. package/src/components/ui/select.tsx +14 -14
  44. package/src/components/ui/separator.tsx +2 -2
  45. package/src/components/ui/sheet.tsx +13 -13
  46. package/src/components/ui/sidebar.tsx +60 -60
  47. package/src/components/ui/skeleton.tsx +1 -1
  48. package/src/components/ui/sonner.tsx +1 -1
  49. package/src/components/ui/switch.tsx +2 -2
  50. package/src/components/ui/table.tsx +7 -7
  51. package/src/components/ui/tabs.tsx +3 -3
  52. package/src/components/ui/textarea.tsx +1 -1
  53. package/src/components/ui/toggle.tsx +6 -6
  54. package/src/components/ui/tooltip.tsx +1 -1
  55. package/src/index.css +27 -27
  56. package/src/index.tsx +4 -4
  57. package/src/lib/utils.ts +7 -3
  58. package/src/pages/auth/first-user-page.tsx +18 -18
  59. package/src/pages/auth/login-page.tsx +14 -14
  60. package/src/pages/collections/edit-page.tsx +37 -37
  61. package/src/pages/collections/list-page.tsx +23 -23
  62. package/src/pages/dashboard/dashboard.tsx +49 -49
  63. package/src/pages/globals/editor-page.tsx +13 -13
  64. package/src/pages/media/media-page.tsx +106 -106
  65. package/src/pages/setup/setup-prompt.tsx +48 -48
  66. package/tailwind.config.ts +1 -0
  67. 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:-translate-x-0.5 transition-transform" />
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" : "-translate-x-full"
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 -ml-4 -mt-4 border-2 border-white rounded-full shadow-2xl flex items-center justify-center pointer-events-none transition-all duration-200 ease-out"
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}