Vector Motion
Finance

Net Income Card - Bottom Line

Monitor net income and profitability. Track earnings after all expenses and taxes.

Finance Net Income Card

The Finance Net Income Card displays net income metrics and trends, showing the company's bottom-line profitability after all expenses.

Preview

Installation

npx shadcn@latest add https://vectormotion.vercel.app/registry/finance-net-income-card.json
Finance Net Income Card
'use client'import React from 'react';import { TrendingDown, TrendingUp, DollarSign, Wallet } from 'lucide-react';import { ResponsiveContainer, ComposedChart, Bar, Line, XAxis, Tooltip, Cell, ReferenceLine } 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 NetIncomeData {  month: string;  value: number;  margin: number;  [key: string]: any;}interface NetIncomeCardProps {  isInteractive?: boolean;  className?: string;  title?: string;  totalIncome?: string;  incomeLabel?: string;  profitLabel?: string;  targetLabel?: string;  data?: NetIncomeData[];}const DEFAULT_TITLE = "Net Income";const DEFAULT_TOTAL_INCOME = "$41.7k";const DEFAULT_INCOME_LABEL = "YTD";const DEFAULT_PROFIT_LABEL = "+12.4% Profit";const DEFAULT_TARGET_LABEL = "Target: $40k";const DEFAULT_DATA: NetIncomeData[] = [  { month: 'Q1', value: 12500, margin: 12 },  { month: 'Q2', value: 15000, margin: 15 },  { month: 'Q3', value: -4000, margin: -5 }, // Loss  { month: 'Q4', value: 18200, margin: 18 },];export const NetIncomeCard: React.FC<NetIncomeCardProps> = ({  isInteractive = true,  className = "",  title = DEFAULT_TITLE,  totalIncome = DEFAULT_TOTAL_INCOME,  incomeLabel = DEFAULT_INCOME_LABEL,  profitLabel = DEFAULT_PROFIT_LABEL,  targetLabel = DEFAULT_TARGET_LABEL,  data = DEFAULT_DATA,}) => {  const index = 17;  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">{totalIncome}</span>            <span className="text-xs text-muted-foreground">{incomeLabel}</span>          </div>        </div>        <div className="rounded-lg bg-emerald-500/10 p-2 text-emerald-500">          <Wallet className="h-5 w-5" />        </div>      </div>      <div className="relative z-10 flex-1 min-h-[140px]">        <div className="absolute inset-0">          <ResponsiveContainer width="100%" height="100%">            <ComposedChart data={data}>              <XAxis                dataKey="month"                axisLine={false}                tickLine={false}                tick={{ fill: '#71717a', fontSize: 10 }}                dy={10}              />              <Tooltip                cursor={{ fill: 'transparent' }}                content={({ active, payload }) => {                  if (active && payload && payload.length) {                    return (                      <div className="rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 p-2 shadow-sm text-xs">                        <div className="font-bold text-foreground">${payload[0].value}</div>                        <div className="text-muted-foreground">Margin: {payload[0].payload.margin}%</div>                      </div>                    );                  }                  return null;                }}              />              <ReferenceLine y={0} stroke="#e4e4e7" strokeDasharray="3 3" />              <Bar                dataKey="value"                radius={[4, 4, 4, 4]}                barSize={24}              >                {data.map((entry, index) => (                  <Cell                    key={`cell-${index}`}                    fill={entry.value >= 0 ? '#18181b' : '#ef4444'}                    className={entry.value >= 0 ? 'fill-zinc-900 dark:fill-zinc-100' : 'fill-rose-500'}                  />                ))}              </Bar>              <Line type="monotone" dataKey="margin" stroke="#10b981" strokeWidth={2} dot={false} opacity={0.5} />            </ComposedChart>          </ResponsiveContainer>        </div>      </div>      <div className="z-10 mt-2 flex items-center justify-between text-xs pt-2 border-t border-border">        <div className="flex items-center gap-1.5 bg-emerald-500/10 text-emerald-500 px-2 py-1 rounded">          <TrendingUp className="w-3 h-3" />          <span>{profitLabel}</span>        </div>        <span className="text-muted-foreground">{targetLabel}</span>      </div>    </motion.div>  );};

Props

Prop

Type