Files
AdminandClaude Opus 5.5 78c8b702f0 Galerie aller shadcn/ui-Komponenten auf der Startseite
- Alle 63 Komponenten aus dem shadcn-Register installiert (Stil base-luma)
- Galerie unter components/galerie mit 7 Kategorien und Sprungnavigation
- Inhaltsbreite auf max-w-6xl erweitert
- shadcn-Hauptfarbe auf E&P-Tiefblau, größter Radius eckig
- App-Titel "UI-Galerie"

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:07:19 +00:00

253 lines
8.1 KiB
TypeScript
Raw Permalink 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 * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Button } from "@/components/ui/button"
import { CheckIcon, CopyIcon } from "lucide-react"
const codeBlockVariants = cva(
"group/code-block relative flex w-full min-w-0 flex-col overflow-hidden rounded-2xl text-sm has-[>[data-slot=code-block-content]:focus-visible]:ring-3 has-[>[data-slot=code-block-content]:focus-visible]:ring-ring/30 data-[size=sm]:rounded-xl data-[size=sm]:text-xs",
{
variants: {
variant: {
default:
"bg-muted text-foreground ring-1 ring-foreground/5 dark:ring-foreground/10",
destructive:
"bg-destructive/5 text-destructive ring-1 ring-destructive/15 dark:bg-destructive/10 dark:ring-destructive/20",
},
},
defaultVariants: {
variant: "default",
},
}
)
function CodeBlock({
className,
variant = "default",
size = "default",
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof codeBlockVariants> & { size?: "default" | "sm" }) {
return (
<div
data-slot="code-block"
data-variant={variant}
data-size={size}
className={cn(codeBlockVariants({ variant }), className)}
{...props}
/>
)
}
function CodeBlockHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-header"
className={cn(
"flex min-w-0 shrink-0 items-center gap-2 px-5 pt-5 text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pt-3 group-data-[variant=destructive]/code-block:text-destructive *:has-[>[data-slot=code-block-title]]:min-w-0",
className
)}
{...props}
/>
)
}
function CodeBlockTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-title"
className={cn(
"min-w-0 truncate font-sans text-sm font-medium text-foreground group-data-[size=sm]/code-block:text-xs group-data-[variant=destructive]/code-block:text-destructive [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle [&_svg:not([class*='size-'])]:size-4 group-data-[size=sm]/code-block:[&_svg:not([class*='size-'])]:size-3.5 *:[svg]:me-1.5",
className
)}
{...props}
/>
)
}
function CodeBlockActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-actions"
className={cn(
"-my-1 ms-auto -me-1.5 flex shrink-0 items-center gap-1 *:data-[slot=button]:hover:bg-foreground/5 dark:*:data-[slot=button]:hover:bg-foreground/10 [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:end-4 [[data-slot=code-block]>&]:top-4 [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-2 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-2 [[data-slot=code-block]>&]:pointer-coarse:opacity-100 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10",
className
)}
{...props}
/>
)
}
function CodeBlockCopyButton({
value,
variant = "ghost",
size = "icon-xs",
className,
onClick,
...props
}: React.ComponentProps<typeof Button> & { value: string }) {
const [copied, setCopied] = React.useState(false)
React.useEffect(() => {
if (!copied) {
return
}
const timeout = setTimeout(() => setCopied(false), 2000)
return () => clearTimeout(timeout)
}, [copied])
return (
<>
<Button
data-slot="code-block-copy-button"
data-copied={copied || undefined}
variant={variant}
size={size}
aria-label={copied ? "Copied" : "Copy"}
className={cn(
"hover:bg-foreground/5 dark:hover:bg-foreground/10",
className
)}
onClick={(event) => {
onClick?.(event)
navigator.clipboard
.writeText(value)
.then(() => setCopied(true))
.catch(() => {})
}}
{...props}
>
{copied ? (
<CheckIcon
/>
) : (
<CopyIcon
/>
)}
</Button>
<span aria-live="polite" className="sr-only">
{copied ? "Copied" : ""}
</span>
</>
)
}
function CodeBlockContent({
className,
children,
style,
wrap = false,
lineNumbers = false,
startLine = 1,
...props
}: React.ComponentProps<"pre"> & {
wrap?: boolean
lineNumbers?: boolean
startLine?: number
}) {
return (
<pre
data-slot="code-block-content"
data-wrap={wrap || undefined}
data-numbered={lineNumbers || undefined}
dir="ltr"
tabIndex={0}
style={{ counterReset: `line ${startLine - 1}`, ...style }}
className={cn(
"min-h-0 min-w-0 scroll-fade-x scrollbar-thin overflow-auto px-5 py-5 text-start font-mono leading-relaxed outline-none group-has-data-[slot=code-block-footer]/code-block:pb-2 group-has-data-[slot=code-block-header]/code-block:pt-2 group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-3 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-1.5 data-wrap:scroll-fade-y data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap",
className
)}
{...props}
>
<code
data-slot="code-block-code"
className="grid min-h-lh min-w-max content-start in-data-numbered:grid-cols-[auto_1fr] in-data-wrap:min-w-0"
>
{lineNumbers && typeof children === "string"
? children
.replace(/\n$/, "")
.split("\n")
.map((line, index) => (
<CodeBlockLine key={index}>{line}</CodeBlockLine>
))
: children}
</code>
</pre>
)
}
const codeBlockLineVariants = cva(
"relative -mx-5 block min-h-lh px-5 group-data-[size=sm]/code-block:-mx-3 group-data-[size=sm]/code-block:px-3 before:min-w-6 before:text-end before:text-muted-foreground after:absolute after:start-1 after:top-0 after:text-muted-foreground after:select-none in-data-numbered:col-span-2 in-data-numbered:grid in-data-numbered:grid-cols-subgrid in-data-numbered:gap-x-3 in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']",
{
variants: {
variant: {
default: "",
added: "bg-foreground/5 after:content-['+']",
removed: "bg-destructive/10 after:text-destructive after:content-['−']",
highlighted: "bg-foreground/5",
},
},
defaultVariants: {
variant: "default",
},
}
)
function CodeBlockLine({
className,
variant = "default",
children,
...props
}: React.ComponentProps<"span"> & VariantProps<typeof codeBlockLineVariants>) {
// An empty line holds a newline so copying a selection keeps blank lines.
const empty =
children === undefined ||
children === null ||
children === "" ||
(Array.isArray(children) && children.length === 0)
return (
<span
data-slot="code-block-line"
data-variant={variant}
className={cn(codeBlockLineVariants({ variant }), className)}
{...props}
>
<span data-slot="code-block-line-content" className="min-w-0">
{empty ? "\n" : children}
</span>
</span>
)
}
function CodeBlockFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="code-block-footer"
className={cn(
"flex min-w-0 shrink-0 items-center gap-2 px-5 pb-5 text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pb-3",
className
)}
{...props}
/>
)
}
export {
CodeBlock,
CodeBlockHeader,
CodeBlockTitle,
CodeBlockActions,
CodeBlockCopyButton,
CodeBlockContent,
CodeBlockLine,
CodeBlockFooter,
}