syncova-policies/components/markdown-input.tsx
Weapie 328f042307 Syncova Policies: Portal für DSGVO-Datenschutzerklärungen
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>
2026-09-07 16:23:51 +02:00

89 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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