Finance
Interest Coverage Card - Debt Service
Monitor interest coverage ratios and debt obligations. Track ability to service debt payments.
Finance Interest Coverage Card
The Finance Interest Coverage Card displays the interest coverage ratio, measuring a company's ability to pay interest on outstanding debt.
Preview
Installation
npx shadcn@latest add https://vectormotion.vercel.app/registry/finance-interest-coverage-card.jsonFinance Interest Coverage Card
'use client'import React from 'react';import { ShieldAlert } from 'lucide-react';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 InterestCoverageCardProps { isInteractive?: boolean; className?: string; title?: string; description?: string; value?: number; max?: number; statusText?: string; statusSubtext?: string;}const DEFAULT_TITLE = "Solvency";const DEFAULT_DESCRIPTION = "Interest Coverage";const DEFAULT_VALUE = 4.2;const DEFAULT_MAX = 6.0;const DEFAULT_STATUS_TEXT = "Healthy (> 3.0x)";const DEFAULT_STATUS_SUBTEXT = "EBIT / Interest Exp";export const InterestCoverageCard: React.FC<InterestCoverageCardProps> = ({ isInteractive = true, className = "", title = DEFAULT_TITLE, description = DEFAULT_DESCRIPTION, value = DEFAULT_VALUE, max = DEFAULT_MAX, statusText = DEFAULT_STATUS_TEXT, statusSubtext = DEFAULT_STATUS_SUBTEXT,}) => { // Gauge Config const cx = 50; const cy = 50; const r = 40; const circumference = Math.PI * r; const progress = Math.min(value / max, 1); const offset = circumference - (progress * circumference); const index = 40; 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-6 shadow-sm transition-all flex flex-col group", isInteractive ? "cursor-pointer hover:border-zinc-300 dark:hover:border-zinc-700" : "", className )} > <div className="mb-4 flex items-start justify-between relative z-10"> <div> <h3 className="font-semibold text-lg tracking-tight text-foreground"> {title} </h3> {description && ( <p className="text-sm text-muted-foreground mt-1"> {description} </p> )} </div> <div className="rounded-full bg-zinc-100 dark:bg-zinc-800 p-2 text-foreground flex items-center justify-center"> <ShieldAlert className="h-5 w-5" /> </div> </div> <div className="relative z-10 flex-1"> <div className="flex flex-col items-center py-2"> <div className="relative w-40 h-20 overflow-hidden flex justify-center mb-2"> <svg viewBox="0 0 100 50" className="w-full h-full"> {/* Track */} <path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="currentColor" strokeWidth="10" className="text-zinc-200 dark:text-zinc-800" strokeLinecap="round" /> {/* Fill */} <motion.path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#10b981" strokeWidth="10" strokeLinecap="round" strokeDasharray={circumference} initial={{ strokeDashoffset: circumference }} animate={{ strokeDashoffset: offset }} transition={{ duration: 1.5, ease: "easeOut" }} /> </svg> <div className="absolute bottom-0"> <span className="text-xl font-bold text-foreground">{value}x</span> </div> </div> <div className="text-center"> <p className="text-xs text-muted-foreground">{statusText}</p> <p className="text-[10px] text-muted-foreground mt-1">{statusSubtext}</p> </div> </div> </div> </motion.div> );};Props
Prop
Type