/
kaws
/
ui-kit-ce
Обзор
Документация
Войти
/
kaws
/
ui-kit-ce
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
packages/typography/src/Text.tsx
425 строк
11 KB
Строганов Федор
fix(typography): исправлено отображение tooltip с многострочным обрезанным текстом
22 май 2025, 12:29
22 май 2025, 12:29
9c3d3b2
Код
Авторство
О чём код?
import * as React from 'react' import { clsx } from '@v-uik/theme' import { useClassList, useCopy } from '@v-uik/hooks' import { useText } from './hooks' import { TextComponentsConfig } from './components' import { Ellipsis, EllipsisConfig } from './Ellipsis' import { mergeRefs } from '@v-uik/utils' import { Copy } from './Copy' import { getChildrenText } from './utils' import { TooltipProps } from '@v-uik/tooltip' type Classes = Partial< Record< //v2 | 'displayLg' | 'displayMd' | 'displaySm' | 'headline1' | 'headline2' | 'headline3' | 'headline4' | 'headline5' | 'titleLg' | 'titleMd' | 'titleSm' | 'bodyXl' | 'bodyLg' | 'bodyMd' | 'bodySm' | 'uiTextLg' | 'uiTextMd' | 'uiTextSm' | 'codeLg' | 'codeMd' | 'codeSm' //v1 | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'button' | 'caption' | 'overline' | 'code1' | 'code2' //modifiers | 'ellipsis' | 'gutterBottom' | 'root' | 'copy' | 'copyIcon' | 'copiedIcon', string > > export const TextKinds = { //v2 displayLg: 'displayLg', displayMd: 'displayMd', displaySm: 'displaySm', headline1: 'headline1', headline2: 'headline2', headline3: 'headline3', headline4: 'headline4', headline5: 'headline5', titleLg: 'titleLg', titleMd: 'titleMd', titleSm: 'titleSm', bodyXl: 'bodyXl', bodyLg: 'bodyLg', bodyMd: 'bodyMd', bodySm: 'bodySm', uiTextLg: 'uiTextLg', uiTextMd: 'uiTextMd', uiTextSm: 'uiTextSm', codeLg: 'codeLg', codeMd: 'codeMd', codeSm: 'codeSm', //v1 h1: 'h1', h2: 'h2', h3: 'h3', h4: 'h4', h5: 'h5', h6: 'h6', subtitle1: 'subtitle1', subtitle2: 'subtitle2', body1: 'body1', body2: 'body2', button: 'button', caption: 'caption', overline: 'overline', code1: 'code1', code2: 'code2', } as const export type TTextKinds = keyof typeof TextKinds const elementByKindsMap: { [key in TTextKinds]?: keyof React.ReactHTML } = { // v2 displayLg: 'p', displayMd: 'p', displaySm: 'p', headline1: 'h1', headline2: 'h2', headline3: 'h3', headline4: 'h4', headline5: 'h5', titleLg: 'h6', titleMd: 'p', titleSm: 'p', bodyXl: 'div', bodyLg: 'div', bodyMd: 'div', bodySm: 'div', codeLg: 'pre', codeMd: 'pre', codeSm: 'pre', // v1 h1: 'h1', h2: 'h2', h3: 'h3', h4: 'h4', h5: 'h5', h6: 'h6', // https://www.w3.org/TR/2017/WD-html52-20170228/common-idioms-without-dedicated-elements.html#subheadings-subtitles-alternative-titles-and-taglines subtitle1: 'p', subtitle2: 'p', body1: 'div', body2: 'div', code1: 'pre', code2: 'pre', } export type TextPropsCopyConfig = Pick< TextProps<true>, | 'withCopy' | 'onCopy' | 'onBeforeCopy' | 'getCopyText' | 'copyTooltipText' | 'copiedTooltipText' | 'copyTooltipProps' | 'copiedDuration' | 'copyIconWrapperProps' | 'onCopyError' > export interface TextProps<TCanCopy extends boolean | undefined = false> extends Omit<React.HTMLAttributes<HTMLElement>, 'onCopy'> { /** * JSS-классы для стилизации. * * @inner */ classes?: Classes /** * Стиль текста. * * @inner */ kind?: TTextKinds /** * HTML тег, который будет отрисован вместо заданных по умолчанию. * * @inner */ as?: keyof React.ReactHTML /** * Свойство для переопределения элементов `Text`. * * @inner */ components?: TextComponentsConfig /** * Добавить отступ под текстом. * * @inner */ gutterBottom?: boolean /** * Обрезает текст с многоточием в конце. * Обратите внимание, что при использовании свойства `dangerouslySetInnerHTML` конфигурация `ellipsis` с помощью объекта работать не будет. * * @inner */ ellipsis?: boolean | EllipsisConfig /** * Устанавливает HTML-содержимое элемента напрямую. * * @inner */ dangerouslySetInnerHTML?: React.DOMAttributes<HTMLElement>['dangerouslySetInnerHTML'] /** * Добавляет возможность копирования текста по нажатию. * * @inner */ withCopy?: TCanCopy | boolean /** * Функция обратного вызова, срабатывающая в момент копирования текста. * * @param {string} text Копируемый текст. * * @returns */ onCopy?: TCanCopy extends true ? (text: string) => void : never /** * Функция обратного вызова, срабатывающая перед копированием текста. * * @param {string} text Копируемый текст. * * @returns */ onBeforeCopy?: TCanCopy extends true ? (text: string) => void : never /** * Функция обратного вызова, позволяющая изменить текст, копируемый в буфер обмена. * * @param {string} text Копируемый текст. * * @returns */ getCopyText?: TCanCopy extends true ? (text: string) => string : never /** * Текст всплывающей подсказки при наведении на текст после копирования. * * @inner */ copiedTooltipText?: TCanCopy extends true ? string : never /** * Текст всплывающей подсказки при наведении на текст перед копированием. * * @inner */ copyTooltipText?: TCanCopy extends true ? string : never /** * Свойства элемента всплывающей подсказки. * * @inner */ copyTooltipProps?: TCanCopy extends true ? Omit<TooltipProps, 'children'> : never /** * Продолжительность показа состояния "скопировано" в миллисекундах. * * @inner */ copiedDuration?: TCanCopy extends true ? number : never /** * Свойства компонента иконки копирования текста. * * @inner */ copyIconWrapperProps?: TCanCopy extends true ? React.ComponentProps<'span'> : never /** * Функция обратного вызова, отвечающая за обработку ошибки при копировании текста. * * @param error Объект ошибки при копировании. * * @returns */ onCopyError?: TCanCopy extends true ? (error: Error) => void : never } export const Text = React.forwardRef( <TCanCopy extends boolean | undefined = false>( { classes, className: classNameProp, kind = TextKinds.body2, as, ellipsis, children, gutterBottom = false, style, components, dangerouslySetInnerHTML, withCopy, copiedDuration, copyTooltipProps, onCopy, onBeforeCopy, copiedTooltipText, copyTooltipText, copyIconWrapperProps, getCopyText, onCopyError, ...rest }: TextProps<TCanCopy>, ref: React.Ref<HTMLElement> ) => { const containerRef = React.useRef<HTMLElement>(null) const [isCopyTooltipOpen, setIsCopyTooltipOpen] = React.useState(false) const { rows, expandButtonProps = {} } = (ellipsis as EllipsisConfig) || {} const canUseCssEllipsis = withCopy ? !ellipsis : typeof ellipsis === 'boolean' || !ellipsis?.expandable const ellipsisInlineStyle = !!ellipsis && canUseCssEllipsis && rows ? ({ display: '-webkit-box', WebkitBoxOrient: 'vertical', WebkitLineClamp: rows, whiteSpace: 'unset', } as React.CSSProperties) : undefined const textClasses = useText() const classesMap = useClassList(textClasses, classes) const { isCopied, copyToClipboard } = useCopy({ copiedDuration, onCopy, onBeforeCopy, getCopyText, text: getChildrenText(children), onError: onCopyError, }) const className = clsx(classNameProp, classesMap.root, classesMap[kind], { [classesMap.gutterBottom]: gutterBottom, [classesMap.ellipsis]: !!ellipsis && canUseCssEllipsis, [classesMap.copy]: !!withCopy, }) const commonProps = { ...rest, ref: mergeRefs([containerRef, ref]), className, style: { ...style, ...ellipsisInlineStyle, }, } const ellipsisProps = { ...(typeof ellipsis === 'object' ? ellipsis : {}), needMeasure: !canUseCssEllipsis, rows: rows, parentRef: containerRef, components, expandButtonProps, ellipsisInlineStyle, } const copyProps = withCopy ? { ...rest, onMouseEnter: (e: React.MouseEvent<HTMLElement>) => { rest.onMouseEnter?.(e) setIsCopyTooltipOpen(true) }, onMouseLeave: (e: React.MouseEvent<HTMLElement>) => { rest.onMouseLeave?.(e) setIsCopyTooltipOpen(false) }, onClick: async (e: React.MouseEvent<HTMLElement>) => { rest.onClick?.(e) await copyToClipboard() }, style: { cursor: 'pointer', ...style, ...ellipsisInlineStyle, }, } : {} const Element = as || elementByKindsMap[kind] || 'span' if (dangerouslySetInnerHTML) { return React.createElement(Element, { ...commonProps, dangerouslySetInnerHTML, }) } return React.createElement( Element, { ...commonProps, ...copyProps, }, withCopy ? ( <Copy components={components} isCopied={isCopied} isCopyTooltipOpen={isCopyTooltipOpen} iconWrapperProps={copyIconWrapperProps} textKind={kind} copyTooltipText={copyTooltipText} copiedTooltipText={copiedTooltipText} tooltipProps={copyTooltipProps} copyIconClassName={classesMap.copyIcon} copiedIconClassName={classesMap.copiedIcon} > <Ellipsis {...ellipsisProps}>{children}</Ellipsis> </Copy> ) : ( <Ellipsis {...ellipsisProps}>{children}</Ellipsis> ) ) } ) as <TCanCopy extends boolean | undefined = false>( props: TextProps<TCanCopy> & { /** * */ ref?: React.Ref<HTMLElement> | undefined } ) => JSX.Element