Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add types for css prop back #1866

Merged
merged 1 commit into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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