since3-portal/modules/templates/ui/components/editor/design-panel.tsx
2026-10-05 08:23:02 +02:00

112 lines
4.9 KiB
TypeScript

"use client"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import type { TemplateContent, TemplateDefinition } from "../../../lib/types"
import { Toggle } from "./toggle"
type ThemePatch = Partial<Pick<TemplateContent, "palette" | "fonts" | "accent" | "hidden">>
function Heading({ title, text }: { title: string; text?: string }) {
return (
<div className="mb-3">
<h3 className="text-sm font-medium">{title}</h3>
{text && <p className="mt-0.5 text-xs text-muted-foreground">{text}</p>}
</div>
)
}
export function DesignPanel({ def, content, onChange }: {
def: TemplateDefinition
content: TemplateContent
onChange: (patch: ThemePatch) => void
}) {
const palette = def.palettes.find((p) => p.key === content.palette) ?? def.palettes[0]
const accent = content.accent || palette.accent
return (
<div className="flex flex-col gap-8">
<section>
<Heading title="Farbpalette" />
<div className="grid grid-cols-3 gap-2">
{def.palettes.map((p) => (
<button
key={p.key}
type="button"
aria-pressed={p.key === content.palette}
onClick={() => onChange({ palette: p.key })}
className={cn(
"cursor-pointer overflow-hidden rounded-lg border text-left transition-shadow",
p.key === content.palette ? "ring-2 ring-ring" : "hover:border-foreground/30"
)}
>
<div className="flex h-14 items-end gap-1 p-2" style={{ background: p.bg }}>
<span className="size-4 rounded-full" style={{ background: p.fg }} />
<span className="size-4 rounded-full" style={{ background: p.accent }} />
</div>
<p className="px-2 py-1.5 text-xs">{p.label}</p>
</button>
))}
</div>
</section>
<section>
<Heading title="Akzentfarbe" text="Zum Beispiel die Markenfarbe des Kunden. Textfarbe darauf wird automatisch gewählt." />
<div className="flex items-center gap-3">
<input
type="color"
value={accent}
aria-label="Akzentfarbe"
onChange={(event) => onChange({ accent: event.target.value })}
className="h-10 w-14 cursor-pointer rounded-md border bg-transparent p-1"
/>
<code className="font-mono text-xs uppercase">{accent}</code>
{content.accent && (
<Button variant="ghost" size="sm" onClick={() => onChange({ accent: "" })}>
Palette verwenden
</Button>
)}
</div>
</section>
<section>
<Heading title="Schriften" />
<div className="flex flex-col gap-2">
{def.fonts.map((pair) => (
<button
key={pair.key}
type="button"
aria-pressed={pair.key === content.fonts}
onClick={() => onChange({ fonts: pair.key })}
className={cn(
"cursor-pointer rounded-lg border px-3 py-2.5 text-left text-sm transition-shadow",
pair.key === content.fonts ? "ring-2 ring-ring" : "hover:border-foreground/30"
)}
>
{pair.label}
</button>
))}
</div>
</section>
<section>
<Heading title="Sektionen" text="Ausgeblendete Sektionen behalten ihre Inhalte." />
<div className="flex flex-col divide-y rounded-lg border">
{def.sections.filter((section) => section.optional).map((section) => (
<Toggle
key={section.key}
checked={!content.hidden.includes(section.key)}
onChange={(visible) =>
onChange({
hidden: visible ? content.hidden.filter((key) => key !== section.key) : [...content.hidden, section.key],
})
}
>
{section.label}
</Toggle>
))}
</div>
</section>
</div>
)
}