{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "crypto-price-chart",
  "title": "Crypto Price Chart",
  "description": "Interactive animated crypto price chart with tabs and holdings summary.",
  "dependencies": [
    "recharts",
    "motion",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/ui/crypto-price-chart.tsx",
      "content": "\"use client\";\r\nimport { BitcoinPiggyBankIcon, Mining03Icon } from \"@hugeicons/core-free-icons\";\r\nimport { HugeiconsIcon } from \"@hugeicons/react\";\r\nimport { useState } from \"react\";\r\nimport { motion } from \"motion/react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n  Area,\r\n  AreaChart,\r\n  ResponsiveContainer,\r\n  Tooltip,\r\n  TooltipContentProps,\r\n  YAxis,\r\n} from \"recharts\";\r\nimport { useId } from \"react\";\r\n\r\ntype TabKey = \"1H\" | \"1D\" | \"1W\" | \"1M\" | \"1Y\" | \"ALL\";\r\nconst Tabs: TabKey[] = [\"1H\", \"1D\", \"1W\", \"1M\", \"1Y\", \"ALL\"];\r\n\r\nconst getRangeLabels = (range: TabKey) => {\r\n  switch (range) {\r\n    case \"1H\":\r\n      return { low: \"Hour Low\", high: \"Hour High\" };\r\n    case \"1D\":\r\n      return { low: \"Today's Low\", high: \"Today's High\" };\r\n    case \"1W\":\r\n      return { low: \"Week Low\", high: \"Week High\" };\r\n    case \"1M\":\r\n      return { low: \"Month Low\", high: \"Month High\" };\r\n    case \"1Y\":\r\n      return { low: \"Year Low\", high: \"Year High\" };\r\n    case \"ALL\":\r\n      return { low: \"All-T Low\", high: \"All-T High\" };\r\n    default:\r\n      return { low: \"Low\", high: \"High\" };\r\n  }\r\n};\r\n\r\nconst formatCurrency = (value: number) =>\r\n  new Intl.NumberFormat(\"en-US\", {\r\n    style: \"currency\",\r\n    currency: \"USD\",\r\n    maximumFractionDigits: 0,\r\n  }).format(value);\r\n\r\nexport interface CryptoPriceChartProps {\r\n  title: string;\r\n  quantityOwned: number;\r\n  chartData: Record<TabKey, { time: string; price: number }[]>;\r\n}\r\n\r\nexport const CryptoPriceChart = ({\r\n  title,\r\n  quantityOwned,\r\n  chartData,\r\n}: CryptoPriceChartProps) => {\r\n  const [hoverPrice, setHoverPrice] = useState<number | null>(null);\r\n  const [active, setActive] = useState<TabKey>(\"1D\");\r\n  const data = chartData[active];\r\n  const low = Math.min(...data.map((d) => d.price));\r\n  const high = Math.max(...data.map((d) => d.price));\r\n  const currentPrice = data[data.length - 1].price;\r\n  const firstPrice = data[0].price;\r\n  const lastPrice = data[data.length - 1].price;\r\n  const displayPrice = hoverPrice ?? lastPrice;\r\n  const { low: lowLabel, high: highLabel } = getRangeLabels(active);\r\n  const percentageChange = ((lastPrice - firstPrice) / firstPrice) * 100;\r\n  const layoutScope = useId();\r\n\r\n  return (\r\n    <div className=\"w-80 h-80 bg-neutral-900 dark:bg-neutral-950 rounded-3xl border-3 border-double border-neutral-500 dark:border-neutral-800 p-4 text-neutral-100\">\r\n      <div className=\"flex flex-col gap-2 h-full\">\r\n        <ChartHeader\r\n          quantityOwned={quantityOwned}\r\n          displayPrice={displayPrice}\r\n          percentageChange={percentageChange}\r\n          title={title}\r\n        />\r\n        <MainChart\r\n          active={active}\r\n          setHoverPrice={setHoverPrice}\r\n          chartData={chartData}\r\n        />\r\n        <ChartTabs\r\n          active={active}\r\n          setActive={setActive}\r\n          layoutScope={layoutScope}\r\n        />\r\n        <ChartFooter\r\n          quantityOwned={quantityOwned}\r\n          high={high}\r\n          low={low}\r\n          currentPrice={currentPrice}\r\n          lowLabel={lowLabel}\r\n          highLabel={highLabel}\r\n        />\r\n      </div>\r\n    </div>\r\n  );\r\n};\r\n\r\nconst MainChart = ({\r\n  active,\r\n  setHoverPrice,\r\n  chartData,\r\n}: {\r\n  active: TabKey;\r\n  setHoverPrice: React.Dispatch<React.SetStateAction<number | null>>;\r\n  chartData: Record<TabKey, { time: string; price: number }[]>;\r\n}) => {\r\n  return (\r\n    <div className=\"flex-1 w-full h-full\">\r\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\r\n        <AreaChart\r\n          data={chartData[active]}\r\n          onMouseMove={(state) => {\r\n            const rawIndex = state?.activeTooltipIndex;\r\n            if (rawIndex !== undefined && rawIndex !== null) {\r\n              const index = Number(rawIndex);\r\n              const data = chartData[active];\r\n              if (!Number.isNaN(index) && data[index]) {\r\n                setHoverPrice(data[index].price);\r\n              }\r\n            }\r\n          }}\r\n          onMouseLeave={() => {\r\n            setHoverPrice(null);\r\n          }}\r\n        >\r\n          <Area\r\n            dataKey=\"price\"\r\n            type=\"linear\"\r\n            stroke=\"#FF6500\"\r\n            strokeWidth={2}\r\n            fill=\"url(#membersFillGradient)\"\r\n            activeDot={{\r\n              r: 3,\r\n              fill: \"#ffffff\",\r\n              stroke: \"#FA6868\",\r\n              strokeWidth: 1,\r\n            }}\r\n          />\r\n          <Tooltip content={CustomToolTip} cursor={false} />\r\n          {/* Use it as your preference */}\r\n          {/* \r\n          <YAxis\r\n            dataKey=\"price\"\r\n            width={30}\r\n            tickCount={7}\r\n            \r\n            domain={[\"dataMin - 2000\", \"dataMax + 2000\"]}\r\n            tickLine={false}\r\n            tickFormatter={formatCompactNumber}\r\n            axisLine={false}\r\n            tick={{ fontSize: 10, fill: \"#FAF3E1\" }}\r\n          /> */}\r\n\r\n          <defs>\r\n            <linearGradient\r\n              id=\"membersFillGradient\"\r\n              x1=\"0\"\r\n              y1=\"0\"\r\n              x2=\"0\"\r\n              y2=\"1\"\r\n            >\r\n              <stop offset=\"0%\" stopColor=\"#FF6500\" stopOpacity=\"0.45\" />\r\n              <stop offset=\"60%\" stopColor=\"#FF6500\" stopOpacity=\"0.15\" />\r\n              <stop offset=\"100%\" stopColor=\"#FF6500\" stopOpacity=\"0\" />\r\n            </linearGradient>\r\n          </defs>\r\n        </AreaChart>\r\n      </ResponsiveContainer>\r\n    </div>\r\n  );\r\n};\r\n\r\nconst CustomToolTip = ({ active, payload, label }: TooltipContentProps) => {\r\n  if (!active || !payload?.length || !label) return null;\r\n  return (\r\n    <div>\r\n      {payload.map((entry) => {\r\n        const value = entry.value as number;\r\n\r\n        return (\r\n          <span\r\n            key={String(entry.dataKey)}\r\n            className=\"text-white rounded-sm px-1.5 py-0.5 bg-[#13b677] text-[11px] font-semibold shadow-[inset_0_1px_0_rgba(255,255,255,0.6)] flex gap-1\"\r\n          >\r\n            <span>{entry.payload.time}</span>\r\n            {formatCurrency(value)}\r\n          </span>\r\n        );\r\n      })}\r\n    </div>\r\n  );\r\n};\r\n\r\ntype ChartTabsProps = {\r\n  active: TabKey;\r\n  setActive: React.Dispatch<React.SetStateAction<TabKey>>;\r\n  layoutScope: string;\r\n};\r\n\r\nconst ChartTabs = ({ active, setActive, layoutScope }: ChartTabsProps) => {\r\n  return (\r\n    <div className=\"flex gap-1.5 p-1\">\r\n      {Tabs.map((tab) => {\r\n        const isActive = active === tab;\r\n        return (\r\n          <button\r\n            key={tab}\r\n            onClick={() => setActive(tab)}\r\n            className=\"relative flex-1 text-[11px] cursor-pointer rounded-md py-1 bg-linear-to-b from-neutral-600\r\n             to-neutral-800 dark:bg-linear-to-b dark:from-neutral-800 dark:to-neutral-900 text-neutral-400 hover:text-white transition-colors duration-200\"\r\n          >\r\n            {isActive && (\r\n              <motion.div\r\n                layoutId={`chartActiveTab-${layoutScope}`}\r\n                transition={{ type: \"spring\", bounce: 0.3, duration: 0.4 }}\r\n                className=\"absolute inset-0 bg-linear-to-b from-orange-400 to-orange-700 rounded-md shadow-[inset_0_1px_0_rgba(255,255,255,0.6)]\"\r\n              />\r\n            )}\r\n            <span className={cn(\"relative z-20\", isActive && \"text-white\")}>\r\n              {tab}\r\n            </span>\r\n          </button>\r\n        );\r\n      })}\r\n    </div>\r\n  );\r\n};\r\n\r\ninterface ChartFooterProps {\r\n  low: number;\r\n  high: number;\r\n  quantityOwned: number;\r\n  currentPrice: number;\r\n  lowLabel: string;\r\n  highLabel: string;\r\n}\r\n\r\nconst ChartFooter = ({\r\n  low,\r\n  high,\r\n  quantityOwned,\r\n  currentPrice,\r\n  lowLabel,\r\n  highLabel,\r\n}: ChartFooterProps) => {\r\n  const holdingsValue = currentPrice * quantityOwned;\r\n  return (\r\n    <div className=\"w-full h-14 bg-neutral-800 dark:bg-neutral-900 rounded-b-2xl rounded-t-sm flex items-center justify-center\">\r\n      <div className=\"flex-1 flex flex-col items-center gap-0.5\">\r\n        <span className=\"text-[11px] text-neutral-400\">{lowLabel}</span>\r\n        <span className=\"text-sm font-light\">{formatCurrency(low)}</span>\r\n      </div>\r\n      <div className=\"w-px h-8 bg-neutral-700/60\" />\r\n      <div className=\"flex-1 flex flex-col items-center gap-0.5 px-2\">\r\n        <span className=\"text-[11px]\">Holdings</span>\r\n        <span className=\"text-sm text-[#16C784] font-semibold line-clamp-1\">\r\n          {formatCurrency(holdingsValue)}\r\n        </span>\r\n      </div>\r\n      <div className=\"w-px h-8 bg-neutral-700/60\" />\r\n\r\n      <div className=\"flex-1 flex flex-col items-center gap-0.5\">\r\n        <span className=\"text-[11px] text-neutral-400\">{highLabel}</span>\r\n        <span className=\"text-sm font-light\">{formatCurrency(high)}</span>\r\n      </div>\r\n    </div>\r\n  );\r\n};\r\n\r\nconst ChartHeader = ({\r\n  title,\r\n  quantityOwned,\r\n  displayPrice,\r\n  percentageChange,\r\n}: {\r\n  title: string;\r\n  quantityOwned: number;\r\n  displayPrice: number;\r\n  percentageChange: number;\r\n}) => {\r\n  const isPositive = percentageChange >= 0;\r\n\r\n  return (\r\n    <div className=\"flex items-center\">\r\n      <div className=\"flex-1 flex gap-3\">\r\n        <span className=\"w-12 h-12 rounded-full  border-2 border-black\">\r\n          <span className=\"bg-linear-to-b from-neutral-600 to-neutral-700 w-full h-full rounded-full flex items-center justify-center border-2 border-neutral-800\">\r\n            <HugeiconsIcon icon={BitcoinPiggyBankIcon} />\r\n          </span>\r\n        </span>\r\n        <span className=\"flex flex-col\">\r\n          <span className=\"text-lg tracking-wide\">{title}</span>\r\n          <span className=\"text-xs text-neutral-500 font-semibold flex gap-1\">\r\n            <HugeiconsIcon\r\n              icon={Mining03Icon}\r\n              className=\"size-4 text-orange-500\"\r\n            />\r\n            <span>{quantityOwned}</span>\r\n          </span>\r\n        </span>\r\n      </div>\r\n      <div className=\"flex-1 flex flex-col text-end\">\r\n        <span\r\n          className={cn(\r\n            \"text-xs\",\r\n            isPositive ? \"text-[#16C784]\" : \"text-red-400\",\r\n          )}\r\n        >\r\n          {isPositive ? \"+\" : \"\"}\r\n          {percentageChange.toFixed(2)}%\r\n        </span>\r\n        <span className=\"font-semibold text-[16px]\">\r\n          {formatCurrency(displayPrice)}\r\n        </span>\r\n      </div>\r\n    </div>\r\n  );\r\n};\r\n",
      "type": "registry:component"
    },
    {
      "path": "registry/default/ui/crypto-price-chart-data.ts",
      "content": "export type ChartDataPoint = {\r\n  time: string;\r\n  price: number;\r\n};\r\n\r\nexport const oneHourData: ChartDataPoint[] = [\r\n  { time: \"10:00\", price: 51210 },\r\n  { time: \"10:10\", price: 51240 },\r\n  { time: \"10:20\", price: 51190 },\r\n  { time: \"10:30\", price: 51280 },\r\n  { time: \"10:40\", price: 51220 },\r\n  { time: \"10:50\", price: 51310 },\r\n  { time: \"11:00\", price: 51290 },\r\n];\r\n\r\nexport const oneDayData: ChartDataPoint[] = [\r\n  { time: \"00:00\", price: 33000 },\r\n  { time: \"03:00\", price: 32500 },\r\n  { time: \"06:00\", price: 20000 },\r\n  { time: \"09:00\", price: 38000 },\r\n  { time: \"12:00\", price: 29000 },\r\n  { time: \"15:00\", price: 39500 },\r\n  { time: \"18:00\", price: 24000 },\r\n  { time: \"21:00\", price: 41000 },\r\n  { time: \"23:59\", price: 43500 },\r\n];\r\nexport const oneWeekData: ChartDataPoint[] = [\r\n  { time: \"Mon\", price: 42000 },\r\n  { time: \"Tue\", price: 43800 },\r\n  { time: \"Wed\", price: 41000 },\r\n  { time: \"Thu\", price: 46000 },\r\n  { time: \"Fri\", price: 48000 },\r\n  { time: \"Sat\", price: 47000 },\r\n  { time: \"Sun\", price: 43800 },\r\n];\r\n\r\nexport const oneMonthData: ChartDataPoint[] = [\r\n  { time: \"Week 1\", price: 35000 },\r\n  { time: \"Week 2\", price: 39000 },\r\n  { time: \"Week 3\", price: 42000 },\r\n  { time: \"Week 4\", price: 46000 },\r\n  { time: \"Week 5\", price: 45300 },\r\n];\r\n\r\nexport const oneYearData: ChartDataPoint[] = [\r\n  { time: \"Jan\", price: 22000 },\r\n  { time: \"Feb\", price: 25000 },\r\n  { time: \"Mar\", price: 28000 },\r\n  { time: \"Apr\", price: 31000 },\r\n  { time: \"May\", price: 29000 },\r\n  { time: \"Jun\", price: 35000 },\r\n  { time: \"Jul\", price: 38000 },\r\n  { time: \"Aug\", price: 42000 },\r\n  { time: \"Sep\", price: 45000 },\r\n  { time: \"Oct\", price: 48000 },\r\n  { time: \"Nov\", price: 50000 },\r\n  { time: \"Dec\", price: 52000 },\r\n];\r\n\r\nexport const allTimeData: ChartDataPoint[] = [\r\n  { time: \"2018\", price: 8000 },\r\n  { time: \"2019\", price: 12000 },\r\n  { time: \"2020\", price: 18000 },\r\n  { time: \"2021\", price: 45000 },\r\n  { time: \"2022\", price: 30000 },\r\n  { time: \"2023\", price: 38000 },\r\n  { time: \"2024\", price: 52000 },\r\n];\r\n\r\nexport const cryptoChartData = {\r\n  \"1H\": oneHourData,\r\n  \"1D\": oneDayData,\r\n  \"1W\": oneWeekData,\r\n  \"1M\": oneMonthData,\r\n  \"1Y\": oneYearData,\r\n  \"ALL\": allTimeData,\r\n};",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}