3,200+ hardcoded color values replaced with CSS variable-backed Tailwind classes (bg-card, text-foreground, border-border, etc.). Enables light mode via CSS variable swap. Only syntax highlighting colors and intentional one-offs remain hardcoded (~15 values). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Download, X } from 'lucide-react'
|
|
import { flowTransferApi } from '@/api/flowTransfer'
|
|
import { toast } from '@/lib/toast'
|
|
import { Button } from '@/components/ui/Button'
|
|
|
|
interface ExportFlowModalProps {
|
|
treeId: string
|
|
treeName: string
|
|
onClose: () => void
|
|
}
|
|
|
|
function slugify(name: string): string {
|
|
return name.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[-\s]+/g, '-')
|
|
}
|
|
|
|
export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalProps) {
|
|
const [isExporting, setIsExporting] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose()
|
|
}
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
}, [onClose])
|
|
|
|
const handleExport = async () => {
|
|
setIsExporting(true)
|
|
try {
|
|
const blob = await flowTransferApi.exportFlow(treeId)
|
|
const url = URL.createObjectURL(blob)
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = `${slugify(treeName)}.rfflow`
|
|
document.body.appendChild(a)
|
|
a.click()
|
|
document.body.removeChild(a)
|
|
URL.revokeObjectURL(url)
|
|
toast.success('Flow exported successfully')
|
|
onClose()
|
|
} catch (err) {
|
|
console.error('Export failed:', err)
|
|
toast.error('Failed to export flow')
|
|
} finally {
|
|
setIsExporting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
|
|
onClick={onClose}
|
|
>
|
|
<div
|
|
className="w-full max-w-sm rounded-xl border border-border bg-card shadow-xl"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
|
<div className="flex items-center gap-2">
|
|
<Download className="h-4 w-4 text-muted-foreground" />
|
|
<h2 className="text-sm font-semibold text-foreground">Export Flow</h2>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
aria-label="Close"
|
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div className="px-5 py-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
Export <span className="font-medium text-foreground">{treeName}</span> as a <code className="text-xs font-sans text-xs">.rfflow</code> file (JSON format).
|
|
</p>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex justify-end gap-2 border-t border-border px-5 py-3">
|
|
<Button variant="secondary" onClick={onClose}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleExport}
|
|
loading={isExporting}
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
Download .rfflow
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|