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