Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(OverflowTooltipText): add new component #1461

Merged
merged 6 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Canvas, ArgTypes, Meta, Title } from '@storybook/blocks';

import * as OverflowTooltipTextStories from './OverflowTooltipText.stories';

<Meta of={OverflowTooltipTextStories} />

<Title>OverflowTooltipText</Title>

[Intro](#Intro) | [Component API](#ComponentAPI)

## Intro <a id="Intro" />

The OverflowTooltipText component displays a text element with a tooltip that appears when the text overflows its container.
It restricts the component to single-line usage and is utilized specifically for such cases.
It is typically used to ensure that long or truncated text can still be fully viewed by the user, enhancing readability and providing additional context in space-constrained layouts.

<Canvas of={OverflowTooltipTextStories.Default} sourceState="none" />

#### Example implementation

```jsx
<Text>
<OverflowTooltipText
text="This is a long text that will be truncated and displayed as a tooltip"
/>
</Text>
```

## Component API <a id="ComponentAPI" />

<ArgTypes of={OverflowTooltipTextStories.Default} sort="requiredFirst" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.tooltipContent {
word-break: break-word;
}

.text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.text-container {
width: 200px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Meta } from '@storybook/react';

import { Heading, Text } from '../Typography';

import { OverflowTooltipText } from './OverflowTooltipText';
import './OverflowTooltipText.stories.css';
import { OverflowTooltipTextProps } from './types';

export default {
title: 'Components/OverflowTooltipText',
component: OverflowTooltipText,
render: (args: OverflowTooltipTextProps) => (
<div className="text-container">
<OverflowTooltipText {...args} />
</div>
),
} as Meta<typeof OverflowTooltipText>;

export const Default = {
args: {
text: 'This is a text with a tooltip.',
},
};

export const ShortText = {
args: {
text: 'No tooltip.',
},
};

export const VeryLongText = {
args: {
text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
},
};

export const WithTextElement = {
render: (args: OverflowTooltipTextProps) => (
<div className="text-container">
<Text>
<OverflowTooltipText {...args} />
</Text>
</div>
),
args: {
text: 'This is a text with a Text element and a tooltip.',
},
};

export const WithHeadingElement = {
render: (args: OverflowTooltipTextProps) => (
<div className="text-container">
<Heading>
<OverflowTooltipText {...args} />
</Heading>
</div>
),
args: {
text: 'This is a text with a Heading element and a tooltip.',
},
};

export const NoWhiteSpacesText = {
args: {
text: '[email protected]',
},
};

export const SpecialCharacters = {
args: {
text: '🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type FC } from 'react';
import { useRef } from 'react';

import { useIsOverflow, useOnHover } from '../../hooks';
import { Tooltip } from '../Tooltip';

import { type OverflowTooltipTextProps } from './types';

import styles from './OverflowTooltipText.module.scss';

export const OverflowTooltipText: FC<OverflowTooltipTextProps> = ({ text }) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const isOverflow = useIsOverflow(wrapperRef);
const { isHovered, handleMouseOut, handleMouseOver } = useOnHover(isOverflow);

const renderChildren = () => {
return (
<div
onMouseEnter={handleMouseOver}
onMouseLeave={handleMouseOut}
ref={wrapperRef}
className={styles.text}
>
{text}
</div>
);
};

JoannaSikora marked this conversation as resolved.
Show resolved Hide resolved
return (
<Tooltip
kind="invert"
isVisible={isOverflow && isHovered}
triggerRenderer={renderChildren}
>
<div className={styles.tooltipContent}>{text}</div>
</Tooltip>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { OverflowTooltipText } from './OverflowTooltipText';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface OverflowTooltipTextProps {
text: string;
}
2 changes: 2 additions & 0 deletions packages/react-components/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { useAnimations } from './useAnimations';
export { useHeightResizer } from './useHeightResizer';
export { useMobileViewDetector } from './useMobileViewDetector';
export { useInteractive } from './useInteractive';
export { useOnHover } from './useOnHover';
export { useIsOverflow } from './useIsOverflow';
8 changes: 7 additions & 1 deletion packages/react-components/src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

export type NODE = HTMLDivElement | null;
export type NODE = HTMLElement | null;
export type CALLBACK = (newSize: DOMRectReadOnly) => void;

export interface IUseHeightResizer {
Expand All @@ -26,3 +26,9 @@ export interface IUseInteractive {
e: React.MouseEvent<HTMLElement, MouseEvent>
) => void;
}

export interface IUseOnHover {
isHovered: boolean;
handleMouseOver: () => void;
handleMouseOut: () => void;
}
41 changes: 41 additions & 0 deletions packages/react-components/src/hooks/useIsOverflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useState, useLayoutEffect, useCallback, RefObject } from 'react';

import debounce from 'lodash.debounce';

import { resizeCallback } from './helpers';

export const useIsOverflow = <T extends HTMLElement>(
ref: RefObject<T>
): boolean => {
const [isOverflow, setIsOverflow] = useState(false);

const checkOverflow = useCallback(() => {
if (ref.current) {
const isOverflowing =
ref.current.scrollHeight > ref.current.offsetHeight ||
ref.current.scrollWidth > ref.current.offsetWidth;
setIsOverflow(isOverflowing);
}
}, [ref]);

const checkOverflowDebounced = debounce(() => {
checkOverflow();
}, 100);
JoannaSikora marked this conversation as resolved.
Show resolved Hide resolved

useLayoutEffect(() => {
checkOverflow();

const node = ref.current;
if (node) {
resizeCallback(node, () => {
checkOverflowDebounced();
});
}

return () => {
resizeCallback(null, () => {});
};
}, [ref, checkOverflowDebounced]);
JoannaSikora marked this conversation as resolved.
Show resolved Hide resolved

return isOverflow;
};
16 changes: 16 additions & 0 deletions packages/react-components/src/hooks/useOnHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from 'react';

import { IUseOnHover } from './types';

export const useOnHover = (initialState = false): IUseOnHover => {
const [isHovered, setIsHovered] = useState(initialState);

const handleMouseOver = (): void => setIsHovered(true);
const handleMouseOut = (): void => setIsHovered(false);

return {
isHovered,
handleMouseOver,
handleMouseOut,
};
};
1 change: 1 addition & 0 deletions packages/react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export * from './components/Loader';
export * from './components/Modal';
export * from './components/NumericInput';
export * from './components/OnboardingChecklist';
export * from './components/OverflowTooltipText';
export * from './components/Picker';
export * from './components/Popover';
export * from './components/ProductSwitcher';
Expand Down
Loading