Skip to content

Commit f5dd5b3

Browse files
authored
Merge pull request #1031 from hasparus/ts-mdx-2
(@theme-ui/mdx) adopt TypeScript
2 parents 3205a79 + 7d3b8a2 commit f5dd5b3

File tree

7 files changed

+223
-117
lines changed

7 files changed

+223
-117
lines changed

packages/core/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export interface IntrinsicSxElements {
4848
em: JSX.IntrinsicElements['em'] & SxProps
4949
strong: JSX.IntrinsicElements['strong'] & SxProps
5050
div: JSX.IntrinsicElements['div'] & SxProps
51-
delete: JSX.IntrinsicElements['div'] & SxProps
51+
del: JSX.IntrinsicElements['div'] & SxProps
5252
inlineCode: JSX.IntrinsicElements['div'] & SxProps
5353
thematicBreak: JSX.IntrinsicElements['div'] & SxProps
5454
root: JSX.IntrinsicElements['div'] & SxProps

packages/mdx/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
{
22
"name": "@theme-ui/mdx",
33
"version": "0.4.0-rc.1",
4+
"source": "src/index.ts",
45
"main": "dist/index.js",
56
"module": "dist/index.esm.js",
7+
"types": "dist/index.d.ts",
68
"sideEffects": false,
79
"scripts": {
8-
"prepare": "microbundle --no-compress",
9-
"watch": "microbundle watch --no-compress"
10+
"prepare": "microbundle --no-compress --tsconfig tsconfig.json",
11+
"watch": "microbundle watch --no-compress --tsconfig tsconfig.json"
1012
},
1113
"dependencies": {
1214
"@emotion/core": "^10.0.0",
1315
"@emotion/styled": "^10.0.0",
1416
"@mdx-js/react": "^1.0.0"
1517
},
18+
"devDependencies": {
19+
"@theme-ui/core": "^0.4.0-rc.1",
20+
"@theme-ui/css": "^0.4.0-rc.1"
21+
},
1622
"peerDependencies": {
17-
"@theme-ui/core": "*",
18-
"@theme-ui/css": "*",
1923
"react": "^16.11.0"
2024
},
2125
"author": "Brent Jackson",

packages/mdx/src/index.js

Lines changed: 0 additions & 86 deletions
This file was deleted.

packages/mdx/src/index.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { jsx, IntrinsicSxElements } from '@theme-ui/core'
2+
import { css, get, Theme } from '@theme-ui/css'
3+
import {
4+
ComponentType,
5+
FC,
6+
ReactNode,
7+
DetailedHTMLProps,
8+
HTMLAttributes,
9+
ElementType,
10+
ComponentProps,
11+
} from 'react'
12+
import styled, { StyledComponent } from '@emotion/styled'
13+
import { MDXProvider as _MDXProvider, useMDXComponents } from '@mdx-js/react'
14+
15+
type MDXProviderComponentsKnownKeys = {
16+
[key in keyof IntrinsicSxElements]?: React.ComponentType<any> | string
17+
}
18+
export interface MDXProviderComponents extends MDXProviderComponentsKnownKeys {
19+
[key: string]: React.ComponentType<any> | string | undefined
20+
}
21+
export type MdxAliases = {
22+
[key in keyof IntrinsicSxElements]: keyof JSX.IntrinsicElements
23+
}
24+
25+
export type MdxAliasesKeys = 'inlineCode' | 'thematicBreak' | 'root'
26+
27+
export type ThemedProps = {
28+
theme: Theme
29+
}
30+
31+
export interface MdxProviderProps {
32+
components?: MDXProviderComponents
33+
children: ReactNode
34+
}
35+
36+
// mdx components
37+
const tags: Array<keyof IntrinsicSxElements> = [
38+
'p',
39+
'b',
40+
'i',
41+
'a',
42+
'h1',
43+
'h2',
44+
'h3',
45+
'h4',
46+
'h5',
47+
'h6',
48+
'img',
49+
'pre',
50+
'code',
51+
'ol',
52+
'ul',
53+
'li',
54+
'blockquote',
55+
'hr',
56+
'em',
57+
'table',
58+
'tr',
59+
'th',
60+
'td',
61+
'em',
62+
'strong',
63+
'del',
64+
// mdx
65+
'inlineCode',
66+
'thematicBreak',
67+
// other
68+
'div',
69+
// theme-ui
70+
'root',
71+
]
72+
73+
const aliases = {
74+
inlineCode: 'code',
75+
thematicBreak: 'hr',
76+
root: 'div',
77+
} as const
78+
79+
type Aliases = typeof aliases
80+
const isAlias = (x: string): x is keyof Aliases => x in aliases
81+
82+
export type StyledComponentName =
83+
| keyof IntrinsicSxElements
84+
| keyof JSX.IntrinsicElements
85+
86+
const alias = (n: StyledComponentName): keyof JSX.IntrinsicElements =>
87+
isAlias(n) ? aliases[n] : n
88+
89+
export const themed = (key: StyledComponentName) => (props: ThemedProps) =>
90+
css(get(props.theme, `styles.${key}`))(props.theme)
91+
92+
// opt out of typechecking whenever `as` prop is used
93+
export type WithPoorAsProp<
94+
Props,
95+
As extends ElementType | undefined = undefined
96+
> = {
97+
as?: As
98+
} & (As extends undefined ? Props : { [key: string]: unknown })
99+
100+
export interface ThemedComponent<Name extends ElementType> {
101+
<As extends ElementType | undefined>(
102+
props: WithPoorAsProp<ComponentProps<Name>, As>
103+
): JSX.Element
104+
}
105+
106+
export type StyledComponentsDict = {
107+
[K in keyof IntrinsicSxElements]: K extends keyof Aliases
108+
? ThemedComponent<Aliases[K]>
109+
: K extends keyof JSX.IntrinsicElements
110+
? ThemedComponent<K>
111+
: never
112+
}
113+
114+
type StyledDiv = StyledComponent<
115+
DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
116+
ThemedProps,
117+
Theme
118+
>
119+
120+
export const Styled: StyledDiv & StyledComponentsDict = styled('div')(
121+
themed('div')
122+
) as StyledDiv & StyledComponentsDict
123+
124+
export const components = {} as StyledComponentsDict
125+
126+
tags.forEach((tag) => {
127+
// fixme?
128+
components[tag] = styled(alias(tag))(themed(tag)) as any
129+
Styled[tag] = components[tag] as any
130+
})
131+
132+
const createComponents = (comps: MDXProviderComponents) => {
133+
const next = { ...components }
134+
135+
const componentKeys = Object.keys(comps) as Array<keyof IntrinsicSxElements>
136+
137+
componentKeys.forEach((key) => {
138+
;(next[key] as StyledComponentsDict[typeof key]) = styled<any>(comps[key])(
139+
themed(key)
140+
) as StyledComponentsDict[typeof key]
141+
})
142+
return next
143+
}
144+
145+
export const MDXProvider: FC<MdxProviderProps> = ({ components, children }) => {
146+
const outer = useMDXComponents() as MDXProviderComponents
147+
148+
return jsx(_MDXProvider, {
149+
components: createComponents({ ...outer, ...components }),
150+
children,
151+
})
152+
}
Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
/** @jsx mdx */
2-
import React, { useContext } from 'react'
32
import { mdx } from '@mdx-js/react'
43
import renderer from 'react-test-renderer'
54
import { matchers } from 'jest-emotion'
6-
import mockConsole from 'jest-mock-console'
75
import { ThemeProvider } from '@theme-ui/core'
8-
import {
9-
themed,
10-
Styled,
11-
components,
12-
MDXProvider,
13-
} from '../src'
6+
import { themed, Styled, components, MDXProvider } from '../src'
147

158
expect.extend(matchers)
169

17-
const renderJSON = el => renderer.create(el).toJSON()
10+
const renderJSON = (el) => renderer.create(el).toJSON()
1811

1912
test('styles React components', () => {
20-
const Beep = props => <h2 {...props} />
21-
const Inner = props => mdx('Beep', props)
13+
const Beep = (props) => <h2 {...props} />
14+
const Inner = (props) => mdx('Beep', props)
2215

2316
const json = renderJSON(
2417
<ThemeProvider
@@ -36,13 +29,13 @@ test('styles React components', () => {
3629
<Inner />
3730
</MDXProvider>
3831
</ThemeProvider>
39-
)
32+
)!
4033
expect(json.type).toBe('h2')
4134
expect(json).toHaveStyleRule('color', 'tomato')
4235
})
4336

4437
test('components accept an `as` prop', () => {
45-
const Beep = props => <h2 {...props} />
38+
const Beep = (props) => <h2 {...props} />
4639
const json = renderJSON(
4740
<ThemeProvider
4841
theme={{
@@ -56,24 +49,68 @@ test('components accept an `as` prop', () => {
5649
<Styled.h1 as={Beep}>Beep boop</Styled.h1>
5750
</MDXProvider>
5851
</ThemeProvider>
59-
)
52+
)!
6053
expect(json.type).toBe('h2')
6154
expect(json).toHaveStyleRule('color', 'tomato')
6255
})
6356

6457
test('components with `as` prop receive all props', () => {
65-
const Beep = props => <div {...props} />
66-
const json = renderJSON(<Styled.a as={Beep} activeClassName="active" />)
58+
const Beep = (props) => <div {...props} />
59+
const json = renderJSON(<Styled.a as={Beep} activeClassName="active" />)!
6760
expect(json.type).toBe('div')
6861
expect(json.props.activeClassName).toBe('active')
6962
})
7063

7164
test('cleans up style props', () => {
7265
const json = renderJSON(
73-
<Styled.h1 mx={2} id='test'>
66+
<Styled.h1 mx={2} id="test">
7467
Hello
7568
</Styled.h1>
76-
)
69+
)!
7770
expect(json.props.id).toBe('test')
7871
expect(json.props.mx).not.toBeDefined()
7972
})
73+
74+
test('themed extracts styles from the theme', () => {
75+
expect(
76+
themed('footer')({
77+
theme: { styles: { footer: { background: 'skyblue' } } },
78+
})
79+
).toStrictEqual({ background: 'skyblue' })
80+
})
81+
82+
test('keys of components match snapshot', () => {
83+
expect(Object.keys(components)).toMatchInlineSnapshot(`
84+
Array [
85+
"p",
86+
"b",
87+
"i",
88+
"a",
89+
"h1",
90+
"h2",
91+
"h3",
92+
"h4",
93+
"h5",
94+
"h6",
95+
"img",
96+
"pre",
97+
"code",
98+
"ol",
99+
"ul",
100+
"li",
101+
"blockquote",
102+
"hr",
103+
"em",
104+
"table",
105+
"tr",
106+
"th",
107+
"td",
108+
"strong",
109+
"del",
110+
"inlineCode",
111+
"thematicBreak",
112+
"div",
113+
"root",
114+
]
115+
`)
116+
})

0 commit comments

Comments
 (0)