diff --git a/.changeset/rotten-onions-hide.md b/.changeset/rotten-onions-hide.md new file mode 100644 index 000000000..b61eb8a0e --- /dev/null +++ b/.changeset/rotten-onions-hide.md @@ -0,0 +1,5 @@ +--- +'@emotion/styled': minor +--- + +Custom `shouldForwardProp` is being preserved now when using `.withComponent`. Also, when passing an additional `shouldForwardProp` in `.withComponent`'s options (like this: `SomeComponent.withComponent('span', { shouldForwardProp })`) it's being composed with the potentially existing `shouldForwardProp`. diff --git a/packages/styled/__tests__/styled.js b/packages/styled/__tests__/styled.js index ddcc9509c..3cd6047fb 100644 --- a/packages/styled/__tests__/styled.js +++ b/packages/styled/__tests__/styled.js @@ -640,6 +640,7 @@ describe('styled', () => { const tree = renderer.create().toJSON() expect(tree).toMatchSnapshot() }) + test('theming', () => { const Div = styled.div` color: ${props => props.theme.primary}; diff --git a/packages/styled/src/base.js b/packages/styled/src/base.js index 8d9e8c4e2..754059037 100644 --- a/packages/styled/src/base.js +++ b/packages/styled/src/base.js @@ -3,6 +3,7 @@ import * as React from 'react' import type { ElementType } from 'react' import { getDefaultShouldForwardProp, + composeShouldForwardProps, type StyledOptions, type CreateStyled, type PrivateStyledComponent @@ -26,27 +27,18 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => { ) } } + const isReal = tag.__emotion_real === tag + const baseTag = (isReal && tag.__emotion_base) || tag + let identifierName - let shouldForwardProp let targetClassName if (options !== undefined) { identifierName = options.label targetClassName = options.target - shouldForwardProp = - tag.__emotion_forwardProp && options.shouldForwardProp - ? propName => - tag.__emotion_forwardProp(propName) && - // $FlowFixMe - options.shouldForwardProp(propName) - : options.shouldForwardProp } - const isReal = tag.__emotion_real === tag - const baseTag = (isReal && tag.__emotion_base) || tag - if (typeof shouldForwardProp !== 'function' && isReal) { - shouldForwardProp = tag.__emotion_forwardProp - } - let defaultShouldForwardProp = + const shouldForwardProp = composeShouldForwardProps(tag, options, isReal) + const defaultShouldForwardProp = shouldForwardProp || getDefaultShouldForwardProp(baseTag) const shouldUseAs = !defaultShouldForwardProp('as') @@ -196,12 +188,12 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => { nextTag: ElementType, nextOptions?: StyledOptions ) => { - return createStyled( - nextTag, - nextOptions !== undefined - ? { ...(options || {}), ...nextOptions } - : options - )(...styles) + return createStyled(nextTag, { + ...options, + // $FlowFixMe + ...nextOptions, + shouldForwardProp: composeShouldForwardProps(Styled, nextOptions, true) + })(...styles) } return Styled diff --git a/packages/styled/src/utils.js b/packages/styled/src/utils.js index b533e46b6..274bcd311 100644 --- a/packages/styled/src/utils.js +++ b/packages/styled/src/utils.js @@ -38,6 +38,29 @@ export const getDefaultShouldForwardProp = (tag: ElementType) => ? testOmitPropsOnStringTag : testOmitPropsOnComponent +export const composeShouldForwardProps = ( + tag: PrivateStyledComponent, + options: StyledOptions | void, + isReal: boolean +) => { + let shouldForwardProp + if (options) { + const optionsShouldForwardProp = options.shouldForwardProp + shouldForwardProp = + tag.__emotion_forwardProp && optionsShouldForwardProp + ? (propName: string) => + tag.__emotion_forwardProp(propName) && + optionsShouldForwardProp(propName) + : optionsShouldForwardProp + } + + if (typeof shouldForwardProp !== 'function' && isReal) { + shouldForwardProp = tag.__emotion_forwardProp + } + + return shouldForwardProp +} + export type CreateStyledComponent = ( ...args: Interpolations ) => StyledComponent diff --git a/packages/styled/test/__snapshots__/prop-filtering.test.js.snap b/packages/styled/test/__snapshots__/prop-filtering.test.js.snap index 2b0428ae5..5e838d732 100644 --- a/packages/styled/test/__snapshots__/prop-filtering.test.js.snap +++ b/packages/styled/test/__snapshots__/prop-filtering.test.js.snap @@ -1,5 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`composes shouldForwardProp on composed styled components 1`] = ` +
+`; + exports[`custom shouldForwardProp works 1`] = ` .emotion-0, .emotion-0 * { @@ -124,3 +131,61 @@ Array [ />, ] `; + +exports[`withComponent inherits explicit shouldForwardProp 1`] = ` +.emotion-0 { + color: hotpink; +} + + +`; + +exports[`withComponent inherits explicit shouldForwardProp from flattened component 1`] = ` +.emotion-0 { + color: hotpink; + background-color: blue; +} + + +`; + +exports[`withComponent should accept shouldForwardProp 1`] = ` +.emotion-0 { + color: hotpink; +} + + +`; + +exports[`withComponent should compose shouldForwardProp 1`] = ` +.emotion-0 { + color: hotpink; +} + + +`; + +exports[`withComponent should compose shouldForwardProp with a flattened component 1`] = ` +.emotion-0 { + color: hotpink; +} + + +`; diff --git a/packages/styled/test/prop-filtering.test.js b/packages/styled/test/prop-filtering.test.js index 22f4c0dfc..34e4be0e7 100644 --- a/packages/styled/test/prop-filtering.test.js +++ b/packages/styled/test/prop-filtering.test.js @@ -6,17 +6,16 @@ import styled from '@emotion/styled' test('composes shouldForwardProp on composed styled components', () => { const StyledDiv = styled('div', { - shouldForwardProp: prop => prop === 'forwardMe' + shouldForwardProp: prop => prop !== 'foo' })() const ComposedDiv = styled(StyledDiv, { - shouldForwardProp: () => true + shouldForwardProp: prop => prop !== 'bar' })() - const tree = renderer.create().toJSON() + const tree = renderer.create().toJSON() - expect(tree.props.filterMe).toBeUndefined() - expect(tree.props.forwardMe).toBeDefined() + expect(tree).toMatchSnapshot() }) test('custom shouldForwardProp works', () => { @@ -470,3 +469,68 @@ test('prop filtering on composed styled components that are string tags', () => expect(tree).toMatchSnapshot() }) + +test('withComponent inherits explicit shouldForwardProp', () => { + const SomeComponent = styled('div', { + shouldForwardProp: prop => prop === 'foo' + })` + color: hotpink; + ` + const AnotherComponent = SomeComponent.withComponent('span') + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +}) + +test('withComponent inherits explicit shouldForwardProp from flattened component', () => { + const SomeComponent = styled('div', { + shouldForwardProp: prop => prop === 'foo' + })` + color: hotpink; + ` + const AnotherComponent = styled(SomeComponent)` + background-color: blue; + ` + const YetAnotherComponent = AnotherComponent.withComponent('span') + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +}) + +test('withComponent should accept shouldForwardProp', () => { + const SomeComponent = styled('div')` + color: hotpink; + ` + const AnotherComponent = SomeComponent.withComponent('span', { + shouldForwardProp: prop => prop === 'xyz' + }) + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +}) + +test('withComponent should compose shouldForwardProp', () => { + const SomeComponent = styled('div', { + shouldForwardProp: prop => prop !== 'foo' + })` + color: hotpink; + ` + const AnotherComponent = SomeComponent.withComponent('span', { + shouldForwardProp: prop => prop !== 'bar' + }) + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +}) + +test('withComponent should compose shouldForwardProp with a flattened component', () => { + const SomeComponent = styled('div', { + shouldForwardProp: prop => prop !== 'foo' + })` + color: hotpink; + ` + const AnotherComponent = styled(SomeComponent)` + background-color: blue; + ` + const YetAnotherComponent = SomeComponent.withComponent('span', { + shouldForwardProp: prop => prop !== 'bar' + }) + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +})