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