From 15776d71480816d4a09f7cbc512ecbb5f10d79e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sun, 9 Nov 2025 13:31:08 +0100 Subject: [PATCH] feat: add PNG export, text alignment, and font size controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Massive UX improvements for preview customization and export: **PNG Export** - Export ASCII art as high-quality PNG images - Uses html-to-image library (2x pixel ratio) - Preserves background color and styling - Auto-download with font name in filename - Toast notification on success/error - PNG button in preview toolbar **Text Alignment Controls** - Left / Center / Right alignment buttons - Visual toggle buttons with icons - Live preview updates - Persists during font/text changes - Smooth transitions **Font Size Controls** - XS (10px) / SM (12-14px) / MD (14-16px) - Toggle buttons for quick switching - Responsive font sizes - Better readability options - Great for different screen sizes **Enhanced Preview Toolbar** - Reorganized button layout - Better button labels (Copy, Share, PNG, TXT) - Tooltips on all buttons - Icon + text labels - Wrapped flex layout for mobile **Preview Controls UI** - Two control groups (align + size) - Bordered button groups - Active state highlighting - Hover states - Clean, compact design **Updated Keyboard Shortcuts Help** - Better descriptions - Added "Tips" section - Feature discovery hints - Grouped by category - More helpful content **Technical Improvements** - Added html-to-image dependency - Ref-based element capture - Dynamic className composition - State management for controls - Proper TypeScript types **Button Improvements** - "Download" → "TXT" (more specific) - Added "PNG" button - Better icon usage - Consistent sizing - Mobile-friendly layout The preview is now fully customizable with professional export options! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/converter/FontPreview.tsx | 173 ++++++++++++++++++++---- components/ui/KeyboardShortcutsHelp.tsx | 45 +++--- package.json | 1 + pnpm-lock.yaml | 8 ++ 4 files changed, 181 insertions(+), 46 deletions(-) diff --git a/components/converter/FontPreview.tsx b/components/converter/FontPreview.tsx index b8724da..dee80bf 100644 --- a/components/converter/FontPreview.tsx +++ b/components/converter/FontPreview.tsx @@ -1,10 +1,12 @@ 'use client'; import * as React from 'react'; +import { toPng } from 'html-to-image'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; -import { Copy, Download, Share2 } from 'lucide-react'; +import { Copy, Download, Share2, Image as ImageIcon, AlignLeft, AlignCenter, AlignRight } from 'lucide-react'; import { cn } from '@/lib/utils/cn'; +import { useToast } from '@/components/ui/Toast'; export interface FontPreviewProps { text: string; @@ -16,40 +18,139 @@ export interface FontPreviewProps { className?: string; } +type TextAlign = 'left' | 'center' | 'right'; + export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare, className }: FontPreviewProps) { const lineCount = text ? text.split('\n').length : 0; const charCount = text ? text.length : 0; + const previewRef = React.useRef(null); + const [textAlign, setTextAlign] = React.useState('left'); + const [fontSize, setFontSize] = React.useState<'xs' | 'sm' | 'base'>('sm'); + const { addToast } = useToast(); + + const handleExportPNG = async () => { + if (!previewRef.current || !text) return; + + try { + const dataUrl = await toPng(previewRef.current, { + backgroundColor: getComputedStyle(previewRef.current).backgroundColor, + pixelRatio: 2, + }); + + const link = document.createElement('a'); + link.download = `figlet-${font || 'export'}-${Date.now()}.png`; + link.href = dataUrl; + link.click(); + + addToast('Exported as PNG!', 'success'); + } catch (error) { + console.error('Failed to export PNG:', error); + addToast('Failed to export PNG', 'error'); + } + }; return (
-
-
-

Preview

- {font && ( - - {font} - - )} +
+
+
+

Preview

+ {font && ( + + {font} + + )} +
+
+ {onCopy && ( + + )} + {onShare && ( + + )} + + {onDownload && ( + + )} +
-
- {onCopy && ( - - )} - {onShare && ( - - )} - {onDownload && ( - - )} + + {/* Controls */} +
+
+ + + +
+ +
+ + + +
@@ -61,13 +162,25 @@ export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare
)} -
+
{isLoading ? (
Generating...
) : text ? ( -
+            
               {text}
             
) : ( diff --git a/components/ui/KeyboardShortcutsHelp.tsx b/components/ui/KeyboardShortcutsHelp.tsx index 9a7e40d..ed724df 100644 --- a/components/ui/KeyboardShortcutsHelp.tsx +++ b/components/ui/KeyboardShortcutsHelp.tsx @@ -13,10 +13,10 @@ export interface Shortcut { } const shortcuts: Shortcut[] = [ - { key: '/', description: 'Focus search' }, + { key: '/', description: 'Focus font search' }, { key: 'Esc', description: 'Clear search / Close dialog' }, - { key: 'D', description: 'Toggle dark mode', modifier: 'ctrl' }, - { key: '?', description: 'Show keyboard shortcuts', modifier: 'shift' }, + { key: 'D', description: 'Toggle dark/light mode', modifier: 'ctrl' }, + { key: '?', description: 'Show this help dialog', modifier: 'shift' }, ]; export function KeyboardShortcutsHelp() { @@ -65,22 +65,35 @@ export function KeyboardShortcutsHelp() {
-
- {shortcuts.map((shortcut, i) => ( -
- {shortcut.description} -
- {shortcut.modifier && ( +
+
+

Navigation

+ {shortcuts.map((shortcut, i) => ( +
+ {shortcut.description} +
+ {shortcut.modifier && ( + + {shortcut.modifier === 'ctrl' ? '⌘/Ctrl' : 'Shift'} + + )} - {shortcut.modifier === 'ctrl' ? '⌘/Ctrl' : 'Shift'} + {shortcut.key} - )} - - {shortcut.key} - +
-
- ))} + ))} +
+ +
+

Tips

+
    +
  • • Click the Shuffle button for random fonts
  • +
  • • Use the heart icon to favorite fonts
  • +
  • • Filter by All, Favorites, or Recent
  • +
  • • Text alignment and size controls in Preview
  • +
+
diff --git a/package.json b/package.json index 87ecf76..291f9bd 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "clsx": "^2.1.1", "figlet": "^1.8.0", "fuse.js": "^7.1.0", + "html-to-image": "^1.11.13", "lucide-react": "^0.553.0", "next": "^16.0.0", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2ace12..44de8ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: fuse.js: specifier: ^7.1.0 version: 7.1.0 + html-to-image: + specifier: ^1.11.13 + version: 1.11.13 lucide-react: specifier: ^0.553.0 version: 0.553.0(react@19.2.0) @@ -1226,6 +1229,9 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-to-image@1.11.13: + resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3255,6 +3261,8 @@ snapshots: dependencies: hermes-estree: 0.25.1 + html-to-image@1.11.13: {} + ignore@5.3.2: {} ignore@7.0.5: {}