import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import type { JSX } from 'react'; import type { CodeBlockControlsProps } from '@redocly/theme/components/CodeBlock/CodeBlockControls'; import type { ReportDialogProps } from '@redocly/theme/components/Feedback/ReportDialog'; import { addLineNumbers } from '@redocly/theme/core/utils'; import { useModalScrollLock, useReportDialog, useThemeHooks } from '@redocly/theme/core/hooks'; import { ReportDialog } from '@redocly/theme/components/Feedback/ReportDialog'; import { CodeBlockContainer } from '@redocly/theme/components/CodeBlock/CodeBlockContainer'; import { CodeBlockControls } from '@redocly/theme/components/CodeBlock/CodeBlockControls'; export type CodeBlockProps = { lang?: string; source?: string; externalSource?: ExternalSource; header?: CodeBlockControlsProps; dataTestId?: string; className?: string; tabs?: CodeBlockItems; dropdown?: CodeBlockItems; withLineNumbers?: boolean; startLineNumber?: number; highlightedHtml?: string; skipHighlight?: boolean; codeBlockRef?: (instance: HTMLPreElement | null) => void; codeBlockMaxHeight?: string; hideCodeColors?: boolean; wrapContents?: boolean; [key: string]: unknown; }; type UnstableExternalCodeSample = { lang: string; label?: string; get: (source: ExternalSource) => string; }; export type CodeBlockItems = { items: { name: string; lang?: string; id: string }[]; onChange: (id: string) => void; value: string; }; type ExternalSource = { sample: UnstableExternalCodeSample; exampleName?: string; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ pathParams?: any; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ properties?: any; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ operation?: any; }; export function CodeBlock({ lang, source, externalSource, header, dataTestId = 'source-code', codeBlockRef, highlightedHtml, withLineNumbers, startLineNumber, className, codeBlockMaxHeight, tabs, dropdown, hideCodeColors, wrapContents = false, children, ...rest }: React.PropsWithChildren): JSX.Element { const [sourceCode, setSourceCode] = useState( (source || externalSource?.sample?.get?.(externalSource)) ?? '', ); const { useCodeHighlight } = useThemeHooks(); const { highlight } = useCodeHighlight() || {}; const highlightedCode = highlightedHtml ? withLineNumbers ? addLineNumbers(highlightedHtml, startLineNumber) : highlightedHtml : children ? null : highlight?.(sourceCode, lang, { withLineNumbers, startLineNumber, highlight: rest['data-highlight'] as string | undefined, }); // The same initial value should be returned for ssr and frontend to avoid issues // Because we don't have session storage in ssr and can't get the security details there // Issue for more details https://github.com/Redocly/reference-docs/issues/888 useEffect(() => { const _source = source || externalSource?.sample?.get?.(externalSource); if (_source) { setSourceCode(_source); } }, [source, externalSource]); const { reportDialog, reportButton } = useReportDialog(); useModalScrollLock(Boolean(reportDialog.visible)); const controls = header?.controls && { ...header?.controls, report: { ...header?.controls?.report, props: reportButton.props }, copy: header?.controls?.copy ? { ...header?.controls?.copy, data: sourceCode } : undefined, }; return ( {children} {reportDialog.visible && ( )} ); } const ContainerWrapper = styled.div` display: grid; // prevents content to overstretch position: relative; `; const CodeBlockWrapper = styled.div` border: 1px solid var(--border-color-secondary); border-radius: var(--border-radius); background-color: var(--code-block-bg-color); margin: 0 0 var(--spacing-sm); --md-pre-margin: 0; `;