Files
audio-ui/components/ui/Toast.tsx
Sebastian Krüger 591f726899 feat: initialize Next.js 16 project with Tailwind CSS 4 and Docker support
Phase 1 Implementation:
- Set up Next.js 16 with React 19, TypeScript 5, and Turbopack
- Configure Tailwind CSS 4 with OKLCH color system
- Implement dark/light theme support
- Create core UI components: Button, Card, Slider, Progress, Toast
- Add ThemeToggle component for theme switching
- Set up project directory structure for audio editor
- Create storage utilities for settings management
- Add Dockerfile with multi-stage build (Node + nginx)
- Configure nginx for static file serving with caching
- Add docker-compose.yml for easy deployment
- Configure static export mode for production

Tech Stack:
- Next.js 16 with Turbopack
- React 19
- TypeScript 5
- Tailwind CSS 4
- pnpm 9.0.0
- nginx 1.27 (for Docker deployment)

Build verified and working ✓

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:23:00 +01:00

135 lines
3.5 KiB
TypeScript

'use client';
import * as React from 'react';
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils/cn';
export interface Toast {
id: string;
title?: string;
description?: string;
variant?: 'default' | 'success' | 'error' | 'warning' | 'info';
duration?: number;
}
interface ToastContextType {
toasts: Toast[];
addToast: (toast: Omit<Toast, 'id'>) => void;
removeToast: (id: string) => void;
}
const ToastContext = React.createContext<ToastContextType | undefined>(
undefined
);
export function useToast() {
const context = React.useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within ToastProvider');
}
return context;
}
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = React.useState<Toast[]>([]);
const addToast = React.useCallback((toast: Omit<Toast, 'id'>) => {
const id = Math.random().toString(36).substring(2, 9);
const newToast: Toast = { id, ...toast };
setToasts((prev) => [...prev, newToast]);
// Auto remove after duration
const duration = toast.duration ?? 5000;
if (duration > 0) {
setTimeout(() => {
removeToast(id);
}, duration);
}
}, []);
const removeToast = React.useCallback((id: string) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
{children}
<ToastContainer toasts={toasts} onRemove={removeToast} />
</ToastContext.Provider>
);
}
function ToastContainer({
toasts,
onRemove,
}: {
toasts: Toast[];
onRemove: (id: string) => void;
}) {
if (toasts.length === 0) return null;
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-md w-full pointer-events-none">
{toasts.map((toast) => (
<ToastItem key={toast.id} toast={toast} onRemove={onRemove} />
))}
</div>
);
}
function ToastItem({
toast,
onRemove,
}: {
toast: Toast;
onRemove: (id: string) => void;
}) {
const variant = toast.variant ?? 'default';
const icons = {
default: Info,
success: CheckCircle,
error: AlertCircle,
warning: AlertTriangle,
info: Info,
};
const Icon = icons[variant];
return (
<div
className={cn(
'flex items-start gap-3 rounded-lg border p-4 shadow-lg pointer-events-auto',
'animate-slideInFromRight',
{
'bg-card border-border': variant === 'default',
'bg-success/10 border-success text-success-foreground':
variant === 'success',
'bg-destructive/10 border-destructive text-destructive-foreground':
variant === 'error',
'bg-warning/10 border-warning text-warning-foreground':
variant === 'warning',
'bg-info/10 border-info text-info-foreground': variant === 'info',
}
)}
>
<Icon className="h-5 w-5 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
{toast.title && (
<div className="font-semibold text-sm">{toast.title}</div>
)}
{toast.description && (
<div className="text-sm opacity-90 mt-1">{toast.description}</div>
)}
</div>
<button
onClick={() => onRemove(toast.id)}
className="flex-shrink-0 rounded-md p-1 hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
>
<X className="h-4 w-4" />
</button>
</div>
);
}