mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 19:51:17 +00:00
112 lines
4.9 KiB
TypeScript
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>
|
|
)
|
|
}
|