Skip to content

Commit

Permalink
feat: enhance OverflowTooltip overflow detection for precise handli…
Browse files Browse the repository at this point in the history
…ng of non-integral width or height (#806)

* fix: fix the OverflowTooltip issue in a specific environment

* feat: improve overflow detection using `getClientRects()

* feat: import utility functions from `@tonic-ui/utils`

* docs: update overtool tooltip examples

* docs: update tooltip and overflow-tooltip examples

* docs: update popover examples

---------

Co-authored-by: cheton <[email protected]>
  • Loading branch information
tinaClin and cheton authored Nov 15, 2023
1 parent 2a849da commit a1db209
Show file tree
Hide file tree
Showing 46 changed files with 1,355 additions and 929 deletions.
30 changes: 30 additions & 0 deletions packages/react-docs/pages/components/overflow-tooltip/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Box, Divider, OverflowTooltip } from '@tonic-ui/react';
import React from 'react';

const App = () => {
return (
<>
<OverflowTooltip
label="This is a tooltip"
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</OverflowTooltip>
<Divider my="4x" />
<Box width={140.7}>
<OverflowTooltip
label="This is a tooltip"
>
This text string is truncted
</OverflowTooltip>
</Box>
<Divider my="4x" />
<OverflowTooltip
label="This is a tooltip"
>
This text string is not truncated
</OverflowTooltip>
</>
);
};

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { OverflowTooltip } from '@tonic-ui/react';
import React from 'react';

const App = () => {
return (
<OverflowTooltip
PopperProps={{ usePortal: true }}
label="This is a tooltip"
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</OverflowTooltip>
);
};

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Icon, Flex, OverflowTooltip, Text } from '@tonic-ui/react';
import React from 'react';

const App = () => {
return (
<OverflowTooltip
label="This is a tooltip"
>
{({ ref, style }) => (
<Flex alignItems="center" columnGap="2x">
<Icon icon="menu" />
<Text ref={ref} {...style}>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</Text>
</Flex>
)}
</OverflowTooltip>
);
};

export default App;
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,11 @@ import { OverflowTooltip } from '@tonic-ui/react';

If the text overflows its container, it will be truncated, and an ellipsis will be added. When you hover or focus on the ellipsis text, a tooltip will appear.

```jsx
<OverflowTooltip
label="This is a tooltip"
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</OverflowTooltip>
```
{render('./basic')}

In the second example, a function is passed as a child of the `OverflowTooltip` component. The function is called with an object containing a `ref` and a `style` property, which should be spread to the element that needs to be truncated. In this case, a `Text` component is used to display the text, and the `ref` and `style` props are spread to it. This allows the `OverflowTooltip` component to detect when the text overflows and display a tooltip.

```jsx
<OverflowTooltip
label="This is a tooltip"
>
{({ ref, style }) => (
<Flex alignItems="center" columnGap="2x">
<Icon icon="menu" />
<Text ref={ref} {...style}>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</Text>
</Flex>
)}
</OverflowTooltip>
```
{render('./function-as-child-component')}

## Commonly Asked Questions

Expand All @@ -45,14 +26,7 @@ By default, the `OverflowTooltip` component positions the tooltip relative to it

To mitigate this issue, you can pass `PopperProps={{ usePortal: true }}` to `OverflowTooltip` to make it positioned on the document root.

```jsx
<OverflowTooltip
PopperProps={{ usePortal: true }}
label="This is a tooltip"
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</OverflowTooltip>
```
{render('./faq-use-portal')}

## Props

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Box, Divider, OverflowTooltip, Text } from '@tonic-ui/react';
import React from 'react';

const App = () => {
return (
<>
<Box width={360}>
<OverflowTooltip
label="This is a tooltip"
>
{({ ref, style }) => (
<Text
ref={ref}
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 3,
overflow: 'hidden',
}}
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</Text>
)}
</OverflowTooltip>
</Box>
<Divider my="4x" />
<Box width={480}>
<OverflowTooltip
label="This is a tooltip"
>
{({ ref, style }) => (
<Text
ref={ref}
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 3,
overflow: 'hidden',
}}
>
This text string will be truncated when exceeding its container width. To see this in action, try resizing your browser viewport. If the text overflows, a tooltip will appear, displaying the full content.
</Text>
)}
</OverflowTooltip>
</Box>
</>
);
};

export default App;
30 changes: 30 additions & 0 deletions packages/react-docs/pages/components/popover/controlled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Button, Flex, Popover, PopoverContent, PopoverTrigger, Switch, Text } from '@tonic-ui/react';
import { useToggle } from '@tonic-ui/react-hooks';
import React from 'react';

const App = () => {
const [on, toggle] = useToggle(false);

return (
<>
<Flex mb="4x">
<Switch checked={on} onChange={toggle} />
</Flex>
<Popover
isOpen={on}
placement="bottom"
>
<PopoverTrigger>
<Button onClick={toggle}>
Trigger
</Button>
</PopoverTrigger>
<PopoverContent>
<Text>This is a controlled popover</Text>
</PopoverContent>
</Popover>
</>
);
};

export default App;
83 changes: 83 additions & 0 deletions packages/react-docs/pages/components/popover/faq-flip-modifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
Box,
Checkbox,
Divider,
Flex,
Popover,
PopoverContent,
PopoverTrigger,
Scrollbar,
Space,
Text,
TextLabel,
useColorMode,
useColorStyle,
} from '@tonic-ui/react';
import { useToggle } from '@tonic-ui/react-hooks';
import React from 'react';

const FormGroup = (props) => (
<Box mb="4x" {...props} />
);

const App = () => {
const [colorMode] = useColorMode();
const [colorStyle] = useColorStyle({ colorMode });
const [isFlipModifierEnabled, toggleIsFlipModifierEnabled] = useToggle(true);

return (
<>
<Box mb="4x">
<Text fontSize="lg" lineHeight="lg">
Modifiers
</Text>
</Box>
<FormGroup>
<TextLabel display="inline-flex" alignItems="center">
<Checkbox
checked={isFlipModifierEnabled}
onChange={() => toggleIsFlipModifierEnabled()}
/>
<Space width="2x" />
<Text fontFamily="mono" whiteSpace="nowrap">Enable flip modifier</Text>
</TextLabel>
</FormGroup>
<Divider my="4x" />
<Scrollbar
height={180}
width={180}
overflowY="visible"
border={1}
borderColor={colorStyle.divider}
>
<Flex
alignItems="center"
justifyContent="center"
height={300}
>
<Popover isOpen placement="top">
<PopoverTrigger>
<Text display="inline-block">
Reference
</Text>
</PopoverTrigger>
<PopoverContent
PopperProps={{
modifiers: [
{ // https://popper.js.org/docs/v2/modifiers/flip/
name: 'flip',
enabled: isFlipModifierEnabled,
},
],
}}
>
Popover
</PopoverContent>
</Popover>
</Flex>
</Scrollbar>
</>
);
};

export default App;
19 changes: 19 additions & 0 deletions packages/react-docs/pages/components/popover/faq-use-portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button, Popover, PopoverContent, PopoverTrigger } from '@tonic-ui/react';
import React from 'react';

const App = () => (
<Popover>
<PopoverTrigger>
<Button variant="secondary">Trigger</Button>
</PopoverTrigger>
<PopoverContent
PopperProps={{
usePortal: true,
}}
>
Popover
</PopoverContent>
</Popover>
);

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Button, Input, Popover, PopoverBody, PopoverContent, PopoverTrigger, Stack, Text } from '@tonic-ui/react';
import React, { useRef } from 'react';

const App = () => {
const initialFocusRef1 = useRef();
const initialFocusRef2 = useRef();

return (
<Stack spacing="6x" width="fit-content">
<Popover initialFocusRef={initialFocusRef1}>
<PopoverTrigger>
<Button variant="secondary">
Interactive Trigger
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Input mt="3x" ref={initialFocusRef1} defaultValue="Popover" />
</PopoverBody>
</PopoverContent>
</Popover>
<Popover initialFocusRef={initialFocusRef2}>
<PopoverTrigger tabIndex={-1}>
<Text
userSelect="none"
_hover={{ cursor: 'pointer' }}
>
Non-interactive Trigger
</Text>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Input mt="3x" ref={initialFocusRef2} defaultValue="Popover" />
</PopoverBody>
</PopoverContent>
</Popover>
</Stack>
);
};

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button, Input, Popover, PopoverBody, PopoverContent, PopoverTrigger, Stack, Text } from '@tonic-ui/react';
import React, { useRef } from 'react';

const App = () => {
const initialFocusRef1 = useRef();
const initialFocusRef2 = useRef();

return (
<Stack spacing="6x" width="fit-content">
<Popover
initialFocusRef={initialFocusRef1}
returnFocusOnClose={false}
>
<PopoverTrigger>
<Button variant="secondary">
Interactive Trigger
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Input mt="3x" ref={initialFocusRef1} defaultValue="Popover" />
</PopoverBody>
</PopoverContent>
</Popover>
<Popover
initialFocusRef={initialFocusRef2}
returnFocusOnClose={false}
>
<PopoverTrigger tabIndex={-1}>
<Text
userSelect="none"
_hover={{ cursor: 'pointer' }}
>
Non-interactive Trigger
</Text>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Input mt="3x" ref={initialFocusRef2} defaultValue="Popover" />
</PopoverBody>
</PopoverContent>
</Popover>
</Stack>
);
};

export default App;
Loading

0 comments on commit a1db209

Please sign in to comment.