15b6cd93dSBartosz Kaszubowskiimport { css } from '@emotion/react';
2c4c6b9d1SBartosz Kaszubowskiimport { theme, typography } from '@expo/styleguide';
3*c730ceaaSBartosz 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;
20586106d6SBartłomiej Klocek`;
21586106d6SBartłomiej Klocek
22c4c6b9d1SBartosz Kaszubowskiconst STYLES_LINK_LABEL = css`
234510b2f2SBartosz Kaszubowski  overflow: hidden;
244510b2f2SBartosz Kaszubowski  text-overflow: ellipsis;
254510b2f2SBartosz Kaszubowski  white-space: nowrap;
26db57e342SBartosz Kaszubowski  color: ${theme.text.secondary};
27db57e342SBartosz Kaszubowski
28db57e342SBartosz Kaszubowski  :hover {
29db57e342SBartosz Kaszubowski    color: ${theme.link.default};
30db57e342SBartosz Kaszubowski  }
31586106d6SBartłomiej Klocek`;
32586106d6SBartłomiej Klocek
33db57e342SBartosz Kaszubowskiconst STYLES_LINK_MONOSPACE = css`
34db57e342SBartosz Kaszubowski  ${typography.fontSizes[13]}
35c4c6b9d1SBartosz Kaszubowski`;
36c4c6b9d1SBartosz Kaszubowski
37586106d6SBartłomiej Klocekconst STYLES_LINK_ACTIVE = css`
388bf05203SJon Samp  color: ${theme.link.default};
39586106d6SBartłomiej Klocek`;
40586106d6SBartłomiej Klocek
41586106d6SBartłomiej Klocekconst STYLES_TOOLTIP = css`
42586106d6SBartłomiej Klocek  border-radius: 3px;
43586106d6SBartłomiej Klocek  position: absolute;
44c4c6b9d1SBartosz Kaszubowski  background-color: ${theme.background.secondary};
45586106d6SBartłomiej Klocek  max-width: 400px;
46c4c6b9d1SBartosz Kaszubowski  border: 1px solid ${theme.border.default};
47586106d6SBartłomiej Klocek  padding: 3px 6px;
48586106d6SBartłomiej Klocek  display: inline-block;
49586106d6SBartłomiej Klocek`;
50586106d6SBartłomiej Klocek
51db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_TEXT = css`
52db57e342SBartosz Kaszubowski  ${typography.fontSizes[13]}
53db57e342SBartosz Kaszubowski  color: ${theme.text.default};
54db57e342SBartosz Kaszubowski  word-break: break-word;
55db57e342SBartosz Kaszubowski  word-wrap: normal;
56db57e342SBartosz Kaszubowski`;
57db57e342SBartosz Kaszubowski
58db57e342SBartosz Kaszubowskiconst STYLES_TOOLTIP_CODE = css`
59db57e342SBartosz Kaszubowski  ${typography.fontSizes[12]}
60586106d6SBartłomiej Klocek`;
61586106d6SBartłomiej Klocek
62c4c6b9d1SBartosz Kaszubowskiconst STYLES_TAG_CONTAINER = css`
63c4c6b9d1SBartosz Kaszubowski  display: inline-flex;
64c4c6b9d1SBartosz Kaszubowski`;
65c4c6b9d1SBartosz Kaszubowski
66c4c6b9d1SBartosz Kaszubowskiconst NESTING_OFFSET = 12;
67586106d6SBartłomiej Klocek
68586106d6SBartłomiej Klocek/**
69586106d6SBartłomiej Klocek * Replaces `Module.someFunction(arguments: argType)`
70586106d6SBartłomiej Klocek * with `someFunction()`
71586106d6SBartłomiej Klocek */
72586106d6SBartłomiej Klocekconst trimCodedTitle = (str: string) => {
73586106d6SBartłomiej Klocek  const dotIdx = str.indexOf('.');
74586106d6SBartłomiej Klocek  if (dotIdx > 0) str = str.substring(dotIdx + 1);
75586106d6SBartłomiej Klocek
76586106d6SBartłomiej Klocek  const parIdx = str.indexOf('(');
77586106d6SBartłomiej Klocek  if (parIdx > 0) str = str.substring(0, parIdx + 1) + ')';
78586106d6SBartłomiej Klocek
79586106d6SBartłomiej Klocek  return str;
80586106d6SBartłomiej Klocek};
81586106d6SBartłomiej Klocek
82586106d6SBartłomiej Klocek/**
83586106d6SBartłomiej Klocek * Determines if element is overflowing
84c4c6b9d1SBartosz Kaszubowski * (its children width exceeds container width)
85586106d6SBartłomiej Klocek * @param {HTMLElement} el element to check
86586106d6SBartłomiej Klocek */
87586106d6SBartłomiej Klocekconst isOverflowing = (el: HTMLElement) => {
88c4c6b9d1SBartosz Kaszubowski  if (!el || !el.children) {
89586106d6SBartłomiej Klocek    return false;
90586106d6SBartłomiej Klocek  }
91586106d6SBartłomiej Klocek
92c4c6b9d1SBartosz Kaszubowski  const childrenWidth = Array.from(el.children).reduce((sum, child) => sum + child.scrollWidth, 0);
93*c730ceaaSBartosz Kaszubowski  const indent = parseInt(window.getComputedStyle(el).paddingLeft, 10);
94*c730ceaaSBartosz Kaszubowski  return childrenWidth >= el.scrollWidth - indent;
95586106d6SBartłomiej Klocek};
96586106d6SBartłomiej Klocek
971f61a071SBartosz Kaszubowskitype TooltipProps = React.PropsWithChildren<{
98586106d6SBartłomiej Klocek  isCode?: boolean;
99586106d6SBartłomiej Klocek  topOffset: number;
1001f61a071SBartosz Kaszubowski}>;
101586106d6SBartłomiej Klocek
102db57e342SBartosz Kaszubowskiconst Tooltip = ({ children, isCode, topOffset }: TooltipProps) => {
103db57e342SBartosz Kaszubowski  const ContentWrapper = isCode ? MONOSPACE : CALLOUT;
104db57e342SBartosz Kaszubowski  return (
105db57e342SBartosz Kaszubowski    <div css={STYLES_TOOLTIP} style={{ right: 24, top: topOffset }}>
106db57e342SBartosz Kaszubowski      <ContentWrapper css={[STYLES_TOOLTIP_TEXT, isCode && STYLES_TOOLTIP_CODE]}>
107586106d6SBartłomiej Klocek        {children}
108db57e342SBartosz Kaszubowski      </ContentWrapper>
109586106d6SBartłomiej Klocek    </div>
110586106d6SBartłomiej Klocek  );
111db57e342SBartosz Kaszubowski};
112586106d6SBartłomiej Klocek
113586106d6SBartłomiej Klocektype SidebarLinkProps = {
114586106d6SBartłomiej Klocek  heading: Heading;
115586106d6SBartłomiej Klocek  isActive: boolean;
116586106d6SBartłomiej Klocek  shortenCode: boolean;
117586106d6SBartłomiej Klocek  onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
118586106d6SBartłomiej Klocek};
119586106d6SBartłomiej Klocek
120586106d6SBartłomiej Klocekconst DocumentationSidebarRightLink = React.forwardRef<HTMLAnchorElement, SidebarLinkProps>(
121586106d6SBartłomiej Klocek  ({ heading, isActive, shortenCode, onClick }, ref) => {
122c4c6b9d1SBartosz Kaszubowski    const { slug, level, title, type, tags } = heading;
123586106d6SBartłomiej Klocek
124586106d6SBartłomiej Klocek    const isCode = type === HeadingType.InlineCode;
125db57e342SBartosz Kaszubowski    const paddingLeft = NESTING_OFFSET * (level - BASE_HEADING_LEVEL);
126586106d6SBartłomiej Klocek    const displayTitle = shortenCode && isCode ? trimCodedTitle(title) : title;
127586106d6SBartłomiej Klocek
128586106d6SBartłomiej Klocek    const [tooltipVisible, setTooltipVisible] = React.useState(false);
129586106d6SBartłomiej Klocek    const [tooltipOffset, setTooltipOffset] = React.useState(-20);
130586106d6SBartłomiej Klocek    const onMouseOver = (event: React.MouseEvent<HTMLAnchorElement>) => {
131586106d6SBartłomiej Klocek      setTooltipVisible(isOverflowing(event.currentTarget));
132c4c6b9d1SBartosz Kaszubowski      setTooltipOffset(
133c4c6b9d1SBartosz Kaszubowski        event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetHeight
134c4c6b9d1SBartosz Kaszubowski      );
135586106d6SBartłomiej Klocek    };
136c4c6b9d1SBartosz Kaszubowski
137586106d6SBartłomiej Klocek    const onMouseOut = () => {
138586106d6SBartłomiej Klocek      setTooltipVisible(false);
139586106d6SBartłomiej Klocek    };
140586106d6SBartłomiej Klocek
141db57e342SBartosz Kaszubowski    const TitleElement = isCode ? MONOSPACE : CALLOUT;
142db57e342SBartosz Kaszubowski
143586106d6SBartłomiej Klocek    return (
144586106d6SBartłomiej Klocek      <>
145586106d6SBartłomiej Klocek        {tooltipVisible && (
146586106d6SBartłomiej Klocek          <Tooltip topOffset={tooltipOffset} isCode={isCode}>
147586106d6SBartłomiej Klocek            {displayTitle}
148586106d6SBartłomiej Klocek          </Tooltip>
149586106d6SBartłomiej Klocek        )}
150*c730ceaaSBartosz Kaszubowski        <Link
151586106d6SBartłomiej Klocek          ref={ref}
152586106d6SBartłomiej Klocek          onMouseOver={onMouseOver}
153586106d6SBartłomiej Klocek          onMouseOut={onMouseOut}
154586106d6SBartłomiej Klocek          href={'#' + slug}
155586106d6SBartłomiej Klocek          onClick={onClick}
156db57e342SBartosz Kaszubowski          css={[STYLES_LINK, paddingLeft && { paddingLeft }]}>
157db57e342SBartosz Kaszubowski          <TitleElement
158db57e342SBartosz Kaszubowski            css={[
159db57e342SBartosz Kaszubowski              STYLES_LINK_LABEL,
160db57e342SBartosz Kaszubowski              isCode && STYLES_LINK_MONOSPACE,
161db57e342SBartosz Kaszubowski              isActive && STYLES_LINK_ACTIVE,
162db57e342SBartosz Kaszubowski            ]}>
163db57e342SBartosz Kaszubowski            {displayTitle}
164db57e342SBartosz Kaszubowski          </TitleElement>
165c4c6b9d1SBartosz Kaszubowski          {tags && tags.length ? (
166c4c6b9d1SBartosz Kaszubowski            <div css={STYLES_TAG_CONTAINER}>
167c4c6b9d1SBartosz Kaszubowski              {tags.map(tag => (
16822ee2951SBartosz Kaszubowski                <Tag name={tag} type="toc" key={`${displayTitle}-${tag}`} />
169c4c6b9d1SBartosz Kaszubowski              ))}
170c4c6b9d1SBartosz Kaszubowski            </div>
171c4c6b9d1SBartosz Kaszubowski          ) : undefined}
172*c730ceaaSBartosz Kaszubowski        </Link>
173586106d6SBartłomiej Klocek      </>
174586106d6SBartłomiej Klocek    );
175586106d6SBartłomiej Klocek  }
176586106d6SBartłomiej Klocek);
177586106d6SBartłomiej Klocek
178586106d6SBartłomiej Klocekexport default DocumentationSidebarRightLink;
179