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]>
This commit is contained in:
1 parent
57d65f0f08
commit
78c8b702f0
79 files changed
+11234
-14
No files matched your search
@@ -0,0 +1,252 @@
|
||||
"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,
|
||||
}
|
||||
Reference in new issue
Block a user