@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @dyrected/admin
|
|
2
2
|
|
|
3
|
+
## 2.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Infrastructure standardization, MySQL adapter improvements, and SDK robustness testing.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @dyrected/core@2.4.0
|
|
13
|
+
- @dyrected/sdk@2.4.0
|
|
14
|
+
|
|
15
|
+
## 2.3.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Standardize database infrastructure and implement field promotion.
|
|
20
|
+
- **Field Promotion**: Added 'promoted' option to Collection fields to extract JSON data into native SQL columns for indexing and performance.
|
|
21
|
+
- **Lazy Migrations**: Added 'renameTo' support for seamless field renames without breaking existing data.
|
|
22
|
+
- **Auto-Seeding**: Standardized 'initialData' seeding logic across all adapters.
|
|
23
|
+
- **MySQL Adapter**: New robust MySQL adapter implementation.
|
|
24
|
+
- **Strict Filtering**: Improved query translation parity across all SQL-based adapters.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies
|
|
29
|
+
- @dyrected/core@2.3.0
|
|
30
|
+
- @dyrected/sdk@2.3.0
|
|
31
|
+
|
|
3
32
|
## 2.0.1
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dyrected/admin",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"tailwind-merge": "^3.5.0",
|
|
61
61
|
"tailwindcss-animate": "^1.0.7",
|
|
62
62
|
"zod": "^3.25.76",
|
|
63
|
-
"@dyrected/core": "^2.
|
|
64
|
-
"@dyrected/sdk": "^2.
|
|
63
|
+
"@dyrected/core": "^2.4.0",
|
|
64
|
+
"@dyrected/sdk": "^2.4.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@eslint/js": "^10.0.1",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"globals": "^17.5.0",
|
|
77
77
|
"postcss": "^8.5.14",
|
|
78
78
|
"tailwindcss": "^3.4.19",
|
|
79
|
-
"typescript": "
|
|
79
|
+
"typescript": "^5.7.3",
|
|
80
80
|
"typescript-eslint": "^8.58.2",
|
|
81
81
|
"vite": "^8.0.10",
|
|
82
82
|
"vite-plugin-dts": "^5.0.0",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
EXCLUDED_WORDS = {
|
|
5
|
+
"default", "ghost", "outline", "secondary", "destructive", "link", "icon",
|
|
6
|
+
"sm", "md", "lg", "xl", "2xl", "top", "bottom", "left", "right",
|
|
7
|
+
"expanded", "collapsed", "sidebar", "floating", "inset", "offcanvas", "none",
|
|
8
|
+
"sidebar_state", "top-right", "asChild", "className", "variant", "size", "side",
|
|
9
|
+
"true", "false", "item", "index", "key", "id", "name", "type", "value", "variant"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def prefix_single_class(c):
|
|
13
|
+
if not c or c.startswith(("dy-", "{", "$", "--", "http")):
|
|
14
|
+
return c
|
|
15
|
+
|
|
16
|
+
if ":" in c:
|
|
17
|
+
parts = c.split(":")
|
|
18
|
+
modifiers = parts[:-1]
|
|
19
|
+
base = parts[-1]
|
|
20
|
+
|
|
21
|
+
new_modifiers = []
|
|
22
|
+
for m in modifiers:
|
|
23
|
+
if m.startswith(("group", "peer")):
|
|
24
|
+
new_modifiers.append(f"dy-{m}")
|
|
25
|
+
else:
|
|
26
|
+
new_modifiers.append(m)
|
|
27
|
+
|
|
28
|
+
if base and base not in EXCLUDED_WORDS and not base.startswith("["):
|
|
29
|
+
base = f"dy-{base}"
|
|
30
|
+
|
|
31
|
+
return ":".join(new_modifiers + [base])
|
|
32
|
+
else:
|
|
33
|
+
if c in EXCLUDED_WORDS or c.startswith("["):
|
|
34
|
+
return c
|
|
35
|
+
if c.startswith(("group", "peer")):
|
|
36
|
+
return f"dy-{c}"
|
|
37
|
+
return f"dy-{c}"
|
|
38
|
+
|
|
39
|
+
def prefix_classes_in_string(s):
|
|
40
|
+
if not s: return s
|
|
41
|
+
return " ".join([prefix_single_class(c) for c in s.split()])
|
|
42
|
+
|
|
43
|
+
def process_content(content):
|
|
44
|
+
# 1. className="literal"
|
|
45
|
+
def class_name_replacer(m):
|
|
46
|
+
quote = m.group(1)
|
|
47
|
+
classes = m.group(2)
|
|
48
|
+
return f'className={quote}{prefix_classes_in_string(classes)}{quote}'
|
|
49
|
+
content = re.sub(r'className=([\'"])(.*?)\1', class_name_replacer, content)
|
|
50
|
+
|
|
51
|
+
# 2. cn(...)
|
|
52
|
+
def cn_replacer(m):
|
|
53
|
+
inner = m.group(1)
|
|
54
|
+
def inner_string_replacer(sm):
|
|
55
|
+
quote = sm.group(1)
|
|
56
|
+
s = sm.group(2)
|
|
57
|
+
preceding = inner[:sm.start()]
|
|
58
|
+
# Only exclude if it's a comparison or arrow function
|
|
59
|
+
if preceding.strip().endswith(("===", "!==", "==", "!=", "=>")):
|
|
60
|
+
return sm.group(0)
|
|
61
|
+
|
|
62
|
+
return f'{quote}{prefix_classes_in_string(s)}{quote}'
|
|
63
|
+
new_inner = re.sub(r'([\'"])(.*?)\1', inner_string_replacer, inner)
|
|
64
|
+
return f'cn({new_inner})'
|
|
65
|
+
content = re.sub(r'cn\((.*?)\)', cn_replacer, content, flags=re.DOTALL)
|
|
66
|
+
|
|
67
|
+
# 3. cva(...)
|
|
68
|
+
def cva_replacer(m):
|
|
69
|
+
inner = m.group(1)
|
|
70
|
+
def cva_string_replacer(sm):
|
|
71
|
+
quote = sm.group(1)
|
|
72
|
+
s = sm.group(2)
|
|
73
|
+
following = inner[sm.end():]
|
|
74
|
+
if following.strip().startswith(":"):
|
|
75
|
+
return sm.group(0)
|
|
76
|
+
return f'{quote}{prefix_classes_in_string(s)}{quote}'
|
|
77
|
+
new_inner = re.sub(r'([\'"])(.*?)\1', cva_string_replacer, inner)
|
|
78
|
+
return f'cva({new_inner})'
|
|
79
|
+
content = re.sub(r'cva\((.*?)\)', cva_replacer, content, flags=re.DOTALL)
|
|
80
|
+
|
|
81
|
+
return content
|
|
82
|
+
|
|
83
|
+
def main():
|
|
84
|
+
src_dir = "packages/admin/src"
|
|
85
|
+
for root, dirs, files in os.walk(src_dir):
|
|
86
|
+
for file in files:
|
|
87
|
+
if file.endswith((".tsx", ".ts")):
|
|
88
|
+
filepath = os.path.join(root, file)
|
|
89
|
+
with open(filepath, 'r') as f:
|
|
90
|
+
content = f.read()
|
|
91
|
+
new_content = process_content(content)
|
|
92
|
+
if new_content != content:
|
|
93
|
+
with open(filepath, 'w') as f:
|
|
94
|
+
f.write(new_content)
|
|
95
|
+
print(f"Updated {filepath}")
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
def prefix_classes_in_string(s):
|
|
5
|
+
# Skip empty strings or strings that already have the prefix
|
|
6
|
+
if not s or s.startswith("dy-"):
|
|
7
|
+
return s
|
|
8
|
+
|
|
9
|
+
# Split by whitespace, prefix each class, then rejoin
|
|
10
|
+
classes = s.split()
|
|
11
|
+
prefixed = []
|
|
12
|
+
for c in classes:
|
|
13
|
+
# Skip variables like {className} or strings that look like CSS variables
|
|
14
|
+
if c.startswith("{") or c.startswith("$") or c.startswith("--"):
|
|
15
|
+
prefixed.append(c)
|
|
16
|
+
# Skip already prefixed
|
|
17
|
+
elif c.startswith("dy-"):
|
|
18
|
+
prefixed.append(c)
|
|
19
|
+
# Prefix everything else (assuming they are tailwind classes)
|
|
20
|
+
else:
|
|
21
|
+
prefixed.append(f"dy-{c}")
|
|
22
|
+
return " ".join(prefixed)
|
|
23
|
+
|
|
24
|
+
def process_content(content):
|
|
25
|
+
# 1. Handle className="literal classes"
|
|
26
|
+
content = re.sub(r'className=([\'"])(.*?)\1',
|
|
27
|
+
lambda m: f'className={m.group(1)}{prefix_classes_in_string(m.group(2))}{m.group(1)}',
|
|
28
|
+
content)
|
|
29
|
+
|
|
30
|
+
# 2. Handle className={cn("literal classes", ...)}
|
|
31
|
+
# We look for strings inside cn(...)
|
|
32
|
+
def cn_match(m):
|
|
33
|
+
# This is a bit rough but works for simple cn calls
|
|
34
|
+
inner = m.group(1)
|
|
35
|
+
# Replace string literals inside the cn call
|
|
36
|
+
new_inner = re.sub(r'([\'"])(.*?)\1',
|
|
37
|
+
lambda sm: f'{sm.group(1)}{prefix_classes_in_string(sm.group(2))}{sm.group(1)}',
|
|
38
|
+
inner)
|
|
39
|
+
return f'cn({new_inner})'
|
|
40
|
+
|
|
41
|
+
content = re.sub(r'cn\((.*?)\)', cn_match, content, flags=re.DOTALL)
|
|
42
|
+
|
|
43
|
+
# 3. Handle @apply in CSS
|
|
44
|
+
content = re.sub(r'@apply (.*?);',
|
|
45
|
+
lambda m: f'@apply {prefix_classes_in_string(m.group(1))};',
|
|
46
|
+
content)
|
|
47
|
+
|
|
48
|
+
return content
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
src_dir = "packages/admin/src"
|
|
52
|
+
for root, dirs, files in os.walk(src_dir):
|
|
53
|
+
for file in files:
|
|
54
|
+
if file.endswith((".tsx", ".ts", ".css")):
|
|
55
|
+
filepath = os.path.join(root, file)
|
|
56
|
+
with open(filepath, 'r') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
|
|
59
|
+
new_content = process_content(content)
|
|
60
|
+
|
|
61
|
+
if new_content != content:
|
|
62
|
+
with open(filepath, 'w') as f:
|
|
63
|
+
f.write(new_content)
|
|
64
|
+
print(f"Updated {filepath}")
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
|
@@ -32,10 +32,10 @@ export function AuthGate({ children }: { children: React.ReactNode }) {
|
|
|
32
32
|
|
|
33
33
|
if (isLoading) {
|
|
34
34
|
return (
|
|
35
|
-
<div className="flex h-screen items-center justify-center bg-background">
|
|
36
|
-
<div className="flex flex-col items-center gap-4">
|
|
37
|
-
<div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
38
|
-
<p className="text-sm text-muted-foreground animate-pulse">Authenticating...</p>
|
|
35
|
+
<div className="dy-flex dy-h-screen dy-items-center dy-justify-center dy-bg-background">
|
|
36
|
+
<div className="dy-flex dy-flex-col dy-items-center dy-gap-4">
|
|
37
|
+
<div className="dy-h-8 dy-w-8 dy-animate-spin dy-rounded-full dy-border-2 dy-border-primary dy-border-t-transparent" />
|
|
38
|
+
<p className="dy-text-sm dy-text-muted-foreground dy-animate-pulse">Authenticating...</p>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
);
|
|
@@ -27,12 +27,12 @@ export class ErrorBoundary extends Component<Props, State> {
|
|
|
27
27
|
public render() {
|
|
28
28
|
if (this.state.hasError) {
|
|
29
29
|
return this.props.fallback || (
|
|
30
|
-
<div className="flex-1 flex flex-col items-center justify-center p-12 bg-destructive/5 text-destructive min-h-[400px]">
|
|
31
|
-
<h2 className="text-xl font-bold mb-2">Something went wrong</h2>
|
|
32
|
-
<p className="text-sm opacity-80 mb-4">{this.state.error?.message}</p>
|
|
30
|
+
<div className="dy-flex-1 dy-flex dy-flex-col dy-items-center dy-justify-center dy-p-12 dy-bg-destructive/5 dy-text-destructive dy-min-h-[400px]">
|
|
31
|
+
<h2 className="dy-text-xl dy-font-bold dy-mb-2">Something went wrong</h2>
|
|
32
|
+
<p className="dy-text-sm dy-opacity-80 dy-mb-4">{this.state.error?.message}</p>
|
|
33
33
|
<button
|
|
34
34
|
onClick={() => window.location.reload()}
|
|
35
|
-
className="px-4 py-2 bg-destructive text-destructive-foreground rounded-md text-sm font-medium hover:bg-destructive/90 transition-colors"
|
|
35
|
+
className="dy-px-4 dy-py-2 dy-bg-destructive dy-text-destructive-foreground dy-rounded-md dy-text-sm dy-font-medium hover:dy-bg-destructive/90 dy-transition-colors"
|
|
36
36
|
>
|
|
37
37
|
Reload Page
|
|
38
38
|
</button>
|
|
@@ -77,33 +77,33 @@ function SortableBlockItem({
|
|
|
77
77
|
if (!blockConfig) return null
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
|
-
<div ref={setNodeRef} style={style} className="relative group left-accent mb-4 py-4 animate-in">
|
|
80
|
+
<div ref={setNodeRef} style={style} className="dy-relative dy-group dy-left-accent dy-mb-4 dy-py-4 dy-animate-in">
|
|
81
81
|
{/* Header / Drag Handle */}
|
|
82
|
-
<div className="flex items-center justify-between pb-3">
|
|
83
|
-
<div className="flex items-center justify-between gap-2">
|
|
84
|
-
<div {...attributes} {...listeners} className="cursor-grab opacity-20 group-hover:opacity-100 hover:bg-muted p-1 rounded-md transition-all">
|
|
85
|
-
<GripVertical className="w-3.5 h-3.5 text-muted-foreground" />
|
|
82
|
+
<div className="dy-flex dy-items-center dy-justify-between dy-pb-3">
|
|
83
|
+
<div className="dy-flex dy-items-center dy-justify-between dy-gap-2">
|
|
84
|
+
<div {...attributes} {...listeners} className="dy-cursor-grab dy-opacity-20 dy-group-hover:dy-opacity-100 hover:dy-bg-muted dy-p-1 dy-rounded-md dy-transition-all">
|
|
85
|
+
<GripVertical className="dy-w-3.5 dy-h-3.5 dy-text-muted-foreground" />
|
|
86
86
|
</div>
|
|
87
|
-
<span className="font-bold text-xs text-foreground/70 tracking-tight">
|
|
87
|
+
<span className="dy-font-bold dy-text-xs dy-text-foreground/70 dy-tracking-tight">
|
|
88
88
|
{blockConfig.labels?.singular || blockConfig.slug}
|
|
89
89
|
</span>
|
|
90
|
-
<span className="text-[10px] text-muted-foreground/40 ml-2 uppercase tracking-widest font-semibold">
|
|
90
|
+
<span className="dy-text-[10px] dy-text-muted-foreground/40 dy-ml-2 dy-uppercase dy-tracking-widest dy-font-semibold">
|
|
91
91
|
Item {index + 1}
|
|
92
92
|
</span>
|
|
93
93
|
</div>
|
|
94
|
-
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
95
|
-
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground/40" onClick={() => setIsExpanded(!isExpanded)}>
|
|
96
|
-
{isExpanded ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
|
|
94
|
+
<div className="dy-flex dy-items-center dy-gap-1 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
95
|
+
<Button type="button" variant="ghost" size="icon" className="dy-h-7 dy-w-7 dy-text-muted-foreground/40" onClick={() => setIsExpanded(!isExpanded)}>
|
|
96
|
+
{isExpanded ? <ChevronUp className="dy-w-3.5 dy-h-3.5" /> : <ChevronDown className="dy-w-3.5 dy-h-3.5" />}
|
|
97
97
|
</Button>
|
|
98
|
-
<Button type="button" variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground/30 hover:text-destructive hover:bg-destructive/10" onClick={() => remove(index)}>
|
|
99
|
-
<X className="w-3.5 h-3.5" />
|
|
98
|
+
<Button type="button" variant="ghost" size="icon" className="dy-h-7 dy-w-7 dy-text-muted-foreground/30 hover:dy-text-destructive hover:dy-bg-destructive/10" onClick={() => remove(index)}>
|
|
99
|
+
<X className="dy-w-3.5 dy-h-3.5" />
|
|
100
100
|
</Button>
|
|
101
101
|
</div>
|
|
102
102
|
</div>
|
|
103
103
|
|
|
104
104
|
{/* Content */}
|
|
105
105
|
{isExpanded && (
|
|
106
|
-
<div className="space-y-6">
|
|
106
|
+
<div className="dy-space-y-6">
|
|
107
107
|
{blockConfig.fields.map(subField => (
|
|
108
108
|
<FormFieldRenderer
|
|
109
109
|
key={subField.name}
|
|
@@ -148,26 +148,26 @@ export function BlockBuilder({ schema, basePath, control, collection }: BlockBui
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
return (
|
|
151
|
-
<div className="space-y-4">
|
|
152
|
-
<div className="flex justify-between items-center pb-2">
|
|
151
|
+
<div className="dy-space-y-4">
|
|
152
|
+
<div className="dy-flex dy-justify-between dy-items-center dy-pb-2">
|
|
153
153
|
<div>
|
|
154
|
-
<h4 className="font-bold text-sm text-foreground tracking-tight">{schema.label}</h4>
|
|
154
|
+
<h4 className="dy-font-bold dy-text-sm dy-text-foreground dy-tracking-tight">{schema.label}</h4>
|
|
155
155
|
{schema.admin?.description && (
|
|
156
|
-
<p className="text-[11px] text-muted-foreground/60 italic">{schema.admin.description}</p>
|
|
156
|
+
<p className="dy-text-[11px] dy-text-muted-foreground/60 dy-italic">{schema.admin.description}</p>
|
|
157
157
|
)}
|
|
158
158
|
</div>
|
|
159
159
|
|
|
160
160
|
{schema.blocks && schema.blocks.length > 0 && (
|
|
161
161
|
<DropdownMenu>
|
|
162
162
|
<DropdownMenuTrigger asChild>
|
|
163
|
-
<Button type="button" variant="outline" size="sm" className="h-7 text-[11px] rounded-md border-primary/20 hover:bg-primary/5 hover:text-primary">
|
|
163
|
+
<Button type="button" variant="outline" size="sm" className="dy-h-7 dy-text-[11px] dy-rounded-md dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary">
|
|
164
164
|
Add Block
|
|
165
|
-
<ChevronDown className="w-3 h-3 ml-1.5" />
|
|
165
|
+
<ChevronDown className="dy-w-3 dy-h-3 dy-ml-1.5" />
|
|
166
166
|
</Button>
|
|
167
167
|
</DropdownMenuTrigger>
|
|
168
|
-
<DropdownMenuContent align="end" className="rounded-lg border-border/40 shadow-xl">
|
|
168
|
+
<DropdownMenuContent align="end" className="dy-rounded-lg dy-border-border/40 dy-shadow-xl">
|
|
169
169
|
{schema.blocks.map((block) => (
|
|
170
|
-
<DropdownMenuItem key={block.slug} onClick={() => handleAddBlock(block)} className="text-[13px] rounded-md focus:bg-primary/5 focus:text-primary transition-colors">
|
|
170
|
+
<DropdownMenuItem key={block.slug} onClick={() => handleAddBlock(block)} className="dy-text-[13px] dy-rounded-md focus:dy-bg-primary/5 focus:dy-text-primary dy-transition-colors">
|
|
171
171
|
{block.labels?.singular || block.slug}
|
|
172
172
|
</DropdownMenuItem>
|
|
173
173
|
))}
|
|
@@ -177,8 +177,8 @@ export function BlockBuilder({ schema, basePath, control, collection }: BlockBui
|
|
|
177
177
|
</div>
|
|
178
178
|
|
|
179
179
|
{fields.length === 0 ? (
|
|
180
|
-
<div className="text-center p-8 border border-dashed border-border/40 rounded-md">
|
|
181
|
-
<p className="text-[11px] text-muted-foreground/50">No blocks added yet.</p>
|
|
180
|
+
<div className="dy-text-center dy-p-8 dy-border dy-border-dashed dy-border-border/40 dy-rounded-md">
|
|
181
|
+
<p className="dy-text-[11px] dy-text-muted-foreground/50">No blocks added yet.</p>
|
|
182
182
|
</div>
|
|
183
183
|
) : (
|
|
184
184
|
<DndContext
|
|
@@ -190,7 +190,7 @@ export function BlockBuilder({ schema, basePath, control, collection }: BlockBui
|
|
|
190
190
|
items={fields.map(f => f.id)}
|
|
191
191
|
strategy={verticalListSortingStrategy}
|
|
192
192
|
>
|
|
193
|
-
<div className="pt-2">
|
|
193
|
+
<div className="dy-pt-2">
|
|
194
194
|
{fields.map((item, index) => (
|
|
195
195
|
<SortableBlockItem
|
|
196
196
|
key={item.id}
|
|
@@ -21,25 +21,25 @@ export function DatePicker({ value, onChange, label, disabled }: DatePickerProps
|
|
|
21
21
|
const date = value ? new Date(value) : undefined
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<div className="flex flex-col gap-2">
|
|
25
|
-
{label && <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{label}</label>}
|
|
24
|
+
<div className="dy-flex dy-flex-col dy-gap-2">
|
|
25
|
+
{label && <label className="dy-text-sm dy-font-medium dy-leading-none dy-peer-disabled:dy-cursor-not-allowed dy-peer-disabled:dy-opacity-70">{label}</label>}
|
|
26
26
|
<Popover open={disabled ? false : undefined}>
|
|
27
27
|
<PopoverTrigger asChild>
|
|
28
28
|
<Button
|
|
29
29
|
variant={"outline"}
|
|
30
30
|
disabled={disabled}
|
|
31
31
|
className={cn(
|
|
32
|
-
"w-full justify-start text-left font-normal h-11 px-4 bg-white hover:bg-muted/50 border-border/60 shadow-sm transition-all hover:shadow-md",
|
|
33
|
-
!date && "text-muted-foreground"
|
|
32
|
+
"dy-w-full dy-justify-start dy-text-left dy-font-normal dy-h-11 dy-px-4 dy-bg-white hover:dy-bg-muted/50 dy-border-border/60 dy-shadow-sm dy-transition-all hover:dy-shadow-md",
|
|
33
|
+
!date && "dy-text-muted-foreground"
|
|
34
34
|
)}
|
|
35
35
|
>
|
|
36
|
-
<CalendarIcon className="mr-3 h-4 w-4 text-primary" />
|
|
37
|
-
<span className="flex-1 truncate">
|
|
36
|
+
<CalendarIcon className="dy-mr-3 dy-h-4 dy-w-4 dy-text-primary" />
|
|
37
|
+
<span className="dy-flex-1 dy-truncate">
|
|
38
38
|
{date ? format(date, "PPP") : "Pick a date..."}
|
|
39
39
|
</span>
|
|
40
40
|
</Button>
|
|
41
41
|
</PopoverTrigger>
|
|
42
|
-
<PopoverContent className="w-auto p-0 border-border/50 shadow-2xl rounded-xl" align="start">
|
|
42
|
+
<PopoverContent className="dy-w-auto dy-p-0 dy-border-border/50 dy-shadow-2xl dy-rounded-xl" align="start">
|
|
43
43
|
<Calendar
|
|
44
44
|
mode="single"
|
|
45
45
|
selected={date}
|
|
@@ -44,19 +44,19 @@ export function JsonEditor({ value, onChange, label, disabled }: JsonEditorProps
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<div className="flex flex-col gap-2">
|
|
48
|
-
{label && <label className="text-sm font-medium leading-none">{label}</label>}
|
|
47
|
+
<div className="dy-flex dy-flex-col dy-gap-2">
|
|
48
|
+
{label && <label className="dy-text-sm dy-font-medium dy-leading-none">{label}</label>}
|
|
49
49
|
<Textarea
|
|
50
50
|
value={internalValue}
|
|
51
51
|
onChange={handleChange}
|
|
52
52
|
disabled={disabled}
|
|
53
53
|
className={cn(
|
|
54
|
-
"font-mono text-xs min-h-[150px]",
|
|
55
|
-
error && "border-destructive focus-visible:ring-destructive"
|
|
54
|
+
"dy-font-mono dy-text-xs dy-min-h-[150px]",
|
|
55
|
+
error && "dy-border-destructive focus-visible:dy-ring-destructive"
|
|
56
56
|
)}
|
|
57
57
|
placeholder='{ "key": "value" }'
|
|
58
58
|
/>
|
|
59
|
-
{error && <span className="text-xs text-destructive">{error}</span>}
|
|
59
|
+
{error && <span className="dy-text-xs dy-text-destructive">{error}</span>}
|
|
60
60
|
</div>
|
|
61
61
|
)
|
|
62
62
|
}
|
|
@@ -98,70 +98,70 @@ export function MediaPicker({
|
|
|
98
98
|
|
|
99
99
|
if (multiple && !isIcon) {
|
|
100
100
|
return (
|
|
101
|
-
<div className="space-y-4">
|
|
101
|
+
<div className="dy-space-y-4">
|
|
102
102
|
{label && (
|
|
103
|
-
<label className="text-sm font-semibold text-foreground/70 tracking-tight leading-none">
|
|
103
|
+
<label className="dy-text-sm dy-font-semibold dy-text-foreground/70 dy-tracking-tight dy-leading-none">
|
|
104
104
|
{label}
|
|
105
105
|
</label>
|
|
106
106
|
)}
|
|
107
107
|
|
|
108
|
-
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
108
|
+
<div className="dy-grid dy-grid-cols-2 md:dy-grid-cols-3 lg:dy-grid-cols-4 dy-gap-4">
|
|
109
109
|
<button
|
|
110
110
|
type="button"
|
|
111
111
|
onClick={() => setIsOpen(true)}
|
|
112
|
-
className="group relative aspect-square rounded-xl border-2 border-dashed border-muted hover:border-primary/40 hover:bg-primary/5 transition-all flex flex-col items-center justify-center gap-3 overflow-hidden"
|
|
112
|
+
className="dy-group dy-relative dy-aspect-square dy-rounded-xl dy-border-2 dy-border-dashed dy-border-muted hover:dy-border-primary/40 hover:dy-bg-primary/5 dy-transition-all dy-flex dy-flex-col dy-items-center dy-justify-center dy-gap-3 dy-overflow-hidden"
|
|
113
113
|
>
|
|
114
|
-
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
115
|
-
<div className="h-12 w-12 bg-muted rounded-full flex items-center justify-center text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary transition-all shadow-inner">
|
|
116
|
-
<Plus className="h-6 w-6" />
|
|
114
|
+
<div className="dy-absolute dy-inset-0 dy-bg-primary/5 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity" />
|
|
115
|
+
<div className="dy-h-12 dy-w-12 dy-bg-muted dy-rounded-full dy-flex dy-items-center dy-justify-center dy-text-muted-foreground dy-group-hover:dy-bg-primary/10 dy-group-hover:dy-text-primary dy-transition-all dy-shadow-inner">
|
|
116
|
+
<Plus className="dy-h-6 dy-w-6" />
|
|
117
117
|
</div>
|
|
118
|
-
<div className="text-center px-4">
|
|
119
|
-
<p className="text-[11px] font-bold uppercase tracking-widest text-muted-foreground group-hover:text-primary transition-colors">Add Media</p>
|
|
120
|
-
<p className="text-[10px] text-muted-foreground/40 mt-1 font-medium group-hover:text-primary/60">Select or upload</p>
|
|
118
|
+
<div className="dy-text-center dy-px-4">
|
|
119
|
+
<p className="dy-text-[11px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground dy-group-hover:dy-text-primary dy-transition-colors">Add Media</p>
|
|
120
|
+
<p className="dy-text-[10px] dy-text-muted-foreground/40 dy-mt-1 dy-font-medium dy-group-hover:dy-text-primary/60">Select or upload</p>
|
|
121
121
|
</div>
|
|
122
122
|
</button>
|
|
123
123
|
|
|
124
124
|
{selectedValues.map((val, index) => {
|
|
125
125
|
const item = media?.find((m: any) => m.id === val)
|
|
126
126
|
return (
|
|
127
|
-
<div key={val} className="relative group animate-in zoom-in duration-300">
|
|
127
|
+
<div key={val} className="dy-relative dy-group dy-animate-in dy-zoom-in dy-duration-300">
|
|
128
128
|
<div className={cn(
|
|
129
|
-
"relative aspect-square rounded-xl overflow-hidden border-2 bg-muted/20 transition-all shadow-sm",
|
|
130
|
-
index === 0 ? "border-primary ring-4 ring-primary/10" : "border-border/40 hover:border-border/80"
|
|
129
|
+
"dy-relative dy-aspect-square dy-rounded-xl dy-overflow-hidden dy-border-2 dy-bg-muted/20 dy-transition-all dy-shadow-sm",
|
|
130
|
+
index === 0 ? "dy-border-primary dy-ring-4 dy-ring-primary/10" : "dy-border-border/40 hover:dy-border-border/80"
|
|
131
131
|
)}>
|
|
132
132
|
{item ? (
|
|
133
133
|
<img
|
|
134
134
|
src={getPreviewUrl(item)}
|
|
135
135
|
alt=""
|
|
136
|
-
className="w-full h-full object-cover transition-transform group-hover:scale-110"
|
|
136
|
+
className="dy-w-full dy-h-full dy-object-cover dy-transition-transform dy-group-hover:dy-scale-110"
|
|
137
137
|
/>
|
|
138
138
|
) : (
|
|
139
|
-
<div className="w-full h-full animate-pulse bg-muted/50 flex items-center justify-center">
|
|
140
|
-
<ImageIcon className="h-6 w-6 text-muted-foreground/20" />
|
|
139
|
+
<div className="dy-w-full dy-h-full dy-animate-pulse dy-bg-muted/50 dy-flex dy-items-center dy-justify-center">
|
|
140
|
+
<ImageIcon className="dy-h-6 dy-w-6 dy-text-muted-foreground/20" />
|
|
141
141
|
</div>
|
|
142
142
|
)}
|
|
143
143
|
|
|
144
144
|
{index === 0 && (
|
|
145
|
-
<div className="absolute top-0 left-0 w-full text-center z-10 px-3 py-1 bg-primary text-white text-[9px] font-black uppercase tracking-widest shadow-primary/20">
|
|
145
|
+
<div className="dy-absolute dy-top-0 dy-left-0 dy-w-full dy-text-center dy-z-10 dy-px-3 dy-py-1 dy-bg-primary dy-text-white dy-text-[9px] dy-font-black dy-uppercase dy-tracking-widest dy-shadow-primary/20">
|
|
146
146
|
Main Image
|
|
147
147
|
</div>
|
|
148
148
|
)}
|
|
149
149
|
|
|
150
|
-
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-all flex items-start justify-end backdrop-blur-[2px]">
|
|
150
|
+
<div className="dy-absolute dy-inset-0 dy-bg-black/40 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-all dy-flex dy-items-start dy-justify-end dy-backdrop-blur-[2px]">
|
|
151
151
|
<Button
|
|
152
152
|
type="button"
|
|
153
153
|
variant="outline"
|
|
154
154
|
size="icon"
|
|
155
|
-
className="h-8 w-8 rounded-lg text-destructive bg-destructive-foreground shadow-2xl scale-75 group-hover:scale-100 transition-all"
|
|
155
|
+
className="dy-h-8 dy-w-8 dy-rounded-lg dy-text-destructive dy-bg-destructive-foreground dy-shadow-2xl dy-scale-75 dy-group-hover:dy-scale-100 dy-transition-all"
|
|
156
156
|
onClick={() => toggleValue(val)}
|
|
157
157
|
>
|
|
158
|
-
<Trash2 className="w-5 h-5" />
|
|
158
|
+
<Trash2 className="dy-w-5 dy-h-5" />
|
|
159
159
|
</Button>
|
|
160
160
|
</div>
|
|
161
161
|
|
|
162
162
|
{item && (
|
|
163
|
-
<div className="absolute inset-x-0 bottom-0 p-2 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
|
|
164
|
-
<p className="text-[10px] text-white truncate font-medium">{item.filename}</p>
|
|
163
|
+
<div className="dy-absolute dy-inset-x-0 dy-bottom-0 dy-p-2 dy-bg-gradient-to-t dy-from-black/60 dy-to-transparent dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
164
|
+
<p className="dy-text-[10px] dy-text-white dy-truncate dy-font-medium">{item.filename}</p>
|
|
165
165
|
</div>
|
|
166
166
|
)}
|
|
167
167
|
</div>
|
|
@@ -186,41 +186,41 @@ export function MediaPicker({
|
|
|
186
186
|
return (
|
|
187
187
|
<div className={isIcon ? "" : "space-y-3"}>
|
|
188
188
|
{label && !isIcon && (
|
|
189
|
-
<label className="text-sm font-semibold text-foreground/70 tracking-tight leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
189
|
+
<label className="dy-text-sm dy-font-semibold dy-text-foreground/70 dy-tracking-tight dy-leading-none dy-peer-disabled:dy-cursor-not-allowed dy-peer-disabled:dy-opacity-70">
|
|
190
190
|
{label}
|
|
191
191
|
</label>
|
|
192
192
|
)}
|
|
193
193
|
|
|
194
194
|
<div className={isIcon ? "" : "relative flex items-center gap-2"}>
|
|
195
195
|
{!isIcon && (
|
|
196
|
-
<div className="relative flex-1 group">
|
|
196
|
+
<div className="dy-relative dy-flex-1 dy-group">
|
|
197
197
|
<Input
|
|
198
198
|
value={displayValue}
|
|
199
199
|
readOnly
|
|
200
200
|
disabled={disabled}
|
|
201
201
|
placeholder={placeholder || "No media selected"}
|
|
202
|
-
className="pr-24 bg-muted/30 border-dashed focus-visible:ring-offset-0 focus-visible:ring-1 h-10 rounded-xl"
|
|
202
|
+
className="dy-pr-24 dy-bg-muted/30 dy-border-dashed focus-visible:dy-ring-offset-0 focus-visible:dy-ring-1 dy-h-10 dy-rounded-xl"
|
|
203
203
|
/>
|
|
204
|
-
<div className="absolute right-1 top-1/2
|
|
204
|
+
<div className="dy-absolute dy-right-1 dy-top-1/2 dy--translate-y-1/2 dy-flex dy-items-center dy-gap-1 dy-pr-1">
|
|
205
205
|
{value && (
|
|
206
206
|
<Button
|
|
207
207
|
type="button"
|
|
208
208
|
variant="ghost"
|
|
209
209
|
size="icon"
|
|
210
|
-
className="h-7 w-7 text-muted-foreground hover:text-destructive transition-colors rounded-lg"
|
|
210
|
+
className="dy-h-7 dy-w-7 dy-text-muted-foreground hover:dy-text-destructive dy-transition-colors dy-rounded-lg"
|
|
211
211
|
onClick={(e) => {
|
|
212
212
|
e.preventDefault();
|
|
213
213
|
onChange(multiple ? [] : "");
|
|
214
214
|
}}
|
|
215
215
|
>
|
|
216
|
-
<X className="h-4 w-4" />
|
|
216
|
+
<X className="dy-h-4 dy-w-4" />
|
|
217
217
|
</Button>
|
|
218
218
|
)}
|
|
219
219
|
<Button
|
|
220
220
|
type="button"
|
|
221
221
|
variant="secondary"
|
|
222
222
|
size="sm"
|
|
223
|
-
className="h-8 text-xs font-bold px-3 rounded-lg shadow-sm border border-border/50"
|
|
223
|
+
className="dy-h-8 dy-text-xs dy-font-bold dy-px-3 dy-rounded-lg dy-shadow-sm dy-border dy-border-border/50"
|
|
224
224
|
disabled={disabled}
|
|
225
225
|
onClick={() => setIsOpen(true)}
|
|
226
226
|
>
|
|
@@ -231,8 +231,8 @@ export function MediaPicker({
|
|
|
231
231
|
)}
|
|
232
232
|
|
|
233
233
|
{isIcon && (
|
|
234
|
-
<Button variant="ghost" size="sm" className="px-2 h-8 w-8 rounded-lg" disabled={disabled} onClick={() => setIsOpen(true)}>
|
|
235
|
-
<ImageIcon className="h-4 w-4" />
|
|
234
|
+
<Button variant="ghost" size="sm" className="dy-px-2 dy-h-8 dy-w-8 dy-rounded-lg" disabled={disabled} onClick={() => setIsOpen(true)}>
|
|
235
|
+
<ImageIcon className="dy-h-4 dy-w-4" />
|
|
236
236
|
</Button>
|
|
237
237
|
)}
|
|
238
238
|
|
|
@@ -248,33 +248,33 @@ export function MediaPicker({
|
|
|
248
248
|
</div>
|
|
249
249
|
|
|
250
250
|
{!isIcon && selectedValues.length > 0 && !multiple && (
|
|
251
|
-
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 pt-2">
|
|
251
|
+
<div className="dy-grid dy-grid-cols-2 sm:dy-grid-cols-3 md:dy-grid-cols-4 lg:dy-grid-cols-5 dy-gap-4 dy-pt-2">
|
|
252
252
|
{selectedValues.map((val) => {
|
|
253
253
|
const item = media?.find((m: any) => m.id === val)
|
|
254
254
|
if (!item) return (
|
|
255
|
-
<div key={val} className="aspect-square rounded-xl bg-muted/20 animate-pulse border-2 border-dashed border-border/50" />
|
|
255
|
+
<div key={val} className="dy-aspect-square dy-rounded-xl dy-bg-muted/20 dy-animate-pulse dy-border-2 dy-border-dashed dy-border-border/50" />
|
|
256
256
|
)
|
|
257
257
|
return (
|
|
258
258
|
<div
|
|
259
259
|
key={val}
|
|
260
|
-
className="relative aspect-square group rounded-2xl overflow-hidden border-2 border-border/50 hover:border-primary/50 transition-all bg-muted/20 shadow-sm"
|
|
260
|
+
className="dy-relative dy-aspect-square dy-group dy-rounded-2xl dy-overflow-hidden dy-border-2 dy-border-border/50 hover:dy-border-primary/50 dy-transition-all dy-bg-muted/20 dy-shadow-sm"
|
|
261
261
|
>
|
|
262
262
|
<img
|
|
263
263
|
src={getPreviewUrl(item)}
|
|
264
264
|
alt=""
|
|
265
|
-
className="w-full h-full object-cover transition-transform group-hover:scale-110"
|
|
265
|
+
className="dy-w-full dy-h-full dy-object-cover dy-transition-transform dy-group-hover:dy-scale-110"
|
|
266
266
|
/>
|
|
267
267
|
{!disabled && (
|
|
268
268
|
<button
|
|
269
269
|
type="button"
|
|
270
270
|
onClick={() => toggleValue(val)}
|
|
271
|
-
className="absolute top-2 right-2 p-1.5 bg-destructive text-destructive-foreground rounded-full opacity-0 group-hover:opacity-100 transition-all hover:scale-110 shadow-lg border-2 border-white"
|
|
271
|
+
className="dy-absolute dy-top-2 dy-right-2 dy-p-1.5 dy-bg-destructive dy-text-destructive-foreground dy-rounded-full dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-all hover:dy-scale-110 dy-shadow-lg dy-border-2 dy-border-white"
|
|
272
272
|
>
|
|
273
|
-
<X className="h-3.5 w-3.5" />
|
|
273
|
+
<X className="dy-h-3.5 dy-w-3.5" />
|
|
274
274
|
</button>
|
|
275
275
|
)}
|
|
276
|
-
<div className="absolute inset-x-0 bottom-0 p-2 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
|
|
277
|
-
<p className="text-[10px] text-white truncate font-medium">{item.filename}</p>
|
|
276
|
+
<div className="dy-absolute dy-inset-x-0 dy-bottom-0 dy-p-2 dy-bg-gradient-to-t dy-from-black/60 dy-to-transparent dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
277
|
+
<p className="dy-text-[10px] dy-text-white dy-truncate dy-font-medium">{item.filename}</p>
|
|
278
278
|
</div>
|
|
279
279
|
</div>
|
|
280
280
|
)
|