{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "analytics-area-chart",
  "title": "Analytics Area Chart",
  "description": "Composed Tremor area chart with Archway formatting and tooltips.",
  "dependencies": [
    "@tremor/react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "components/composed/tremor/analytics-area-chart.tsx",
      "content": "\"use client\";\n\nimport type { ComponentType } from \"react\";\nimport { AreaChart, type AreaChartProps } from \"@tremor/react\";\nimport type { CustomTooltipProps } from \"@tremor/react/dist/components/chart-elements/common/CustomTooltipProps\";\n\nimport {\n  availableChartColors,\n  constructCategoryColors,\n  getColorClassName,\n  type AvailableChartColorsKeys,\n} from \"../../../lib/tremor/chart-utils\";\nimport { cx } from \"../../../lib/tremor/utils\";\nimport { cn } from \"../../../lib/utils\";\n\nexport interface AnalyticsAreaChartProps\n  extends Omit<AreaChartProps, \"colors\" | \"customTooltip\" | \"minValue\" | \"maxValue\"> {\n  index: string;\n  data: AreaChartProps[\"data\"];\n  categories: string[];\n  colors?: AvailableChartColorsKeys[];\n  yAxisMin?: number;\n  yAxisMax?: number;\n  valueFormatter?: (value: number) => string;\n  className?: string;\n}\n\nconst defaultFormatter = (value: number) =>\n  new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency: \"USD\",\n    notation: \"compact\",\n  }).format(value);\n\nexport function AnalyticsAreaChart({\n  categories,\n  colors = [\"blue\", \"violet\", \"emerald\", \"amber\"],\n  className,\n  yAxisMin,\n  yAxisMax,\n  valueFormatter = defaultFormatter,\n  index,\n  data,\n  ...rest\n}: AnalyticsAreaChartProps) {\n  const palette = colors.length ? colors : availableChartColors;\n  const fallbackColor = (palette[0] ?? availableChartColors[0]) as AvailableChartColorsKeys;\n  const categoryColors = constructCategoryColors(categories, palette);\n  const TremorAreaChart = AreaChart as ComponentType<AreaChartProps>;\n  const resolvedColors: AvailableChartColorsKeys[] = categories.map((category) =>\n    categoryColors.get(category) ?? fallbackColor,\n  );\n  const TooltipComponent = createAnalyticsTooltip(valueFormatter, categoryColors, fallbackColor);\n\n  const chartProps: AreaChartProps = {\n    ...rest,\n    data,\n    index: index!,\n    categories,\n    className: cn(\"w-full\", className),\n    yAxisWidth: 64,\n    autoMinValue: yAxisMin === undefined,\n    valueFormatter,\n    colors: resolvedColors,\n    curveType: \"monotone\",\n    customTooltip: TooltipComponent,\n  } as AreaChartProps;\n\n  if (typeof yAxisMin === \"number\") {\n    chartProps.minValue = yAxisMin;\n  }\n\n  if (typeof yAxisMax === \"number\") {\n    chartProps.maxValue = yAxisMax;\n  }\n\n  return <TremorAreaChart {...chartProps} />;\n}\n\nfunction createAnalyticsTooltip(\n  formatter: (value: number) => string,\n  categoryColors: Map<string, AvailableChartColorsKeys>,\n  fallbackColor: AvailableChartColorsKeys,\n) {\n  const TooltipComponent: ComponentType<CustomTooltipProps> = ({ active, payload, label }) => {\n    if (!active || !payload?.length) {\n      return null;\n    }\n\n    return (\n      <div className=\"rounded-lg border border-border bg-popover/90 px-3 py-2 text-sm shadow-sm backdrop-blur\">\n        <p className=\"mb-2 text-foreground/80\">{label}</p>\n        <ul className=\"space-y-1\">\n          {payload.map((item) => {\n            const key = String(item.dataKey);\n            const colorKey = categoryColors.get(key) ?? fallbackColor;\n            return (\n              <li key={key} className=\"flex items-center justify-between gap-4\">\n                <span className={cx(\"flex items-center gap-2\")}>\n                  <span\n                    className={cn(\n                      \"inline-block h-2.5 w-2.5 rounded-full\",\n                      getColorClassName(colorKey, \"bg\"),\n                    )}\n                  />\n                  {key}\n                </span>\n                <span className=\"font-medium text-foreground\">\n                  {formatter(Number(item.value))}\n                </span>\n              </li>\n            );\n          })}\n        </ul>\n      </div>\n    );\n  };\n\n  TooltipComponent.displayName = \"AnalyticsAreaTooltip\";\n\n  return TooltipComponent;\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/composed/tremor/kpi-card.tsx",
      "content": "\"use client\";\n\nimport {\n  Card,\n  Flex,\n  Metric,\n  ProgressBar,\n  Text,\n  type CardProps,\n  type FlexProps,\n  type MetricProps,\n  type ProgressBarProps,\n  type TextProps,\n} from \"@tremor/react\";\nimport type { ComponentType } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface KpiCardProps {\n  title: string;\n  currentValue: number;\n  targetValue?: number;\n  delta?: number;\n  showProgress?: boolean;\n  formatter?: (value: number) => string;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nconst defaultFormatter = (value: number) =>\n  new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency: \"USD\",\n    maximumFractionDigits: 0,\n  }).format(value);\n\nexport function KpiCard({\n  title,\n  currentValue,\n  targetValue,\n  delta,\n  showProgress = Boolean(targetValue),\n  formatter = defaultFormatter,\n  className,\n  children,\n}: KpiCardProps) {\n  const progress = targetValue ? Math.min((currentValue / targetValue) * 100, 100) : undefined;\n  const TremorCard = Card as ComponentType<CardProps>;\n  const TremorText = Text as ComponentType<TextProps>;\n  const TremorMetric = Metric as ComponentType<MetricProps>;\n  const TremorFlex = Flex as ComponentType<FlexProps>;\n  const TremorProgressBar = ProgressBar as ComponentType<ProgressBarProps>;\n\n  return (\n    <TremorCard className={cn(\"flex h-full flex-col gap-4\", className)}>\n      <div className=\"space-y-2\">\n        <TremorText className=\"text-sm font-medium text-muted-foreground\">{title}</TremorText>\n        <TremorMetric className=\"text-3xl font-semibold text-foreground\">\n          {formatter(currentValue)}\n        </TremorMetric>\n        {typeof delta === \"number\" && (\n          <TremorText className={cn(delta >= 0 ? \"text-emerald-500\" : \"text-red-500\", \"text-sm\")}>{\n            delta >= 0 ? `▲ ${delta.toFixed(1)}%` : `▼ ${Math.abs(delta).toFixed(1)}%`\n          }</TremorText>\n        )}\n      </div>\n      {showProgress && typeof progress === \"number\" && (\n        <TremorFlex className=\"items-center gap-3\">\n          <TremorProgressBar\n            value={progress}\n            className=\"w-full\"\n            color={delta && delta < 0 ? \"red\" : \"blue\"}\n          />\n          <TremorText className=\"text-xs text-muted-foreground\">\n            {formatter(targetValue ?? 0)} target\n          </TremorText>\n        </TremorFlex>\n      )}\n      {children ? <div className=\"mt-auto text-sm text-muted-foreground\">{children}</div> : null}\n    </TremorCard>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/index.ts",
      "content": "export * from \"./ui\";\nexport * from \"./composed/tremor/analytics-area-chart\";\nexport * from \"./composed/tremor/kpi-card\";\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/tremor/utils.ts",
      "content": "import type { ClassValue } from \"clsx\";\nimport clsx from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cx(...args: ClassValue[]): string {\n  return twMerge(clsx(...args));\n}\n\nexport const focusInput = [\n  \"focus:ring-2\",\n  \"focus:ring-blue-200 dark:focus:ring-blue-700/30\",\n  \"focus:border-blue-500 dark:focus:border-blue-700\",\n];\n\nexport const focusRing = [\n  \"outline outline-offset-2 outline-0 focus-visible:outline-2\",\n  \"outline-blue-500 dark:outline-blue-500\",\n];\n\nexport const hasErrorInput = [\n  \"ring-2\",\n  \"border-red-500 dark:border-red-700\",\n  \"ring-red-200 dark:ring-red-700/30\",\n];\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/tremor/chart-utils.ts",
      "content": "export type ColorUtility = \"bg\" | \"stroke\" | \"fill\" | \"text\";\n\nexport const chartColors = {\n  blue: {\n    bg: \"bg-blue-500\",\n    stroke: \"stroke-blue-500\",\n    fill: \"fill-blue-500\",\n    text: \"text-blue-500\",\n  },\n  emerald: {\n    bg: \"bg-emerald-500\",\n    stroke: \"stroke-emerald-500\",\n    fill: \"fill-emerald-500\",\n    text: \"text-emerald-500\",\n  },\n  violet: {\n    bg: \"bg-violet-500\",\n    stroke: \"stroke-violet-500\",\n    fill: \"fill-violet-500\",\n    text: \"text-violet-500\",\n  },\n  amber: {\n    bg: \"bg-amber-500\",\n    stroke: \"stroke-amber-500\",\n    fill: \"fill-amber-500\",\n    text: \"text-amber-500\",\n  },\n  gray: {\n    bg: \"bg-gray-500\",\n    stroke: \"stroke-gray-500\",\n    fill: \"fill-gray-500\",\n    text: \"text-gray-500\",\n  },\n  cyan: {\n    bg: \"bg-cyan-500\",\n    stroke: \"stroke-cyan-500\",\n    fill: \"fill-cyan-500\",\n    text: \"text-cyan-500\",\n  },\n  pink: {\n    bg: \"bg-pink-500\",\n    stroke: \"stroke-pink-500\",\n    fill: \"fill-pink-500\",\n    text: \"text-pink-500\",\n  },\n  lime: {\n    bg: \"bg-lime-500\",\n    stroke: \"stroke-lime-500\",\n    fill: \"fill-lime-500\",\n    text: \"text-lime-500\",\n  },\n  fuchsia: {\n    bg: \"bg-fuchsia-500\",\n    stroke: \"stroke-fuchsia-500\",\n    fill: \"fill-fuchsia-500\",\n    text: \"text-fuchsia-500\",\n  },\n} as const satisfies Record<string, Record<ColorUtility, string>>;\n\nexport type AvailableChartColorsKeys = keyof typeof chartColors;\n\nexport const availableChartColors: AvailableChartColorsKeys[] = (\n  Object.keys(chartColors) as AvailableChartColorsKeys[]\n);\n\nexport const constructCategoryColors = (\n  categories: string[],\n  colors: AvailableChartColorsKeys[],\n): Map<string, AvailableChartColorsKeys> => {\n  const categoryColors = new Map<string, AvailableChartColorsKeys>();\n  const paletteBase = colors.length ? colors : availableChartColors;\n  const palette = (paletteBase.length ? paletteBase : [\"gray\"]) as AvailableChartColorsKeys[];\n  categories.forEach((category, index) => {\n    const color = palette[index % palette.length] ?? \"gray\";\n    categoryColors.set(category, color as AvailableChartColorsKeys);\n  });\n  return categoryColors;\n};\n\nexport const getColorClassName = (\n  color: AvailableChartColorsKeys | undefined,\n  type: ColorUtility,\n): string => {\n  const fallback = {\n    bg: \"bg-gray-500\",\n    stroke: \"stroke-gray-500\",\n    fill: \"fill-gray-500\",\n    text: \"text-gray-500\",\n  } as const satisfies Record<ColorUtility, string>;\n\n  if (!color) {\n    return fallback[type];\n  }\n\n  return chartColors[color]?.[type] ?? fallback[type];\n};\n\nexport const getYAxisDomain = (\n  autoMinValue: boolean,\n  minValue: number | undefined,\n  maxValue: number | undefined,\n): [number | \"auto\", number | \"auto\"] => {\n  const minDomain = autoMinValue ? \"auto\" : minValue ?? 0;\n  const maxDomain = maxValue ?? \"auto\";\n  return [minDomain, maxDomain];\n};\n\nexport function hasOnlyOneValueForKey<T extends Record<string, unknown>>(\n  array: T[],\n  keyToCheck: keyof T,\n): boolean {\n  const values: unknown[] = [];\n\n  for (const obj of array) {\n    if (Object.prototype.hasOwnProperty.call(obj, keyToCheck)) {\n      values.push(obj[keyToCheck]);\n      if (values.length > 1) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/index.ts",
      "content": "export * from \"./utils\";\nexport * from \"./tremor/utils\";\nexport * from \"./tremor/chart-utils\";\n",
      "type": "registry:lib"
    }
  ],
  "docs": "Install with: npx shadcn@latest add @archway-ai/analytics-area-chart. Requires Tailwind tokens and @tremor/react.",
  "categories": [
    "analytics",
    "charts"
  ],
  "type": "registry:block"
}