@pandabox/define-recipe
Version:
End-to-end type-safe theming for PandaCSS
323 lines (280 loc) • 7.82 kB
Markdown
# @pandabox/define-recipe
Extend, pick, omit and merge config Recipes/Slots Recipes to easily compose them together.
### Config Recipe
The `defineRecipe` method will now return a `RecipeBuilder` object instead of a `RecipeConfig` object. The
`RecipeBuilder` object has the following methods:
- `extend`: add additional variants to or override variants of a recipe.
```ts
const button = defineRecipe({
className: 'btn',
variants: {
variant: { primary: { color: 'red' } },
},
}).extend({
variant: {
primary: { px: 2 },
secondary: { color: 'blue' },
},
})
```
resulting in:
```json
{
"className": "btn",
"variants": {
"variant": {
"primary": { "color": "red", "px": 2 },
"secondary": { "color": "blue" }
}
}
}
```
- `merge`: deep merge a recipe with another recipe. It takes a partial `RecipeConfig` object as an argument, which can
include new (or existing) variants, compound variants, and default variants.
```ts
const button = defineRecipe({
className: 'btn',
variants: {
variant: { primary: { color: 'red' } },
},
}).merge({
className: 'custom-btn',
variants: {
secondary: { color: 'blue' },
},
defaultVariants: {
variant: 'secondary',
},
})
```
resulting in:
```json
{
"className": "custom-btn",
"variants": {
"variant": {
"primary": { "color": "red" },
"secondary": { "color": "blue" }
}
},
"defaultVariants": {
"variant": "secondary"
}
}
```
- `pick`: pick only specified variants from a recipe. It takes a list of variant keys as arguments and returns a new
`RecipeBuilder` object with only the specified variants. This will also filter out any compound variants that include
any of the omitted variants.
```ts
const button = defineRecipe({
className: 'btn',
variants: {
variant: { primary: { color: 'red' } },
size: { small: { px: 2 }, large: { px: 4 } },
},
}).pick('size')
```
resulting in:
```json
{
"className": "btn",
"variants": {
"variant": {
"size": {
"small": { "px": 2 },
"large": { "px": 4 }
}
}
}
}
```
- `omit`: omit specified variants from a recipe. It takes a list of variant keys as arguments and returns a new
`RecipeBuilder` object without the specified variants. This will also filter out any compound variants that include
any of the omitted variants.
```ts
const button = defineRecipe({
className: 'btn',
variants: {
variant: { primary: { color: 'red' } },
size: { small: { px: 2 }, large: { px: 4 } },
},
}).omit('size')
```
resulting in:
```json
{
"className": "btn",
"variants": {
"variant": {
"primary": { "color": "red" }
}
}
}
```
- `cast`: make the recipe generic to simplify the typings. It returns a new `RecipeConfig` object with the final
computed variants, without the `RecipeBuilder` methods.
Each of these methods return a new `RecipeBuilder` object, so they can be chained together.
### Config Slot Recipe
The `defineSlotRecipe` method will now return a `SlotRecipeBuilder` object instead of a `SlotRecipeConfig` object. The
`SlotRecipeBuilder` object has the same following methods as the `RecipeBuilder` object: `extend`, `merge`, `pick`, and
`omit`.
In addition, the `SlotRecipeBuilder` object has an object property called `slots` that is a `SlotRecipeBuilder`, which
has the following methods:
- `add`: add additional slots to a slot recipe. It takes a list of slot names as arguments and returns a new
`SlotRecipeBuilder` object with the updated slots.
```ts
const card = defineSlotRecipe({
className: 'card',
slots: ['root', 'input', 'icon'],
variants: {
variant: {
subtle: { root: { color: 'blue.100' } },
solid: { root: { color: 'blue.100' } },
},
size: {
sm: { root: { fontSize: 'sm' } },
md: { root: { fontSize: 'md' } },
},
},
}).slot.add('label')
```
resulting in:
```json
{
"className": "card",
"slots": ["root", "input", "icon", "label"],
"variants": {
"variant": {
"subtle": { "root": { "color": "blue.100" } },
"solid": { "root": { "color": "blue.100" } }
},
"size": {
"sm": { "root": { "fontSize": "sm" } },
"md": { "root": { "fontSize": "md" } }
}
}
}
```
- `pick`: pick only specified slots from a slot recipe. It takes a list of slot keys as arguments and returns a new
`SlotRecipeBuilder` object with only the specified slots. This will also filter out any styles defined in a slot that
is not picked, as well as any compound variants that include any of the omitted slots.
```ts
const card = defineSlotRecipe({
className: 'card',
slots: ['root', 'input', 'icon'],
variants: {
variant: {
subtle: { root: { color: 'blue.100' } },
solid: { input: { color: 'blue.100' } },
},
size: {
sm: { root: { fontSize: 'sm' } },
md: { input: { fontSize: 'md' } },
},
},
}).slot.pick('input')
```
resulting in:
```json
{
"className": "card",
"slots": ["input"],
"variants": {
"variant": {
"solid": { "input": { "color": "blue.100" } }
},
"size": {
"md": { "input": { "fontSize": "md" } }
}
}
}
```
- `omit`: omit specified slots from a slot recipe. It takes a list of slot keys as arguments and returns a new
`SlotRecipeBuilder` object without the specified slots. This will also filter out any styles defined in a slot that is
not picked, as well as any compound variants that include any of the omitted slots.
```ts
const card = defineSlotRecipe({
className: 'card',
slots: ['root', 'input', 'icon'],
variants: {
variant: {
subtle: { root: { color: 'blue.100' } },
solid: { input: { color: 'blue.100' } },
},
size: {
sm: { root: { fontSize: 'sm' } },
md: { input: { fontSize: 'md' } },
},
},
}).slot.omit('input')
```
resulting in:
```json
{
"className": "card",
"slots": ["root", "icon"],
"variants": {
"variant": {
"subtle": { "root": { "color": "blue.100" } }
},
"size": {
"sm": { "root": { "fontSize": "sm" } }
}
}
}
```
- `assignTo`: assign a simple (without slots) recipe to a slot of the current slot recipe. It takes a slot name and a
recipe config as arguments and returns a new `SlotRecipeBuilder` object with the updated slot recipe. If a slot name
already has styles defined in a matching (both defined in the simple recipe to assign from and the current slot recipe
being assigned to) variant, the styles will be merged with the existing slot recipe, with priority given to the styles
defined in the simple recipe to assign from.
```ts
const button = defineRecipe({
className: 'btn',
variants: {
variant: {
outline: { color: 'green.100' },
empty: { border: 'none' },
},
size: {
lg: { fontSize: 'xl', h: '10' },
},
},
})
const card = defineSlotRecipe({
className: 'card',
slots: ['root', 'input', 'icon'],
variants: {
variant: {
subtle: { root: { color: 'blue.100' } },
solid: { input: { color: 'blue.100' } },
outline: { input: { mx: 2 } },
empty: { input: {} },
},
size: {
sm: { root: { fontSize: 'sm' } },
md: { input: { fontSize: 'md' } },
},
},
}).slot.assignTo('input', button)
```
resulting in:
```json
{
"className": "card",
"slots": ["root", "input", "icon"],
"variants": {
"variant": {
"subtle": { "root": { "color": "blue.100" } },
"solid": { "input": { "color": "blue.100" } },
"outline": { "input": { "mx": 2, "color": "green.100" } },
"empty": { "input": {} }
},
"size": {
"sm": { "root": { "fontSize": "sm" } },
"md": { "input": { "fontSize": "md" } }
}
}
}
```
> Originally implemented [here](https://github.com/chakra-ui/panda/pull/1414)