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