import React, { useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import { useTranslations } from '@shoptet/ui-core-web'; import { Button } from '../Button/Button'; import { IconName } from '../Icon/IconName'; import { IconButton } from '../IconButton/IconButton'; import { dictionary } from './dictionary'; export interface CodeSnippetProps { /** * Content to display inside the snippet. * For multi-line code, pass a plain string as children. */ children: React.ReactNode; /** * Text value to place in the clipboard when the copy button is clicked. * Falls back to the rendered text content of the snippet when omitted. */ copyValue?: string; /** * Maximum number of visible lines before the snippet is collapsed. * When omitted, all content is always visible. */ maxLines?: number; /** * When true, the URL-safe font (Inter) is used instead of JetBrains Mono. * @default false */ isUrl?: boolean; /** * Called after the content has been successfully copied to the clipboard. */ onCopy?: () => void; /** * Called when copying to the clipboard fails (e.g. permission denied or API unavailable). */ onCopyError?: (error: unknown) => void; } const COPIED_RESET_DELAY_MS = 2000; export const CodeSnippet: React.FC = ({ children, copyValue, isUrl = false, maxLines, onCopy, onCopyError, ...rest }) => { const [isCopied, setIsCopied] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [isOverflowing, setIsOverflowing] = useState(false); const isCollapsed = Boolean(maxLines) && !isExpanded; const contentRef = useRef(null); const timerRef = useRef | null>(null); const translations = useTranslations(dictionary); useEffect(() => { const el = contentRef.current; if (!el) { return; } if (!maxLines) { el.style.removeProperty('max-height'); setIsOverflowing(false); return; } const computedStyle = window.getComputedStyle(el); const lineHeight = parseFloat(computedStyle.lineHeight); if (!Number.isFinite(lineHeight) || lineHeight <= 0) { el.style.removeProperty('max-height'); return; } if (isCollapsed) { el.style.maxHeight = `${lineHeight * maxLines}px`; } else { el.style.removeProperty('max-height'); } setIsOverflowing(el.scrollHeight > lineHeight * maxLines); }, [children, isCollapsed, maxLines]); useEffect(() => { return () => { if (timerRef.current) { clearTimeout(timerRef.current); } }; }, []); const handleCopy = useCallback(async () => { const text = copyValue ?? contentRef.current?.textContent ?? ''; try { await navigator.clipboard.writeText(text); setIsCopied(true); onCopy?.(); if (timerRef.current) { clearTimeout(timerRef.current); } timerRef.current = setTimeout(() => { setIsCopied(false); timerRef.current = null; }, COPIED_RESET_DELAY_MS); } catch (error) { onCopyError?.(error); } }, [copyValue, onCopy, onCopyError]); return (
        {children}
      
{maxLines && isOverflowing && (
)}
); };