|
| 1 | +import clsx from 'clsx' |
| 2 | +import ReactMarkdown from 'react-markdown' |
| 3 | +import {CustomLink} from 'web/components/links' |
| 4 | + |
| 5 | +/** |
| 6 | + * The answer body renderer. |
| 7 | + * |
| 8 | + * The old FAQ handed the entire file to `react-markdown` inside one `prose prose-neutral` div, which |
| 9 | + * is why it read as a document rather than as part of the product: `prose` is a *reset for unstyled |
| 10 | + * HTML*, not a design, so none of the tokens `/about` and `/home` are built on (`text-ink-600`, |
| 11 | + * `primary-*`, the leading scale) ever reached it. This maps the handful of node types an FAQ answer |
| 12 | + * actually uses onto those tokens instead. |
| 13 | + * |
| 14 | + * `a` goes through `CustomLink` — the FAQ links to `/news`, `/vote`, `/support`, `/stats` and a dozen |
| 15 | + * other internal routes, and `CustomLink` is what keeps those as client-side navigations while still |
| 16 | + * opening external links in a new tab. |
| 17 | + */ |
| 18 | +export function FaqMarkdown({children, className}: {children: string; className?: string}) { |
| 19 | + return ( |
| 20 | + <div className={clsx('text-[15px] leading-relaxed text-ink-600', className)}> |
| 21 | + <ReactMarkdown |
| 22 | + components={{ |
| 23 | + p: ({node: _node, children, ...props}) => ( |
| 24 | + <p className="mb-4 last:mb-0" {...props}> |
| 25 | + {children} |
| 26 | + </p> |
| 27 | + ), |
| 28 | + a: ({node: _node, children, ...props}) => ( |
| 29 | + <CustomLink |
| 30 | + className="font-medium text-primary-700 underline decoration-primary-500/35 underline-offset-2 transition-colors hover:decoration-primary-500" |
| 31 | + {...props} |
| 32 | + > |
| 33 | + {children} |
| 34 | + </CustomLink> |
| 35 | + ), |
| 36 | + strong: ({node: _node, children, ...props}) => ( |
| 37 | + <strong className="font-semibold text-ink-900" {...props}> |
| 38 | + {children} |
| 39 | + </strong> |
| 40 | + ), |
| 41 | + // A drawn marker rather than a native bullet: the bulleted answers are the page's densest |
| 42 | + // content, and a small primary dot pinned to the first line's optical centre is both calmer |
| 43 | + // than a default disc and consistent with the flow steps on /about. Done as a `before:` on |
| 44 | + // the children so the `li` renderer stays shared with ordered lists, which keep their real |
| 45 | + // numbers. |
| 46 | + // |
| 47 | + // `list-none pl-0 mt-0` undoes globals.css's blanket `ul { list-style: disc; padding-left: |
| 48 | + // 1.25rem; margin-top: 0.5rem }` — without it every bullet is drawn twice, once by the |
| 49 | + // browser and once by us, at two different indents. |
| 50 | + ul: ({node: _node, children, ...props}) => ( |
| 51 | + <ul |
| 52 | + className={clsx( |
| 53 | + 'mb-4 mt-0 list-none space-y-2.5 pl-0 last:mb-0', |
| 54 | + "[&>li]:relative [&>li]:pl-5 [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:top-[0.6em] [&>li]:before:h-1.5 [&>li]:before:w-1.5 [&>li]:before:rounded-full [&>li]:before:bg-primary-500/70 [&>li]:before:content-['']", |
| 55 | + )} |
| 56 | + {...props} |
| 57 | + > |
| 58 | + {children} |
| 59 | + </ul> |
| 60 | + ), |
| 61 | + // `list-outside` because the global `ol` rule sets `list-style-position: inside`, which puts |
| 62 | + // the number in the text flow and kills the hanging indent on wrapped lines. |
| 63 | + ol: ({node: _node, children, ...props}) => ( |
| 64 | + <ol |
| 65 | + className="mb-4 mt-0 list-decimal list-outside space-y-2.5 pl-5 last:mb-0 marker:text-ink-400" |
| 66 | + {...props} |
| 67 | + > |
| 68 | + {children} |
| 69 | + </ol> |
| 70 | + ), |
| 71 | + code: ({node: _node, children, ...props}) => ( |
| 72 | + <code |
| 73 | + className="rounded bg-canvas-100 px-1.5 py-0.5 font-mono text-[0.9em] text-ink-800" |
| 74 | + {...props} |
| 75 | + > |
| 76 | + {children} |
| 77 | + </code> |
| 78 | + ), |
| 79 | + blockquote: ({node: _node, children, ...props}) => ( |
| 80 | + <blockquote |
| 81 | + className="mb-4 border-l-2 border-primary-300 pl-4 italic last:mb-0" |
| 82 | + {...props} |
| 83 | + > |
| 84 | + {children} |
| 85 | + </blockquote> |
| 86 | + ), |
| 87 | + }} |
| 88 | + > |
| 89 | + {children} |
| 90 | + </ReactMarkdown> |
| 91 | + </div> |
| 92 | + ) |
| 93 | +} |
0 commit comments