{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid",
  "type": "registry:component",
  "title": "Chart Grid",
  "description": "Grid lines for charts",
  "dependencies": [
    "@visx/grid@4.0.1-alpha.0"
  ],
  "registryDependencies": [
    "@bklit/chart-context"
  ],
  "files": [
    {
      "path": "src/charts/grid.tsx",
      "content": "\"use client\";\n\nimport { GridColumns, GridRows } from \"@visx/grid\";\nimport { motion } from \"motion/react\";\nimport { useId } from \"react\";\nimport { chartCssVars, useChartStable, useYScale } from \"./chart-context\";\nimport { useGridShimmer } from \"./use-grid-shimmer\";\nimport {\n  isLoadingChromePhase,\n  isLoadingGridChromePhase,\n} from \"./y-domain-utils\";\n\nconst DEFAULT_SHIMMER_LENGTH_PX = 140;\nconst DEFAULT_SHIMMER_SPEED = 1;\nconst DEFAULT_SHIMMER_STROKE =\n  \"color-mix(in oklch, var(--foreground) 68%, transparent)\";\n\nexport interface GridProps {\n  /** Show horizontal grid lines. Default: true */\n  horizontal?: boolean;\n  /** Show vertical grid lines. Default: false */\n  vertical?: boolean;\n  /** Number of horizontal grid lines. Default: 5 */\n  numTicksRows?: number;\n  /** Number of vertical grid lines. Default: 10 */\n  numTicksColumns?: number;\n  /** Explicit tick values for horizontal grid lines. Overrides numTicksRows. */\n  rowTickValues?: number[];\n  /** Grid line stroke color. Default: var(--chart-grid) */\n  stroke?: string;\n  /** Grid stroke while loading chrome is active. Falls back to `stroke`. */\n  loadingStroke?: string;\n  /** Grid line stroke opacity. Default: 1 */\n  strokeOpacity?: number;\n  /** Grid line stroke width. Default: 1 */\n  strokeWidth?: number;\n  /** Grid line dash array. Default: \"4,4\" for dashed lines */\n  strokeDasharray?: string;\n  /** Horizontal row values rendered with alternate styling (e.g. zero baseline). */\n  highlightRowValues?: number[];\n  /** Stroke for highlighted rows. Default: var(--chart-foreground-muted) */\n  highlightRowStroke?: string;\n  /** Stroke opacity for highlighted rows. Default: 1 */\n  highlightRowStrokeOpacity?: number;\n  /** Stroke width for highlighted rows. Default: 1 */\n  highlightRowStrokeWidth?: number;\n  /** Dash array for highlighted rows. Default: solid line */\n  highlightRowStrokeDasharray?: string;\n  /** Enable horizontal fade effect on grid rows (fades at left/right). Default: true */\n  fadeHorizontal?: boolean;\n  /** Enable vertical fade effect on grid columns (fades at top/bottom). Default: false */\n  fadeVertical?: boolean;\n  /** Omit the first and last horizontal grid lines. Default: false */\n  hideHorizontalEdgeLines?: boolean;\n  /** Omit the first and last vertical grid lines. Default: false */\n  hideVerticalEdgeLines?: boolean;\n  /** Y-scale for horizontal grid lines. Default: primary (`\"left\"`) axis. */\n  yAxisId?: string | number;\n  /** Animate a shimmer band across horizontal grid lines. Default: false */\n  shimmer?: boolean;\n  /** Shimmer band stroke (color and opacity via color-mix or oklch alpha). */\n  shimmerStroke?: string;\n  /** Shimmer band width in pixels. Default: 140 */\n  shimmerLength?: number;\n  /** Shimmer speed multiplier (higher = faster). Default: 1 */\n  shimmerSpeed?: number;\n  /** Match loop timing to the loading line pulse (cycle + inter-loop pause). */\n  shimmerSync?: boolean;\n}\n\nfunction hideEdgeTicks<T>(ticks: T[], hideEdgeLines: boolean): T[] {\n  if (!hideEdgeLines || ticks.length <= 2) {\n    return ticks;\n  }\n  return ticks.slice(1, -1);\n}\n\nfunction resolveRowTickValues(options: {\n  hideHorizontalEdgeLines: boolean;\n  numTicksRows: number;\n  rowTickValues?: number[];\n  yScale: { ticks?: (count: number) => number[] };\n}): number[] | undefined {\n  const { hideHorizontalEdgeLines, numTicksRows, rowTickValues, yScale } =\n    options;\n  const ticks =\n    rowTickValues ?? (yScale.ticks ? yScale.ticks(numTicksRows) : []);\n  const filtered = hideEdgeTicks(ticks, hideHorizontalEdgeLines);\n  if (filtered === ticks && !rowTickValues && !hideHorizontalEdgeLines) {\n    return undefined;\n  }\n  return filtered.length > 0 ? filtered : undefined;\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: grid fade masks and shimmer share one layer tree\nexport function Grid({\n  horizontal = true,\n  vertical = false,\n  numTicksRows = 5,\n  numTicksColumns = 10,\n  rowTickValues,\n  stroke = chartCssVars.grid,\n  loadingStroke,\n  strokeOpacity = 1,\n  strokeWidth = 1,\n  strokeDasharray = \"4,4\",\n  highlightRowValues,\n  highlightRowStroke = chartCssVars.foregroundMuted,\n  highlightRowStrokeOpacity = 1,\n  highlightRowStrokeWidth = 1,\n  highlightRowStrokeDasharray = \"0\",\n  fadeHorizontal = true,\n  fadeVertical = false,\n  hideHorizontalEdgeLines = false,\n  hideVerticalEdgeLines = false,\n  yAxisId,\n  shimmer = false,\n  shimmerStroke = DEFAULT_SHIMMER_STROKE,\n  shimmerLength = DEFAULT_SHIMMER_LENGTH_PX,\n  shimmerSpeed = DEFAULT_SHIMMER_SPEED,\n  shimmerSync = false,\n}: GridProps) {\n  const { xScale, innerWidth, innerHeight, orientation, barScale, chartPhase } =\n    useChartStable();\n  const yScale = useYScale(yAxisId);\n  const shimmerActive = shimmer && isLoadingChromePhase(chartPhase);\n  const gridStroke =\n    isLoadingGridChromePhase(chartPhase) && loadingStroke != null\n      ? loadingStroke\n      : stroke;\n  const { shimmerEnabled, shimmerTransform } = useGridShimmer({\n    innerWidth,\n    shimmer,\n    shimmerLength,\n    shimmerSpeed,\n    shimmerSync,\n    active: shimmerActive,\n  });\n\n  // For bar charts, determine which scale to use for grid lines\n  // Horizontal bar charts: vertical grid should use yScale (value scale)\n  // Vertical bar charts: horizontal grid uses yScale (value scale)\n  const isHorizontalBarChart = orientation === \"horizontal\" && barScale;\n\n  // For vertical grid lines in horizontal bar charts, use yScale (the value scale)\n  // For time-based charts, use xScale\n  const columnScale = isHorizontalBarChart ? yScale : xScale;\n  const rowTickValuesResolved = resolveRowTickValues({\n    hideHorizontalEdgeLines,\n    numTicksRows,\n    rowTickValues,\n    yScale,\n  });\n  const columnTickValuesResolved =\n    vertical &&\n    columnScale &&\n    typeof columnScale === \"function\" &&\n    hideVerticalEdgeLines\n      ? (() => {\n          const ticks = columnScale.ticks?.(numTicksColumns) ?? [];\n          const filtered = hideEdgeTicks<number | Date>(ticks, true);\n          return filtered.length > 0 ? filtered : undefined;\n        })()\n      : undefined;\n  const uniqueId = useId();\n\n  // Horizontal fade mask (for grid rows - fades left/right)\n  const hMaskId = `grid-rows-fade-${uniqueId}`;\n  const hGradientId = `${hMaskId}-gradient`;\n  const shimmerGradientId = `grid-shimmer-${uniqueId}`;\n\n  // Vertical fade mask (for grid columns - fades top/bottom)\n  const vMaskId = `grid-cols-fade-${uniqueId}`;\n  const vGradientId = `${vMaskId}-gradient`;\n  const horizontalFadeMask = fadeHorizontal ? `url(#${hMaskId})` : undefined;\n\n  return (\n    <g className=\"chart-grid\">\n      {/* Gradient mask for horizontal grid lines - fades at left/right */}\n      {horizontal && fadeHorizontal && (\n        <defs>\n          <linearGradient id={hGradientId} x1=\"0%\" x2=\"100%\" y1=\"0%\" y2=\"0%\">\n            <stop offset=\"0%\" style={{ stopColor: \"white\", stopOpacity: 0 }} />\n            <stop offset=\"10%\" style={{ stopColor: \"white\", stopOpacity: 1 }} />\n            <stop offset=\"90%\" style={{ stopColor: \"white\", stopOpacity: 1 }} />\n            <stop\n              offset=\"100%\"\n              style={{ stopColor: \"white\", stopOpacity: 0 }}\n            />\n          </linearGradient>\n          <mask id={hMaskId}>\n            <rect\n              fill={`url(#${hGradientId})`}\n              height={innerHeight}\n              width={innerWidth}\n              x=\"0\"\n              y=\"0\"\n            />\n          </mask>\n        </defs>\n      )}\n\n      {horizontal && shimmerEnabled ? (\n        <defs>\n          <motion.linearGradient\n            gradientTransform={shimmerTransform}\n            gradientUnits=\"userSpaceOnUse\"\n            id={shimmerGradientId}\n            x1={0}\n            x2={shimmerLength}\n            y1={0}\n            y2={0}\n          >\n            <stop offset=\"0%\" stopColor={shimmerStroke} stopOpacity={0} />\n            <stop offset=\"35%\" stopColor={shimmerStroke} stopOpacity={0.45} />\n            <stop offset=\"50%\" stopColor={shimmerStroke} stopOpacity={1} />\n            <stop offset=\"65%\" stopColor={shimmerStroke} stopOpacity={0.45} />\n            <stop offset=\"100%\" stopColor={shimmerStroke} stopOpacity={0} />\n          </motion.linearGradient>\n        </defs>\n      ) : null}\n\n      {/* Gradient mask for vertical grid lines - fades at top/bottom */}\n      {vertical && fadeVertical && (\n        <defs>\n          <linearGradient id={vGradientId} x1=\"0%\" x2=\"0%\" y1=\"0%\" y2=\"100%\">\n            <stop offset=\"0%\" style={{ stopColor: \"white\", stopOpacity: 0 }} />\n            <stop offset=\"10%\" style={{ stopColor: \"white\", stopOpacity: 1 }} />\n            <stop offset=\"90%\" style={{ stopColor: \"white\", stopOpacity: 1 }} />\n            <stop\n              offset=\"100%\"\n              style={{ stopColor: \"white\", stopOpacity: 0 }}\n            />\n          </linearGradient>\n          <mask id={vMaskId}>\n            <rect\n              fill={`url(#${vGradientId})`}\n              height={innerHeight}\n              width={innerWidth}\n              x=\"0\"\n              y=\"0\"\n            />\n          </mask>\n        </defs>\n      )}\n\n      {horizontal && (\n        <g mask={horizontalFadeMask}>\n          <GridRows\n            numTicks={rowTickValuesResolved ? undefined : numTicksRows}\n            scale={yScale}\n            stroke={gridStroke}\n            strokeDasharray={strokeDasharray}\n            strokeOpacity={strokeOpacity}\n            strokeWidth={strokeWidth}\n            tickValues={rowTickValuesResolved}\n            width={innerWidth}\n          />\n          {shimmerEnabled ? (\n            <GridRows\n              numTicks={rowTickValuesResolved ? undefined : numTicksRows}\n              scale={yScale}\n              stroke={`url(#${shimmerGradientId})`}\n              strokeDasharray={strokeDasharray}\n              strokeOpacity={1}\n              strokeWidth={strokeWidth}\n              tickValues={rowTickValuesResolved}\n              width={innerWidth}\n            />\n          ) : null}\n        </g>\n      )}\n      {horizontal && highlightRowValues && highlightRowValues.length > 0 ? (\n        <g className=\"chart-grid-highlight-rows\">\n          {highlightRowValues.map((value) => {\n            const y = yScale(value);\n            if (y == null || !Number.isFinite(y)) {\n              return null;\n            }\n\n            return (\n              <line\n                key={value}\n                stroke={highlightRowStroke}\n                strokeDasharray={highlightRowStrokeDasharray}\n                strokeOpacity={highlightRowStrokeOpacity}\n                strokeWidth={highlightRowStrokeWidth}\n                x1={0}\n                x2={innerWidth}\n                y1={y}\n                y2={y}\n              />\n            );\n          })}\n        </g>\n      ) : null}\n      {vertical && columnScale && typeof columnScale === \"function\" && (\n        <g mask={fadeVertical ? `url(#${vMaskId})` : undefined}>\n          <GridColumns\n            height={innerHeight}\n            numTicks={columnTickValuesResolved ? undefined : numTicksColumns}\n            scale={columnScale}\n            stroke={stroke}\n            strokeDasharray={strokeDasharray}\n            strokeOpacity={strokeOpacity}\n            strokeWidth={strokeWidth}\n            tickValues={columnTickValuesResolved}\n          />\n        </g>\n      )}\n    </g>\n  );\n}\n\nGrid.displayName = \"Grid\";\n\nexport default Grid;\n",
      "type": "registry:component",
      "target": "components/charts/grid.tsx"
    },
    {
      "path": "src/charts/use-grid-shimmer.ts",
      "content": "\"use client\";\n\nimport {\n  animate,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\";\nimport { useEffect } from \"react\";\nimport {\n  LINE_LOADING_LOOP_PAUSE_MS,\n  LINE_LOADING_PULSE_CYCLE_S,\n  LINE_LOADING_PULSE_EASE,\n} from \"./line-loading-timing\";\n\nexport interface UseGridShimmerOptions {\n  innerWidth: number;\n  shimmer: boolean;\n  shimmerLength: number;\n  shimmerSpeed: number;\n  shimmerSync: boolean;\n  /** When false, shimmer animation is paused (e.g. during exit transition). */\n  active: boolean;\n  /** Run a single synced sweep (loading → ready handoff). */\n  oneShot?: boolean;\n}\n\nexport function useGridShimmer({\n  innerWidth,\n  shimmer,\n  shimmerLength,\n  shimmerSpeed,\n  shimmerSync,\n  active,\n  oneShot = false,\n}: UseGridShimmerOptions) {\n  const progress = useMotionValue(0);\n  const reducedMotion = useReducedMotion();\n  const shimmerCycleS =\n    LINE_LOADING_PULSE_CYCLE_S / Math.max(shimmerSpeed, 0.1);\n  const shimmerEnabled =\n    active && shimmer && reducedMotion !== true && innerWidth > 0;\n\n  useEffect(() => {\n    if (!shimmerEnabled) {\n      return;\n    }\n\n    let cancelled = false;\n    let timeoutId: number | undefined;\n    let controls: ReturnType<typeof animate> | undefined;\n\n    const runSyncedCycle = () => {\n      if (cancelled) {\n        return;\n      }\n\n      progress.set(0);\n      controls = animate(progress, 1, {\n        duration: shimmerCycleS,\n        ease: [...LINE_LOADING_PULSE_EASE],\n        onComplete: () => {\n          if (cancelled) {\n            return;\n          }\n          timeoutId = window.setTimeout(\n            runSyncedCycle,\n            LINE_LOADING_LOOP_PAUSE_MS\n          );\n        },\n      });\n    };\n\n    if (shimmerSync && oneShot) {\n      progress.set(0);\n      controls = animate(progress, 1, {\n        duration: shimmerCycleS / 2,\n        ease: [...LINE_LOADING_PULSE_EASE],\n      });\n      return () => controls?.stop();\n    }\n\n    if (shimmerSync) {\n      runSyncedCycle();\n      return () => {\n        cancelled = true;\n        controls?.stop();\n        if (timeoutId !== undefined) {\n          window.clearTimeout(timeoutId);\n        }\n      };\n    }\n\n    progress.set(0);\n    controls = animate(progress, 1, {\n      duration: shimmerCycleS,\n      repeat: Number.POSITIVE_INFINITY,\n      ease: [...LINE_LOADING_PULSE_EASE],\n    });\n\n    return () => controls?.stop();\n  }, [oneShot, progress, shimmerCycleS, shimmerEnabled, shimmerSync]);\n\n  const shimmerX = useTransform(\n    progress,\n    (value) => -shimmerLength + value * (innerWidth + shimmerLength * 2)\n  );\n  const shimmerTransform = useTransform(shimmerX, (x) => `translate(${x}, 0)`);\n\n  return { shimmerEnabled, shimmerTransform };\n}\n",
      "type": "registry:component",
      "target": "components/charts/use-grid-shimmer.ts"
    }
  ]
}