{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cash-flow-chart",
  "type": "registry:ui",
  "title": "Accessible Cash Flow Chart",
  "description": "Interactive cash flow bar chart with keyboard focus, tooltips, and screen reader support",
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/data/charts/cash-flow-chart.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { useMemo, useState } from \"react\";\n\ntype ChartView = \"monthly\" | \"yearly\";\n\ninterface CashFlowDatum {\n  label: string;\n  shortLabel: string;\n  value: number;\n  cashflow: number;\n  inflow: number;\n  dateRange: string;\n}\n\nconst monthlyData: CashFlowDatum[] = [\n  {\n    label: \"January 2026\",\n    shortLabel: \"Jan\",\n    value: 35000,\n    cashflow: 35000,\n    inflow: 12000,\n    dateRange: \"January 2026\",\n  },\n  {\n    label: \"February 2026\",\n    shortLabel: \"Feb\",\n    value: 28000,\n    cashflow: 28000,\n    inflow: 9500,\n    dateRange: \"February 2026\",\n  },\n  {\n    label: \"March 2026\",\n    shortLabel: \"Mar\",\n    value: 52000,\n    cashflow: 52000,\n    inflow: 14200,\n    dateRange: \"March 2026\",\n  },\n  {\n    label: \"April 2026\",\n    shortLabel: \"Apr\",\n    value: 31000,\n    cashflow: 31000,\n    inflow: 10100,\n    dateRange: \"April 2026\",\n  },\n  {\n    label: \"May 2026\",\n    shortLabel: \"May\",\n    value: 38000,\n    cashflow: 38000,\n    inflow: 11800,\n    dateRange: \"May 2026\",\n  },\n  {\n    label: \"June 2026\",\n    shortLabel: \"Jun\",\n    value: 22000,\n    cashflow: 22000,\n    inflow: 8200,\n    dateRange: \"June 2026\",\n  },\n  {\n    label: \"July 2026\",\n    shortLabel: \"Jul\",\n    value: 33000,\n    cashflow: 33000,\n    inflow: 9600,\n    dateRange: \"July 2026\",\n  },\n];\n\nconst yearlyData: CashFlowDatum[] = [\n  {\n    label: \"2020\",\n    shortLabel: \"2020\",\n    value: 265000,\n    cashflow: 265000,\n    inflow: 62000,\n    dateRange: \"Fiscal Year 2020\",\n  },\n  {\n    label: \"2021\",\n    shortLabel: \"2021\",\n    value: 302000,\n    cashflow: 302000,\n    inflow: 78500,\n    dateRange: \"Fiscal Year 2021\",\n  },\n  {\n    label: \"2022\",\n    shortLabel: \"2022\",\n    value: 358000,\n    cashflow: 358000,\n    inflow: 91200,\n    dateRange: \"Fiscal Year 2022\",\n  },\n  {\n    label: \"2023\",\n    shortLabel: \"2023\",\n    value: 402000,\n    cashflow: 402000,\n    inflow: 103500,\n    dateRange: \"Fiscal Year 2023\",\n  },\n  {\n    label: \"2024\",\n    shortLabel: \"2024\",\n    value: 446000,\n    cashflow: 446000,\n    inflow: 118000,\n    dateRange: \"Fiscal Year 2024\",\n  },\n  {\n    label: \"2025\",\n    shortLabel: \"2025\",\n    value: 489000,\n    cashflow: 489000,\n    inflow: 124300,\n    dateRange: \"Fiscal Year 2025 (projected)\",\n  },\n];\n\nconst formatCurrency = (value: number) =>\n  new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency: \"USD\",\n    maximumFractionDigits: 0,\n  }).format(value);\n\nexport function CashFlowChart() {\n  const [hoveredBar, setHoveredBar] = useState<number | null>(null);\n  const [activeView, setActiveView] = useState<ChartView>(\"yearly\");\n  const prefersReducedMotion = useReducedMotion();\n\n  const displayData = useMemo(\n    () => (activeView === \"monthly\" ? monthlyData : yearlyData),\n    [activeView]\n  );\n\n  const maxValue = useMemo(\n    () => Math.max(...displayData.map((d) => d.value)),\n    [displayData]\n  );\n\n  const summaryText = useMemo(() => {\n    if (!displayData.length) {\n      return \"No cash flow data available.\";\n    }\n\n    const highest = displayData.reduce((prev, current) =>\n      current.value > prev.value ? current : prev\n    );\n    const lowest = displayData.reduce((prev, current) =>\n      current.value < prev.value ? current : prev\n    );\n    const first = displayData[0];\n    const last = displayData[displayData.length - 1];\n\n    if (activeView === \"monthly\") {\n      return `Monthly cash flow from ${first.label} to ${last.label}. Highest cash flow in ${highest.label} at ${formatCurrency(\n        highest.value\n      )}. Lowest in ${lowest.label} at ${formatCurrency(lowest.value)}.`;\n    }\n\n    return `Yearly cash flow from ${first.label} to ${last.label}. Highest cash flow in ${highest.label} at ${formatCurrency(\n      highest.value\n    )}. Lowest in ${lowest.label} at ${formatCurrency(lowest.value)}.`;\n  }, [activeView, displayData]);\n\n  const chartTitleId = \"cashflow-chart-title\";\n  const chartSummaryId = \"cashflow-chart-summary\";\n\n  const handleViewChange = (view: ChartView) => {\n    setActiveView(view);\n    setHoveredBar(null);\n  };\n\n  const shouldAnimate = !prefersReducedMotion;\n\n  return (\n    <motion.section\n      initial={shouldAnimate ? { opacity: 0, y: 20 } : { opacity: 1, y: 0 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={shouldAnimate ? { duration: 0.5 } : { duration: 0 }}\n      className=\"w-full bg-card rounded-3xl p-8 hover:shadow-2xl backdrop-blur-sm border border-border\"\n      aria-labelledby={chartTitleId}\n      aria-describedby={chartSummaryId}\n      role=\"group\"\n    >\n      <div className=\"grid grid-cols-1 md:flex items-start justify-between mb-8 gap-6\">\n        <div>\n          <p\n            className=\"text-muted-foreground text-lg mb-2\"\n            id=\"cashflow-chart-label\"\n          >\n            Cash Flow\n          </p>\n          <motion.h2\n            id={chartTitleId}\n            initial={shouldAnimate ? { scale: 0.95 } : { scale: 1 }}\n            animate={{ scale: 1 }}\n            transition={shouldAnimate ? { duration: 0.4 } : { duration: 0 }}\n            className=\"text-3xl md:text-5xl font-bold text-foreground\"\n          >\n            {formatCurrency(\n              displayData.reduce((total, datum) => total + datum.cashflow, 0)\n            )}\n          </motion.h2>\n          <p id={chartSummaryId} className=\"sr-only\" aria-live=\"polite\">\n            {summaryText}\n          </p>\n        </div>\n\n        <div\n          className=\"flex bg-secondary rounded-full p-1 w-fit\"\n          role=\"radiogroup\"\n          aria-label=\"Chart view\"\n        >\n          <button\n            type=\"button\"\n            onClick={() => handleViewChange(\"monthly\")}\n            className={cn(\n              \"px-6 py-2 rounded-full text-sm font-medium transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-secondary\",\n              activeView === \"monthly\"\n                ? \"bg-primary text-primary-foreground shadow-lg\"\n                : \"text-foreground hover:text-primary\"\n            )}\n            aria-pressed={activeView === \"monthly\"}\n            aria-controls=\"cashflow-chart-bars\"\n          >\n            Monthly\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => handleViewChange(\"yearly\")}\n            className={cn(\n              \"px-6 py-2 rounded-full text-sm font-medium transition-all duration-300 flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-secondary\",\n              activeView === \"yearly\"\n                ? \"bg-primary text-primary-foreground shadow-lg\"\n                : \"text-foreground hover:text-primary\"\n            )}\n            aria-pressed={activeView === \"yearly\"}\n            aria-controls=\"cashflow-chart-bars\"\n          >\n            <span className=\"sr-only\">Currently selected:&nbsp;</span>\n            Yearly\n          </button>\n        </div>\n      </div>\n\n      <figure\n        className=\"relative h-auto overflow-x-auto md:overflow-x-visible overflow-y-hidden md:overflow-y-visible pt-10 md:pt-0\"\n        aria-labelledby={chartTitleId}\n      >\n        <figcaption className=\"sr-only\">\n          Bar chart comparing cash flow performance by period. Use the Monthly\n          or Yearly toggle to change the data set.\n        </figcaption>\n\n        <div className=\"relative min-w-[640px] md:min-w-0 h-[24rem]\">\n          <div\n            className=\"absolute left-0 top-0 bottom-12 flex flex-col justify-between text-sm text-muted-foreground\"\n            aria-hidden=\"true\"\n          >\n            {[50, 40, 30, 20, 10, 0].map((value) => (\n              <div key={value}>{value}k</div>\n            ))}\n          </div>\n\n          <div\n            id=\"cashflow-chart-bars\"\n            className=\"absolute left-16 right-0 top-0 bottom-12 flex items-end justify-between gap-4\"\n            role=\"list\"\n            aria-describedby={chartSummaryId}\n          >\n            {displayData.map((data, index) => {\n              const heightPercentage = maxValue\n                ? Math.max((data.value / maxValue) * 100, 4)\n                : 0;\n              // Keep tooltip inside the chart area so it doesn't get clipped\n              const tooltipBase = Math.min(heightPercentage + 8, 88);\n              const tooltipVisible = hoveredBar === index;\n              const tooltipPosition = `${tooltipBase}%`;\n\n              return (\n                <div\n                  key={`${data.shortLabel}-${data.label}`}\n                  className=\"relative flex-1 flex flex-col items-center justify-end h-full\"\n                  onMouseEnter={() => setHoveredBar(index)}\n                  onMouseLeave={() => setHoveredBar(null)}\n                  role=\"listitem\"\n                >\n                  <AnimatePresence>\n                    {tooltipVisible && (\n                      <motion.div\n                        initial={\n                          shouldAnimate\n                            ? { opacity: 0, y: 10, scale: 0.95 }\n                            : { opacity: 1, y: 0, scale: 1 }\n                        }\n                        animate={{ opacity: 1, y: 0, scale: 1 }}\n                        exit={\n                          shouldAnimate\n                            ? { opacity: 0, y: 10, scale: 0.95 }\n                            : { opacity: 0, y: 10, scale: 0.95 }\n                        }\n                        transition={\n                          shouldAnimate ? { duration: 0.2 } : { duration: 0 }\n                        }\n                        className=\"absolute mx-auto bg-popover rounded-2xl p-4 shadow-xl border border-border min-w-[200px] z-10 text-left\"\n                        role=\"status\"\n                        aria-live=\"polite\"\n                        style={{ bottom: tooltipPosition }}\n                      >\n                        <div className=\"text-xs text-muted-foreground mb-2\">\n                          {data.dateRange}\n                        </div>\n                        <div className=\"flex justify-between items-center mb-1\">\n                          <span className=\"text-sm text-foreground\">\n                            Cash flow\n                          </span>\n                          <span className=\"text-sm font-semibold text-foreground\">\n                            {formatCurrency(data.cashflow)}\n                          </span>\n                        </div>\n                        <div className=\"flex justify-between items-center\">\n                          <span className=\"text-sm text-foreground\">\n                            Inflow\n                          </span>\n                          <span className=\"text-sm font-semibold text-foreground\">\n                            {formatCurrency(data.inflow)}\n                          </span>\n                        </div>\n                      </motion.div>\n                    )}\n                  </AnimatePresence>\n\n                  <motion.button\n                    type=\"button\"\n                    initial={shouldAnimate ? { height: 0 } : false}\n                    animate={{ height: `${heightPercentage}%` }}\n                    transition={\n                      shouldAnimate\n                        ? {\n                            duration: 0.8,\n                            delay: index * 0.08,\n                            ease: \"easeOut\",\n                          }\n                        : { duration: 0 }\n                    }\n                    onFocus={() => setHoveredBar(index)}\n                    onBlur={() => setHoveredBar(null)}\n                    className={`w-full rounded-t-2xl relative overflow-hidden cursor-pointer transition-colors duration-300 min-h-[0.5rem] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background ${tooltipVisible ? \"bg-gradient-to-b from-primary to-primary/20\" : \"bg-primary/30\"}\n                    `}\n                    aria-label={`${data.label} cash flow ${formatCurrency(\n                      data.cashflow\n                    )} with ${formatCurrency(data.inflow)} in inflow`}\n                    aria-describedby={`${data.shortLabel}-summary`}\n                  >\n                    <span id={`${data.shortLabel}-summary`} className=\"sr-only\">\n                      {`${data.label}: cash flow ${formatCurrency(\n                        data.cashflow\n                      )}, inflow ${formatCurrency(data.inflow)}`}\n                    </span>\n                  </motion.button>\n\n                  <div\n                    className=\"text-sm text-muted-foreground mt-3\"\n                    aria-hidden=\"true\"\n                  >\n                    {data.shortLabel}\n                  </div>\n                </div>\n              );\n            })}\n          </div>\n        </div>\n\n        <table className=\"sr-only\">\n          <caption>\n            Tabular representation of the cash flow data for assistive\n            technologies.\n          </caption>\n          <thead>\n            <tr>\n              <th scope=\"col\">Period</th>\n              <th scope=\"col\">Cash flow</th>\n              <th scope=\"col\">Inflow</th>\n            </tr>\n          </thead>\n          <tbody>\n            {displayData.map((datum) => (\n              <tr key={`table-${datum.label}`}>\n                <th scope=\"row\">{datum.label}</th>\n                <td>{formatCurrency(datum.cashflow)}</td>\n                <td>{formatCurrency(datum.inflow)}</td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </figure>\n    </motion.section>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/uitripled/cash-flow-chart-carbon.tsx"
    }
  ]
}