import * as React from 'react' import {FC, HTMLAttributes} from 'react' /** * @category Props */ export interface FitTextProps extends HTMLAttributes { /** * The text to display. Must be a string, not a React node. */ children: string /** * A number from 1 to 100 representing the percentage of line width to scale text to. * @defaultValue 100 */ width?: number /** * A string representing the style of the text. This will be used to calculate the width of the text. * @defaultValue `undefined` */ isStyle?: `uppercase` | `lowercase` | `monospace` /** * A number representing the relative width of the font family compared to other font families. This will be used to multiply the width of the text. * @defaultValue `var(--family-heading-width, 1)` */ familyWidth?: number | string } /** * This component is used to scale text to fit the width of a container. It uses the `cqw` unit to scale the text to fit the width of the `inline-size` containment element which is defined by the `@container` query. * * @param props - the {@link FitTextProps}. You can use the `width` prop to change the line-length percentage. You may also add any valid attribute for the underlying span. * @returns a spicy span to wrap one line of text. * @category Components * * @example * Note that you must define a CSS container for the container query units to be relative to. Here we are using a Tailwind plugin which provides the `@container` class for this purpose. * ```jsx *
*

* ❤️❤️❤️ I’m full width ❤️❤️❤️ * {dynamicFullWidthText} * {smallerText} * /p> *

* ``` */ export const FitText: FC = ({ children, width = 100, familyWidth = `var(--family-heading-width, 1)`, isStyle, style = {}, ...props }) => { const caseTransformers = { uppercase: (input: string) => input.toUpperCase(), lowercase: (input: string) => input.toLowerCase(), monospace: (input: string) => input.replace(/.*/gi, 'M'), } const transformer = caseTransformers[isStyle] || ((input) => input) const trimmedContent = children.trim() const charCount = trimmedContent.length const charRatio = getFontRatio(transformer(trimmedContent)) return ( {children} ) } export default FitText /** * This helper function computes the font family ratio for a given English language string. This is used to scale the font size of the text to fit the width of the container. * @param inputString the input string to compute the font ratio for * @returns a number between 0 and 1 * * @private * @internal */ function getFontRatio(inputString: string) { // Define the expected widths for each character const charWidths = { // Wide characters M: 1.0, W: 1.0, O: 1.0, Q: 1.0, // Medium characters B: 0.75, D: 0.75, E: 0.75, F: 0.75, H: 0.75, K: 0.75, L: 0.75, N: 0.75, P: 0.75, R: 0.75, S: 0.75, T: 0.75, U: 0.75, V: 0.75, X: 0.75, Y: 0.75, Z: 0.75, // Narrow characters A: 0.5, C: 0.5, G: 0.5, I: 0.5, J: 0.5, a: 0.5, b: 0.5, c: 0.5, d: 0.5, e: 0.5, f: 0.5, g: 0.5, h: 0.5, i: 0.5, j: 0.5, k: 0.5, l: 0.5, m: 0.5, n: 0.5, o: 0.5, p: 0.5, q: 0.5, r: 0.5, s: 0.5, t: 0.5, u: 0.5, v: 0.5, w: 0.5, x: 0.5, y: 0.5, z: 0.5, // Extra narrow characters '1': 0.25, '2': 0.5, '3': 0.5, '4': 0.5, '5': 0.5, '6': 0.5, '7': 0.5, '8': 0.5, '9': 0.5, '0': 0.5, '-': 0.5, _: 0.5, ',': 0.25, '.': 0.25, ':': 0.25, ';': 0.25, '!': 0.25, '?': 0.5, "'": 0.25, '"': 0.5, '`': 0.25, '(': 0.25, ')': 0.25, '[': 0.25, ']': 0.25, '{': 0.25, '}': 0.25, '<': 0.5, '>': 0.5, '/': 0.5, '\\': 0.5, '|': 0.25, '+': 0.5, '=': 0.5, '#': 0.5, $: 0.5, '%': 0.75, '^': 0.5, '&': 0.75, '*': 0.5, '@': 0.75, '~': 0.5, } // Compute the total expected width of the string const totalWidth = Array.from(inputString) .map((c) => charWidths[c as string] ?? 0.5) .reduce((acc, val) => acc + val, 0) // Compute the expected width per character return 1 / (totalWidth / inputString.length) }