/// /** * Timing Module - Pattern for cooking/prep times * * A composable pattern that can be used standalone or embedded in containers * like Record. Tracks prep, cook, and rest times with auto-calculated total. */ import { computed, type Default, NAME, recipe, UI } from "commontools"; import type { ModuleMetadata } from "./container-protocol.ts"; // ===== Self-Describing Metadata ===== export const MODULE_METADATA: ModuleMetadata = { type: "timing", label: "Timing", icon: "\u{23F1}", // stopwatch emoji schema: { prepTime: { type: "number", description: "Prep time in minutes" }, cookTime: { type: "number", description: "Cook time in minutes" }, restTime: { type: "number", description: "Rest time in minutes" }, }, fieldMapping: ["prepTime", "cookTime", "restTime"], }; // ===== Types ===== export interface TimingModuleInput { /** Prep time in minutes */ prepTime: Default; /** Cook time in minutes */ cookTime: Default; /** Rest time in minutes */ restTime: Default; } // ===== Helpers ===== // Format minutes as "Xh Ym" function formatTime(minutes: number | null): string { if (!minutes) return "-"; if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; } // ===== The Pattern ===== export const TimingModule = recipe( "TimingModule", ({ prepTime, cookTime, restTime }) => { // Compute total time const totalTime = computed(() => { const prep = prepTime ?? 0; const cook = cookTime ?? 0; const rest = restTime ?? 0; return prep + cook + rest || null; }); const displayText = computed(() => formatTime(totalTime)); return { [NAME]: computed(() => `${MODULE_METADATA.icon} Timing: ${displayText}`), [UI]: ( Prep Time min Cook Time min Rest Time min {/* Total time display */} Total: {displayText} ), prepTime, cookTime, restTime, totalTime, }; }, ); export default TimingModule;