15b6cd93dSBartosz Kaszubowskiimport { css } from '@emotion/react'; 2c4c6b9d1SBartosz Kaszubowskiimport { theme, typography } from '@expo/styleguide'; 3c730ceaaSBartosz Kaszubowskiimport Link from 'next/link'; 4586106d6SBartłomiej Klocekimport * as React from 'react'; 5586106d6SBartłomiej Klocek 6c4c6b9d1SBartosz Kaszubowskiimport { BASE_HEADING_LEVEL, Heading, HeadingType } from '~/common/headingManager'; 7c4c6b9d1SBartosz Kaszubowskiimport { Tag } from '~/ui/components/Tag'; 8db57e342SBartosz Kaszubowskiimport { MONOSPACE, CALLOUT } from '~/ui/components/Text'; 9586106d6SBartłomiej Klocek 10586106d6SBartłomiej Klocekconst STYLES_LINK = css` 118bf05203SJon Samp transition: 50ms ease color; 12c4c6b9d1SBartosz Kaszubowski display: flex; 13586106d6SBartłomiej Klocek text-decoration: none; 14586106d6SBartłomiej Klocek margin-bottom: 6px; 15586106d6SBartłomiej Klocek cursor: pointer; 16c4c6b9d1SBartosz Kaszubowski overflow: hidden; 17c4c6b9d1SBartosz Kaszubowski text-overflow: ellipsis; 18c4c6b9d1SBartosz Kaszubowski align-items: center; 19c4c6b9d1SBartosz Kaszubowski justify-content: space-between; 20*8c97fc97SBartosz Kaszubowski 21*8c97fc97SBartosz Kaszubowski :focus-visible { 22*8c97fc97SBartosz Kaszubowski position: relative; 23*8c97fc97SBartosz Kaszubowski z-index: 10; 24*8c97fc97SBartosz Kaszubowski } 25586106d6SBartłomiej Klocek`; 26586106d6SBartłomiej Klocek 27c4c6b9d1SBartosz Kaszubowskiconst STYLES_LINK_LABEL = css` 28db57e342SBartosz Kaszubowski color: ${theme.text.secondary}; 29db57e342SBartosz Kaszubowski 30db57e342SBartosz Kaszubowski :hover { 3114c78e61SJon Samp color: ${theme.text.link}; 32db57e342SBartosz Kaszubowski } 33586106d6SBartłomiej Klocek`; 34586106d6SBartłomiej Klocek 35db57e342SBartosz Kaszubowskiconst STYLES_LINK_MONOSPACE = css` 3649324930SBartosz Kaszubowski overflow: hidden; 3749324930SBartosz Kaszubowski text-overflow: ellipsis; 3849324930SBartosz Kaszubowski white-space: nowrap; 39db57e342SBartosz Kaszubowski ${typography.fontSizes[13]} 40c4c6b9d1SBartosz Kaszubowski`; 41c4c6b9d1SBartosz Kaszubowski 42586106d6SBartłomiej Klocekconst STYLES_LINK_ACTIVE = css` 4314c78e61SJon Samp color: ${theme.text.link}; 44586106d6SBartłomiej Klocek`; 45586106d6SBartłomiej Klocek 46586106d6SBartłomiej Klocekconst STYLES_TOOLTIP = css` 47586106d6SBartłomiej Klocek border-radius: 3px; 48586106d6SBartłomiej Klocek position: absolute; 4914c78e61SJon Samp background-color: ${theme.background.subtle}; 50586106d6SBartłomiej Klocek max-width: 400px; 51c4c6b9d1SBartosz Kaszubowski border: 1px solid ${theme.border.default}; 52586106d6SBartłomiej Klocek padding: 3px 6px; 53586106d6SBartłomiej Klocek display: inline-block; 54586106d6SBartłomiej Klocek`; 55586106d6SBartłomiej Klocek 56db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_TEXT = css` 57db57e342SBartosz Kaszubowski ${typography.fontSizes[13]} 58db57e342SBartosz Kaszubowski color: ${theme.text.default}; 59db57e342SBartosz Kaszubowski word-break: break-word; 60db57e342SBartosz Kaszubowski word-wrap: normal; 61db57e342SBartosz Kaszubowski`; 62db57e342SBartosz Kaszubowski 63db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_CODE = css` 64db57e342SBartosz Kaszubowski ${typography.fontSizes[12]} 65586106d6SBartłomiej Klocek`; 66586106d6SBartłomiej Klocek 67c4c6b9d1SBartosz Kaszubowskiconst STYLES_TAG_CONTAINER = css` 68c4c6b9d1SBartosz Kaszubowski display: inline-flex; 69c4c6b9d1SBartosz Kaszubowski`; 70c4c6b9d1SBartosz Kaszubowski 71c4c6b9d1SBartosz Kaszubowskiconst NESTING_OFFSET = 12; 72586106d6SBartłomiej Klocek 73586106d6SBartłomiej Klocek/** 74586106d6SBartłomiej Klocek * Replaces `Module.someFunction(arguments: argType)` 75586106d6SBartłomiej Klocek * with `someFunction()` 76586106d6SBartłomiej Klocek */ 77586106d6SBartłomiej Klocekconst trimCodedTitle = (str: string) => { 78586106d6SBartłomiej Klocek const dotIdx = str.indexOf('.'); 79586106d6SBartłomiej Klocek if (dotIdx > 0) str = str.substring(dotIdx + 1); 80586106d6SBartłomiej Klocek 81586106d6SBartłomiej Klocek const parIdx = str.indexOf('('); 82586106d6SBartłomiej Klocek if (parIdx > 0) str = str.substring(0, parIdx + 1) + ')'; 83586106d6SBartłomiej Klocek 84586106d6SBartłomiej Klocek return str; 85586106d6SBartłomiej Klocek}; 86586106d6SBartłomiej Klocek 87586106d6SBartłomiej Klocek/** 88586106d6SBartłomiej Klocek * Determines if element is overflowing 89c4c6b9d1SBartosz Kaszubowski * (its children width exceeds container width) 90586106d6SBartłomiej Klocek * @param {HTMLElement} el element to check 91586106d6SBartłomiej Klocek */ 92586106d6SBartłomiej Klocekconst isOverflowing = (el: HTMLElement) => { 93c4c6b9d1SBartosz Kaszubowski if (!el || !el.children) { 94586106d6SBartłomiej Klocek return false; 95586106d6SBartłomiej Klocek } 96586106d6SBartłomiej Klocek 97c4c6b9d1SBartosz Kaszubowski const childrenWidth = Array.from(el.children).reduce((sum, child) => sum + child.scrollWidth, 0); 98c730ceaaSBartosz Kaszubowski const indent = parseInt(window.getComputedStyle(el).paddingLeft, 10); 99c730ceaaSBartosz Kaszubowski return childrenWidth >= el.scrollWidth - indent; 100586106d6SBartłomiej Klocek}; 101586106d6SBartłomiej Klocek 1021f61a071SBartosz Kaszubowskitype TooltipProps = React.PropsWithChildren<{ 103586106d6SBartłomiej Klocek isCode?: boolean; 104586106d6SBartłomiej Klocek topOffset: number; 1051f61a071SBartosz Kaszubowski}>; 106586106d6SBartłomiej Klocek 107db57e342SBartosz Kaszubowskiconst Tooltip = ({ children, isCode, topOffset }: TooltipProps) => { 108db57e342SBartosz Kaszubowski const ContentWrapper = isCode ? MONOSPACE : CALLOUT; 109db57e342SBartosz Kaszubowski return ( 110db57e342SBartosz Kaszubowski <div css={STYLES_TOOLTIP} style={{ right: 24, top: topOffset }}> 111db57e342SBartosz Kaszubowski <ContentWrapper css={[STYLES_TOOLTIP_TEXT, isCode && STYLES_TOOLTIP_CODE]}> 112586106d6SBartłomiej Klocek {children} 113db57e342SBartosz Kaszubowski </ContentWrapper> 114586106d6SBartłomiej Klocek </div> 115586106d6SBartłomiej Klocek ); 116db57e342SBartosz Kaszubowski}; 117586106d6SBartłomiej Klocek 118586106d6SBartłomiej Klocektype SidebarLinkProps = { 119586106d6SBartłomiej Klocek heading: Heading; 120586106d6SBartłomiej Klocek isActive: boolean; 121586106d6SBartłomiej Klocek shortenCode: boolean; 122586106d6SBartłomiej Klocek onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void; 123586106d6SBartłomiej Klocek}; 124586106d6SBartłomiej Klocek 125586106d6SBartłomiej Klocekconst DocumentationSidebarRightLink = React.forwardRef<HTMLAnchorElement, SidebarLinkProps>( 126586106d6SBartłomiej Klocek ({ heading, isActive, shortenCode, onClick }, ref) => { 127c4c6b9d1SBartosz Kaszubowski const { slug, level, title, type, tags } = heading; 128586106d6SBartłomiej Klocek 129586106d6SBartłomiej Klocek const isCode = type === HeadingType.InlineCode; 130db57e342SBartosz Kaszubowski const paddingLeft = NESTING_OFFSET * (level - BASE_HEADING_LEVEL); 131586106d6SBartłomiej Klocek const displayTitle = shortenCode && isCode ? trimCodedTitle(title) : title; 132586106d6SBartłomiej Klocek 133586106d6SBartłomiej Klocek const [tooltipVisible, setTooltipVisible] = React.useState(false); 134586106d6SBartłomiej Klocek const [tooltipOffset, setTooltipOffset] = React.useState(-20); 135586106d6SBartłomiej Klocek const onMouseOver = (event: React.MouseEvent<HTMLAnchorElement>) => { 136586106d6SBartłomiej Klocek setTooltipVisible(isOverflowing(event.currentTarget)); 137c4c6b9d1SBartosz Kaszubowski setTooltipOffset( 138c4c6b9d1SBartosz Kaszubowski event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetHeight 139c4c6b9d1SBartosz Kaszubowski ); 140586106d6SBartłomiej Klocek }; 141c4c6b9d1SBartosz Kaszubowski 142586106d6SBartłomiej Klocek const onMouseOut = () => { 143586106d6SBartłomiej Klocek setTooltipVisible(false); 144586106d6SBartłomiej Klocek }; 145586106d6SBartłomiej Klocek 146db57e342SBartosz Kaszubowski const TitleElement = isCode ? MONOSPACE : CALLOUT; 147db57e342SBartosz Kaszubowski 148586106d6SBartłomiej Klocek return ( 149586106d6SBartłomiej Klocek <> 15049324930SBartosz Kaszubowski {tooltipVisible && isCode && ( 151586106d6SBartłomiej Klocek <Tooltip topOffset={tooltipOffset} isCode={isCode}> 152586106d6SBartłomiej Klocek {displayTitle} 153586106d6SBartłomiej Klocek </Tooltip> 154586106d6SBartłomiej Klocek )} 155c730ceaaSBartosz Kaszubowski <Link 156586106d6SBartłomiej Klocek ref={ref} 15749324930SBartosz Kaszubowski onMouseOver={isCode ? onMouseOver : undefined} 15849324930SBartosz Kaszubowski onMouseOut={isCode ? onMouseOut : undefined} 159586106d6SBartłomiej Klocek href={'#' + slug} 160586106d6SBartłomiej Klocek onClick={onClick} 161db57e342SBartosz Kaszubowski css={[STYLES_LINK, paddingLeft && { paddingLeft }]}> 162db57e342SBartosz Kaszubowski <TitleElement 163db57e342SBartosz Kaszubowski css={[ 164db57e342SBartosz Kaszubowski STYLES_LINK_LABEL, 165db57e342SBartosz Kaszubowski isCode && STYLES_LINK_MONOSPACE, 166db57e342SBartosz Kaszubowski isActive && STYLES_LINK_ACTIVE, 167db57e342SBartosz Kaszubowski ]}> 168db57e342SBartosz Kaszubowski {displayTitle} 169db57e342SBartosz Kaszubowski </TitleElement> 170c4c6b9d1SBartosz Kaszubowski {tags && tags.length ? ( 171c4c6b9d1SBartosz Kaszubowski <div css={STYLES_TAG_CONTAINER}> 172c4c6b9d1SBartosz Kaszubowski {tags.map(tag => ( 17322ee2951SBartosz Kaszubowski <Tag name={tag} type="toc" key={`${displayTitle}-${tag}`} /> 174c4c6b9d1SBartosz Kaszubowski ))} 175c4c6b9d1SBartosz Kaszubowski </div> 176c4c6b9d1SBartosz Kaszubowski ) : undefined} 177c730ceaaSBartosz Kaszubowski </Link> 178586106d6SBartłomiej Klocek </> 179586106d6SBartłomiej Klocek ); 180586106d6SBartłomiej Klocek } 181586106d6SBartłomiej Klocek); 182586106d6SBartłomiej Klocek 183586106d6SBartłomiej Klocekexport default DocumentationSidebarRightLink; 184