Files
Jiao77-Blog/src/components/react/MathFlipCard.tsx
2026-03-01 20:49:27 +08:00

148 lines
4.2 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import katex from 'katex';
interface MathFlipCardProps {
latex: string;
displayMode?: boolean;
className?: string;
}
export default function MathFlipCard({
latex,
displayMode = true,
className = ''
}: MathFlipCardProps) {
const [isFlipped, setIsFlipped] = useState(false);
const mathContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (mathContainerRef.current && !isFlipped) {
try {
katex.render(latex, mathContainerRef.current, {
displayMode: displayMode,
throwOnError: false,
trust: true,
});
} catch (error) {
console.error('KaTeX rendering error:', error);
if (mathContainerRef.current) {
mathContainerRef.current.textContent = latex;
}
}
}
}, [latex, displayMode, isFlipped]);
const containerStyle: React.CSSProperties = {
perspective: '1000px',
width: '100%',
minHeight: displayMode ? '120px' : '60px',
position: 'relative',
};
const innerStyle: React.CSSProperties = {
position: 'relative',
width: '100%',
minHeight: 'inherit',
transformStyle: 'preserve-3d',
transition: 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
};
const faceStyle = (isFront: boolean): React.CSSProperties => ({
position: 'absolute',
width: '100%',
minHeight: 'inherit',
backfaceVisibility: 'hidden',
borderRadius: '0.75rem',
padding: displayMode ? '1.5rem' : '0.75rem 1rem',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--color-muted, #f1f5f9)',
border: '1px solid var(--color-border, #e2e8f0)',
transform: isFront ? 'rotateY(0deg)' : 'rotateY(180deg)',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
});
const codeStyle: React.CSSProperties = {
fontFamily: 'var(--font-mono, "JetBrains Mono", monospace)',
fontSize: displayMode ? '0.9rem' : '0.85rem',
color: 'var(--color-foreground, #1e293b)',
background: 'transparent',
wordBreak: 'break-word',
textAlign: 'center',
width: '100%',
};
const buttonStyle: React.CSSProperties = {
position: 'absolute',
bottom: '0.5rem',
right: '0.5rem',
padding: '0.25rem 0.75rem',
fontSize: '0.75rem',
fontWeight: 500,
color: 'var(--primary-500, #0ea5e9)',
background: 'transparent',
border: '1px solid var(--primary-500, #0ea5e9)',
borderRadius: '0.375rem',
cursor: 'pointer',
transition: 'all 0.2s ease',
zIndex: 10,
};
const handleButtonClick = (e: React.MouseEvent) => {
e.stopPropagation();
setIsFlipped(!isFlipped);
};
return (
<div style={containerStyle} className={className}>
<div style={innerStyle}>
<div style={faceStyle(true)}>
<div
ref={mathContainerRef}
style={{
color: 'var(--color-foreground, #1e293b)',
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
/>
<button
style={buttonStyle}
onClick={handleButtonClick}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--primary-500, #0ea5e9)';
e.currentTarget.style.color = 'white';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = 'var(--primary-500, #0ea5e9)';
}}
>
LaTeX
</button>
</div>
<div style={faceStyle(false)}>
<pre style={codeStyle}>{latex}</pre>
<button
style={buttonStyle}
onClick={handleButtonClick}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--primary-500, #0ea5e9)';
e.currentTarget.style.color = 'white';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = 'var(--primary-500, #0ea5e9)';
}}
>
</button>
</div>
</div>
</div>
);
}