// components/ui/index.tsx // ───────────────────────────────────────────── // All primitives match your NutritionPage aesthetic. // Use these on every page for instant consistency. // ───────────────────────────────────────────── "use client"; import { motion, AnimatePresence, HTMLMotionProps } from "framer-motion"; import { colors, severity, SeverityLevel, motionPresets, sectionLabelClass } from "@/lib/tokens"; import { cn } from "@/lib/utils"; export { sectionLabelClass }; // ── PageShell ───────────────────────────────────────────────────────────────── // Wraps every page. Provides the dark bg + ambient glow + safe padding. interface PageShellProps { children: React.ReactNode; className?: string; /** Show the saffron/green ambient glow in the background */ glow?: boolean; } export function PageShell({ children, className, glow = true }: PageShellProps) { return (
{glow && (
)}
{children}
); } // ── PageHeader ──────────────────────────────────────────────────────────────── interface PageHeaderProps { icon?: string; title: string; subtitle?: string; delay?: number; } export function PageHeader({ icon, title, subtitle, delay = 0 }: PageHeaderProps) { return (
{icon && {icon}}

{title}

{subtitle && (

{subtitle}

)}
); } // ── Card ────────────────────────────────────────────────────────────────────── interface CardProps extends HTMLMotionProps<"div"> { children: React.ReactNode; className?: string; /** When true, uses the expanded/hover background */ active?: boolean; /** Coloured left-border accent */ accentColor?: string; delay?: number; } export function Card({ children, className, active, accentColor, delay = 0, ...props }: CardProps) { return ( {children} ); } // ── SectionLabel ────────────────────────────────────────────────────────────── export function SectionLabel({ children }: { children: React.ReactNode }) { return (

{children}

); } // ── StatGrid + StatCard ─────────────────────────────────────────────────────── // The 3-column target grid from NutritionPage, generalised. interface StatCardProps { icon: string; value: string | number; unit?: string; label: string; } export function StatCard({ icon, value, unit, label }: StatCardProps) { return (
{icon}
{value} {unit && {unit}}
{label}
); } // ── SeverityBadge ───────────────────────────────────────────────────────────── export function SeverityBadge({ level }: { level: SeverityLevel }) { const s = severity[level]; return ( {s.label} ); } // ── Banner ──────────────────────────────────────────────────────────────────── // The orange deficiency/info banner from NutritionPage. interface BannerProps { children: React.ReactNode; color?: string; // defaults to accent orange delay?: number; } export function Banner({ children, color = colors.accent, delay = 0.1 }: BannerProps) { return ( ⚠️ {children} ); } // ── Button ──────────────────────────────────────────────────────────────────── interface ButtonProps extends React.ButtonHTMLAttributes { variant?: "primary" | "ghost" | "success"; accentColor?: string; } export function Button({ variant = "primary", accentColor, className, children, ...props }: ButtonProps) { const styles: Record = { primary: { background: accentColor ?? colors.accent, color: "#0d0d1a" }, ghost: { background: colors.bgSubtle, color: colors.textSecondary, border: `1px solid ${colors.border}` }, success: { background: colors.okBg, color: colors.ok }, }; return ( {children} ); } // ── LoadingShell ────────────────────────────────────────────────────────────── // Drop-in loading skeleton that matches the dark theme. export function LoadingShell({ rows = 4 }: { rows?: number }) { return (
{[...Array(rows)].map((_, i) => (
))}
); } // ── Chip ────────────────────────────────────────────────────────────────────── // Small coloured pill — used for food group tags, report types, etc. interface ChipProps { label: string; color?: string; } export function Chip({ label, color = colors.accent }: ChipProps) { return ( {label} ); }