feat!: complete visual redesign → 1.0.0

BREAKING: version bump to 1.0.0

New design system:
- Emerald/teal color palette with semantic tokens (success, warning, destructive)
- Light/dark mode toggle with localStorage persistence and anti-FOUC
- CSS custom properties via Tailwind darkMode: 'class'
- Zero new dependencies — all primitives hand-rolled

Foundation:
- 70+ inline SVG icons (no external icon library)
- ThemeProvider + useTheme hook
- cn() utility for conditional classes

UI primitives (web/src/components/ui/):
- Button (6 variants, 4 sizes)
- Input, Label, Card with slots, Badge (6 variants + sizes)
- Switch (animated), Checkbox, Separator
- Skeleton, Spinner, EmptyState
- Toast system with auto-dismiss (success/error/warning/info)
- Dialog with focus trap + escape/click-outside
- ConfirmDialog replacing native confirm()
- DropdownMenu with keyboard support

Layout & navigation:
- Sidebar with icon+label nav, active indicator, responsive drawer on mobile
- Topbar for <lg viewports with hamburger
- PageHeader with title/description/actions pattern
- UserMenu with avatar + dropdown (change password, logout)
- DirtyBanner redesigned with semantic styling

Pages redesigned:
- Login: centered card with gradient background
- Dashboard: 4 KPI tiles, disk usage bars, services grid, recent activity
- Users: table with search, empty state, edit modal
- Files: breadcrumbs, drag-drop upload zone, SVG file icons, rename/delete
- Samba: card list with badges, edit modal with PathField
- NFS: card list with client chips, client editor
- Storage: tabs (Mover/SnapRAID/Jobs), real-time job log streaming
- Log: timeline grouped by date with filters
- Settings: watched mounts, import actions, system info
- NotFound: friendly 404 page

Polish:
- Toast notifications replace all native alert()/confirm()
- Tailwind animations (dialog-enter, toast-enter/leave, dropdown-enter)
- Custom scrollbar styling
- Focus-visible rings
- Skeleton loading states across all pages
This commit is contained in:
2026-07-08 01:24:51 -04:00
parent 56e5fabe8d
commit 10d0769071
40 changed files with 3742 additions and 2458 deletions
+51
View File
@@ -0,0 +1,51 @@
import { ButtonHTMLAttributes, forwardRef } from "react";
import { cn } from "../../lib/cn";
import { Loader2 } from "../../lib/icons";
export type ButtonVariant = "default" | "secondary" | "ghost" | "outline" | "destructive" | "link";
export type ButtonSize = "sm" | "md" | "lg" | "icon";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
}
const variantClasses: Record<ButtonVariant, string> = {
default: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70",
ghost: "hover:bg-accent hover:text-accent-foreground active:bg-accent/80",
outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground active:bg-accent/80",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80",
link: "text-primary underline-offset-4 hover:underline active:text-primary/80",
};
const sizeClasses: Record<ButtonSize, string> = {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
icon: "h-10 w-10",
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "default", size = "md", loading, disabled, children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
variantClasses[variant],
sizeClasses[size],
className
)}
disabled={disabled || loading}
{...props}
>
{loading && <Loader2 size={14} className="animate-spin" />}
{children}
</button>
);
}
);
Button.displayName = "Button";