{
  "name": "code",
  "type": "registry:ui",
  "title": "Code / CodeBlock",
  "description": "Code formats short inline tokens, while CodeBlock presents multiline or copyable snippets.",
  "registryDependencies": [
    "@siteplane/base",
    "@siteplane/button"
  ],
  "dependencies": [
    "class-variance-authority@0.7.1",
    "lucide-react@1.18.0"
  ],
  "files": [
    {
      "path": "src/components/ui/action-icons.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { EyeIcon, EyeOffIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type CopyIconPhase = \"idle\" | \"check\" | \"check-out\" | \"redraw\";\n\nexport function useCopyIconPhase() {\n  const [copyPhase, setCopyPhase] = useState<CopyIconPhase>(\"idle\");\n  const timersRef = useRef<ReturnType<typeof setTimeout>[]>([]);\n\n  useEffect(() => {\n    return () => {\n      for (const timer of timersRef.current) {\n        clearTimeout(timer);\n      }\n    };\n  }, []);\n\n  function playCopyFeedback() {\n    for (const timer of timersRef.current) {\n      clearTimeout(timer);\n    }\n\n    setCopyPhase(\"check\");\n    timersRef.current = [\n      setTimeout(() => setCopyPhase(\"check-out\"), 1300),\n      setTimeout(() => setCopyPhase(\"redraw\"), 1550),\n      setTimeout(() => setCopyPhase(\"idle\"), 2400),\n    ];\n  }\n\n  return {\n    copyPhase,\n    isShowingCheck: copyPhase === \"check\" || copyPhase === \"check-out\",\n    playCopyFeedback,\n  };\n}\n\nexport function CopyRedrawIcon({\n  className,\n  phase,\n}: {\n  className?: string;\n  phase: CopyIconPhase;\n}) {\n  return (\n    <span\n      className={cn(\"siteplane-copy relative inline-flex size-4\", className)}\n      data-phase={phase}\n    >\n      <svg\n        aria-hidden=\"true\"\n        className=\"siteplane-copy-check absolute inset-0 size-4 !mx-0 text-success-foreground opacity-0\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"2\"\n        viewBox=\"0 0 24 24\"\n      >\n        <path d=\"M20 6 9 17l-5-5\" pathLength={1} />\n      </svg>\n      <svg\n        aria-hidden=\"true\"\n        className=\"siteplane-copy-draw absolute inset-0 size-4 !mx-0\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"2\"\n        viewBox=\"0 0 24 24\"\n      >\n        <path\n          d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"\n          data-draw=\"1\"\n          pathLength={1}\n        />\n        <rect\n          data-draw=\"2\"\n          height=\"14\"\n          pathLength={1}\n          rx=\"2\"\n          ry=\"2\"\n          width=\"14\"\n          x=\"8\"\n          y=\"8\"\n        />\n      </svg>\n    </span>\n  );\n}\n\nexport function RevealIcon({\n  className,\n  revealed,\n}: {\n  className?: string;\n  revealed: boolean;\n}) {\n  return revealed ? (\n    <EyeOffIcon className={cn(\"siteplane-icon-pop size-4\", className)} />\n  ) : (\n    <EyeIcon className={cn(\"siteplane-icon-pop size-4\", className)} />\n  );\n}\n"
    },
    {
      "path": "src/components/ui/typography.ts",
      "type": "registry:ui",
      "content": "export const codeInlineClassName =\n  \"rounded-(--code-inline-radius) border border-border bg-muted px-1.5 py-0.5 font-mono [font-size:var(--code-inline-font-size)] text-foreground\";\n\nexport const codeBlockBaseClassName =\n  \"relative w-full overflow-x-auto rounded-lg border border-border bg-muted/50 font-mono text-foreground leading-relaxed\";\n\nexport const codeBlockSizeClassNames = {\n  default: \"px-3.5 py-3 text-xs\",\n  sm: \"px-3 py-2 [font-size:var(--code-block-sm-font-size)]\",\n} as const;\n\nexport const kbdClassName =\n  \"pointer-events-none inline-flex h-5 min-w-5 shrink-0 select-none items-center justify-center gap-1 whitespace-nowrap rounded-(--kbd-radius) bg-muted px-1 font-medium font-sans text-muted-foreground text-xs [&_svg:not([class*='size-'])]:size-3\";\n\nexport const shortcutClassName =\n  \"ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-normal\";\n"
    },
    {
      "path": "src/components/ui/code.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  CopyRedrawIcon,\n  RevealIcon,\n  useCopyIconPhase,\n} from \"@/components/ui/action-icons\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  codeBlockBaseClassName,\n  codeBlockSizeClassNames,\n  codeInlineClassName,\n} from \"@/components/ui/typography\";\n\nexport function Code({\n  className,\n  ...props\n}: React.ComponentProps<\"code\">): React.ReactElement {\n  return (\n    <code\n      className={cn(codeInlineClassName, className)}\n      data-slot=\"code\"\n      {...props}\n    />\n  );\n}\n\nconst codeBlockVariants = cva(codeBlockBaseClassName, {\n  defaultVariants: { size: \"default\" },\n  variants: {\n    size: codeBlockSizeClassNames,\n  },\n});\n\nexport interface CodeBlockProps\n  extends Omit<React.ComponentProps<\"pre\">, \"children\">,\n    VariantProps<typeof codeBlockVariants> {\n  value: string;\n  copyable?: boolean;\n  mask?: boolean;\n}\n\nexport function CodeBlock({\n  value,\n  copyable = false,\n  mask = false,\n  size,\n  className,\n  ...props\n}: CodeBlockProps): React.ReactElement {\n  const { copyPhase, isShowingCheck, playCopyFeedback } = useCopyIconPhase();\n  const [revealed, setRevealed] = useState(!mask);\n\n  async function copy(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(value);\n      playCopyFeedback();\n    } catch {\n      // Clipboard failures leave the icon in the idle copy state.\n    }\n  }\n\n  const display =\n    mask && !revealed ? \"•\".repeat(Math.min(value.length, 44)) : value;\n  const hasActions = copyable || mask;\n  const actionClassName = mask\n    ? \"absolute end-2 top-1/2 flex -translate-y-1/2 gap-1\"\n    : \"absolute end-2 top-2 flex gap-1\";\n\n  return (\n    <pre\n      className={cn(codeBlockVariants({ size }), hasActions && \"pe-20\", className)}\n      data-slot=\"code-block\"\n      {...props}\n    >\n      <code className=\"block whitespace-pre-wrap break-words\">{display}</code>\n      {hasActions ? (\n        <span className={actionClassName}>\n          {mask ? (\n            <Button\n              aria-label={revealed ? \"Hide value\" : \"Reveal value\"}\n              onClick={() => setRevealed((value) => !value)}\n              size=\"icon\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <RevealIcon revealed={revealed} />\n            </Button>\n          ) : null}\n          {copyable ? (\n            <Button\n              aria-label={isShowingCheck ? \"Copied to clipboard\" : \"Copy to clipboard\"}\n              onClick={copy}\n              size=\"icon\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <CopyRedrawIcon phase={copyPhase} />\n            </Button>\n          ) : null}\n        </span>\n      ) : null}\n    </pre>\n  );\n}\n"
    }
  ],
  "categories": [
    "data-display"
  ],
  "docs": "# Code / CodeBlock\n\n> Siteplane UI release `0.1.1`.\n\n## Public Purpose\n\nCode formats short inline tokens. CodeBlock presents multiline or copyable\nsnippets.\n\n## Import\n\n```tsx\nimport { Code, CodeBlock } from \"@/components/ui/code\";\n```\n\n## Public API\n\n- Use `Code` inside prose and `CodeBlock` for complete snippets.\n- Use `mask` only with the built-in reveal and copy actions.\n- Keep copy and reveal actions in the reserved top-right action area.\n- Leave enough right-side padding for actions in long snippets.\n\n## Public Motion\n\n- Code itself does not animate.\n- Copy and reveal feedback uses the native action behavior and global\n  reduced-motion contract.\n\n## Public Accessibility\n\n- Masked content requires a clearly named reveal control and a clearly named\n  copy control.\n- Do not rely on syntax color alone to explain a snippet.\n- Never place real secrets in documentation, screenshots, or examples.\n\n## Public Agent Guidance\n\n- Use Code for short inline tokens and CodeBlock for multiline snippets.\n- Do not build custom copy overlays around CodeBlock.\n- Keep all examples safe to publish.\n\n## Public Links\n\n- [Documentation and preview](https://ui.siteplane.io/docs/components/data-display/code)\n\n- [Registry source](https://ui.siteplane.io/r/code.json)\n",
  "meta": {
    "siteplane:agentContractVersion": 1,
    "siteplane:docs": "https://ui.siteplane.io/docs/components/data-display/code",
    "siteplane:releaseVersion": "0.1.1",
    "siteplane:sourceOwned": true
  },
  "devDependencies": []
}
