Skip to content

Commit

Permalink
fix: improve Styled type to accept optional props type
Browse files Browse the repository at this point in the history
  • Loading branch information
FezVrasta committed Oct 26, 2019
1 parent 2fecf15 commit ee69409
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 111 deletions.
1 change: 1 addition & 0 deletions .changeset/red-chefs-camp.md
@@ -1,5 +1,6 @@
---
'@emotion/styled-base': patch
'@emotion/styled': patch
---

Fixed package's Flow types
21 changes: 21 additions & 0 deletions packages/styled-base/flow-tests/flow.js
@@ -0,0 +1,21 @@
// @flow
import * as React from 'react'
import createStyled from '../src'
import type { CreateStyledComponent } from '../src/utils'

export const valid: CreateStyledComponent = createStyled('div')

// $FlowExpectError: we can't cast a StyledComponent to string
export const invalid: string = createStyled('div')

const styled = createStyled('div')
type Props = { color: string }
const Div = styled<Props>({ color: props => props.color })

export const validProp = <Div color="red" />

// $FlowExpectError: color property should be a string
export const invalidProp = <Div color={2} />

// $FlowExpectError: we don't expose the private StyledComponent properties
export const invalidPropAccess = styled().__emotion_base
190 changes: 96 additions & 94 deletions packages/styled-base/src/index.js
Expand Up @@ -5,8 +5,7 @@ import {
getDefaultShouldForwardProp,
type StyledOptions,
type CreateStyled,
type CreateStyledComponent,
type StyledComponent
type PrivateStyledComponent
} from './utils'
import { withEmotionCache, ThemeContext } from '@emotion/core'
import { getRegisteredStyles, insertStyles } from '@emotion/utils'
Expand Down Expand Up @@ -51,7 +50,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
shouldForwardProp || getDefaultShouldForwardProp(baseTag)
const shouldUseAs = !defaultShouldForwardProp('as')

return (function() {
return function<P>(): PrivateStyledComponent<P> {
let args = arguments
let styles =
isReal && tag.__emotion_styles !== undefined
Expand All @@ -78,104 +77,106 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
}
}

const Styled: StyledComponent = withEmotionCache((props, context, ref) => {
return (
<ThemeContext.Consumer>
{theme => {
const finalTag = (shouldUseAs && props.as) || baseTag

let className = ''
let classInterpolations = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
for (let key in props) {
mergedProps[key] = props[key]
const Styled: PrivateStyledComponent<P> = withEmotionCache(
(props, context, ref) => {
return (
<ThemeContext.Consumer>
{theme => {
const finalTag = (shouldUseAs && props.as) || baseTag

let className = ''
let classInterpolations = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
for (let key in props) {
mergedProps[key] = props[key]
}
mergedProps.theme = theme
}

if (typeof props.className === 'string') {
className = getRegisteredStyles(
context.registered,
classInterpolations,
props.className
)
} else if (props.className != null) {
className = `${props.className} `
}
mergedProps.theme = theme
}

if (typeof props.className === 'string') {
className = getRegisteredStyles(
const serialized = serializeStyles(
styles.concat(classInterpolations),
context.registered,
classInterpolations,
props.className
mergedProps
)
} else if (props.className != null) {
className = `${props.className} `
}

const serialized = serializeStyles(
styles.concat(classInterpolations),
context.registered,
mergedProps
)
const rules = insertStyles(
context,
serialized,
typeof finalTag === 'string'
)
className += `${context.key}-${serialized.name}`
if (targetClassName !== undefined) {
className += ` ${targetClassName}`
}

const finalShouldForwardProp =
shouldUseAs && shouldForwardProp === undefined
? getDefaultShouldForwardProp(finalTag)
: defaultShouldForwardProp

let newProps = {}

for (let key in props) {
if (shouldUseAs && key === 'as') continue

if (
// $FlowFixMe
finalShouldForwardProp(key)
) {
newProps[key] = props[key]
const rules = insertStyles(
context,
serialized,
typeof finalTag === 'string'
)
className += `${context.key}-${serialized.name}`
if (targetClassName !== undefined) {
className += ` ${targetClassName}`
}
}

newProps.className = className
const finalShouldForwardProp =
shouldUseAs && shouldForwardProp === undefined
? getDefaultShouldForwardProp(finalTag)
: defaultShouldForwardProp

newProps.ref = ref || props.innerRef
if (process.env.NODE_ENV !== 'production' && props.innerRef) {
console.error(
'`innerRef` is deprecated and will be removed in a future major version of Emotion, please use the `ref` prop instead' +
(identifierName === undefined
? ''
: ` in the usage of \`${identifierName}\``)
)
}

const ele = React.createElement(finalTag, newProps)
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
let newProps = {}

for (let key in props) {
if (shouldUseAs && key === 'as') continue

if (
// $FlowFixMe
finalShouldForwardProp(key)
) {
newProps[key] = props[key]
}
}
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
)
}
return ele
}}
</ThemeContext.Consumer>
)
})

newProps.className = className

newProps.ref = ref || props.innerRef
if (process.env.NODE_ENV !== 'production' && props.innerRef) {
console.error(
'`innerRef` is deprecated and will be removed in a future major version of Emotion, please use the `ref` prop instead' +
(identifierName === undefined
? ''
: ` in the usage of \`${identifierName}\``)
)
}

const ele = React.createElement(finalTag, newProps)
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
}
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
)
}
return ele
}}
</ThemeContext.Consumer>
)
}
)

Styled.displayName =
identifierName !== undefined
Expand Down Expand Up @@ -216,8 +217,9 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
: options
)(...styles)
}

return Styled
}: CreateStyledComponent)
}
}

export default createStyled
21 changes: 12 additions & 9 deletions packages/styled-base/src/utils.js
Expand Up @@ -11,19 +11,20 @@ export type StyledOptions = {
target?: string
}

export type StyledComponent = {
(Interpolations): React$Node,
export type StyledComponent<P> = React.StatelessFunctionalComponent<P> & {
defaultProps: any,
toString: () => string,
withComponent: (
nextTag: ElementType,
nextOptions?: StyledOptions
) => StyledComponent,
displayName: string,
defaultProps: any,
__emotion_real: StyledComponent,
) => StyledComponent<P>
}

export type PrivateStyledComponent<P> = StyledComponent<P> & {
__emotion_real: StyledComponent<P>,
__emotion_base: any,
__emotion_styles: any,
__emotion_forwardProp: any,
toString: () => string
__emotion_forwardProp: any
}

const testOmitPropsOnStringTag = isPropValid
Expand All @@ -39,7 +40,9 @@ export const getDefaultShouldForwardProp = (tag: React.ElementType) =>
? testOmitPropsOnStringTag
: testOmitPropsOnComponent

export type CreateStyledComponent = (...args: Interpolations) => StyledComponent
export type CreateStyledComponent = <P>(
...args: Interpolations
) => StyledComponent<P>

export type CreateStyled = {
(tag: React.ElementType, options?: StyledOptions): CreateStyledComponent,
Expand Down
8 changes: 0 additions & 8 deletions packages/styled-base/tests/flow.js

This file was deleted.

0 comments on commit ee69409

Please sign in to comment.