Skip to content

Commit

Permalink
Add withConfig API (#1103)
Browse files Browse the repository at this point in the history
* Format code

* Add basic withConfig impl

* Fix test swallowing the error

* Add main withConfig api for componentId and displayName

* add shouldForwardStitchesProp

* Fix type issue

* Update types
  • Loading branch information
hadihallak authored Oct 6, 2022
1 parent aa59b01 commit 02ba207
Show file tree
Hide file tree
Showing 13 changed files with 59,269 additions and 48,023 deletions.
3 changes: 2 additions & 1 deletion .task/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ const main = async (pkg, opts) => {
error.stack = [ ...error.stack.split(/\n/g).slice(1)].join('\n')

// assign failure to the results
results[description][test].push(`${failIcon} ${infoText(test)}`, getErrorStack(error), '')
const errorMessage = error.message.split('\n')[0]
results[description][test].push(`${failIcon} ${infoText(test)}\n\t${errorMessage}`, getErrorStack(error), '')
results[description][test][didFail] = error
results[description][didFail] = true
results[didFail] = true
Expand Down
89 changes: 48 additions & 41 deletions packages/core/src/features/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,59 @@ import { createRulesInjectionDeferrer } from '../sheet.js'
const createCssFunctionMap = createMemo()

/** Returns a function that applies component styles. */
export const createCssFunction = ( config, sheet) =>
createCssFunctionMap(config, () => (...args) => {
let internals = {
type: null,
composers: new Set(),
}
export const createCssFunction = (config, sheet) =>
createCssFunctionMap(config, () => {
const _css = (args, componentConfig = {}) => {
let internals = {
type: null,
composers: new Set(),
}

for (const arg of args) {
// skip any void argument
if (arg == null) continue
for (const arg of args) {
// skip any void argument
if (arg == null) continue

// conditionally extend the component
if (arg[internal]) {
if (internals.type == null) internals.type = arg[internal].type
// conditionally extend the component
if (arg[internal]) {
if (internals.type == null) internals.type = arg[internal].type

for (const composer of arg[internal].composers) {
internals.composers.add(composer)
}
}
for (const composer of arg[internal].composers) {
internals.composers.add(composer)
}
}

// otherwise, conditionally define the component type
else if (arg.constructor !== Object || arg.$$typeof) {
if (internals.type == null) internals.type = arg
}
// otherwise, conditionally define the component type
else if (arg.constructor !== Object || arg.$$typeof) {
if (internals.type == null) internals.type = arg
}

// otherwise, add a new composer to this component
else {
internals.composers.add(createComposer(arg, config))
}
}
// otherwise, add a new composer to this component
else {
internals.composers.add(createComposer(arg, config, componentConfig))
}
}

// set the component type if none was set
if (internals.type == null) internals.type = 'span'
if (!internals.composers.size) internals.composers.add(['PJLV', {}, [], [], {}, []])
// set the component type if none was set
if (internals.type == null) internals.type = 'span'
if (!internals.composers.size) internals.composers.add(['PJLV', {}, [], [], {}, []])

return createRenderer(config, internals, sheet, componentConfig)
}

const css = (...args) => _css(args)

css.withConfig = (componentConfig) => (...args) => _css(args, componentConfig)

return css
})

return createRenderer(config, internals, sheet)
})

/** Creates a composer from a configuration object. */
const createComposer = ({ variants: initSingularVariants, compoundVariants: initCompoundVariants, defaultVariants: initDefaultVariants, ...style }, config) => {
const createComposer = ({ variants: initSingularVariants, compoundVariants: initCompoundVariants, defaultVariants: initDefaultVariants, ...style }, config, {componentId, displayName}) => {
/** @type {string} Composer Unique Identifier. @see `{CONFIG_PREFIX}-?c-{STYLE_HASH}` */
const className = `${toTailDashed(config.prefix)}c-${toHash(style)}`
const hash = componentId || toHash(style)
const componentNamePrefix = displayName ? ('c-' + displayName +'') : 'c'
const className = `${toTailDashed(config.prefix)}${componentNamePrefix}-${hash}`

const singularVariants = []

Expand Down Expand Up @@ -108,11 +118,7 @@ const createComposer = ({ variants: initSingularVariants, compoundVariants: init
return ([className, style, singularVariants, compoundVariants, prefilledVariants, undefinedVariants])
}

const createRenderer = (
config,
internals,
sheet
) => {
const createRenderer = (config, internals, sheet, { shouldForwardStitchesProp }) => {
const [
baseClassName,
baseClassNames,
Expand All @@ -132,14 +138,13 @@ const createRenderer = (
// 2. we delete variant props
// 3. we delete `css` prop
// therefore: we must create a new props & css variables
const { css, ...forwardProps } = props
const { ...forwardProps } = props

const variantProps = {}

for (const name in prefilledVariants) {
delete forwardProps[name]

if (name in props) {
if (!shouldForwardStitchesProp?.(name)) delete forwardProps[name]
let data = props[name]

if (typeof data === 'object' && data) {
Expand Down Expand Up @@ -226,7 +231,9 @@ const createRenderer = (
}

// apply css property styles
const css = forwardProps.css
if (typeof css === 'object' && css) {
if (!shouldForwardStitchesProp?.('css')) delete forwardProps.css
/** @type {string} Inline Class Unique Identifier. @see `{COMPOSER_UUID}-i{VARIANT_UUID}-css` */
const iClass = `${baseClassName}-i${toHash(css)}-css`

Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const PotatoButton = css({
color: 'Turquoise',
},
},
hue: {
variant: {
blue: {
backgroundColor: '$gray100',
},
Expand Down
153 changes: 153 additions & 0 deletions packages/core/tests/with-config-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { createStitches } from '../src/index.js'

describe('css.withConfig', () => {
test('Basic css calls without a config', () => {
const { css, getCssText } = createStitches()

expect(css.withConfig).toBeInstanceOf(Function)

const component1of2 = css.withConfig()()
const className1of2 = `${component1of2}`
const cssString1of2 = getCssText()

expect(component1of2).toBeInstanceOf(Function)
expect(className1of2).toBe('PJLV')
expect(cssString1of2).toBe('')

const componentToRender = css.withConfig()({ color: 'DodgerBlue' })
const className = componentToRender().toString()
const cssString = getCssText()

expect(componentToRender).toBeInstanceOf(Function)
expect(className).toBe('c-dataoT')
expect(cssString).toBe(`--sxs{--sxs:2 PJLV c-dataoT}@media{.c-dataoT{color:DodgerBlue}}`)
})
test('Creates the correct className with a componentId', () => {
const { css, getCssText } = createStitches()

const componentConfig = {
componentId: 'cool-id',
}
const componentToRender = css.withConfig(componentConfig)({ color: 'red' })
const className = componentToRender().toString()
const cssString = getCssText()

expect(componentToRender).toBeInstanceOf(Function)
expect(className).toBe('c-cool-id')
expect(cssString).toBe(`--sxs{--sxs:2 c-cool-id}@media{.c-cool-id{color:red}}`)
})

test('Creates the correct className with a displayName', () => {
const { css, getCssText } = createStitches()

const componentConfig = {
displayName: 'my-cool-display-name',
}
const componentToRender = css.withConfig(componentConfig)({ color: 'red' })
const className = componentToRender().toString()
const cssString = getCssText()

expect(componentToRender).toBeInstanceOf(Function)
expect(className).toBe('c-my-cool-display-name-gmqXFB')
expect(cssString).toBe(`--sxs{--sxs:2 c-my-cool-display-name-gmqXFB}@media{.c-my-cool-display-name-gmqXFB{color:red}}`)
})

test('Creates the correct className with a displayName and componentId', () => {
const { css, getCssText } = createStitches()

const componentConfig = {
componentId: 'cool-id',
displayName: 'my-cool-display-name',
}
const componentToRender = css.withConfig(componentConfig)({ color: 'red' })
const className = componentToRender().toString()
const cssString = getCssText()

expect(componentToRender).toBeInstanceOf(Function)
expect(className).toBe('c-my-cool-display-name-cool-id')
expect(cssString).toBe(`--sxs{--sxs:2 c-my-cool-display-name-cool-id}@media{.c-my-cool-display-name-cool-id{color:red}}`)
})

test('Creates the correct className with a componentConfig while extending components', () => {
const { css, getCssText } = createStitches()

const ComponentToExtend = css.withConfig({
componentId: 'component-to-extend-id',
})({ color: 'red' })
const componentToRender = css.withConfig({ componentId: 'cool-component-id' })(ComponentToExtend, { color: 'blue' })
const className = componentToRender().toString()
expect(className).toBe('c-component-to-extend-id c-cool-component-id')
const cssString = getCssText()

expect(cssString).toBe(`--sxs{--sxs:2 c-component-to-extend-id c-cool-component-id}@media{.c-component-to-extend-id{color:red}.c-cool-component-id{color:blue}}`)
})
})

describe('shouldForwardStitchesProp', () => {
test('does not omit stitches props when shouldForwardStitchesProp returns true', () => {
const { css } = createStitches()

const componentOneConfig = {
shouldForwardStitchesProp: () => false,
}
const componentOne = css.withConfig(componentOneConfig)('button', {
variants: {
variant: {
red: { background: 'red' },
},
},
})

const {props: firstComponentProps} = componentOne({ variant: 'red', css: {} })
expect(firstComponentProps.variant).toBe(undefined)
expect(firstComponentProps.css).toEqual(undefined)

const componentTwoConfig = {
shouldForwardStitchesProp: () => true,
}

const componentTwo = css.withConfig(componentTwoConfig)('button', {
variants: {
variant: {
red: { background: 'red' },
},
},
})

const {props: secondComponentProps} = componentTwo({ variant: 'red', css: {} })
expect(secondComponentProps.variant).toBe('red')
expect(secondComponentProps.css).toEqual({})
})

test('does not omit a non-stitches props when shouldForwardStitchesProp returns true', () => {
const { css } = createStitches()
const componentConfig = {
shouldForwardStitchesProp: () => true,
}
const componentToRender = css.withConfig(componentConfig)('button', {
variants: {
variant: {
red: { background: 'red' },
},
},
})
const props = componentToRender({ href: 'www.hello.com' }).props
expect(props.href).toBe('www.hello.com')
})

test('Omits variants when shouldForwardStitchesProp returns false', () => {
const { css } = createStitches()
const componentConfig = {
shouldForwardStitchesProp: () => false,
}
const componentToRender = css.withConfig(componentConfig)('button', {
variants: {
variant: {
red: { background: 'red' },
},
},
})
const props = componentToRender({ variant: 'red' }).props
expect(props.variant).toBe(undefined)
})
})
Loading

0 comments on commit 02ba207

Please sign in to comment.