1import { css, CSSObject } from '@emotion/react'; 2import { typography } from '@expo/styleguide'; 3import type { CSSProperties, ComponentType, PropsWithChildren } from 'react'; 4 5import { Code as PrismCodeBlock } from '~/components/base/code'; 6import { Callout } from '~/ui/components/Callout'; 7import { Cell, HeaderCell, Row, Table, TableHead } from '~/ui/components/Table'; 8import { H1, H2, H3, H4, H5, A, CODE, P, BOLD, UL, OL, LI, KBD } from '~/ui/components/Text'; 9 10type Config = ConfigStyles & { 11 Component: ComponentType<PropsWithChildren<ComponentProps>> | string; 12}; 13 14type ConfigStyles = { 15 css?: CSSObject; 16 style?: CSSProperties; 17}; 18 19type ComponentProps = PropsWithChildren<{ 20 className?: string; 21 style?: CSSProperties; 22}>; 23 24const markdownStyles: Record<string, Config | null> = { 25 h1: { 26 Component: H1, 27 }, 28 h2: { 29 Component: H2, 30 }, 31 h3: { 32 Component: H3, 33 }, 34 h4: { 35 Component: H4, 36 }, 37 h5: { 38 Component: H5, 39 }, 40 p: { 41 Component: P, 42 style: { marginBottom: '1.5ch' }, 43 }, 44 strong: { 45 Component: BOLD, 46 }, 47 ul: { 48 Component: UL, 49 style: { marginBottom: '1.5ch', paddingLeft: `1ch` }, 50 }, 51 ol: { 52 Component: OL, 53 style: { marginBottom: '1.5ch', paddingLeft: `1ch` }, 54 }, 55 li: { 56 Component: LI, 57 }, 58 hr: { 59 Component: 'hr', 60 css: typography.utility.hr, 61 style: { margin: `2ch 0` }, 62 }, 63 blockquote: { 64 Component: Callout, 65 }, 66 img: { 67 Component: 'img', 68 style: { width: '100%' }, 69 }, 70 code: { 71 Component: CODE, 72 }, 73 pre: { 74 Component: PrismCodeBlock, 75 }, 76 a: { 77 Component: A, 78 }, 79 table: { 80 Component: Table, 81 }, 82 thead: { 83 Component: TableHead, 84 }, 85 tr: { 86 Component: Row, 87 }, 88 th: { 89 Component: HeaderCell, 90 }, 91 td: { 92 Component: Cell, 93 }, 94 kbd: { 95 Component: KBD, 96 }, 97}; 98 99type MarkdownComponent = Record<keyof typeof markdownStyles, any>; 100 101export const markdownComponents: MarkdownComponent = Object.keys(markdownStyles).reduce( 102 (all, key) => ({ 103 ...all, 104 [key]: markdownStyles[key] ? createMarkdownComponent(markdownStyles[key]!) : null, 105 }), 106 {} 107); 108 109function componentName({ Component }: Config) { 110 if (typeof Component === 'string') return Component; 111 return Component.displayName || Component.name || 'Anonymous'; 112} 113 114function createMarkdownComponent(config: Config): ComponentType<ComponentProps> { 115 const { Component, css: cssClassname, style } = config; 116 const MDXComponent = (props: ComponentProps) => ( 117 <Component {...props} css={css(cssClassname)} style={style}> 118 {props.children} 119 </Component> 120 ); 121 MDXComponent.displayName = `Markdown(${componentName(config)})`; 122 return MDXComponent; 123} 124