
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
CI/CD deployment history, active environment status, execution stepper, collapsible build log terminal with stdout timestamps, and filterable deployments table.
Also available for Vue ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/deployment-pipeline.json$npx shadcn@latest add https://uipkge.dev/r/react/deployment-pipeline.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/deployment-pipeline.json$bunx shadcn@latest add https://uipkge.dev/r/react/deployment-pipeline.jsonnpx shadcn@latest add @uipkge-react/deployment-pipelineInstalls to:components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
initialEnv | DeploymentEnvironment | — | optional |
initialLogsOpen | boolean | — | optional |
className | string | — | optional |
Type aliases exported from this item's source. Use these to shape the data you pass in.
DeploymentRecordinterface DeploymentRecord {
id: string
commitMessage: string
commitHash: string
branch: string
environment: 'production' | 'preview' | 'staging'
status: DeploymentStatus
duration: string
deployer: {
name: string
initials: string
}
trigger: DeploymentTrigger
deployedAt: string
url: string
}PipelineStepinterface PipelineStep {
id: string
name: string
duration: string
description: string
status: 'completed' | 'in-progress' | 'pending'
}'use client'
import * as React from 'react'
import {
Check,
CheckCircle2,
ChevronDown,
ChevronUp,
Clock,
Copy,
ExternalLink,
GitBranch,
GitCommit,
Layers,
Loader2,
MoreHorizontal,
Play,
RotateCcw,
RotateCw,
Terminal,
Undo2,
XCircle,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Separator } from '@/components/ui/separator'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
export type DeploymentEnvironment = 'all' | 'production' | 'preview' | 'staging'
export type DeploymentStatus = 'ready' | 'building' | 'failed' | 'rolled-back'
export type DeploymentTrigger = 'push' | 'manual' | 'rollback'
export interface DeploymentRecord {
id: string
commitMessage: string
commitHash: string
branch: string
environment: 'production' | 'preview' | 'staging'
status: DeploymentStatus
duration: string
deployer: {
name: string
initials: string
}
trigger: DeploymentTrigger
deployedAt: string
url: string
}
export interface PipelineStep {
id: string
name: string
duration: string
description: string
status: 'completed' | 'in-progress' | 'pending'
}
export interface DeploymentPipelineProps {
initialEnv?: DeploymentEnvironment
initialLogsOpen?: boolean
className?: string
}
const branches = ['main', 'staging', 'preview/safari-tabs', 'preview/e2e-tests']
const pipelineSteps: PipelineStep[] = [
{ id: 'step-1', name: 'Queued', duration: '0.2s', description: 'Worker allocated', status: 'completed' },
{ id: 'step-2', name: 'Build & Bundle', duration: '42s', description: 'Vite & TS compilation', status: 'completed' },
{
id: 'step-3',
name: 'Static Generation',
duration: '18s',
description: '42 SSG pages rendered',
status: 'completed',
},
{ id: 'step-4', name: 'Edge Propagation', duration: '3s', description: 'Synced to 310 PoPs', status: 'completed' },
{ id: 'step-5', name: 'Ready', duration: 'Instant', description: 'Verified & live', status: 'completed' },
]
const terminalLogs = [
{
time: '00:00:00.120',
type: 'info',
prefix: '❯ [worker]',
text: 'Initializing deployment worker in region iad-1 (runner-edge-iad-4)...',
},
{
time: '00:00:00.340',
type: 'info',
prefix: '❯ [git]',
text: 'Cloning repository uipkge/core-registry at commit a3f89b1 (branch: main)...',
},
{
time: '00:00:01.050',
type: 'info',
prefix: '❯ [cache]',
text: 'Restoring pnpm store cache (v9.15.0)... (1,482 packages restored, 98.4% cache hit)',
},
{ time: '00:00:02.410', type: 'cmd', prefix: '$', text: 'pnpm run build:registry && pnpm run build' },
{ time: '00:00:04.180', type: 'stdout', prefix: ' [vite]', text: 'Transformed 482 modules in 312ms' },
{
time: '00:00:15.920',
type: 'stdout',
prefix: ' [astro]',
text: 'Building static entrypoints for production (Vue 3.5 + React 19 islands)...',
},
{ time: '00:00:32.400', type: 'stdout', prefix: ' [astro]', text: '✓ 42 static pages rendered in 16.48s' },
{
time: '00:00:44.210',
type: 'stdout',
prefix: ' [registry]',
text: 'Generated 120 manifests into public/r/vue/*.json and public/r/react/*.json',
},
{
time: '00:00:58.800',
type: 'info',
prefix: '❯ [edge]',
text: 'Optimizing edge assets with Brotli compression (saved 4.8 MB)...',
},
{
time: '00:01:01.320',
type: 'info',
prefix: '❯ [edge]',
text: 'Propagating edge routes to Cloudflare global network across 310 PoPs...',
},
{
time: '00:01:03.200',
type: 'success',
prefix: '✓ [ready]',
text: 'Deployment successfully verified and healthy at https://uipkge.dev',
},
{
time: '00:01:03.210',
type: 'success',
prefix: '✓ [exit]',
text: 'Process completed with exit code 0 (success in 1m 03.2s)',
},
]
const deployments: DeploymentRecord[] = [
{
id: 'd-1042',
commitMessage: 'feat(blocks): add ecommerce suite (#412)',
commitHash: 'a3f89b1',
branch: 'main',
environment: 'production',
status: 'ready',
duration: '1m 03s',
deployer: { name: 'Jordan Diaz', initials: 'JD' },
trigger: 'push',
deployedAt: '12m ago',
url: 'https://uipkge.dev',
},
{
id: 'd-1041',
commitMessage: 'fix(tabs): resolve indicator jitter in safari (#411)',
commitHash: 'f79b20e',
branch: 'preview/safari-tabs',
environment: 'preview',
status: 'ready',
duration: '48s',
deployer: { name: 'Amara Osei', initials: 'AO' },
trigger: 'push',
deployedAt: '2h ago',
url: 'https://uipkge-pr-411.uipkge.dev',
},
{
id: 'd-1040',
commitMessage: 'refactor(avatar): extract avatar-group primitive (#410)',
commitHash: '91bc8d3',
branch: 'staging',
environment: 'staging',
status: 'ready',
duration: '54s',
deployer: { name: 'Jonas Weber', initials: 'JW' },
trigger: 'manual',
deployedAt: '5h ago',
url: 'https://staging.uipkge.dev',
},
{
id: 'd-1039',
commitMessage: 'test(e2e): add playwright visual regression suite (#409)',
commitHash: '62e08a4',
branch: 'preview/e2e-tests',
environment: 'preview',
status: 'building',
duration: '1m 12s',
deployer: { name: 'Priya Nair', initials: 'PN' },
trigger: 'push',
deployedAt: 'Just now',
url: 'https://uipkge-pr-409.uipkge.dev',
},
{
id: 'd-1038',
commitMessage: 'perf(theme): eliminate css custom property recalculation (#408)',
commitHash: '3d12fa9',
branch: 'staging',
environment: 'staging',
status: 'failed',
duration: '24s',
deployer: { name: 'Marcus Lee', initials: 'ML' },
trigger: 'push',
deployedAt: 'Yesterday',
url: 'https://staging-failed.uipkge.dev',
},
{
id: 'd-1037',
commitMessage: 'chore(deps): update reka-ui to v1.1.0 (#407)',
commitHash: '180eac2',
branch: 'main',
environment: 'production',
status: 'rolled-back',
duration: '1m 20s',
deployer: { name: 'Jordan Diaz', initials: 'JD' },
trigger: 'rollback',
deployedAt: '2 days ago',
url: 'https://uipkge-v2-rollback.uipkge.dev',
},
]
export function DeploymentPipeline({ initialEnv = 'all', initialLogsOpen = true, className }: DeploymentPipelineProps) {
const [selectedEnv, setSelectedEnv] = React.useState<DeploymentEnvironment>(initialEnv)
const [selectedBranch, setSelectedBranch] = React.useState<string>('main')
const [showLogs, setShowLogs] = React.useState<boolean>(initialLogsOpen)
const [copiedLogs, setCopiedLogs] = React.useState<boolean>(false)
const [copiedUrlId, setCopiedUrlId] = React.useState<string | null>(null)
const [isDeploying, setIsDeploying] = React.useState<boolean>(false)
const filteredDeployments =
selectedEnv === 'all' ? deployments : deployments.filter((d) => d.environment === selectedEnv)
function triggerDeployment() {
setIsDeploying(true)
setTimeout(() => {
setIsDeploying(false)
}, 1200)
}
function copyLogs() {
const text = terminalLogs.map((l) => `[${l.time}] ${l.prefix} ${l.text}`).join('\n')
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(text)
setCopiedLogs(true)
setTimeout(() => {
setCopiedLogs(false)
}, 2000)
}
}
function copyUrl(url: string, id: string) {
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(url)
setCopiedUrlId(id)
setTimeout(() => {
setCopiedUrlId(null)
}, 2000)
}
}
return (
<div data-slot="deployment-pipeline" className={cn('w-full space-y-6', className)}>
{/* Header Section */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3">
<div className="bg-card flex size-10 items-center justify-center rounded-lg border shadow-xs">
<Layers className="text-primary size-5" aria-hidden="true" />
</div>
<div>
<h2 className="text-2xl font-bold tracking-tight">Deployments</h2>
<div className="text-muted-foreground flex items-center gap-1.5 text-xs">
<span className="text-foreground font-mono font-medium">uipkge</span>
<span>/</span>
<span className="font-mono">core-registry</span>
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="font-mono text-xs">
<GitBranch className="text-muted-foreground size-3.5" aria-hidden="true" />
<span>{selectedBranch}</span>
<ChevronDown className="text-muted-foreground size-3.5 opacity-60" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>Select branch</DropdownMenuLabel>
<DropdownMenuSeparator />
{branches.map((branch) => (
<DropdownMenuItem
key={branch}
className={selectedBranch === branch ? 'bg-accent font-semibold' : ''}
onClick={() => setSelectedBranch(branch)}
>
<GitBranch className="mr-2 size-3.5" aria-hidden="true" />
<span className="font-mono text-xs">{branch}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Button size="sm" disabled={isDeploying} onClick={triggerDeployment}>
{isDeploying ? (
<Loader2 className="size-3.5 animate-spin" aria-hidden="true" />
) : (
<Play className="size-3.5 fill-current" aria-hidden="true" />
)}
{isDeploying ? 'Triggering...' : 'Trigger Deployment'}
</Button>
</div>
</div>
{/* Environment Tabs Navigation */}
<Tabs
value={selectedEnv}
onValueChange={(val) => setSelectedEnv(val as DeploymentEnvironment)}
className="w-full"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<TabsList variant="segmented" className="w-full sm:w-auto">
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="production">Production</TabsTrigger>
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="staging">Staging</TabsTrigger>
</TabsList>
<span className="text-muted-foreground text-xs tabular-nums">
Showing {filteredDeployments.length} of {deployments.length} deployments
</span>
</div>
</Tabs>
{/* Latest Deployment Hero Card */}
<Card className="border-border shadow-xs">
<CardHeader className="space-y-4 pb-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-1.5">
<div className="flex flex-wrap items-center gap-2">
<CardTitle className="text-base font-semibold">Latest Deployment</CardTitle>
<div className="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium">
<span className="relative flex size-2 shrink-0">
<span className="bg-success absolute inline-flex h-full w-full rounded-full opacity-75" />
<span className="bg-success relative inline-flex size-2 rounded-full" />
</span>
Live · Production
</div>
</div>
<CardDescription>
<a
href="https://uipkge.dev"
target="_blank"
rel="noopener noreferrer"
className="text-foreground hover:text-primary inline-flex min-h-6 items-center gap-1 py-0.5 font-mono text-xs font-medium underline underline-offset-4 transition-colors"
>
https://uipkge.dev
<ExternalLink className="text-muted-foreground size-3" aria-hidden="true" />
</a>
</CardDescription>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" onClick={() => setShowLogs(!showLogs)}>
<Terminal className="size-3.5" aria-hidden="true" />
<span>{showLogs ? 'Hide Build Logs' : 'View Build Logs'}</span>
{showLogs ? (
<ChevronUp className="text-muted-foreground size-3.5" aria-hidden="true" />
) : (
<ChevronDown className="text-muted-foreground size-3.5" aria-hidden="true" />
)}
</Button>
<Button variant="outline" size="sm">
<RotateCw className="size-3.5" aria-hidden="true" />
<span>Redeploy</span>
</Button>
<Button variant="outline" size="sm">
<Undo2 className="size-3.5" aria-hidden="true" />
<span>Instant Rollback</span>
</Button>
</div>
</div>
{/* Commit Details Bar */}
<div className="bg-muted/40 flex flex-wrap items-center gap-x-4 gap-y-2 rounded-lg border px-3.5 py-2.5 text-xs">
<div className="flex items-center gap-2">
<Avatar className="size-5">
<AvatarFallback className="text-xs font-medium">JD</AvatarFallback>
</Avatar>
<span className="text-foreground font-medium">Jordan Diaz</span>
</div>
<Separator orientation="vertical" className="hidden h-3.5 sm:block" />
<span className="text-foreground font-medium">feat(blocks): add ecommerce suite (#412)</span>
<Separator orientation="vertical" className="hidden h-3.5 sm:block" />
<div className="text-muted-foreground flex items-center gap-1 font-mono">
<GitCommit className="size-3.5" aria-hidden="true" />
<span>a3f89b1</span>
</div>
<Separator orientation="vertical" className="hidden h-3.5 sm:block" />
<div className="text-muted-foreground flex items-center gap-1 font-mono">
<GitBranch className="size-3.5" aria-hidden="true" />
<span>main</span>
</div>
<Separator orientation="vertical" className="hidden h-3.5 sm:block" />
<div className="text-muted-foreground flex items-center gap-1">
<Clock className="size-3.5" aria-hidden="true" />
<span>Deployed 12m ago</span>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4 pt-0">
{/* Execution Stepper */}
<div className="space-y-2.5">
<div className="flex items-center justify-between">
<span className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
Pipeline Execution
</span>
<span className="text-muted-foreground font-mono text-xs">5 of 5 steps completed · 1m 03s</span>
</div>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-5">
{pipelineSteps.map((step, index) => (
<div
key={step.id}
className="bg-card/60 hover:bg-muted/40 flex flex-col justify-between rounded-lg border p-3 shadow-2xs transition-colors"
>
<div className="flex items-center justify-between">
<span className="bg-success/15 text-success flex size-5 items-center justify-center rounded-full">
<Check className="size-3 stroke-[3]" aria-hidden="true" />
</span>
<span className="text-muted-foreground font-mono text-xs">{step.duration}</span>
</div>
<div className="mt-2">
<p className="text-foreground text-xs font-semibold">
{index + 1}. {step.name}
</p>
<p className="text-muted-foreground truncate text-xs">{step.description}</p>
</div>
</div>
))}
</div>
</div>
{/* Collapsible Dark Terminal / Build Log Viewer */}
{showLogs && (
<div className="overflow-hidden rounded-lg border border-zinc-800 bg-zinc-950 font-mono text-xs shadow-inner">
<div className="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3.5 py-2">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<span className="bg-destructive/80 size-2.5 rounded-full" />
<span className="bg-warning/80 size-2.5 rounded-full" />
<span className="bg-success/80 size-2.5 rounded-full" />
</div>
<span className="text-muted-foreground text-xs">
build-stdout — node v22.12.0 — runner-edge-iad-4
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground text-muted-foreground hidden text-xs sm:inline">
Total: 1m 03.2s
</span>
<Button
variant="ghost"
size="xs"
className="h-6 gap-1 px-2 text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
onClick={copyLogs}
>
{copiedLogs ? (
<Check className="text-success size-3" aria-hidden="true" />
) : (
<Copy className="size-3" aria-hidden="true" />
)}
<span>{copiedLogs ? 'Copied' : 'Copy logs'}</span>
</Button>
</div>
</div>
<div className="max-h-72 space-y-1 overflow-y-auto p-4 select-text">
{terminalLogs.map((line, idx) => (
<div key={idx} className="flex items-start gap-2 leading-relaxed">
<span className="text-muted-foreground select-none">[{line.time}]</span>
<span
className={cn(
'font-medium select-none',
line.type === 'success' && 'text-success',
line.type === 'info' && 'text-info',
line.type === 'cmd' && 'text-warning',
line.type === 'stdout' && 'text-muted-foreground',
)}
>
{line.prefix}
</span>
<span
className={cn(
line.type === 'success' && 'text-success font-medium',
line.type === 'cmd' && 'font-semibold text-zinc-100',
line.type === 'info' && 'text-zinc-300',
line.type === 'stdout' && 'text-zinc-300',
)}
>
{line.text}
</span>
</div>
))}
</div>
<div className="text-muted-foreground flex items-center justify-between border-t border-zinc-800 bg-zinc-900/60 px-3.5 py-1.5 text-xs">
<div className="flex items-center gap-2">
<span className="bg-success/10 text-success inline-flex items-center gap-1 rounded px-1.5 py-0.5 font-medium">
<Check className="size-3" aria-hidden="true" /> Exit code: 0
</span>
<span>Clean build</span>
</div>
<span className="text-muted-foreground">Memory: 412 MB / 2048 MB</span>
</div>
</div>
)}
</CardContent>
</Card>
{/* Deployment History Table */}
<Card className="border-border shadow-xs">
<CardHeader className="pb-3">
<CardTitle className="text-base font-semibold">Deployment History</CardTitle>
<CardDescription className="text-xs">
Past deployments across production, preview, and staging environments.
</CardDescription>
</CardHeader>
<CardContent className="p-0">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-10">
<span className="sr-only">Status</span>
</TableHead>
<TableHead>Commit & Message</TableHead>
<TableHead>Environment</TableHead>
<TableHead>Duration</TableHead>
<TableHead>Deployer</TableHead>
<TableHead>Trigger</TableHead>
<TableHead className="text-right">Deployed</TableHead>
<TableHead className="w-10">
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredDeployments.map((deployment) => (
<TableRow key={deployment.id} className="hover:bg-muted/50">
<TableCell className="py-3">
{deployment.status === 'ready' && (
<span title="Ready">
<CheckCircle2 className="text-success size-4" aria-hidden="true" />
</span>
)}
{deployment.status === 'building' && (
<span title="Building">
<Loader2 className="text-info size-4 animate-spin" aria-hidden="true" />
</span>
)}
{deployment.status === 'failed' && (
<span title="Failed">
<XCircle className="text-destructive size-4" aria-hidden="true" />
</span>
)}
{deployment.status === 'rolled-back' && (
<span title="Rolled back">
<Undo2 className="text-warning size-4" aria-hidden="true" />
</span>
)}
</TableCell>
<TableCell>
<div className="min-w-[220px]">
<p className="text-foreground max-w-sm truncate text-sm font-medium">
{deployment.commitMessage}
</p>
<div className="text-muted-foreground mt-0.5 flex items-center gap-2 font-mono text-xs">
<span className="flex items-center gap-1">
<GitBranch className="size-3" aria-hidden="true" />
{deployment.branch}
</span>
<span>·</span>
<span className="flex items-center gap-1">
<GitCommit className="size-3" aria-hidden="true" />
{deployment.commitHash}
</span>
</div>
</div>
</TableCell>
<TableCell>
<Badge
variant={
deployment.environment === 'production'
? 'default'
: deployment.environment === 'preview'
? 'secondary'
: 'outline'
}
className="text-xs capitalize"
>
{deployment.environment}
</Badge>
</TableCell>
<TableCell className="text-muted-foreground font-mono text-xs tabular-nums">
{deployment.duration}
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="size-6">
<AvatarFallback className="text-xs font-medium">
{deployment.deployer.initials}
</AvatarFallback>
</Avatar>
<span className="text-foreground text-xs">{deployment.deployer.name}</span>
</div>
</TableCell>
<TableCell>
<div className="text-muted-foreground flex items-center gap-1.5 text-xs">
{deployment.trigger === 'push' && <GitCommit className="size-3.5" aria-hidden="true" />}
{deployment.trigger === 'manual' && <Play className="size-3.5" aria-hidden="true" />}
{deployment.trigger === 'rollback' && <RotateCcw className="size-3.5" aria-hidden="true" />}
<span className="capitalize">{deployment.trigger}</span>
</div>
</TableCell>
<TableCell className="text-muted-foreground text-right text-xs whitespace-nowrap tabular-nums">
{deployment.deployedAt}
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon-sm" className="text-muted-foreground">
<MoreHorizontal className="size-4" aria-hidden="true" />
<span className="sr-only">Open actions for {deployment.id}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Undo2 className="mr-2 size-4" aria-hidden="true" />
Rollback to this deploy
</DropdownMenuItem>
<DropdownMenuItem onClick={() => copyUrl(deployment.url, deployment.id)}>
{copiedUrlId === deployment.id ? (
<Check className="text-success mr-2 size-4" aria-hidden="true" />
) : (
<Copy className="mr-2 size-4" aria-hidden="true" />
)}
{copiedUrlId === deployment.id ? 'Copied URL' : 'Copy deployment URL'}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setShowLogs(true)}>
<Terminal className="mr-2 size-4" aria-hidden="true" />
Inspect build logs
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href={deployment.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center"
>
<ExternalLink className="mr-2 size-4" aria-hidden="true" />
View live preview
</a>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
{filteredDeployments.length === 0 && (
<TableRow>
<TableCell colSpan={8} className="text-muted-foreground h-24 text-center text-sm">
No deployments match the selected environment filter.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
)
}
Raw manifest:https://uipkge.dev/r/react/deployment-pipeline.json