/// /** * Rating Module - Pattern for 1-5 star ratings * * A composable pattern that can be used standalone or embedded in containers * like Record. Provides interactive star rating with toggle-off support. */ import { Cell, computed, type Default, handler, NAME, recipe, UI, } from "commontools"; import type { ModuleMetadata } from "./container-protocol.ts"; // ===== Self-Describing Metadata ===== export const MODULE_METADATA: ModuleMetadata = { type: "rating", label: "Rating", icon: "\u{2B50}", // star emoji schema: { rating: { type: "number", minimum: 1, maximum: 5, description: "Rating 1-5", }, }, fieldMapping: ["rating"], }; // ===== Types ===== export interface RatingModuleInput { /** Rating from 1-5 stars */ rating: Default; } // ===== Handlers ===== // Handler for rating selection - value is passed in context const setRating = handler< unknown, { rating: Cell; value: number } >((_event, { rating, value }) => { const current = rating.get(); // Toggle off if clicking the same rating rating.set(current === value ? null : value); }); // ===== The Pattern ===== export const RatingModule = recipe( "RatingModule", ({ rating }) => { const displayText = computed(() => rating.get() ? `${rating.get()}/5` : "Not rated" ); return { [NAME]: computed(() => `${MODULE_METADATA.icon} Rating: ${displayText}`), [UI]: ( {[1, 2, 3, 4, 5].map((value, index) => ( ))}
{displayText}
), rating, }; }, ); export default RatingModule;