import { css } from '@emotion/react'; import { theme, typography } from '@expo/styleguide'; import * as React from 'react'; import { BASE_HEADING_LEVEL, Heading, HeadingType } from '~/common/headingManager'; import { paragraph } from '~/components/base/typography'; import { Tag } from '~/ui/components/Tag'; const STYLES_LINK = css` ${paragraph} color: ${theme.text.secondary}; transition: 50ms ease color; font-size: 14px; display: flex; text-decoration: none; margin-bottom: 6px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; align-items: center; justify-content: space-between; :hover { color: ${theme.link.default}; } `; const STYLES_LINK_HEADER = css` font-family: ${typography.fontFaces.medium}; `; const STYLES_LINK_LABEL = css` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; const STYLES_LINK_CODE = css` font-family: ${typography.fontFaces.mono}; font-size: 13px; `; const STYLES_LINK_ACTIVE = css` color: ${theme.link.default}; `; const STYLES_TOOLTIP = css` ${typography.fontSizes[13]} border-radius: 3px; position: absolute; color: ${theme.text.default}; background-color: ${theme.background.secondary}; font-family: ${typography.fontFaces.medium}; max-width: 400px; border: 1px solid ${theme.border.default}; padding: 3px 6px; word-break: break-word; word-wrap: normal; display: inline-block; `; const STYLES_CODE_TOOLTIP = css` font-family: ${typography.fontFaces.mono}; font-size: 11px; `; const STYLES_TAG_CONTAINER = css` display: inline-flex; `; const NESTING_OFFSET = 12; /** * Replaces `Module.someFunction(arguments: argType)` * with `someFunction()` */ const trimCodedTitle = (str: string) => { const dotIdx = str.indexOf('.'); if (dotIdx > 0) str = str.substring(dotIdx + 1); const parIdx = str.indexOf('('); if (parIdx > 0) str = str.substring(0, parIdx + 1) + ')'; return str; }; /** * Determines if element is overflowing * (its children width exceeds container width) * @param {HTMLElement} el element to check */ const isOverflowing = (el: HTMLElement) => { if (!el || !el.children) { return false; } const childrenWidth = Array.from(el.children).reduce((sum, child) => sum + child.scrollWidth, 0); return childrenWidth >= el.scrollWidth - parseInt(el.style.paddingLeft, 10); }; type TooltipProps = React.PropsWithChildren<{ isCode?: boolean; topOffset: number; }>; const Tooltip = ({ children, isCode, topOffset }: TooltipProps) => (
{children}
); type SidebarLinkProps = { heading: Heading; isActive: boolean; shortenCode: boolean; onClick: (event: React.MouseEvent) => void; }; const DocumentationSidebarRightLink = React.forwardRef( ({ heading, isActive, shortenCode, onClick }, ref) => { const { slug, level, title, type, tags } = heading; const isNested = level <= BASE_HEADING_LEVEL; const isCode = type === HeadingType.InlineCode; const paddingLeft = NESTING_OFFSET * (level - BASE_HEADING_LEVEL) + 'px'; const displayTitle = shortenCode && isCode ? trimCodedTitle(title) : title; const [tooltipVisible, setTooltipVisible] = React.useState(false); const [tooltipOffset, setTooltipOffset] = React.useState(-20); const onMouseOver = (event: React.MouseEvent) => { setTooltipVisible(isOverflowing(event.currentTarget)); setTooltipOffset( event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetHeight ); }; const onMouseOut = () => { setTooltipVisible(false); }; return ( <> {tooltipVisible && ( {displayTitle} )} {displayTitle} {tags && tags.length ? (
{tags.map(tag => ( ))}
) : undefined}
); } ); export default DocumentationSidebarRightLink;