Skip to content
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
2 changes: 1 addition & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface IntrinsicSxElements {
em: JSX.IntrinsicElements['em'] & SxProps
strong: JSX.IntrinsicElements['strong'] & SxProps
div: JSX.IntrinsicElements['div'] & SxProps
delete: JSX.IntrinsicElements['div'] & SxProps
del: JSX.IntrinsicElements['div'] & SxProps
inlineCode: JSX.IntrinsicElements['div'] & SxProps
thematicBreak: JSX.IntrinsicElements['div'] & SxProps
root: JSX.IntrinsicElements['div'] & SxProps
Expand Down
12 changes: 8 additions & 4 deletions packages/mdx/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
{
"name": "@theme-ui/mdx",
"version": "0.4.0-rc.1",
"source": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"scripts": {
"prepare": "microbundle --no-compress",
"watch": "microbundle watch --no-compress"
"prepare": "microbundle --no-compress --tsconfig tsconfig.json",
"watch": "microbundle watch --no-compress --tsconfig tsconfig.json"
},
"dependencies": {
"@emotion/core": "^10.0.0",
"@emotion/styled": "^10.0.0",
"@mdx-js/react": "^1.0.0"
},
"devDependencies": {
"@theme-ui/core": "^0.4.0-rc.1",
"@theme-ui/css": "^0.4.0-rc.1"
},
"peerDependencies": {
"@theme-ui/core": "*",
"@theme-ui/css": "*",
"react": "^16.11.0"
},
"author": "Brent Jackson",
Expand Down
86 changes: 0 additions & 86 deletions packages/mdx/src/index.js

This file was deleted.

152 changes: 152 additions & 0 deletions packages/mdx/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { jsx, IntrinsicSxElements } from '@theme-ui/core'
import { css, get, Theme } from '@theme-ui/css'
import {
ComponentType,
FC,
ReactNode,
DetailedHTMLProps,
HTMLAttributes,
ElementType,
ComponentProps,
} from 'react'
import styled, { StyledComponent } from '@emotion/styled'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB: more of a note for myself, but we should consider removing the styled dependency for v1 #832

import { MDXProvider as _MDXProvider, useMDXComponents } from '@mdx-js/react'

type MDXProviderComponentsKnownKeys = {
[key in keyof IntrinsicSxElements]?: React.ComponentType<any> | string
}
export interface MDXProviderComponents extends MDXProviderComponentsKnownKeys {
[key: string]: React.ComponentType<any> | string | undefined
}
export type MdxAliases = {
[key in keyof IntrinsicSxElements]: keyof JSX.IntrinsicElements
}

export type MdxAliasesKeys = 'inlineCode' | 'thematicBreak' | 'root'

export type ThemedProps = {
theme: Theme
}

export interface MdxProviderProps {
components?: MDXProviderComponents
children: ReactNode
}

// mdx components
const tags: Array<keyof IntrinsicSxElements> = [
'p',
'b',
'i',
'a',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'img',
'pre',
'code',
'ol',
'ul',
'li',
'blockquote',
'hr',
'em',
'table',
'tr',
'th',
'td',
'em',
'strong',
'del',
// mdx
'inlineCode',
'thematicBreak',
// other
'div',
// theme-ui
'root',
]

const aliases = {
inlineCode: 'code',
thematicBreak: 'hr',
root: 'div',
} as const

type Aliases = typeof aliases
const isAlias = (x: string): x is keyof Aliases => x in aliases

export type StyledComponentName =
| keyof IntrinsicSxElements
| keyof JSX.IntrinsicElements

const alias = (n: StyledComponentName): keyof JSX.IntrinsicElements =>
isAlias(n) ? aliases[n] : n

export const themed = (key: StyledComponentName) => (props: ThemedProps) =>
css(get(props.theme, `styles.${key}`))(props.theme)

// opt out of typechecking whenever `as` prop is used
export type WithPoorAsProp<
Props,
As extends ElementType | undefined = undefined
> = {
as?: As
} & (As extends undefined ? Props : { [key: string]: unknown })

export interface ThemedComponent<Name extends ElementType> {
<As extends ElementType | undefined>(
props: WithPoorAsProp<ComponentProps<Name>, As>
): JSX.Element
}

export type StyledComponentsDict = {
[K in keyof IntrinsicSxElements]: K extends keyof Aliases
? ThemedComponent<Aliases[K]>
: K extends keyof JSX.IntrinsicElements
? ThemedComponent<K>
: never
}

type StyledDiv = StyledComponent<
DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
ThemedProps,
Theme
>

export const Styled: StyledDiv & StyledComponentsDict = styled('div')(
themed('div')
) as StyledDiv & StyledComponentsDict

export const components = {} as StyledComponentsDict

tags.forEach((tag) => {
// fixme?
components[tag] = styled(alias(tag))(themed(tag)) as any
Styled[tag] = components[tag] as any
})

const createComponents = (comps: MDXProviderComponents) => {
const next = { ...components }

const componentKeys = Object.keys(comps) as Array<keyof IntrinsicSxElements>

componentKeys.forEach((key) => {
;(next[key] as StyledComponentsDict[typeof key]) = styled<any>(comps[key])(
themed(key)
) as StyledComponentsDict[typeof key]
})
return next
}

export const MDXProvider: FC<MdxProviderProps> = ({ components, children }) => {
const outer = useMDXComponents() as MDXProviderComponents

return jsx(_MDXProvider, {
components: createComponents({ ...outer, ...components }),
children,
})
}
73 changes: 55 additions & 18 deletions packages/mdx/test/index.js → packages/mdx/test/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
/** @jsx mdx */
import React, { useContext } from 'react'
import { mdx } from '@mdx-js/react'
import renderer from 'react-test-renderer'
import { matchers } from 'jest-emotion'
import mockConsole from 'jest-mock-console'
import { ThemeProvider } from '@theme-ui/core'
import {
themed,
Styled,
components,
MDXProvider,
} from '../src'
import { themed, Styled, components, MDXProvider } from '../src'

expect.extend(matchers)

const renderJSON = el => renderer.create(el).toJSON()
const renderJSON = (el) => renderer.create(el).toJSON()

test('styles React components', () => {
const Beep = props => <h2 {...props} />
const Inner = props => mdx('Beep', props)
const Beep = (props) => <h2 {...props} />
const Inner = (props) => mdx('Beep', props)

const json = renderJSON(
<ThemeProvider
Expand All @@ -36,13 +29,13 @@ test('styles React components', () => {
<Inner />
</MDXProvider>
</ThemeProvider>
)
)!
expect(json.type).toBe('h2')
expect(json).toHaveStyleRule('color', 'tomato')
})

test('components accept an `as` prop', () => {
const Beep = props => <h2 {...props} />
const Beep = (props) => <h2 {...props} />
const json = renderJSON(
<ThemeProvider
theme={{
Expand All @@ -56,24 +49,68 @@ test('components accept an `as` prop', () => {
<Styled.h1 as={Beep}>Beep boop</Styled.h1>
</MDXProvider>
</ThemeProvider>
)
)!
expect(json.type).toBe('h2')
expect(json).toHaveStyleRule('color', 'tomato')
})

test('components with `as` prop receive all props', () => {
const Beep = props => <div {...props} />
const json = renderJSON(<Styled.a as={Beep} activeClassName="active" />)
const Beep = (props) => <div {...props} />
const json = renderJSON(<Styled.a as={Beep} activeClassName="active" />)!
expect(json.type).toBe('div')
expect(json.props.activeClassName).toBe('active')
})

test('cleans up style props', () => {
const json = renderJSON(
<Styled.h1 mx={2} id='test'>
<Styled.h1 mx={2} id="test">
Hello
</Styled.h1>
)
)!
expect(json.props.id).toBe('test')
expect(json.props.mx).not.toBeDefined()
})

test('themed extracts styles from the theme', () => {
expect(
themed('footer')({
theme: { styles: { footer: { background: 'skyblue' } } },
})
).toStrictEqual({ background: 'skyblue' })
})

test('keys of components match snapshot', () => {
expect(Object.keys(components)).toMatchInlineSnapshot(`
Array [
"p",
"b",
"i",
"a",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"img",
"pre",
"code",
"ol",
"ul",
"li",
"blockquote",
"hr",
"em",
"table",
"tr",
"th",
"td",
"strong",
"del",
"inlineCode",
"thematicBreak",
"div",
"root",
]
`)
})
Loading