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,9 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { Check, Copy, Repeat2, Square, SquareCheckBig, X } from "lucide-react";
|
||||
import { RiCheckLine as Check, RiFileCopyLine as Copy, RiRepeat2Line as Repeat2, RiCloseLine as X } from "@remixicon/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { formatAmount, formatUsd } from "@/app/lib/units";
|
||||
import type { Aisle, GroceryLine, GrocerySummary, ItemId } from "@/app/types";
|
||||
import { cx } from "@/utils/cx";
|
||||
import { BudgetMeter } from "./BudgetMeter";
|
||||
|
||||
interface PanelProps {
|
||||
|
|
@ -35,7 +36,7 @@ export function GrocerySummaryPanel({ summary, budget, priceSourceLabel, onToggl
|
|||
return (
|
||||
<div>
|
||||
{/* --- Tactile Market Receipt Header -------------------------------- */}
|
||||
<div className="paper-grain rounded-xl border border-line bg-canvas p-4">
|
||||
<div className="rounded-3xl border border-border-button-default bg-canvas p-4">
|
||||
<BudgetMeter spent={summary.checkoutTotal} budget={budget} />
|
||||
|
||||
<dl className="receipt-rule mt-4 grid grid-cols-2 gap-3 pt-3.5 text-xs">
|
||||
|
|
@ -61,7 +62,7 @@ export function GrocerySummaryPanel({ summary, budget, priceSourceLabel, onToggl
|
|||
<div className="mt-5 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-serif text-base font-semibold">Shopping list</h3>
|
||||
<p className="text-xs text-ink-faint">Tap items you already have on hand</p>
|
||||
<p className="text-xs text-ink-faint">Tap “I have this” on anything in your kitchen and it drops off the bill.</p>
|
||||
</div>
|
||||
{summary.lines.length > 0 && (
|
||||
<button
|
||||
|
|
@ -102,48 +103,46 @@ export function GrocerySummaryPanel({ summary, budget, priceSourceLabel, onToggl
|
|||
}
|
||||
|
||||
function GroceryRow({ line, onToggleOnHand }: { line: GroceryLine; onToggleOnHand: PanelProps["onToggleOnHand"] }) {
|
||||
const { item } = line;
|
||||
const { item, onHand } = line;
|
||||
return (
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggleOnHand(item.id, !line.onHand)}
|
||||
aria-pressed={line.onHand}
|
||||
aria-label={line.onHand ? `Mark ${item.name} as needed at the store` : `Mark ${item.name} as already on hand`}
|
||||
className={`flex min-h-12 w-full items-start justify-between gap-3 py-2.5 text-left text-sm transition-colors hover:bg-raised/50 ${
|
||||
line.onHand ? "text-ink-faint" : "text-ink"
|
||||
}`}
|
||||
>
|
||||
<span className="flex min-w-0 items-start gap-2.5">
|
||||
<span className="mt-0.5 shrink-0 text-ink-muted">
|
||||
{line.onHand ? (
|
||||
<SquareCheckBig className="size-4 text-status-ok" strokeWidth={2} aria-hidden />
|
||||
) : (
|
||||
<Square className="size-4 text-line-strong" strokeWidth={1.75} aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className={`block leading-snug ${line.onHand ? "line-through" : "font-medium"}`}>
|
||||
{line.packages > 1 && <span className="font-mono tabular-nums">{line.packages} × </span>}
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-xs text-ink-muted">
|
||||
{item.packageLabel} · need {formatAmount(line.neededAmount, item.baseUnit)}
|
||||
{line.onHand && " · on hand"}
|
||||
</span>
|
||||
{line.usedBy.length > 1 && (
|
||||
<span className="mt-0.5 inline-flex items-center gap-1 text-xs text-ink-faint" title={line.usedBy.join(", ")}>
|
||||
<Repeat2 className="size-3 shrink-0" aria-hidden />
|
||||
Used in {line.usedBy.length} meals
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<li className="flex min-h-12 items-start justify-between gap-3 py-2.5 text-sm">
|
||||
<span className="min-w-0">
|
||||
<span className={cx("block leading-snug", onHand ? "text-ink-faint line-through" : "font-medium text-ink")}>
|
||||
{line.packages > 1 && <span className="font-mono tabular-nums">{line.packages} × </span>}
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-xs text-ink-muted">
|
||||
{item.packageLabel} · need {formatAmount(line.neededAmount, item.baseUnit)}
|
||||
</span>
|
||||
{line.usedBy.length > 1 && (
|
||||
<span className="mt-0.5 inline-flex items-center gap-1 text-xs text-ink-faint" title={line.usedBy.join(", ")}>
|
||||
<Repeat2 className="size-3 shrink-0" aria-hidden />
|
||||
Used in {line.usedBy.length} meals
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="shrink-0 text-right font-mono text-sm font-medium tabular-nums">
|
||||
{line.onHand ? "$0.00" : formatUsd(line.checkoutCost)}
|
||||
<span className="flex shrink-0 flex-col items-end gap-1.5">
|
||||
<span className={cx("font-mono text-sm font-medium tabular-nums", onHand ? "text-ink-faint" : "text-ink")}>
|
||||
{onHand ? "$0.00" : formatUsd(line.checkoutCost)}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggleOnHand(item.id, !onHand)}
|
||||
aria-pressed={onHand}
|
||||
aria-label={`I have ${item.name}`}
|
||||
title={onHand ? "Counted as $0. Tap to add it back to the bill." : "Already have it? Tap to take it off the bill."}
|
||||
className={cx(
|
||||
"inline-flex min-h-7 cursor-pointer items-center gap-1 rounded-full border px-2.5 text-caption-1-medium whitespace-nowrap transition-colors duration-150",
|
||||
"outline-none focus-visible:ring-2 focus-visible:ring-border-focus-ring",
|
||||
onHand
|
||||
? "border-status-ok-line bg-status-ok-bg text-status-ok"
|
||||
: "border-border-button-default bg-background-primary-default text-ink-muted hover:border-border-button-hover hover:text-ink",
|
||||
)}
|
||||
>
|
||||
{onHand && <Check className="size-3.5 shrink-0" aria-hidden />}I have this
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
|
@ -198,7 +197,7 @@ export function GroceryListDrawer({ open, onClose, ...panelProps }: DrawerProps)
|
|||
onClose={onClose}
|
||||
onClick={(e) => e.target === dialogRef.current && onClose()}
|
||||
aria-labelledby="grocery-drawer-title"
|
||||
className="sheet-dialog m-0 mt-auto max-h-[88dvh] w-full max-w-none overflow-y-auto rounded-t-2xl border-t border-line bg-surface p-0 shadow-2xl lg:hidden"
|
||||
className="sheet-dialog m-0 mt-auto max-h-[88dvh] w-full max-w-none overflow-y-auto rounded-t-3xl border-t border-line bg-surface p-0 shadow-2xl lg:hidden"
|
||||
>
|
||||
<div className="p-4 pb-8 sm:px-6">
|
||||
<div className="mb-4 flex items-center justify-between border-b border-line pb-3">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue