1adfb0600SCedric van Puttenimport { css } from '@emotion/react';
2*f4b1168bSBartosz Kaszubowskiimport { spacing } from '@expo/styleguide-base';
3*f4b1168bSBartosz Kaszubowskiimport { useMemo } from 'react';
4adfb0600SCedric van Putten
5adfb0600SCedric van Puttenimport { HeadingEntry, useHeadingsObserver } from './useHeadingObserver';
6adfb0600SCedric van Putten
7adfb0600SCedric van Puttenimport { LayoutScroll, useAutoScrollTo } from '~/ui/components/Layout';
8adfb0600SCedric van Puttenimport { A, CALLOUT } from '~/ui/components/Text';
9adfb0600SCedric van Putten
10adfb0600SCedric van Puttentype TableOfContentsLinkProps = HeadingEntry & {
11adfb0600SCedric van Putten  isActive?: boolean;
12adfb0600SCedric van Putten};
13adfb0600SCedric van Putten
14adfb0600SCedric van Puttenexport function TableOfContents() {
15adfb0600SCedric van Putten  const { headings, activeId } = useHeadingsObserver();
16adfb0600SCedric van Putten  const autoScroll = useAutoScrollTo(activeId ? `[data-toc-id="${activeId}"]` : '');
17adfb0600SCedric van Putten
18adfb0600SCedric van Putten  return (
19adfb0600SCedric van Putten    <LayoutScroll ref={autoScroll.ref}>
20adfb0600SCedric van Putten      <nav css={containerStyle}>
21adfb0600SCedric van Putten        <CALLOUT css={titleStyle} weight="medium">
22adfb0600SCedric van Putten          On this page
23adfb0600SCedric van Putten        </CALLOUT>
24adfb0600SCedric van Putten        <ul css={listStyle}>
25adfb0600SCedric van Putten          {headings.map(heading => (
26adfb0600SCedric van Putten            <li key={`heading-${heading.id}`} data-toc-id={heading.id}>
27adfb0600SCedric van Putten              <TableOfContentsLink {...heading} isActive={heading.id === activeId} />
28adfb0600SCedric van Putten            </li>
29adfb0600SCedric van Putten          ))}
30adfb0600SCedric van Putten        </ul>
31adfb0600SCedric van Putten      </nav>
32adfb0600SCedric van Putten    </LayoutScroll>
33adfb0600SCedric van Putten  );
34adfb0600SCedric van Putten}
35adfb0600SCedric van Putten
36adfb0600SCedric van Puttenfunction TableOfContentsLink({ id, element, isActive }: TableOfContentsLinkProps) {
37adfb0600SCedric van Putten  const info = useMemo(() => getHeadingInfo(element), [element]);
38adfb0600SCedric van Putten
39adfb0600SCedric van Putten  return (
40adfb0600SCedric van Putten    <A css={[linkStyle, getHeadingIndent(element)]} href={`#${id}`}>
41adfb0600SCedric van Putten      <CALLOUT weight={isActive ? 'medium' : 'regular'} tag="span">
42adfb0600SCedric van Putten        {info.text}
43adfb0600SCedric van Putten      </CALLOUT>
44adfb0600SCedric van Putten    </A>
45adfb0600SCedric van Putten  );
46adfb0600SCedric van Putten}
47adfb0600SCedric van Putten
48adfb0600SCedric van Puttenconst containerStyle = css({
49adfb0600SCedric van Putten  display: 'block',
50adfb0600SCedric van Putten  width: '100%',
51adfb0600SCedric van Putten  padding: spacing[8],
52adfb0600SCedric van Putten});
53adfb0600SCedric van Putten
54adfb0600SCedric van Puttenconst titleStyle = css({
55adfb0600SCedric van Putten  marginTop: spacing[4],
56adfb0600SCedric van Putten  marginBottom: spacing[1.5],
57adfb0600SCedric van Putten});
58adfb0600SCedric van Putten
59adfb0600SCedric van Puttenconst listStyle = css({
60adfb0600SCedric van Putten  listStyle: 'none',
61adfb0600SCedric van Putten});
62adfb0600SCedric van Putten
63adfb0600SCedric van Puttenconst linkStyle = css({
64adfb0600SCedric van Putten  display: 'block',
65adfb0600SCedric van Putten  padding: `${spacing[1.5]}px 0`,
66adfb0600SCedric van Putten});
67adfb0600SCedric van Putten
68adfb0600SCedric van Puttenexport function getHeadingIndent(heading: HTMLHeadingElement) {
69adfb0600SCedric van Putten  const level = Math.max(Number(heading.tagName.slice(1)) - 2, 0);
70adfb0600SCedric van Putten  return { paddingLeft: spacing[2] * level };
71adfb0600SCedric van Putten}
72adfb0600SCedric van Putten
73adfb0600SCedric van Putten/**
74adfb0600SCedric van Putten * Parse the heading information from an HTML heading element.
75640b8015Sapeltop * If it contains parenthesis, we try to extract the function name only.
76adfb0600SCedric van Putten */
77adfb0600SCedric van Puttenexport function getHeadingInfo(heading: HTMLHeadingElement) {
78adfb0600SCedric van Putten  const text = heading.textContent || '';
79adfb0600SCedric van Putten  const functionOpenChar = text.indexOf('(');
80adfb0600SCedric van Putten
81adfb0600SCedric van Putten  return functionOpenChar >= 0 && text[functionOpenChar - 1].trim()
82adfb0600SCedric van Putten    ? { type: 'code', text: text.slice(0, functionOpenChar) }
83adfb0600SCedric van Putten    : { type: 'text', text };
84adfb0600SCedric van Putten}
85