Vector Motion
Finance

Portfolio Card - Asset Management

Monitor investment portfolio composition and performance. Track diversification and asset allocation.

Finance Portfolio Card

The Finance Portfolio Card displays current investment holdings with allocation percentages and performance metrics, helping investors track their portfolio distribution and individual asset performance.

Preview

Installation

npx shadcn@latest add https://vectormotion.vercel.app/registry/finance-portfolio-card.json
Finance Portfolio Card
'use client'import React from 'react';import { PieChart as PieChartIcon, Wallet } from 'lucide-react';import { motion } from 'motion/react';import { clsx, type ClassValue } from "clsx"import { twMerge } from "tailwind-merge"import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip } from 'recharts';function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs))}interface AssetData {  name: string;  fullName: string;  value: string;  change: string;  percent: number;  color: string;  [key: string]: any;}interface PortfolioCardProps {  isInteractive?: boolean;  className?: string;  title?: string;  totalValue?: string;  totalValueLabel?: string;  data?: AssetData[];}const DEFAULT_TITLE = "Allocation";const DEFAULT_TOTAL_VALUE = "$29k";const DEFAULT_TOTAL_VALUE_LABEL = "Total Value";const DEFAULT_DATA: AssetData[] = [  { name: "AAPL", fullName: "Apple Inc.", value: "$14,205", change: "+2.4%", percent: 45, color: '#10b981' }, // Emerald  { name: "TSLA", fullName: "Tesla", value: "$8,400", change: "-1.2%", percent: 25, color: '#3b82f6' },   // Blue  { name: "GOOG", fullName: "Alphabet", value: "$4,100", change: "+0.8%", percent: 15, color: '#f59e0b' }, // Amber  { name: "CASH", fullName: "Reserves", value: "$2,300", change: "0.0%", percent: 15, color: '#f43f5e' }, // Rose];const container = {  hidden: { opacity: 0 },  show: {    opacity: 1,    transition: {      staggerChildren: 0.1    }  }};const item = {  hidden: { x: -20, opacity: 0 },  show: { x: 0, opacity: 1 }};export const PortfolioCard: React.FC<PortfolioCardProps> = ({  isInteractive = true,  className = "",  title = DEFAULT_TITLE,  totalValue = DEFAULT_TOTAL_VALUE,  totalValueLabel = DEFAULT_TOTAL_VALUE_LABEL,  data = DEFAULT_DATA,}) => {  const index = 30;  return (    <motion.div      layoutId={isInteractive ? `card-${index}-${title}` : undefined}      transition={{ duration: 0.4, ease: "easeOut" }}      className={cn(        "relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground p-5 shadow-sm transition-all flex flex-col h-full group",        isInteractive ? "cursor-pointer hover:border-primary/50 hover:shadow-md" : "",        className      )}    >      <div className="mb-2 flex items-start justify-between relative z-10">        <div>          <h3 className="font-semibold text-lg text-foreground">            {title}          </h3>          <div className="flex items-center gap-2 mt-1">            <span className="text-2xl font-bold text-foreground">{totalValue}</span>            <span className="text-xs text-muted-foreground">{totalValueLabel}</span>          </div>        </div>        <div className="h-12 w-12">          <ResponsiveContainer width="100%" height="100%">            <PieChart>              <Pie data={data} dataKey="percent" innerRadius={12} outerRadius={22} stroke="none">                {data.map((entry, index) => (                  <Cell key={`cell-${index}`} fill={entry.color} />                ))}              </Pie>              <Tooltip contentStyle={{ borderRadius: '8px', border: 'none', backgroundColor: 'var(--tooltip-bg)', color: 'var(--tooltip-text)', fontSize: '10px' }} />            </PieChart>          </ResponsiveContainer>        </div>      </div>      <div className="relative z-10 flex-1">        <motion.div          className="space-y-3"          variants={container}          initial="hidden"          animate="show"        >          {data.slice(0, 3).map((asset) => (            <motion.div              key={asset.name}              variants={item}              className="group cursor-pointer"            >              <div className="flex items-center justify-between mb-1">                <div className="flex items-center gap-2">                  <div className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: asset.color }} />                  <span className="font-bold text-xs text-foreground w-8">{asset.name}</span>                  <span className="text-[10px] text-muted-foreground hidden sm:inline">{asset.fullName}</span>                </div>                <div className="text-right">                  <span className="block text-xs font-medium text-foreground">{asset.value}</span>                </div>              </div>              <div className="flex items-center gap-2">                <div className="h-1 w-full rounded-full bg-zinc-100 dark:bg-zinc-800 overflow-hidden">                  <motion.div                    initial={{ width: 0 }}                    animate={{ width: `${asset.percent}%` }}                    transition={{ duration: 1, ease: "easeOut", delay: 0.2 }}                    className="h-full rounded-full"                    style={{ backgroundColor: asset.color }}                  />                </div>              </div>            </motion.div>          ))}        </motion.div>      </div>    </motion.div>  );};

Props

Prop

Type