{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "background",
  "type": "registry:component",
  "title": "Chart Background",
  "description": "Pattern fill for the plot area when grid lines are hidden",
  "dependencies": [
    "@visx/pattern@4.0.1-alpha.0",
    "motion"
  ],
  "registryDependencies": [
    "@bklit/chart-context"
  ],
  "files": [
    {
      "path": "src/charts/background.tsx",
      "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useId, useMemo } from \"react\";\nimport { useChartStable } from \"./chart-context\";\nimport {\n  type PatternPresetId,\n  type PatternPresetOptions,\n  renderPatternPreset,\n} from \"./pattern-preset\";\n\nexport type BackgroundPatternPreset = PatternPresetId;\n\nexport interface BackgroundProps extends PatternPresetOptions {\n  /** Pattern preset. `\"none\"` renders nothing. */\n  pattern?: BackgroundPatternPreset;\n  /** Pattern stroke color. Default: `var(--chart-grid)` */\n  color?: string;\n  /** Apply the pattern texture to the plot area. Default: true */\n  showFill?: boolean;\n  /** Pattern fill opacity. Default: 1 */\n  opacity?: number;\n  /** Fade pattern at the left and right chart edges. Default: true */\n  fadeHorizontal?: boolean;\n  /** Fade pattern at the top and bottom chart edges. Default: true */\n  fadeVertical?: boolean;\n  /** Horizontal fade zone as % of plot width per edge. Default: 10 */\n  fadeHorizontalLength?: number;\n  /** Vertical fade zone as % of plot height per edge. Default: 10 */\n  fadeVerticalLength?: number;\n}\n\nconst BACKGROUND_ENTER_FADE_MS = 420;\n\nfunction clampFadeLength(length: number): number {\n  return Math.min(45, Math.max(0, length));\n}\n\nfunction fadeMaskStops(lengthPercent: number): Array<{\n  offset: string;\n  opacity: number;\n}> {\n  const edge = clampFadeLength(lengthPercent);\n  return [\n    { offset: \"0%\", opacity: 0 },\n    { offset: `${edge}%`, opacity: 1 },\n    { offset: `${100 - edge}%`, opacity: 1 },\n    { offset: \"100%\", opacity: 0 },\n  ];\n}\n\n/** Plot-area pattern fill for charts without a grid. Renders behind series layers. */\nexport function Background({\n  pattern = \"diagonal\",\n  color = \"var(--chart-grid)\",\n  scale = 1,\n  strokeWidth,\n  radius,\n  complement,\n  fill,\n  dotFill,\n  tileBackground,\n  showFill = true,\n  opacity = 1,\n  fadeHorizontal = true,\n  fadeVertical = true,\n  fadeHorizontalLength = 10,\n  fadeVerticalLength = 10,\n}: BackgroundProps) {\n  const { innerWidth, innerHeight, isLoaded, enterTransition } =\n    useChartStable();\n  const uniqueId = useId();\n  const patternId = `chart-background-${uniqueId.replace(/:/g, \"\")}`;\n\n  const hStops = useMemo(\n    () => fadeMaskStops(fadeHorizontalLength),\n    [fadeHorizontalLength]\n  );\n  const vStops = useMemo(\n    () => fadeMaskStops(fadeVerticalLength),\n    [fadeVerticalLength]\n  );\n\n  if (pattern === \"none\" || !showFill || innerWidth <= 0 || innerHeight <= 0) {\n    return null;\n  }\n\n  const patternNode = renderPatternPreset(pattern, patternId, {\n    color,\n    scale,\n    strokeWidth,\n    radius,\n    complement,\n    fill,\n    dotFill,\n    tileBackground,\n  });\n  if (!patternNode) {\n    return null;\n  }\n\n  const fadeMask = fadeHorizontal || fadeVertical;\n  const hMaskId = `chart-background-fade-h-${uniqueId.replace(/:/g, \"\")}`;\n  const hGradientId = `${hMaskId}-gradient`;\n  const vMaskId = `chart-background-fade-v-${uniqueId.replace(/:/g, \"\")}`;\n  const vGradientId = `${vMaskId}-gradient`;\n  const combinedMaskId = `chart-background-fade-${uniqueId.replace(/:/g, \"\")}`;\n\n  let maskRef: string | undefined;\n  if (fadeHorizontal && fadeVertical) {\n    maskRef = `url(#${combinedMaskId})`;\n  } else if (fadeHorizontal) {\n    maskRef = `url(#${hMaskId})`;\n  } else if (fadeVertical) {\n    maskRef = `url(#${vMaskId})`;\n  }\n\n  const targetOpacity = opacity;\n  const revealOpacity = isLoaded ? targetOpacity : 0;\n  const enterFade =\n    enterTransition && typeof enterTransition === \"object\"\n      ? enterTransition\n      : { duration: BACKGROUND_ENTER_FADE_MS / 1000, ease: \"easeOut\" as const };\n\n  return (\n    // biome-ignore lint/a11y/noAriaHiddenOnFocusable: decorative plot-area pattern layer\n    <g aria-hidden=\"true\" className=\"chart-background\">\n      {fadeMask ? (\n        <defs>\n          {fadeHorizontal ? (\n            <>\n              <linearGradient\n                id={hGradientId}\n                x1=\"0%\"\n                x2=\"100%\"\n                y1=\"0%\"\n                y2=\"0%\"\n              >\n                {hStops.map((stop) => (\n                  <stop\n                    key={stop.offset}\n                    offset={stop.offset}\n                    style={{ stopColor: \"white\", stopOpacity: stop.opacity }}\n                  />\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            </>\n          ) : null}\n          {fadeVertical ? (\n            <>\n              <linearGradient\n                id={vGradientId}\n                x1=\"0%\"\n                x2=\"0%\"\n                y1=\"0%\"\n                y2=\"100%\"\n              >\n                {vStops.map((stop) => (\n                  <stop\n                    key={stop.offset}\n                    offset={stop.offset}\n                    style={{ stopColor: \"white\", stopOpacity: stop.opacity }}\n                  />\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            </>\n          ) : null}\n          {fadeHorizontal && fadeVertical ? (\n            <mask id={combinedMaskId}>\n              <g mask={`url(#${hMaskId})`}>\n                <rect\n                  fill={`url(#${vGradientId})`}\n                  height={innerHeight}\n                  width={innerWidth}\n                  x={0}\n                  y={0}\n                />\n              </g>\n            </mask>\n          ) : null}\n        </defs>\n      ) : null}\n      <defs>{patternNode}</defs>\n      <motion.rect\n        animate={{ opacity: revealOpacity }}\n        fill={`url(#${patternId})`}\n        height={innerHeight}\n        initial={{ opacity: 0 }}\n        mask={maskRef}\n        transition={isLoaded ? enterFade : { duration: 0 }}\n        width={innerWidth}\n        x={0}\n        y={0}\n      />\n    </g>\n  );\n}\n\nBackground.displayName = \"Background\";\n\nexport default Background;\n",
      "type": "registry:component",
      "target": "components/charts/background.tsx"
    },
    {
      "path": "src/charts/pattern-preset.tsx",
      "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { PatternCircles, PatternLines } from \"./visx-pattern\";\n\nexport const PATTERN_PRESET_IDS = [\n  \"none\",\n  \"diagonal\",\n  \"horizontal\",\n  \"vertical\",\n  \"cross\",\n  \"dots\",\n  \"circles\",\n  \"accent\",\n] as const;\n\nexport type PatternPresetId = (typeof PATTERN_PRESET_IDS)[number];\n\nexport interface PatternPresetOptions {\n  color?: string;\n  scale?: number;\n  strokeWidth?: number;\n  radius?: number;\n  complement?: boolean;\n  fill?: string;\n  /** Dot grid only — when false, render hollow dots (stroke only). Default: true */\n  dotFill?: boolean;\n  tileBackground?: string;\n}\n\n/** Presets rendered with @visx/pattern `PatternCircles`. */\nexport function isCirclePattern(preset: PatternPresetId): boolean {\n  return preset === \"circles\" || preset === \"dots\";\n}\n\n/** @deprecated Use `isCirclePattern`. */\nexport function isCirclesPattern(preset: PatternPresetId): boolean {\n  return isCirclePattern(preset);\n}\n\nexport function patternPresetTileSize(\n  preset: PatternPresetId,\n  scale = 1\n): { width: number; height: number; strokeWidth: number } {\n  let base = { width: 6, height: 6, strokeWidth: 1 };\n  if (preset === \"dots\") {\n    base = { width: 10, height: 10, strokeWidth: 0 };\n  } else if (preset === \"cross\") {\n    base = { width: 8, height: 8, strokeWidth: 1 };\n  } else if (preset === \"circles\") {\n    base = { width: 6, height: 6, strokeWidth: 1 };\n  }\n\n  return {\n    width: base.width * scale,\n    height: base.height * scale,\n    strokeWidth: base.strokeWidth * scale,\n  };\n}\n\nfunction renderPatternCircles(\n  preset: \"dots\" | \"circles\",\n  _id: string,\n  color: string,\n  common: {\n    id: string;\n    height: number;\n    width: number;\n    strokeWidth: number;\n    background?: string;\n  },\n  options: PatternPresetOptions,\n  scale: number\n) {\n  const isDotGrid = preset === \"dots\";\n  const radius =\n    options.radius ?? (isDotGrid ? Math.max(0.5, 1.5 * scale) : 2 * scale);\n  const dotFillEnabled = options.dotFill !== false;\n\n  if (isDotGrid) {\n    const dotFill = dotFillEnabled ? options.fill || color : undefined;\n    return (\n      <PatternCircles\n        {...common}\n        complement={options.complement}\n        fill={dotFill}\n        radius={radius}\n        stroke={dotFillEnabled && options.fill ? undefined : color}\n        strokeWidth={\n          dotFillEnabled && !options.fill\n            ? (options.strokeWidth ?? 0)\n            : (options.strokeWidth ?? 1)\n        }\n      />\n    );\n  }\n\n  return (\n    <PatternCircles\n      {...common}\n      complement={options.complement}\n      fill={options.fill || undefined}\n      radius={radius}\n      stroke={color}\n      strokeWidth={options.strokeWidth ?? common.strokeWidth}\n    />\n  );\n}\n\n/** Renders a @visx/pattern definition node for the given preset. */\nexport function renderPatternPreset(\n  preset: PatternPresetId,\n  id: string,\n  options: PatternPresetOptions = {}\n): ReactNode {\n  if (preset === \"none\") {\n    return null;\n  }\n\n  const color = options.color ?? \"var(--chart-1)\";\n  const scale = options.scale ?? 1;\n  const tile = patternPresetTileSize(preset, scale);\n  const common = {\n    id,\n    height: tile.height,\n    width: tile.width,\n    strokeWidth: tile.strokeWidth,\n    ...(options.tileBackground ? { background: options.tileBackground } : {}),\n  };\n\n  if (preset === \"dots\" || preset === \"circles\") {\n    return renderPatternCircles(preset, id, color, common, options, scale);\n  }\n\n  const strokeWidth = options.strokeWidth ?? tile.strokeWidth;\n\n  switch (preset) {\n    case \"diagonal\":\n      return (\n        <PatternLines\n          {...common}\n          orientation={[\"diagonal\"]}\n          stroke={color}\n          strokeWidth={strokeWidth}\n        />\n      );\n    case \"horizontal\":\n      return (\n        <PatternLines\n          {...common}\n          orientation={[\"horizontal\"]}\n          stroke={color}\n          strokeWidth={strokeWidth}\n        />\n      );\n    case \"vertical\":\n      return (\n        <PatternLines\n          {...common}\n          orientation={[\"vertical\"]}\n          stroke={color}\n          strokeWidth={strokeWidth}\n        />\n      );\n    case \"cross\":\n      return (\n        <PatternLines\n          {...common}\n          orientation={[\"diagonal\", \"diagonalRightToLeft\"]}\n          stroke={color}\n          strokeWidth={strokeWidth}\n        />\n      );\n    case \"accent\":\n      return (\n        <PatternLines\n          {...common}\n          orientation={[\"diagonal\"]}\n          stroke=\"#e879f9\"\n          strokeWidth={strokeWidth}\n        />\n      );\n    default:\n      return null;\n  }\n}\n",
      "type": "registry:component",
      "target": "components/charts/pattern-preset.tsx"
    },
    {
      "path": "src/charts/visx-pattern.tsx",
      "content": "\"use client\";\n\nimport {\n  PatternCircles as VisxPatternCircles,\n  PatternHexagons as VisxPatternHexagons,\n  PatternLines as VisxPatternLines,\n  PatternWaves as VisxPatternWaves,\n} from \"@visx/pattern\";\nimport type { ComponentProps } from \"react\";\n\nexport function PatternLines(props: ComponentProps<typeof VisxPatternLines>) {\n  return <VisxPatternLines {...props} />;\n}\nPatternLines.displayName = \"PatternLines\";\n\nexport function PatternCircles(\n  props: ComponentProps<typeof VisxPatternCircles>\n) {\n  return <VisxPatternCircles {...props} />;\n}\nPatternCircles.displayName = \"PatternCircles\";\n\nexport function PatternWaves(props: ComponentProps<typeof VisxPatternWaves>) {\n  return <VisxPatternWaves {...props} />;\n}\nPatternWaves.displayName = \"PatternWaves\";\n\nexport function PatternHexagons(\n  props: ComponentProps<typeof VisxPatternHexagons>\n) {\n  return <VisxPatternHexagons {...props} />;\n}\nPatternHexagons.displayName = \"PatternHexagons\";\n",
      "type": "registry:component",
      "target": "components/charts/visx-pattern.tsx"
    }
  ]
}