|
| 1 | +import * as React from 'react' |
| 2 | +import emStyled, { CreateStyledComponent, CreateStyled } from '@emotion/styled' |
| 3 | +import { Theme } from '@emotion/react' |
| 4 | +import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system' |
| 5 | +import { BoxElements } from '@xstyled/core' |
| 6 | +import { css } from './css' |
| 7 | + |
| 8 | +const flattenArgs = (arg: any, props: any): any => { |
| 9 | + if (typeof arg === 'function') return flattenArgs(arg(props), props) |
| 10 | + if (Array.isArray(arg)) return arg.map((arg) => flattenArgs(arg, props)) |
| 11 | + return arg |
| 12 | +} |
| 13 | + |
| 14 | +const getCreateStyle = (baseCreateStyle: any, generator?: StyleGenerator) => { |
| 15 | + if (!generator) { |
| 16 | + return (strings: any, ...args: any) => |
| 17 | + baseCreateStyle((props: any) => |
| 18 | + css(strings, ...flattenArgs(args, props))(props), |
| 19 | + ) |
| 20 | + } |
| 21 | + return (strings: any, ...args: any) => { |
| 22 | + if (Array.isArray(strings)) { |
| 23 | + // The tagged template literal should receive an equal number of |
| 24 | + // additional separators. |
| 25 | + strings = [...strings, '\n'] |
| 26 | + } |
| 27 | + args = [...args, generator] |
| 28 | + return baseCreateStyle((props: any) => |
| 29 | + css(strings, ...flattenArgs(args, props))(props), |
| 30 | + ) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +type BoxStyledTags<TProps extends object> = { |
| 35 | + [Tag in keyof BoxElements]: CreateStyledComponent< |
| 36 | + TProps & { as?: React.ElementType; theme?: Theme }, |
| 37 | + JSX.IntrinsicElements[BoxElements[Tag]] |
| 38 | + > |
| 39 | +} |
| 40 | + |
| 41 | +export interface CreateXStyled<TProps extends object = {}> |
| 42 | + extends CreateStyled, |
| 43 | + BoxStyledTags<TProps> {} |
| 44 | + |
| 45 | +const createShouldForwardProp = ( |
| 46 | + generator: StyleGenerator, |
| 47 | +): ((prop: string) => boolean) => { |
| 48 | + const propSet = new Set<string>(generator.meta.props) |
| 49 | + return (prop: string) => |
| 50 | + prop !== 'as' && !prop.startsWith('$') && !propSet.has(prop) |
| 51 | +} |
| 52 | + |
| 53 | +export const createBaseStyled = <TGen extends StyleGenerator>( |
| 54 | + generator?: TGen, |
| 55 | +): CreateXStyled<StyleGeneratorProps<TGen>> => { |
| 56 | + const defaultOptions = generator |
| 57 | + ? { |
| 58 | + shouldForwardProp: createShouldForwardProp(generator), |
| 59 | + } |
| 60 | + : {} |
| 61 | + return ((component: any, options: any) => |
| 62 | + getCreateStyle( |
| 63 | + emStyled(component, { ...defaultOptions, ...options }), |
| 64 | + generator, |
| 65 | + )) as CreateXStyled<StyleGeneratorProps<TGen>> |
| 66 | +} |
| 67 | + |
| 68 | +export const createStyled = <TGen extends StyleGenerator>( |
| 69 | + generator: TGen, |
| 70 | +): CreateXStyled<StyleGeneratorProps<TGen>> => { |
| 71 | + const styled = createBaseStyled() |
| 72 | + const xstyled = createBaseStyled(generator) |
| 73 | + styled.box = xstyled('div') |
| 74 | + Object.keys(emStyled).forEach((key) => { |
| 75 | + // @ts-ignore |
| 76 | + styled[key] = styled(key) |
| 77 | + // @ts-ignore |
| 78 | + styled[`${key}Box`] = xstyled(key) |
| 79 | + }) |
| 80 | + return styled |
| 81 | +} |
0 commit comments