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:
parent
b02c7a024b
commit
df5dd3a70e
56 changed files with 4749 additions and 788 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue