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 customIconStyle?: React.CSSProperties; 19 id?: string; 20}>; 21 22const STYLES_PERMALINK = css` 23 position: relative; 24`; 25 26const STYLES_PERMALINK_TARGET = css` 27 display: block; 28 position: absolute; 29 top: -100px; 30 visibility: hidden; 31`; 32 33const STYLES_PERMALINK_LINK = css` 34 color: inherit; 35 text-decoration: inherit; 36 37 /* Disable link when used in collapsible, to allow expand on click */ 38 details & { 39 pointer-events: none; 40 } 41`; 42 43const STYLED_PERMALINK_CONTENT = css` 44 display: inline; 45`; 46 47const STYLES_PERMALINK_ICON = css` 48 cursor: pointer; 49 vertical-align: text-top; 50 display: inline-block; 51 width: 1.2em; 52 height: 1.2em; 53 padding: 0 0.2em; 54 visibility: hidden; 55 56 a:hover & { 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 76/** 77 * Props: 78 * - children: Title or component containing title text 79 * - nestingLevel: Sidebar heading level override 80 * - additionalProps: Additional properties passed to component 81 */ 82const Permalink: React.FC<EnhancedProps> = withHeadingManager( 83 (props: EnhancedProps & HeadingManagerProps) => { 84 // NOTE(jim): Not the greatest way to generate permalinks. 85 // for now I've shortened the length of permalinks. 86 const component = props.children as JSX.Element; 87 const children = component.props.children || ''; 88 89 let permalinkKey = props.id; 90 let heading; 91 92 if (props.nestingLevel) { 93 heading = props.headingManager.addHeading( 94 children, 95 props.nestingLevel, 96 props.additionalProps, 97 permalinkKey 98 ); 99 } 100 101 if (!permalinkKey && heading?.slug) { 102 permalinkKey = heading.slug; 103 } 104 105 return ( 106 <PermalinkBase component={component} data-components-heading> 107 <div css={STYLES_PERMALINK} ref={heading?.ref}> 108 <span css={STYLES_PERMALINK_TARGET} id={permalinkKey} /> 109 <a css={STYLES_PERMALINK_LINK} href={'#' + permalinkKey}> 110 <span css={STYLED_PERMALINK_CONTENT}>{children}</span> 111 <span css={STYLES_PERMALINK_ICON} style={props.customIconStyle}> 112 <PermalinkIcon /> 113 </span> 114 </a> 115 </div> 116 </PermalinkBase> 117 ); 118 } 119); 120 121export default Permalink; 122