Skip to content

Commit 556ffd5

Browse files
Merge branch 'dev' of https://github.com/DistributedCollective/sovryn-dapp into feat/SOV-680-d-2-address-table-pagination
# Conflicts: # packages/ui/src/1_atoms/Icon/iconNames.ts # packages/ui/src/2_molecules/index.ts
2 parents ce80f8b + 56b09ea commit 556ffd5

17 files changed

Lines changed: 869 additions & 7 deletions

.changeset/rotten-pugs-enjoy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sovryn/ui": patch
3+
---
4+
5+
SOV-146: add tooltip component

packages/ui/src/1_atoms/Icon/Icon.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { Story } from '@storybook/react';
44
import React, { ComponentProps, useMemo } from 'react';
55

66
import { Icon } from './Icon';
7-
import { IconName } from './Icon.types';
8-
import * as IconNames from './iconNames';
7+
import { IconName, IconNames } from './Icon.types';
98

109
export default {
1110
title: 'Atoms/Icon',

packages/ui/src/1_atoms/Icon/Icon.types.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,33 @@ import type { IconProp } from '@fortawesome/fontawesome-svg-core';
22

33
import { ReactNode } from 'react';
44

5-
import * as IconNames from './iconNames';
5+
export enum IconNames {
6+
ARROW_DOWN_WIDE = 'arrow-down-wide',
7+
ARROW_DOWN = 'arrow-down',
8+
ARROW_RIGHT = 'arrow-right',
9+
ARROW_FORWARD = 'arrow-forward',
10+
DEPOSIT = 'deposit',
11+
EDIT = 'edit',
12+
FAILED_TX = 'failed-tx',
13+
INFO = 'info',
14+
MEATBALLS_MENU = 'meatballs-menu',
15+
NEW_TAB = 'new-tab',
16+
NOTIFICATIONS_ACTIVE = 'notifications-active',
17+
NOTIFICATIONS = 'notifications',
18+
PENDING = 'pending',
19+
SEARCH = 'search',
20+
SETTINGS = 'settings',
21+
SUCCESS_ICON = 'success-icon',
22+
SWAP_ARROW = 'swap-arrow',
23+
TRANSFER = 'transfer',
24+
TREND_ARROW_UP = 'trend-arrow-up',
25+
TREND_ARROW_DOWN = 'trend-arrow-down',
26+
WARNING = 'warning',
27+
WITHDRAW = 'withdraw',
28+
X_MARK = 'x-mark',
29+
COPY = 'copy',
30+
EXIT = 'exit',
31+
}
632

733
export type IconName = typeof IconNames[keyof typeof IconNames];
834

packages/ui/src/1_atoms/Icon/icon.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
44
import React from 'react';
55

66
import { Icon } from './Icon';
7-
import { WARNING } from './iconNames';
7+
import { IconNames } from './Icon.types';
88

99
const customIcon = (
1010
<svg width="20" height="20" data-icon="customIcon" viewBox="0 0 20 20">
@@ -27,7 +27,7 @@ describe('Test for all icon types', () => {
2727
});
2828

2929
test('renders an icon from Sovryn Library', () => {
30-
render(<Icon icon={WARNING} />);
30+
render(<Icon icon={IconNames.WARNING} />);
3131
expect(screen.findAllByRole('svg[data-icon="warning"]')).toBeDefined();
3232
});
3333
});

packages/ui/src/2_molecules/Dropdown/Dropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import React, {
1212
import classNames from 'classnames';
1313

1414
import { Icon } from '../../1_atoms/Icon/Icon';
15-
import { ARROW_DOWN } from '../../1_atoms/Icon/iconNames';
15+
import { IconNames } from '../../1_atoms/Icon/Icon.types';
1616
import { Portal } from '../../1_atoms/Portal/Portal';
1717
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
1818
import { Nullable } from '../../types';
@@ -120,7 +120,7 @@ export const Dropdown = forwardRef<HTMLButtonElement, DropdownProps>(
120120
>
121121
{text}
122122
<Icon
123-
icon={ARROW_DOWN}
123+
icon={IconNames.ARROW_DOWN}
124124
size={10}
125125
className={classNames(styles.icon, {
126126
[styles.isOpen]: isOpen,
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
.tooltip {
2+
@apply bg-gray-10 rounded p-3 text-gray-90 absolute inline-block min-w-5 max-w-72 leading-5 opacity-0;
3+
4+
&.bottom,
5+
&.bottom-start,
6+
&.bottom-end,
7+
&.top,
8+
&.top-start,
9+
&.top-end,
10+
&.left,
11+
&.left-start,
12+
&.left-end,
13+
&.right,
14+
&.right-start,
15+
&.right-end {
16+
@apply opacity-100;
17+
&:after {
18+
@apply content-[''] absolute w-0 h-0 border-solid;
19+
}
20+
}
21+
22+
&.top,
23+
&.top-start,
24+
&.top-end {
25+
&:after {
26+
@apply border-t-[0.656rem] border-b-0 border-t-gray-10 border-b-transparent -bottom-[0.656rem] border-x-[0.516rem] border-x-transparent;
27+
}
28+
}
29+
30+
&.right,
31+
&.right-start,
32+
&.right-end {
33+
&:after {
34+
@apply border-r-[0.656rem] border-l-0 border-r-gray-10 border-l-transparent -left-[0.656rem] border-y-[0.516rem] border-y-transparent;
35+
}
36+
}
37+
38+
&.left,
39+
&.left-start,
40+
&.left-end {
41+
&:after {
42+
@apply border-l-[0.656rem] border-r-0 border-l-gray-10 border-r-transparent -right-[0.656rem] border-y-[0.516rem] border-y-transparent;
43+
}
44+
}
45+
46+
&.bottom,
47+
&.bottom-start,
48+
&.bottom-end {
49+
&:after {
50+
@apply border-b-[0.656rem] border-t-0 border-b-gray-10 border-t-transparent -top-[0.656rem] border-x-[0.516rem] border-x-transparent;
51+
}
52+
}
53+
54+
&.top,
55+
&.bottom {
56+
&:after {
57+
@apply left-0 right-0 m-auto;
58+
}
59+
&-start {
60+
&:after {
61+
@apply left-auto right-4;
62+
}
63+
}
64+
&-end {
65+
&:after {
66+
@apply left-4 right-auto;
67+
}
68+
}
69+
}
70+
71+
&.left,
72+
&.right {
73+
&:after {
74+
@apply top-0 bottom-0 m-auto;
75+
}
76+
&-start {
77+
&:after {
78+
@apply top-auto bottom-4;
79+
}
80+
}
81+
&-end {
82+
&:after {
83+
@apply top-4 bottom-auto;
84+
}
85+
}
86+
}
87+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Story } from '@storybook/react';
2+
3+
import React, { ComponentProps } from 'react';
4+
5+
import { Button, Icon, Link } from '../../1_atoms';
6+
import { Tooltip } from './Tooltip';
7+
import { TooltipPlacement, TooltipTrigger } from './Tooltip.types';
8+
9+
export default {
10+
title: 'Molecule/Tooltip',
11+
component: Tooltip,
12+
};
13+
14+
const Template: Story<ComponentProps<typeof Tooltip>> = args => (
15+
<div className="flex justify-center items-center h-96 w-full">
16+
<Tooltip {...args} />
17+
</div>
18+
);
19+
20+
export const Basic = Template.bind({});
21+
Basic.args = {
22+
content: (
23+
<div className="max-w-52">
24+
Click here to fund your wallet and get started with Sovryn
25+
<Link
26+
className="mt-4 block text-blue-2 hover:no-underline"
27+
text="Read more"
28+
href="#"
29+
/>
30+
</div>
31+
),
32+
children: (
33+
<div>
34+
<Icon icon="info" />
35+
</div>
36+
),
37+
className: '',
38+
tooltipClassName: '',
39+
dataLayoutId: '',
40+
placement: TooltipPlacement.top,
41+
disabled: false,
42+
trigger: TooltipTrigger.hover,
43+
};
44+
45+
const InteractiveTemplate: Story<ComponentProps<typeof Tooltip>> = args => (
46+
<div className="flex justify-center mt-32">
47+
<Tooltip
48+
{...args}
49+
onHide={() => console.log('onHide event called')}
50+
onShow={() => console.log('onShow event called')}
51+
/>
52+
</div>
53+
);
54+
55+
export const Interactive = InteractiveTemplate.bind({});
56+
Interactive.args = {
57+
content: (
58+
<div className="max-w-52">
59+
Click here to fund your wallet and get started with Sovryn
60+
<Link
61+
className="mt-4 block text-blue-2 hover:no-underline"
62+
text="Read more"
63+
href="#"
64+
/>
65+
</div>
66+
),
67+
children: (
68+
<div>
69+
<Button text="Info" />
70+
</div>
71+
),
72+
className: '',
73+
tooltipClassName: '',
74+
dataLayoutId: '',
75+
placement: TooltipPlacement.top,
76+
disabled: false,
77+
trigger: TooltipTrigger.hover,
78+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { act, render, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
4+
import React from 'react';
5+
6+
import { Tooltip } from './Tooltip';
7+
import { TooltipPlacement, TooltipTrigger } from './Tooltip.types';
8+
9+
describe('Tooltip', () => {
10+
beforeEach(() => {
11+
jest.useFakeTimers();
12+
});
13+
14+
afterEach(() => {
15+
jest.useRealTimers();
16+
});
17+
18+
test('should render a tooltip on hover in and hide on hover out', () => {
19+
const { getByRole, queryByText } = render(
20+
<Tooltip children={<button>Text</button>} content={<>Tooltip</>} />,
21+
);
22+
const button = getByRole('button');
23+
userEvent.hover(button);
24+
const tooltip = queryByText('Tooltip');
25+
expect(tooltip).toBeInTheDocument();
26+
userEvent.unhover(button);
27+
act(() => {
28+
jest.runAllTimers();
29+
});
30+
expect(tooltip).not.toBeInTheDocument();
31+
});
32+
33+
it('should render a tooltip on focus in and hide on focus out', async () => {
34+
const { getByRole, queryByText } = render(
35+
<Tooltip
36+
trigger={TooltipTrigger.focus}
37+
children={<button>Text</button>}
38+
content={<>Tooltip</>}
39+
/>,
40+
);
41+
const button = getByRole('button');
42+
await waitFor(() => button.focus());
43+
const tooltip = queryByText('Tooltip');
44+
expect(tooltip).toBeInTheDocument();
45+
await waitFor(() => button.blur());
46+
act(() => {
47+
jest.runAllTimers();
48+
});
49+
expect(tooltip).not.toBeInTheDocument();
50+
});
51+
52+
it('should render a tooltip on click and hide on a second click', () => {
53+
const { getByRole, queryByText } = render(
54+
<Tooltip
55+
trigger={TooltipTrigger.click}
56+
children={<button>Text</button>}
57+
content={<>Tooltip</>}
58+
/>,
59+
);
60+
const button = getByRole('button');
61+
userEvent.click(button);
62+
const tooltip = queryByText('Tooltip');
63+
expect(tooltip).toBeInTheDocument();
64+
userEvent.click(button);
65+
act(() => {
66+
jest.runAllTimers();
67+
});
68+
expect(tooltip).not.toBeInTheDocument();
69+
});
70+
71+
it('should not render a tooltip if disabled is true', () => {
72+
const { getByRole, queryByText } = render(
73+
<Tooltip
74+
disabled
75+
children={<button>Text</button>}
76+
content={<>Tooltip</>}
77+
/>,
78+
);
79+
userEvent.click(getByRole('button'));
80+
expect(queryByText('Tooltip')).not.toBeInTheDocument();
81+
});
82+
83+
it('should render a tooltip with a bottom placement', () => {
84+
const { getByRole, getByText } = render(
85+
<Tooltip
86+
children={<button>Text</button>}
87+
placement={TooltipPlacement.bottom}
88+
content={<>Tooltip</>}
89+
/>,
90+
);
91+
userEvent.hover(getByRole('button'));
92+
const tooltip = getByText('Tooltip');
93+
const classes = tooltip.getAttribute('class');
94+
expect(classes).toContain('bottom');
95+
});
96+
});

0 commit comments

Comments
 (0)