xref: /expo/docs/components/Permalink.tsx (revision 5d331dec)
1import { css } from '@emotion/react';
2import * as React from 'react';
3
4import { AdditionalProps } from '~/common/headingManager';
5import PermalinkIcon from '~/components/icons/Permalink';
6import withHeadingManager, {
7  HeadingManagerProps,
8} from '~/components/page-higher-order/withHeadingManager';
9import { LinkBase } from '~/ui/components/Text';
10
11type BaseProps = React.PropsWithChildren<{
12  component: any;
13  className?: string;
14}>;
15
16type EnhancedProps = React.PropsWithChildren<{
17  // Sidebar heading level override
18  nestingLevel?: number;
19  additionalProps?: AdditionalProps;
20  id?: string;
21}>;
22
23const STYLES_PERMALINK_TARGET = css`
24  display: block;
25  position: absolute;
26  top: -46px;
27  visibility: hidden;
28`;
29
30const STYLES_PERMALINK_LINK = css`
31  position: relative;
32  color: inherit;
33  text-decoration: none !important;
34
35  /* Disable link when used in collapsible, to allow expand on click */
36  details & {
37    pointer-events: none;
38  }
39`;
40
41const STYLED_PERMALINK_CONTENT = css`
42  display: inline;
43`;
44
45const STYLES_PERMALINK_ICON = css`
46  cursor: pointer;
47  vertical-align: text-top;
48  display: inline-block;
49  width: 1.2em;
50  height: 1.2em;
51  padding: 0 0.2em;
52  visibility: hidden;
53
54  a:hover & {
55    visibility: visible;
56  }
57
58  svg {
59    width: 100%;
60    height: auto;
61  }
62`;
63
64const PermalinkBase = ({ component, children, className, ...rest }: BaseProps) =>
65  React.cloneElement(
66    component,
67    {
68      className: [className, component.props.className || ''].join(' '),
69      ...rest,
70    },
71    children
72  );
73
74const Permalink: React.FC<EnhancedProps> = withHeadingManager(
75  (props: EnhancedProps & HeadingManagerProps) => {
76    // NOTE(jim): Not the greatest way to generate permalinks.
77    // for now I've shortened the length of permalinks.
78    const component = props.children as JSX.Element;
79    const children = component.props.children || '';
80
81    const heading = props.nestingLevel
82      ? props.headingManager.addHeading(
83          children,
84          props.nestingLevel,
85          props.additionalProps,
86          props.id
87        )
88      : undefined;
89    const permalinkKey = props.id ?? heading?.slug;
90
91    return (
92      <PermalinkBase component={component} data-components-heading>
93        <LinkBase css={STYLES_PERMALINK_LINK} href={'#' + permalinkKey} ref={heading?.ref}>
94          <span css={STYLES_PERMALINK_TARGET} id={permalinkKey} />
95          <span css={STYLED_PERMALINK_CONTENT}>{children}</span>
96          <span css={STYLES_PERMALINK_ICON}>
97            <PermalinkIcon />
98          </span>
99        </LinkBase>
100      </PermalinkBase>
101    );
102  }
103);
104
105export default Permalink;
106