xref: /expo/docs/components/Permalink.tsx (revision 370067c6)
1import { css } from '@emotion/react';
2import * as React from 'react';
3
4import { AdditionalProps } from '~/common/headingManager';
5import PermalinkIcon from '~/components/icons/Permalink';
6import withHeadingManager from '~/components/page-higher-order/withHeadingManager';
7
8type BaseProps = {
9  component: any;
10  className?: string;
11};
12
13type EnhancedProps = {
14  nestingLevel?: number;
15  additionalProps?: AdditionalProps;
16  customIconStyle?: React.CSSProperties;
17  id?: string;
18};
19
20const STYLES_PERMALINK = css`
21  position: relative;
22`;
23
24const STYLES_PERMALINK_TARGET = css`
25  display: block;
26  position: absolute;
27  top: -100px;
28  visibility: hidden;
29`;
30
31const STYLES_PERMALINK_LINK = css`
32  color: inherit;
33  text-decoration: inherit;
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: React.FC<React.PropsWithChildren<BaseProps>> = ({
65  component,
66  children,
67  className,
68  ...rest
69}) =>
70  React.cloneElement(
71    component,
72    {
73      className: [className, component.props.className || ''].join(' '),
74      ...rest,
75    },
76    children
77  );
78
79/**
80 * Props:
81 * - children: Title or component containing title text
82 * - nestingLevel: Sidebar heading level override
83 * - additionalProps: Additional properties passed to component
84 */
85const Permalink: React.FC<React.PropsWithChildren<EnhancedProps>> = withHeadingManager(props => {
86  // NOTE(jim): Not the greatest way to generate permalinks.
87  // for now I've shortened the length of permalinks.
88  const component = props.children as JSX.Element;
89  const children = component.props.children || '';
90
91  let permalinkKey = props.id;
92  let heading;
93
94  if (props.nestingLevel) {
95    heading = props.headingManager.addHeading(
96      children,
97      props.nestingLevel,
98      props.additionalProps,
99      permalinkKey
100    );
101  }
102
103  if (!permalinkKey && heading?.slug) {
104    permalinkKey = heading.slug;
105  }
106
107  return (
108    <PermalinkBase component={component} data-components-heading>
109      <div css={STYLES_PERMALINK} ref={heading?.ref}>
110        <span css={STYLES_PERMALINK_TARGET} id={permalinkKey} />
111        <a css={STYLES_PERMALINK_LINK} href={'#' + permalinkKey}>
112          <span css={STYLED_PERMALINK_CONTENT}>{children}</span>
113          <span css={STYLES_PERMALINK_ICON} style={props.customIconStyle}>
114            <PermalinkIcon />
115          </span>
116        </a>
117      </div>
118    </PermalinkBase>
119  );
120});
121
122export default Permalink;
123