Skip to content

Commit

Permalink
feat(OverflowTooltipText): setup new component
Browse files Browse the repository at this point in the history
  • Loading branch information
JoannaSikora committed Dec 19, 2024
1 parent 8736e71 commit fe04d25
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 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,67 @@
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: 'john.doensky.lorem.ipsum.lorem.ipsum.lorem.dolorem@gmail.com',
},
};
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>
);
};

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;
}
45 changes: 45 additions & 0 deletions packages/react-components/src/hooks/useIsOverflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 = useCallback(
() =>
debounce(() => {
checkOverflow();
}, 100),
[checkOverflow]
);

useLayoutEffect(() => {
checkOverflow();

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

return () => {
resizeCallback(null, () => {});
};
}, [ref, checkOverflowDebounced]);

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

0 comments on commit fe04d25

Please sign in to comment.