Move UI to BoardUI with slate + salmon theme; add PDF plan export

- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button,
  SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's
  color aliases onto BoardUI tokens and swap lucide for Remix icons
- Retint BoardUI's neutral ramp to cool slate with a salmon accent;
  drop the dotted background; raise muted text contrast
- Simplify the landing page and remove the overlap explorer
- Planner: explicit "I have this" toggle on grocery lines, pantry panel
  above the menu with the staples list spelled out, sticky aisle
  header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded)
- Recipes: replace the family cabbage soup with Cade's Cabbage Soup
  (adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup
- Remove unused BoardUI components, dependencies and exports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cayden Schwechten 2026-11-06 16:36:33 -06:00
parent b02c7a024b
commit df5dd3a70e
56 changed files with 4749 additions and 788 deletions

View file

@ -1,13 +1,15 @@
"use client";
import { ChevronDown, RotateCcw, Shuffle, SlidersHorizontal, Wand2 } from "lucide-react";
import { RiDownload2Line, RiArrowDownSLine as ChevronDown, RiResetLeftLine as RotateCcw, RiShuffleLine as Shuffle, RiEqualizerLine as SlidersHorizontal, RiMagicLine as Wand2 } from "@remixicon/react";
import { useState, type Dispatch } from "react";
import { APPLIANCES, CUISINES, STYLES, activeRecipeIds, mealLabel, visibleMeals } from "@/app/lib/plan";
import { buildPlanModel, renderPlanPdf } from "@/app/lib/exportPlan";
import type { PlanAction } from "@/app/lib/usePlanState";
import type { FillMode, MealPlanState } from "@/app/types";
import { Chip } from "./Chip";
import { ApplianceIcon, StyleIcon } from "./Icons";
import { RadioGroup } from "./RadioGroup";
import { Button } from "@/components/base/buttons/button";
interface ConfigPanelProps {
state: MealPlanState;
@ -36,6 +38,19 @@ export function ConfigPanel({ state, dispatch, onGenerate, onClear, isGenerating
const [filtersOpen, setFiltersOpen] = useState(false);
const filledCount = activeRecipeIds(state).length;
const [isExporting, setIsExporting] = useState(false);
async function downloadPlan() {
setIsExporting(true);
try {
// Loaded on demand so jsPDF stays out of the planner's initial bundle.
const { jsPDF } = await import("jspdf");
renderPlanPdf(buildPlanModel(state), jsPDF).save("meal-plan.pdf");
} finally {
setIsExporting(false);
}
}
const mealNames = visibleMeals(state.mealsPerDay).map(mealLabel).join(" · ");
const activeFilterCount =
(APPLIANCES.length - state.selectedAppliances.length > 0 ? 1 : 0) +
@ -51,7 +66,7 @@ export function ConfigPanel({ state, dispatch, onGenerate, onClear, isGenerating
.join(" · ");
return (
<section aria-labelledby="config-heading" className="rounded-xl border border-line bg-surface p-4 sm:p-5">
<section aria-labelledby="config-heading" className="rounded-3xl border border-border-button-default bg-surface p-4 sm:p-5">
<h2 id="config-heading" className="sr-only">
Plan parameters
</h2>
@ -155,25 +170,13 @@ export function ConfigPanel({ state, dispatch, onGenerate, onClear, isGenerating
{/* --- Action Toolbar + Progressive Disclosure Toggle --------------- */}
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t border-line pt-3.5">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => onGenerate("empty")}
disabled={isGenerating}
className="trim-cap inline-flex min-h-10 items-center gap-2 rounded-lg bg-brand-600 px-4 py-2 text-sm font-semibold text-on-accent shadow-2xs transition-colors hover:bg-brand-500 disabled:opacity-60"
>
<Wand2 className="size-4 shrink-0" strokeWidth={2} aria-hidden />
<Button leadingIcon={Wand2} onClick={() => onGenerate("empty")} disabled={isGenerating}>
{isGenerating ? "Planning…" : "Auto-fill empty"}
</button>
</Button>
<button
type="button"
onClick={() => onGenerate("all")}
disabled={isGenerating}
className="trim-cap inline-flex min-h-10 items-center gap-1.5 rounded-lg border border-line-strong bg-surface px-3.5 py-2 text-sm font-medium text-ink transition-colors hover:bg-raised disabled:opacity-60"
>
<Shuffle className="size-4 shrink-0 text-ink-muted" strokeWidth={1.75} aria-hidden />
<Button variant="secondary" leadingIcon={Shuffle} onClick={() => onGenerate("all")} disabled={isGenerating}>
Shuffle plan
</button>
</Button>
<button
type="button"
@ -184,6 +187,16 @@ export function ConfigPanel({ state, dispatch, onGenerate, onClear, isGenerating
<RotateCcw className="size-3.5 shrink-0" strokeWidth={1.75} aria-hidden />
Clear
</button>
<Button
variant="secondary"
leadingIcon={RiDownload2Line}
onClick={downloadPlan}
disabled={isGenerating || isExporting || filledCount === 0}
title="Save the week as a PDF, with the full recipe for every meal, organized by day"
>
{isExporting ? "Preparing PDF…" : "Download PDF"}
</Button>
</div>
<button