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