-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Tooltip)!: introduce 2.0 component
- add in new styles - preserve existing API, tests, etc.
- Loading branch information
1 parent
9dfd62d
commit 3870b47
Showing
7 changed files
with
743 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/*------------------------------------*\ | ||
# TOOLTIP | ||
\*------------------------------------*/ | ||
|
||
.tooltip { | ||
border-style: solid; | ||
border-width: var(--eds-border-width-sm); | ||
border-radius: calc(var(--eds-theme-border-radius-surfaces-md) * 1px); | ||
box-shadow: var(--eds-box-shadow-md); | ||
max-width: 14rem; | ||
|
||
@media (prefers-reduced-motion) { | ||
transition: none; | ||
} | ||
|
||
border-color: var(--eds-theme-color-background-utility-default-high-emphasis); | ||
color: var(--eds-theme-color-text-utility-inverse); | ||
background-color: var(--eds-theme-color-background-utility-default-high-emphasis); | ||
--arrow-color: var(--eds-theme-color-background-utility-default-high-emphasis); | ||
} | ||
|
||
/** | ||
* Enables opacity fade animation | ||
*/ | ||
.tooltip[data-state='hidden'] { | ||
opacity: 0; | ||
} | ||
|
||
/* TODO-AH: consider finding a way to not use these module semantics, e.g., global: */ | ||
.tooltip :global(.tippy-content) { | ||
padding-left: 1rem; | ||
padding-right: 1rem; | ||
padding-bottom: 0.5rem; | ||
padding-top: 0.5rem; | ||
} | ||
|
||
/** | ||
* Add arrows | ||
*/ | ||
.tooltip :global(.tippy-arrow) { | ||
position: absolute; | ||
|
||
width: 1rem; | ||
height: 1rem; | ||
} | ||
|
||
.tooltip :global(.tippy-arrow::before) { | ||
position: absolute; | ||
|
||
border-style: solid; | ||
border-color: transparent; | ||
border-width: 0.5rem; | ||
|
||
content: ''; | ||
} | ||
|
||
.tooltip[data-placement^='top'] :global(.tippy-arrow) { | ||
left: 0; | ||
|
||
transform: translate3d(73px, 0, 0); | ||
} | ||
|
||
.tooltip[data-placement^='bottom'] :global(.tippy-arrow) { | ||
top: 0; | ||
left: 0; | ||
|
||
transform: translate3d(73px, 0, 0); | ||
} | ||
|
||
.tooltip[data-placement^='left'] :global(.tippy-arrow) { | ||
top: 0; | ||
right: 0; | ||
|
||
transform: translate3d(0, 19px, 0); | ||
} | ||
|
||
.tooltip[data-placement^='right'] :global(.tippy-arrow) { | ||
top: 0; | ||
left: 0; | ||
|
||
transform: translate3d(0, 25px, 0); | ||
} | ||
|
||
.tooltip[data-placement^='top'] :global(.tippy-arrow::before) { | ||
left: 0; | ||
|
||
border-top-color: var(--arrow-color); | ||
border-bottom-width: 0; | ||
} | ||
|
||
.tooltip[data-placement^='bottom'] :global(.tippy-arrow::before) { | ||
left: 0; | ||
|
||
border-bottom-color: var(--arrow-color); | ||
border-top-width: 0; | ||
top: -7px; | ||
} | ||
|
||
.tooltip[data-placement^='left'] :global(.tippy-arrow::before) { | ||
border-left-color: var(--arrow-color); | ||
border-right-width: 0; | ||
right: -7px; | ||
} | ||
|
||
.tooltip[data-placement^='right'] :global(.tippy-arrow::before) { | ||
border-right-color: var(--arrow-color); | ||
border-left-width: 0; | ||
left: -7px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import React from 'react'; | ||
|
||
import { Tooltip } from './Tooltip-v2'; | ||
|
||
// diminishing the threshold of this component to avoid sub-pixel jittering | ||
// https://www.chromatic.com/docs/threshold | ||
const diffThreshold = 0.75; | ||
const defaultArgs = { | ||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a erat eu augue consequat eleifend non vel sem. Praesent efficitur mauris ac leo semper accumsan.', | ||
children: <div className="fpo p-1">Target Component</div>, | ||
placement: 'right', | ||
// most stories show a visible, non-interactive tooltip. | ||
// this turns animation off to ensure stable visual snapshots | ||
duration: 0, | ||
visible: true, | ||
}; | ||
|
||
export default { | ||
title: 'Components/V2/Tooltip', | ||
component: Tooltip, | ||
args: defaultArgs, | ||
argTypes: { | ||
text: { | ||
control: { | ||
type: 'text', | ||
}, | ||
}, | ||
children: { | ||
control: { | ||
type: null, | ||
}, | ||
}, | ||
placement: { | ||
table: { | ||
defaultValue: { summary: 'top' }, | ||
}, | ||
}, | ||
}, | ||
parameters: { | ||
layout: 'centered', | ||
badges: ['intro-1.0', 'current-2.0'], | ||
chromatic: { | ||
diffThreshold, | ||
diffIncludeAntiAliasing: false, | ||
}, | ||
}, | ||
decorators: [(Story) => <div className="p-8">{Story()}</div>], | ||
} as Meta<Args>; | ||
|
||
type Args = React.ComponentProps<typeof Tooltip>; | ||
type Story = StoryObj<Args>; | ||
|
||
/** | ||
* The following stories demonstrate how `Tooltip` can be made to appear on different sides of the trigger. | ||
* Each story name denotes a value pased to `placement`. | ||
*/ | ||
export const LeftPlacement: Story = { | ||
args: { | ||
placement: 'left', | ||
children: <div className="fpo p-1">Target Component</div>, | ||
}, | ||
parameters: { | ||
chromatic: { disableSnapshot: true }, | ||
}, | ||
}; | ||
|
||
export const TopPlacement: Story = { | ||
args: { | ||
placement: 'top', | ||
children: <div className="fpo p-1">Target Component</div>, | ||
}, | ||
}; | ||
|
||
export const BottomPlacement: Story = { | ||
args: { | ||
placement: 'bottom', | ||
children: <div className="fpo p-1">Target Component</div>, | ||
}, | ||
}; | ||
|
||
export const LongText: Story = { | ||
args: { | ||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a erat eu augue consequat eleifend non vel sem. Praesent efficitur mauris ac leo semper accumsan. Donec posuere semper fermentum. Vivamus venenatis laoreet venenatis. Sed consectetur, dolor sed tristique vehicula, sapien nulla convallis odio, et tempus urna mi eu leo. Phasellus a venenatis sapien. Cras massa lectus, sollicitudin id nulla id, laoreet facilisis est.', | ||
}, | ||
}; | ||
|
||
export const LongTriggerText: Story = { | ||
args: { | ||
children: <div className="fpo p-1">Longer text to test placement</div>, | ||
}, | ||
}; | ||
|
||
/** | ||
* Hover over the button to make the tooltip appear. | ||
*/ | ||
export const Interactive: Story = { | ||
args: { | ||
// reset prop values defined in defaultArgs | ||
duration: undefined, | ||
visible: undefined, | ||
children: <button className="fpo p-1">Target Component</button>, | ||
}, | ||
}; | ||
|
||
/** | ||
* Hover over the button to make the tooltip appear. | ||
*/ | ||
export const InteractiveDisabled: Story = { | ||
args: { | ||
duration: undefined, | ||
}, | ||
render: (args) => ( | ||
<Tooltip | ||
childNotInteractive | ||
duration={args.duration} | ||
placement="top" | ||
text={defaultArgs.text} | ||
> | ||
<div className="fpo p-1">Target Component</div> | ||
</Tooltip> | ||
), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { generateSnapshots } from '@chanzuckerberg/story-utils'; | ||
import { composeStories } from '@storybook/testing-react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
|
||
import * as TooltipStoryFile from './Tooltip-v2.stories'; | ||
|
||
const { Interactive, InteractiveDisabled } = composeStories(TooltipStoryFile); | ||
|
||
describe('<Tooltip /> (v2)', () => { | ||
generateSnapshots(TooltipStoryFile, { | ||
// Tippy renders tooltip as a child of <body> and hence is why baseElement needs to be targetted | ||
getElement: (wrapper) => { | ||
return wrapper.baseElement; | ||
}, | ||
}); | ||
|
||
it('should close tooltip via escape key', async () => { | ||
const user = userEvent.setup(); | ||
// disable animation for test | ||
render(<Interactive duration={0} />); | ||
const trigger = await screen.findByRole('button'); | ||
expect(screen.queryByTestId('tooltip-content')).not.toBeInTheDocument(); | ||
await user.hover(trigger); | ||
expect(screen.getByTestId('tooltip-content')).toBeInTheDocument(); | ||
await user.keyboard('{Escape}'); | ||
expect(screen.queryByTestId('tooltip-content')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('should close tooltip via escape key for disabled buttons', async () => { | ||
const user = userEvent.setup(); | ||
// disable animation for test | ||
render(<InteractiveDisabled duration={0} />); | ||
const trigger = await screen.findByTestId('disabled-child-tooltip-wrapper'); | ||
expect(screen.queryByTestId('tooltip-content')).not.toBeInTheDocument(); | ||
await user.hover(trigger); | ||
expect(screen.getByTestId('tooltip-content')).toBeInTheDocument(); | ||
await user.keyboard('{Escape}'); | ||
expect(screen.queryByTestId('tooltip-content')).not.toBeInTheDocument(); | ||
}); | ||
}); |
Oops, something went wrong.