15b6cd93dSBartosz Kaszubowskiimport { css } from '@emotion/react'; 2c4c6b9d1SBartosz Kaszubowskiimport { theme, typography } from '@expo/styleguide'; 3586106d6SBartłomiej Klocekimport * as React from 'react'; 4586106d6SBartłomiej Klocek 5c4c6b9d1SBartosz Kaszubowskiimport { BASE_HEADING_LEVEL, Heading, HeadingType } from '~/common/headingManager'; 6c4c6b9d1SBartosz Kaszubowskiimport { Tag } from '~/ui/components/Tag'; 7*db57e342SBartosz Kaszubowskiimport { MONOSPACE, CALLOUT } from '~/ui/components/Text'; 8586106d6SBartłomiej Klocek 9586106d6SBartłomiej Klocekconst STYLES_LINK = css` 108bf05203SJon Samp transition: 50ms ease color; 11c4c6b9d1SBartosz Kaszubowski display: flex; 12586106d6SBartłomiej Klocek text-decoration: none; 13586106d6SBartłomiej Klocek margin-bottom: 6px; 14586106d6SBartłomiej Klocek cursor: pointer; 15c4c6b9d1SBartosz Kaszubowski overflow: hidden; 16c4c6b9d1SBartosz Kaszubowski text-overflow: ellipsis; 17c4c6b9d1SBartosz Kaszubowski align-items: center; 18c4c6b9d1SBartosz Kaszubowski justify-content: space-between; 19586106d6SBartłomiej Klocek`; 20586106d6SBartłomiej Klocek 21c4c6b9d1SBartosz Kaszubowskiconst STYLES_LINK_LABEL = css` 224510b2f2SBartosz Kaszubowski overflow: hidden; 234510b2f2SBartosz Kaszubowski text-overflow: ellipsis; 244510b2f2SBartosz Kaszubowski white-space: nowrap; 25*db57e342SBartosz Kaszubowski color: ${theme.text.secondary}; 26*db57e342SBartosz Kaszubowski 27*db57e342SBartosz Kaszubowski :hover { 28*db57e342SBartosz Kaszubowski color: ${theme.link.default}; 29*db57e342SBartosz Kaszubowski } 30586106d6SBartłomiej Klocek`; 31586106d6SBartłomiej Klocek 32*db57e342SBartosz Kaszubowskiconst STYLES_LINK_MONOSPACE = css` 33*db57e342SBartosz Kaszubowski ${typography.fontSizes[13]} 34c4c6b9d1SBartosz Kaszubowski`; 35c4c6b9d1SBartosz Kaszubowski 36586106d6SBartłomiej Klocekconst STYLES_LINK_ACTIVE = css` 378bf05203SJon Samp color: ${theme.link.default}; 38586106d6SBartłomiej Klocek`; 39586106d6SBartłomiej Klocek 40586106d6SBartłomiej Klocekconst STYLES_TOOLTIP = css` 41586106d6SBartłomiej Klocek border-radius: 3px; 42586106d6SBartłomiej Klocek position: absolute; 43c4c6b9d1SBartosz Kaszubowski background-color: ${theme.background.secondary}; 44586106d6SBartłomiej Klocek max-width: 400px; 45c4c6b9d1SBartosz Kaszubowski border: 1px solid ${theme.border.default}; 46586106d6SBartłomiej Klocek padding: 3px 6px; 47586106d6SBartłomiej Klocek display: inline-block; 48586106d6SBartłomiej Klocek`; 49586106d6SBartłomiej Klocek 50*db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_TEXT = css` 51*db57e342SBartosz Kaszubowski ${typography.fontSizes[13]} 52*db57e342SBartosz Kaszubowski color: ${theme.text.default}; 53*db57e342SBartosz Kaszubowski word-break: break-word; 54*db57e342SBartosz Kaszubowski word-wrap: normal; 55*db57e342SBartosz Kaszubowski`; 56*db57e342SBartosz Kaszubowski 57*db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_CODE = css` 58*db57e342SBartosz Kaszubowski ${typography.fontSizes[12]} 59586106d6SBartłomiej Klocek`; 60586106d6SBartłomiej Klocek 61c4c6b9d1SBartosz Kaszubowskiconst STYLES_TAG_CONTAINER = css` 62c4c6b9d1SBartosz Kaszubowski display: inline-flex; 63c4c6b9d1SBartosz Kaszubowski`; 64c4c6b9d1SBartosz Kaszubowski 65c4c6b9d1SBartosz Kaszubowskiconst NESTING_OFFSET = 12; 66586106d6SBartłomiej Klocek 67586106d6SBartłomiej Klocek/** 68586106d6SBartłomiej Klocek * Replaces `Module.someFunction(arguments: argType)` 69586106d6SBartłomiej Klocek * with `someFunction()` 70586106d6SBartłomiej Klocek */ 71586106d6SBartłomiej Klocekconst trimCodedTitle = (str: string) => { 72586106d6SBartłomiej Klocek const dotIdx = str.indexOf('.'); 73586106d6SBartłomiej Klocek if (dotIdx > 0) str = str.substring(dotIdx + 1); 74586106d6SBartłomiej Klocek 75586106d6SBartłomiej Klocek const parIdx = str.indexOf('('); 76586106d6SBartłomiej Klocek if (parIdx > 0) str = str.substring(0, parIdx + 1) + ')'; 77586106d6SBartłomiej Klocek 78586106d6SBartłomiej Klocek return str; 79586106d6SBartłomiej Klocek}; 80586106d6SBartłomiej Klocek 81586106d6SBartłomiej Klocek/** 82586106d6SBartłomiej Klocek * Determines if element is overflowing 83c4c6b9d1SBartosz Kaszubowski * (its children width exceeds container width) 84586106d6SBartłomiej Klocek * @param {HTMLElement} el element to check 85586106d6SBartłomiej Klocek */ 86586106d6SBartłomiej Klocekconst isOverflowing = (el: HTMLElement) => { 87c4c6b9d1SBartosz Kaszubowski if (!el || !el.children) { 88586106d6SBartłomiej Klocek return false; 89586106d6SBartłomiej Klocek } 90586106d6SBartłomiej Klocek 91c4c6b9d1SBartosz Kaszubowski const childrenWidth = Array.from(el.children).reduce((sum, child) => sum + child.scrollWidth, 0); 92fc13007cSBartosz Kaszubowski return childrenWidth >= el.scrollWidth - parseInt(el.style.paddingLeft, 10); 93586106d6SBartłomiej Klocek}; 94586106d6SBartłomiej Klocek 951f61a071SBartosz Kaszubowskitype TooltipProps = React.PropsWithChildren<{ 96586106d6SBartłomiej Klocek isCode?: boolean; 97586106d6SBartłomiej Klocek topOffset: number; 981f61a071SBartosz Kaszubowski}>; 99586106d6SBartłomiej Klocek 100*db57e342SBartosz Kaszubowskiconst Tooltip = ({ children, isCode, topOffset }: TooltipProps) => { 101*db57e342SBartosz Kaszubowski const ContentWrapper = isCode ? MONOSPACE : CALLOUT; 102*db57e342SBartosz Kaszubowski return ( 103*db57e342SBartosz Kaszubowski <div css={STYLES_TOOLTIP} style={{ right: 24, top: topOffset }}> 104*db57e342SBartosz Kaszubowski <ContentWrapper css={[STYLES_TOOLTIP_TEXT, isCode && STYLES_TOOLTIP_CODE]}> 105586106d6SBartłomiej Klocek {children} 106*db57e342SBartosz Kaszubowski </ContentWrapper> 107586106d6SBartłomiej Klocek </div> 108586106d6SBartłomiej Klocek ); 109*db57e342SBartosz Kaszubowski}; 110586106d6SBartłomiej Klocek 111586106d6SBartłomiej Klocektype SidebarLinkProps = { 112586106d6SBartłomiej Klocek heading: Heading; 113586106d6SBartłomiej Klocek isActive: boolean; 114586106d6SBartłomiej Klocek shortenCode: boolean; 115586106d6SBartłomiej Klocek onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void; 116586106d6SBartłomiej Klocek}; 117586106d6SBartłomiej Klocek 118586106d6SBartłomiej Klocekconst DocumentationSidebarRightLink = React.forwardRef<HTMLAnchorElement, SidebarLinkProps>( 119586106d6SBartłomiej Klocek ({ heading, isActive, shortenCode, onClick }, ref) => { 120c4c6b9d1SBartosz Kaszubowski const { slug, level, title, type, tags } = heading; 121586106d6SBartłomiej Klocek 122586106d6SBartłomiej Klocek const isCode = type === HeadingType.InlineCode; 123*db57e342SBartosz Kaszubowski const paddingLeft = NESTING_OFFSET * (level - BASE_HEADING_LEVEL); 124586106d6SBartłomiej Klocek const displayTitle = shortenCode && isCode ? trimCodedTitle(title) : title; 125586106d6SBartłomiej Klocek 126586106d6SBartłomiej Klocek const [tooltipVisible, setTooltipVisible] = React.useState(false); 127586106d6SBartłomiej Klocek const [tooltipOffset, setTooltipOffset] = React.useState(-20); 128586106d6SBartłomiej Klocek const onMouseOver = (event: React.MouseEvent<HTMLAnchorElement>) => { 129586106d6SBartłomiej Klocek setTooltipVisible(isOverflowing(event.currentTarget)); 130c4c6b9d1SBartosz Kaszubowski setTooltipOffset( 131c4c6b9d1SBartosz Kaszubowski event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetHeight 132c4c6b9d1SBartosz Kaszubowski ); 133586106d6SBartłomiej Klocek }; 134c4c6b9d1SBartosz Kaszubowski 135586106d6SBartłomiej Klocek const onMouseOut = () => { 136586106d6SBartłomiej Klocek setTooltipVisible(false); 137586106d6SBartłomiej Klocek }; 138586106d6SBartłomiej Klocek 139*db57e342SBartosz Kaszubowski const TitleElement = isCode ? MONOSPACE : CALLOUT; 140*db57e342SBartosz Kaszubowski 141586106d6SBartłomiej Klocek return ( 142586106d6SBartłomiej Klocek <> 143586106d6SBartłomiej Klocek {tooltipVisible && ( 144586106d6SBartłomiej Klocek <Tooltip topOffset={tooltipOffset} isCode={isCode}> 145586106d6SBartłomiej Klocek {displayTitle} 146586106d6SBartłomiej Klocek </Tooltip> 147586106d6SBartłomiej Klocek )} 148586106d6SBartłomiej Klocek <a 149586106d6SBartłomiej Klocek ref={ref} 150586106d6SBartłomiej Klocek onMouseOver={onMouseOver} 151586106d6SBartłomiej Klocek onMouseOut={onMouseOut} 152586106d6SBartłomiej Klocek href={'#' + slug} 153586106d6SBartłomiej Klocek onClick={onClick} 154*db57e342SBartosz Kaszubowski css={[STYLES_LINK, paddingLeft && { paddingLeft }]}> 155*db57e342SBartosz Kaszubowski <TitleElement 156*db57e342SBartosz Kaszubowski css={[ 157*db57e342SBartosz Kaszubowski STYLES_LINK_LABEL, 158*db57e342SBartosz Kaszubowski isCode && STYLES_LINK_MONOSPACE, 159*db57e342SBartosz Kaszubowski isActive && STYLES_LINK_ACTIVE, 160*db57e342SBartosz Kaszubowski ]}> 161*db57e342SBartosz Kaszubowski {displayTitle} 162*db57e342SBartosz Kaszubowski </TitleElement> 163c4c6b9d1SBartosz Kaszubowski {tags && tags.length ? ( 164c4c6b9d1SBartosz Kaszubowski <div css={STYLES_TAG_CONTAINER}> 165c4c6b9d1SBartosz Kaszubowski {tags.map(tag => ( 16622ee2951SBartosz Kaszubowski <Tag name={tag} type="toc" key={`${displayTitle}-${tag}`} /> 167c4c6b9d1SBartosz Kaszubowski ))} 168c4c6b9d1SBartosz Kaszubowski </div> 169c4c6b9d1SBartosz Kaszubowski ) : undefined} 170586106d6SBartłomiej Klocek </a> 171586106d6SBartłomiej Klocek </> 172586106d6SBartłomiej Klocek ); 173586106d6SBartłomiej Klocek } 174586106d6SBartłomiej Klocek); 175586106d6SBartłomiej Klocek 176586106d6SBartłomiej Klocekexport default DocumentationSidebarRightLink; 177