mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-23 20:59:23 +00:00
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import React, { CSSProperties, useMemo, useRef } from 'react';
|
|
import { useAdjustFontSizeToFit } from './fitText/textFit';
|
|
|
|
type FitTextProps = {
|
|
text: string;
|
|
fontSize?: number; // px; if omitted, uses computed style
|
|
minimumFontScale?: number; // 0..1
|
|
lines?: number; // max lines
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
as?: 'span' | 'div';
|
|
/**
|
|
* Insert zero-width soft breaks after these characters to prefer wrapping at them
|
|
* when multi-line is enabled. Defaults to '/'. Ignored when lines === 1.
|
|
*/
|
|
softBreakChars?: string | string[];
|
|
};
|
|
|
|
const FitText: React.FC<FitTextProps> = ({
|
|
text,
|
|
fontSize,
|
|
minimumFontScale = 0.8,
|
|
lines = 1,
|
|
className,
|
|
style,
|
|
as = 'span',
|
|
softBreakChars = ['-','_','/'],
|
|
}) => {
|
|
const ref = useRef<HTMLElement | null>(null);
|
|
|
|
// Hook runs after mount and on size/text changes; uses observers internally
|
|
useAdjustFontSizeToFit(ref as any, {
|
|
maxFontSizePx: fontSize,
|
|
minFontScale: minimumFontScale,
|
|
maxLines: lines,
|
|
singleLine: lines === 1,
|
|
});
|
|
|
|
// Memoize the HTML tag to render (span/div) from the `as` prop so
|
|
// React doesn't create a new component function on each render.
|
|
const ElementTag: any = useMemo(() => as, [as]);
|
|
|
|
// For the / character, insert zero-width soft breaks to prefer wrapping at them
|
|
const displayText = useMemo(() => {
|
|
if (!text) return text;
|
|
if (!lines || lines <= 1) return text;
|
|
const chars = Array.isArray(softBreakChars) ? softBreakChars : [softBreakChars];
|
|
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const re = new RegExp(`(${chars.filter(Boolean).map(esc).join('|')})`, 'g');
|
|
return text.replace(re, `$1\u200B`);
|
|
}, [text, lines, softBreakChars]);
|
|
|
|
const clampStyles: CSSProperties = {
|
|
// Multi-line clamp with ellipsis fallback
|
|
whiteSpace: lines === 1 ? 'nowrap' : 'normal',
|
|
overflow: 'visible',
|
|
textOverflow: 'ellipsis',
|
|
display: lines > 1 ? ('-webkit-box' as any) : undefined,
|
|
WebkitBoxOrient: lines > 1 ? ('vertical' as any) : undefined,
|
|
WebkitLineClamp: lines > 1 ? (lines as any) : undefined,
|
|
lineClamp: lines > 1 ? (lines as any) : undefined,
|
|
// Favor shrinking over breaking words; only break at natural spaces or softBreakChars
|
|
wordBreak: lines > 1 ? ('keep-all' as any) : ('normal' as any),
|
|
overflowWrap: 'normal',
|
|
hyphens: 'manual',
|
|
// fontSize expects rem values (e.g., 1.2, 0.9) to scale with global font size
|
|
fontSize: fontSize ? `${fontSize}rem` : undefined,
|
|
};
|
|
|
|
return (
|
|
<ElementTag ref={ref} className={className} style={{ ...clampStyles, ...style }}>
|
|
{displayText}
|
|
</ElementTag>
|
|
);
|
|
};
|
|
|
|
export default FitText;
|
|
|
|
|