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