Files
EpicNext-Cms/src/app/admin/theme-builder/theme-editor-tabs.tsx
T
Simo e17346ab9f
CI / check (push) Successful in 1m32s
CI / deploy (push) Successful in 1m20s
CI / e2e (push) Successful in 21s
refactor(cms): separate dashboard data and editor components with focused UX fixes
2026-09-05 18:46:03 +02:00

274 lines
9.4 KiB
TypeScript

"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>
);
}