///
/**
* 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;