1import { css } from '@emotion/react'; 2import * as React from 'react'; 3 4import { AdditionalProps } from '~/common/headingManager'; 5import PermalinkIcon from '~/components/icons/Permalink'; 6import withHeadingManager from '~/components/page-higher-order/withHeadingManager'; 7 8type BaseProps = { 9 component: any; 10 className?: string; 11}; 12 13type EnhancedProps = { 14 nestingLevel?: number; 15 additionalProps?: AdditionalProps; 16 customIconStyle?: React.CSSProperties; 17 id?: string; 18}; 19 20const STYLES_PERMALINK = css` 21 position: relative; 22`; 23 24const STYLES_PERMALINK_TARGET = css` 25 display: block; 26 position: absolute; 27 top: -100px; 28 visibility: hidden; 29`; 30 31const STYLES_PERMALINK_LINK = css` 32 color: inherit; 33 text-decoration: inherit; 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: React.FC<React.PropsWithChildren<BaseProps>> = ({ 65 component, 66 children, 67 className, 68 ...rest 69}) => 70 React.cloneElement( 71 component, 72 { 73 className: [className, component.props.className || ''].join(' '), 74 ...rest, 75 }, 76 children 77 ); 78 79/** 80 * Props: 81 * - children: Title or component containing title text 82 * - nestingLevel: Sidebar heading level override 83 * - additionalProps: Additional properties passed to component 84 */ 85const Permalink: React.FC<React.PropsWithChildren<EnhancedProps>> = withHeadingManager(props => { 86 // NOTE(jim): Not the greatest way to generate permalinks. 87 // for now I've shortened the length of permalinks. 88 const component = props.children as JSX.Element; 89 const children = component.props.children || ''; 90 91 let permalinkKey = props.id; 92 let heading; 93 94 if (props.nestingLevel) { 95 heading = props.headingManager.addHeading( 96 children, 97 props.nestingLevel, 98 props.additionalProps, 99 permalinkKey 100 ); 101 } 102 103 if (!permalinkKey && heading?.slug) { 104 permalinkKey = heading.slug; 105 } 106 107 return ( 108 <PermalinkBase component={component} data-components-heading> 109 <div css={STYLES_PERMALINK} ref={heading?.ref}> 110 <span css={STYLES_PERMALINK_TARGET} id={permalinkKey} /> 111 <a css={STYLES_PERMALINK_LINK} href={'#' + permalinkKey}> 112 <span css={STYLED_PERMALINK_CONTENT}>{children}</span> 113 <span css={STYLES_PERMALINK_ICON} style={props.customIconStyle}> 114 <PermalinkIcon /> 115 </span> 116 </a> 117 </div> 118 </PermalinkBase> 119 ); 120}); 121 122export default Permalink; 123