{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-ai-jsx-preview",
  "title": "Aurora AI JSX Preview",
  "author": "jmagar <https://github.com/jmagar>",
  "description": "Aurora-native jsx preview parity surface from AI Elements.",
  "dependencies": [
    "lucide-react@^0.487.0"
  ],
  "registryDependencies": [
    "@aurora/aurora-tokens",
    "@aurora/aurora-ai-elements"
  ],
  "files": [
    {
      "path": "registry/aurora/blocks/ai/elements/jsx-preview.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Code, Copy } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/registry/aurora/ui/badge\"\nimport { Button } from \"@/registry/aurora/ui/button\"\nimport { useClipboard } from \"@/registry/aurora/lib/use-clipboard\"\n\nexport interface JsxPreviewProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Source to render in the preview surface. */\n  code: string\n  /** Filename shown in the header, e.g. `DeployButton.jsx`. */\n  filename?: string\n  /** Language pill label. Defaults to `JSX`. */\n  language?: string\n  /** Show the gutter line numbers. Defaults to `true`. */\n  lineNumbers?: boolean\n}\n\n// ── Aurora token panel surface (mirrors the elements panelStyle) ───────────────\nfunction jsxPanelStyle(style?: React.CSSProperties): React.CSSProperties {\n  return {\n    background: \"var(--aurora-surface-raised)\",\n    border: \"1px solid var(--aurora-border-strong)\",\n    borderRadius: \"var(--aurora-radius-1)\",\n    boxShadow: \"var(--aurora-shadow-medium), var(--aurora-highlight-medium)\",\n    ...style,\n  }\n}\n\n// ── Lightweight JSX token tinting (cosmetic — not a real tokenizer) ────────────\nconst KEYWORDS = new Set([\n  \"export\",\n  \"function\",\n  \"return\",\n  \"const\",\n  \"let\",\n  \"var\",\n  \"import\",\n  \"from\",\n  \"default\",\n  \"if\",\n  \"else\",\n  \"for\",\n  \"while\",\n  \"new\",\n  \"await\",\n  \"async\",\n])\n\ntype Tone =\n  | \"keyword\"\n  | \"tag\"\n  | \"string\"\n  | \"comment\"\n  | \"punctuation\"\n  | \"plain\"\n\nconst TONE_COLOR: Record<Tone, string> = {\n  keyword: \"var(--aurora-accent-pink)\",\n  tag: \"var(--aurora-code-function)\",\n  string: \"var(--aurora-accent-strong)\",\n  comment: \"var(--aurora-text-muted)\",\n  punctuation: \"var(--aurora-text-muted)\",\n  plain: \"var(--aurora-text-primary)\",\n}\n\n// Split a line into tinted spans. Order matters: strings/comments first so we\n// don't re-tokenize their contents.\nfunction tintLine(line: string, keyPrefix: string): React.ReactNode[] {\n  const out: React.ReactNode[] = []\n  // Match strings, comments, JSX tags, identifiers, or any other run.\n  const re =\n    /(\\/\\/[^\\n]*)|(\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|`(?:[^`\\\\]|\\\\.)*`)|(<\\/?[A-Za-z][\\w.]*)|([A-Za-z_$][\\w$]*)|([^\\sA-Za-z_$\"'`/]+)|(\\s+|.)/g\n  let m: RegExpExecArray | null\n  let i = 0\n  while ((m = re.exec(line)) !== null) {\n    const [, comment, str, tag, ident, punct] = m\n    const text = m[0]\n    let tone: Tone = \"plain\"\n    if (comment) tone = \"comment\"\n    else if (str) tone = \"string\"\n    else if (tag) tone = \"tag\"\n    else if (ident) tone = KEYWORDS.has(ident) ? \"keyword\" : \"plain\"\n    else if (punct) tone = \"punctuation\"\n    out.push(\n      tone === \"plain\" ? (\n        text\n      ) : (\n        <span key={`${keyPrefix}-${i}`} style={{ color: TONE_COLOR[tone] }}>\n          {text}\n        </span>\n      ),\n    )\n    i += 1\n  }\n  return out\n}\n\nconst JsxPreview = React.memo(\n  function JsxPreview(\n    { ref,\n      code,\n      filename,\n      language = \"JSX\",\n      lineNumbers = true,\n      className,\n      style,\n      ...props\n    }: JsxPreviewProps & { ref?: React.Ref<HTMLDivElement> },\n  ) {\n    const { copied, error, copy } = useClipboard(1200)\n    const lines = React.useMemo(() => code.split(\"\\n\"), [code])\n\n    const handleCopy = React.useCallback(() => void copy(code), [code, copy])\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"overflow-hidden\", className)}\n        style={jsxPanelStyle(style)}\n        {...props}\n      >\n        {/* Header: code icon · filename · language pill · copy */}\n        <div className=\"flex items-center gap-3 px-4 py-3\">\n          <Code\n            className=\"size-4 shrink-0\"\n            aria-hidden\n            style={{ color: \"var(--aurora-accent-primary)\" }}\n          />\n          <span\n            className=\"min-w-0 flex-1 truncate aurora-text-ui\"\n            style={{\n              color: \"var(--aurora-text-primary)\",\n            }}\n          >\n            {filename ?? \"preview\"}\n          </span>\n          <Badge tone=\"rose\" size=\"sm\">\n            {language}\n          </Badge>\n          <Button\n            type=\"button\"\n            variant=\"neutral\"\n            size=\"icon\"\n            onClick={handleCopy}\n            aria-label={copied ? \"Copied source\" : error ? \"Unable to copy source\" : \"Copy source\"}\n          >\n            {copied ? (\n              <Check className=\"size-4\" aria-hidden style={{ color: \"var(--aurora-accent-primary)\" }} />\n            ) : (\n              <Copy className=\"size-4\" aria-hidden />\n            )}\n            <span className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n              {copied ? \"Copied\" : error ? \"Unable to copy\" : \"\"}\n            </span>\n          </Button>\n        </div>\n\n        {/* Divider */}\n        <div aria-hidden style={{ height: 1, background: \"var(--aurora-border-default)\" }} />\n\n        {/* Body: line-numbered, tinted source */}\n        <pre\n          className=\"overflow-auto aurora-text-code\"\n          style={{\n            margin: 0,\n            padding: \"14px 0\",\n            background: \"var(--aurora-panel-strong)\",\n            lineHeight: 1.65,\n            tabSize: 2,\n          }}\n        >\n          <code style={{ display: \"block\" }}>\n            {lines.map((line, idx) => (\n              <span key={idx} className=\"grid grid-cols-[auto_minmax(0,1fr)]\">\n                {lineNumbers ? (\n                  <span\n                    aria-hidden\n                    className=\"select-none pr-4 pl-5 text-right tabular-nums\"\n                    style={{ color: \"var(--aurora-text-muted)\", opacity: 0.7, minWidth: \"3.25em\" }}\n                  >\n                    {idx + 1}\n                  </span>\n                ) : (\n                  <span aria-hidden />\n                )}\n                <span className=\"whitespace-pre pr-5\" style={{ color: \"var(--aurora-text-primary)\" }}>\n                  {line.length === 0 ? \" \" : tintLine(line, `l${idx}`)}\n                </span>\n              </span>\n            ))}\n          </code>\n        </pre>\n      </div>\n    )\n  },\n)\nJsxPreview.displayName = \"JsxPreview\"\n\nexport { JsxPreview }\n",
      "type": "registry:component",
      "target": "@components/aurora/ai/jsx-preview.tsx"
    }
  ],
  "meta": {
    "namespace": "@aurora",
    "style": "aurora",
    "family": "block",
    "requiresTokens": true,
    "sourcePath": "registry/aurora/blocks/ai/elements/jsx-preview.tsx",
    "installTarget": "@components/aurora/ai/jsx-preview.tsx",
    "taxonomy": "ai"
  },
  "docs": "Aurora block that expects `aurora-tokens` to be installed first.\n\nDefault install target: `@components/aurora/ai/jsx-preview.tsx`.\n\nRegistry dependencies: `aurora-tokens`, `aurora-ai-elements`.",
  "categories": [
    "aurora",
    "block",
    "ai"
  ],
  "type": "registry:block"
}