feat: add templates, history, comparison mode, animations, and empty states
- Add text templates with 16 pre-made options across 4 categories (greeting, tech, fun, seasonal) - Add copy history panel tracking last 10 copied items with restore functionality - Add font comparison mode to view multiple fonts side-by-side (up to 6 fonts) - Add smooth animations: slide-down, slide-up, scale-in, fade-in, pulse, and shimmer - Add loading skeletons for better perceived performance - Add EmptyState component with contextual messages and icons - Add hover effects and transitions throughout the UI - Improve visual feedback with animated badges and shadows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,20 @@ import * as React from 'react';
|
||||
import { TextInput } from './TextInput';
|
||||
import { FontPreview } from './FontPreview';
|
||||
import { FontSelector } from './FontSelector';
|
||||
import { TextTemplates } from './TextTemplates';
|
||||
import { HistoryPanel } from './HistoryPanel';
|
||||
import { ComparisonMode } from './ComparisonMode';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { GitCompare } from 'lucide-react';
|
||||
import { textToAscii } from '@/lib/figlet/figletService';
|
||||
import { getFontList } from '@/lib/figlet/fontLoader';
|
||||
import { debounce } from '@/lib/utils/debounce';
|
||||
import { addRecentFont } from '@/lib/storage/favorites';
|
||||
import { addToHistory, type HistoryItem } from '@/lib/storage/history';
|
||||
import { decodeFromUrl, updateUrl, getShareableUrl } from '@/lib/utils/urlSharing';
|
||||
import { useToast } from '@/components/ui/Toast';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import type { FigletFont } from '@/types/figlet';
|
||||
|
||||
export function FigletConverter() {
|
||||
@@ -18,6 +26,9 @@ export function FigletConverter() {
|
||||
const [asciiArt, setAsciiArt] = React.useState('');
|
||||
const [fonts, setFonts] = React.useState<FigletFont[]>([]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [isComparisonMode, setIsComparisonMode] = React.useState(false);
|
||||
const [comparisonFonts, setComparisonFonts] = React.useState<string[]>([]);
|
||||
const [comparisonResults, setComparisonResults] = React.useState<Record<string, string>>({});
|
||||
const { addToast } = useToast();
|
||||
|
||||
// Load fonts and check URL params on mount
|
||||
@@ -72,6 +83,7 @@ export function FigletConverter() {
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(asciiArt);
|
||||
addToHistory(text, selectedFont, asciiArt);
|
||||
addToast('Copied to clipboard!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to copy:', error);
|
||||
@@ -115,24 +127,148 @@ export function FigletConverter() {
|
||||
addToast(`Random font: ${fonts[randomIndex].name}`, 'info');
|
||||
};
|
||||
|
||||
const handleSelectTemplate = (templateText: string) => {
|
||||
setText(templateText);
|
||||
addToast(`Template applied: ${templateText}`, 'info');
|
||||
};
|
||||
|
||||
const handleSelectHistory = (item: HistoryItem) => {
|
||||
setText(item.text);
|
||||
setSelectedFont(item.font);
|
||||
addToast(`Restored from history`, 'info');
|
||||
};
|
||||
|
||||
// Comparison mode handlers
|
||||
const handleToggleComparisonMode = () => {
|
||||
const newMode = !isComparisonMode;
|
||||
setIsComparisonMode(newMode);
|
||||
if (newMode && comparisonFonts.length === 0) {
|
||||
// Initialize with current font
|
||||
setComparisonFonts([selectedFont]);
|
||||
}
|
||||
addToast(newMode ? 'Comparison mode enabled' : 'Comparison mode disabled', 'info');
|
||||
};
|
||||
|
||||
const handleAddToComparison = (fontName: string) => {
|
||||
if (comparisonFonts.includes(fontName)) {
|
||||
addToast('Font already in comparison', 'info');
|
||||
return;
|
||||
}
|
||||
if (comparisonFonts.length >= 6) {
|
||||
addToast('Maximum 6 fonts for comparison', 'info');
|
||||
return;
|
||||
}
|
||||
setComparisonFonts([...comparisonFonts, fontName]);
|
||||
addToast(`Added ${fontName} to comparison`, 'success');
|
||||
};
|
||||
|
||||
const handleRemoveFromComparison = (fontName: string) => {
|
||||
setComparisonFonts(comparisonFonts.filter((f) => f !== fontName));
|
||||
addToast(`Removed ${fontName} from comparison`, 'info');
|
||||
};
|
||||
|
||||
const handleCopyComparisonFont = async (fontName: string, result: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(result);
|
||||
addToHistory(text, fontName, result);
|
||||
addToast(`Copied ${fontName} to clipboard!`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to copy:', error);
|
||||
addToast('Failed to copy', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadComparisonFont = (fontName: string, result: string) => {
|
||||
const blob = new Blob([result], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `figlet-${fontName}-${Date.now()}.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// Generate comparison results
|
||||
React.useEffect(() => {
|
||||
if (!isComparisonMode || comparisonFonts.length === 0 || !text) return;
|
||||
|
||||
const generateComparisons = async () => {
|
||||
const results: Record<string, string> = {};
|
||||
for (const fontName of comparisonFonts) {
|
||||
try {
|
||||
results[fontName] = await textToAscii(text, fontName);
|
||||
} catch (error) {
|
||||
console.error(`Error generating ASCII art for ${fontName}:`, error);
|
||||
results[fontName] = 'Error generating ASCII art';
|
||||
}
|
||||
}
|
||||
setComparisonResults(results);
|
||||
};
|
||||
|
||||
generateComparisons();
|
||||
}, [isComparisonMode, comparisonFonts, text]);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Left Column - Input and Preview */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Comparison Mode Toggle */}
|
||||
<Card className="scale-in">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitCompare className={cn(
|
||||
"h-4 w-4",
|
||||
isComparisonMode ? "text-primary" : "text-muted-foreground"
|
||||
)} />
|
||||
<span className="text-sm font-medium">Comparison Mode</span>
|
||||
{isComparisonMode && comparisonFonts.length > 0 && (
|
||||
<span className="text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full font-medium slide-down">
|
||||
{comparisonFonts.length} {comparisonFonts.length === 1 ? 'font' : 'fonts'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant={isComparisonMode ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={handleToggleComparisonMode}
|
||||
className={cn(isComparisonMode && "shadow-lg")}
|
||||
>
|
||||
{isComparisonMode ? 'Disable' : 'Enable'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<TextTemplates onSelectTemplate={handleSelectTemplate} />
|
||||
|
||||
<HistoryPanel onSelectHistory={handleSelectHistory} />
|
||||
|
||||
<TextInput
|
||||
value={text}
|
||||
onChange={setText}
|
||||
placeholder="Type your text here..."
|
||||
/>
|
||||
|
||||
<FontPreview
|
||||
text={asciiArt}
|
||||
font={selectedFont}
|
||||
isLoading={isLoading}
|
||||
onCopy={handleCopy}
|
||||
onDownload={handleDownload}
|
||||
onShare={handleShare}
|
||||
/>
|
||||
{isComparisonMode ? (
|
||||
<ComparisonMode
|
||||
text={text}
|
||||
selectedFonts={comparisonFonts}
|
||||
fontResults={comparisonResults}
|
||||
onRemoveFont={handleRemoveFromComparison}
|
||||
onCopyFont={handleCopyComparisonFont}
|
||||
onDownloadFont={handleDownloadComparisonFont}
|
||||
/>
|
||||
) : (
|
||||
<FontPreview
|
||||
text={asciiArt}
|
||||
font={selectedFont}
|
||||
isLoading={isLoading}
|
||||
onCopy={handleCopy}
|
||||
onDownload={handleDownload}
|
||||
onShare={handleShare}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Column - Font Selector */}
|
||||
@@ -142,6 +278,9 @@ export function FigletConverter() {
|
||||
selectedFont={selectedFont}
|
||||
onSelectFont={setSelectedFont}
|
||||
onRandomFont={handleRandomFont}
|
||||
isComparisonMode={isComparisonMode}
|
||||
comparisonFonts={comparisonFonts}
|
||||
onAddToComparison={handleAddToComparison}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user