@pandabox/define-recipe
Version:
End-to-end type-safe theming for PandaCSS
177 lines (175 loc) • 6.71 kB
JavaScript
// src/define-recipe.ts
import deepmerge from "lodash.merge";
function defineRecipe(config) {
return Object.assign(
{
extend: function(variants) {
return defineRecipe(Object.assign({}, config, deepmerge({ variants: config.variants }, { variants })));
},
merge: function(extension) {
return defineRecipe(deepmerge({}, config, extension));
},
pick: function(...keys) {
return defineRecipe(
Object.assign({}, config, {
variants: keys.reduce((acc, key) => ({ ...acc, [key]: config.variants?.[key] }), {}),
compoundVariants: config.compoundVariants?.filter((compound) => {
return Object.keys(compound).some((variant) => keys.includes(variant));
})
})
);
},
omit: function(...keys) {
return defineRecipe(
Object.assign({}, config, {
variants: Object.entries(config.variants ?? {}).reduce((acc, [key, value]) => {
if (keys.includes(key))
return acc;
return Object.assign({}, acc, { [key]: value });
}, {}),
compoundVariants: config.compoundVariants?.filter((compound) => {
return !Object.keys(compound).some((variant) => keys.includes(variant));
})
})
);
},
cast: function() {
return config;
}
},
config
);
}
// src/define-slot-recipe.ts
import deepmerge2 from "lodash.merge";
function defineSlotRecipe(config) {
return Object.assign(
{
extend: function(variants) {
return defineSlotRecipe(Object.assign({}, config, deepmerge2({ variants: config.variants }, { variants })));
},
merge: function(extension) {
return defineSlotRecipe(deepmerge2({}, config, extension));
},
pick: function(...keys) {
return defineSlotRecipe(
Object.assign({}, config, {
variants: keys.reduce((acc, key) => ({ ...acc, [key]: config.variants?.[key] }), {}),
compoundVariants: config.compoundVariants?.filter((compound) => {
return Object.keys(compound).some((variant) => keys.includes(variant));
})
})
);
},
omit: function(...keys) {
return defineSlotRecipe(
Object.assign({}, config, {
variants: Object.entries(config.variants ?? {}).reduce((acc, [key, value]) => {
if (keys.includes(key))
return acc;
return Object.assign({}, acc, { [key]: value });
}, {}),
compoundVariants: config.compoundVariants?.filter((compound) => {
return !Object.keys(compound).some((variant) => keys.includes(variant));
})
})
);
},
cast: function() {
return config;
},
slot: {
add: function(...slots) {
return defineSlotRecipe(Object.assign({}, config, { slots: [...config.slots ?? [], ...slots] }));
},
pick: function(...keys) {
const { slots = [], compoundVariants = [] } = config;
const pickedSlots = slots.filter((slot) => keys.includes(slot));
const pickedVariants = {};
const variants = config.variants ?? {};
for (const [vName, variantRecord] of Object.entries(variants)) {
pickedVariants[vName] ||= {};
for (const [vKey, bySlots] of Object.entries(variantRecord)) {
pickedVariants[vName][vKey] ||= {};
for (const [vSlot, styles] of Object.entries(bySlots)) {
if (keys.includes(vSlot)) {
pickedVariants[vName][vKey][vSlot] = styles;
}
}
}
}
const pickedCompoundVariants = compoundVariants.filter((compound) => {
return !Object.keys(compound).some((variant) => keys.includes(variant));
});
return defineSlotRecipe({
...config,
slots: pickedSlots,
variants: pickedVariants,
compoundVariants: pickedCompoundVariants
});
},
omit: function(...keys) {
const { slots = [], compoundVariants = [] } = config;
const pickedSlots = slots.filter((slot) => !keys.includes(slot));
const pickedVariants = {};
const variants = config.variants ?? {};
for (const [vName, variantRecord] of Object.entries(variants)) {
if (!keys.includes(vName)) {
pickedVariants[vName] ||= {};
for (const [vKey, bySlots] of Object.entries(variantRecord)) {
pickedVariants[vName][vKey] ||= {};
for (const [vSlot, styles] of Object.entries(bySlots)) {
if (!keys.includes(vSlot)) {
pickedVariants[vName][vKey][vSlot] = styles;
}
}
}
}
}
const pickedCompoundVariants = compoundVariants.filter((compound) => {
return !Object.keys(compound).some((variant) => keys.includes(variant));
});
return defineSlotRecipe({
...config,
slots: pickedSlots,
variants: pickedVariants,
compoundVariants: pickedCompoundVariants
});
},
assignTo: function(slot, recipe) {
const base = config.base ?? {};
const recipeVariants = recipe.variants ?? {};
const variants = config.variants ?? {};
const overridenVariants = cloneDeep(variants);
for (const [vName, variantRecord] of Object.entries(variants)) {
overridenVariants[vName] ||= {};
for (const [vKey, bySlots] of Object.entries(variantRecord)) {
overridenVariants[vName][vKey] ||= {};
for (const [vSlot, styles] of Object.entries(bySlots)) {
if (vSlot === slot) {
const recipeStyles = recipeVariants[vName]?.[vKey];
overridenVariants[vName][vKey][vSlot] = Object.assign({}, styles, recipeStyles);
}
}
}
}
const overridenBase = cloneDeep(base);
for (const [vSlot, styles] of Object.entries(base)) {
if (vSlot === slot) {
overridenBase[vSlot] = Object.assign({}, styles, recipe.base);
}
}
return defineSlotRecipe({ ...config, variants: overridenVariants, base: overridenBase });
}
}
},
config
);
}
function cloneDeep(variants) {
return JSON.parse(JSON.stringify(variants));
}
export {
defineRecipe,
defineSlotRecipe
};