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