planmymeal/app/components/RotatingPreview.tsx
Cayden Schwechten c19b910c36 init
2026-10-31 20:44:29 -05:00

123 lines
5.2 KiB
TypeScript

"use client";
import { Pause, Play, Repeat2, ShoppingBag } from "lucide-react";
import { useEffect, useState } from "react";
export interface PreviewSet {
theme: string;
blurb: string;
meals: { slot: string; title: string; cost: string }[];
total: string;
sharedItems: number;
itemCount: number;
}
const INTERVAL_MS = 5000;
export function RotatingPreview({ sets }: { sets: PreviewSet[] }) {
const [index, setIndex] = useState(0);
const [paused, setPaused] = useState(false);
const [hovering, setHovering] = useState(false);
const running = !paused && !hovering;
useEffect(() => {
if (!running || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const timer = window.setInterval(() => setIndex((i) => (i + 1) % sets.length), INTERVAL_MS);
return () => window.clearInterval(timer);
}, [running, index, sets.length]);
const set = sets[index];
return (
<section
aria-label="Example meal plans"
className="relative"
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
onFocus={() => setHovering(true)}
onBlur={() => setHovering(false)}
>
{/* Architectural editorial frame — crisp 1px border & tactile offset shadow instead of blurred glow blobs */}
<div className="rounded-xl border border-line-strong bg-surface shadow-[6px_6px_0_0_var(--color-line)]">
{/* Top progress rule */}
<div className="h-1 overflow-hidden rounded-t-xl bg-raised">
<div
key={index}
className="h-full origin-left animate-preview-timer bg-brand-600"
style={{ animationDuration: `${INTERVAL_MS}ms`, animationPlayState: running ? "running" : "paused" }}
/>
</div>
<div className="p-5 sm:p-6">
<div key={set.theme} className="animate-fade-in">
<div className="flex items-baseline justify-between gap-3 border-b border-line pb-3">
<div>
<p className="font-mono text-[11px] tracking-wider text-ink-faint uppercase">Sample menu · 2 people</p>
<h2 className="mt-0.5 font-serif text-xl font-semibold tracking-tight text-ink">{set.theme}</h2>
</div>
<span className="rounded-md border border-line bg-raised px-2.5 py-1 text-xs font-medium text-ink-muted">
{set.blurb}
</span>
</div>
<ul className="divide-y divide-line">
{set.meals.map((m) => (
<li key={m.slot} className="flex items-baseline justify-between gap-4 py-3">
<div className="min-w-0">
<p className="font-mono text-[11px] tracking-wide text-ink-faint uppercase">{m.slot}</p>
<p className="truncate font-medium text-ink">{m.title}</p>
</div>
<p className="shrink-0 font-mono text-xs text-ink-muted tabular-nums">{m.cost}/meal</p>
</li>
))}
</ul>
{/* Receipt tear-off footer */}
<div className="receipt-rule mt-2 pt-3.5">
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-raised px-3.5 py-2.5 text-xs">
<span className="inline-flex items-center gap-1.5 font-medium text-status-ok">
<Repeat2 className="size-3.5 shrink-0" strokeWidth={2} aria-hidden />
{set.sharedItems} shared store items
</span>
<span className="inline-flex items-center gap-1.5 font-mono font-semibold text-ink tabular-nums">
<ShoppingBag className="size-3.5 shrink-0 text-ink-muted" strokeWidth={1.75} aria-hidden />
{set.itemCount} pkgs · {set.total}
</span>
</div>
</div>
</div>
{/* Accessible 40x40px pagination & playback controls */}
<div className="mt-3 flex items-center justify-between border-t border-line pt-2">
<div className="flex items-center">
{sets.map((s, i) => (
<button
key={s.theme}
type="button"
onClick={() => setIndex(i)}
aria-label={`Show example: ${s.theme}`}
aria-current={i === index}
className="grid size-9 place-items-center rounded-md hover:bg-raised"
>
<span
className={`block h-2 rounded-full transition-all ${
i === index ? "w-5 bg-brand-600" : "w-2 bg-line-strong"
}`}
/>
</button>
))}
</div>
<button
type="button"
onClick={() => setPaused((p) => !p)}
className="trim-cap inline-flex min-h-9 items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-ink-muted transition-colors hover:bg-raised hover:text-ink"
>
{paused ? <Play className="size-3.5" aria-hidden /> : <Pause className="size-3.5" aria-hidden />}
{paused ? "Play" : "Pause"}
</button>
</div>
</div>
</div>
</section>
);
}