import React, { useState } from "react"; import { Info } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"; import { cn } from "@/lib/utils"; import { formatCurrency } from "@/lib/format-currency"; /** * ExpenseCategoriesBar — WealthX Design System * * Horizontal stacked percentage bar for categorical breakdowns. * Used for both expense categories (`color="secondary"`, default) * and income sources (`color="primary"`). * * - `color="secondary"` (default): segments use `--brand-secondary` (dark navy) * - `color="primary"`: segments use `--primary` (tenant green) * * Hovering a segment shows a tooltip with the category name, icon, and amount. * * Mirrors the FE component at * `frontend/src/components/ExpenseCategoriesBar/index.tsx` */ // ─── Types ─────────────────────────────────────────────────────────────────── export interface ExpenseCategoryPart { id: string; title: string; /** Fraction of total (0–1). Used to size the segment. */ percentage: number; /** Absolute amount in AUD. Shown in the hover tooltip. */ amount: number; } export interface ExpenseCategoriesBarProps { /** Main heading above the bar (e.g. "Expense Categories"). Omit to hide the header row entirely. */ title?: string; /** Secondary label shown next to the title (e.g. date range or period) */ subtitle?: string; /** Text shown in the info-icon tooltip next to the subtitle */ tooltipText?: string; /** Ordered list of category segments */ parts?: ExpenseCategoryPart[]; /** Icon node for each segment, indexed to match `parts` */ icons?: React.ReactNode[]; /** * Bar segment colour scheme. * - `"secondary"` (default) — `--brand-secondary` (dark navy); used for expenses * - `"primary"` — `--primary` (tenant green); used for income sources */ color?: "primary" | "secondary"; className?: string; } // ─── Component ─────────────────────────────────────────────────────────────── export function ExpenseCategoriesBar({ title, subtitle, tooltipText, parts = [], icons = [], color = "secondary", className, }: ExpenseCategoriesBarProps) { const [hoveredIndex, setHoveredIndex] = useState(-1); const segmentBase = color === "primary" ? "bg-primary" : "bg-brand-secondary"; const segmentHover = color === "primary" ? "border-primary/50 bg-primary/60" : "border-brand-secondary/50 bg-brand-secondary/60"; // Use *-foreground tokens so icons/text on a primary/secondary background // always have sufficient contrast — avoids hardcoded colours that fail on // custom tenant themes. const iconDefault = color === "primary" ? "[&_svg]:stroke-primary-foreground [&_svg]:text-primary-foreground" : "[&_svg]:stroke-brand-secondary-foreground [&_svg]:text-brand-secondary-foreground"; const iconHovered = "[&_svg]:stroke-foreground [&_svg]:text-foreground"; return (
{/* ── Header ── */} {(title || subtitle) && (
{title && (

{title}

)} {subtitle && (
{subtitle} {tooltipText && ( } > {tooltipText} )}
)}
)} {/* ── Bar ── */}
setHoveredIndex(-1)} > {parts.length === 0 ? (
) : ( parts.map((part, index) => { const isHovered = hoveredIndex === index; const showIcon = part.percentage > 0.01; const icon = icons[index]; return ( setHoveredIndex(index)} /> } > {showIcon && icon && (
{icon}
)}
{icon && {icon}} {part.title}
{formatCurrency(part.amount)}
); }) )}
); }