orange-400→blue-400, orange-500→blue-500, orange-600→blue-600 across ~21 component files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
import atomOneDark from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark'
|
|
import { X, Copy, Check, BookmarkPlus } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const LANGUAGE_MAP: Record<string, string> = {
|
|
powershell: 'powershell',
|
|
bash: 'bash',
|
|
python: 'python',
|
|
}
|
|
|
|
const LANGUAGE_LABELS: Record<string, string> = {
|
|
powershell: 'PowerShell',
|
|
bash: 'Bash',
|
|
python: 'Python',
|
|
}
|
|
|
|
interface ScriptPreviewModalProps {
|
|
script: string
|
|
filename: string | null
|
|
language: string
|
|
onClose: () => void
|
|
onSave: () => void
|
|
}
|
|
|
|
export function ScriptPreviewModal({
|
|
script,
|
|
filename,
|
|
language,
|
|
onClose,
|
|
onSave,
|
|
}: ScriptPreviewModalProps) {
|
|
const [copied, setCopied] = useState(false)
|
|
const hlLanguage = LANGUAGE_MAP[language] || 'powershell'
|
|
const lineCount = script.split('\n').length
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose()
|
|
}
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
}, [onClose])
|
|
|
|
const handleCopy = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(script)
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
} catch {
|
|
// clipboard not available
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center"
|
|
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
|
|
>
|
|
<div className="bg-card rounded-xl border border-[rgba(255,255,255,0.08)] max-w-[900px] w-full mx-4 max-h-[85vh] flex flex-col">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-5 py-3.5 border-b border-[rgba(255,255,255,0.06)]">
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
<span className="font-mono text-sm text-blue-400 truncate">
|
|
{filename || 'script'}
|
|
</span>
|
|
<span className="shrink-0 font-mono text-[0.625rem] uppercase tracking-wider px-2 py-0.5 rounded-full bg-[rgba(255,255,255,0.06)] text-muted-foreground">
|
|
{LANGUAGE_LABELS[language] || language}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={handleCopy}
|
|
className={cn(
|
|
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
|
|
"bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)]"
|
|
)}
|
|
>
|
|
{copied ? <Check size={14} className="text-emerald-400" /> : <Copy size={14} />}
|
|
{copied ? 'Copied' : 'Copy'}
|
|
</button>
|
|
<button
|
|
onClick={onSave}
|
|
className={cn(
|
|
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
|
|
"bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 hover:bg-emerald-500/15"
|
|
)}
|
|
>
|
|
<BookmarkPlus size={14} />
|
|
Save to Library
|
|
</button>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
|
|
>
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Code body */}
|
|
<div className="flex-1 overflow-auto min-h-0">
|
|
<SyntaxHighlighter
|
|
language={hlLanguage}
|
|
style={atomOneDark}
|
|
showLineNumbers
|
|
customStyle={{
|
|
background: 'transparent',
|
|
padding: '16px',
|
|
margin: 0,
|
|
fontSize: '0.8125rem',
|
|
lineHeight: '1.6',
|
|
}}
|
|
lineNumberStyle={{
|
|
color: '#5a6170',
|
|
minWidth: '2.5em',
|
|
paddingRight: '1em',
|
|
userSelect: 'none',
|
|
}}
|
|
wrapLongLines
|
|
>
|
|
{script}
|
|
</SyntaxHighlighter>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex items-center justify-between px-5 py-3 border-t border-[rgba(255,255,255,0.06)]">
|
|
<span className="font-mono text-[0.625rem] text-muted-foreground">
|
|
{lineCount} line{lineCount !== 1 ? 's' : ''}
|
|
</span>
|
|
<button
|
|
onClick={onClose}
|
|
className={cn(
|
|
"px-4 py-1.5 rounded-lg text-xs font-medium transition-colors",
|
|
"bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)]"
|
|
)}
|
|
>
|
|
Close & Return to Chat
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|