/* eslint-disable jsdoc/require-jsdoc */ import type * as React from 'react'; import { warnOnce } from '../../lib/warnOnce'; import { getBadgeIconSizeByImageBaseSize, getFallbackIconSizeByImageBaseSize, getOverlayIconSizeByImageBaseSize, } from './helpers'; import { imageBaseSizes } from './types'; /** * Пример, * Icon28User -> 28 * Icon12Circle1 -> 12 * Icon12Circle2 -> 12 * Icon20TextHeading1Outline -> 20. */ function parseIconSizeByDisplayName(displayName: unknown): number | null { if (typeof displayName !== 'string') { return null; } const match = /Icon(\d+)/.exec(displayName); return match ? Number(match[1]) : null; } function parseIconSizeByWidthProp(width: unknown): number | null { if (typeof width !== 'string' && typeof width !== 'number') { return null; } const size = Number(width); return size > 0 ? size : null; } function getElementDisplayName(element: React.JSX.Element): string | null { return element.type.displayName ?? element.type.name ?? null; } function getElementWidthProp(element: React.JSX.Element): number | string | null { return element.props.width ?? null; } function getIconSizeByElement(element: React.JSX.Element): number | null { const sizeByDisplayName = parseIconSizeByDisplayName(getElementDisplayName(element)); const sizeByWidth = parseIconSizeByWidthProp(getElementWidthProp(element)); return sizeByWidth ? sizeByWidth : sizeByDisplayName; } interface IconProp { name: string; value: React.JSX.Element; } interface SelectorFn { (imageSize: number): number; MAX_SIZE: number; } function validateIconComponentSizeByImageSize( imageSize: number, iconProp: IconProp, selectorFn: SelectorFn, logger: ReturnType, ) { const iconSize = getIconSizeByElement(iconProp.value); if (iconSize === null) { return; } const result = selectorFn(imageSize); if (result === iconSize || (result === selectorFn.MAX_SIZE && iconSize >= result)) { return; } const iconName = getElementDisplayName(iconProp.value); const propMessage = iconName ? `${iconProp.name}={<${iconName} />}` : iconProp.name; logger( `Размер \`${propMessage}\` не соответствует дизайн-системе. Для \`size={${imageSize}}\` размер иконки для \`${iconProp.name}\` должен соответствовать />. Если такого размера нет, то используйте <${iconName} width={${result}} height={${result}} />`, 'log', ); } const warnImageBase = warnOnce('ImageBase'); export function validateFallbackIcon(imageSize: number, iconProp: IconProp): void { return validateIconComponentSizeByImageSize( imageSize, iconProp, getFallbackIconSizeByImageBaseSize, warnImageBase, ); } const mapOfExpectedSize = new Set(imageBaseSizes); const arrayOfSizes = Array.from(mapOfExpectedSize).map((str) => Number(str)); const maxSize = arrayOfSizes.reduce((maxSize, size) => (size > maxSize ? size : maxSize), 0); export function validateSize(imageSize: number): void { if (imageSize > maxSize || mapOfExpectedSize.has(imageSize)) { return; } warnImageBase( `\`size={${imageSize}}\` не соответствует дизайн-системе. Пожалуйста, используйте один из следующих вариантов: ${arrayOfSizes.join( ' | ', )}`, 'log', ); } const warnImageBaseBadge = warnOnce('ImageBase.Badge'); export function validateBadgeIcon(imageSize: number, iconProp: IconProp): void { if (imageSize < 24 && iconProp) { return warnImageBaseBadge('Не используйте бейдж при `size < 24`.', 'log'); } validateIconComponentSizeByImageSize( imageSize, iconProp, getBadgeIconSizeByImageBaseSize, warnImageBaseBadge, ); } const warnImageBaseOverlay = warnOnce('ImageBase.Overlay'); export function validateOverlayIcon(imageSize: number, iconProp: IconProp): void { validateIconComponentSizeByImageSize( imageSize, iconProp, getOverlayIconSizeByImageBaseSize, warnImageBaseOverlay, ); }