"use client"; import { RiCheckLine as Check, RiLightbulbLine as Lightbulb } from "@remixicon/react"; import { useState } from "react"; import { ingredientLines } from "@/app/lib/display"; import { APPLIANCES, CUISINES, MEAL_KIND_LABELS, STYLES } from "@/app/lib/plan"; import type { Recipe } from "@/app/types"; import { ApplianceIcon, StyleIcon } from "./Icons"; interface RecipeDetailProps { recipe: Recipe; /** People to scale ingredient amounts for. */ servings: number; /** Hide the cuisine/time/appliance chips when surrounding UI already shows them. */ showMeta?: boolean; /** Smaller type for use inside lists. */ compact?: boolean; /** Allow tapping ingredients & steps to check them off while cooking. */ interactive?: boolean; } export function RecipeDetail({ recipe, servings, showMeta = true, compact = false, interactive = false, }: RecipeDetailProps) { const lines = ingredientLines(recipe, servings); const text = compact ? "text-sm" : "text-base"; const [checkedIngredients, setCheckedIngredients] = useState>(() => new Set()); const [checkedSteps, setCheckedSteps] = useState>(() => new Set()); function toggleSet(setter: React.Dispatch>>, index: number) { setter((prev) => { const next = new Set(prev); if (next.has(index)) next.delete(index); else next.add(index); return next; }); } return (
{showMeta && }
{/* --- Ingredients ------------------------------------------------- */}

Ingredients{" "} · {servings} serving{servings === 1 ? "" : "s"}

{interactive && checkedIngredients.size > 0 && ( )}
    {lines.map((line, i) => { const done = interactive && checkedIngredients.has(i); const content = ( <> {line.quantity} {line.unit} {line.name} {line.detail && ({line.detail})} ); return (
  • {interactive ? ( ) : (
    {content}
    )}
  • ); })}
{/* --- Instructions ------------------------------------------------ */}

Instructions

{interactive && checkedSteps.size > 0 && ( )}
{servings !== recipe.servings && (

Ingredient quantities on the left are scaled to {servings}. Step prose is written for the original {recipe.servings}-serving batch—scale pan sizes or liquid mentions the same way.

)}
    {recipe.steps.map((step, i) => { const done = interactive && checkedSteps.has(i); const badge = ( {done ? : i + 1} ); return (
  1. {interactive ? ( ) : (
    {badge} {step}
    )}
  2. ); })}
{recipe.tip && (

Cook's note: {recipe.tip}

)}
); } export function RecipeMeta({ recipe }: { recipe: Recipe }) { const cuisineLabel = CUISINES.find((c) => c.id === recipe.cuisine)?.label; return (
    {cuisineLabel && (
  • {cuisineLabel}
  • )}
  • {MEAL_KIND_LABELS[recipe.kind]}
  • {recipe.prepTime} min
  • Serves {recipe.servings}
  • {recipe.requiredAppliances.length === 0 ? (
  • No cooking
  • ) : ( recipe.requiredAppliances.map((a) => { const label = APPLIANCES.find((x) => x.id === a)?.label ?? a; return (
  • {label}
  • ); }) )} {recipe.styles.map((s) => { const label = STYLES.find((x) => x.id === s)?.label; if (!label) return null; return (
  • {label}
  • ); })}
); }