Skip to content

Commit

Permalink
fix: styled nested composition with cva
Browse files Browse the repository at this point in the history
  • Loading branch information
astahmer committed Feb 12, 2024
1 parent 688cfbe commit e5a81d9
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 37 deletions.
8 changes: 8 additions & 0 deletions .changeset/shiny-fans-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@pandacss/generator': patch
'@pandacss/config': patch
'@pandacss/types': patch
'@pandacss/node': patch
---

Fix `styled` factory nested composition with `cva`
7 changes: 4 additions & 3 deletions packages/generator/src/artifacts/preact-jsx/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ export function generatePreactJsxFactory(ctx: Context) {
options.defaultProps,
)
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const ${componentName} = /* @__PURE__ */ forwardRef(function ${componentName}(props, ref) {
const { as: Element = Dynamic.__base__ || Dynamic, children, ...restProps } = props
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
Expand Down Expand Up @@ -66,7 +67,7 @@ export function generatePreactJsxFactory(ctx: Context) {
const name = getDisplayName(Dynamic)
${componentName}.displayName = \`${factoryName}.\${name}\`
${componentName}.__cva__ = cvaFn
${componentName}.__cva__ = __cvaFn__
${componentName}.__base__ = Dynamic
${componentName}.__shouldForwardProps__ = shouldForwardProp
Expand Down
8 changes: 4 additions & 4 deletions packages/generator/src/artifacts/qwik-jsx/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export function generateQwikJsxFactory(ctx: Context) {
options.defaultProps,
)
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const ${componentName} = function ${componentName}(props) {
const { as: Element = Dynamic.__base__ || Dynamic, children, className, ...restProps } = props
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const combinedProps = Object.assign({}, defaultProps, restProps)
const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] =
Expand Down Expand Up @@ -64,7 +64,7 @@ export function generateQwikJsxFactory(ctx: Context) {
const name = getDisplayName(Dynamic)
${componentName}.displayName = \`${factoryName}.\${name}\`
${componentName}.__cva__ = cvaFn
${componentName}.__cva__ = __cvaFn__
${componentName}.__base__ = Dynamic
${componentName}.__shouldForwardProps__ = shouldForwardProp
Expand Down
8 changes: 4 additions & 4 deletions packages/generator/src/artifacts/react-jsx/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export function generateReactJsxFactory(ctx: Context) {
options.defaultProps,
)
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const ${componentName} = /* @__PURE__ */ forwardRef(function ${componentName}(props, ref) {
const { as: Element = Dynamic.__base__ || Dynamic, children, ...restProps } = props
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])
const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
Expand Down Expand Up @@ -64,7 +64,7 @@ export function generateReactJsxFactory(ctx: Context) {
const name = getDisplayName(Dynamic)
${componentName}.displayName = \`${factoryName}.\${name}\`
${componentName}.__cva__ = cvaFn
${componentName}.__cva__ = __cvaFn__
${componentName}.__base__ = Dynamic
${componentName}.__shouldForwardProps__ = shouldForwardProp
Expand Down
17 changes: 8 additions & 9 deletions packages/generator/src/artifacts/solid-jsx/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,19 @@ export function generateSolidJsxFactory(ctx: Context) {
options.defaultProps
)
const __cvaFn__ = composeCvaFn(element.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(
element,
shouldForwardProp
)
const ${componentName} = (props) => {
const mergedProps = mergeProps(
{ as: element.__base__ || element },
defaultProps,
props
)
const __cvaFn__ = composeCvaFn(element.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(
element,
shouldForwardProp
)
const [localProps, restProps] = splitProps(mergedProps, [
'as',
'class',
Expand Down Expand Up @@ -78,7 +77,7 @@ export function generateSolidJsxFactory(ctx: Context) {
localProps.className
)
}
function cvaClass() {
const { css: cssStyles, ...propStyles } = styleProps
const cvaStyles = __cvaFn__.raw(variantProps)
Expand Down Expand Up @@ -111,7 +110,7 @@ export function generateSolidJsxFactory(ctx: Context) {
const name = getDisplayName(element)
${componentName}.displayName = \`${factoryName}.\${name}\`
${componentName}.__cva__ = cvaFn
${componentName}.__cva__ = __cvaFn__
${componentName}.__base__ = element
${componentName}.__shouldForwardProps__ = shouldForwardProp
Expand Down
13 changes: 6 additions & 7 deletions packages/generator/src/artifacts/vue-jsx/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export function generateVueJsxFactory(ctx: Context) {
)
const name = getDisplayName(Dynamic)
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const ${componentName} = defineComponent({
name: \`${factoryName}.\${name}\`,
Expand All @@ -36,9 +38,6 @@ export function generateVueJsxFactory(ctx: Context) {
as: { type: [String, Object], default: Dynamic.__base__ || Dynamic }
},
setup(props, { slots, attrs, emit }) {
const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)
const combinedProps = computed(() => Object.assign({}, defaultProps, attrs))
const splittedProps = computed(() => {
Expand All @@ -63,7 +62,7 @@ export function generateVueJsxFactory(ctx: Context) {
const vModelProps = computed(() => {
const result = {};
if (
props.as === 'input' &&
(props.type === 'checkbox' || props.type === 'radio')
Expand All @@ -86,13 +85,13 @@ export function generateVueJsxFactory(ctx: Context) {
emit('update:modelValue', value, event);
};
}
return result;
});
return () => {
const [htmlProps, forwardedProps, _variantProps, _styleProps, elementProps] = splittedProps.value
return h(
props.as,
{
Expand All @@ -109,7 +108,7 @@ export function generateVueJsxFactory(ctx: Context) {
})
${componentName}.displayName = \`${factoryName}.\${name}\`
${componentName}.__cva__ = cvaFn
${componentName}.__cva__ = __cvaFn__
${componentName}.__base__ = Dynamic
${componentName}.__shouldForwardProps__ = shouldForwardProp
Expand Down
8 changes: 4 additions & 4 deletions packages/studio/styled-system/jsx/factory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ function styledFn(Dynamic, configOrCva = {}, options = {}) {
options.defaultProps,
)

const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)

const PandaComponent = /* @__PURE__ */ forwardRef(function PandaComponent(props, ref) {
const { as: Element = Dynamic.__base__ || Dynamic, children, ...restProps } = props

const __cvaFn__ = composeCvaFn(Dynamic.__cva__, cvaFn)
const __shouldForwardProps__ = composeShouldForwardProps(Dynamic, shouldForwardProp)

const combinedProps = useMemo(() => Object.assign({}, defaultProps, restProps), [restProps])

const [htmlProps, forwardedProps, variantProps, styleProps, elementProps] = useMemo(() => {
Expand Down Expand Up @@ -53,7 +53,7 @@ function styledFn(Dynamic, configOrCva = {}, options = {}) {
const name = getDisplayName(Dynamic)

PandaComponent.displayName = `panda.${name}`
PandaComponent.__cva__ = cvaFn
PandaComponent.__cva__ = __cvaFn__
PandaComponent.__base__ = Dynamic
PandaComponent.__shouldForwardProps__ = shouldForwardProp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,83 @@ describe('styled factory - cva', () => {
)
})

test('nested composition', () => {
const StyledButton = styled('button', {
base: {
fontWeight: 'semibold',
h: '10',
},
variants: {
visual: {
solid: {
color: 'white',
},
outline: {
borderColor: 'currentColor',
},
},
},
})
const WithSize = styled(StyledButton, {
base: {
colorPalette: 'blue', // adding new key
},
variants: {
size: {
// new variant
sm: { px: '2', fontSize: '12px' },
lg: { px: '8', fontSize: '24px' },
},
},
})
const WithOverrides = styled(WithSize, {
base: {
borderWidth: '4px', // adding new key
h: '20', // overriding 1st cva
},
variants: {
visual: {
outline: {
// extend 1st cva
colorPalette: 'red',
},
},
size: {
lg: { px: '12', fontSize: '32px' }, // override 2nd cva
'2xl': { px: '20', fontSize: '40px' }, // add new one
},
},
})

const { container } = render(
<WithOverrides visual="outline" size="lg">
Click me
</WithOverrides>,
)
const { firstChild } = container as HTMLElement
expect(firstChild).toMatchInlineSnapshot(`
<button
class="font_semibold h_10 font_semibold h_20 color-palette_red border-w_4px border_currentColor px_12 fs_32px"
>
Click me
</button>
`)

expect(
render(
<WithOverrides visual="solid" size="2xl">
Click me
</WithOverrides>,
).container.firstChild,
).toMatchInlineSnapshot(`
<button
class="font_semibold h_10 font_semibold h_20 color-palette_blue border-w_4px text_white px_20 fs_40px"
>
Click me
</button>
`)
})

test('html props', () => {
const { container } = render(
<styled.div htmlWidth={123} height="123">
Expand All @@ -150,9 +227,7 @@ describe('styled factory - button recipe', () => {
test('base styles', () => {
const { container } = render(<Button>Click me</Button>)

expect(container.firstElementChild?.outerHTML).toMatchInlineSnapshot(
`"<button class="button">Click me</button>"`,
)
expect(container.firstElementChild?.outerHTML).toMatchInlineSnapshot(`"<button class="button">Click me</button>"`)
})

test('variant styles', () => {
Expand Down Expand Up @@ -238,9 +313,7 @@ describe('styled factory - button recipe', () => {
test('box pattern', () => {
const { container } = render(<Box color="red.300">Click me</Box>)

expect(container.firstElementChild?.outerHTML).toMatchInlineSnapshot(
`"<div class="text_red.300">Click me</div>"`,
)
expect(container.firstElementChild?.outerHTML).toMatchInlineSnapshot(`"<div class="text_red.300">Click me</div>"`)
})

test('stack pattern', () => {
Expand Down
67 changes: 67 additions & 0 deletions sandbox/codegen/__tests__/frameworks/qwik.styled-factory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,73 @@ describe('styled factory - cva', async () => {
)
})

test('nested composition', async () => {
const StyledButton = styled('button', {
base: {
fontWeight: 'semibold',
h: '10',
},
variants: {
visual: {
solid: {
color: 'white',
},
outline: {
borderColor: 'currentColor',
},
},
},
})
const WithSize = styled(StyledButton, {
base: {
colorPalette: 'blue', // adding new key
},
variants: {
size: {
// new variant
sm: { px: '2', fontSize: '12px' },
lg: { px: '8', fontSize: '24px' },
},
},
})
const WithOverrides = styled(WithSize, {
base: {
borderWidth: '4px', // adding new key
h: '20', // overriding 1st cva
},
variants: {
visual: {
outline: {
// extend 1st cva
colorPalette: 'red',
},
},
size: {
lg: { px: '12', fontSize: '32px' }, // override 2nd cva
'2xl': { px: '20', fontSize: '40px' }, // add new one
},
},
})

const { render, screen } = await createDOM()
await render(
<WithOverrides visual="outline" size="lg">
Click me
</WithOverrides>,
)
const container = screen.querySelector('button')!
expect(container.outerHTML).toMatchInlineSnapshot(`"<button class="font_semibold h_10 font_semibold h_20 color-palette_red border-w_4px border_currentColor px_12 fs_32px">Click me</button>"`)

const second = await createDOM()
await second.render(
<WithOverrides visual="solid" size="2xl">
Click me
</WithOverrides>,
)
const container2 = second.screen.querySelector('button')!
expect(container2.outerHTML).toMatchInlineSnapshot(`"<button class="font_semibold h_10 font_semibold h_20 color-palette_blue border-w_4px text_white px_20 fs_40px">Click me</button>"`)
})

test('html props', async () => {
const { render, screen } = await createDOM()
await render(
Expand Down
Loading

0 comments on commit e5a81d9

Please sign in to comment.