xref: /expo/docs/ui/components/Markdown/index.tsx (revision bcc59125)
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