{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"code-block","type":"registry:component","title":"Code Block","description":"A syntax-highlighted code surface with stable streaming updates, line numbers, focused lines, smooth following, and copy feedback.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","lucide-react","motion","shiki","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/agents/code-block.tsx","type":"registry:component","target":"@components/agents/code-block.tsx","content":"\"use client\";\n// beui.dev/components/agents/code-block\n\nimport { Check, Copy, FileCode2, LoaderCircle } from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  type AgentCodeLanguage,\n  AgentCodeLine,\n  useAgentCodeTokens,\n} from \"@/components/agents/agent-code\";\nimport { SPRING_PRESS } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CodeBlockStatus = \"streaming\" | \"complete\";\n\nexport interface CodeBlockProps {\n  code: string;\n  language?: AgentCodeLanguage;\n  filename?: ReactNode;\n  status?: CodeBlockStatus;\n  showLineNumbers?: boolean;\n  highlightLines?: number[];\n  maxHeight?: number;\n  wrap?: boolean;\n  copyable?: boolean;\n  onCopy?: () => void | Promise<void>;\n  className?: string;\n}\n\nexport function CodeBlock({\n  code,\n  language = \"typescript\",\n  filename,\n  status = \"complete\",\n  showLineNumbers = true,\n  highlightLines = [],\n  maxHeight = 280,\n  wrap = false,\n  copyable = true,\n  onCopy,\n  className,\n}: CodeBlockProps) {\n  const reduce = useReducedMotion() ?? false;\n  const viewportRef = useRef<HTMLDivElement>(null);\n  const copyTimer = useRef<number | undefined>(undefined);\n  const [copied, setCopied] = useState(false);\n  const streaming = status === \"streaming\";\n  const tokens = useAgentCodeTokens(code, language);\n  const highlighted = useMemo(\n    () => new Set(highlightLines),\n    [highlightLines],\n  );\n  let offset = 0;\n  const lines = code.split(\"\\n\").map((content) => {\n    const line = { content, offset };\n    offset += content.length + 1;\n    return line;\n  });\n\n  useEffect(\n    () => () => {\n      if (copyTimer.current) window.clearTimeout(copyTimer.current);\n    },\n    [],\n  );\n\n  useLayoutEffect(() => {\n    const viewport = viewportRef.current;\n    if (!viewport || !streaming) return;\n\n    const frame = requestAnimationFrame(() => {\n      if (viewport.scrollHeight <= viewport.clientHeight) return;\n      if (typeof viewport.scrollTo === \"function\") {\n        viewport.scrollTo({\n          top: viewport.scrollHeight,\n          behavior: reduce ? \"auto\" : \"smooth\",\n        });\n      } else {\n        viewport.scrollTop = viewport.scrollHeight;\n      }\n    });\n    return () => cancelAnimationFrame(frame);\n  });\n\n  const handleCopy = useCallback(async () => {\n    if (onCopy) await onCopy();\n    else await navigator.clipboard?.writeText(code);\n\n    setCopied(true);\n    if (copyTimer.current) window.clearTimeout(copyTimer.current);\n    copyTimer.current = window.setTimeout(() => setCopied(false), 1600);\n  }, [code, onCopy]);\n\n  return (\n    <div\n      data-state={status}\n      aria-busy={streaming}\n      className={cn(\n        \"w-full overflow-hidden rounded-2xl bg-muted/80 text-sm\",\n        className,\n      )}\n    >\n      <div className=\"flex h-10 items-center gap-2.5 px-3\">\n        <FileCode2\n          aria-hidden=\"true\"\n          className=\"size-3.5 shrink-0 text-muted-foreground/70\"\n        />\n        {filename ? (\n          <span className=\"min-w-0 truncate font-mono text-xs text-foreground/80\">\n            {filename}\n          </span>\n        ) : null}\n        <span className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground/55\">\n          {language}\n        </span>\n        <span\n          className={cn(\n            \"ml-auto inline-flex shrink-0 items-center gap-1 text-[10px] font-medium\",\n            streaming\n              ? \"text-blue-600 dark:text-blue-400\"\n              : \"text-emerald-600 dark:text-emerald-400\",\n          )}\n        >\n          {streaming ? (\n            <LoaderCircle className={cn(\"size-3\", !reduce && \"animate-spin\")} />\n          ) : (\n            <Check className=\"size-3\" />\n          )}\n          {streaming ? \"Writing\" : \"Ready\"}\n        </span>\n        {copyable || onCopy ? (\n          <motion.button\n            type=\"button\"\n            aria-label={copied ? \"Copied\" : \"Copy code\"}\n            title={copied ? \"Copied\" : \"Copy code\"}\n            onClick={handleCopy}\n            whileTap={reduce ? undefined : { scale: 0.9 }}\n            transition={SPRING_PRESS}\n            className=\"grid size-7 shrink-0 place-items-center rounded-full text-muted-foreground outline-none transition-colors hover:bg-background/70 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n          >\n            {copied ? (\n              <Check className=\"size-3.5\" />\n            ) : (\n              <Copy className=\"size-3.5\" />\n            )}\n          </motion.button>\n        ) : null}\n      </div>\n\n      <div\n        ref={viewportRef}\n        role={streaming ? \"log\" : undefined}\n        aria-live={streaming ? \"polite\" : undefined}\n        className=\"scrollbar-hide overflow-auto border-t border-foreground/[0.06] py-2\"\n        style={{ maxHeight }}\n      >\n        <pre className=\"m-0 min-w-max font-mono text-xs leading-5 text-foreground/85\">\n          <code>\n            {lines.map((line, index) => {\n              const lineNumber = index + 1;\n              return (\n                <span\n                  key={line.offset}\n                  className={cn(\n                    \"grid min-h-5\",\n                    showLineNumbers\n                      ? \"grid-cols-[2.75rem_minmax(0,1fr)]\"\n                      : \"grid-cols-1\",\n                    highlighted.has(lineNumber) && \"bg-blue-500/[0.07]\",\n                  )}\n                >\n                  {showLineNumbers ? (\n                    <span className=\"select-none pr-3 text-right tabular-nums text-muted-foreground/35\">\n                      {lineNumber}\n                    </span>\n                  ) : null}\n                  <AgentCodeLine\n                    code={line.content}\n                    tokens={tokens?.[index]}\n                    className={cn(\n                      \"pr-4\",\n                      showLineNumbers ? \"pl-1\" : \"pl-4\",\n                      wrap ? \"whitespace-pre-wrap break-words\" : \"whitespace-pre\",\n                    )}\n                  />\n                </span>\n              );\n            })}\n          </code>\n        </pre>\n      </div>\n    </div>\n  );\n}\n"},{"path":"components/agents/agent-code.tsx","type":"registry:component","target":"@components/agents/agent-code.tsx","content":"\"use client\";\n\nimport {\n  type CSSProperties,\n  Fragment,\n  useEffect,\n  useState,\n} from \"react\";\nimport { createHighlighter, type Highlighter } from \"shiki\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AgentCodeLanguage =\n  | \"bash\"\n  | \"diff\"\n  | \"json\"\n  | \"text\"\n  | \"tsx\"\n  | \"typescript\";\n\nexport interface AgentCodeToken {\n  content: string;\n  offset: number;\n  light?: string;\n  dark?: string;\n}\n\nexport type AgentCodeTokenLines = AgentCodeToken[][];\n\nexport interface AgentCodeProps {\n  code: string;\n  language?: AgentCodeLanguage;\n  className?: string;\n}\n\nexport interface AgentCodeLineProps {\n  code: string;\n  tokens?: AgentCodeToken[];\n  className?: string;\n}\n\nconst LIGHT_THEME = \"github-light-high-contrast\";\nconst DARK_THEME = \"github-dark-high-contrast\";\nlet agentCodeHighlighter: Promise<Highlighter> | null = null;\nconst tokenCache = new Map<string, AgentCodeTokenLines>();\n\nfunction getAgentCodeHighlighter() {\n  if (!agentCodeHighlighter) {\n    agentCodeHighlighter = createHighlighter({\n      themes: [LIGHT_THEME, DARK_THEME],\n      langs: [\"bash\", \"diff\", \"json\", \"tsx\", \"typescript\"],\n    });\n  }\n  return agentCodeHighlighter;\n}\n\nfunction tokenCacheKey(code: string, language: AgentCodeLanguage) {\n  return `${language}\\u0000${code}`;\n}\n\nexport function useAgentCodeTokens(\n  code: string,\n  language: AgentCodeLanguage,\n) {\n  const key = tokenCacheKey(code, language);\n  const cached = tokenCache.get(key);\n  const [result, setResult] = useState<{\n    key: string;\n    code: string;\n    language: AgentCodeLanguage;\n    lines: AgentCodeTokenLines;\n  } | null>(cached ? { key, code, language, lines: cached } : null);\n\n  useEffect(() => {\n    const current = tokenCache.get(key);\n    if (current) {\n      setResult({ key, code, language, lines: current });\n      return;\n    }\n\n    let cancelled = false;\n    getAgentCodeHighlighter().then((highlighter) => {\n      if (cancelled) return;\n      const lines = highlighter\n        .codeToTokensWithThemes(code, {\n          lang: language,\n          themes: {\n            light: LIGHT_THEME,\n            dark: DARK_THEME,\n          },\n        })\n        .map((line) =>\n          line.map((token) => ({\n            content: token.content,\n            offset: token.offset,\n            light: token.variants.light?.color,\n            dark: token.variants.dark?.color,\n          })),\n      );\n      tokenCache.set(key, lines);\n      setResult({ key, code, language, lines });\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, [code, key, language]);\n\n  if (result?.key === key) return result.lines;\n  if (result?.language === language && code.startsWith(result.code)) {\n    return result.lines;\n  }\n  return null;\n}\n\nexport function AgentCodeLine({\n  code,\n  tokens,\n  className,\n}: AgentCodeLineProps) {\n  return (\n    <span className={className}>\n      {tokens\n        ? tokens.map((token) => (\n            <span\n              key={`${token.offset}-${token.content}`}\n              style={\n                {\n                  \"--agent-code-light\": token.light ?? \"currentColor\",\n                  \"--agent-code-dark\": token.dark ?? token.light ?? \"currentColor\",\n                } as CSSProperties\n              }\n              className=\"text-[var(--agent-code-light)] dark:text-[var(--agent-code-dark)]\"\n            >\n              {token.content}\n            </span>\n          ))\n        : code}\n    </span>\n  );\n}\n\nexport function AgentCode({\n  code,\n  language = \"bash\",\n  className,\n}: AgentCodeProps) {\n  const tokens = useAgentCodeTokens(code, language);\n  let offset = 0;\n  const lines = code.split(\"\\n\").map((content) => {\n    const line = { content, offset };\n    offset += content.length + 1;\n    return line;\n  });\n\n  return (\n    <pre\n      className={cn(\n        \"m-0 overflow-x-auto whitespace-pre font-mono text-xs leading-5 text-foreground/85\",\n        className,\n      )}\n    >\n      <code>\n        {lines.map((line, index) => (\n          <Fragment key={line.offset}>\n            <AgentCodeLine code={line.content} tokens={tokens?.[index]} />\n            {index < lines.length - 1 ? \"\\n\" : null}\n          </Fragment>\n        ))}\n      </code>\n    </pre>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n\n/** Dragged handles and fills (sliders) — critically damped `useSpring` config,\n * so the value follows the pointer butterily and never rebounds off an end. */\nexport const SPRING_GLIDE = {\n  stiffness: 700,\n  damping: 50,\n  mass: 0.5,\n} as const;\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}