xref: /expo/docs/components/Permalink.tsx (revision d4cd4dfc)
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  style?: React.CSSProperties;
15}>;
16
17type EnhancedProps = React.PropsWithChildren<{
18  // Sidebar heading level override
19  nestingLevel?: number;
20  additionalProps?: AdditionalProps;
21  id?: string;
22}>;
23
24const STYLES_PERMALINK_TARGET = css`
25  display: block;
26  position: absolute;
27  top: -46px;
28  visibility: hidden;
29`;
30
31const STYLES_PERMALINK_LINK = css`
32  position: relative;
33  color: inherit;
34  text-decoration: none !important;
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 = ({ component, children, className, ...rest }: BaseProps) =>
66  React.cloneElement(
67    component,
68    {
69      className: [className, component.props.className || ''].join(' '),
70      ...rest,
71    },
72    children
73  );
74
75const Permalink: React.FC<EnhancedProps> = withHeadingManager(
76  (props: EnhancedProps & HeadingManagerProps) => {
77    // NOTE(jim): Not the greatest way to generate permalinks.
78    // for now I've shortened the length of permalinks.
79    const component = props.children as JSX.Element;
80    const children = component.props.children || '';
81
82    const heading = props.nestingLevel
83      ? props.headingManager.addHeading(
84          children,
85          props.nestingLevel,
86          props.additionalProps,
87          props.id
88        )
89      : undefined;
90    const permalinkKey = props.id ?? heading?.slug;
91
92    return (
93      <PermalinkBase component={component} style={props.additionalProps?.style}>
94        <LinkBase css={STYLES_PERMALINK_LINK} href={'#' + permalinkKey} ref={heading?.ref}>
95          <span css={STYLES_PERMALINK_TARGET} id={permalinkKey} />
96          <span css={STYLED_PERMALINK_CONTENT}>{children}</span>
97          <span css={STYLES_PERMALINK_ICON}>
98            <PermalinkIcon />
99          </span>
100        </LinkBase>
101      </PermalinkBase>
102    );
103  }
104);
105
106export default Permalink;
107