Menu

Change language

GitHub repo

Virtual Card Manager

blockfinance

Mercury/Ramp-style corporate virtual card manager: interactive dark credit card visual hero with EMV chip, revealable credentials, live spending limit progress bar, instant freeze toggles, limit adjusters, and an active virtual cards table.

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/virtual-card-manager.json
Named registry:npx shadcn@latest add @uipkge-react/virtual-card-managerInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
classNamestring—optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

VirtualCard
interface VirtualCard {
  id: string
  name: string
  brand: 'mastercard' | 'visa'
  last4: string
  fullNumber: string
  holder: string
  avatar: string
  initials: string
  expiry: string
  cvv: string
  spent: number
  limit: number
  type: 'Monthly Recurring' | 'Single-use'
  status: 'Active' | 'Frozen'
  billingCycle: string
  purpose: string
}

Files installed (1)

  • components/blocks/VirtualCardManager.tsx25.8 kB
    'use client'
    
    import * as React from 'react'
    import {
      Check,
      Copy,
      CreditCard,
      Eye,
      EyeOff,
      Plus,
      Radio,
      ShieldCheck,
      SlidersHorizontal,
      Snowflake,
      CheckCircle2,
      Trash2,
      Zap,
    } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Avatar, AvatarFallback, AvatarImage } 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 { Progress } from '@/components/ui/progress'
    import { Switch } from '@/components/ui/switch'
    
    interface VirtualCard {
      id: string
      name: string
      brand: 'mastercard' | 'visa'
      last4: string
      fullNumber: string
      holder: string
      avatar: string
      initials: string
      expiry: string
      cvv: string
      spent: number
      limit: number
      type: 'Monthly Recurring' | 'Single-use'
      status: 'Active' | 'Frozen'
      billingCycle: string
      purpose: string
    }
    
    const initialCards: VirtualCard[] = [
      {
        id: 'vc-1',
        name: 'AWS & Cloud Infrastructure',
        brand: 'mastercard',
        last4: '8492',
        fullNumber: '5532 8920 4108 8492',
        holder: 'Elena Rostova',
        avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
        initials: 'ER',
        expiry: '08/29',
        cvv: '482',
        spent: 4200,
        limit: 5000,
        type: 'Monthly Recurring',
        status: 'Active',
        billingCycle: 'Resets on 1st of month',
        purpose: 'Cloud compute, S3 buckets, and RDS instances',
      },
      {
        id: 'vc-2',
        name: 'Marketing & Ad Spend',
        brand: 'visa',
        last4: '3190',
        fullNumber: '4012 8831 9204 3190',
        holder: 'Marcus Chen',
        avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&auto=format&fit=crop&q=80',
        initials: 'MC',
        expiry: '11/28',
        cvv: '915',
        spent: 8750,
        limit: 10000,
        type: 'Monthly Recurring',
        status: 'Active',
        billingCycle: 'Resets on 1st of month',
        purpose: 'Google Ads, LinkedIn campaigns, and Meta Ads',
      },
      {
        id: 'vc-3',
        name: 'Travel & Conferences',
        brand: 'visa',
        last4: '6021',
        fullNumber: '4111 5900 1284 6021',
        holder: 'Sarah Jenkins',
        avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&auto=format&fit=crop&q=80',
        initials: 'SJ',
        expiry: '03/27',
        cvv: '304',
        spent: 1850,
        limit: 2500,
        type: 'Single-use',
        status: 'Frozen',
        billingCycle: 'Single event allowance',
        purpose: 'Q3 Design Systems Summit flights and lodging',
      },
      {
        id: 'vc-4',
        name: 'SaaS Subscriptions',
        brand: 'mastercard',
        last4: '9455',
        fullNumber: '5241 7719 3302 9455',
        holder: 'Elena Rostova',
        avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
        initials: 'ER',
        expiry: '05/29',
        cvv: '729',
        spent: 620,
        limit: 1200,
        type: 'Monthly Recurring',
        status: 'Active',
        billingCycle: 'Resets on 1st of month',
        purpose: 'GitHub Enterprise, Figma, and Slack licenses',
      },
    ]
    
    export interface VirtualCardManagerProps {
      className?: string
    }
    
    export function VirtualCardManager({ className }: VirtualCardManagerProps) {
      const [cards, setCards] = React.useState<VirtualCard[]>(initialCards)
      const [selectedId, setSelectedId] = React.useState<string>('vc-1')
      const [showDetails, setShowDetails] = React.useState<boolean>(false)
      const [copied, setCopied] = React.useState<boolean>(false)
      const [actionFeedback, setActionFeedback] = React.useState<string | null>(null)
    
      const selectedCard = cards.find((c) => c.id === selectedId) ?? cards[0]
      const isSelectedFrozen = selectedCard?.status === 'Frozen'
      const selectedPercent =
        selectedCard && selectedCard.limit > 0
          ? Math.min(100, Math.round((selectedCard.spent / selectedCard.limit) * 100))
          : 0
    
      const showFeedback = React.useCallback((msg: string) => {
        setActionFeedback(msg)
        setTimeout(() => {
          setActionFeedback((prev) => (prev === msg ? null : prev))
        }, 3000)
      }, [])
    
      const toggleFreeze = React.useCallback((id: string) => {
        setCards((prev) =>
          prev.map((c) => {
            if (c.id === id) {
              const nextStatus = c.status === 'Active' ? 'Frozen' : 'Active'
              return { ...c, status: nextStatus }
            }
            return c
          }),
        )
      }, [])
    
      const handleCopyNumber = React.useCallback(() => {
        if (!selectedCard) return
        if (typeof navigator !== 'undefined' && navigator.clipboard) {
          navigator.clipboard.writeText(selectedCard.fullNumber.replace(/\s+/g, ''))
        }
        setCopied(true)
        setTimeout(() => setCopied(false), 2000)
      }, [selectedCard])
    
      const adjustLimit = React.useCallback(
        (cardId: string) => {
          setCards((prev) =>
            prev.map((c) => {
              if (c.id === cardId) {
                const newLimit = c.limit === 5000 ? 7500 : c.limit === 10000 ? 12000 : c.limit + 1000
                return { ...c, limit: newLimit }
              }
              return c
            }),
          )
          const current = cards.find((c) => c.id === cardId)
          const newLimit = current
            ? current.limit === 5000
              ? 7500
              : current.limit === 10000
                ? 12000
                : current.limit + 1000
            : 0
          showFeedback(`Limit updated to $${newLimit.toLocaleString()}`)
        },
        [cards, showFeedback],
      )
    
      const terminateCard = React.useCallback(
        (cardId: string) => {
          if (cards.length <= 1) {
            showFeedback('Cannot terminate the last remaining card')
            return
          }
          const remaining = cards.filter((c) => c.id !== cardId)
          setCards(remaining)
          if (selectedId === cardId) {
            setSelectedId(remaining[0].id)
          }
          showFeedback('Card terminated permanently')
        },
        [cards, selectedId, showFeedback],
      )
    
      const issueNewCard = React.useCallback(() => {
        const newId = `vc-${Date.now().toString().slice(-4)}`
        const newLast4 = Math.floor(1000 + Math.random() * 9000).toString()
        const newCard: VirtualCard = {
          id: newId,
          name: 'AI & API Workloads',
          brand: 'visa',
          last4: newLast4,
          fullNumber: `4400 1284 9912 ${newLast4}`,
          holder: 'Elena Rostova',
          avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
          initials: 'ER',
          expiry: '09/30',
          cvv: '814',
          spent: 0,
          limit: 3000,
          type: 'Monthly Recurring',
          status: 'Active',
          billingCycle: 'Resets on 1st of month',
          purpose: 'OpenAI, Anthropic, and Vector DB infrastructure',
        }
        setCards((prev) => [newCard, ...prev])
        setSelectedId(newId)
        showFeedback('New virtual card issued successfully')
      }, [showFeedback])
    
      return (
        <div data-slot="virtual-card-manager" className={cn('mx-auto w-full max-w-6xl space-y-6', className)}>
          {/* Header */}
          <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <div className="space-y-1">
              <div className="flex items-center gap-2.5">
                <h2 className="text-foreground text-xl font-semibold tracking-tight sm:text-2xl">Virtual Cards</h2>
                <Badge wrap variant="secondary" className="font-mono text-xs tabular-nums">
                  {cards.length} Cards
                </Badge>
              </div>
              <p className="text-muted-foreground text-sm">
                Generate instant corporate cards for subscriptions, vendors, and team expense limits.
              </p>
            </div>
    
            <div className="flex items-center gap-2">
              <Button size="sm" className="gap-1.5 shadow-xs" onClick={issueNewCard}>
                <Plus className="size-4" />
                Issue New Card
              </Button>
            </div>
          </div>
    
          {/* Notification / Action feedback */}
          {actionFeedback && (
            <div className="bg-primary/10 text-primary border-primary/20 flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium transition-colors">
              <div className="flex items-center gap-2">
                <CheckCircle2 className="size-4 shrink-0" />
                <span>{actionFeedback}</span>
              </div>
              <Button
                aria-label="Dismiss notification"
                variant="ghost"
                size="xs"
                className="h-6 px-2 text-xs"
                onClick={() => setActionFeedback(null)}
              >
                Dismiss
              </Button>
            </div>
          )}
    
          {/* Virtual Card Visual Hero */}
          <Card className="border-border bg-card shadow-xs">
            <CardHeader className="border-b pb-4">
              <div className="flex flex-wrap items-center justify-between gap-2">
                <div>
                  <div className="flex items-center gap-2">
                    <CardTitle className="text-base font-semibold">{selectedCard.name}</CardTitle>
                    <Badge wrap variant={selectedCard.status === 'Active' ? 'success' : 'warning'} className="text-xs">
                      {selectedCard.status}
                    </Badge>
                  </div>
                  <CardDescription className="mt-0.5 text-xs">
                    {selectedCard.purpose} · {selectedCard.billingCycle}
                  </CardDescription>
                </div>
                <Badge wrap variant="outline" className="font-mono text-xs">
                  Ending in {selectedCard.last4}
                </Badge>
              </div>
            </CardHeader>
    
            <CardContent className="pt-6">
              <div className="grid gap-6 lg:grid-cols-12 lg:items-center">
                {/* Stylized Dark Credit Card Container */}
                <div className="lg:col-span-6 xl:col-span-5">
                  <div
                    className={cn(
                      'relative aspect-[1.586/1] w-full rounded-2xl p-6 text-white transition-[filter] duration-300 lg:max-w-[420px]',
                      'border border-zinc-700/60 bg-gradient-to-br from-zinc-900 via-neutral-900 to-zinc-950 shadow-xl',
                      isSelectedFrozen && 'contrast-95 grayscale filter',
                    )}
                  >
                    {/* Ambient light accents */}
                    <div
                      className="bg-chart-1/15 pointer-events-none absolute -top-16 -right-16 size-48 rounded-full blur-2xl"
                      aria-hidden="true"
                    />
                    <div
                      className="bg-success/10 pointer-events-none absolute -bottom-16 -left-16 size-48 rounded-full blur-2xl"
                      aria-hidden="true"
                    />
    
                    {/* Frozen watermark overlay */}
                    {isSelectedFrozen && (
                      <div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 rounded-2xl bg-zinc-950/70 backdrop-blur-xs">
                        <div className="border-warning/40 bg-warning/20 text-warning inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-semibold tracking-wider uppercase">
                          <Snowflake className="size-3.5" />
                          Card Frozen
                        </div>
                        <p className="text-muted-foreground text-xs">Transactions are currently blocked</p>
                      </div>
                    )}
    
                    {/* Top Row: Chip & Contactless + Brand Logo */}
                    <div className="relative z-10 flex items-center justify-between">
                      <div className="flex items-center gap-3">
                        {/* Metallic EMV Chip */}
                        <div
                          className="border-warning/20/40 relative flex h-8 w-11 items-center justify-center rounded-md border bg-gradient-to-br from-amber-200/30 via-amber-400/20 to-amber-600/30 shadow-inner"
                          aria-hidden="true"
                        >
                          <div className="border-warning/20/30 absolute inset-0 grid grid-cols-3 grid-rows-2">
                            <div className="border-warning/30 border-r border-b" />
                            <div className="border-warning/30 border-r border-b" />
                            <div className="border-warning/30 border-b" />
                            <div className="border-warning/30 border-r" />
                            <div className="border-warning/30 border-r" />
                            <div />
                          </div>
                        </div>
    
                        {/* NFC Wave icon */}
                        <div className="text-muted-foreground/80 rotate-90" aria-hidden="true">
                          <Radio className="size-4" />
                        </div>
                      </div>
    
                      {/* Brand Indicator */}
                      <div className="flex items-center gap-2">
                        <span className="text-muted-foreground font-mono text-xs font-medium tracking-wider uppercase">
                          Virtual
                        </span>
                        {selectedCard.brand === 'mastercard' ? (
                          <div className="flex -space-x-2" aria-label="Mastercard">
                            <div className="bg-destructive/90 size-6 rounded-full" />
                            <div className="bg-warning/90 size-6 rounded-full mix-blend-screen" />
                          </div>
                        ) : (
                          <div className="text-base font-bold tracking-widest text-zinc-100 italic" aria-label="Visa">
                            VISA
                          </div>
                        )}
                      </div>
                    </div>
    
                    {/* Middle Row: Card Number & Quick Actions */}
                    <div className="relative z-10 my-auto pt-4">
                      <div className="flex items-center justify-between gap-2">
                        <p className="font-mono text-lg font-medium tracking-[0.16em] text-zinc-100 tabular-nums drop-shadow-sm sm:text-xl">
                          {showDetails ? selectedCard.fullNumber : `•••• •••• •••• ${selectedCard.last4}`}
                        </p>
                        <div className="flex items-center gap-1">
                          <Button
                            variant="ghost"
                            size="icon-sm"
                            className="size-7 text-zinc-300 hover:bg-white/10 hover:text-white"
                            aria-label={showDetails ? 'Hide card number' : 'Reveal card number'}
                            onClick={() => setShowDetails(!showDetails)}
                          >
                            {showDetails ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
                          </Button>
                          <Button
                            variant="ghost"
                            size="icon-sm"
                            className="size-7 text-zinc-300 hover:bg-white/10 hover:text-white"
                            aria-label="Copy card number"
                            onClick={handleCopyNumber}
                          >
                            {copied ? <Check className="text-success size-3.5" /> : <Copy className="size-3.5" />}
                          </Button>
                        </div>
                      </div>
                    </div>
    
                    {/* Bottom Row: Cardholder, Expiry, CVV */}
                    <div className="relative z-10 flex items-end justify-between pt-2">
                      <div className="space-y-0.5">
                        <p className="text-muted-foreground font-mono text-xs tracking-wider uppercase">Cardholder</p>
                        <p className="max-w-[140px] truncate text-xs font-semibold tracking-wide text-zinc-200 uppercase">
                          {selectedCard.holder}
                        </p>
                      </div>
    
                      <div className="space-y-0.5">
                        <p className="text-muted-foreground font-mono text-xs tracking-wider uppercase">Expires</p>
                        <p className="font-mono text-xs font-medium text-zinc-200 tabular-nums">{selectedCard.expiry}</p>
                      </div>
    
                      <div className="space-y-0.5 text-right">
                        <p className="text-muted-foreground font-mono text-xs tracking-wider uppercase">CVV</p>
                        <p className="font-mono text-xs font-medium text-zinc-200 tabular-nums">
                          {showDetails ? selectedCard.cvv : '•••'}
                        </p>
                      </div>
                    </div>
                  </div>
                </div>
    
                {/* Card Controls & Spending Breakdown */}
                <div className="space-y-6 lg:col-span-6 xl:col-span-7">
                  {/* Spending Limit Meter */}
                  <div className="border-border bg-muted/30 space-y-3 rounded-lg border p-4">
                    <div className="flex items-center justify-between">
                      <span className="text-sm font-medium">Monthly Spending Limit</span>
                      <span className="text-muted-foreground font-mono text-xs tabular-nums">
                        ${selectedCard.spent.toLocaleString()} / ${selectedCard.limit.toLocaleString()}
                      </span>
                    </div>
    
                    <Progress value={selectedPercent} className="h-2" />
    
                    <div className="text-muted-foreground flex items-center justify-between text-xs">
                      <span className="text-foreground font-medium tabular-nums">
                        ${Math.max(0, selectedCard.limit - selectedCard.spent).toLocaleString()} available
                      </span>
                      <span className="font-medium tabular-nums">{selectedPercent}% utilized</span>
                    </div>
                  </div>
    
                  {/* Card Controls Row */}
                  <div className="grid gap-3 sm:grid-cols-3">
                    {/* Freeze Card Toggle */}
                    <div className="border-border bg-card flex items-center justify-between rounded-lg border p-3 sm:flex-col sm:items-start sm:justify-between sm:gap-2">
                      <div className="space-y-0.5">
                        <p className="text-foreground text-xs font-medium">Freeze Card</p>
                        <p className="text-muted-foreground text-xs">Block charges</p>
                      </div>
                      <Switch
                        checked={isSelectedFrozen}
                        size="sm"
                        aria-label="Toggle card freeze status"
                        onCheckedChange={() => toggleFreeze(selectedCard.id)}
                      />
                    </div>
    
                    {/* Change Limit Button */}
                    <Button
                      variant="outline"
                      size="sm"
                      className="h-auto flex-col items-start gap-1 p-3 text-left"
                      onClick={() => adjustLimit(selectedCard.id)}
                    >
                      <div className="flex w-full items-center justify-between">
                        <span className="text-foreground text-xs font-medium">Adjust Limit</span>
                        <SlidersHorizontal className="text-muted-foreground size-3.5" />
                      </div>
                      <span className="text-muted-foreground text-xs font-normal">Edit monthly cap</span>
                    </Button>
    
                    {/* Terminate Card Button */}
                    <Button
                      variant="outline"
                      size="sm"
                      className="text-destructive hover:text-destructive hover:bg-destructive/10 border-border h-auto flex-col items-start gap-1 p-3 text-left"
                      onClick={() => terminateCard(selectedCard.id)}
                    >
                      <div className="flex w-full items-center justify-between">
                        <span className="text-xs font-medium">Terminate</span>
                        <Trash2 className="size-3.5" />
                      </div>
                      <span className="text-muted-foreground text-xs font-normal">Close permanently</span>
                    </Button>
                  </div>
    
                  {/* Card Meta Specs */}
                  <div className="grid grid-cols-2 gap-3 pt-1 sm:grid-cols-3">
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground text-xs">Cardholder</span>
                      <div className="flex items-center gap-1.5 pt-0.5">
                        <Avatar size="xs" className="size-4.5">
                          <AvatarImage src={selectedCard.avatar} alt={selectedCard.holder} />
                          <AvatarFallback className="text-xs">{selectedCard.initials}</AvatarFallback>
                        </Avatar>
                        <span className="truncate text-xs font-medium">{selectedCard.holder}</span>
                      </div>
                    </div>
    
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground text-xs">Card Type</span>
                      <p className="text-foreground text-xs font-medium">{selectedCard.type}</p>
                    </div>
    
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground text-xs">Fraud Protection</span>
                      <div className="text-success flex items-center gap-1 text-xs font-medium">
                        <ShieldCheck className="size-3.5" />
                        <span>3D Secure Active</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </CardContent>
          </Card>
    
          {/* Active Cards Grid / Table */}
          <Card className="border-border bg-card shadow-xs">
            <CardHeader className="border-b pb-4">
              <div className="flex items-center justify-between">
                <div>
                  <CardTitle className="text-base font-semibold">Active Cards List</CardTitle>
                  <CardDescription className="mt-0.5 text-xs">
                    All company virtual cards currently assigned to team members and services.
                  </CardDescription>
                </div>
                <Badge wrap variant="outline" className="font-mono text-xs tabular-nums">
                  {cards.length} Issued
                </Badge>
              </div>
            </CardHeader>
    
            <CardContent className="p-0">
              <div className="divide-border divide-y">
                {cards.map((card) => (
                  <div
                    key={card.id}
                    className={cn(
                      'group flex flex-col gap-4 p-4 transition-colors sm:flex-row sm:items-center sm:justify-between',
                      selectedId === card.id ? 'bg-muted/40' : 'hover:bg-muted/20',
                    )}
                  >
                    {/* Card Identity & Number */}
                    <div className="flex min-w-0 items-center gap-3.5 sm:w-1/3">
                      <div
                        className={cn(
                          'border-border flex size-10 shrink-0 items-center justify-center rounded-lg border shadow-xs transition-transform',
                          card.status === 'Frozen' ? 'bg-muted/60 text-muted-foreground' : 'bg-primary/10 text-primary',
                        )}
                      >
                        <CreditCard className="size-5" />
                      </div>
    
                      <div className="min-w-0 flex-1 space-y-0.5">
                        <div className="flex items-center gap-2">
                          <p className="text-foreground truncate text-sm font-medium">{card.name}</p>
                        </div>
                        <div className="text-muted-foreground flex items-center gap-2 text-xs">
                          <span className="font-mono tabular-nums">•••• {card.last4}</span>
                          <span>·</span>
                          <span className="uppercase">{card.brand}</span>
                        </div>
                      </div>
                    </div>
    
                    {/* Cardholder Avatar & Name */}
                    <div className="flex items-center gap-2 sm:w-1/5">
                      <Avatar size="sm">
                        <AvatarImage src={card.avatar} alt={card.holder} />
                        <AvatarFallback>{card.initials}</AvatarFallback>
                      </Avatar>
                      <div className="min-w-0">
                        <p className="text-foreground truncate text-xs font-medium">{card.holder}</p>
                        <p className="text-muted-foreground truncate text-xs">{card.type}</p>
                      </div>
                    </div>
    
                    {/* Spending Limit & Progress */}
                    <div className="space-y-1.5 sm:w-1/4">
                      <div className="flex items-center justify-between text-xs">
                        <span className="text-foreground font-mono font-medium tabular-nums">
                          ${card.spent.toLocaleString()} / ${card.limit.toLocaleString()}
                        </span>
                        <span className="text-muted-foreground font-mono tabular-nums">
                          {Math.round((card.spent / card.limit) * 100)}%
                        </span>
                      </div>
                      <Progress value={Math.min(100, Math.round((card.spent / card.limit) * 100))} className="h-1.5" />
                    </div>
    
                    {/* Status Badge & Actions */}
                    <div className="flex items-center justify-between gap-3 sm:justify-end">
                      <Badge wrap variant={card.status === 'Active' ? 'success' : 'warning'} className="text-xs">
                        {card.status}
                      </Badge>
    
                      <div className="flex items-center gap-1.5">
                        <Button
                          variant={selectedId === card.id ? 'secondary' : 'ghost'}
                          size="sm"
                          className="text-xs"
                          onClick={() => setSelectedId(card.id)}
                        >
                          {selectedId === card.id ? 'Viewing' : 'View Card'}
                        </Button>
    
                        <Button
                          variant="ghost"
                          size="icon-sm"
                          aria-label={card.status === 'Active' ? 'Freeze card' : 'Unfreeze card'}
                          onClick={() => toggleFreeze(card.id)}
                        >
                          {card.status === 'Active' ? (
                            <Snowflake className="text-muted-foreground size-4" />
                          ) : (
                            <Zap className="text-warning size-4" />
                          )}
                        </Button>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </CardContent>
          </Card>
        </div>
      )
    }
    

Raw manifest:https://uipkge.dev/r/react/virtual-card-manager.json