UNPKG

4.03 kBJavaScriptView Raw
1/* eslint-disable react/prop-types, react/require-default-props */
2
3import * as React from 'react'
4import isPropValid from '@emotion/is-prop-valid'
5import {ThemeContext as DefaultThemeContext} from 'theming'
6
7import createUseStyles from './createUseStyles'
8
9// eslint-disable-next-line no-unused-vars
10const parseStyles = (args) => {
11 const dynamicStyles = []
12 let staticStyle
13 const labels = []
14
15 // Not using ...rest to optimize perf.
16 for (const key in args) {
17 const style = args[key]
18 if (!style) continue
19 if (typeof style === 'function') {
20 dynamicStyles.push(style)
21 } else {
22 if (!staticStyle) staticStyle = {}
23 Object.assign(staticStyle, style)
24 const {label} = staticStyle
25 if (label) {
26 if (labels.indexOf(label) === -1) labels.push(label)
27 }
28 }
29 }
30
31 const styles = {}
32 const label = labels.length === 0 ? 'sc' : labels.join('-')
33
34 if (staticStyle) {
35 // Label should not leak to the core.
36 if ('label' in staticStyle) delete staticStyle.label
37 styles[label] = staticStyle
38 }
39
40 // When there is only one function rule, we don't need to wrap it.
41 if (dynamicStyles.length === 1) {
42 styles.scd = dynamicStyles[0]
43 }
44
45 // We create a new function rule which will call all other function rules
46 // and merge the styles they return.
47 if (dynamicStyles.length > 1) {
48 styles.scd = (props) => {
49 const merged = {}
50 for (let i = 0; i < dynamicStyles.length; i++) {
51 const dynamicStyle = dynamicStyles[i](props)
52 if (dynamicStyle) Object.assign(merged, dynamicStyle)
53 }
54 return merged
55 }
56 }
57
58 return {styles, label}
59}
60
61const shouldForwardPropSymbol = Symbol('react-jss-styled')
62
63const getShouldForwardProp = (tagOrComponent, options) => {
64 const {shouldForwardProp} = options
65 const childShouldForwardProp = tagOrComponent[shouldForwardPropSymbol]
66 let finalShouldForwardProp = shouldForwardProp || childShouldForwardProp
67 if (shouldForwardProp && childShouldForwardProp) {
68 finalShouldForwardProp = (prop) => childShouldForwardProp(prop) && shouldForwardProp(prop)
69 }
70 return finalShouldForwardProp
71}
72
73const getChildProps = (props, shouldForwardProp, isTag) => {
74 const childProps = {}
75
76 for (const prop in props) {
77 if (shouldForwardProp) {
78 if (shouldForwardProp(prop) === true) {
79 childProps[prop] = props[prop]
80 }
81 continue
82 }
83
84 // We don't want to pass non-dom props to the DOM.
85 if (isTag) {
86 if (isPropValid(prop)) {
87 childProps[prop] = props[prop]
88 }
89 continue
90 }
91
92 childProps[prop] = props[prop]
93 }
94
95 return childProps
96}
97
98// eslint-disable-next-line no-unused-vars
99const configureStyled = (tagOrComponent, options = {}) => {
100 const {theming} = options
101 const isTag = typeof tagOrComponent === 'string'
102
103 const ThemeContext = theming ? theming.context : DefaultThemeContext
104 const shouldForwardProp = getShouldForwardProp(tagOrComponent, options)
105 const {shouldForwardProp: _, ...hookOptions} = options
106
107 return function createStyledComponent() {
108 // eslint-disable-next-line prefer-rest-params
109 const {styles, label} = parseStyles(arguments)
110 const useStyles = createUseStyles(styles, hookOptions)
111
112 const Styled = (props) => {
113 const {as, className} = props
114 const theme = React.useContext(ThemeContext)
115 const propsWithTheme = Object.assign({theme}, props)
116 const classes = useStyles(propsWithTheme)
117 const childProps = getChildProps(props, shouldForwardProp, isTag)
118
119 const classNames = `${classes[label] || classes.sc || ''} ${classes.scd || ''}`.trim()
120 childProps.className = className ? `${className} ${classNames}` : classNames
121
122 if (!isTag && shouldForwardProp) {
123 tagOrComponent[shouldForwardPropSymbol] = shouldForwardProp
124 }
125
126 if (isTag && as) {
127 return React.createElement(as, childProps)
128 }
129
130 return React.createElement(tagOrComponent, childProps)
131 }
132
133 return Styled
134 }
135}
136
137export default configureStyled