feat: Sprint 3 — delight, polish, and responsive sidebar

Celebration micro-interactions:
- SuccessToast: auto-dismissing pill toast (success/info/warning variants)
- ConfettiBurst: pure CSS 20-particle confetti on project creation
- Project wizard: confetti + toast on successful creation
- Vacation approval/rejection: contextual toasts
- Allocation status change: success toast
- Button: active:scale-[0.97] press feedback on all variants

Collapsible sidebar + responsive:
- Desktop: toggle collapse (72px icons-only mode) with localStorage persistence
- NavTooltip: hover labels on collapsed icons
- Mobile: hamburger menu + slide-in overlay with backdrop
- Auto-close sidebar on mobile navigation
- Scroll-to-top on route change (smooth behavior)

Hover polish + accessibility:
- Table rows: animated left-border accent + hover-lift
- Stat cards + widgets: hover elevation + border glow
- Timeline blocks: scale(1.02) + shadow-md on hover
- Smooth scroll globally with prefers-reduced-motion fallback
- Filter chips: framer-motion scale+fade enter/exit
- Dropdowns: scaleY origin-top reveal animation
- Preferences modal: scale+fade entrance
- Link underline: animated ::after width expansion on hover

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-19 01:02:51 +01:00
parent a97597093f
commit f1f1be21c7
15 changed files with 804 additions and 177 deletions
@@ -11,6 +11,8 @@ import { SkillTagInput } from "~/components/ui/SkillTagInput.js";
import { usePermissions } from "~/hooks/usePermissions.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { formatCents } from "~/lib/format.js";
import { ConfettiBurst } from "~/components/ui/ConfettiBurst.js";
import { SuccessToast } from "~/components/ui/SuccessToast.js";
// ─── Constants ────────────────────────────────────────────────────────────────
@@ -1023,6 +1025,8 @@ export function ProjectWizard({ open, onClose }: ProjectWizardProps) {
const [state, setState] = useState<WizardState>(makeDefaultState);
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null);
const [showConfetti, setShowConfetti] = useState(false);
const [showSuccessToast, setShowSuccessToast] = useState(false);
const createProject = trpc.project.create.useMutation();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1139,7 +1143,12 @@ export function ProjectWizard({ open, onClose }: ProjectWizardProps) {
await utils.project.list.invalidate();
await utils.timeline.getEntries.invalidate();
await utils.timeline.getEntriesView.invalidate();
handleClose();
setShowConfetti(true);
setShowSuccessToast(true);
setTimeout(() => {
setShowConfetti(false);
handleClose();
}, 1200);
} catch (err) {
setSubmitError(err instanceof Error ? err.message : "Failed to create project");
} finally {
@@ -1158,7 +1167,14 @@ export function ProjectWizard({ open, onClose }: ProjectWizardProps) {
className="fixed inset-0 bg-black/50 z-50 flex items-start justify-center overflow-y-auto py-8 px-4"
onClick={handleBackdropClick}
>
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl relative">
{/* Celebration effects */}
<ConfettiBurst trigger={showConfetti} />
<SuccessToast
show={showSuccessToast}
message="Project created successfully!"
onDone={() => setShowSuccessToast(false)}
/>
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">New Project Wizard</h2>