| 18 | // Transforms a code element with plain text content into a more structured |
| 19 | // format for rendering with line numbers |
| 20 | const transformCode = <T extends ReactElement<PropsWithChildren>>( |
| 21 | code: T, |
| 22 | language: string |
| 23 | ): ReactElement<HTMLElement> | T => { |
| 24 | if (!isValidElement(code)) { |
| 25 | // Early return when the `CodeBox` child is not a valid element since the |
| 26 | // type is a ReactNode, and can assume any value |
| 27 | return code; |
| 28 | } |
| 29 | |
| 30 | const content = code.props?.children; |
| 31 | |
| 32 | if (code.type !== 'code' || typeof content !== 'string') { |
| 33 | // There is no need to transform an element that is not a code element or |
| 34 | // a content that is not a string |
| 35 | return code; |
| 36 | } |
| 37 | |
| 38 | // Note that since we use `.split` we will have an extra entry |
| 39 | // being an empty string, so we need to remove it |
| 40 | const lines = content.split('\n'); |
| 41 | |
| 42 | const extraClasses = classNames({ 'plain-text': language.length === 0 }); |
| 43 | |
| 44 | return ( |
| 45 | <code className={extraClasses}> |
| 46 | {lines.flatMap((line, lineIndex) => { |
| 47 | const columns = line.split(' '); |
| 48 | |
| 49 | return [ |
| 50 | // eslint-disable-next-line @eslint-react/no-array-index-key -- lines from split string have no stable ID |
| 51 | <span key={lineIndex} className="line"> |
| 52 | {columns.map((column, columnIndex) => ( |
| 53 | // eslint-disable-next-line @eslint-react/no-array-index-key -- columns from split string have no stable ID |
| 54 | <Fragment key={columnIndex}> |
| 55 | <span>{column}</span> |
| 56 | {columnIndex < columns.length - 1 && <span> </span>} |
| 57 | </Fragment> |
| 58 | ))} |
| 59 | </span>, |
| 60 | // Add a break line so the text content is formatted correctly |
| 61 | // when copying to clipboard |
| 62 | '\n', |
| 63 | ]; |
| 64 | })} |
| 65 | </code> |
| 66 | ); |
| 67 | }; |
| 68 | |
| 69 | type CodeBoxProps = { |
| 70 | language: string; |