000h-cojeev
uipreviewsource: ok · hit
Component preview
Pairs a live component example with a code view and a copy action.
npx shadcn add "https://000h.cojeev.com/r/preview.json"free
Where it goes
testimonial
Visual style
terminal-mono
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
https://cojeev.com/ui/r/cojeev.jsonhttps://cojeev.com/ui/r/code-block.jsonhttps://cojeev.com/ui/r/animated-icon.jsonhttps://cojeev.com/ui/r/tabs.jsonhttps://cojeev.com/ui/r/typography.jsonhttps://cojeev.com/ui/r/pattern-background.jsonhttps://cojeev.com/ui/r/pigment-field.jsonhttps://cojeev.com/ui/r/depth-background.jsonhttps://cojeev.com/ui/r/ambient-background.jsonhttps://cojeev.com/ui/r/button.jsonhttps://cojeev.com/ui/r/icon.jsonhttps://cojeev.com/ui/r/popover.json
Files4 · source cached 10/14/2026, 1:32:19 PM
registry/cojeev/ui/preview.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { CodeBlock, CopyButton } from "@/components/ui/code-block";
import { AnimatedIcon } from "@/components/ui/animated-icon";
import { PreviewBackgroundPaint, type PreviewBackgroundVariant } from "@/lib/cojeev/preview-backgrounds";
export type { PreviewBackgroundVariant } from "@/lib/cojeev/preview-backgrounds";
import { PreviewBackgroundPicker } from "@/lib/cojeev/preview-background-picker";
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@/components/ui/tabs";
import { Title, BodySecondary } from "@/components/ui/typography";
export type PreviewProps = React.ComponentProps<"section"> & {
code: string;
title?: string;
description?: string;
/** Optional controls shown with the live specimen. */
controls?: React.ReactNode;
/** Optional actions beside the view switcher. */
actions?: React.ReactNode;
/** Controlled background. Without onPatternChange the background is fixed. */
pattern?: PreviewBackgroundVariant;
/** Initial background for an uncontrolled preview. */
defaultPattern?: PreviewBackgroundVariant;
onPatternChange?: (pattern: PreviewBackgroundVariant) => void;
showBackgroundPicker?: boolean;
/** Reports the source copy outcome without coupling the registry to analytics. */
onCopyResult?: (result: "success" | "failure") => void;
};
export function Preview({
children,
code,
title,
description,
controls,
actions,
pattern,
defaultPattern = "dots",
onPatternChange,
showBackgroundPicker = true,
onCopyResult,
className,
...props
}: PreviewProps) {
const [localPattern, setLocalPattern] = React.useState(defaultPattern);
const [view, setView] = React.useState("preview");
const selectedPattern = pattern ?? localPattern;
const changePattern = (next: PreviewBackgroundVariant) => {
if (pattern === undefined) setLocalPattern(next);
onPatternChange?.(next);
};
return (
<section
data-slot="preview"
className={cn("v-preview", className)}
{...props}
>
{title && <Title as="h3">{title}</Title>}
{description && <BodySecondary>{description}</BodySecondary>}
<Tabs value={view} onValueChange={setView} variant="pills" className="v-preview__frame">
<div className="v-preview__toolbar">
<TabsList aria-label={title ? `${title} view` : "Example view"}>
<TabsTrigger value="preview"><AnimatedIcon name="eye" size="sm" aria-hidden="true" />Preview</TabsTrigger>
<TabsTrigger value="code"><AnimatedIcon name="code" size="sm" aria-hidden="true" />Code</TabsTrigger>
</TabsList>
<div className="v-preview__actions" role="group" aria-label="Example tools">
{showBackgroundPicker && <PreviewBackgroundPicker value={selectedPattern} onChange={changePattern} disabled={view !== "preview" || (pattern !== undefined && !onPatternChange)} />}
{actions}
</div>
<CopyButton code={code} onCopyResult={onCopyResult}>Copy code</CopyButton>
</div>
<TabsContent value="preview" forceMount hidden={view !== "preview"} className="v-preview__panel">
{controls && <div className="v-preview__controls">{controls}</div>}
<div
data-slot="preview-canvas"
className="v-preview__canvas"
style={
{ "--muted-foreground": "var(--v-text-2)" } as React.CSSProperties
}
>
<PreviewBackgroundPaint value={selectedPattern} />
{children}
</div>
</TabsContent>
<TabsContent value="code" forceMount hidden={view !== "code"} className="v-preview__panel">
<CodeBlock embedded code={code} language="tsx" title="Example" />
</TabsContent>
</Tabs>
</section>
);
}
registry/cojeev/lib/preview-backgrounds.tsx
"use client";
import * as React from "react";
import { PatternBackground } from "@/components/ui/pattern-background";
import { useChoreography } from "@/lib/cojeev-motion/choreography";
import { subtleBackgrounds, type PatternBackgroundVariant } from "@/lib/cojeev/subtle-backgrounds";
const PigmentField = React.lazy(() => import("@/components/ui/pigment-field").then(module => ({ default: module.PigmentField })));
const DepthBackground = React.lazy(() => import("@/components/ui/depth-background").then(module => ({ default: module.DepthBackground })));
const AmbientBackground = React.lazy(() => import("@/components/ui/ambient-background").then(module => ({ default: module.AmbientBackground })));
const livingBackgrounds = [
{ value: "pigment", label: "Pigment wash", description: "Slowly blending color with a fine pigment texture." },
{ value: "pollen", label: "Pollen", description: "Soft pieces floating at different depths." },
{ value: "depth-contour", label: "Depth contours", description: "Layered organic contours around the edges." },
{ value: "orbital", label: "Orbital", description: "Small orbiting shapes with perspective." },
{ value: "ambient-drift", label: "Ambient drift", description: "Sparse shapes drifting past the canvas edges." },
{ value: "ambient-orbit", label: "Ambient orbit", description: "A slow orbit of soft, colored shapes." },
{ value: "ambient-contour", label: "Ambient contours", description: "Broad curved bands framing the content." },
] as const;
export type PreviewBackgroundVariant = PatternBackgroundVariant | typeof livingBackgrounds[number]["value"];
export const previewBackgroundGroups = [
{ label: "Atmosphere", choices: livingBackgrounds },
{ label: "Patterns", choices: subtleBackgrounds },
] as const;
/** Swatches are still CSS/SVG paint, never seven additional animation engines. */
export function PreviewBackgroundThumbnail({ value }: { value: PreviewBackgroundVariant }) {
if (subtleBackgrounds.some(choice => choice.value === value)) {
return <PatternBackground variant={value as PatternBackgroundVariant} spacing={12} opacity={.25} />;
}
return <span className="v-preview-background-thumbnail" data-preview-thumbnail={value} aria-hidden="true" />;
}
function LivingPreviewBackground({ value }: { value: Exclude<PreviewBackgroundVariant, PatternBackgroundVariant> }) {
const { quiet } = useChoreography();
let content: React.ReactNode;
if (value === "pigment") content = <PigmentField speed={.45} intensity={.8} paused={quiet} />;
else if (value === "pollen" || value === "depth-contour" || value === "orbital") {
content = <DepthBackground variant={value === "depth-contour" ? "contour" : value} density={.6} intensity={quiet ? 0 : .45} seed="preview" />;
} else {
content = <AmbientBackground variant={value === "ambient-orbit" ? "orbit" : value === "ambient-contour" ? "contour" : "drift"} paused={quiet} />;
}
return <div data-preview-background={value} className="v-preview-background" aria-hidden="true" inert>
<React.Suspense fallback={<PreviewBackgroundThumbnail value={value} />}>{content}</React.Suspense>
</div>;
}
/** Only the chosen field mounts. The specimen stays a separate, stable sibling. */
export function PreviewBackgroundPaint({ value }: { value: PreviewBackgroundVariant }) {
if (value === "none") return null;
if (livingBackgrounds.some(choice => choice.value === value)) {
return <LivingPreviewBackground value={value as Exclude<PreviewBackgroundVariant, PatternBackgroundVariant>} />;
}
return <PatternBackground variant={value as PatternBackgroundVariant} opacity={.12} />;
}
registry/cojeev/lib/preview-background-picker.tsx
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover";
import { PreviewBackgroundThumbnail, previewBackgroundGroups, type PreviewBackgroundVariant } from "@/lib/cojeev/preview-backgrounds";
/** One visual choice, outside document flow; selecting paint never remounts content. */
export function PreviewBackgroundPicker({ value, onChange, disabled = false }: {
value: PreviewBackgroundVariant;
onChange: (value: PreviewBackgroundVariant) => void;
disabled?: boolean;
}) {
const [open, setOpen] = React.useState(false);
const choiceButton = (choice: { value: PreviewBackgroundVariant; label: string; description: string }) => <Button key={choice.value} shape="card" variant="ghost" className="v-preview-background-picker__choice" aria-label={choice.label} aria-pressed={value === choice.value} title={choice.description}
onClick={() => { onChange(choice.value); setOpen(false); }}>
<span className="v-preview-background-picker__swatch" aria-hidden="true">
{choice.value === "none" ? <Icon name="ban" size="sm" /> : <PreviewBackgroundThumbnail value={choice.value} />}
</span>
<span className="v-preview-background-picker__label">{choice.label}<span className="v-preview-background-picker__check" aria-hidden="true">{value === choice.value && <Icon name="check" size="sm" />}</span></span>
</Button>;
return <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button size="sm" variant="ghost" disabled={disabled} title={disabled ? "Background is fixed here, or switch to Preview to change it." : "Choose a preview background"}>
<Icon name="paintbrush" size="sm" aria-hidden="true" />Background
</Button>
</PopoverTrigger>
<PopoverContent align="end" collisionPadding={12} className="v-preview-background-picker" aria-label="Choose preview background">
<div className="v-preview-background-picker__heading">
<strong>Preview backgrounds</strong>
<span>Change the canvas. Keep your example.</span>
</div>
<div className="v-preview-background-picker__plain">{choiceButton({ value: "none", label: "Plain", description: "No decorative background." })}</div>
{previewBackgroundGroups.map(group => <div key={group.label} className="v-preview-background-picker__group" role="group" aria-label={group.label}>
<strong className="v-preview-background-picker__group-label">{group.label}</strong>
<div className="v-preview-background-picker__choices">{group.choices.map(choiceButton)}</div>
</div>)}
</PopoverContent>
</Popover>;
}
registry/cojeev/styles/preview.css
@layer cojeev-states {
@layer cojeev-states {
.v-preview {
display: grid;
gap: var(--s-4);
min-width: 0;
container: preview / inline-size;
}
.v-preview__frame {
min-width: 0;
border: 1px solid color-mix(in srgb, var(--v-border) 72%, transparent);
border-radius: var(--r-card-sm);
background: var(--v-paper);
}
.v-preview__toolbar {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
justify-content: space-between;
gap: var(--s-3);
padding: var(--s-3) var(--s-4);
border-radius: calc(var(--r-card-sm) - 1px) calc(var(--r-card-sm) - 1px) 0 0;
background: var(--v-paper);
border-block-end: 1px solid
color-mix(in srgb, var(--v-border) 60%, transparent);
}
.v-preview__toolbar > [data-slot="tabs-list"] {
align-self: stretch;
align-items: center;
gap: var(--s-1);
padding: var(--s-1);
}
.v-preview__toolbar > [data-slot="tabs-list"] > [data-slot="tabs-trigger"] {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--s-2);
min-height: 40px;
padding-block: var(--s-2);
font-size: var(--fs-small);
}
.v-preview__panel {
min-width: 0;
}
.v-preview__actions {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: var(--s-2);
min-width: 0;
justify-content: end;
}
.v-preview__controls {
padding: var(--s-4) var(--s-5);
border-block-end: 1px solid var(--v-border);
}
.v-preview .v-preview__canvas {
position: relative;
isolation: isolate;
display: grid;
align-content: center;
min-width: 0;
min-height: 280px;
margin: 0;
overflow: visible;
padding: clamp(20px, 4vw, 48px);
border: 0;
border-radius: 0 0 var(--r-card-sm) var(--r-card-sm);
background: color-mix(in srgb, var(--v-paper) 96%, var(--v-beige));
color: var(--v-text);
}
.v-preview__canvas > .v-btn {
justify-self: start;
max-width: 100%;
}
@media (max-width: 600px) {
.v-preview__toolbar,
.v-preview__controls {
padding-inline: var(--s-4);
}
.v-preview .v-preview__canvas {
min-height: 240px;
padding: var(--s-5) var(--s-4);
}
}
}
.v-preview__panel[hidden] {
display: none;
}
.v-preview__toolbar > [data-slot="copy-control"] {
justify-self: end;
}
.v-preview__toolbar .docs-playground-actions {
display: contents;
}
.v-preview__toolbar .v-btn {
font-size: var(--fs-meta);
min-height: 40px;
padding-inline: var(--s-3);
gap: var(--s-2);
}
.v-preview__toolbar [data-slot="icon"] {
width: 17px;
height: 17px;
}
@container preview (max-width: 850px) {
.v-preview__toolbar {
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--s-3);
}
.v-preview__toolbar > [data-slot="tabs-list"] {
justify-self: start;
}
.v-preview__toolbar > [data-slot="copy-control"] {
grid-column: 2;
grid-row: 1;
}
.v-preview__actions {
grid-column: 1 / -1;
grid-row: 2;
justify-content: start;
padding-top: var(--s-3);
border-top: 1px solid color-mix(in srgb, var(--v-border) 50%, transparent);
}
}
@container preview (max-width: 460px) {
.v-preview__toolbar {
padding: var(--s-3);
}
.v-preview__toolbar > [data-slot="tabs-list"] {
gap: 2px;
padding: 0;
}
.v-preview__toolbar > [data-slot="tabs-list"] > [data-slot="tabs-trigger"] {
gap: var(--s-1);
padding-inline: var(--s-2);
font-size: var(--fs-meta);
}
.v-preview__toolbar > [data-slot="copy-control"] .v-btn {
padding-inline: var(--s-2);
}
.v-preview__actions {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--s-2);
}
.v-preview__actions .v-btn {
display: flex;
flex-direction: column;
gap: var(--s-1);
min-width: 0;
width: 100%;
min-height: 60px;
padding: var(--s-2) 3px;
white-space: normal;
text-align: center;
line-height: 1.25;
border-radius: var(--r-md);
}
}
/* Preview joins its tab bar and canvas into one frame. */
@container preview (max-width: 350px) {
.v-preview__toolbar {
grid-template-columns: minmax(0, 1fr);
}
.v-preview__toolbar > [data-slot="tabs-list"] {
width: 100%;
justify-content: center;
overflow: visible;
}
.v-preview__toolbar > [data-slot="copy-control"] {
grid-column: 1;
grid-row: 2;
}
.v-preview__actions {
grid-row: 3;
}
}
[data-slot="tabs"].v-preview__frame {
gap: 0;
}
/* The host owns embedded code corners; keep this alongside CodeBlock's base layer. */
.v-preview__panel > .v-code-block[data-slot="code-block"][data-embedded] {
border-radius: 0 0 calc(var(--r-card-sm) - 1px) calc(var(--r-card-sm) - 1px);
}
.v-preview-background-picker[data-slot="popover-content"] {
width: min(392px, calc(100vw - 24px));
max-height: min(620px, var(--radix-popover-content-available-height));
overflow: auto;
padding: var(--s-4);
border-radius: var(--r-card-sm);
background: var(--v-paper);
color: var(--v-text);
}
.v-preview-background-picker__heading {
display: grid;
gap: var(--s-1);
margin-bottom: var(--s-4);
}
.v-preview-background-picker__heading strong {
font: var(--fw-semibold) var(--fs-body)/1.4 var(--font-text);
}
.v-preview-background-picker__heading > span {
font: var(--fs-meta)/1.5 var(--font-text);
color: var(--v-text-2);
}
.v-preview-background-picker__group {
margin-top: var(--s-4);
}
.v-preview-background-picker__group-label {
display: block;
margin-bottom: var(--s-2);
font: var(--fw-medium) var(--fs-meta)/1.4 var(--font-text);
color: var(--v-text-2);
}
.v-preview-background-picker__choices {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--s-2);
}
.v-preview-background-picker__choice.v-btn {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: start;
gap: var(--s-2);
height: auto;
min-width: 0;
min-height: 86px;
padding: var(--s-1);
border-radius: var(--r-md);
text-align: start;
white-space: normal;
}
.v-preview-background-picker__choice[aria-pressed="true"] {
background: var(--v-beige);
box-shadow: inset 0 0 0 1px var(--v-text-3);
}
.v-preview-background-picker__swatch {
position: relative;
isolation: isolate;
display: grid;
place-items: center;
overflow: hidden;
width: 100%;
height: 52px;
border-radius: var(--r-sm);
background: var(--v-canvas);
color: var(--v-text-3);
}
.v-preview-background-picker__label {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2px;
min-width: 0;
color: var(--v-text);
font: var(--fs-meta)/1.4 var(--font-text);
}
.v-preview-background-picker__check {
display: grid;
place-items: center;
width: 14px;
height: 16px;
flex: none;
}
.v-preview-background-picker__check [data-slot="icon"] {
width: 13px;
height: 13px;
}
.v-preview-background-picker__plain .v-preview-background-picker__choice {
width: 100%;
min-height: 40px;
flex-direction: row;
align-items: center;
padding: var(--s-2) var(--s-3);
}
.v-preview-background-picker__plain .v-preview-background-picker__swatch {
width: 24px;
height: 24px;
background: none;
flex: none;
}
.v-preview-background-picker__plain .v-preview-background-picker__label {
flex: 1;
}
/* A single selected library field, behind stable foreground content. */
.v-preview-background {
position: absolute;
inset: 0;
z-index: -1;
overflow: hidden;
border-radius: inherit;
pointer-events: none;
user-select: none;
opacity: 0.46;
}
.v-preview-background > [data-slot="ambient-background"] {
position: absolute;
inset: 0;
min-block-size: 0;
padding: 0;
border-radius: inherit;
background: transparent;
}
.v-preview-background
> [data-slot="ambient-background"]
[data-slot="ambient-background-decoration"] {
opacity: 0.65;
}
.v-preview-background-thumbnail {
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
overflow: hidden;
}
.v-preview-background-thumbnail[data-preview-thumbnail="pigment"] {
background:
radial-gradient(
ellipse at 12% 30%,
color-mix(in srgb, var(--v-pink) 65%, transparent),
transparent 65%
),
radial-gradient(
ellipse at 80% 90%,
color-mix(in srgb, var(--v-blue) 65%, transparent),
transparent 65%
),
radial-gradient(
ellipse at 75% 0%,
color-mix(in srgb, var(--v-yellow) 45%, transparent),
transparent 60%
);
}
.v-preview-background-thumbnail[data-preview-thumbnail="pollen"] {
background:
radial-gradient(circle at 20% 30%, var(--v-pink) 2px, transparent 6px),
radial-gradient(circle at 80% 64%, var(--v-blue) 3px, transparent 7px),
radial-gradient(circle at 16% 88%, var(--v-yellow) 1px, transparent 4px),
radial-gradient(circle at 78% 10%, var(--v-olive) 2px, transparent 3px);
}
.v-preview-background-thumbnail[data-preview-thumbnail="depth-contour"] {
background: repeating-radial-gradient(
ellipse at -15% 40%,
transparent 0 9px,
color-mix(in srgb, var(--v-blue) 50%, transparent) 10px 11px,
transparent 12px 18px
);
}
.v-preview-background-thumbnail[data-preview-thumbnail="orbital"] {
background:
radial-gradient(
ellipse 16px 8px at 22% 40%,
transparent 80%,
var(--v-blue) 85% 92%,
transparent 98%
),
radial-gradient(
ellipse 8px 16px at 22% 40%,
transparent 80%,
var(--v-pink) 85% 92%,
transparent 98%
),
radial-gradient(
ellipse 14px 6px at 80% 70%,
transparent 80%,
var(--v-yellow) 85% 92%,
transparent 98%
);
}
.v-preview-background-thumbnail[data-preview-thumbnail^="ambient-"]::before,
.v-preview-background-thumbnail[data-preview-thumbnail^="ambient-"]::after {
content: "";
position: absolute;
width: 60%;
height: 95%;
border-radius: 42% 58% 64% 36%;
background: var(--v-pink);
opacity: 0.4;
right: -12%;
top: -30%;
transform: rotate(25deg);
}
.v-preview-background-thumbnail[data-preview-thumbnail^="ambient-"]::after {
left: -15%;
top: 60%;
background: var(--v-olive);
}
.v-preview-background-thumbnail[data-preview-thumbnail="ambient-orbit"]::before {
right: 25%;
top: -65%;
background: var(--v-yellow);
border-radius: 50%;
}
.v-preview-background-thumbnail[data-preview-thumbnail="ambient-orbit"]::after {
left: 55%;
top: 45%;
background: var(--v-blue);
border-radius: 50%;
}
.v-preview-background-thumbnail[data-preview-thumbnail="ambient-contour"] {
background: repeating-radial-gradient(
ellipse at 110% -40%,
transparent 0 10px,
color-mix(in srgb, var(--v-olive) 35%, transparent) 11px 15px,
transparent 16px 24px
);
}
.v-preview-background-thumbnail[data-preview-thumbnail="ambient-contour"]::before,
.v-preview-background-thumbnail[data-preview-thumbnail="ambient-contour"]::after {
display: none;
}
@media (forced-colors: active) {
.v-preview-background,
.v-preview-background-thumbnail {
display: none;
}
}
}