197 lines · 6.8 KB · TypeScript
RawDownload
// CRITICAL'use client'; import { useId } from 'react';import { Copy, Check, Play, Code2, Maximize2, Minimize2 } from 'lucide-react';import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';import { CodeSandbox } from './code-sandbox';import { ArtifactRenderer, getArtifactType } from './artifact-renderer';import { useAppStore } from '@/store'; interface EnhancedCodeBlockProps {  children: string;  className?: string;  artifactsEnabled?: boolean;  isStreaming?: boolean;  language?: string;} export function EnhancedCodeBlock({  children,  className,  artifactsEnabled,  isStreaming,  language}: EnhancedCodeBlockProps) {  const blockId = useId();  const blockState = useAppStore(    (state) =>      state.codeBlockState[blockId] ?? {        copied: false,        showPreview: false,        isExpanded: false,      },  );  const updateCodeBlockState = useAppStore((state) => state.updateCodeBlockState);  const { copied, showPreview, isExpanded } = blockState;  const updateState = (partial: Partial<typeof blockState>) => {    updateCodeBlockState(blockId, (prev) => ({ ...prev, ...partial }));  };  const lang = language || className?.replace('language-', '') || 'text';  const code = String(children).replace(/\n$/, '');   const copyCode = () => {    navigator.clipboard.writeText(code);    updateState({ copied: true });    setTimeout(() => updateState({ copied: false }), 2000);  };   // Check if this should be rendered as an artifact  const artifactType = getArtifactType(lang);  const canPreview = artifactsEnabled && artifactType &&    ['html', 'react', 'javascript', 'svg'].includes(artifactType);   // If showing preview, render CodeSandbox  if (showPreview && canPreview && artifactType) {    if (artifactType === 'svg') {      return (        <div className="my-3 animate-in fade-in slide-in-from-bottom-2 duration-300">          <ArtifactRenderer            artifact={{ id: 'inline-svg', type: 'svg', title: `${lang} Preview`, code }}          />          <button            onClick={() => updateState({ showPreview: false })}            className="mt-2 text-xs text-[#9a9590] hover:text-[var(--foreground)] transition-colors flex items-center gap-1"          >            <Code2 className="h-3 w-3" />            Show code          </button>        </div>      );    }     return (      <div className="my-3 animate-in fade-in slide-in-from-bottom-2 duration-300">        <CodeSandbox          code={code}          language={artifactType as 'html' | 'react' | 'javascript'}          title={`${lang} Preview`}          autoRun={true}        />        <button          onClick={() => updateState({ showPreview: false })}          className="mt-2 text-xs text-[#9a9590] hover:text-[var(--foreground)] transition-colors flex items-center gap-1"        >          <Code2 className="h-3 w-3" />          Show code        </button>      </div>    );  }   const lineCount = code.split('\n').length;  const isLongCode = lineCount > 20;  const shouldCollapse = isLongCode && !isExpanded;   return (    <div className="my-3 rounded-xl overflow-hidden border border-[var(--border)] bg-[var(--card)] shadow-sm transition-all duration-200 hover:shadow-md group">      {/* Header */}      <div className="flex items-center justify-between bg-[var(--accent)]/50 backdrop-blur-sm px-4 py-2.5 border-b border-[var(--border)]">        <div className="flex items-center gap-2">          <div className="flex items-center gap-1.5">            <div className="w-2.5 h-2.5 rounded-full bg-red-400/80" />            <div className="w-2.5 h-2.5 rounded-full bg-yellow-400/80" />            <div className="w-2.5 h-2.5 rounded-full bg-green-400/80" />          </div>          <span className="text-xs font-mono font-medium text-[#b0a8a0] bg-[var(--background)] px-2 py-0.5 rounded">            {lang || 'code'}          </span>          {isLongCode && (            <span className="text-xs text-[#9a9590]">              {lineCount} lines            </span>          )}        </div>         <div className="flex items-center gap-1">          {canPreview && (            <button              onClick={() => updateState({ showPreview: true })}              className="p-1.5 rounded-lg hover:bg-[var(--accent)] transition-all duration-200 flex items-center gap-1.5 text-xs font-medium text-[var(--success)] hover:text-[var(--success)]"              title="Run preview"            >              <Play className="h-3.5 w-3.5" />              <span className="hidden sm:inline">Preview</span>            </button>          )}           {isLongCode && (            <button              onClick={() => updateState({ isExpanded: !isExpanded })}              className="p-1.5 rounded-lg hover:bg-[var(--accent)] transition-all duration-200 text-[#9a9590] hover:text-[var(--foreground)]"              title={isExpanded ? 'Collapse' : 'Expand'}            >              {isExpanded ? (                <Minimize2 className="h-3.5 w-3.5" />              ) : (                <Maximize2 className="h-3.5 w-3.5" />              )}            </button>          )}           <button            onClick={copyCode}            className="p-1.5 rounded-lg hover:bg-[var(--accent)] transition-all duration-200 text-[#9a9590] hover:text-[var(--foreground)]"            title="Copy code"          >            {copied ? (              <Check className="h-3.5 w-3.5 text-[var(--success)]" />            ) : (              <Copy className="h-3.5 w-3.5" />            )}          </button>        </div>      </div>       {/* Code */}      <div className={shouldCollapse ? 'max-h-[300px] overflow-hidden relative' : ''}>        <SyntaxHighlighter          language={lang}          style={vscDarkPlus}          customStyle={{            margin: 0,            padding: '1rem',            background: 'transparent',            fontSize: '0.875rem',            lineHeight: '1.6',          }}          showLineNumbers          lineNumberStyle={{            color: 'var(--muted)',            fontSize: '0.75rem',            paddingRight: '1rem',            minWidth: '2.5rem',            textAlign: 'right',            userSelect: 'none',          }}          wrapLongLines        >          {code}        </SyntaxHighlighter>         {shouldCollapse && (          <div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-[var(--card)] to-transparent flex items-end justify-center pb-3">            <button              onClick={() => updateState({ isExpanded: true })}              className="text-xs text-[#b0a8a0] hover:text-[var(--foreground)] transition-colors flex items-center gap-1"            >              Expand full code              <Maximize2 className="h-3 w-3" />            </button>          </div>        )}      </div>    </div>  );}