{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Ninaero code-block",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "cn@^0.4.0"
  ],
  "registryDependencies": [
    "https://ninaero.pages.dev/r/theme.json",
    "https://ninaero.pages.dev/r/button.json",
    "https://ninaero.pages.dev/r/icons.json",
    "https://ninaero.pages.dev/r/use-highlighted-code.json"
  ],
  "files": [
    {
      "path": "registry/ninaero/ui/code-block.tsx",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport * as React from \"react\";\n\nimport { CheckIcon, CopyIcon, TriangleAlertIcon } from \"@/lib/icons\";\nimport { Button } from \"@/registry/ninaero/ui/button\";\n\nconst codeBlockVariants = cva(\n  \"ninaero-code-block group/code-block relative flex w-full min-w-0 flex-col overflow-hidden rounded-xl text-sm has-[>[data-slot=code-block-content]:focus-visible]:outline-2 has-[>[data-slot=code-block-content]:focus-visible]:outline-offset-2 has-[>[data-slot=code-block-content]:focus-visible]:outline-ring data-[size=sm]:text-xs\",\n  {\n    variants: {\n      variant: {\n        default: \"border border-border/70 bg-card text-foreground\",\n        destructive: \"border border-destructive/50 bg-card text-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\nfunction CodeBlock({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof codeBlockVariants> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"code-block\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(codeBlockVariants({ variant }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction CodeBlockHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-header\"\n      className={cn(\n        \"flex min-w-0 shrink-0 items-center gap-2 border-b border-border/50 px-4 py-2.5 text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-2 group-data-[variant=destructive]/code-block:text-destructive *:has-[>[data-slot=code-block-title]]:min-w-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CodeBlockTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-title\"\n      className={cn(\n        \"min-w-0 truncate font-sans text-sm type-label text-foreground group-data-[size=sm]/code-block:text-xs group-data-[size=sm]/code-block:leading-4 group-data-[variant=destructive]/code-block:text-destructive [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle [&_svg:not([class*='size-'])]:size-3.5 *:[svg]:me-1.5\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CodeBlockActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-actions\"\n      className={cn(\n        \"-my-1 ms-auto -me-1.5 flex shrink-0 items-center gap-1 [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:top-2 [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-1.5 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-1.5 [[data-slot=code-block]>&]:pointer-coarse:opacity-100 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CodeBlockCopyButton({\n  value,\n  variant = \"ghost\",\n  size = \"icon-xs\",\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button> & { value: string }) {\n  const [copyState, setCopyState] = React.useState<\"idle\" | \"copied\" | \"error\">(\"idle\");\n\n  React.useEffect(() => {\n    if (copyState === \"idle\") {\n      return;\n    }\n\n    const timeout = setTimeout(() => setCopyState(\"idle\"), 2000);\n    return () => clearTimeout(timeout);\n  }, [copyState]);\n\n  async function copyToClipboard() {\n    setCopyState(\"idle\");\n\n    try {\n      await navigator.clipboard.writeText(value);\n      setCopyState(\"copied\");\n    } catch {\n      setCopyState(\"error\");\n    }\n  }\n\n  return (\n    <>\n      <Button\n        data-slot=\"code-block-copy-button\"\n        data-copy-state={copyState}\n        variant={variant}\n        size={size}\n        aria-label={\n          copyState === \"copied\" ? \"Copied\" : copyState === \"error\" ? \"Try copying again\" : \"Copy\"\n        }\n        className={cn(className)}\n        onClick={(event) => {\n          onClick?.(event);\n          if (!event.defaultPrevented) void copyToClipboard();\n        }}\n        {...props}\n      >\n        {copyState === \"copied\" ? (\n          <CheckIcon />\n        ) : copyState === \"error\" ? (\n          <TriangleAlertIcon />\n        ) : (\n          <CopyIcon />\n        )}\n      </Button>\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copyState === \"copied\"\n          ? \"Copied to clipboard.\"\n          : copyState === \"error\"\n            ? \"Unable to copy.\"\n            : \"\"}\n      </span>\n    </>\n  );\n}\n\nfunction CodeBlockContent({\n  className,\n  children,\n  style,\n  wrap = false,\n  lineNumbers = false,\n  startLine = 1,\n  ...props\n}: React.ComponentProps<\"pre\"> & {\n  wrap?: boolean;\n  lineNumbers?: boolean;\n  startLine?: number;\n}) {\n  return (\n    <pre\n      data-slot=\"code-block-content\"\n      data-wrap={wrap || undefined}\n      data-numbered={lineNumbers || undefined}\n      dir=\"ltr\"\n      tabIndex={0}\n      style={{ counterReset: `line ${startLine - 1}`, ...style }}\n      className={cn(\n        \"ninaero-code-block-content min-h-0 min-w-0 scroll-fade-x scrollbar-thin overflow-auto px-4 py-3 text-start font-mono leading-relaxed outline-none group-has-data-[slot=code-block-footer]/code-block:pb-2 group-has-data-[slot=code-block-header]/code-block:pt-3 group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-2.5 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-2.5 data-wrap:scroll-fade-y data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap\",\n        className,\n      )}\n      {...props}\n    >\n      <code\n        data-slot=\"code-block-code\"\n        className=\"grid min-h-lh min-w-max content-start gap-x-3 group-data-[size=sm]/code-block:gap-x-2 in-data-wrap:min-w-0\"\n      >\n        {typeof children === \"string\"\n          ? children\n              .replace(/\\n$/, \"\")\n              .split(\"\\n\")\n              .map((line, index) => <CodeBlockLine key={index}>{line}</CodeBlockLine>)\n          : children}\n      </code>\n    </pre>\n  );\n}\n\nconst codeBlockLineVariants = cva(\n  \"relative -mx-4 col-span-full grid min-h-lh grid-cols-subgrid border-b border-(--ninaero-code-rule) px-4 last:border-b-0 group-data-[size=sm]/code-block:-mx-3 group-data-[size=sm]/code-block:px-3 before:min-w-[3ch] before:self-start before:text-end before:text-muted-foreground in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']\",\n  {\n    variants: {\n      variant: {\n        default: \"\",\n        added: \"bg-muted/70\",\n        removed: \"bg-destructive/10\",\n        highlighted:\n          \"bg-primary/10 before:text-foreground/70 after:pointer-events-none after:absolute after:inset-y-0 after:start-0 after:border-s-2 after:border-primary\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\nfunction CodeBlockLine({\n  className,\n  variant = \"default\",\n  children,\n  ...props\n}: React.ComponentProps<\"span\"> & VariantProps<typeof codeBlockLineVariants>) {\n  return (\n    <span\n      data-slot=\"code-block-line\"\n      data-variant={variant}\n      className={cn(codeBlockLineVariants({ variant }), className)}\n      {...props}\n    >\n      <span\n        data-slot=\"code-block-line-marker\"\n        className={cn(\n          \"select-none self-start text-center\",\n          variant === \"removed\" ? \"text-destructive\" : \"text-foreground\",\n        )}\n      >\n        {variant === \"added\" ? \"+\" : null}\n        {variant === \"removed\" ? \"−\" : null}\n      </span>\n      <span data-slot=\"code-block-line-content\" className=\"min-h-lh min-w-0\">\n        {children}\n      </span>\n    </span>\n  );\n}\n\nfunction CodeBlockFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"code-block-footer\"\n      className={cn(\n        \"flex min-w-0 shrink-0 items-center gap-2 border-t border-border/50 px-4 py-2.5 text-xs type-caption text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  CodeBlock,\n  CodeBlockHeader,\n  CodeBlockTitle,\n  CodeBlockActions,\n  CodeBlockCopyButton,\n  CodeBlockContent,\n  CodeBlockLine,\n  CodeBlockFooter,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}