snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
"use client"
import { Alert, AlertDescription } from "@/registry/bases/radix/ui/alert"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Item,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/radix/ui/item"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// Agent feature descriptions.
const agentFeatures = [
{
id: "code-reviews",
content: (
<>
<strong>Code reviews</strong> with full codebase context to catch{" "}
<strong>hard-to-find</strong> bugs.
</>
),
},
{
id: "code-suggestions",
content: (
<>
<strong>Code suggestions</strong> validated in sandboxes before you
merge.
</>
),
},
{
id: "root-cause",
content: (
<>
<strong>Root-cause analysis</strong> for production issues with
deployment context.{" "}
<Badge variant="secondary" className="bg-chart-1 text-chart-5">
Requires Observability Plus
</Badge>
</>
),
},
]
export function ActivateAgentDialog() {
return (
<Card>
<CardHeader>
<CardTitle>Ship faster & safer with Vercel Agent</CardTitle>
<CardDescription>
Your use is subject to Vercel&apos;s{" "}
<a href="#">Public Beta Agreement</a> and{" "}
<a href="#">AI Product Terms</a>.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<ItemGroup className="gap-0">
{agentFeatures.map((feature) => (
<Item key={feature.id} size="xs" className="px-0">
<ItemMedia variant="icon" className="self-start">
<IconPlaceholder
lucide="CheckCircle2Icon"
tabler="IconCircleCheckFilled"
hugeicons="CheckmarkCircle02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
className="size-5 fill-primary text-primary-foreground"
/>
</ItemMedia>
<ItemContent>
<ItemTitle className="inline leading-relaxed font-normal text-muted-foreground *:[strong]:font-medium *:[strong]:text-foreground">
{feature.content}
</ItemTitle>
</ItemContent>
</Item>
))}
</ItemGroup>
<Alert>
<AlertDescription>
Pro teams get $100 in Vercel Agent trial credit for 2 weeks after
activation.
</AlertDescription>
</Alert>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline" className="style-sera:hidden">
Cancel
</Button>
<Button className="style-sera:w-full">Enable with $100 credits</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,78 @@
"use client"
import { Area, AreaChart } from "recharts"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardAction,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/radix/ui/chart"
// Monthly visitor data for the area chart.
const chartData = [
{ month: "January", visitors: 186 },
{ month: "February", visitors: 305 },
{ month: "March", visitors: 237 },
{ month: "April", visitors: 73 },
{ month: "May", visitors: 209 },
{ month: "June", visitors: 214 },
]
// Chart configuration for the visitors dataset.
const chartConfig = {
visitors: {
label: "Visitors",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function AnalyticsCard() {
return (
<Card className="mx-auto w-full max-w-sm data-[size=sm]:pb-0" size="sm">
<CardHeader>
<CardTitle>Analytics</CardTitle>
<CardDescription>
418.2K Visitors <Badge>+10%</Badge>
</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
View Analytics
</Button>
</CardAction>
</CardHeader>
<ChartContainer config={chartConfig} className="aspect-[1/0.35]">
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 0,
right: 0,
}}
>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" hideLabel />}
defaultIndex={2}
/>
<Area
dataKey="visitors"
type="linear"
fill="var(--color-visitors)"
fillOpacity={0.4}
stroke="var(--color-visitors)"
/>
</AreaChart>
</ChartContainer>
</Card>
)
}

View File

@@ -0,0 +1,32 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/registry/bases/radix/ui/empty"
export function AnomalyAlert() {
return (
<Card>
<CardContent>
<Empty className="h-48">
<EmptyHeader>
<EmptyTitle>Get alerted for anomalies</EmptyTitle>
<EmptyDescription>
Automatically monitor your projects for anomalies and get
notified.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Upgrade to Observability Plus</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,128 @@
"use client"
import * as React from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/radix/ui/avatar"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent,
ComboboxEmpty,
ComboboxItem,
ComboboxList,
ComboboxValue,
useComboboxAnchor,
} from "@/registry/bases/radix/ui/combobox"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/radix/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// Users available for assignment.
const users = [
"shadcn",
"maxleiter",
"evilrabbit",
"pranathip",
"jorgezreik",
"shuding",
"rauchg",
]
export function AssignIssue() {
const anchor = useComboboxAnchor()
return (
<Card className="w-full max-w-sm" size="sm">
<CardHeader className="border-b">
<CardTitle className="text-sm">Assign Issue</CardTitle>
<CardDescription className="text-sm">
Select users to assign to this issue.
</CardDescription>
<CardAction>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon-xs">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
</TooltipTrigger>
<TooltipContent>Add user</TooltipContent>
</Tooltip>
</CardAction>
</CardHeader>
<CardContent>
<Combobox
multiple
autoHighlight
items={users}
defaultValue={[users[0]]}
>
<ComboboxChips ref={anchor}>
<ComboboxValue>
{(values) => (
<React.Fragment>
{values.map((username: string) => (
<ComboboxChip key={username}>
<Avatar className="size-4">
<AvatarImage
src={`https://github.com/${username}.png`}
alt={username}
/>
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar>
{username}
</ComboboxChip>
))}
<ComboboxChipsInput
placeholder={
values.length > 0 ? undefined : "Select a item..."
}
/>
</React.Fragment>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No users found.</ComboboxEmpty>
<ComboboxList>
{(username: string) => (
<ComboboxItem key={username} value={username}>
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${username}.png`}
alt={username}
/>
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar>
{username}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,133 @@
"use client"
import * as React from "react"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/radix/ui/chart"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
const barChartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const barChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
const desktopTotal = barChartData.reduce((sum, item) => sum + item.desktop, 0)
const mobileTotal = barChartData.reduce((sum, item) => sum + item.mobile, 0)
const desktopDelta = Math.round(
((desktopTotal - mobileTotal) / mobileTotal) * 100
)
const desktopDeltaPrefix = desktopDelta > 0 ? "+" : ""
export function BarChartCard() {
const [params] = useDesignSystemSearchParams()
const isRounded = !["lyra", "sera"].includes(params.style)
return (
<Card>
<CardHeader>
<CardTitle className="text-lg">Traffic channels</CardTitle>
<CardDescription className="line-clamp-2 text-sm leading-snug">
Monthly desktop and mobile traffic for the last six months—compare
volume and mix across platforms and devices at a glance.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4 pt-0">
<ChartContainer
config={barChartConfig}
className="max-h-[180px] w-full"
>
<BarChart
accessibilityLayer
data={barChartData}
margin={{ left: 0, right: 0, top: 8, bottom: 0 }}
>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={8}
axisLine={false}
tickFormatter={(value) => String(value).slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dashed" />}
/>
<ChartLegend content={<ChartLegendContent />} />
<Bar
dataKey="desktop"
fill="var(--color-desktop)"
radius={isRounded ? [6, 6, 0, 0] : 0}
/>
<Bar
dataKey="mobile"
fill="var(--color-mobile)"
radius={isRounded ? [6, 6, 0, 0] : 0}
/>
</BarChart>
</ChartContainer>
<div className="grid w-full grid-cols-3 divide-x divide-border/60">
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Desktop
</div>
<div className="text-sm font-medium tabular-nums">
{desktopTotal.toLocaleString()}
</div>
</div>
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Mobile
</div>
<div className="text-sm font-medium tabular-nums">
{mobileTotal.toLocaleString()}
</div>
</div>
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Mix Delta
</div>
<div className="text-sm font-medium tabular-nums">
{desktopDeltaPrefix}
{desktopDelta}%
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full">View report</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,483 @@
"use client"
import * as React from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import { cn } from "cn"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
type AgentState =
| "connecting"
| "initializing"
| "listening"
| "speaking"
| "thinking"
type AnimationState = AgentState | undefined
interface AudioAnalyserOptions {
fftSize?: number
smoothingTimeConstant?: number
minDecibels?: number
maxDecibels?: number
}
interface MultiBandVolumeOptions {
bands?: number
loPass?: number
hiPass?: number
updateInterval?: number
analyserOptions?: AudioAnalyserOptions
}
const multibandDefaults: MultiBandVolumeOptions = {
bands: 5,
loPass: 100,
hiPass: 600,
updateInterval: 32,
analyserOptions: { fftSize: 2048 },
}
const normalizeDb = (value: number) => {
if (value === -Infinity) return 0
const minDb = -100
const maxDb = -10
const db = 1 - (Math.max(minDb, Math.min(maxDb, value)) * -1) / 100
return Math.sqrt(db)
}
function createAudioAnalyser(
mediaStream: MediaStream,
options: AudioAnalyserOptions = {}
) {
const audioContext = new (window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext)()
const source = audioContext.createMediaStreamSource(mediaStream)
const analyser = audioContext.createAnalyser()
if (options.fftSize) analyser.fftSize = options.fftSize
if (options.smoothingTimeConstant !== undefined) {
analyser.smoothingTimeConstant = options.smoothingTimeConstant
}
if (options.minDecibels !== undefined)
analyser.minDecibels = options.minDecibels
if (options.maxDecibels !== undefined)
analyser.maxDecibels = options.maxDecibels
source.connect(analyser)
const cleanup = () => {
source.disconnect()
audioContext.close()
}
return { analyser, audioContext, cleanup }
}
function useMultibandVolume(
mediaStream?: MediaStream | null,
options: MultiBandVolumeOptions = {}
) {
const opts = useMemo(() => ({ ...multibandDefaults, ...options }), [options])
const [frequencyBands, setFrequencyBands] = useState<number[]>(() =>
new Array(opts.bands).fill(0)
)
const bandsRef = useRef<number[]>(new Array(opts.bands).fill(0))
const frameId = useRef<number | undefined>(undefined)
useEffect(() => {
if (!mediaStream) {
const emptyBands = new Array(opts.bands).fill(0)
setTimeout(() => {
setFrequencyBands(emptyBands)
}, 0)
bandsRef.current = emptyBands
return
}
const { analyser, cleanup } = createAudioAnalyser(
mediaStream,
opts.analyserOptions
)
const bufferLength = analyser.frequencyBinCount
const dataArray = new Float32Array(bufferLength)
const sliceStart = opts.loPass!
const sliceEnd = opts.hiPass!
const sliceLength = sliceEnd - sliceStart
const chunkSize = Math.ceil(sliceLength / opts.bands!)
let lastUpdate = 0
const updateInterval = opts.updateInterval!
const updateVolume = (timestamp: number) => {
if (timestamp - lastUpdate >= updateInterval) {
analyser.getFloatFrequencyData(dataArray)
const chunks = new Array(opts.bands!)
for (let i = 0; i < opts.bands!; i++) {
let sum = 0
let count = 0
const startIdx = sliceStart + i * chunkSize
const endIdx = Math.min(sliceStart + (i + 1) * chunkSize, sliceEnd)
for (let j = startIdx; j < endIdx; j++) {
sum += normalizeDb(dataArray[j])
count++
}
chunks[i] = count > 0 ? sum / count : 0
}
let hasChanged = false
for (let i = 0; i < chunks.length; i++) {
if (Math.abs(chunks[i] - bandsRef.current[i]) > 0.01) {
hasChanged = true
break
}
}
if (hasChanged) {
bandsRef.current = chunks
setFrequencyBands(chunks)
}
lastUpdate = timestamp
}
frameId.current = requestAnimationFrame(updateVolume)
}
frameId.current = requestAnimationFrame(updateVolume)
return () => {
cleanup()
if (frameId.current) {
cancelAnimationFrame(frameId.current)
}
}
}, [mediaStream, opts])
return frequencyBands
}
const generateConnectingSequenceBar = (columns: number): number[][] => {
const seq = []
for (let x = 0; x < columns; x++) {
seq.push([x, columns - 1 - x])
}
return seq
}
const generateListeningSequenceBar = (columns: number): number[][] => {
const center = Math.floor(columns / 2)
const noIndex = -1
return [[center], [noIndex]]
}
const useBarAnimator = (
state: AnimationState,
columns: number,
interval: number
): number[] => {
const indexRef = useRef(0)
const [currentFrame, setCurrentFrame] = useState<number[]>([])
const animationFrameId = useRef<number | null>(null)
const sequence = useMemo(() => {
if (state === "thinking" || state === "listening") {
return generateListeningSequenceBar(columns)
} else if (state === "connecting" || state === "initializing") {
return generateConnectingSequenceBar(columns)
} else if (state === undefined || state === "speaking") {
return [new Array(columns).fill(0).map((_, idx) => idx)]
} else {
return [[]]
}
}, [state, columns])
useEffect(() => {
indexRef.current = 0
setTimeout(() => {
setCurrentFrame(sequence[0] || [])
}, 0)
}, [sequence])
useEffect(() => {
let startTime = performance.now()
const animate = (time: DOMHighResTimeStamp) => {
const timeElapsed = time - startTime
if (timeElapsed >= interval) {
indexRef.current = (indexRef.current + 1) % sequence.length
setCurrentFrame(sequence[indexRef.current] || [])
startTime = time
}
animationFrameId.current = requestAnimationFrame(animate)
}
animationFrameId.current = requestAnimationFrame(animate)
return () => {
if (animationFrameId.current !== null) {
cancelAnimationFrame(animationFrameId.current)
}
}
}, [interval, sequence])
return currentFrame
}
// Memoized Bar component to prevent unnecessary re-renders.
const Bar = React.memo<{
heightPct: number
isHighlighted: boolean
state?: AgentState
}>(({ heightPct, isHighlighted, state }) => (
<div
data-highlighted={isHighlighted}
className={cn(
"max-w-[12px] min-w-[8px] flex-1 transition-all duration-150",
"rounded-full",
"bg-border data-[highlighted=true]:bg-chart-3",
state === "speaking" && "bg-chart-3",
state === "thinking" && isHighlighted && "animate-pulse"
)}
style={{
height: `${heightPct}%`,
animationDuration: state === "thinking" ? "300ms" : undefined,
}}
/>
))
Bar.displayName = "Bar"
const BarVisualizerComponent = React.forwardRef<
HTMLDivElement,
{
state?: AgentState
barCount?: number
mediaStream?: MediaStream | null
minHeight?: number
maxHeight?: number
demo?: boolean
centerAlign?: boolean
} & React.HTMLAttributes<HTMLDivElement>
>(
(
{
state,
barCount = 15,
mediaStream,
minHeight = 20,
maxHeight = 100,
demo = false,
centerAlign = false,
className,
style,
...props
},
ref
) => {
const realVolumeBands = useMultibandVolume(mediaStream, {
bands: barCount,
loPass: 100,
hiPass: 200,
})
const fakeVolumeBandsRef = useRef<number[]>(new Array(barCount).fill(0.2))
const [fakeVolumeBands, setFakeVolumeBands] = useState<number[]>(() =>
new Array(barCount).fill(0.2)
)
const fakeAnimationRef = useRef<number | undefined>(undefined)
useEffect(() => {
if (!demo) return
if (state !== "speaking" && state !== "listening") {
const bands = new Array(barCount).fill(0.2)
fakeVolumeBandsRef.current = bands
setTimeout(() => {
setFakeVolumeBands(bands)
}, 0)
return
}
let lastUpdate = 0
const updateInterval = 50
const startTime = Date.now() / 1000
const updateFakeVolume = (timestamp: number) => {
if (timestamp - lastUpdate >= updateInterval) {
const time = Date.now() / 1000 - startTime
const newBands = new Array(barCount)
for (let i = 0; i < barCount; i++) {
const waveOffset = i * 0.5
const baseVolume = Math.sin(time * 2 + waveOffset) * 0.3 + 0.5
const randomNoise = Math.random() * 0.2
newBands[i] = Math.max(0.1, Math.min(1, baseVolume + randomNoise))
}
let hasChanged = false
for (let i = 0; i < barCount; i++) {
if (Math.abs(newBands[i] - fakeVolumeBandsRef.current[i]) > 0.05) {
hasChanged = true
break
}
}
if (hasChanged) {
fakeVolumeBandsRef.current = newBands
setFakeVolumeBands(newBands)
}
lastUpdate = timestamp
}
fakeAnimationRef.current = requestAnimationFrame(updateFakeVolume)
}
fakeAnimationRef.current = requestAnimationFrame(updateFakeVolume)
return () => {
if (fakeAnimationRef.current) {
cancelAnimationFrame(fakeAnimationRef.current)
}
}
}, [demo, state, barCount])
const volumeBands = useMemo(
() => (demo ? fakeVolumeBands : realVolumeBands),
[demo, fakeVolumeBands, realVolumeBands]
)
const highlightedIndices = useBarAnimator(
state,
barCount,
state === "connecting"
? 2000 / barCount
: state === "thinking"
? 150
: state === "listening"
? 500
: 1000
)
return (
<div
ref={ref}
data-state={state}
className={cn(
"relative flex justify-center gap-1.5",
centerAlign ? "items-center" : "items-end",
"h-32 w-full overflow-hidden rounded-lg bg-muted p-4",
className
)}
style={{
...style,
}}
{...props}
>
{volumeBands.map((volume, index) => {
const heightPct = Math.min(
maxHeight,
Math.max(minHeight, volume * 100 + 5)
)
const isHighlighted = highlightedIndices?.includes(index) ?? false
return (
<Bar
key={index}
heightPct={heightPct}
isHighlighted={isHighlighted}
state={state}
/>
)
})}
</div>
)
}
)
BarVisualizerComponent.displayName = "BarVisualizerComponent"
const BarVisualizer = React.memo(
BarVisualizerComponent,
(prevProps, nextProps) => {
return (
prevProps.state === nextProps.state &&
prevProps.barCount === nextProps.barCount &&
prevProps.mediaStream === nextProps.mediaStream &&
prevProps.minHeight === nextProps.minHeight &&
prevProps.maxHeight === nextProps.maxHeight &&
prevProps.demo === nextProps.demo &&
prevProps.centerAlign === nextProps.centerAlign &&
prevProps.className === nextProps.className &&
JSON.stringify(prevProps.style) === JSON.stringify(nextProps.style)
)
}
)
BarVisualizer.displayName = "BarVisualizer"
export function BarVisualizerCard() {
const [state, setState] = useState<AgentState>("speaking")
return (
<Card>
<CardHeader>
<CardTitle>Audio Frequency Visualizer</CardTitle>
<CardDescription>
Real-time frequency band visualization with animated state transitions
</CardDescription>
</CardHeader>
<CardContent>
<BarVisualizer
state={state}
demo={true}
barCount={20}
minHeight={15}
maxHeight={90}
className="h-40 max-w-full"
/>
</CardContent>
<CardFooter className="gap-2">
<Button
size="sm"
variant={state === "connecting" ? "default" : "outline"}
onClick={() => setState("connecting")}
>
Connecting
</Button>
<Button
size="sm"
variant={state === "listening" ? "default" : "outline"}
onClick={() => setState("listening")}
>
Listening
</Button>
<Button
size="sm"
variant={state === "speaking" ? "default" : "outline"}
onClick={() => setState("speaking")}
>
Speaking
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,56 @@
"use client"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/registry/bases/radix/ui/alert"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/radix/ui/field"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/radix/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function BookAppointment() {
return (
<Card>
<CardHeader>
<CardTitle>Book Appointment</CardTitle>
<CardDescription>Dr. Sarah Chen · Cardiology</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<FieldGroup>
<Field>
<FieldLabel>Available on March 18, 2026</FieldLabel>
<ToggleGroup type="multiple" spacing={2} defaultValue={["slot-0"]}>
{["9:00 AM", "10:30 AM", "11:00 AM", "1:30 PM"].map(
(time, index) => (
<ToggleGroupItem key={time} value={`slot-${index}`}>
{time}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</Field>
</FieldGroup>
<Alert>
<AlertTitle>New patient?</AlertTitle>
<AlertDescription>Please arrive 15 minutes early.</AlertDescription>
</Alert>
</CardContent>
<CardFooter>
<Button className="w-full">Book Appointment</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,390 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/radix/ui/dropdown-menu"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/radix/ui/empty"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/radix/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/radix/ui/item"
import { Separator } from "@/registry/bases/radix/ui/separator"
import { Spinner } from "@/registry/bases/radix/ui/spinner"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/radix/ui/tabs"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/radix/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function CodespacesCard() {
const [isCreatingCodespace, setIsCreatingCodespace] = React.useState(false)
return (
<Card>
<CardContent>
<Tabs defaultValue="codespaces">
<TabsList className="w-full">
<TabsTrigger value="codespaces">Codespaces</TabsTrigger>
<TabsTrigger value="local">Local</TabsTrigger>
</TabsList>
<TabsContent value="codespaces">
<Item size="sm" className="px-1 pt-2">
<ItemContent>
<ItemTitle>Codespaces</ItemTitle>
<ItemDescription>Your workspaces in the cloud</ItemDescription>
</ItemContent>
<ItemActions>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
Create a codespace on main
</TooltipContent>
</Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
New with options...
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ContainerIcon"
tabler="IconBox"
hugeicons="CubeIcon"
phosphor="CubeIcon"
remixicon="RiBox1Line"
/>
Configure container
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ZapIcon"
tabler="IconBolt"
hugeicons="ZapIcon"
phosphor="LightningIcon"
remixicon="RiFlashlightLine"
/>
Set up prebuilds
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="ServerIcon"
tabler="IconServer"
hugeicons="ServerStackIcon"
phosphor="HardDrivesIcon"
remixicon="RiHardDriveLine"
/>
Manage codespaces
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare2"
hugeicons="Share03Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share deep link
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
What are codespaces?
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ItemActions>
</Item>
<Separator className="-mx-2 my-2 w-auto!" />
<Empty className="p-4">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="ServerIcon"
tabler="IconServer"
hugeicons="ServerStackIcon"
phosphor="HardDrivesIcon"
remixicon="RiHardDriveLine"
/>
</EmptyMedia>
<EmptyTitle>No codespaces</EmptyTitle>
<EmptyDescription>
You don&apos;t have any codespaces with this repository
checked out
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button
size="sm"
onClick={() => {
setIsCreatingCodespace(true)
setTimeout(() => {
setIsCreatingCodespace(false)
}, 2000)
}}
disabled={isCreatingCodespace}
>
{isCreatingCodespace ? (
<Spinner data-icon="inline-start" />
) : (
""
)}
Create Codespace
</Button>
<a
href="#learn-more"
className="text-xs text-muted-foreground underline underline-offset-4"
>
Learn more about codespaces
</a>
</EmptyContent>
</Empty>
<Separator className="-mx-2 my-2 w-auto!" />
<div className="p-1.5 text-xs text-muted-foreground">
Codespace usage for this repository is paid for by{" "}
<span className="font-medium">shadcn</span>.
</div>
</TabsContent>
<TabsContent value="local">
<Item size="sm" className="hidden p-0">
<ItemContent>
<ItemTitle className="gap-2">
<IconPlaceholder
lucide="TerminalIcon"
tabler="IconTerminal"
hugeicons="ComputerTerminal01Icon"
phosphor="TerminalIcon"
remixicon="RiTerminalBoxLine"
className="size-4"
/>
Clone
</ItemTitle>
</ItemContent>
<ItemActions>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</Button>
</TooltipTrigger>
<TooltipContent side="left">
Which remote URL should I use?
</TooltipContent>
</Tooltip>
</ItemActions>
</Item>
<Tabs defaultValue="https">
<TabsList
variant="line"
className="w-full justify-start border-b *:[button]:flex-0"
>
<TabsTrigger value="https">HTTPS</TabsTrigger>
<TabsTrigger value="ssh">SSH</TabsTrigger>
<TabsTrigger value="cli">GitHub CLI</TabsTrigger>
</TabsList>
<div className="rounded-md border bg-muted/30 p-2">
<TabsContent value="https">
<Field className="gap-2">
<FieldLabel htmlFor="https-url" className="sr-only">
HTTPS URL
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="https-url"
defaultValue="https://github.com/shadcn-ui/ui.git"
readOnly
/>
</InputGroup>
<FieldDescription>
Clone using the web URL.
</FieldDescription>
</Field>
</TabsContent>
<TabsContent value="ssh">
<Field className="gap-2">
<FieldLabel htmlFor="ssh-url" className="sr-only">
SSH URL
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="ssh-url"
defaultValue="git@github.com:shadcn-ui/ui.git"
readOnly
/>
</InputGroup>
<FieldDescription>
Use a password-protected SSH key.
</FieldDescription>
</Field>
</TabsContent>
<TabsContent value="cli">
<Field className="gap-2">
<FieldLabel htmlFor="cli-command" className="sr-only">
CLI Command
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="cli-command"
defaultValue="gh repo clone shadcn-ui/ui"
readOnly
/>
</InputGroup>
<FieldDescription>
Work fast with our official CLI.{" "}
<a href="#learn-more">Learn more</a>
</FieldDescription>
</Field>
</TabsContent>
</div>
</Tabs>
<Separator className="-mx-2 my-2 w-auto!" />
<div className="flex flex-col">
<Button
variant="ghost"
size="sm"
className="justify-start gap-1.5"
>
<IconPlaceholder
lucide="MonitorIcon"
tabler="IconDeviceDesktop"
hugeicons="ComputerIcon"
phosphor="MonitorIcon"
remixicon="RiComputerLine"
data-icon="inline-start"
/>
Open with GitHub Desktop
</Button>
<Button
variant="ghost"
size="sm"
className="justify-start gap-1.5"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="DownloadIcon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
data-icon="inline-start"
/>
Download ZIP
</Button>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { Checkbox } from "@/registry/bases/radix/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/registry/bases/radix/ui/field"
export function ContributionsActivity() {
return (
<Card>
<CardHeader>
<CardTitle>Contributions & Activity</CardTitle>
<CardDescription>
Manage your contributions and activity visibility.
</CardDescription>
</CardHeader>
<CardContent>
<form id="contributions-activity">
<FieldGroup>
<FieldSet>
<FieldLegend className="sr-only">
Contributions & activity
</FieldLegend>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox id="activity-private-profile" />
<FieldContent>
<FieldLabel htmlFor="activity-private-profile">
Make profile private and hide activity
</FieldLabel>
<FieldDescription>
Enabling this will hide your contributions and activity
from your GitHub profile and from social features like
followers, stars, feeds, leaderboards and releases.
</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button form="contributions-activity" className="style-sera:w-full">
Save Changes
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/radix/ui/avatar"
import { Badge } from "@/registry/bases/radix/ui/badge"
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
// GitHub usernames displayed as contributor avatars.
const usernames = [
"shadcn",
"vercel",
"nextjs",
"tailwindlabs",
"typescript-lang",
"eslint",
"prettier",
"babel",
"webpack",
"rollup",
"parcel",
"vite",
"react",
"vue",
"angular",
"solid",
]
export function Contributors() {
return (
<Card className="max-w-sm">
<CardHeader>
<CardTitle>
Contributors <Badge variant="secondary">312</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{usernames.map((username) => (
<Avatar key={username} className="grayscale">
<AvatarImage
src={`https://github.com/${username}.png`}
alt={username}
/>
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar>
))}
</div>
</CardContent>
<CardFooter>
<a href="#" className="text-sm underline underline-offset-4">
+ 810 contributors
</a>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,43 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
export function EnvironmentVariables() {
return (
<Card>
<CardHeader>
<CardTitle>Environment Variables</CardTitle>
<CardDescription>Production · 8 variables</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
{[
{ key: "DATABASE_URL", masked: true },
{ key: "NEXT_PUBLIC_API", masked: false },
{ key: "STRIPE_SECRET", masked: true },
].map((env) => (
<div
key={env.key}
className="flex items-center gap-2 rounded-md px-2.5 py-2 font-mono text-xs ring ring-border"
>
<span className="font-medium">{env.key}</span>
<span className="ml-auto text-muted-foreground">
{env.masked ? "••••••••" : "https://api.example.com"}
</span>
</div>
))}
</CardContent>
<CardFooter>
<Button variant="outline">Edit</Button>
<Button className="ml-auto">Deploy</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,70 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent, CardFooter } from "@/registry/bases/radix/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/radix/ui/field"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/radix/ui/native-select"
import { Textarea } from "@/registry/bases/radix/ui/textarea"
export function FeedbackForm() {
return (
<Card>
<CardContent>
<form id="feedback-form">
<FieldGroup>
<Field>
<FieldLabel htmlFor="topic">Topic</FieldLabel>
<NativeSelect id="topic">
<NativeSelectOption value="">Select a topic</NativeSelectOption>
<NativeSelectOption value="ai">AI</NativeSelectOption>
<NativeSelectOption value="accounts-and-access-controls">
Accounts and Access Controls
</NativeSelectOption>
<NativeSelectOption value="billing">Billing</NativeSelectOption>
<NativeSelectOption value="cdn">
CDN (Firewall, Caching)
</NativeSelectOption>
<NativeSelectOption value="ci-cd">
CI/CD (Builds, Deployments, Environment Variables)
</NativeSelectOption>
<NativeSelectOption value="dashboard-interface">
Dashboard Interface (Navigation, UI Issues)
</NativeSelectOption>
<NativeSelectOption value="domains">Domains</NativeSelectOption>
<NativeSelectOption value="frameworks">
Frameworks
</NativeSelectOption>
<NativeSelectOption value="marketplace-and-integrations">
Marketplace and Integrations
</NativeSelectOption>
<NativeSelectOption value="observability">
Observability (Observability, Logs, Monitoring)
</NativeSelectOption>
<NativeSelectOption value="storage">Storage</NativeSelectOption>
</NativeSelect>
</Field>
<Field>
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
<Textarea
id="feedback"
placeholder="Your feedback helps us improve..."
/>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button
type="submit"
form="feedback-form"
className="style-sera:w-full"
>
Submit
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/radix/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function FileUpload() {
return (
<Card>
<CardHeader>
<CardTitle>File Upload</CardTitle>
<CardDescription>Drag and drop or browse</CardDescription>
</CardHeader>
<CardContent>
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="UploadCloudIcon"
tabler="IconCloudUpload"
hugeicons="CloudUploadIcon"
phosphor="CloudArrowUpIcon"
remixicon="RiUploadCloudLine"
/>
</EmptyMedia>
<EmptyTitle>Upload files</EmptyTitle>
<EmptyDescription>PNG, JPG, PDF up to 10MB</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Browse Files</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,79 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/radix/ui/field"
import { Input } from "@/registry/bases/radix/ui/input"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/radix/ui/native-select"
import { Textarea } from "@/registry/bases/radix/ui/textarea"
export function GithubProfile() {
return (
<Card className="mx-auto w-full max-w-md">
<CardHeader>
<CardTitle>Profile</CardTitle>
<CardDescription>Manage your profile information.</CardDescription>
</CardHeader>
<CardContent>
<form id="profile">
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" placeholder="shadcn" />
<FieldDescription>
Your name may appear around GitHub where you contribute or are
mentioned. You can remove it at any time.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="email">Public Email</FieldLabel>
<NativeSelect id="email">
<NativeSelectOption value="m@shadcn.com">
m@shadcn.com
</NativeSelectOption>
<NativeSelectOption value="m@gmail.com">
m@gmail.com
</NativeSelectOption>
</NativeSelect>
<FieldDescription>
You can manage verified email addresses in your{" "}
<a href="#email-settings">email settings</a>.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="bio">Bio</FieldLabel>
<Textarea
id="bio"
placeholder="Tell us a little bit about yourself"
/>
<FieldDescription>
You can <span>@mention</span> other users and organizations to
link to them.
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button form="profile" className="style-sera:w-full">
Save Profile
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,146 @@
"use client"
import { type IconLibraryName } from "shadcn/icons"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const PREVIEW_ICONS = [
{
lucide: "CopyIcon",
tabler: "IconCopy",
hugeicons: "Copy01Icon",
phosphor: "CopyIcon",
remixicon: "RiFileCopyLine",
},
{
lucide: "CircleAlertIcon",
tabler: "IconExclamationCircle",
hugeicons: "AlertCircleIcon",
phosphor: "WarningCircleIcon",
remixicon: "RiErrorWarningLine",
},
{
lucide: "TrashIcon",
tabler: "IconTrash",
hugeicons: "Delete02Icon",
phosphor: "TrashIcon",
remixicon: "RiDeleteBinLine",
},
{
lucide: "ShareIcon",
tabler: "IconShare",
hugeicons: "Share03Icon",
phosphor: "ShareIcon",
remixicon: "RiShareLine",
},
{
lucide: "ShoppingBagIcon",
tabler: "IconShoppingBag",
hugeicons: "ShoppingBag01Icon",
phosphor: "BagIcon",
remixicon: "RiShoppingBagLine",
},
{
lucide: "MoreHorizontalIcon",
tabler: "IconDots",
hugeicons: "MoreHorizontalCircle01Icon",
phosphor: "DotsThreeIcon",
remixicon: "RiMoreLine",
},
{
lucide: "Loader2Icon",
tabler: "IconLoader",
hugeicons: "Loading03Icon",
phosphor: "SpinnerIcon",
remixicon: "RiLoaderLine",
},
{
lucide: "PlusIcon",
tabler: "IconPlus",
hugeicons: "PlusSignIcon",
phosphor: "PlusIcon",
remixicon: "RiAddLine",
},
{
lucide: "MinusIcon",
tabler: "IconMinus",
hugeicons: "MinusSignIcon",
phosphor: "MinusIcon",
remixicon: "RiSubtractLine",
},
{
lucide: "ArrowLeftIcon",
tabler: "IconArrowLeft",
hugeicons: "ArrowLeft02Icon",
phosphor: "ArrowLeftIcon",
remixicon: "RiArrowLeftLine",
},
{
lucide: "ArrowRightIcon",
tabler: "IconArrowRight",
hugeicons: "ArrowRight02Icon",
phosphor: "ArrowRightIcon",
remixicon: "RiArrowRightLine",
},
{
lucide: "CheckIcon",
tabler: "IconCheck",
hugeicons: "Tick02Icon",
phosphor: "CheckIcon",
remixicon: "RiCheckLine",
},
{
lucide: "ChevronDownIcon",
tabler: "IconChevronDown",
hugeicons: "ArrowDown01Icon",
phosphor: "CaretDownIcon",
remixicon: "RiArrowDownSLine",
},
{
lucide: "ChevronRightIcon",
tabler: "IconChevronRight",
hugeicons: "ArrowRight01Icon",
phosphor: "CaretRightIcon",
remixicon: "RiArrowRightSLine",
},
{
lucide: "SearchIcon",
tabler: "IconSearch",
hugeicons: "Search01Icon",
phosphor: "MagnifyingGlassIcon",
remixicon: "RiSearchLine",
},
{
lucide: "SettingsIcon",
tabler: "IconSettings",
hugeicons: "Settings01Icon",
phosphor: "GearIcon",
remixicon: "RiSettingsLine",
},
] satisfies Record<IconLibraryName, string>[]
export function IconPreviewGrid() {
return (
<Card>
<CardContent>
<div className="grid grid-cols-8 place-items-center gap-4">
{PREVIEW_ICONS.map((icon, index) => (
<div
key={index}
className="flex size-8 items-center justify-center rounded-md ring ring-border style-sera:rounded-none *:[svg]:size-4"
>
<IconPlaceholder
lucide={icon.lucide}
tabler={icon.tabler}
hugeicons={icon.hugeicons}
phosphor={icon.phosphor}
remixicon={icon.remixicon}
/>
</div>
))}
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,100 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { Field, FieldLabel } from "@/registry/bases/radix/ui/field"
import { Input } from "@/registry/bases/radix/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/radix/ui/input-group"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/radix/ui/select"
import { Separator } from "@/registry/bases/radix/ui/separator"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function InviteTeam() {
return (
<Card>
<CardHeader>
<CardTitle>Invite Team</CardTitle>
<CardDescription>Add members to your workspace</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
{[
{ email: "alex@example.com", role: "Editor" },
{ email: "sam@example.com", role: "Viewer" },
].map((invite) => (
<div key={invite.email} className="flex items-center gap-2">
<Input defaultValue={invite.email} className="flex-1" />
<Select defaultValue={invite.role.toLowerCase()}>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper" align="end">
<SelectGroup>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="editor">Editor</SelectItem>
<SelectItem value="viewer">Viewer</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
))}
</div>
<Button variant="outline">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Add another
</Button>
<Separator />
<Field>
<FieldLabel htmlFor="invite-link">Or share invite link</FieldLabel>
<InputGroup>
<InputGroupInput
id="invite-link"
defaultValue="https://app.co/invite/x8f2k"
readOnly
/>
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs" aria-label="Copy link">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</CardContent>
<CardFooter>
<Button className="w-full">Send Invites</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,115 @@
"use client"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/radix/ui/table"
const INVOICE_ITEMS = [
{ item: "Design System License", qty: 1, unitPrice: 499 },
{ item: "Priority Support", qty: 12, unitPrice: 99 },
{ item: "Custom Components", qty: 3, unitPrice: 250 },
] as const
const subtotal = INVOICE_ITEMS.reduce(
(sum, row) => sum + row.qty * row.unitPrice,
0
)
const tax = 0
const totalDue = subtotal + tax
function formatCurrency(value: number) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 2,
}).format(value)
}
export function Invoice() {
return (
<Card>
<CardHeader>
<CardTitle>Invoice #INV-2847</CardTitle>
<CardDescription>Due March 30, 2026</CardDescription>
<CardAction>
<Badge variant="secondary">Pending</Badge>
</CardAction>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Item</TableHead>
<TableHead className="text-right">Qty</TableHead>
<TableHead className="text-right">Rate</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{INVOICE_ITEMS.map((row) => (
<TableRow key={row.item}>
<TableCell>{row.item}</TableCell>
<TableCell className="text-right tabular-nums">
{row.qty}
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(row.unitPrice)}
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(row.qty * row.unitPrice)}
</TableCell>
</TableRow>
))}
<TableRow>
<TableCell colSpan={3} className="text-right">
Subtotal
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(subtotal)}
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={3} className="text-right">
Tax
</TableCell>
<TableCell className="text-right tabular-nums">$0.00</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={3} className="text-right">
Total Due
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(totalDue)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm">
Download PDF
</Button>
<Button size="sm" className="ml-auto">
Pay Now
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,556 @@
"use client"
import * as React from "react"
import { useEffect, useRef, useState } from "react"
import { cn } from "cn"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
const LiveWaveform = ({
active = false,
processing = false,
deviceId,
barWidth = 3,
barGap = 1,
barRadius = 1.5,
barColor,
fadeEdges = true,
fadeWidth = 24,
barHeight: baseBarHeight = 4,
height = 64,
sensitivity = 1,
smoothingTimeConstant = 0.8,
fftSize = 256,
historySize = 60,
updateRate = 30,
mode = "static",
onError,
onStreamReady,
onStreamEnd,
className,
...props
}: React.HTMLAttributes<HTMLDivElement> & {
active?: boolean
processing?: boolean
deviceId?: string
barWidth?: number
barHeight?: number
barGap?: number
barRadius?: number
barColor?: string
fadeEdges?: boolean
fadeWidth?: number
height?: string | number
sensitivity?: number
smoothingTimeConstant?: number
fftSize?: number
historySize?: number
updateRate?: number
mode?: "scrolling" | "static"
onError?: (error: Error) => void
onStreamReady?: (stream: MediaStream) => void
onStreamEnd?: () => void
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const historyRef = useRef<number[]>([])
const analyserRef = useRef<AnalyserNode | null>(null)
const audioContextRef = useRef<AudioContext | null>(null)
const streamRef = useRef<MediaStream | null>(null)
const animationRef = useRef<number>(0)
const lastUpdateRef = useRef<number>(0)
const processingAnimationRef = useRef<number | null>(null)
const lastActiveDataRef = useRef<number[]>([])
const transitionProgressRef = useRef(0)
const staticBarsRef = useRef<number[]>([])
const needsRedrawRef = useRef(true)
const gradientCacheRef = useRef<CanvasGradient | null>(null)
const lastWidthRef = useRef(0)
const heightStyle = typeof height === "number" ? `${height}px` : height
// Handle canvas resizing.
useEffect(() => {
const canvas = canvasRef.current
const container = containerRef.current
if (!canvas || !container) return
const resizeObserver = new ResizeObserver(() => {
const rect = container.getBoundingClientRect()
const dpr = window.devicePixelRatio || 1
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
canvas.style.width = `${rect.width}px`
canvas.style.height = `${rect.height}px`
const ctx = canvas.getContext("2d")
if (ctx) {
ctx.scale(dpr, dpr)
}
gradientCacheRef.current = null
lastWidthRef.current = rect.width
needsRedrawRef.current = true
})
resizeObserver.observe(container)
return () => resizeObserver.disconnect()
}, [])
useEffect(() => {
if (processing && !active) {
let time = 0
transitionProgressRef.current = 0
const animateProcessing = () => {
time += 0.03
transitionProgressRef.current = Math.min(
1,
transitionProgressRef.current + 0.02
)
const processingData = []
const barCount = Math.floor(
(containerRef.current?.getBoundingClientRect().width || 200) /
(barWidth + barGap)
)
if (mode === "static") {
const halfCount = Math.floor(barCount / 2)
for (let i = 0; i < barCount; i++) {
const normalizedPosition = (i - halfCount) / halfCount
const centerWeight = 1 - Math.abs(normalizedPosition) * 0.4
const wave1 = Math.sin(time * 1.5 + normalizedPosition * 3) * 0.25
const wave2 = Math.sin(time * 0.8 - normalizedPosition * 2) * 0.2
const wave3 = Math.cos(time * 2 + normalizedPosition) * 0.15
const combinedWave = wave1 + wave2 + wave3
const processingValue = (0.2 + combinedWave) * centerWeight
let finalValue = processingValue
if (
lastActiveDataRef.current.length > 0 &&
transitionProgressRef.current < 1
) {
const lastDataIndex = Math.min(
i,
lastActiveDataRef.current.length - 1
)
const lastValue = lastActiveDataRef.current[lastDataIndex] || 0
finalValue =
lastValue * (1 - transitionProgressRef.current) +
processingValue * transitionProgressRef.current
}
processingData.push(Math.max(0.05, Math.min(1, finalValue)))
}
} else {
for (let i = 0; i < barCount; i++) {
const normalizedPosition = (i - barCount / 2) / (barCount / 2)
const centerWeight = 1 - Math.abs(normalizedPosition) * 0.4
const wave1 = Math.sin(time * 1.5 + i * 0.15) * 0.25
const wave2 = Math.sin(time * 0.8 - i * 0.1) * 0.2
const wave3 = Math.cos(time * 2 + i * 0.05) * 0.15
const combinedWave = wave1 + wave2 + wave3
const processingValue = (0.2 + combinedWave) * centerWeight
let finalValue = processingValue
if (
lastActiveDataRef.current.length > 0 &&
transitionProgressRef.current < 1
) {
const lastDataIndex = Math.floor(
(i / barCount) * lastActiveDataRef.current.length
)
const lastValue = lastActiveDataRef.current[lastDataIndex] || 0
finalValue =
lastValue * (1 - transitionProgressRef.current) +
processingValue * transitionProgressRef.current
}
processingData.push(Math.max(0.05, Math.min(1, finalValue)))
}
}
if (mode === "static") {
staticBarsRef.current = processingData
} else {
historyRef.current = processingData
}
needsRedrawRef.current = true
processingAnimationRef.current =
requestAnimationFrame(animateProcessing)
}
animateProcessing()
return () => {
if (processingAnimationRef.current) {
cancelAnimationFrame(processingAnimationRef.current)
}
}
} else if (!active && !processing) {
const hasData =
mode === "static"
? staticBarsRef.current.length > 0
: historyRef.current.length > 0
if (hasData) {
let fadeProgress = 0
const fadeToIdle = () => {
fadeProgress += 0.03
if (fadeProgress < 1) {
if (mode === "static") {
staticBarsRef.current = staticBarsRef.current.map(
(value) => value * (1 - fadeProgress)
)
} else {
historyRef.current = historyRef.current.map(
(value) => value * (1 - fadeProgress)
)
}
needsRedrawRef.current = true
requestAnimationFrame(fadeToIdle)
} else {
if (mode === "static") {
staticBarsRef.current = []
} else {
historyRef.current = []
}
}
}
fadeToIdle()
}
}
}, [processing, active, barWidth, barGap, mode])
// Handle microphone setup and teardown.
useEffect(() => {
if (!active) {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop())
streamRef.current = null
onStreamEnd?.()
}
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close()
audioContextRef.current = null
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
animationRef.current = 0
}
return
}
const setupMicrophone = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: deviceId
? {
deviceId: { exact: deviceId },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
}
: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
})
streamRef.current = stream
onStreamReady?.(stream)
const AudioContextConstructor =
window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext
const audioContext = new AudioContextConstructor()
const analyser = audioContext.createAnalyser()
analyser.fftSize = fftSize
analyser.smoothingTimeConstant = smoothingTimeConstant
const source = audioContext.createMediaStreamSource(stream)
source.connect(analyser)
audioContextRef.current = audioContext
analyserRef.current = analyser
historyRef.current = []
} catch (error) {
onError?.(error as Error)
}
}
setupMicrophone()
return () => {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop())
streamRef.current = null
onStreamEnd?.()
}
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close()
audioContextRef.current = null
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
animationRef.current = 0
}
}
}, [
active,
deviceId,
fftSize,
smoothingTimeConstant,
onError,
onStreamReady,
onStreamEnd,
])
// Animation loop.
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")
if (!ctx) return
let rafId: number
const animate = (currentTime: number) => {
const rect = canvas.getBoundingClientRect()
if (active && currentTime - lastUpdateRef.current > updateRate) {
lastUpdateRef.current = currentTime
if (analyserRef.current) {
const dataArray = new Uint8Array(
analyserRef.current.frequencyBinCount
)
analyserRef.current.getByteFrequencyData(dataArray)
if (mode === "static") {
const startFreq = Math.floor(dataArray.length * 0.05)
const endFreq = Math.floor(dataArray.length * 0.4)
const relevantData = dataArray.slice(startFreq, endFreq)
const barCount = Math.floor(rect.width / (barWidth + barGap))
const halfCount = Math.floor(barCount / 2)
const newBars: number[] = []
for (let i = halfCount - 1; i >= 0; i--) {
const dataIndex = Math.floor(
(i / halfCount) * relevantData.length
)
const value = Math.min(
1,
(relevantData[dataIndex] / 255) * sensitivity
)
newBars.push(Math.max(0.05, value))
}
for (let i = 0; i < halfCount; i++) {
const dataIndex = Math.floor(
(i / halfCount) * relevantData.length
)
const value = Math.min(
1,
(relevantData[dataIndex] / 255) * sensitivity
)
newBars.push(Math.max(0.05, value))
}
staticBarsRef.current = newBars
lastActiveDataRef.current = newBars
} else {
let sum = 0
const startFreq = Math.floor(dataArray.length * 0.05)
const endFreq = Math.floor(dataArray.length * 0.4)
const relevantData = dataArray.slice(startFreq, endFreq)
for (let i = 0; i < relevantData.length; i++) {
sum += relevantData[i]
}
const average = (sum / relevantData.length / 255) * sensitivity
historyRef.current.push(Math.min(1, Math.max(0.05, average)))
lastActiveDataRef.current = [...historyRef.current]
if (historyRef.current.length > historySize) {
historyRef.current.shift()
}
}
needsRedrawRef.current = true
}
}
if (!needsRedrawRef.current && !active) {
rafId = requestAnimationFrame(animate)
return
}
needsRedrawRef.current = active
ctx.clearRect(0, 0, rect.width, rect.height)
const computedBarColor =
barColor ||
(() => {
const style = getComputedStyle(canvas)
const color = style.color
return color || "#000"
})()
const step = barWidth + barGap
const barCount = Math.floor(rect.width / step)
const centerY = rect.height / 2
if (mode === "static") {
const dataToRender = processing
? staticBarsRef.current
: active
? staticBarsRef.current
: staticBarsRef.current.length > 0
? staticBarsRef.current
: []
for (let i = 0; i < barCount && i < dataToRender.length; i++) {
const value = dataToRender[i] || 0.1
const x = i * step
const bh = Math.max(baseBarHeight, value * rect.height * 0.8)
const y = centerY - bh / 2
ctx.fillStyle = computedBarColor
ctx.globalAlpha = 0.4 + value * 0.6
if (barRadius > 0) {
ctx.beginPath()
ctx.roundRect(x, y, barWidth, bh, barRadius)
ctx.fill()
} else {
ctx.fillRect(x, y, barWidth, bh)
}
}
} else {
for (let i = 0; i < barCount && i < historyRef.current.length; i++) {
const dataIndex = historyRef.current.length - 1 - i
const value = historyRef.current[dataIndex] || 0.1
const x = rect.width - (i + 1) * step
const bh = Math.max(baseBarHeight, value * rect.height * 0.8)
const y = centerY - bh / 2
ctx.fillStyle = computedBarColor
ctx.globalAlpha = 0.4 + value * 0.6
if (barRadius > 0) {
ctx.beginPath()
ctx.roundRect(x, y, barWidth, bh, barRadius)
ctx.fill()
} else {
ctx.fillRect(x, y, barWidth, bh)
}
}
}
if (fadeEdges && fadeWidth > 0 && rect.width > 0) {
if (!gradientCacheRef.current || lastWidthRef.current !== rect.width) {
const gradient = ctx.createLinearGradient(0, 0, rect.width, 0)
const fadePercent = Math.min(0.3, fadeWidth / rect.width)
gradient.addColorStop(0, "rgba(255,255,255,1)")
gradient.addColorStop(fadePercent, "rgba(255,255,255,0)")
gradient.addColorStop(1 - fadePercent, "rgba(255,255,255,0)")
gradient.addColorStop(1, "rgba(255,255,255,1)")
gradientCacheRef.current = gradient
lastWidthRef.current = rect.width
}
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = gradientCacheRef.current
ctx.fillRect(0, 0, rect.width, rect.height)
ctx.globalCompositeOperation = "source-over"
}
ctx.globalAlpha = 1
rafId = requestAnimationFrame(animate)
}
rafId = requestAnimationFrame(animate)
return () => {
if (rafId) {
cancelAnimationFrame(rafId)
}
}
}, [
active,
processing,
sensitivity,
updateRate,
historySize,
barWidth,
baseBarHeight,
barGap,
barRadius,
barColor,
fadeEdges,
fadeWidth,
mode,
])
return (
<div
className={cn("relative h-full w-full", className)}
ref={containerRef}
style={{ height: heightStyle }}
aria-label={
active
? "Live audio waveform"
: processing
? "Processing audio"
: "Audio waveform idle"
}
role="img"
{...props}
>
{!active && !processing && (
<div className="absolute top-1/2 right-0 left-0 -translate-y-1/2 border-t-2 border-dotted border-muted-foreground/20" />
)}
<canvas
className="block h-full w-full"
ref={canvasRef}
aria-hidden="true"
/>
</div>
)
}
export function LiveWaveformCard() {
const [active, setActive] = useState(false)
const [processing, setProcessing] = useState(true)
const [mode, setMode] = useState<"static" | "scrolling">("static")
const handleToggleActive = () => {
setActive(!active)
if (!active) {
setProcessing(false)
}
}
const handleToggleProcessing = () => {
setProcessing(!processing)
if (!processing) {
setActive(false)
}
}
return (
<Card>
<CardHeader>
<CardTitle>Live Audio Waveform</CardTitle>
<CardDescription>
Real-time microphone input visualization with audio reactivity
</CardDescription>
</CardHeader>
<CardContent>
<LiveWaveform
active={active}
processing={processing}
height={80}
barWidth={3}
barGap={2}
mode={mode}
fadeEdges={true}
barColor="gray"
historySize={120}
/>
</CardContent>
<CardFooter className="gap-2">
<Button
size="sm"
variant={active ? "default" : "outline"}
onClick={handleToggleActive}
>
{active ? "Stop" : "Start"} Listening
</Button>
<Button
size="sm"
variant={processing ? "default" : "outline"}
onClick={handleToggleProcessing}
>
{processing ? "Stop" : "Start"} Processing
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setMode(mode === "static" ? "scrolling" : "static")}
>
{mode === "static" ? "Static" : "Scrolling"}
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,63 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@/registry/bases/radix/ui/avatar"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/radix/ui/empty"
export function NoTeamMembers() {
return (
<Card>
<CardContent>
<Empty className="h-56 border">
<EmptyHeader>
<EmptyMedia>
<AvatarGroup className="grayscale">
<Avatar size="lg">
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>
Invite your team to collaborate on this project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Invite Members</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,54 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/registry/bases/radix/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/radix/ui/input-group"
import { Kbd } from "@/registry/bases/radix/ui/kbd"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function NotFound() {
return (
<Card>
<CardContent>
<Empty className="h-72">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching
for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<Button variant="link">Go to homepage</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function ObservabilityCard() {
return (
<Card className="relative w-full max-w-md overflow-hidden pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" />
<img
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
<CardTitle>Observability Plus is replacing Monitoring</CardTitle>
<CardDescription>
Switch to the improved way to explore your data, with natural
language. Monitoring will no longer be available on the Pro plan in
November, 2025
</CardDescription>
</CardHeader>
<CardFooter>
<Button>
Create Query{" "}
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-end"
/>
</Button>
<Badge variant="secondary" className="ml-auto">
Warning
</Badge>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,147 @@
"use client"
import { Label, Pie, PieChart } from "recharts"
import { Badge } from "@/registry/bases/radix/ui/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/radix/ui/chart"
import { Progress } from "@/registry/bases/radix/ui/progress"
const pieChartData = [
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 287, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
]
const pieChartConfig = {
visitors: {
label: "Visitors",
},
chrome: {
label: "Chrome",
color: "var(--chart-1)",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
firefox: {
label: "Firefox",
color: "var(--chart-3)",
},
edge: {
label: "Edge",
color: "var(--chart-4)",
},
} satisfies ChartConfig
export function PieChartCard() {
const totalVisitors = pieChartData.reduce(
(sum, item) => sum + item.visitors,
0
)
const topBrowser = pieChartData.reduce((max, item) =>
item.visitors > max.visitors ? item : max
)
const topBrowserShare = Math.round(
(topBrowser.visitors / totalVisitors) * 100
)
const topBrowserLabel =
pieChartConfig[topBrowser.browser as keyof typeof pieChartConfig]?.label ??
"Top"
return (
<Card>
<CardHeader className="pb-0">
<CardTitle>Browser Share</CardTitle>
<CardDescription>January - June 2026</CardDescription>
<CardAction>
<Badge variant="outline">{topBrowserLabel}</Badge>
</CardAction>
</CardHeader>
<CardContent className="pt-0">
<ChartContainer
config={pieChartConfig}
className="mx-auto aspect-square max-h-[190px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={pieChartData}
dataKey="visitors"
nameKey="browser"
innerRadius={50}
strokeWidth={5}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
fill="red"
>
<tspan
x={viewBox.cx}
y={viewBox.cy - 16}
className="fill-foreground text-2xl font-bold"
>
{totalVisitors.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 4}
className="fill-muted-foreground text-xs"
>
Visitors
</tspan>
</text>
)
}
}}
/>
</Pie>
<ChartLegend
content={<ChartLegendContent nameKey="browser" />}
className="translate-y-2"
/>
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-stretch gap-2">
<div className="flex items-center text-xs">
<span className="font-medium">{topBrowserLabel}</span>
<span className="ml-auto text-muted-foreground tabular-nums">
{topBrowserShare}%
</span>
</div>
<Progress
value={topBrowserShare}
className="**:data-[slot=progress-indicator]:bg-chart-3"
/>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,97 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/radix/ui/field"
import { Input } from "@/registry/bases/radix/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/radix/ui/select"
import { Textarea } from "@/registry/bases/radix/ui/textarea"
export function ReportBug() {
return (
<Card>
<CardHeader>
<CardTitle>Report Bug</CardTitle>
<CardDescription>Help us fix issues faster.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="bug-title">Title</FieldLabel>
<Input
id="bug-title"
placeholder="Brief description of the issue"
/>
</Field>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="bug-severity">Severity</FieldLabel>
<Select defaultValue="medium">
<SelectTrigger id="bug-severity" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="critical">Critical</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="low">Low</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="bug-component">Component</FieldLabel>
<Select defaultValue="dashboard">
<SelectTrigger id="bug-component" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="dashboard">Dashboard</SelectItem>
<SelectItem value="auth">Auth</SelectItem>
<SelectItem value="api">API</SelectItem>
<SelectItem value="billing">Billing</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="bug-steps">Steps to reproduce</FieldLabel>
<Textarea
id="bug-steps"
placeholder="1. Go to&#10;2. Click on&#10;3. Observe..."
className="min-h-24 resize-none"
/>
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Field
orientation="horizontal"
className="justify-end style-sera:justify-center"
>
<Button variant="outline" className="style-sera:flex-1">
Attach File
</Button>
<Button className="style-sera:flex-1">Submit Bug</Button>
</Field>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,101 @@
"use client"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import { Checkbox } from "@/registry/bases/radix/ui/checkbox"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/radix/ui/field"
import { Input } from "@/registry/bases/radix/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/radix/ui/select"
export function ShippingAddress() {
return (
<Card>
<CardHeader>
<CardTitle>Shipping Address</CardTitle>
<CardDescription>Where should we deliver?</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="shipping-street">Street address</FieldLabel>
<Input id="shipping-street" placeholder="123 Main Street" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-apt">Apt / Suite</FieldLabel>
<Input id="shipping-apt" placeholder="Apt 4B" />
</Field>
<FieldGroup className="grid grid-cols-2">
<Field>
<FieldLabel htmlFor="shipping-city">City</FieldLabel>
<Input id="shipping-city" placeholder="San Francisco" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-state">State</FieldLabel>
<Select defaultValue="CA">
<SelectTrigger id="shipping-state" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="CA">California</SelectItem>
<SelectItem value="NY">New York</SelectItem>
<SelectItem value="TX">Texas</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
<FieldGroup className="grid grid-cols-2">
<Field>
<FieldLabel htmlFor="shipping-zip">ZIP Code</FieldLabel>
<Input id="shipping-zip" placeholder="94102" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-country">Country</FieldLabel>
<Select defaultValue="US">
<SelectTrigger id="shipping-country" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="US">United States</SelectItem>
<SelectItem value="CA">Canada</SelectItem>
<SelectItem value="UK">United Kingdom</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
<Field orientation="horizontal">
<Checkbox id="shipping-save" defaultChecked />
<FieldLabel htmlFor="shipping-save" className="font-normal">
Save as default address
</FieldLabel>
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm">
Cancel
</Button>
<Button size="sm" className="ml-auto">
Save Address
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,55 @@
import * as React from "react"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import {
Item,
ItemActions,
ItemGroup,
ItemHeader,
ItemSeparator,
ItemTitle,
} from "@/registry/bases/radix/ui/item"
import { Kbd } from "@/registry/bases/radix/ui/kbd"
const shortcuts = [
{ label: "Search", keys: ["⌘", "K"] },
{ label: "Quick Actions", keys: ["⌘", "J"] },
{ label: "New File", keys: ["⌘", "N"] },
{ label: "Save", keys: ["⌘", "S"] },
{ label: "Toggle Sidebar", keys: ["⌘", "B"] },
] as const
export function Shortcuts() {
return (
<Card>
<CardContent>
<div className="flex flex-col gap-3">
<div className="text-sm font-medium">Shortcuts</div>
<ItemGroup className="gap-2 text-muted-foreground" data-size="xs">
{shortcuts.map(({ label, keys }, i) => (
<React.Fragment key={label}>
{i > 0 && <ItemSeparator />}
<Item
variant="default"
size="xs"
className="border-0 px-0 py-0"
>
<ItemHeader>
<ItemTitle className="font-normal">{label}</ItemTitle>
<ItemActions>
<div className="flex gap-1">
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</div>
</ItemActions>
</ItemHeader>
</Item>
</React.Fragment>
))}
</ItemGroup>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,29 @@
"use client"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import { Skeleton } from "@/registry/bases/radix/ui/skeleton"
export function SkeletonLoading() {
return (
<Card>
<CardContent className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<Skeleton className="size-10 rounded-full" />
<div className="flex flex-1 flex-col gap-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
</div>
</div>
<div className="flex flex-col gap-2">
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-4/5" />
</div>
<div className="flex gap-2">
<Skeleton className="h-8 w-20" />
<Skeleton className="h-8 w-20" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,104 @@
"use client"
import { Bar, BarChart } from "recharts"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
ChartContainer,
type ChartConfig,
} from "@/registry/bases/radix/ui/chart"
const sleepChartData = [
{ hour: "10pm", deep: 0, light: 30, rem: 0 },
{ hour: "11pm", deep: 20, light: 10, rem: 0 },
{ hour: "12am", deep: 40, light: 0, rem: 10 },
{ hour: "1am", deep: 30, light: 5, rem: 15 },
{ hour: "2am", deep: 10, light: 20, rem: 30 },
{ hour: "3am", deep: 25, light: 10, rem: 20 },
{ hour: "4am", deep: 15, light: 25, rem: 10 },
{ hour: "5am", deep: 5, light: 35, rem: 15 },
{ hour: "6am", deep: 0, light: 20, rem: 25 },
]
const sleepChartConfig = {
deep: {
label: "Deep",
color: "var(--chart-1)",
},
light: {
label: "Light",
color: "var(--chart-2)",
},
rem: {
label: "REM",
color: "var(--chart-3)",
},
} satisfies ChartConfig
export function SleepReport() {
return (
<Card>
<CardHeader>
<CardTitle>Sleep Report</CardTitle>
<CardDescription>Last night · 7h 24m</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<ChartContainer config={sleepChartConfig} className="h-32 w-full">
<BarChart
accessibilityLayer
data={sleepChartData}
margin={{ left: 0, right: 0, top: 0, bottom: 0 }}
barSize={16}
>
<Bar
dataKey="deep"
stackId="a"
fill="var(--color-deep)"
radius={0}
/>
<Bar
dataKey="light"
stackId="a"
fill="var(--color-light)"
radius={0}
/>
<Bar
dataKey="rem"
stackId="a"
fill="var(--color-rem)"
radius={[2, 2, 0, 0]}
/>
</BarChart>
</ChartContainer>
<div className="grid grid-cols-4 gap-2">
{[
{ label: "Deep", value: "2h 10m" },
{ label: "Light", value: "3h 48m" },
{ label: "REM", value: "1h 26m" },
{ label: "Score", value: "84" },
].map((s) => (
<div key={s.label} className="text-center">
<div className="text-sm font-medium tabular-nums">{s.value}</div>
<div className="text-xs text-muted-foreground">{s.label}</div>
</div>
))}
</div>
</CardContent>
<CardFooter>
<Badge variant="outline">Good</Badge>
<Button variant="outline" size="sm" className="ml-auto">
Details
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,80 @@
"use client"
import * as React from "react"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import { STYLES } from "@/registry/styles"
import { FONTS } from "@/app/(create)/lib/fonts"
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function StyleOverview() {
const [params] = useDesignSystemSearchParams()
const currentFont = React.useMemo(
() => FONTS.find((font) => font.value === params.font),
[params.font]
)
const currentFontHeading = React.useMemo(
() => FONTS.find((font) => font.value === params.fontHeading),
[params.fontHeading]
)
const currentStyle = React.useMemo(
() => STYLES.find((style) => style.name === params.style),
[params.style]
)
return (
<Card>
<CardContent className="flex flex-col gap-6 style-lyra:gap-4 style-mira:gap-4">
<div className="flex flex-col gap-1">
<div className="cn-font-heading text-2xl font-medium style-lyra:text-lg style-mira:text-lg style-sera:text-lg style-sera:font-semibold style-sera:tracking-wide style-sera:uppercase">
{currentStyle?.title} -{" "}
{currentFontHeading?.name &&
currentFontHeading.name !== currentFont?.name
? currentFontHeading.name
: currentFont?.name}
</div>
<div className="line-clamp-2 text-base text-muted-foreground style-lyra:text-sm style-mira:text-sm style-sera:text-sm style-sera:leading-relaxed">
Designers love packing quirky glyphs into test phrases. This is a
preview of the typography styles.
</div>
</div>
<div className="grid grid-cols-6 gap-3">
{[
"--background",
"--foreground",
"--primary",
"--secondary",
"--muted",
"--accent",
"--border",
"--chart-1",
"--chart-2",
"--chart-3",
"--chart-4",
"--chart-5",
].map((variant) => (
<div
key={variant}
className="flex flex-col flex-wrap items-center gap-2"
>
<div
className="relative aspect-square w-full rounded-lg bg-(--color) after:absolute after:inset-0 after:rounded-lg after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten style-sera:rounded-none style-sera:after:rounded-none"
style={
{
"--color": `var(${variant})`,
} as React.CSSProperties
}
/>
<div className="hidden max-w-14 truncate font-mono text-[0.60rem] md:block style-lyra:max-w-10 style-mira:max-w-10">
{variant}
</div>
</div>
))}
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,132 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/radix/ui/button"
import { Card, CardContent, CardFooter } from "@/registry/bases/radix/ui/card"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/radix/ui/dialog"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/radix/ui/field"
import { Input } from "@/registry/bases/radix/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/radix/ui/select"
import { Textarea } from "@/registry/bases/radix/ui/textarea"
import { FONTS } from "@/app/(create)/lib/fonts"
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function TypographySpecimen() {
const [params] = useDesignSystemSearchParams()
const currentFont = React.useMemo(
() => FONTS.find((font) => font.value === params.font),
[params.font]
)
const currentFontHeading = React.useMemo(
() => FONTS.find((font) => font.value === params.fontHeading),
[params.fontHeading]
)
const headingLabel =
currentFontHeading?.name && currentFontHeading.name !== currentFont?.name
? currentFontHeading.name
: "Inherit"
const bodyLabel = currentFont?.name ?? "Default"
return (
<Card>
<CardContent className="flex flex-col gap-2">
<div className="text-xs font-medium text-muted-foreground uppercase">
{headingLabel} - {bodyLabel}
</div>
<p className="cn-font-heading text-2xl font-medium style-sera:text-lg style-sera:font-semibold style-sera:tracking-wider style-sera:uppercase">
Designing with rhythm and hierarchy.
</p>
<p className="text-sm leading-relaxed text-muted-foreground">
A strong body style keeps long-form content readable and balances the
visual weight of headings.
</p>
<p className="text-sm leading-relaxed text-muted-foreground">
Thoughtful spacing and cadence help paragraphs scan quickly without
feeling dense.
</p>
</CardContent>
<CardFooter>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className="w-full">
Share Feedback
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Share Feedback</DialogTitle>
<DialogDescription>
Let us know how we can improve your experience.
</DialogDescription>
</DialogHeader>
<FieldGroup>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="feedback-name">Name</FieldLabel>
<Input id="feedback-name" placeholder="Your name" />
</Field>
<Field>
<FieldLabel htmlFor="feedback-email">Email</FieldLabel>
<Input
id="feedback-email"
type="email"
placeholder="you@example.com"
/>
</Field>
</div>
<Field>
<FieldLabel htmlFor="feedback-category">Category</FieldLabel>
<Select defaultValue="general">
<SelectTrigger id="feedback-category" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="general">General</SelectItem>
<SelectItem value="bug">Bug Report</SelectItem>
<SelectItem value="feature">Feature Request</SelectItem>
<SelectItem value="improvement">Improvement</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="feedback-message">Message</FieldLabel>
<Textarea
id="feedback-message"
placeholder="Tell us what's on your mind..."
className="min-h-24 resize-none"
/>
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button>Submit</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,206 @@
"use client"
import * as React from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/radix/ui/alert-dialog"
import { Badge } from "@/registry/bases/radix/ui/badge"
import { Button } from "@/registry/bases/radix/ui/button"
import { ButtonGroup } from "@/registry/bases/radix/ui/button-group"
import { Card, CardContent } from "@/registry/bases/radix/ui/card"
import { Checkbox } from "@/registry/bases/radix/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/radix/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/radix/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/radix/ui/item"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/radix/ui/radio-group"
import { Slider } from "@/registry/bases/radix/ui/slider"
import { Switch } from "@/registry/bases/radix/ui/switch"
import { Textarea } from "@/registry/bases/radix/ui/textarea"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function UIElements() {
const [sliderValue, setSliderValue] = React.useState<number[]>([500])
const handleSliderValueChange = React.useCallback(
(value: number | readonly number[]) => {
if (typeof value === "number") {
setSliderValue([value])
} else {
setSliderValue([...value])
}
},
[]
)
return (
<Card className="w-full">
<CardContent className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
<Button>Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
</div>
<Item variant="outline">
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
Verify via email or phone number.
</ItemDescription>
</ItemContent>
<ItemActions className="hidden md:flex">
<Button size="sm" variant="secondary">
Enable
</Button>
</ItemActions>
</Item>
</div>
<Slider
value={sliderValue}
onValueChange={handleSliderValueChange}
max={1000}
min={0}
step={10}
className="flex-1"
aria-label="Slider"
/>
<FieldGroup>
<Field>
<InputGroup>
<InputGroupInput placeholder="Name" />
<InputGroupAddon align="inline-end">
<InputGroupText>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field className="flex-1">
<Textarea placeholder="Message" className="resize-none" />
</Field>
</FieldGroup>
<div className="flex items-center gap-2">
<div className="flex gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<RadioGroup defaultValue="apple" className="ml-auto flex w-fit gap-3">
<RadioGroupItem value="apple" />
<RadioGroupItem value="banana" />
</RadioGroup>
<div className="flex gap-3">
<Checkbox defaultChecked />
<Checkbox />
</div>
</div>
<div className="flex items-center gap-4">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<span className="hidden md:flex style-sera:md:hidden">
Alert Dialog
</span>
<span className="flex md:hidden style-sera:md:flex">
Dialog
</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this
device and your data?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ButtonGroup>
<Button variant="outline">
<span className="style-sera:hidden">Button Group</span>
<span className="hidden style-sera:block">Group</span>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ChevronUpIcon"
tabler="IconChevronUp"
hugeicons="ArrowUp01Icon"
phosphor="CaretUpIcon"
remixicon="RiArrowUpSLine"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="top" className="w-fit">
<DropdownMenuGroup>
<DropdownMenuLabel>Quick Actions</DropdownMenuLabel>
<DropdownMenuItem>Mute Conversation</DropdownMenuItem>
<DropdownMenuItem>Mark as Read</DropdownMenuItem>
<DropdownMenuItem>Block User</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Conversation</DropdownMenuLabel>
<DropdownMenuItem>Share Conversation</DropdownMenuItem>
<DropdownMenuItem>Copy Conversation</DropdownMenuItem>
<DropdownMenuItem>Report Conversation</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<Switch defaultChecked className="ml-auto" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,136 @@
"use client"
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
Item,
ItemActions,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/radix/ui/item"
// Usage items with percentage and value.
const items = [
{
name: "Edge Requests",
value: "$1.83K",
percentage: 67.34,
},
{
name: "Fast Data Transfer",
percentage: 52.18,
value: "$952.51",
},
{
name: "Monitoring data points",
percentage: 89.42,
value: "$901.20",
},
{
name: "Web Analytics Events",
percentage: 45.67,
value: "$603.71",
},
{
name: "ISR Writes",
percentage: 26.23,
value: "524.52K / 2M",
},
{
name: "Function Duration",
percentage: 5.11,
value: "5.11 GB Hrs / 1K GB Hrs",
},
]
// Circular gauge SVG component for displaying a percentage.
function CircularGauge({ percentage }: { percentage: number }) {
const normalizedPercentage = Math.min(Math.max(percentage, 0), 100)
const circumference = 2 * Math.PI * 42.5
const strokePercent = (normalizedPercentage / 100) * circumference
return (
<svg
aria-hidden="true"
fill="none"
height="16"
width="16"
strokeWidth="2"
viewBox="0 0 100 100"
className="-rotate-90"
>
<circle
cx="50"
cy="50"
r="42.5"
strokeWidth="12"
strokeDashoffset="0"
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-20"
stroke="currentColor"
style={{
strokeDasharray: `${circumference} ${circumference}`,
}}
/>
<circle
cx="50"
cy="50"
r="42.5"
strokeWidth="12"
strokeDashoffset="0"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
className="transition-all duration-300"
style={{
strokeDasharray: `${strokePercent} ${circumference}`,
}}
/>
</svg>
)
}
export function UsageCard() {
return (
<Card className="w-full max-w-sm gap-4">
<CardHeader>
<CardTitle className="px-1 text-sm">
5 days remaining in cycle
</CardTitle>
</CardHeader>
<CardContent>
<ItemGroup className="gap-0">
{items.map((item) => (
<Item
key={item.name}
size="xs"
className="px-0 group-hover/item-group:bg-transparent"
asChild
>
<a href="#">
<ItemMedia variant="icon" className="text-primary">
<CircularGauge percentage={item.percentage} />
</ItemMedia>
<ItemContent className="inline-block truncate">
<ItemTitle className="inline">{item.name}</ItemTitle>
</ItemContent>
<ItemActions>
<span className="font-mono text-xs font-medium text-muted-foreground tabular-nums">
{item.value}
</span>
</ItemActions>
</a>
</Item>
))}
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,91 @@
"use client"
import { Area, AreaChart, XAxis } from "recharts"
import { Badge } from "@/registry/bases/radix/ui/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/radix/ui/chart"
const areaChartData = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 },
]
const areaChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
} satisfies ChartConfig
const latestVisitors = areaChartData[areaChartData.length - 1]?.desktop ?? 0
const previousVisitors =
areaChartData[areaChartData.length - 2]?.desktop ?? latestVisitors
const trendPercent =
previousVisitors === 0
? 0
: Math.round(((latestVisitors - previousVisitors) / previousVisitors) * 100)
const trendPrefix = trendPercent > 0 ? "+" : ""
export function Visitors() {
return (
<Card className="pb-0">
<CardHeader>
<CardTitle>Visitors</CardTitle>
<CardDescription>Last 6 months </CardDescription>
<CardAction>
<Badge variant={trendPercent >= 0 ? "secondary" : "destructive"}>
{trendPrefix}
{trendPercent}% vs last month
</Badge>
</CardAction>
</CardHeader>
<CardContent className="px-0">
<ChartContainer config={areaChartConfig} className="h-48 w-full">
<AreaChart
accessibilityLayer
data={areaChartData}
margin={{ left: 0, right: 0, top: 6, bottom: 0 }}
>
<XAxis
dataKey="month"
tickLine={false}
hide
axisLine={false}
tickMargin={6}
tickFormatter={(value) => String(value).slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.15}
stroke="var(--color-desktop)"
strokeWidth={2}
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,59 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/radix/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/radix/ui/card"
const FITNESS_WEEKLY_LOAD = [
{ day: "M", load: 84 },
{ day: "T", load: 52 },
{ day: "W", load: 73 },
{ day: "T", load: 66 },
{ day: "F", load: 91 },
{ day: "S", load: 48 },
{ day: "S", load: 61 },
]
export function WeeklyFitnessSummary() {
return (
<Card>
<CardHeader>
<CardTitle>Weekly Fitness Summary</CardTitle>
<CardDescription>Calories and workout load by day</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="grid grid-cols-7 gap-1.5">
{FITNESS_WEEKLY_LOAD.map((day, index) => (
<div
key={`${day.day}-${index}`}
className="rounded-md p-1.5 text-center ring ring-border"
>
<div className="text-sm text-muted-foreground">{day.day}</div>
<div className="relative mt-1 h-16 overflow-hidden rounded-sm bg-muted">
<div
className="absolute inset-x-0 bottom-0 rounded-sm bg-chart-3"
style={
{
height: `${day.load}%`,
} as React.CSSProperties
}
/>
</div>
</div>
))}
</div>
</CardContent>
<CardFooter>
<Button className="w-full">View details</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,97 @@
"use client"
import { ActivateAgentDialog } from "@/registry/bases/radix/blocks/preview/cards/activate-agent-dialog"
import { AnalyticsCard } from "@/registry/bases/radix/blocks/preview/cards/analytics-card"
import { AnomalyAlert } from "@/registry/bases/radix/blocks/preview/cards/anomaly-alert"
import { BarChartCard } from "@/registry/bases/radix/blocks/preview/cards/bar-chart-card"
import { BookAppointment } from "@/registry/bases/radix/blocks/preview/cards/book-appointment"
import { CodespacesCard } from "@/registry/bases/radix/blocks/preview/cards/codespaces-card"
import { ContributionsActivity } from "@/registry/bases/radix/blocks/preview/cards/contributions-activity"
import { Contributors } from "@/registry/bases/radix/blocks/preview/cards/contributors"
import { EnvironmentVariables } from "@/registry/bases/radix/blocks/preview/cards/environment-variables"
import { FeedbackForm } from "@/registry/bases/radix/blocks/preview/cards/feedback-form"
import { FileUpload } from "@/registry/bases/radix/blocks/preview/cards/file-upload"
import { GithubProfile } from "@/registry/bases/radix/blocks/preview/cards/github-profile"
import { IconPreviewGrid } from "@/registry/bases/radix/blocks/preview/cards/icon-preview-grid"
import { InviteTeam } from "@/registry/bases/radix/blocks/preview/cards/invite-team"
import { Invoice } from "@/registry/bases/radix/blocks/preview/cards/invoice"
import { LiveWaveformCard } from "@/registry/bases/radix/blocks/preview/cards/live-waveform"
import { NoTeamMembers } from "@/registry/bases/radix/blocks/preview/cards/no-team-members"
import { NotFound } from "@/registry/bases/radix/blocks/preview/cards/not-found"
import { ObservabilityCard } from "@/registry/bases/radix/blocks/preview/cards/observability-card"
import { PieChartCard } from "@/registry/bases/radix/blocks/preview/cards/pie-chart-card"
import { ReportBug } from "@/registry/bases/radix/blocks/preview/cards/report-bug"
import { ShippingAddress } from "@/registry/bases/radix/blocks/preview/cards/shipping-address"
import { Shortcuts } from "@/registry/bases/radix/blocks/preview/cards/shortcuts"
import { SkeletonLoading } from "@/registry/bases/radix/blocks/preview/cards/skeleton-loading"
import { SleepReport } from "@/registry/bases/radix/blocks/preview/cards/sleep-report"
import { StyleOverview } from "@/registry/bases/radix/blocks/preview/cards/style-overview"
import { TypographySpecimen } from "@/registry/bases/radix/blocks/preview/cards/typography-specimen"
import { UIElements } from "@/registry/bases/radix/blocks/preview/cards/ui-elements"
import { UsageCard } from "@/registry/bases/radix/blocks/preview/cards/usage-card"
import { Visitors } from "@/registry/bases/radix/blocks/preview/cards/visitors"
import { WeeklyFitnessSummary } from "@/registry/bases/radix/blocks/preview/cards/weekly-fitness-summary"
export default function PreviewExample() {
return (
<div className="overflow-x-auto overflow-y-hidden bg-muted contain-[paint] [--gap:--spacing(4)] 3xl:[--gap:--spacing(12)] md:[--gap:--spacing(10)] dark:bg-background style-lyra:md:[--gap:--spacing(6)] style-mira:md:[--gap:--spacing(6)]">
<div className="flex w-full min-w-max justify-center">
<div
className="grid w-[2400px] grid-cols-7 items-start gap-(--gap) bg-muted p-(--gap) md:w-[3000px] dark:bg-background style-lyra:md:w-[2600px] style-mira:md:w-[2600px] *:[div]:gap-(--gap)"
data-slot="capture-target"
>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<StyleOverview />
<TypographySpecimen />
<div className="md:hidden">
<UIElements />
</div>
<CodespacesCard />
<Invoice />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<IconPreviewGrid />
<div className="hidden w-full md:flex">
<UIElements />
</div>
<ObservabilityCard />
<ShippingAddress />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<EnvironmentVariables />
<BarChartCard />
<InviteTeam />
<ActivateAgentDialog />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<SkeletonLoading />
<PieChartCard />
<NoTeamMembers />
<ReportBug />
<Contributors />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<FeedbackForm />
<BookAppointment />
<SleepReport />
<GithubProfile />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<WeeklyFitnessSummary />
<FileUpload />
<AnalyticsCard />
<UsageCard />
<Shortcuts />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<AnomalyAlert />
<LiveWaveformCard />
<Visitors />
<ContributionsActivity />
<NotFound />
</div>
</div>
</div>
</div>
)
}