diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e426e26d..3e0e6cc0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,6 @@ export * from './breakpoints' export * from './colorModes' export * from './createBox' -export * from './propGetters' export * from './transform' export * from './theme' export * from './types' diff --git a/packages/core/src/propGetters.test.ts b/packages/core/src/propGetters.test.ts deleted file mode 100644 index a6589bd0..00000000 --- a/packages/core/src/propGetters.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { propGetters } from './propGetters' - -describe('#propGetters', () => { - it('handles simple values', () => { - expect(propGetters['border-radius']('10')({})).toBe('10px') - expect(propGetters['font-size']('10')({})).toBe('10px') - }) - - it('handles multiple dimensions', () => { - expect(propGetters['border-radius']('1 2')({})).toBe('1px 2px') - }) - - it('handles multiple values', () => { - expect( - propGetters['box-shadow']('theme-shadow, 1px 0 0 red')({ - theme: { shadows: { 'theme-shadow': '10px black' } }, - }), - ).toBe('10px black,1px 0 0 red') - }) - - it('handles theme', () => { - expect( - propGetters['border-radius']('md')({ theme: { radii: { md: 10 } } }), - ).toBe('10px') - expect( - propGetters['border-radius']('md md')({ theme: { radii: { md: 10 } } }), - ).toBe('10px 10px') - }) -}) diff --git a/packages/core/src/propGetters.ts b/packages/core/src/propGetters.ts deleted file mode 100644 index 6e8c8fa7..00000000 --- a/packages/core/src/propGetters.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - getSpace, - getColor, - getRadius, - getBorder, - getBorderWidth, - getBorderStyle, - getShadow, - getSize, - getZIndex, - getFont, - getFontSize, - getLineHeight, - getFontWeight, - getLetterSpacing, - getTransition, - getInset, - getAnimation, - getTimingFunction, - getTransform, - getTransitionProperty, - getDuration, - Props, -} from '@xstyled/system' - -interface Transform { - (value: any): any -} - -const getNumber = (transform: Transform): Transform => (value: any) => { - const num = Number(value) - return transform(Number.isNaN(num) ? value : num) -} - -const SPACES = /\s+/ -const getMultiDimensions = (transform: Transform): Transform => ( - value: any, -) => { - const values = value.split(SPACES) - return (p: object) => - values.map((value: any) => transform(value)(p)).join(' ') -} - -const COMMA = /\s*,\s*/ -const getMultiValues = (transform: Transform): Transform => (value: any) => { - const values = value.split(COMMA) - return (p: Props) => values.map((value: any) => transform(value)(p)).join(',') -} - -const getNumberInset = getNumber(getInset) - -const getNumberSpace = getNumber(getSpace) -const getMultiNumberSpace = getMultiDimensions(getNumberSpace) - -const getNumberBorder = getNumber(getBorder) - -const getNumberBorderWidth = getNumber(getBorderWidth) -const getMultiNumberBorderWidth = getMultiDimensions(getNumberBorderWidth) - -const getNumberSize = getNumber(getSize) - -const getMultiBorderStyle = getMultiDimensions(getBorderStyle) - -export const propGetters = { - // getSpace - margin: getMultiNumberSpace, - 'margin-top': getNumberSpace, - 'margin-bottom': getNumberSpace, - 'margin-left': getNumberSpace, - 'margin-right': getNumberSpace, - padding: getMultiNumberSpace, - 'padding-top': getNumberSpace, - 'padding-bottom': getNumberSpace, - 'padding-left': getNumberSpace, - 'padding-right': getNumberSpace, - gap: getMultiNumberSpace, - 'grid-gap': getMultiNumberSpace, - 'row-gap': getNumberSpace, - 'grid-row-gap': getNumberSpace, - 'column-gap': getNumberSpace, - 'grid-column-gap': getNumberSpace, - - // getColor - color: getColor, - 'background-color': getColor, - 'border-color': getColor, - 'border-top-color': getColor, - 'border-right-color': getColor, - 'border-bottom-color': getColor, - 'border-left-color': getColor, - 'outline-color': getColor, - fill: getColor, - stroke: getColor, - - // getRadius - 'border-radius': getMultiDimensions(getNumber(getRadius)), - 'border-top-left-radius': getMultiDimensions(getNumber(getRadius)), - 'border-top-right-radius': getMultiDimensions(getNumber(getRadius)), - 'border-bottom-right-radius': getMultiDimensions(getNumber(getRadius)), - 'border-bottom-left-radius': getMultiDimensions(getNumber(getRadius)), - - // getBorder - border: getNumberBorder, - 'border-top': getNumberBorder, - 'border-right': getNumberBorder, - 'border-bottom': getNumberBorder, - 'border-left': getNumberBorder, - - // getBorderWidth - 'border-width': getMultiNumberBorderWidth, - 'border-top-width': getNumberBorderWidth, - 'border-right-width': getNumberBorderWidth, - 'border-bottom-width': getNumberBorderWidth, - 'border-left-width': getNumberBorderWidth, - 'outline-width': getNumberBorderWidth, - - // getBorderStyle - 'border-style': getMultiBorderStyle, - 'border-top-style': getBorderStyle, - 'border-right-style': getBorderStyle, - 'border-bottom-style': getBorderStyle, - 'border-left-style': getBorderStyle, - 'outline-style': getBorderStyle, - - // getShadow - 'box-shadow': getMultiValues(getShadow), - 'text-shadow': getMultiValues(getShadow), - - // getSize - width: getNumberSize, - height: getNumberSize, - 'max-width': getNumberSize, - 'max-height': getNumberSize, - 'min-width': getNumberSize, - 'min-height': getNumberSize, - 'mask-size': getMultiValues(getNumberSize), - - // getZIndex - 'z-index': getNumber(getZIndex), - - // getFont - 'font-family': getFont, - - // getFontSize - 'font-size': getNumber(getFontSize), - - // getLineHeight - 'line-height': getNumber(getLineHeight), - - // getFontWeight - 'font-weight': getFontWeight, - - // getLetterSpacing - 'letter-spacing': getNumber(getLetterSpacing), - - // getTransition - transition: getTransition, - - // getDuration - 'transition-duration': getDuration, - 'animation-duration': getDuration, - - // getAnimation - animation: getAnimation, - - // getInset - top: getNumberInset, - right: getNumberInset, - bottom: getNumberInset, - left: getNumberInset, - - // getTimingFunction - 'animation-timing-function': getTimingFunction, - 'transition-timing-function': getTimingFunction, - - // getTransform - transform: getTransform, - - // getTransitionProperty - 'transition-property': getTransitionProperty, -} diff --git a/packages/core/src/transform.test.ts b/packages/core/src/transform.test.ts index b4e1852e..4c0dd2c6 100644 --- a/packages/core/src/transform.test.ts +++ b/packages/core/src/transform.test.ts @@ -1,4 +1,5 @@ -import { transform } from './transform' +import { system } from '@xstyled/system' +import { createTransform } from './transform' const props = { theme: { @@ -7,6 +8,8 @@ const props = { }, } +const transform = createTransform(system) + const transformToStr = (s: string) => transform(s) .map((x: Function | string) => (typeof x === 'function' ? x(props) : x)) diff --git a/packages/core/src/transform.ts b/packages/core/src/transform.ts index 811da046..5b1e9328 100644 --- a/packages/core/src/transform.ts +++ b/packages/core/src/transform.ts @@ -1,6 +1,6 @@ /* eslint-disable no-continue, no-loop-func, no-cond-assign */ +import type { StyleGenerator } from '@xstyled/system' import { mediaGetters } from './mediaGetters' -import { propGetters } from './propGetters' // prop name is an ident: word chars, underscore and dash. const PROP_CHAR = `[-\\w]` @@ -62,31 +62,33 @@ const mediaTransform = (rawValue: string) => { return values } -export const transform = (rawValue: any): any => { - if (typeof rawValue !== 'string') return rawValue - let matches - let lastIndex = 0 - const values = [] - while ((matches = MATCH_REGEXP.exec(rawValue))) { - const [, prop, colon, value, imp, semi, media, query, brace] = matches - if (media) { - values.push(rawValue.slice(lastIndex, matches.index)) - values.push(media) - mediaTransform(query).forEach((v) => values.push(v)) - values.push(brace) - lastIndex = matches.index + matches[0].length - } else { - const getter = (propGetters as any)[prop] - if (getter) { +export const createTransform = + (generator: StyleGenerator) => + (rawValue: any): any => { + if (typeof rawValue !== 'string') return rawValue + let matches + let lastIndex = 0 + const values = [] + while ((matches = MATCH_REGEXP.exec(rawValue))) { + const [, prop, colon, value, imp, semi, media, query, brace] = matches + if (media) { values.push(rawValue.slice(lastIndex, matches.index)) - values.push( - (p: object) => - `${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`, - ) + values.push(media) + mediaTransform(query).forEach((v) => values.push(v)) + values.push(brace) lastIndex = matches.index + matches[0].length + } else { + const getter = generator.meta.cssGetters[prop] + if (getter) { + values.push(rawValue.slice(lastIndex, matches.index)) + values.push( + (p: object) => + `${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`, + ) + lastIndex = matches.index + matches[0].length + } } } + values.push(rawValue.slice(lastIndex, rawValue.length)) + return values } - values.push(rawValue.slice(lastIndex, rawValue.length)) - return values -} diff --git a/packages/emotion/src/create.ts b/packages/emotion/src/create.ts new file mode 100644 index 00000000..fda2ffa8 --- /dev/null +++ b/packages/emotion/src/create.ts @@ -0,0 +1,32 @@ +import { StyleGenerator } from '@xstyled/system' +import { createCssFunction, XCSSFunction } from './createCssFunction' +import { createX, X } from './createX' +import { createStyled, XStyled } from './createStyled' +import { + createCreateGlobalStyle, + XCreateGlobalStyle, +} from './createCreateGlobalStyle' +import { createCx, Cx } from './createCx' +import { createJsx, XJsx } from './createJsx' + +interface XStyledSet { + css: XCSSFunction + x: X + styled: XStyled + createGlobalStyle: XCreateGlobalStyle + cx: Cx + jsx: XJsx +} + +export const createCss = ( + generator: TGen, +): XStyledSet => { + return { + css: createCssFunction(generator), + x: createX(generator), + styled: createStyled(generator), + createGlobalStyle: createCreateGlobalStyle(generator), + cx: createCx(generator), + jsx: createJsx(generator), + } +} diff --git a/packages/emotion/src/createCreateGlobalStyle.tsx b/packages/emotion/src/createCreateGlobalStyle.tsx new file mode 100644 index 00000000..891ec5fc --- /dev/null +++ b/packages/emotion/src/createCreateGlobalStyle.tsx @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import * as React from 'react' +import { Global, useTheme } from '@emotion/react' +import { StyleGenerator } from '@xstyled/system' +import { createCssFunction, XCSSFunction } from './createCssFunction' + +export interface XCreateGlobalStyle

{ + (...styles: Parameters): React.FC

+} + +export const createCreateGlobalStyle = ( + generator: TGen, +): XCreateGlobalStyle => { + const css = createCssFunction(generator) + return

(...styles: Parameters) => { + const GlobalStyle = (props: P) => { + const theme = useTheme() + return + } + GlobalStyle.displayName = 'GlobalStyle' + return GlobalStyle + } +} diff --git a/packages/emotion/src/createCssFunction.ts b/packages/emotion/src/createCssFunction.ts new file mode 100644 index 00000000..36a035bd --- /dev/null +++ b/packages/emotion/src/createCssFunction.ts @@ -0,0 +1,64 @@ +import { css as emCss, SerializedStyles, Theme } from '@emotion/react' +import { CSSInterpolation } from '@emotion/serialize' +import { StyleGenerator } from '@xstyled/system' +import { createTransform } from '@xstyled/core' + +const styleToString = (style: any, props: any): any => { + if (Array.isArray(style)) + return style.reduce((str, style) => str + styleToString(style, props), '') + if (typeof style === 'function') return styleToString(style(props), props) + return style +} + +interface Props { + theme?: Theme +} + +interface CSSInterpolationFn { + (props: Props): CSSInterpolation +} + +export interface SerializedStylesFn { + (props: Props): SerializedStyles +} + +export interface XCSSFunction { + (fn: CSSInterpolationFn): SerializedStylesFn + (...args: CSSInterpolation[]): SerializedStylesFn + ( + strings: TemplateStringsArray, + ...rawArgs: CSSInterpolation[] + ): SerializedStylesFn + ( + strings: TemplateStringsArray | CSSInterpolation | CSSInterpolationFn, + ...rawArgs: CSSInterpolation[] + ): SerializedStylesFn +} + +export const createCssFunction = ( + generator: TGen, +): XCSSFunction => { + const transform = createTransform(generator) + return (( + strings: TemplateStringsArray | CSSInterpolation | CSSInterpolationFn, + ...rawArgs: CSSInterpolation[] + ): SerializedStylesFn => { + return (props: Props): SerializedStyles => { + const emCssArgs = + typeof strings === 'function' + ? emCss(strings(props)) + : emCss( + strings as TemplateStringsArray, + ...rawArgs.map((arg) => { + // @ts-expect-error + if (typeof arg === 'function') return arg(props) + return arg + }), + ) + return { + ...emCssArgs, + styles: styleToString(transform(emCssArgs.styles), props), + } + } + }) as XCSSFunction +} diff --git a/packages/emotion/src/createCx.ts b/packages/emotion/src/createCx.ts new file mode 100644 index 00000000..4a08b519 --- /dev/null +++ b/packages/emotion/src/createCx.ts @@ -0,0 +1,32 @@ +import { cascade, flatten, string } from '@xstyled/util' +import { Theme, StyleGenerator } from '@xstyled/system' +import { createCssFunction, SerializedStylesFn } from './createCssFunction' + +export interface Cx { + (styles: SerializedStylesFn | SerializedStylesFn[]): + | string + | ((theme: Theme) => any) +} + +export const createCx = (generator: TGen): Cx => { + const css = createCssFunction(generator) + return (styles: SerializedStylesFn | SerializedStylesFn[]) => { + if (string(styles)) return styles + return (theme: Theme) => { + const p = { theme } + + const parseStyle = (style: any) => { + if (typeof style === 'object') { + style = css(style) + } + return cascade(style, p) + } + + if (Array.isArray(styles)) { + return flatten(styles).map(parseStyle) + } + + return parseStyle(styles) + } + } +} diff --git a/packages/emotion/src/createGlobalStyle.tsx b/packages/emotion/src/createGlobalStyle.tsx deleted file mode 100644 index 41659920..00000000 --- a/packages/emotion/src/createGlobalStyle.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import * as React from 'react' -import { useTheme } from '@emotion/react' -import { Global } from '@emotion/react' -import { css } from './css' - -export const createGlobalStyle =

( - ...styles: Parameters -) => { - const GlobalStyle = (props: P) => { - const theme = useTheme() - return - } - GlobalStyle.displayName = 'GlobalStyle' - return GlobalStyle -} diff --git a/packages/emotion/src/createJsx.ts b/packages/emotion/src/createJsx.ts new file mode 100644 index 00000000..ae479318 --- /dev/null +++ b/packages/emotion/src/createJsx.ts @@ -0,0 +1,26 @@ +/* eslint-disable prefer-rest-params */ +import * as React from 'react' +import { jsx as emJsx } from '@emotion/react' +import { StyleGenerator } from '@xstyled/system' +import { createCx } from './createCx' + +export type XJsx = typeof emJsx + +export const createJsx = ( + generator: TGen, +): XJsx => { + const cx = createCx(generator) + return function jsx( + type: React.ElementType, + props?: object, + ...children: React.ReactNode[] + ) { + if (props == null || !Object.prototype.hasOwnProperty.call(props, 'css')) { + // @ts-expect-error + return React.createElement.apply(undefined, arguments, ...children) + } + + // @ts-expect-error + return emJsx(type, { ...props, css: cx(props.css) }, ...children) + } as XJsx +} diff --git a/packages/emotion/src/createStyled.ts b/packages/emotion/src/createStyled.ts index 98b3d069..a17fb7fa 100644 --- a/packages/emotion/src/createStyled.ts +++ b/packages/emotion/src/createStyled.ts @@ -3,7 +3,7 @@ import emStyled, { CreateStyledComponent, CreateStyled } from '@emotion/styled' import { Theme } from '@emotion/react' import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system' import { BoxElements } from '@xstyled/core' -import { css } from './css' +import { createCssFunction, XCSSFunction } from './createCssFunction' const flattenArgs = (arg: any, props: any): any => { if (typeof arg === 'function') return flattenArgs(arg(props), props) @@ -11,7 +11,11 @@ const flattenArgs = (arg: any, props: any): any => { return arg } -const getCreateStyle = (baseCreateStyle: any, generator?: StyleGenerator) => { +const getCreateStyle = ( + baseCreateStyle: any, + css: XCSSFunction, + generator?: StyleGenerator, +) => { if (!generator) { return (strings: any, ...args: any) => baseCreateStyle((props: any) => @@ -38,9 +42,9 @@ type BoxStyledTags = { > } -export interface CreateXStyled +export interface XStyled extends CreateStyled, - BoxStyledTags {} + BoxStyledTags> {} const createShouldForwardProp = ( generator: StyleGenerator, @@ -51,8 +55,9 @@ const createShouldForwardProp = ( } export const createBaseStyled = ( + css: XCSSFunction, generator?: TGen, -): CreateXStyled> => { +): XStyled => { const defaultOptions = generator ? { shouldForwardProp: createShouldForwardProp(generator), @@ -61,15 +66,17 @@ export const createBaseStyled = ( return ((component: any, options: any) => getCreateStyle( emStyled(component, { ...defaultOptions, ...options }), + css, generator, - )) as CreateXStyled> + )) as XStyled } export const createStyled = ( generator: TGen, -): CreateXStyled> => { - const styled = createBaseStyled() - const xstyled = createBaseStyled(generator) +): XStyled => { + const css = createCssFunction(generator) + const styled = createBaseStyled(css) + const xstyled = createBaseStyled(css, generator) styled.box = xstyled('div') Object.keys(emStyled).forEach((key) => { // @ts-ignore diff --git a/packages/emotion/src/createX.test.tsx b/packages/emotion/src/createX.test.tsx deleted file mode 100644 index fc92b7d5..00000000 --- a/packages/emotion/src/createX.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react' -import '@testing-library/jest-dom/extend-expect' -import { render, cleanup } from '@testing-library/react' -import { Theme } from '@emotion/react' -import { - fontSize, - FontSizeProps, - fontWeight, - FontWeightProps, -} from '@xstyled/system' -import { createX } from '.' - -afterEach(cleanup) - -describe('#createX', () => { - it('creates system based components', () => { - const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).toHaveStyle(` - font-size: 10px; - `) - }) -}) - -describe('#x.extend', () => { - it('is possible to extend it', () => { - const x = createX>(fontSize) - const y = x.extend & FontWeightProps>( - fontWeight, - ) - const { container } = render() - expect(container.firstChild).toHaveStyle(` - font-size: 10px; - font-weight: bold; - `) - }) -}) diff --git a/packages/emotion/src/createX.ts b/packages/emotion/src/createX.ts index 3db11e0f..06d3e0da 100644 --- a/packages/emotion/src/createX.ts +++ b/packages/emotion/src/createX.ts @@ -1,32 +1,24 @@ -/* eslint-disable @typescript-eslint/ban-types */ import * as React from 'react' import { Theme } from '@emotion/react' import emStyled, { StyledComponent } from '@emotion/styled' -import { compose, StyleGenerator } from '@xstyled/system' +import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system' import { createBaseStyled } from './createStyled' +import { createCssFunction } from './createCssFunction' type JSXElementKeys = keyof JSX.IntrinsicElements -type JSXElements = { +export type X = { [Key in JSXElementKeys]: StyledComponent< - TProps & { as?: React.ElementType; theme?: Theme }, + StyleGeneratorProps & { as?: React.ElementType; theme?: Theme }, JSX.IntrinsicElements[Key] > } -type CreateX = (generator: StyleGenerator) => X - -export interface X extends JSXElements { - extend: CreateX -} - -export const createX: CreateX = ( - generator: StyleGenerator, -) => { - const styled = createBaseStyled(generator) - const x: X = { - extend: (...generators) => createX(compose(generator, ...generators)), - } as X +export const createX = ( + generator: TGen, +): X => { + const styled = createBaseStyled(createCssFunction(generator), generator) + const x = {} as X Object.keys(emStyled).forEach((tag) => { // @ts-ignore x[tag] = styled(tag)`` diff --git a/packages/emotion/src/css.ts b/packages/emotion/src/css.ts deleted file mode 100644 index bc0d4991..00000000 --- a/packages/emotion/src/css.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { css as emCss, SerializedStyles, Theme } from '@emotion/react' -import { CSSInterpolation } from '@emotion/serialize' -import { transform } from '@xstyled/core' - -function styleToString(style: any, props: any): any { - if (Array.isArray(style)) - return style.reduce((str, style) => str + styleToString(style, props), '') - if (typeof style === 'function') return styleToString(style(props), props) - return style -} - -interface Props { - theme?: Theme -} - -interface CSSInterpolationFn { - (props: Props): CSSInterpolation -} - -export interface SerializedStylesFn { - (props: Props): SerializedStyles -} - -export function css(fn: CSSInterpolationFn): SerializedStylesFn -export function css(...args: CSSInterpolation[]): SerializedStylesFn -export function css( - strings: TemplateStringsArray, - ...rawArgs: CSSInterpolation[] -): SerializedStylesFn -export function css( - strings: TemplateStringsArray | CSSInterpolation | CSSInterpolationFn, - ...rawArgs: CSSInterpolation[] -): SerializedStylesFn { - return (props: Props): SerializedStyles => { - const emCssArgs = - typeof strings === 'function' - ? emCss(strings(props)) - : emCss( - strings as TemplateStringsArray, - ...rawArgs.map((arg) => { - // @ts-expect-error - if (typeof arg === 'function') return arg(props) - return arg - }), - ) - return { - ...emCssArgs, - styles: styleToString(transform(emCssArgs.styles), props), - } - } -} diff --git a/packages/emotion/src/cx.ts b/packages/emotion/src/cx.ts deleted file mode 100644 index cc1e84e9..00000000 --- a/packages/emotion/src/cx.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { cascade, flatten } from '@xstyled/util' -import { Theme } from '@xstyled/system' -import { css, SerializedStylesFn } from './css' - -export function cx( - styles: SerializedStylesFn | SerializedStylesFn[], -): (theme: Theme) => any { - if (typeof styles === 'string') return styles - - return (theme: Theme) => { - const p = { theme } - - function parseStyle(style: any) { - if (typeof style === 'object') { - style = css(style) - } - return cascade(style, p) - } - - if (Array.isArray(styles)) { - return flatten(styles).map(parseStyle) - } - - return parseStyle(styles) - } -} diff --git a/packages/emotion/src/index.ts b/packages/emotion/src/index.ts index 1ec683f4..684b2c82 100644 --- a/packages/emotion/src/index.ts +++ b/packages/emotion/src/index.ts @@ -1,11 +1,4 @@ -export { css } from './css' -export { cx } from './cx' -export { jsx } from './jsx' -export { createGlobalStyle } from './createGlobalStyle' -export { styled as default } from './styled' export * from './colorModes' -export * from './x' -export * from './createX' export { withEmotionCache, CacheProvider, @@ -20,3 +13,10 @@ export * from './breakpoints' export * from './theme' export * from './preflight' export * from '@xstyled/system' + +// Create and export default system +import { system } from '@xstyled/system' +import { createCss } from './create' + +const { css, styled, x, createGlobalStyle, cx, jsx } = createCss(system) +export { css, styled as default, x, createGlobalStyle, cx, jsx } diff --git a/packages/emotion/src/jsx.ts b/packages/emotion/src/jsx.ts deleted file mode 100644 index 5828a074..00000000 --- a/packages/emotion/src/jsx.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable prefer-rest-params */ -import * as React from 'react' -import { jsx as emJsx } from '@emotion/react' -import { cx } from './cx' - -// @ts-expect-error -export const jsx: typeof emJsx = function ( - type: React.ElementType, - props?: object, - ...children: React.ReactNode[] -) { - if (props == null || !Object.prototype.hasOwnProperty.call(props, 'css')) { - // @ts-expect-error - return React.createElement.apply(undefined, arguments, ...children) - } - - // @ts-expect-error - return emJsx(type, { ...props, css: cx(props.css) }, ...children) -} diff --git a/packages/emotion/src/preflight.tsx b/packages/emotion/src/preflight.tsx index 4982e4ab..2378cab5 100644 --- a/packages/emotion/src/preflight.tsx +++ b/packages/emotion/src/preflight.tsx @@ -1,4 +1,8 @@ -import { createPreflight } from '@xstyled/system' -import { createGlobalStyle } from './createGlobalStyle' +import * as React from 'react' +import { Global, useTheme } from '@emotion/react' +import { getPreflightStyles } from '@xstyled/system' -export const Preflight = createPreflight({ createGlobalStyle }) +export const Preflight: React.FC = () => { + const theme = useTheme() + return +} diff --git a/packages/emotion/src/styled.ts b/packages/emotion/src/styled.ts deleted file mode 100644 index 9fe03a25..00000000 --- a/packages/emotion/src/styled.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { system } from '@xstyled/system' -import { createStyled } from './createStyled' - -export const styled = createStyled(system) diff --git a/packages/emotion/src/x.ts b/packages/emotion/src/x.ts deleted file mode 100644 index 2de381ec..00000000 --- a/packages/emotion/src/x.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Theme } from '@emotion/react' -import { SystemProps, system } from '@xstyled/system' -import { createX } from './createX' - -export const x = createX>(system) diff --git a/packages/styled-components/src/create.ts b/packages/styled-components/src/create.ts new file mode 100644 index 00000000..cdc2bb11 --- /dev/null +++ b/packages/styled-components/src/create.ts @@ -0,0 +1,26 @@ +import { StyleGenerator } from '@xstyled/system' +import { createCssFunction, XCSSFunction } from './createCssFunction' +import { createX, X } from './createX' +import { createStyled, XStyled } from './createStyled' +import { + createCreateGlobalStyle, + XCreateGlobalStyle, +} from './createCreateGlobalStyle' + +interface XStyledSet { + css: XCSSFunction + x: X + styled: XStyled + createGlobalStyle: XCreateGlobalStyle +} + +export const createCss = ( + generator: TGen, +): XStyledSet => { + return { + css: createCssFunction(generator), + x: createX(generator), + styled: createStyled(generator), + createGlobalStyle: createCreateGlobalStyle(generator), + } +} diff --git a/packages/styled-components/src/createCreateGlobalStyle.ts b/packages/styled-components/src/createCreateGlobalStyle.ts new file mode 100644 index 00000000..9bb7b53d --- /dev/null +++ b/packages/styled-components/src/createCreateGlobalStyle.ts @@ -0,0 +1,18 @@ +import { createGlobalStyle as scCreateGlobalStyle } from 'styled-components' +import { StyleGenerator } from '@xstyled/system' +import { createCssFunction } from './createCssFunction' + +export type XCreateGlobalStyle = typeof scCreateGlobalStyle + +export const createCreateGlobalStyle = ( + generator: TGen, +): XCreateGlobalStyle => { + const css = createCssFunction(generator) + return (( + ...args: Parameters + ): ReturnType => + scCreateGlobalStyle([ + // @ts-ignore + css(...args), + ])) as XCreateGlobalStyle +} diff --git a/packages/styled-components/src/createCssFunction.ts b/packages/styled-components/src/createCssFunction.ts new file mode 100644 index 00000000..9ddb8c38 --- /dev/null +++ b/packages/styled-components/src/createCssFunction.ts @@ -0,0 +1,22 @@ +/* eslint-disable no-continue, no-loop-func, no-cond-assign */ +import { + css as scCss, + FlattenSimpleInterpolation, + ThemedCssFunction, +} from 'styled-components' +import { StyleGenerator, Theme } from '@xstyled/system' +import { flattenStrings } from '@xstyled/util' +import { createTransform } from '@xstyled/core' + +export type XCSSFunction = ThemedCssFunction + +export const createCssFunction = ( + generator: TGen, +): XCSSFunction => { + const transform = createTransform(generator) + return ((...args: Parameters) => { + const scCssArgs = scCss(...args) + const flattenedArgs = flattenStrings(scCssArgs as any[]) + return flattenedArgs.map(transform) as FlattenSimpleInterpolation + }) as XCSSFunction +} diff --git a/packages/styled-components/src/createGlobalStyle.ts b/packages/styled-components/src/createGlobalStyle.ts deleted file mode 100644 index 32ce5c52..00000000 --- a/packages/styled-components/src/createGlobalStyle.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - createGlobalStyle as scCreateGlobalStyle, - GlobalStyleComponent, - DefaultTheme, - CSSObject, - ThemedStyledProps, - Interpolation, - InterpolationFunction, -} from 'styled-components' -import { css } from './css' - -export const createGlobalStyle =

( - first: - | TemplateStringsArray - | CSSObject - | InterpolationFunction>, - ...interpolations: Array>> -): GlobalStyleComponent => { - // @ts-ignore - return scCreateGlobalStyle

([css(first, ...interpolations)]) -} diff --git a/packages/styled-components/src/createStyled.ts b/packages/styled-components/src/createStyled.ts index ab4f7b49..3ade6e72 100644 --- a/packages/styled-components/src/createStyled.ts +++ b/packages/styled-components/src/createStyled.ts @@ -2,40 +2,40 @@ import type { ElementType } from 'react' import { BoxElements } from '@xstyled/core' import { string } from '@xstyled/util' -import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system' +import { StyleGenerator, StyleGeneratorProps, Theme } from '@xstyled/system' import scStyled, { - DefaultTheme, StyledConfig, ThemedBaseStyledInterface, ThemedStyledFunction, } from 'styled-components' -import { css } from './css' +import { createCssFunction, XCSSFunction } from './createCssFunction' const getCreateStyle = ( baseCreateStyle: ThemedStyledFunction, + css: XCSSFunction, generator?: StyleGenerator, ) => { const createStyle = (...args: Parameters) => // @ts-ignore baseCreateStyle`${css(...args)}${generator}` createStyle.attrs = (attrs: Parameters[0]) => - getCreateStyle(baseCreateStyle.attrs(attrs), generator) + getCreateStyle(baseCreateStyle.attrs(attrs), css, generator) createStyle.withConfig = (config: StyledConfig) => - getCreateStyle(baseCreateStyle.withConfig(config), generator) + getCreateStyle(baseCreateStyle.withConfig(config), css, generator) return createStyle } type BoxStyledTags = { [Key in keyof BoxElements]: ThemedStyledFunction< BoxElements[Key], - DefaultTheme, + Theme, TProps > } -export interface CreateXStyled - extends ThemedBaseStyledInterface, - BoxStyledTags {} +export interface XStyled + extends ThemedBaseStyledInterface, + BoxStyledTags> {} const createShouldForwardProp = ( generator: StyleGenerator, @@ -67,8 +67,9 @@ const createShouldForwardProp = ( } export const createBaseStyled = ( + css: XCSSFunction, generator?: TGen, -): CreateXStyled> => { +): XStyled => { const config = generator ? { shouldForwardProp: createShouldForwardProp(generator), @@ -78,16 +79,18 @@ export const createBaseStyled = ( const baseStyled = scStyled(component) return getCreateStyle( config ? baseStyled.withConfig(config) : baseStyled, + css, generator, ) - }) as CreateXStyled> + }) as XStyled } export const createStyled = ( generator: TGen, -): CreateXStyled> => { - const styled = createBaseStyled() - const xstyled = createBaseStyled(generator) +): XStyled => { + const css = createCssFunction(generator) + const styled = createBaseStyled(css) + const xstyled = createBaseStyled(css, generator) styled.box = xstyled('div') Object.keys(scStyled).forEach((key) => { // @ts-ignore diff --git a/packages/styled-components/src/createX.test.tsx b/packages/styled-components/src/createX.test.tsx deleted file mode 100644 index 5868c96d..00000000 --- a/packages/styled-components/src/createX.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from 'react' -import '@testing-library/jest-dom/extend-expect' -import { render, cleanup } from '@testing-library/react' -import { Theme } from '@emotion/react' -import { - fontSize, - FontSizeProps, - fontWeight, - FontWeightProps, -} from '@xstyled/system' -import { createX } from '.' - -afterEach(cleanup) - -describe('#createX', () => { - it('creates system based components', () => { - const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).toHaveStyle(` - font-size: 10px; - `) - }) - - it('avoids passing system props to HTML element', () => { - const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).not.toHaveAttribute('fontSize') - }) - - it('passes HTML attrs to HTML element', () => { - const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).toHaveAttribute('role', 'presentation') - }) - - it('avoids passing system props to "as" component', () => { - const Component = (props) =>

- const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).not.toHaveAttribute('fontSize') - }) - - it('passes non-system props to "as" component', () => { - const Component = ({ asdf, ...props }) =>
{asdf}
- const x = createX>(fontSize) - const { container } = render() - expect(container.firstChild).toHaveTextContent('boo!') - }) - - // skip because this depends on unreleased styled-components 5.2.4 or 6 - it.skip('avoids passing non-HTML attrs to HTML element', () => { - const x = createX>(fontSize) - // @ts-expect-error explicit test - const { container } = render() - expect(container.firstChild).not.toHaveAttribute('asdf') - }) -}) - -describe('#x.extend', () => { - it('is possible to extend it', () => { - const x = createX>(fontSize) - const y = x.extend & FontWeightProps>( - fontWeight, - ) - const { container } = render() - expect(container.firstChild).toHaveStyle(` - font-size: 10px; - font-weight: bold; - `) - }) -}) diff --git a/packages/styled-components/src/createX.ts b/packages/styled-components/src/createX.ts index 62de1bf6..ab9283de 100644 --- a/packages/styled-components/src/createX.ts +++ b/packages/styled-components/src/createX.ts @@ -1,7 +1,8 @@ /* eslint-disable no-continue, no-loop-func, no-cond-assign */ import scStyled, { StyledComponent, DefaultTheme } from 'styled-components' -import { compose, StyleGenerator } from '@xstyled/system' +import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system' import { createBaseStyled } from './createStyled' +import { createCssFunction } from './createCssFunction' type JSXElementKeys = keyof JSX.IntrinsicElements @@ -9,31 +10,23 @@ type SafeIntrinsicComponent = ( props: Omit, ) => React.ReactElement -export const createX = (generator: StyleGenerator) => { - type X = { - extend( - ...generators: StyleGenerator[] - ): X - } & { - [Key in JSXElementKeys]: StyledComponent< - SafeIntrinsicComponent, - DefaultTheme, - TProps, - 'color' - > - } - - // @ts-ignore - const x: X = { - extend: (...generators) => createX(compose(generator, ...generators)), - } - - const xstyled = createBaseStyled(generator) +export type X = { + [Key in JSXElementKeys]: StyledComponent< + SafeIntrinsicComponent, + DefaultTheme, + StyleGeneratorProps, + 'color' + > +} +export const createX = ( + generator: TGen, +): X => { + const xstyled = createBaseStyled(createCssFunction(generator), generator) + const x = {} as X Object.keys(scStyled).forEach((tag) => { // @ts-ignore x[tag] = xstyled(tag)`` }) - return x } diff --git a/packages/styled-components/src/css.ts b/packages/styled-components/src/css.ts deleted file mode 100644 index bebb3cf5..00000000 --- a/packages/styled-components/src/css.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-continue, no-loop-func, no-cond-assign */ -import { css as scCss, FlattenSimpleInterpolation } from 'styled-components' -import { flattenStrings } from '@xstyled/util' -import { transform } from '@xstyled/core' - -export function css( - ...args: Parameters -): ReturnType { - const scCssArgs = scCss(...args) - const flattenedArgs = flattenStrings(scCssArgs as any[]) - return flattenedArgs.map(transform) as FlattenSimpleInterpolation -} diff --git a/packages/styled-components/src/index.ts b/packages/styled-components/src/index.ts index bda9b3ec..c0d2b11c 100644 --- a/packages/styled-components/src/index.ts +++ b/packages/styled-components/src/index.ts @@ -8,14 +8,16 @@ export { ThemeProvider, withTheme, } from 'styled-components' - -export { css } from './css' -export { createGlobalStyle } from './createGlobalStyle' -export { styled as default } from './styled' -export * from './x' -export * from './createX' export * from './colorModes' export * from './theme' export * from './breakpoints' export * from './preflight' export * from '@xstyled/system' +export * from './create' + +// Create and export default system +import { system } from '@xstyled/system' +import { createCss } from './create' + +const { css, styled, x, createGlobalStyle } = createCss(system) +export { css, styled as default, x, createGlobalStyle } diff --git a/packages/styled-components/src/preflight.ts b/packages/styled-components/src/preflight.ts index 09b23964..d755f922 100644 --- a/packages/styled-components/src/preflight.ts +++ b/packages/styled-components/src/preflight.ts @@ -1,4 +1,6 @@ -import { createPreflight } from '@xstyled/system' import { createGlobalStyle } from 'styled-components' +import { getPreflightStyles } from '@xstyled/system' -export const Preflight = createPreflight({ createGlobalStyle }) +export const Preflight = createGlobalStyle(({ theme }) => + getPreflightStyles(theme), +) diff --git a/packages/styled-components/src/styled.ts b/packages/styled-components/src/styled.ts deleted file mode 100644 index 9fe03a25..00000000 --- a/packages/styled-components/src/styled.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { system } from '@xstyled/system' -import { createStyled } from './createStyled' - -export const styled = createStyled(system) diff --git a/packages/styled-components/src/x.test.tsx b/packages/styled-components/src/x.test.tsx index 94278c14..613aab10 100644 --- a/packages/styled-components/src/x.test.tsx +++ b/packages/styled-components/src/x.test.tsx @@ -65,4 +65,33 @@ describe('#x', () => { expect(container.firstChild).not.toHaveAttribute('display') expect(container.firstChild).toHaveAttribute('data-foo') }) + + it('avoids passing system props to HTML element', () => { + const { container } = render() + expect(container.firstChild).not.toHaveAttribute('fontSize') + }) + + it('passes HTML attrs to HTML element', () => { + const { container } = render() + expect(container.firstChild).toHaveAttribute('role', 'presentation') + }) + + it('avoids passing system props to "as" component', () => { + const Component = (props: any) =>
+ const { container } = render() + expect(container.firstChild).not.toHaveAttribute('fontSize') + }) + + it('passes non-system props to "as" component', () => { + const Component = ({ asdf, ...props }: any) =>
{asdf}
+ const { container } = render() + expect(container.firstChild).toHaveTextContent('boo!') + }) + + // skip because this depends on unreleased styled-components 5.2.4 or 6 + it.skip('avoids passing non-HTML attrs to HTML element', () => { + // @ts-expect-error explicit test + const { container } = render() + expect(container.firstChild).not.toHaveAttribute('asdf') + }) }) diff --git a/packages/styled-components/src/x.ts b/packages/styled-components/src/x.ts deleted file mode 100644 index b60a4762..00000000 --- a/packages/styled-components/src/x.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { DefaultTheme } from 'styled-components' -import { SystemProps, system } from '@xstyled/system' -import { createX } from './createX' - -export const x = createX>(system) diff --git a/packages/system/src/preflight.ts b/packages/system/src/preflight.ts index 5fe0de62..288b57a3 100644 --- a/packages/system/src/preflight.ts +++ b/packages/system/src/preflight.ts @@ -1,129 +1,124 @@ import { th } from './th' +import { ITheme } from './types' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type TagTemplate = (strs: TemplateStringsArray, ...args: any[]) => any +export const getPreflightStyles = (theme: ITheme): string => ` +/*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */ +*,::after,::before{box-sizing:border-box}:root{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item} -export const createPreflight = ({ - createGlobalStyle, -}: { - createGlobalStyle: T -}): ReturnType => { - return createGlobalStyle` - /*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */ - *,::after,::before{box-sizing:border-box}:root{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item} - - /* Role button pointer */ - [role=button], button { - cursor: pointer; - } +/* Role button pointer */ +[role=button], button { + cursor: pointer; +} - /* Remove default margins */ - blockquote, - dl, - dd, - h1, - h2, - h3, - h4, - h5, - h6, - hr, - figure, - p, - pre { - margin: 0; - } +/* Remove default margins */ +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} - /* Remove headings styles */ - h1, - h2, - h3, - h4, - h5, - h6 { - font-size: inherit; - font-weight: inherit; - } +/* Remove headings styles */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} - /* Unstyle lists */ - ol, - ul { - list-style: none; - margin: 0; - padding: 0; - } +/* Unstyle lists */ +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} - /* Image are block-level */ - img, - svg, - video, - canvas, - audio, - iframe, - embed, - object { - display: block; - vertical-align: middle; - } +/* Image are block-level */ +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + vertical-align: middle; +} - /* Reset border styles */ - *, - ::before, - ::after { - border-width: 0; - border-style: solid; - border-color: ${th.color('default-border-color', 'currentColor')}; - } +/* Reset border styles */ +*, +::before, +::after { + border-width: 0; + border-style: solid; + border-color: ${th.color('default-border-color', 'currentColor')({ theme })}; +} - * { - --x-ring-color: ${th.color('default-ring-color', 'rgba(59,130,246,0.5)')}; - } +* { + --x-ring-color: ${th.color( + 'default-ring-color', + 'rgba(59,130,246,0.5)', + )({ theme })}; +} - /* Default outline on buttons */ - button:focus { - outline: 1px dotted; - outline: 5px auto -webkit-focus-ring-color; - } +/* Default outline on buttons */ +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} - // Animations - @keyframes x-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } +// Animations +@keyframes x-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} - @keyframes x-ping { - 0% { - transform: scale(1); - opacity: 1; - } - 75%, 100% { - transform: scale(2); - opacity: 0; - } - } +@keyframes x-ping { + 0% { + transform: scale(1); + opacity: 1; + } + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} - @keyframes x-pulse { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: .5; - } - } +@keyframes x-pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: .5; + } +} - @keyframes x-bounce { - 0%, 100% { - transform: translateY(-25%); - animationTimingFunction: cubic-bezier(0.8, 0, 1, 1); - } - 50% { - transform: translateY(0); - animationTimingFunction: cubic-bezier(0, 0, 0.2, 1); - } - } - ` +@keyframes x-bounce { + 0%, 100% { + transform: translateY(-25%); + animationTimingFunction: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: translateY(0); + animationTimingFunction: cubic-bezier(0, 0, 0.2, 1); + } } +` diff --git a/packages/system/src/style.test.ts b/packages/system/src/style.test.ts index 96b8f679..e06fb1a2 100644 --- a/packages/system/src/style.test.ts +++ b/packages/system/src/style.test.ts @@ -38,13 +38,31 @@ describe('#style', () => { expect(scope(true)({ theme: { scope: { default: 'foo' } } })).toBe('foo') }) - it('supports shorthand', () => { + it('supports shorthand mode', () => { const scope = themeGetter({ key: 'scope', shorthand: true }) expect(scope('1 value 2')({ theme: { scope: { value: 'foo' } } })).toBe( '1 foo 2', ) }) + it('supports multiple mode', () => { + const scope = themeGetter({ key: 'scope', multiple: true }) + expect(scope('1, value, 2')({ theme: { scope: { value: 'foo' } } })).toBe( + '1,foo,2', + ) + }) + + it('supports both shorthand and multiple modes', () => { + const scope = themeGetter({ + key: 'scope', + shorthand: true, + multiple: true, + }) + expect( + scope('1, value y, 2')({ theme: { scope: { value: 'foo' } } }), + ).toBe('1,foo y,2') + }) + it('supports array', () => { const scope = themeGetter({ key: 'scope' }) expect( diff --git a/packages/system/src/style.ts b/packages/system/src/style.ts index b79d65cb..09f7f84f 100644 --- a/packages/system/src/style.ts +++ b/packages/system/src/style.ts @@ -31,7 +31,16 @@ import { let themeGetterId = 0 -const SPACES = /\s+/ +type Splitter = [RegExp, string] + +const SPLITTERS: { [key: string]: Splitter } = { + shorthand: [/\s+/, ' '], + multiple: [/\s*,\s*/, ','], +} + +const splitValue = + (splitter: Splitter, transform: (v: string) => any) => (value: string) => + value.split(splitter[0]).map(transform).join(splitter[1]) export const themeGetter = ({ name, @@ -39,12 +48,14 @@ export const themeGetter = ({ key, compose, shorthand, + multiple, }: { name?: string key?: string transform?: TransformValue compose?: ThemeGetter shorthand?: boolean + multiple?: boolean }): ThemeGetter => { const id = themeGetterId++ const getter = @@ -96,9 +107,11 @@ export const themeGetter = ({ return compose ? compose(res)(props) : res } - if (shorthand && string(value)) { - const values = value.split(SPACES) - res = values.map((value: string) => getValue(value)).join(' ') + if ((shorthand || multiple) && string(value)) { + let transform: (value: string) => any = getValue + if (shorthand) transform = splitValue(SPLITTERS.shorthand, transform) + if (multiple) transform = splitValue(SPLITTERS.multiple, transform) + res = transform(value) } else { res = getValue(value) } @@ -110,14 +123,21 @@ export const themeGetter = ({ return getter } -export const createStyleGenerator = ( - getStyle: CSSFromProps & TProps>, - props: string[], - generators?: StyleGenerator[], -): StyleGenerator => { +export const createStyleGenerator = ({ + getStyle, + props, + cssGetters = {}, + generators, +}: { + getStyle: CSSFromProps & TProps> + props: string[] + cssGetters?: { [key: string]: ThemeGetter } + generators?: StyleGenerator[] +}): StyleGenerator => { const generator = getStyle as unknown as StyleGenerator generator.meta = { props, + cssGetters, getStyle: generator, generators, } @@ -153,13 +173,13 @@ export const reduceVariants = ( const getStyleFactory = ( prop: string, mixin: Mixin, - themeGet: ThemeGetter, + themeGet?: ThemeGetter, ): CSSFromProps => { return (props: Props) => { const fromValue = (value: unknown): CSSObject | null | undefined => { if (!is(value)) return null if (obj(value)) return reduceVariants(props, value, fromValue) - return cascade(mixin(themeGet(value)(props)), props) + return cascade(mixin(themeGet ? themeGet(value)(props) : value), props) } const value = props[prop] @@ -173,13 +193,13 @@ const getStyleFactory = ( } const indexGeneratorsByProp = ( - styles: StyleGenerator[], + generators: StyleGenerator[], ): { [key: string]: StyleGenerator } => { const index: { [key: string]: StyleGenerator } = {} - for (let i = 0; i < styles.length; i++) { - const style = styles[i] + for (let i = 0; i < generators.length; i++) { + const style = generators[i] if (style && style.meta) { for (let j = 0; j < style.meta.props.length; j++) { const prop = style.meta.props[j] @@ -224,26 +244,30 @@ export const compose = ( const getStyle = (props: Props, sort = true) => { const styles = {} as CSSObject + let generator for (const key in props) { - const generator = generatorsByProp[key] + generator = generatorsByProp[key] if (generator) { const style = generator.meta.getStyle(props, false) merge(styles, style) } } - if (!sort) return styles + if (!generator || !sort) return styles const medias = getCachedVariants(props, getCache(props.theme, '__states')) return sortStyles(styles, medias) } - const props = flatGenerators.reduce( - (allProps, generator) => [...allProps, ...generator.meta.props], - [] as string[], - ) + let props = [] as string[] + let cssGetters = {} as { [key: string]: ThemeGetter } + for (let i = 0; i < flatGenerators.length; i++) { + const generator = flatGenerators[i] + props = [...props, ...generator.meta.props] + cssGetters = { ...cssGetters, ...generator.meta.cssGetters } + } - return createStyleGenerator(getStyle, props, generators) + return createStyleGenerator({ getStyle, props, cssGetters, generators }) } const getMixinFromCSSProperties = @@ -262,19 +286,34 @@ const getMixinFromCSSOption = (css: CSSOption): Mixin => { return getMixinFromCSSProperties(css) } +const dasherize = (key: string) => key.replace(/[A-Z]/g, '-$&').toLowerCase() + export const style = ({ prop, css, themeGet, key, transform, + cssProps: cssPropsOption, }: StyleOptions): StyleGenerator => { - const getter = themeGet || themeGetter({ key, transform }) + const getter = + themeGet || (key || transform ? themeGetter({ key, transform }) : undefined) + const cssProps = + cssPropsOption || + (string(css) + ? [css] + : Array.isArray(css) + ? css + : string(prop) + ? [prop] + : Array.isArray(prop) + ? prop + : []) if (Array.isArray(prop)) { const mixin = css ? getMixinFromCSSOption(css) : css const generators = prop.map((prop) => - style({ prop, css: mixin, themeGet: getter }), + style({ prop, css: mixin, cssProps, themeGet: getter }), ) return compose(...generators) } @@ -284,7 +323,13 @@ export const style = ({ const generators = [] as StyleGenerator[] const getStyle = getStyleFactory(prop as string, mixin, getter) - const generator = createStyleGenerator(getStyle, props) + const cssGetters = getter + ? cssProps.reduce((getters, cssProp) => { + getters[dasherize(cssProp)] = getter + return getters + }, {} as { [key: string]: ThemeGetter }) + : {} + const generator = createStyleGenerator({ getStyle, props, cssGetters }) generators.push(generator) return compose(...generators) } diff --git a/packages/system/src/styles/animations.ts b/packages/system/src/styles/animations.ts index ae5dd74e..bfc87fe6 100644 --- a/packages/system/src/styles/animations.ts +++ b/packages/system/src/styles/animations.ts @@ -1,5 +1,7 @@ import * as CSS from 'csstype' import { style, themeGetter, compose } from '../style' +import { getDuration, ThemeDuration } from './units' +import { getTimingFunction, ThemeTimingFunction } from './transitions' import { ITheme, SystemProp, ThemeNamespaceValue, Theme } from '../types' export type ThemeAnimation = ThemeNamespaceValue< @@ -21,7 +23,35 @@ export const animation = style({ themeGet: getAnimation, }) +export interface AnimationDurationProps { + animationDuration?: SystemProp< + ThemeDuration | CSS.Property.AnimationDuration, + T + > +} +export const animationDuration = style({ + prop: 'animationDuration', + themeGet: getDuration, +}) + +export interface AnimationTimingFunctionProps { + animationTimingFunction?: SystemProp< + ThemeTimingFunction | CSS.Property.AnimationTimingFunction, + T + > +} +export const animationTimingFunction = style({ + prop: 'animationTimingFunction', + themeGet: getTimingFunction, +}) + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AnimationsProps - extends AnimationProps {} -export const animations = compose(animation) + extends AnimationProps, + AnimationDurationProps, + AnimationTimingFunctionProps {} +export const animations = compose( + animation, + animationDuration, + animationTimingFunction, +) diff --git a/packages/system/src/styles/borders.ts b/packages/system/src/styles/borders.ts index 0c0a2f7f..89f6f37f 100644 --- a/packages/system/src/styles/borders.ts +++ b/packages/system/src/styles/borders.ts @@ -190,6 +190,13 @@ export interface BorderStyleProps { export const borderStyle = style({ prop: 'borderStyle', themeGet: getBorderStyle, + cssProps: [ + 'borderStyle', + 'borderTopStyle', + 'borderRightStyle', + 'borderBottomStyle', + 'borderLeftStyle', + ], }) // Outline @@ -245,6 +252,13 @@ export interface BorderRadiusProps { export const borderRadius = style({ prop: 'borderRadius', themeGet: getRadius, + cssProps: [ + 'borderRadius', + 'borderTopLeftRadius', + 'borderTopRightRadius', + 'borderBottomRightRadius', + 'borderBottomLeftRadius', + ], }) // Divide diff --git a/packages/system/src/styles/effects.ts b/packages/system/src/styles/effects.ts index b479176d..51982b2b 100644 --- a/packages/system/src/styles/effects.ts +++ b/packages/system/src/styles/effects.ts @@ -11,6 +11,7 @@ export type ThemeShadow = ThemeNamespaceValue< export const getShadow = themeGetter({ name: 'shadow', key: 'shadows', + multiple: true, }) // Style diff --git a/packages/system/src/styles/flexbox-grids.ts b/packages/system/src/styles/flexbox-grids.ts index 7aeec4a8..8c342390 100644 --- a/packages/system/src/styles/flexbox-grids.ts +++ b/packages/system/src/styles/flexbox-grids.ts @@ -49,8 +49,8 @@ const getColStyle = ( export interface ColProps { col?: SystemProp } -export const col = createStyleGenerator( - (props) => { +export const col = createStyleGenerator({ + getStyle: (props) => { const value = props.col const common = { boxSizing: 'border-box' as const, @@ -77,8 +77,8 @@ export const col = createStyleGenerator( ...getColStyle(props, value), } }, - ['col'], -) + props: ['col'], +}) export interface FlexboxGridsProps extends RowProps, diff --git a/packages/system/src/styles/layout.ts b/packages/system/src/styles/layout.ts index 4d374091..1302a2fb 100644 --- a/packages/system/src/styles/layout.ts +++ b/packages/system/src/styles/layout.ts @@ -36,8 +36,8 @@ export const boxSizing = style({ export interface ContainerProps { container?: SystemProp } -export const container = createStyleGenerator( - (props) => { +export const container = createStyleGenerator({ + getStyle: (props) => { if (!props.container) return null const breakpoints = getScreens(props) let styles = reduceVariants(props, breakpoints, (v: string | number) => @@ -52,8 +52,8 @@ export const container = createStyleGenerator( ...styles, } }, - ['container'], -) + props: ['container'], +}) export interface OverflowProps { overflow?: SystemProp diff --git a/packages/system/src/styles/sizing.ts b/packages/system/src/styles/sizing.ts index ca385416..50059921 100644 --- a/packages/system/src/styles/sizing.ts +++ b/packages/system/src/styles/sizing.ts @@ -86,13 +86,28 @@ export const minHeight = style({ css: 'minHeight', }) +export interface MaskSizeProps { + maskSize?: SystemProp | CSS.Property.MaskSize, T> +} +export const maskSize = style({ + prop: 'maskSize', + themeGet: themeGetter({ + name: 'size', + key: 'sizes', + compose: getPercent, + multiple: true, + shorthand: true, + }), +}) + export interface SizingProps extends WidthProps, HeightProps, MaxWidthProps, MaxHeightProps, MinWidthProps, - MinHeightProps {} + MinHeightProps, + MaskSizeProps {} export const sizing = compose( width, height, @@ -100,4 +115,5 @@ export const sizing = compose( maxHeight, minWidth, minHeight, + maskSize, ) diff --git a/packages/system/src/styles/space.ts b/packages/system/src/styles/space.ts index ea7e3be3..31e8c677 100644 --- a/packages/system/src/styles/space.ts +++ b/packages/system/src/styles/space.ts @@ -12,6 +12,7 @@ export const getSpace = themeGetter({ name: 'space', key: 'space', compose: getPx, + shorthand: true, transform: transformNegative, }) diff --git a/packages/system/src/types.ts b/packages/system/src/types.ts index 7cf6cab2..b576f996 100644 --- a/packages/system/src/types.ts +++ b/packages/system/src/types.ts @@ -86,6 +86,7 @@ export type CSSOption = string | string[] | Mixin export interface StyleOptions { prop: string | string[] | readonly string[] css?: CSSOption + cssProps?: string[] themeGet?: ThemeGetter key?: string transform?: TransformValue @@ -95,6 +96,7 @@ export interface StyleGenerator { (props: Props & TProps, sort?: boolean): CSSObject | null meta: { props: string[] + cssGetters: { [key: string]: ThemeGetter } getStyle: StyleGenerator generators?: StyleGenerator[] }