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