1import { css } from '@emotion/react'; 2import { LinkBase } from '@expo/styleguide'; 3import * as React from 'react'; 4 5import { AdditionalProps } from '~/common/headingManager'; 6import PermalinkIcon from '~/components/icons/Permalink'; 7import withHeadingManager, { 8 HeadingManagerProps, 9} from '~/components/page-higher-order/withHeadingManager'; 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: middle; 49 display: inline-block; 50 width: 1.2em; 51 height: 1em; 52 padding: 0 0.2em; 53 visibility: hidden; 54 55 a:hover &, 56 a:focus-visible & { 57 visibility: visible; 58 } 59 60 svg { 61 width: 100%; 62 height: auto; 63 } 64`; 65 66const PermalinkBase = ({ component, children, className, ...rest }: BaseProps) => 67 React.cloneElement( 68 component, 69 { 70 className: [className, component.props.className || ''].join(' '), 71 ...rest, 72 }, 73 children 74 ); 75 76const Permalink: React.FC<EnhancedProps> = withHeadingManager( 77 (props: EnhancedProps & HeadingManagerProps) => { 78 // NOTE(jim): Not the greatest way to generate permalinks. 79 // for now I've shortened the length of permalinks. 80 const component = props.children as JSX.Element; 81 const children = component.props.children || ''; 82 83 if (!props.nestingLevel) { 84 return children; 85 } 86 87 const heading = props.headingManager.addHeading( 88 children, 89 props.nestingLevel, 90 props.additionalProps, 91 props.id 92 ); 93 94 return ( 95 <PermalinkBase component={component} style={props.additionalProps?.style}> 96 <LinkBase css={STYLES_PERMALINK_LINK} href={'#' + heading.slug} ref={heading.ref}> 97 <span css={STYLES_PERMALINK_TARGET} id={heading.slug} /> 98 <span css={STYLED_PERMALINK_CONTENT}>{children}</span> 99 <span css={STYLES_PERMALINK_ICON}> 100 <PermalinkIcon /> 101 </span> 102 </LinkBase> 103 </PermalinkBase> 104 ); 105 } 106); 107 108export default Permalink; 109