import { mergeGuiProps } from "../sx/mergeGuiProps"; import type { Sx } from "../sx/sx"; import { resolveSx } from "../sx/sx"; import type { Theme } from "../theme/types"; type GuiPropRecord = Record; export type RecipeVariants = Record>>; export type RecipeSelection> = Partial< Record >; export type RecipeConfig> = { base?: Sx; variants?: Variants; defaultVariants?: RecipeSelection; compoundVariants?: Array<{ variants: RecipeSelection; sx: Sx; }>; }; function isCompoundMatch>( candidate: RecipeSelection, resolvedSelection: RecipeSelection, ) { const candidateRecord = candidate as Record; const resolvedRecord = resolvedSelection as Record; for (const [rawVariantName, rawExpectedValue] of pairs(candidateRecord)) { if (!typeIs(rawVariantName, "string") || !typeIs(rawExpectedValue, "string")) { continue; } const actualValue = resolvedRecord[rawVariantName]; if (actualValue !== rawExpectedValue) { return false; } } return true; } export function createRecipe>( config: RecipeConfig, ) { return (selection: RecipeSelection | undefined, theme: Theme): Partial => { const resolvedSelection = { ...(config.defaultVariants ?? {}), ...(selection ?? {}), } as RecipeSelection; let merged = resolveSx(config.base, theme); const variants = config.variants; if (variants) { const variantsRecord = variants as Record>>; const resolvedRecord = resolvedSelection as Record; for (const [rawVariantName, rawVariantMap] of pairs(variantsRecord)) { if (!typeIs(rawVariantName, "string") || !typeIs(rawVariantMap, "table")) { continue; } const selectedValue = resolvedRecord[rawVariantName]; if (selectedValue === undefined) { continue; } const sx = rawVariantMap[selectedValue]; merged = mergeGuiProps(merged, resolveSx(sx, theme)); } } for (const compound of config.compoundVariants ?? []) { if (!isCompoundMatch(compound.variants, resolvedSelection)) { continue; } merged = mergeGuiProps(merged, resolveSx(compound.sx, theme)); } return merged; }; }