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