import {
CodeButtonGroup,
type CodeButtonGroupProps,
IconArrowDown,
SvgWrapper,
useCodeButtonGroup,
} from '@rspress/core/theme';
import clsx from 'clsx';
import { useCallback, useEffect, useRef, useState } from 'react';
import './index.scss';
const DEFAULT_FOLD_HEIGHT = 300;
export type CodeBlockProps = {
title?: string;
lang?: string;
/**
* @default false
*/
wrapCode?: boolean;
/**
* @default false
*/
lineNumbers?: boolean;
/**
* @default false
*/
fold?: boolean;
height?: number;
containerElementClassName?: string;
codeButtonGroupProps?: Omit<
CodeButtonGroupProps,
'preElementRef' | 'wrapCode' | 'toggleWrapCode'
>;
children?: React.ReactNode;
};
/**
* This component only provides styling and must be used with Shiki.
* If you need runtime code highlighting, please use instead.
*
* @example
*
* Input:
*
* ```tsx
*
*
*
*
*
* ```
*
* Expected html output:
*
* ```html
*
*```
*/
export function CodeBlock({
containerElementClassName,
title,
lang = 'txt',
wrapCode: wrapCodeProp = false,
lineNumbers: lineNumbersProp = false,
fold = false,
height,
codeButtonGroupProps,
children,
}: CodeBlockProps) {
if (import.meta.env.SSG_MD) {
return <>{children}>;
}
const {
wrapCode: wrapCodeState,
toggleWrapCode,
copyElementRef,
} = useCodeButtonGroup(wrapCodeProp);
const [expanded, setExpanded] = useState(false);
const [needFold, setNeedFold] = useState(false);
const hasScroll = !fold && height !== undefined;
const codeBlockRef = useRef(null);
const contentRef = useRef(null);
useEffect(() => {
if (!fold || !contentRef.current) {
setNeedFold(false);
return;
}
const foldHeight = height ?? DEFAULT_FOLD_HEIGHT;
const realHeight = contentRef.current.scrollHeight;
setNeedFold(realHeight > foldHeight);
}, [fold, height]);
const handleFoldToggle = useCallback(() => {
if (expanded) {
setExpanded(false);
requestAnimationFrame(() => {
if (codeBlockRef.current) {
const rect = codeBlockRef.current.getBoundingClientRect();
if (rect.top < 0) {
window.scrollBy({
top: rect.top - 16,
behavior: 'smooth',
});
}
}
});
} else {
setExpanded(true);
}
}, [expanded]);
return (
{title &&
{title}
}
{needFold && (
)}
);
}