mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana-UI: Create fast path in Text component (#76167)
Text component fast path Truncated text an isolated component
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { createElement, CSSProperties, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import ReactDomServer from 'react-dom/server';
|
||||
import React, { createElement, CSSProperties } from 'react';
|
||||
|
||||
import { GrafanaTheme2, ThemeTypographyVariantTypes } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
|
||||
import { TruncatedText } from './TruncatedText';
|
||||
import { customWeight, customColor, customVariant } from './utils';
|
||||
|
||||
export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
@@ -30,70 +29,35 @@ export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'clas
|
||||
export const Text = React.forwardRef<HTMLElement, TextProps>(
|
||||
({ element = 'span', variant, weight, color, truncate, italic, textAlignment, children, ...restProps }, ref) => {
|
||||
const styles = useStyles2(getTextStyles, element, variant, color, weight, truncate, italic, textAlignment);
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
const internalRef = useRef<HTMLElement>(null);
|
||||
|
||||
// wire up the forwarded ref to the internal ref
|
||||
useImperativeHandle<HTMLElement | null, HTMLElement | null>(ref, () => internalRef.current);
|
||||
|
||||
const childElement = createElement(
|
||||
const childElement = (ref: React.ForwardedRef<HTMLElement> | undefined) => {
|
||||
return createElement(
|
||||
element,
|
||||
{
|
||||
...restProps,
|
||||
style: undefined, // remove style prop to avoid overriding the styles
|
||||
style: undefined, // Remove the style prop to avoid overriding the styles
|
||||
className: styles,
|
||||
// when overflowing, the internalRef is passed to the tooltip which forwards it on to the child element
|
||||
ref: isOverflowing ? undefined : internalRef,
|
||||
// When overflowing, the internalRef is passed to the tooltip, which forwards it to the child element
|
||||
ref,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
const resizeObserver = useMemo(
|
||||
() =>
|
||||
new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.target.clientWidth && entry.target.scrollWidth) {
|
||||
if (entry.target.scrollWidth > entry.target.clientWidth) {
|
||||
setIsOverflowing(true);
|
||||
}
|
||||
if (entry.target.scrollWidth <= entry.target.clientWidth) {
|
||||
setIsOverflowing(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { current } = internalRef;
|
||||
if (current && truncate) {
|
||||
resizeObserver.observe(current);
|
||||
}
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [isOverflowing, resizeObserver, truncate]);
|
||||
|
||||
const getTooltipText = (children: NonNullable<React.ReactNode>) => {
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
// A 'span' is an inline element, so it can't be truncated
|
||||
// and it should be wrapped in a parent element that will show the tooltip
|
||||
if (!truncate || element === 'span') {
|
||||
return childElement(undefined);
|
||||
}
|
||||
const html = ReactDomServer.renderToStaticMarkup(<>{children}</>);
|
||||
const getRidOfTags = html.replace(/(<([^>]+)>)/gi, '');
|
||||
return getRidOfTags;
|
||||
};
|
||||
// A 'span' is an inline element therefore it can't be truncated
|
||||
// and it should be wrapped in a parent element that is the one that will show the tooltip
|
||||
if (truncate && isOverflowing && element !== 'span') {
|
||||
|
||||
return (
|
||||
<Tooltip ref={internalRef} content={getTooltipText(children)}>
|
||||
{childElement}
|
||||
</Tooltip>
|
||||
<TruncatedText
|
||||
childElement={childElement}
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
children={children}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return childElement;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
64
packages/grafana-ui/src/components/Text/TruncatedText.tsx
Normal file
64
packages/grafana-ui/src/components/Text/TruncatedText.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
|
||||
interface TruncatedTextProps {
|
||||
childElement: (ref: React.ForwardedRef<HTMLElement> | undefined) => React.ReactElement;
|
||||
children: NonNullable<React.ReactNode>;
|
||||
}
|
||||
|
||||
export const TruncatedText = React.forwardRef<HTMLElement, TruncatedTextProps>(({ childElement, children }, ref) => {
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
const internalRef = useRef<HTMLElement>(null);
|
||||
|
||||
// Wire up the forwarded ref to the internal ref
|
||||
useImperativeHandle<HTMLElement | null, HTMLElement | null>(ref, () => internalRef.current);
|
||||
|
||||
const resizeObserver = useMemo(
|
||||
() =>
|
||||
new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.target.clientWidth && entry.target.scrollWidth) {
|
||||
if (entry.target.scrollWidth > entry.target.clientWidth) {
|
||||
setIsOverflowing(true);
|
||||
}
|
||||
if (entry.target.scrollWidth <= entry.target.clientWidth) {
|
||||
setIsOverflowing(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { current } = internalRef;
|
||||
if (current) {
|
||||
resizeObserver.observe(current);
|
||||
}
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [setIsOverflowing, resizeObserver]);
|
||||
|
||||
const getTooltipText = (children: NonNullable<React.ReactNode>) => {
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
}
|
||||
const html = ReactDOMServer.renderToStaticMarkup(<>{children}</>);
|
||||
return html.replace(/(<([^>]+)>)/gi, '');
|
||||
};
|
||||
|
||||
if (isOverflowing) {
|
||||
return (
|
||||
<Tooltip ref={internalRef} content={getTooltipText(children)}>
|
||||
{childElement(undefined)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return childElement(internalRef);
|
||||
}
|
||||
});
|
||||
|
||||
TruncatedText.displayName = 'TruncatedText';
|
||||
Reference in New Issue
Block a user