Next.js 15 App Router mit öffentlicher Übersicht und geschütztem Admin-Bereich zur Pflege von Datenschutzerklärungen nach Art. 12ff. DSGVO. - Oberfläche auf Basis von shadcn/ui, Inter lokal eingebunden - Prisma/SQLite, Auth.js mit Credentials-Provider und Rollen - Massenimport der Merkblätter des Amtes Leezen aus PDF - Docker-Image (standalone) und Gitea-Workflow zur Veröffentlichung Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from "react"
|
||
import { Eye, Pencil } from "lucide-react"
|
||
|
||
import { Label } from "@/components/ui/label"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||
import { MarkdownRenderer } from "@/components/markdown-renderer"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
interface MarkdownInputProps {
|
||
id: string
|
||
label: string
|
||
value: string
|
||
onChange: (value: string) => void
|
||
placeholder?: string
|
||
required?: boolean
|
||
rows?: number
|
||
className?: string
|
||
invalid?: boolean
|
||
}
|
||
|
||
export function MarkdownInput({
|
||
id,
|
||
label,
|
||
value,
|
||
onChange,
|
||
placeholder,
|
||
required = false,
|
||
rows = 6,
|
||
className,
|
||
invalid = false,
|
||
}: MarkdownInputProps) {
|
||
const [tab, setTab] = useState<"edit" | "preview">("edit")
|
||
|
||
return (
|
||
<Tabs
|
||
value={tab}
|
||
onValueChange={(next) => setTab(next as "edit" | "preview")}
|
||
className={cn("gap-2", className)}
|
||
>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<Label htmlFor={id} className="gap-1">
|
||
{label}
|
||
{required && <span className="text-destructive">*</span>}
|
||
</Label>
|
||
<TabsList className="h-8">
|
||
<TabsTrigger value="edit" className="px-2.5">
|
||
<Pencil />
|
||
<span className="sr-only">Bearbeiten</span>
|
||
</TabsTrigger>
|
||
<TabsTrigger value="preview" className="px-2.5">
|
||
<Eye />
|
||
<span className="sr-only">Vorschau</span>
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
</div>
|
||
|
||
<TabsContent value="edit">
|
||
<Textarea
|
||
id={id}
|
||
value={value}
|
||
onChange={(e) => onChange(e.target.value)}
|
||
placeholder={placeholder}
|
||
rows={rows}
|
||
aria-invalid={invalid}
|
||
className="resize-y font-mono text-sm"
|
||
/>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="preview">
|
||
<div
|
||
className="rounded-md border bg-muted/30 p-4"
|
||
style={{ minHeight: `${rows * 1.5 + 1}rem` }}
|
||
>
|
||
{value ? (
|
||
<MarkdownRenderer content={value} />
|
||
) : (
|
||
<p className="text-sm text-muted-foreground italic">
|
||
Noch kein Inhalt – im Bearbeiten-Tab eingeben.
|
||
</p>
|
||
)}
|
||
</div>
|
||
</TabsContent>
|
||
</Tabs>
|
||
)
|
||
}
|