@dyrected/admin 2.0.0 → 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 +46 -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 +73 -44
  66. package/tailwind.config.ts +1 -0
  67. package/vite.config.ts +0 -1
@@ -80,14 +80,14 @@ export function FormFieldRenderer({ schema, basePath, control, collection }: For
80
80
 
81
81
  if (schema.type === "object") {
82
82
  return (
83
- <div className="left-accent space-y-6">
84
- <div className="flex items-center gap-2 mb-2">
85
- <h4 className="font-bold text-sm text-foreground/80 tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
83
+ <div className="dy-left-accent dy-space-y-6">
84
+ <div className="dy-flex dy-items-center dy-gap-2 dy-mb-2">
85
+ <h4 className="dy-font-bold dy-text-sm dy-text-foreground/80 dy-tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
86
86
  {schema.admin?.description && (
87
- <p className="text-[10px] text-muted-foreground/50 italic">{schema.admin.description}</p>
87
+ <p className="dy-text-[10px] dy-text-muted-foreground/50 dy-italic">{schema.admin.description}</p>
88
88
  )}
89
89
  </div>
90
- <div className="space-y-6">
90
+ <div className="dy-space-y-6">
91
91
  {schema.fields?.map(subField => (
92
92
  <FormFieldRenderer key={subField.name} schema={subField} basePath={fullPath} control={control} collection={collection} />
93
93
  ))}
@@ -113,24 +113,24 @@ export function FormFieldRenderer({ schema, basePath, control, collection }: For
113
113
  render={({ field: formField }: { field: any }) => (
114
114
  <FormItem className={cn(
115
115
  isBoolean
116
- ? "flex flex-row items-center justify-between rounded-xl border border-border/40 p-4 bg-white/50 shadow-sm space-y-0"
117
- : "space-y-3"
116
+ ? "dy-flex dy-flex-row dy-items-center dy-justify-between dy-rounded-xl dy-border dy-border-border/40 dy-p-4 dy-bg-white/50 dy-shadow-sm dy-space-y-0"
117
+ : "dy-space-y-3"
118
118
  )}>
119
- <div className={cn(isBoolean ? "space-y-1" : "flex items-center gap-2 mb-1")}>
120
- <FormLabel className="text-sm font-semibold text-foreground/80 cursor-pointer">
119
+ <div className={cn(isBoolean ? "dy-space-y-1" : "dy-flex dy-items-center dy-gap-2 dy-mb-1")}>
120
+ <FormLabel className="dy-text-sm dy-font-semibold dy-text-foreground/80 dy-cursor-pointer">
121
121
  {schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}
122
- {schema.required && <span className="text-destructive ml-1">*</span>}
122
+ {schema.required && <span className="dy-text-destructive dy-ml-1">*</span>}
123
123
  </FormLabel>
124
124
  {schema.admin?.description && (
125
125
  <p className={cn(
126
- "text-muted-foreground/60 italic",
127
- isBoolean ? "text-[11px] leading-tight" : "text-[11px] leading-relaxed"
126
+ "dy-text-muted-foreground/60 dy-italic",
127
+ isBoolean ? "dy-text-[11px] dy-leading-tight" : "dy-text-[11px] dy-leading-relaxed"
128
128
  )}>
129
129
  {schema.admin.description}
130
130
  </p>
131
131
  )}
132
132
  {!isBoolean && schema.unique && (
133
- <span className="inline-flex items-center rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary ring-1 ring-inset ring-primary/10">
133
+ <span className="dy-inline-flex dy-items-center dy-rounded-full dy-bg-primary/10 dy-px-1.5 dy-py-0.5 dy-text-[10px] dy-font-medium dy-text-primary dy-ring-1 dy-ring-inset dy-ring-primary/10">
134
134
  Unique
135
135
  </span>
136
136
  )}
@@ -139,9 +139,9 @@ export function FormFieldRenderer({ schema, basePath, control, collection }: For
139
139
  <FieldRenderer schema={schema} field={formField} collection={collection} context={{ user, schemas, siblingData: conditionData }} />
140
140
  </FormControl>
141
141
  {!isBoolean && schema.admin?.description && (
142
- <p className="text-[11px] text-muted-foreground/70 leading-relaxed italic">{schema.admin.description}</p>
142
+ <p className="dy-text-[11px] dy-text-muted-foreground/70 dy-leading-relaxed dy-italic">{schema.admin.description}</p>
143
143
  )}
144
- <FormMessage className="text-xs font-medium" />
144
+ <FormMessage className="dy-text-xs dy-font-medium" />
145
145
  </FormItem>
146
146
  )}
147
147
  />
@@ -179,45 +179,45 @@ function ArrayFieldRenderer({ schema, basePath, control, collection }: { schema:
179
179
  }
180
180
 
181
181
  return (
182
- <div className="space-y-6 transition-all py-6">
183
- <div className="flex justify-between items-end pb-2">
184
- <div className="space-y-1">
185
- <div className="flex items-center gap-2">
186
- <Layers className="h-4 w-4 text-primary" />
187
- <h4 className="font-serif font-bold text-base text-foreground tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
182
+ <div className="dy-space-y-6 dy-transition-all dy-py-6">
183
+ <div className="dy-flex dy-justify-between dy-items-end dy-pb-2">
184
+ <div className="dy-space-y-1">
185
+ <div className="dy-flex dy-items-center dy-gap-2">
186
+ <Layers className="dy-h-4 dy-w-4 dy-text-primary" />
187
+ <h4 className="dy-font-serif dy-font-bold dy-text-base dy-text-foreground dy-tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
188
188
  </div>
189
189
  {schema.admin?.description && (
190
- <p className="text-[11px] text-muted-foreground/60 italic leading-relaxed">{schema.admin.description}</p>
190
+ <p className="dy-text-[11px] dy-text-muted-foreground/60 dy-italic dy-leading-relaxed">{schema.admin.description}</p>
191
191
  )}
192
192
  </div>
193
- <div className="flex items-center gap-2">
193
+ <div className="dy-flex dy-items-center dy-gap-2">
194
194
  <Button
195
195
  type="button"
196
196
  variant="outline"
197
197
  size="sm"
198
- className="h-9 text-[11px] font-bold rounded-xl border-primary/20 hover:bg-primary/5 hover:text-primary transition-all shadow-sm"
198
+ className="dy-h-9 dy-text-[11px] dy-font-bold dy-rounded-xl dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary dy-transition-all dy-shadow-sm"
199
199
  onClick={() => append(buildDefaultValues(schema.fields || [], {}))}
200
200
  >
201
- <Plus className="w-3.5 h-3.5 mr-1.5" />
201
+ <Plus className="dy-w-3.5 dy-h-3.5 dy-mr-1.5" />
202
202
  Add Item
203
203
  </Button>
204
204
  </div>
205
205
  </div>
206
206
 
207
- <div className="space-y-8 pl-0 border-l border-muted/30">
207
+ <div className="dy-space-y-8 dy-pl-0 dy-border-l dy-border-muted/30">
208
208
  {fields.map((item, index) => (
209
- <div key={item.id} className="relative group animate-in slide-in-from-left-2 duration-300">
210
- <div className="bg-muted/5 left-accent transition-all relative">
209
+ <div key={item.id} className="dy-relative dy-group dy-animate-in dy-slide-in-from-left-2 dy-duration-300">
210
+ <div className="dy-bg-muted/5 dy-left-accent dy-transition-all dy-relative">
211
211
  <Button
212
212
  type="button"
213
213
  variant="ghost"
214
214
  size="icon"
215
- className="absolute top-4 right-4 h-8 w-8 text-muted-foreground/20 hover:text-destructive hover:bg-destructive/10 rounded-xl opacity-0 group-hover:opacity-100 transition-all"
215
+ className="dy-absolute dy-top-4 dy-right-4 dy-h-8 dy-w-8 dy-text-muted-foreground/20 hover:dy-text-destructive hover:dy-bg-destructive/10 dy-rounded-xl dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-all"
216
216
  onClick={() => remove(index)}
217
217
  >
218
- <Trash2 className="w-4 h-4" />
218
+ <Trash2 className="dy-w-4 dy-h-4" />
219
219
  </Button>
220
- <div className="space-y-6">
220
+ <div className="dy-space-y-6">
221
221
  {schema.fields?.map(subField => (
222
222
  <FormFieldRenderer
223
223
  key={subField.name}
@@ -233,11 +233,11 @@ function ArrayFieldRenderer({ schema, basePath, control, collection }: { schema:
233
233
  ))}
234
234
 
235
235
  {fields.length === 0 && (
236
- <div className="flex flex-col items-center justify-center py-12 border-2 border-dashed border-muted rounded-3xl bg-muted/5 space-y-3">
237
- <div className="p-3 bg-muted rounded-full">
238
- <Layers className="h-6 w-6 text-muted-foreground/40" />
236
+ <div className="dy-flex dy-flex-col dy-items-center dy-justify-center dy-py-12 dy-border-2 dy-border-dashed dy-border-muted dy-rounded-3xl dy-bg-muted/5 dy-space-y-3">
237
+ <div className="dy-p-3 dy-bg-muted dy-rounded-full">
238
+ <Layers className="dy-h-6 dy-w-6 dy-text-muted-foreground/40" />
239
239
  </div>
240
- <p className="text-xs font-medium text-muted-foreground/50">No items added yet</p>
240
+ <p className="dy-text-xs dy-font-medium dy-text-muted-foreground/50">No items added yet</p>
241
241
  </div>
242
242
  )}
243
243
 
@@ -245,10 +245,10 @@ function ArrayFieldRenderer({ schema, basePath, control, collection }: { schema:
245
245
  type="button"
246
246
  variant="outline"
247
247
  size="sm"
248
- className="w-full h-10 text-xs font-bold rounded-2xl border-dashed border-primary/20 hover:bg-primary/5 hover:text-primary transition-all shadow-sm"
248
+ className="dy-w-full dy-h-10 dy-text-xs dy-font-bold dy-rounded-2xl dy-border-dashed dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary dy-transition-all dy-shadow-sm"
249
249
  onClick={() => append(buildDefaultValues(schema.fields || [], {}))}
250
250
  >
251
- <Plus className="w-4 h-4 mr-2" />
251
+ <Plus className="dy-w-4 dy-h-4 dy-mr-2" />
252
252
  Add Item
253
253
  </Button>
254
254
  </div>
@@ -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
  )