Vector Motion
Finance

Asset Allocation Drift Card - Portfolio Management

Monitor portfolio drift from target asset allocation. Track rebalancing needs and investment performance.

Finance Asset Allocation Drift Card

The Finance Asset Allocation Drift Card tracks how your current portfolio allocation compares to target allocations, helping investors maintain their desired asset mix and rebalance when necessary.

Preview

Installation

npx shadcn@latest add https://vectormotion.vercel.app/registry/finance-asset-allocation-drift-card.json
Finance Asset Allocation Drift Card
'use client';'use client';import React from 'react';import { Scale, AlertTriangle, ArrowRight } from 'lucide-react';import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, ReferenceLine, Cell } from 'recharts';import { motion } from 'motion/react';import { clsx, type ClassValue } from "clsx"import { twMerge } from "tailwind-merge"function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs))}interface DataItem {  asset: string;  drift: number;}interface AssetAllocationDriftCardProps {  isInteractive?: boolean;  className?: string;  title?: string;  driftValue?: string;  driftLabel?: string;  data?: DataItem[];}const DEFAULT_TITLE = "Portfolio Drift";const DEFAULT_DRIFT_VALUE = "2.5%";const DEFAULT_DRIFT_LABEL = "Max Deviation";const DEFAULT_DATA: DataItem[] = [  { asset: 'Equity', drift: 2.5 },   // Overweight  { asset: 'Fixed Inc', drift: -1.8 }, // Underweight  { asset: 'Alt', drift: 0.5 },      // Slight overweight  { asset: 'Cash', drift: -1.2 },    // Underweight];export const AssetAllocationDriftCard: React.FC<AssetAllocationDriftCardProps> = ({  isInteractive = true,  className = "",  title = DEFAULT_TITLE,  driftValue = DEFAULT_DRIFT_VALUE,  driftLabel = DEFAULT_DRIFT_LABEL,  data = DEFAULT_DATA,}) => {  const index = 42;  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-xl font-bold text-foreground">{driftValue}</span>            <span className="text-xs text-amber-500 font-medium">{driftLabel}</span>          </div>        </div>        <div className="rounded-lg bg-emerald-500/10 p-2 text-emerald-500">          <Scale className="h-5 w-5" />        </div>      </div>      <div className="relative z-10 flex-1 min-h-[140px]">        <ResponsiveContainer width="100%" height="100%">          <BarChart data={data} layout="vertical" stackOffset="sign" margin={{ left: 0, right: 30 }}>            <XAxis type="number" hide domain={[-3, 3]} />            <YAxis              dataKey="asset"              type="category"              axisLine={false}              tickLine={false}              width={60}              tick={{ fill: '#71717a', fontSize: 10, fontWeight: 500 }}            />            <Tooltip              cursor={{ fill: 'transparent' }}              contentStyle={{ backgroundColor: 'var(--tooltip-bg)', borderRadius: '8px', border: 'none', color: 'var(--tooltip-text)', fontSize: '12px' }}              formatter={(value: number) => [`${value > 0 ? '+' : ''}${value}%`, 'Drift']}            />            <ReferenceLine x={0} stroke="#e4e4e7" strokeDasharray="3 3" />            <Bar dataKey="drift" barSize={12} radius={[4, 4, 4, 4]}>              {data.map((entry, index) => (                <Cell                  key={`cell-${index}`}                  fill={entry.drift > 0 ? '#10b981' : '#f43f5e'} // Emerald for Over (positive), Rose for Under (negative) for visual distinction                  className={entry.drift > 0 ? "fill-emerald-500" : "fill-rose-500"}                />              ))}            </Bar>          </BarChart>        </ResponsiveContainer>        {/* Rebalance Action */}        <div className="absolute top-0 right-0">          <div className="flex items-center gap-1 text-amber-600 bg-amber-500/10 px-2 py-1 rounded-full text-[10px] font-medium border border-amber-100 dark:border-amber-900/30">            <AlertTriangle className="w-3 h-3" />            Rebalance          </div>        </div>      </div>      <div className="z-10 mt-2 flex items-center justify-between text-xs pt-2 border-t border-border">        <span className="text-muted-foreground">Target Model</span>        <button className="flex items-center gap-1 font-medium text-emerald-500 hover:underline">          View <ArrowRight className="w-3 h-3" />        </button>      </div>    </motion.div>  );};

Props

Prop

Type