Skip to content

Commit

Permalink
Merge pull request #1866 from system-ui/css-prop
Browse files Browse the repository at this point in the history
add types for css prop back
  • Loading branch information
hasparus committed Jul 27, 2021
2 parents 27424d6 + bc7f2d8 commit a7e27f6
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 165 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ module.exports = {

// TypeScript checks this
'no-undef': 'off',
'no-lone-blocks': 'off',
},
}
102 changes: 52 additions & 50 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
import { ThemeUIStyleObject } from '@theme-ui/css'

import { ThemeUIJSX } from './jsx-namespace'

export interface SxProp {
/**
* The sx prop lets you style elements inline, using values from your
* theme. To use the sx prop, add the custom pragma as a comment to the
* top of your module and import the jsx function.
*
* ```ts
* // @jsx jsx
*
* import { jsx } from 'theme-ui'
* ```
*/
sx?: ThemeUIStyleObject
}

export interface IntrinsicSxElements {
p: ThemeUIJSX.IntrinsicElements['p']
b: ThemeUIJSX.IntrinsicElements['b']
i: ThemeUIJSX.IntrinsicElements['i']
a: ThemeUIJSX.IntrinsicElements['a']
h1: ThemeUIJSX.IntrinsicElements['h1']
h2: ThemeUIJSX.IntrinsicElements['h2']
h3: ThemeUIJSX.IntrinsicElements['h3']
h4: ThemeUIJSX.IntrinsicElements['h4']
h5: ThemeUIJSX.IntrinsicElements['h5']
h6: ThemeUIJSX.IntrinsicElements['h6']
img: ThemeUIJSX.IntrinsicElements['img']
pre: ThemeUIJSX.IntrinsicElements['pre']
code: ThemeUIJSX.IntrinsicElements['code']
ol: ThemeUIJSX.IntrinsicElements['ol']
ul: ThemeUIJSX.IntrinsicElements['ul']
li: ThemeUIJSX.IntrinsicElements['li']
blockquote: ThemeUIJSX.IntrinsicElements['blockquote']
hr: ThemeUIJSX.IntrinsicElements['hr']
table: ThemeUIJSX.IntrinsicElements['table']
tr: ThemeUIJSX.IntrinsicElements['tr']
th: ThemeUIJSX.IntrinsicElements['th']
td: ThemeUIJSX.IntrinsicElements['td']
em: ThemeUIJSX.IntrinsicElements['em']
strong: ThemeUIJSX.IntrinsicElements['strong']
div: ThemeUIJSX.IntrinsicElements['div']
del: ThemeUIJSX.IntrinsicElements['div']
inlineCode: ThemeUIJSX.IntrinsicElements['div']
thematicBreak: ThemeUIJSX.IntrinsicElements['div']
root: ThemeUIJSX.IntrinsicElements['div']
}
import { Interpolation } from '@emotion/react'
import { ThemeUIStyleObject, Theme as ThemeUITheme } from '@theme-ui/css'

import { ThemeUIJSX } from './jsx-namespace'

export interface SxProp {
/**
* The sx prop lets you style elements inline, using values from your
* theme.
*
* @see https://theme-ui.com/sx-prop/
*/
sx?: ThemeUIStyleObject
/**
* Theme UI uses Emotion's JSX function. You can pass styles to it directly
* using `css` prop.
* @see https://theme-ui.com/sx-prop/#raw-css
*/
css?: Interpolation<ThemeUITheme>
}

export interface IntrinsicSxElements {
p: ThemeUIJSX.IntrinsicElements['p']
b: ThemeUIJSX.IntrinsicElements['b']
i: ThemeUIJSX.IntrinsicElements['i']
a: ThemeUIJSX.IntrinsicElements['a']
h1: ThemeUIJSX.IntrinsicElements['h1']
h2: ThemeUIJSX.IntrinsicElements['h2']
h3: ThemeUIJSX.IntrinsicElements['h3']
h4: ThemeUIJSX.IntrinsicElements['h4']
h5: ThemeUIJSX.IntrinsicElements['h5']
h6: ThemeUIJSX.IntrinsicElements['h6']
img: ThemeUIJSX.IntrinsicElements['img']
pre: ThemeUIJSX.IntrinsicElements['pre']
code: ThemeUIJSX.IntrinsicElements['code']
ol: ThemeUIJSX.IntrinsicElements['ol']
ul: ThemeUIJSX.IntrinsicElements['ul']
li: ThemeUIJSX.IntrinsicElements['li']
blockquote: ThemeUIJSX.IntrinsicElements['blockquote']
hr: ThemeUIJSX.IntrinsicElements['hr']
table: ThemeUIJSX.IntrinsicElements['table']
tr: ThemeUIJSX.IntrinsicElements['tr']
th: ThemeUIJSX.IntrinsicElements['th']
td: ThemeUIJSX.IntrinsicElements['td']
em: ThemeUIJSX.IntrinsicElements['em']
strong: ThemeUIJSX.IntrinsicElements['strong']
div: ThemeUIJSX.IntrinsicElements['div']
del: ThemeUIJSX.IntrinsicElements['div']
inlineCode: ThemeUIJSX.IntrinsicElements['div']
thematicBreak: ThemeUIJSX.IntrinsicElements['div']
root: ThemeUIJSX.IntrinsicElements['div']
}
36 changes: 35 additions & 1 deletion packages/core/test/react-jsx.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-lone-blocks */

/** @jsx jsx */
import { renderJSON, NotHas, Assert } from '@theme-ui/test-utils'
import { renderJSON, NotHas, Assert, expecter } from '@theme-ui/test-utils'

import { jsx, SxProp, ThemeUIJSX } from '../src'

Expand All @@ -26,6 +26,40 @@ describe('JSX', () => {
)
).toMatchSnapshot()
})

test('accepts css prop', () => {
const expectSnippet = expecter(
`/** @jsxImportSource @theme-ui/core */
export {}`,
{ jsx: false }
)

expectSnippet(`const _1 = <div css={{ color: 'red' }} />`).toSucceed()

// Theme UI theme can be injected to @emotion/react module in userspace
expectSnippet(
`
import { Theme as ThemeUITheme } from '@theme-ui/css'
declare module '@emotion/react' {
export interface Theme extends ThemeUITheme {}
}
<div
css={(t) => {
const _t = t;
return {}
}}
/>`
).toInfer('_t', 'Theme')

expectSnippet(
`import { css } from '@emotion/react'
const TestComponent = () => <div css={css\`display: block;\`} />`
).toSucceed()
})
})

{
Expand Down
9 changes: 0 additions & 9 deletions packages/css/emotion-theme.d.ts

This file was deleted.

153 changes: 77 additions & 76 deletions packages/css/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,99 +302,100 @@ const transforms = [
{}
)

const responsive = (
styles: Exclude<ThemeUIStyleObject, ThemeDerivedStyles>
) => (theme?: Theme) => {
const next: Exclude<ThemeUIStyleObject, ThemeDerivedStyles> = {}
const breakpoints =
(theme && (theme.breakpoints as string[])) || defaultBreakpoints
const mediaQueries = [
null,
...breakpoints.map((n) =>
n.includes('@media') ? n : `@media screen and (min-width: ${n})`
),
]
const responsive =
(styles: Exclude<ThemeUIStyleObject, ThemeDerivedStyles>) =>
(theme?: Theme) => {
const next: Exclude<ThemeUIStyleObject, ThemeDerivedStyles> = {}
const breakpoints =
(theme && (theme.breakpoints as string[])) || defaultBreakpoints
const mediaQueries = [
null,
...breakpoints.map((n) =>
n.includes('@media') ? n : `@media screen and (min-width: ${n})`
),
]

for (const k in styles) {
const key = k as keyof typeof styles
let value = styles[key]
if (typeof value === 'function') {
value = value(theme || {})
}
for (const k in styles) {
const key = k as keyof typeof styles
let value = styles[key]
if (typeof value === 'function') {
value = value(theme || {})
}

if (value === false || value == null) {
continue
}
if (!Array.isArray(value)) {
next[key] = value
continue
}
for (let i = 0; i < value.slice(0, mediaQueries.length).length; i++) {
const media = mediaQueries[i]
if (!media) {
next[key] = value[i]
if (value === false || value == null) {
continue
}
if (!Array.isArray(value)) {
next[key] = value
continue
}
next[media] = next[media] || {}
if (value[i] == null) continue
;(next[media] as Record<string, any>)[key] = value[i]
for (let i = 0; i < value.slice(0, mediaQueries.length).length; i++) {
const media = mediaQueries[i]
if (!media) {
next[key] = value[i]
continue
}
next[media] = next[media] || {}
if (value[i] == null) continue
;(next[media] as Record<string, any>)[key] = value[i]
}
}
return next
}
return next
}

type CssPropsArgument = { theme: Theme } | Theme

export const css = (args: ThemeUIStyleObject = {}) => (
props: CssPropsArgument = {}
): CSSObject => {
const theme: Theme = {
...defaultTheme,
...('theme' in props ? props.theme : props),
}
// insert variant props before responsive styles, so they can be merged
// we need to maintain order of the style props, so if a variant is place in the middle
// of other props, it will extends its props at that same location order.
export const css =
(args: ThemeUIStyleObject = {}) =>
(props: CssPropsArgument = {}): CSSObject => {
const theme: Theme = {
...defaultTheme,
...('theme' in props ? props.theme : props),
}
// insert variant props before responsive styles, so they can be merged
// we need to maintain order of the style props, so if a variant is place in the middle
// of other props, it will extends its props at that same location order.

const obj: CSSObject = getObjectWithVariants(
typeof args === 'function' ? args(theme) : args,
theme
)
const obj: CSSObject = getObjectWithVariants(
typeof args === 'function' ? args(theme) : args,
theme
)

const styles = responsive(obj as ThemeUICSSObject)(theme)
let result: CSSObject = {}
for (const key in styles) {
const x = styles[key as keyof typeof styles]
const val = typeof x === 'function' ? x(theme) : x
const styles = responsive(obj as ThemeUICSSObject)(theme)
let result: CSSObject = {}
for (const key in styles) {
const x = styles[key as keyof typeof styles]
const val = typeof x === 'function' ? x(theme) : x

if (val && typeof val === 'object') {
if (hasDefault(val)) {
result[key] = val[THEME_UI_DEFAULT_KEY]
continue
}

if (val && typeof val === 'object') {
if (hasDefault(val)) {
result[key] = val[THEME_UI_DEFAULT_KEY]
// On type level, val can also be an array here,
// but we transform all arrays in `responsive` function.
result[key] = css(val as ThemeUICSSObject)(theme)
continue
}

// On type level, val can also be an array here,
// but we transform all arrays in `responsive` function.
result[key] = css(val as ThemeUICSSObject)(theme)
continue
}
const prop = key in aliases ? aliases[key as keyof Aliases] : key
const scaleName =
prop in scales ? scales[prop as keyof Scales] : undefined
const scale = scaleName ? theme?.[scaleName] : get(theme, prop, {})
const transform = get(transforms, prop, get)
const value = transform(scale, val, val)

const prop = key in aliases ? aliases[key as keyof Aliases] : key
const scaleName = prop in scales ? scales[prop as keyof Scales] : undefined
const scale = scaleName ? theme?.[scaleName] : get(theme, prop, {})
const transform = get(transforms, prop, get)
const value = transform(scale, val, val)
if (prop in multiples) {
const dirs = multiples[prop as keyof typeof multiples]

if (prop in multiples) {
const dirs = multiples[prop as keyof typeof multiples]

for (let i = 0; i < dirs.length; i++) {
result[dirs[i]] = value
for (let i = 0; i < dirs.length; i++) {
result[dirs[i]] = value
}
} else {
result[prop] = value
}
} else {
result[prop] = value
}
}

return result
}
return result
}
Loading

0 comments on commit a7e27f6

Please sign in to comment.