Files
figlet-ui/components/ui/Toast.tsx
Sebastian Krüger 704695f14f feat: add toast notifications, random font, and font info display
Major UX enhancements for better user feedback and discovery:

**Toast Notification System**
- Beautiful toast notifications with 3 types (success/error/info)
- Auto-dismiss after 3 seconds
- Slide-in animation from right
- Color-coded by type (green/red/blue)
- Dark mode support
- Close button for manual dismiss
- ToastProvider context for global access
- Non-blocking UI overlay

**Random Font Discovery**
- Shuffle button in font selector
- One-click random font selection
- Toast notification shows selected font
- Perfect for discovering new fonts
- Located next to "Select Font" header

**Enhanced Font Preview**
- Font name badge display
- Character count statistics
- Line count statistics
- Better visual hierarchy
- Responsive stat display

**Improved Feedback**
- Toast on copy: "Copied to clipboard!"
- Toast on share: "Shareable URL copied!"
- Toast on random: "Random font: FontName"
- Error toasts for failed operations
- Removed temporary text replacement

**Smooth Animations**
- Slide-in animation for toasts
- Fade-in animation class
- Custom keyframe animations
- CSS utility classes
- Smooth transitions throughout

**Technical Improvements**
- useToast custom hook
- Context-based state management
- Auto-cleanup with setTimeout
- Unique toast IDs
- TypeScript types for toast system
- Proper event propagation

**Better UX**
- No more jarring text replacements
- Non-intrusive notifications
- Professional feedback system
- Discoverable random feature
- Informative preview stats

The app now feels polished and professional with proper user feedback!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 13:21:13 +01:00

91 lines
2.9 KiB
TypeScript

'use client';
import * as React from 'react';
import { X, CheckCircle2, AlertCircle, Info } from 'lucide-react';
import { cn } from '@/lib/utils/cn';
export type ToastType = 'success' | 'error' | 'info';
export interface Toast {
id: string;
message: string;
type: ToastType;
}
interface ToastContextType {
toasts: Toast[];
addToast: (message: string, type?: ToastType) => void;
removeToast: (id: string) => void;
}
const ToastContext = React.createContext<ToastContextType | undefined>(undefined);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = React.useState<Toast[]>([]);
const addToast = React.useCallback((message: string, type: ToastType = 'success') => {
const id = Math.random().toString(36).substring(7);
setToasts((prev) => [...prev, { id, message, type }]);
// Auto remove after 3 seconds
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, 3000);
}, []);
const removeToast = React.useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
{children}
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 pointer-events-none">
{toasts.map((toast) => (
<ToastItem key={toast.id} toast={toast} onClose={() => removeToast(toast.id)} />
))}
</div>
</ToastContext.Provider>
);
}
function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) {
const Icon = toast.type === 'success' ? CheckCircle2 : toast.type === 'error' ? AlertCircle : Info;
return (
<div
className={cn(
'flex items-center gap-3 px-4 py-3 rounded-lg shadow-lg pointer-events-auto',
'animate-in slide-in-from-right-full duration-300',
'min-w-[300px] max-w-[400px]',
{
'bg-green-50 text-green-900 border border-green-200 dark:bg-green-900/20 dark:text-green-100 dark:border-green-800':
toast.type === 'success',
'bg-red-50 text-red-900 border border-red-200 dark:bg-red-900/20 dark:text-red-100 dark:border-red-800':
toast.type === 'error',
'bg-blue-50 text-blue-900 border border-blue-200 dark:bg-blue-900/20 dark:text-blue-100 dark:border-blue-800':
toast.type === 'info',
}
)}
>
<Icon className="h-5 w-5 flex-shrink-0" />
<p className="text-sm font-medium flex-1">{toast.message}</p>
<button
onClick={onClose}
className="flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity"
aria-label="Close"
>
<X className="h-4 w-4" />
</button>
</div>
);
}
export function useToast() {
const context = React.useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within ToastProvider');
}
return context;
}