///
/**
* Birthday Module - Pattern for birthday/date of birth tracking
*
* A composable pattern that can be used standalone or embedded in containers
* like Record. Tracks birthday with separate month, day, and year fields.
*/
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: "birthday",
label: "Birthday",
icon: "\u{1F382}", // cake emoji
schema: {
birthMonth: { type: "string", description: "Birth month (1-12)" },
birthDay: { type: "string", description: "Birth day (1-31)" },
birthYear: { type: "string", description: "Birth year (YYYY)" },
},
fieldMapping: ["birthMonth", "birthDay", "birthYear"],
};
// ===== Autocomplete Items =====
const MONTH_ITEMS = [
{ value: "1", label: "January", searchAliases: ["jan", "01"] },
{ value: "2", label: "February", searchAliases: ["feb", "02"] },
{ value: "3", label: "March", searchAliases: ["mar", "03"] },
{ value: "4", label: "April", searchAliases: ["apr", "04"] },
{ value: "5", label: "May", searchAliases: ["05"] },
{ value: "6", label: "June", searchAliases: ["jun", "06"] },
{ value: "7", label: "July", searchAliases: ["jul", "07"] },
{ value: "8", label: "August", searchAliases: ["aug", "08"] },
{ value: "9", label: "September", searchAliases: ["sep", "sept", "09"] },
{ value: "10", label: "October", searchAliases: ["oct"] },
{ value: "11", label: "November", searchAliases: ["nov"] },
{ value: "12", label: "December", searchAliases: ["dec"] },
];
const DAY_ITEMS = Array.from({ length: 31 }, (_, i) => ({
value: String(i + 1),
label: String(i + 1),
searchAliases: i < 9 ? [`0${i + 1}`] : undefined,
}));
function generateYearItems(): Array<{ value: string; label: string }> {
const currentYear = new Date().getFullYear();
const years: Array<{ value: string; label: string }> = [];
for (let year = currentYear; year >= 1920; year--) {
years.push({ value: String(year), label: String(year) });
}
return years;
}
const YEAR_ITEMS = generateYearItems();
// ===== Types =====
export interface BirthdayModuleInput {
/** Birth month (1-12 as string) */
birthMonth: Default;
/** Birth day (1-31 as string) */
birthDay: Default;
/** Birth year (e.g., "1990") */
birthYear: Default;
}
// ===== The Pattern =====
export const BirthdayModule = recipe(
"BirthdayModule",
({ birthMonth, birthDay, birthYear }) => {
const getMonthName = (month: string): string => {
const monthItem = MONTH_ITEMS.find((m) => m.value === month);
return monthItem?.label || month;
};
// Compute display text for NAME
const displayText = computed(() => {
const month = birthMonth?.trim();
const day = birthDay?.trim();
const year = birthYear?.trim();
if (!month && !day && !year) return "Not set";
const parts: string[] = [];
if (month) parts.push(getMonthName(month));
if (day) parts.push(day);
if (year) parts.push(year);
return parts.join(" ");
});
return {
[NAME]: computed(() =>
`${MODULE_METADATA.icon} Birthday: ${displayText}`
),
[UI]: (
),
birthMonth,
birthDay,
birthYear,
};
},
);
export default BirthdayModule;