refactor(cms): separate dashboard data and editor components with focused UX fixes
This commit is contained in:
1 parent
3ae4f21217
commit
e17346ab9f
11 files changed
+1212
-1118
No files matched your search
@@ -0,0 +1,273 @@
|
||||
"use client";
|
||||
import { BLOCK_KEY_PREFIX, getBlocksByCategory } from "@/lib/theme-blocks";
|
||||
import { ColorField } from "./color-field";
|
||||
import {
|
||||
COLOR_FIELDS,
|
||||
EFFECT_FIELDS,
|
||||
getVal,
|
||||
LAYOUT_FIELDS,
|
||||
MEDIA_FIELDS,
|
||||
type ScopeValues,
|
||||
} from "./theme-editor-fields";
|
||||
// ─── Colors Tab ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function ColorsTab({
|
||||
values,
|
||||
mode,
|
||||
}: {
|
||||
values: ScopeValues;
|
||||
mode: "light" | "dark";
|
||||
}) {
|
||||
return (
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Colors ({mode} mode)
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Only values you set here override the parent scope. Leave empty to
|
||||
inherit.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{COLOR_FIELDS.map((field) => (
|
||||
<ColorField
|
||||
key={`${field.key}-${mode}`}
|
||||
name={`${field.key}_${mode}`}
|
||||
label={field.label}
|
||||
description={field.desc}
|
||||
defaultValue={getVal(values, field.key, mode) || field.def}
|
||||
backgroundColorKey={
|
||||
field.bgKey ? `${field.bgKey}_${mode}` : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Blocks Tab ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function BlocksTab({ values }: { values: ScopeValues }) {
|
||||
const grouped = getBlocksByCategory();
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
chrome: "Site Chrome",
|
||||
layout: "Layout",
|
||||
components: "Components",
|
||||
effects: "Effects",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(grouped).map(([category, blocks]) => (
|
||||
<div key={category} className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-1">
|
||||
{categoryLabels[category] ?? category}
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Toggle visibility of blocks in this scope. Hidden blocks use{" "}
|
||||
<code className="bg-[var(--admin-surface-elevated)] px-1 rounded">
|
||||
display: none
|
||||
</code>
|
||||
.
|
||||
</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
{blocks.map((block) => {
|
||||
const key = `${BLOCK_KEY_PREFIX}${block.id}`;
|
||||
const currentVal = values.blocks[key];
|
||||
const isChecked =
|
||||
currentVal === "true" ||
|
||||
(currentVal === undefined && block.defaultVisible);
|
||||
|
||||
return (
|
||||
<label
|
||||
key={block.id}
|
||||
className="flex items-center gap-3 p-2 rounded-lg hover:bg-[var(--admin-surface-elevated)] cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={key}
|
||||
defaultChecked={isChecked}
|
||||
className="h-4 w-4 rounded border-[var(--admin-border)] text-[var(--admin-accent)] focus:ring-[var(--admin-accent)]"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm text-[var(--admin-text)]">
|
||||
{block.label}
|
||||
</span>
|
||||
<span className="text-xs text-[var(--admin-text-muted)] ml-2">
|
||||
{block.description}
|
||||
</span>
|
||||
</div>
|
||||
<code className="text-[10px] text-[var(--admin-text-muted)] font-mono">
|
||||
{block.selector}
|
||||
</code>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Layout Tab ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function LayoutTab({ values }: { values: ScopeValues }) {
|
||||
return (
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Layout Settings
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Control spacing, width, and grid settings for this scope. Values are
|
||||
applied as CSS custom properties.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{LAYOUT_FIELDS.map((field) => (
|
||||
<div key={field.key} className="rounded-lg p-2">
|
||||
<label className="block text-sm font-semibold text-[var(--admin-text)] mb-1">
|
||||
{field.label}
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-2 font-normal">
|
||||
{field.desc}
|
||||
</p>
|
||||
<input
|
||||
type={field.type || "text"}
|
||||
name={field.key}
|
||||
defaultValue={getVal(values, field.key, "light") || field.def}
|
||||
className="w-full px-3 py-2 rounded-lg border bg-[var(--admin-canvas)] text-[var(--admin-text)] text-sm font-mono"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Effects Tab ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function EffectsTab({ values }: { values: ScopeValues }) {
|
||||
return (
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Effects & Transitions
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Control shadows, borders, and animation timing for this scope.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{EFFECT_FIELDS.map((field) => (
|
||||
<div key={field.key} className="rounded-lg p-2">
|
||||
<label className="block text-sm font-semibold text-[var(--admin-text)] mb-1">
|
||||
{field.label}
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-2 font-normal">
|
||||
{field.desc}
|
||||
</p>
|
||||
<input
|
||||
type={field.type || "text"}
|
||||
name={field.key}
|
||||
defaultValue={getVal(values, field.key, "light") || field.def}
|
||||
className="w-full px-3 py-2 rounded-lg border bg-[var(--admin-canvas)] text-[var(--admin-text)] text-sm font-mono"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Media Tab ───────────────────────────────────────────────────────────────
|
||||
|
||||
export function MediaTab({ values }: { values: ScopeValues }) {
|
||||
return (
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Media & Backgrounds
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Set custom logos, background images, and background properties for this
|
||||
scope.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{MEDIA_FIELDS.map((field) => (
|
||||
<div key={field.key} className="rounded-lg p-2">
|
||||
<label className="block text-sm font-semibold text-[var(--admin-text)] mb-1">
|
||||
{field.label}
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-2 font-normal">
|
||||
{field.desc}
|
||||
</p>
|
||||
<input
|
||||
type={field.type || "text"}
|
||||
name={field.key}
|
||||
defaultValue={getVal(values, field.key, "light") || field.def}
|
||||
placeholder={field.def}
|
||||
className="w-full px-3 py-2 rounded-lg border bg-[var(--admin-canvas)] text-[var(--admin-text)] text-sm"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Custom Tab ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function CustomTab({ values }: { values: ScopeValues }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Custom CSS
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Add custom CSS that applies within this scope. Use{" "}
|
||||
<code className="bg-[var(--admin-surface-elevated)] px-1 rounded">
|
||||
&{" "}
|
||||
</code>{" "}
|
||||
as a placeholder for the scope selector (e.g.{" "}
|
||||
<code className="bg-[var(--admin-surface-elevated)] px-1 rounded">
|
||||
& .content-card {"{"} border: 2px solid red; {"}"}
|
||||
</code>
|
||||
).
|
||||
</p>
|
||||
<textarea
|
||||
name="custom:css"
|
||||
defaultValue={getVal(values, "custom:css", "light")}
|
||||
rows={8}
|
||||
placeholder={".content-card { border-radius: 20px; }"}
|
||||
className="w-full px-3 py-2 rounded-lg border bg-[var(--admin-canvas)] text-[var(--admin-text)] text-sm font-mono resize-y"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="admin-card p-4">
|
||||
<h3 className="text-sm font-semibold text-[var(--admin-text)] mb-4">
|
||||
Custom HTML
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--admin-text-muted)] mb-4">
|
||||
Add custom HTML that injects into the page for this scope. Use for
|
||||
custom banners, scripts, or content blocks.
|
||||
</p>
|
||||
<textarea
|
||||
name="custom:html"
|
||||
defaultValue={getVal(values, "custom:html", "light")}
|
||||
rows={6}
|
||||
placeholder='<div class="my-banner">Welcome!</div>'
|
||||
className="w-full px-3 py-2 rounded-lg border bg-[var(--admin-canvas)] text-[var(--admin-text)] text-sm font-mono resize-y"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user