15b6cd93dSBartosz Kaszubowskiimport { css } from '@emotion/react';
2*c4c6b9d1SBartosz Kaszubowskiimport { theme, typography } from '@expo/styleguide';
3586106d6SBartłomiej Klocekimport * as React from 'react';
4586106d6SBartłomiej Klocek
5*c4c6b9d1SBartosz Kaszubowskiimport { BASE_HEADING_LEVEL, Heading, HeadingType } from '~/common/headingManager';
6586106d6SBartłomiej Klocekimport { paragraph } from '~/components/base/typography';
7*c4c6b9d1SBartosz Kaszubowskiimport { Tag } from '~/ui/components/Tag';
8586106d6SBartłomiej Klocek
9586106d6SBartłomiej Klocekconst STYLES_LINK = css`
10586106d6SBartłomiej Klocek  ${paragraph}
118bf05203SJon Samp  color: ${theme.text.secondary};
128bf05203SJon Samp  transition: 50ms ease color;
13586106d6SBartłomiej Klocek  font-size: 14px;
14*c4c6b9d1SBartosz Kaszubowski  display: flex;
15586106d6SBartłomiej Klocek  text-decoration: none;
16586106d6SBartłomiej Klocek  margin-bottom: 6px;
17586106d6SBartłomiej Klocek  cursor: pointer;
18*c4c6b9d1SBartosz Kaszubowski  overflow: hidden;
19*c4c6b9d1SBartosz Kaszubowski  text-overflow: ellipsis;
20*c4c6b9d1SBartosz Kaszubowski  align-items: center;
21*c4c6b9d1SBartosz Kaszubowski  justify-content: space-between;
22586106d6SBartłomiej Klocek
23586106d6SBartłomiej Klocek  :hover {
248bf05203SJon Samp    color: ${theme.link.default};
25586106d6SBartłomiej Klocek  }
26586106d6SBartłomiej Klocek`;
27586106d6SBartłomiej Klocek
28586106d6SBartłomiej Klocekconst STYLES_LINK_HEADER = css`
298947d273SCedric van Putten  font-family: ${typography.fontFaces.medium};
30586106d6SBartłomiej Klocek`;
31586106d6SBartłomiej Klocek
32*c4c6b9d1SBartosz Kaszubowskiconst STYLES_LINK_LABEL = css`
334510b2f2SBartosz Kaszubowski  overflow: hidden;
344510b2f2SBartosz Kaszubowski  text-overflow: ellipsis;
354510b2f2SBartosz Kaszubowski  white-space: nowrap;
36586106d6SBartłomiej Klocek`;
37586106d6SBartłomiej Klocek
38*c4c6b9d1SBartosz Kaszubowskiconst STYLES_LINK_CODE = css`
39*c4c6b9d1SBartosz Kaszubowski  font-family: ${typography.fontFaces.mono};
40*c4c6b9d1SBartosz Kaszubowski  font-size: 13px;
41*c4c6b9d1SBartosz Kaszubowski`;
42*c4c6b9d1SBartosz Kaszubowski
43586106d6SBartłomiej Klocekconst STYLES_LINK_ACTIVE = css`
448bf05203SJon Samp  color: ${theme.link.default};
45586106d6SBartłomiej Klocek`;
46586106d6SBartłomiej Klocek
47586106d6SBartłomiej Klocekconst STYLES_TOOLTIP = css`
48*c4c6b9d1SBartosz Kaszubowski  ${typography.fontSizes[13]}
49586106d6SBartłomiej Klocek  border-radius: 3px;
50586106d6SBartłomiej Klocek  position: absolute;
51*c4c6b9d1SBartosz Kaszubowski  color: ${theme.text.default};
52*c4c6b9d1SBartosz Kaszubowski  background-color: ${theme.background.secondary};
538947d273SCedric van Putten  font-family: ${typography.fontFaces.medium};
54586106d6SBartłomiej Klocek  max-width: 400px;
55*c4c6b9d1SBartosz Kaszubowski  border: 1px solid ${theme.border.default};
56586106d6SBartłomiej Klocek  padding: 3px 6px;
57586106d6SBartłomiej Klocek  word-break: break-word;
58586106d6SBartłomiej Klocek  word-wrap: normal;
59586106d6SBartłomiej Klocek  display: inline-block;
60586106d6SBartłomiej Klocek`;
61586106d6SBartłomiej Klocek
62586106d6SBartłomiej Klocekconst STYLES_CODE_TOOLTIP = css`
638947d273SCedric van Putten  font-family: ${typography.fontFaces.mono};
64586106d6SBartłomiej Klocek  font-size: 11px;
65586106d6SBartłomiej Klocek`;
66586106d6SBartłomiej Klocek
67*c4c6b9d1SBartosz Kaszubowskiconst STYLES_TAG_CONTAINER = css`
68*c4c6b9d1SBartosz Kaszubowski  display: inline-flex;
69*c4c6b9d1SBartosz Kaszubowski`;
70*c4c6b9d1SBartosz Kaszubowski
71*c4c6b9d1SBartosz 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
89*c4c6b9d1SBartosz 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) => {
93*c4c6b9d1SBartosz Kaszubowski  if (!el || !el.children) {
94586106d6SBartłomiej Klocek    return false;
95586106d6SBartłomiej Klocek  }
96586106d6SBartłomiej Klocek
97*c4c6b9d1SBartosz Kaszubowski  const childrenWidth = Array.from(el.children).reduce((sum, child) => sum + child.scrollWidth, 0);
98*c4c6b9d1SBartosz Kaszubowski  return childrenWidth >= el.scrollWidth;
99586106d6SBartłomiej Klocek};
100586106d6SBartłomiej Klocek
1011f61a071SBartosz Kaszubowskitype TooltipProps = React.PropsWithChildren<{
102586106d6SBartłomiej Klocek  isCode?: boolean;
103586106d6SBartłomiej Klocek  topOffset: number;
1041f61a071SBartosz Kaszubowski}>;
105586106d6SBartłomiej Klocek
1061f61a071SBartosz Kaszubowskiconst Tooltip = ({ children, isCode, topOffset }: TooltipProps) => (
107*c4c6b9d1SBartosz Kaszubowski  <div css={[STYLES_TOOLTIP, isCode && STYLES_CODE_TOOLTIP]} style={{ right: 24, top: topOffset }}>
108586106d6SBartłomiej Klocek    {children}
109586106d6SBartłomiej Klocek  </div>
110586106d6SBartłomiej Klocek);
111586106d6SBartłomiej Klocek
112586106d6SBartłomiej Klocektype SidebarLinkProps = {
113586106d6SBartłomiej Klocek  heading: Heading;
114586106d6SBartłomiej Klocek  isActive: boolean;
115586106d6SBartłomiej Klocek  shortenCode: boolean;
116586106d6SBartłomiej Klocek  onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
117586106d6SBartłomiej Klocek};
118586106d6SBartłomiej Klocek
119586106d6SBartłomiej Klocekconst DocumentationSidebarRightLink = React.forwardRef<HTMLAnchorElement, SidebarLinkProps>(
120586106d6SBartłomiej Klocek  ({ heading, isActive, shortenCode, onClick }, ref) => {
121*c4c6b9d1SBartosz Kaszubowski    const { slug, level, title, type, tags } = heading;
122586106d6SBartłomiej Klocek
123586106d6SBartłomiej Klocek    const isNested = level <= BASE_HEADING_LEVEL;
124586106d6SBartłomiej Klocek    const isCode = type === HeadingType.InlineCode;
125586106d6SBartłomiej Klocek
126586106d6SBartłomiej Klocek    const paddingLeft = NESTING_OFFSET * (level - BASE_HEADING_LEVEL) + 'px';
127586106d6SBartłomiej Klocek    const displayTitle = shortenCode && isCode ? trimCodedTitle(title) : title;
128586106d6SBartłomiej Klocek
129586106d6SBartłomiej Klocek    const [tooltipVisible, setTooltipVisible] = React.useState(false);
130586106d6SBartłomiej Klocek    const [tooltipOffset, setTooltipOffset] = React.useState(-20);
131586106d6SBartłomiej Klocek    const onMouseOver = (event: React.MouseEvent<HTMLAnchorElement>) => {
132586106d6SBartłomiej Klocek      setTooltipVisible(isOverflowing(event.currentTarget));
133*c4c6b9d1SBartosz Kaszubowski      setTooltipOffset(
134*c4c6b9d1SBartosz Kaszubowski        event.currentTarget.getBoundingClientRect().top + event.currentTarget.offsetHeight
135*c4c6b9d1SBartosz Kaszubowski      );
136586106d6SBartłomiej Klocek    };
137*c4c6b9d1SBartosz Kaszubowski
138586106d6SBartłomiej Klocek    const onMouseOut = () => {
139586106d6SBartłomiej Klocek      setTooltipVisible(false);
140586106d6SBartłomiej Klocek    };
141586106d6SBartłomiej Klocek
142586106d6SBartłomiej Klocek    return (
143586106d6SBartłomiej Klocek      <>
144586106d6SBartłomiej Klocek        {tooltipVisible && (
145586106d6SBartłomiej Klocek          <Tooltip topOffset={tooltipOffset} isCode={isCode}>
146586106d6SBartłomiej Klocek            {displayTitle}
147586106d6SBartłomiej Klocek          </Tooltip>
148586106d6SBartłomiej Klocek        )}
149586106d6SBartłomiej Klocek        <a
150586106d6SBartłomiej Klocek          ref={ref}
151586106d6SBartłomiej Klocek          onMouseOver={onMouseOver}
152586106d6SBartłomiej Klocek          onMouseOut={onMouseOut}
153586106d6SBartłomiej Klocek          style={{ paddingLeft }}
154586106d6SBartłomiej Klocek          href={'#' + slug}
155586106d6SBartłomiej Klocek          onClick={onClick}
156*c4c6b9d1SBartosz Kaszubowski          css={[STYLES_LINK, isNested && STYLES_LINK_HEADER, isActive && STYLES_LINK_ACTIVE]}>
157*c4c6b9d1SBartosz Kaszubowski          <span css={[STYLES_LINK_LABEL, isCode && STYLES_LINK_CODE]}>{displayTitle}</span>
158*c4c6b9d1SBartosz Kaszubowski          {tags && tags.length ? (
159*c4c6b9d1SBartosz Kaszubowski            <div css={STYLES_TAG_CONTAINER}>
160*c4c6b9d1SBartosz Kaszubowski              {tags.map(tag => (
161*c4c6b9d1SBartosz Kaszubowski                <Tag name={tag} type="toc" />
162*c4c6b9d1SBartosz Kaszubowski              ))}
163*c4c6b9d1SBartosz Kaszubowski            </div>
164*c4c6b9d1SBartosz Kaszubowski          ) : undefined}
165586106d6SBartłomiej Klocek        </a>
166586106d6SBartłomiej Klocek      </>
167586106d6SBartłomiej Klocek    );
168586106d6SBartłomiej Klocek  }
169586106d6SBartłomiej Klocek);
170586106d6SBartłomiej Klocek
171586106d6SBartłomiej Klocekexport default DocumentationSidebarRightLink;
172