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