@arbor-education/design-system.components 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/components/button/Button.d.ts +1 -0
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +2 -1
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.d.ts +2 -0
- package/dist/components/formField/inputs/number/NumberInput.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.js +9 -4
- package/dist/components/formField/inputs/number/NumberInput.js.map +1 -1
- package/dist/components/table/BulkActionsDropdown.js +2 -2
- package/dist/components/table/BulkActionsDropdown.js.map +1 -1
- package/dist/components/table/GridApiContext.d.ts +1 -2
- package/dist/components/table/GridApiContext.d.ts.map +1 -1
- package/dist/components/table/GridApiContext.js +1 -1
- package/dist/components/table/GridApiContext.js.map +1 -1
- package/dist/components/table/Table.d.ts +9 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +13 -5
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +5 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +36 -1
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +2 -0
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/pagination/PageSizeSelector.d.ts +7 -0
- package/dist/components/table/pagination/PageSizeSelector.d.ts.map +1 -0
- package/dist/components/table/pagination/PageSizeSelector.js +46 -0
- package/dist/components/table/pagination/PageSizeSelector.js.map +1 -0
- package/dist/components/table/pagination/Pagination.test.d.ts +2 -0
- package/dist/components/table/pagination/Pagination.test.d.ts.map +1 -0
- package/dist/components/table/pagination/Pagination.test.js +616 -0
- package/dist/components/table/pagination/Pagination.test.js.map +1 -0
- package/dist/components/table/pagination/PaginationControls.d.ts +6 -0
- package/dist/components/table/pagination/PaginationControls.d.ts.map +1 -0
- package/dist/components/table/pagination/PaginationControls.js +47 -0
- package/dist/components/table/pagination/PaginationControls.js.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.d.ts +9 -0
- package/dist/components/table/pagination/PaginationPanel.d.ts.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.js +11 -0
- package/dist/components/table/pagination/PaginationPanel.js.map +1 -0
- package/dist/components/table/pagination/RowCountInfo.d.ts +5 -0
- package/dist/components/table/pagination/RowCountInfo.d.ts.map +1 -0
- package/dist/components/table/pagination/RowCountInfo.js +53 -0
- package/dist/components/table/pagination/RowCountInfo.js.map +1 -0
- package/dist/components/tooltip/Tooltip.d.ts +7 -0
- package/dist/components/tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/tooltip/Tooltip.js +11 -0
- package/dist/components/tooltip/Tooltip.js.map +1 -0
- package/dist/components/tooltip/Tooltip.stories.d.ts +10 -0
- package/dist/components/tooltip/Tooltip.stories.d.ts.map +1 -0
- package/dist/components/tooltip/Tooltip.stories.js +24 -0
- package/dist/components/tooltip/Tooltip.stories.js.map +1 -0
- package/dist/components/tooltip/Tooltip.test.d.ts +2 -0
- package/dist/components/tooltip/Tooltip.test.d.ts.map +1 -0
- package/dist/components/tooltip/Tooltip.test.js +23 -0
- package/dist/components/tooltip/Tooltip.test.js.map +1 -0
- package/dist/components/tooltip/TooltipContent.d.ts +8 -0
- package/dist/components/tooltip/TooltipContent.d.ts.map +1 -0
- package/dist/components/tooltip/TooltipContent.js +11 -0
- package/dist/components/tooltip/TooltipContent.js.map +1 -0
- package/dist/components/tooltip/TooltipTrigger.d.ts +3 -0
- package/dist/components/tooltip/TooltipTrigger.d.ts.map +1 -0
- package/dist/components/tooltip/TooltipTrigger.js +8 -0
- package/dist/components/tooltip/TooltipTrigger.js.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.d.ts +11 -0
- package/dist/components/tooltip/TooltipWrapper.d.ts.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.js +8 -0
- package/dist/components/tooltip/TooltipWrapper.js.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts +11 -0
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.stories.js +23 -0
- package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.test.d.ts +2 -0
- package/dist/components/tooltip/TooltipWrapper.test.d.ts.map +1 -0
- package/dist/components/tooltip/TooltipWrapper.test.js +42 -0
- package/dist/components/tooltip/TooltipWrapper.test.js.map +1 -0
- package/dist/index.css +41 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/hooks/useGridApi.d.ts +5 -0
- package/dist/utils/hooks/useGridApi.d.ts.map +1 -0
- package/dist/utils/hooks/useGridApi.js +13 -0
- package/dist/utils/hooks/useGridApi.js.map +1 -0
- package/dist/utils/hooks/useIsMounted.d.ts +2 -0
- package/dist/utils/hooks/useIsMounted.d.ts.map +1 -0
- package/dist/utils/hooks/useIsMounted.js +12 -0
- package/dist/utils/hooks/useIsMounted.js.map +1 -0
- package/package.json +1 -1
- package/release/design-system.components.tgz +0 -0
- package/src/components/button/Button.tsx +3 -0
- package/src/components/button/button.scss +4 -0
- package/src/components/formField/inputs/number/NumberInput.tsx +39 -24
- package/src/components/table/BulkActionsDropdown.tsx +2 -2
- package/src/components/table/GridApiContext.ts +2 -2
- package/src/components/table/Table.stories.tsx +64 -2
- package/src/components/table/Table.test.tsx +2 -0
- package/src/components/table/Table.tsx +14 -4
- package/src/components/table/pagination/PageSizeSelector.tsx +73 -0
- package/src/components/table/pagination/Pagination.test.tsx +846 -0
- package/src/components/table/pagination/PaginationControls.tsx +116 -0
- package/src/components/table/pagination/PaginationPanel.tsx +30 -0
- package/src/components/table/pagination/RowCountInfo.tsx +67 -0
- package/src/components/table/pagination/pagination.scss +26 -0
- package/src/components/tooltip/Tooltip.stories.tsx +35 -0
- package/src/components/tooltip/Tooltip.test.tsx +44 -0
- package/src/components/tooltip/Tooltip.tsx +18 -0
- package/src/components/tooltip/TooltipContent.tsx +40 -0
- package/src/components/tooltip/TooltipTrigger.tsx +11 -0
- package/src/components/tooltip/TooltipWrapper.stories.tsx +24 -0
- package/src/components/tooltip/TooltipWrapper.test.tsx +100 -0
- package/src/components/tooltip/TooltipWrapper.tsx +32 -0
- package/src/components/tooltip/tooltip.scss +15 -0
- package/src/index.scss +2 -0
- package/src/index.ts +2 -0
- package/src/utils/hooks/useGridApi.ts +15 -0
- package/src/utils/hooks/useIsMounted.ts +12 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Button } from 'Components/button/Button';
|
|
2
|
+
import { NumberInput } from 'Components/formField/inputs/number/NumberInput';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useGridApi } from 'Utils/hooks/useGridApi';
|
|
5
|
+
|
|
6
|
+
export type PaginationControlsProps = {
|
|
7
|
+
onPageChange?: (newPage: number) => void;
|
|
8
|
+
totalPages?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const PaginationControls = (props: PaginationControlsProps) => {
|
|
12
|
+
const { onPageChange, totalPages: passedTotalPages } = props;
|
|
13
|
+
const { gridApi, isGridApiReady } = useGridApi();
|
|
14
|
+
const [inputValue, setInputValue] = useState(1);
|
|
15
|
+
|
|
16
|
+
// default to 1, because no content is still one page
|
|
17
|
+
const [totalPages, setTotalPages] = useState(() => passedTotalPages ?? gridApi?.paginationGetTotalPages() ?? 1);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isGridApiReady && !passedTotalPages) {
|
|
21
|
+
gridApi?.addEventListener('paginationChanged', () => {
|
|
22
|
+
const newPageCount = gridApi.paginationGetTotalPages();
|
|
23
|
+
if (newPageCount) {
|
|
24
|
+
setTotalPages(newPageCount);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}, [isGridApiReady]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (passedTotalPages) {
|
|
32
|
+
setTotalPages(passedTotalPages);
|
|
33
|
+
}
|
|
34
|
+
}, [passedTotalPages]);
|
|
35
|
+
|
|
36
|
+
const goToPage = (page: number) => {
|
|
37
|
+
if (page === inputValue) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const pageZeroIndexed = page - 1;
|
|
41
|
+
if (onPageChange) {
|
|
42
|
+
onPageChange(pageZeroIndexed);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
gridApi?.paginationGoToPage(pageZeroIndexed);
|
|
46
|
+
}
|
|
47
|
+
setInputValue(page);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="ds-table__pagination-controls">
|
|
52
|
+
<Button
|
|
53
|
+
variant="secondary"
|
|
54
|
+
borderless
|
|
55
|
+
size="S"
|
|
56
|
+
color="black"
|
|
57
|
+
iconLeftName="chevrons-left"
|
|
58
|
+
iconLeftScreenReaderText="Go to first page"
|
|
59
|
+
onClick={() => goToPage(1)}
|
|
60
|
+
disabled={inputValue === 1}
|
|
61
|
+
/>
|
|
62
|
+
<Button
|
|
63
|
+
variant="secondary"
|
|
64
|
+
borderless
|
|
65
|
+
size="S"
|
|
66
|
+
color="black"
|
|
67
|
+
iconLeftName="chevron-left"
|
|
68
|
+
onClick={() => goToPage(inputValue - 1)}
|
|
69
|
+
iconLeftScreenReaderText="Go to previous page"
|
|
70
|
+
disabled={inputValue === 1}
|
|
71
|
+
/>
|
|
72
|
+
<span>
|
|
73
|
+
Page
|
|
74
|
+
{' '}
|
|
75
|
+
<NumberInput
|
|
76
|
+
value={inputValue}
|
|
77
|
+
onChange={(e) => {
|
|
78
|
+
const newPage = Number(e.currentTarget.value);
|
|
79
|
+
if (newPage > 0) {
|
|
80
|
+
goToPage(newPage);
|
|
81
|
+
}
|
|
82
|
+
}}
|
|
83
|
+
max={totalPages}
|
|
84
|
+
min={1}
|
|
85
|
+
disableSpinners
|
|
86
|
+
containerClassName="ds-table__pagination-controls__number-input-container"
|
|
87
|
+
className="ds-table__pagination-controls__number-input"
|
|
88
|
+
aria-label="Current page"
|
|
89
|
+
aria-current="page"
|
|
90
|
+
/>
|
|
91
|
+
{' '}
|
|
92
|
+
of
|
|
93
|
+
{' '}
|
|
94
|
+
{totalPages}
|
|
95
|
+
</span>
|
|
96
|
+
<Button
|
|
97
|
+
variant="secondary"
|
|
98
|
+
borderless
|
|
99
|
+
color="black"
|
|
100
|
+
iconLeftName="chevron-right"
|
|
101
|
+
onClick={() => goToPage(inputValue + 1)}
|
|
102
|
+
disabled={inputValue === totalPages}
|
|
103
|
+
iconLeftScreenReaderText="Go to next page"
|
|
104
|
+
/>
|
|
105
|
+
<Button
|
|
106
|
+
variant="secondary"
|
|
107
|
+
borderless
|
|
108
|
+
color="black"
|
|
109
|
+
iconLeftName="chevrons-right"
|
|
110
|
+
onClick={() => goToPage(totalPages)}
|
|
111
|
+
disabled={inputValue === totalPages}
|
|
112
|
+
iconLeftScreenReaderText="Go to last page"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PageSizeSelector, type PageSizeSelectorProps } from './PageSizeSelector';
|
|
2
|
+
import { RowCountInfo, type RowCountInfoProps } from './RowCountInfo';
|
|
3
|
+
import { PaginationControls, type PaginationControlsProps } from './PaginationControls';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
6
|
+
type PaginationPanelProps = {
|
|
7
|
+
className?: string;
|
|
8
|
+
} & RowCountInfoProps & PaginationControlsProps & PageSizeSelectorProps;
|
|
9
|
+
|
|
10
|
+
export const PaginationPanel = (props: PaginationPanelProps) => {
|
|
11
|
+
const {
|
|
12
|
+
className = '',
|
|
13
|
+
totalRows,
|
|
14
|
+
totalPages,
|
|
15
|
+
onPageChange,
|
|
16
|
+
availableSizes,
|
|
17
|
+
initialPageSize,
|
|
18
|
+
onPageSizeChange,
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
const classes = classNames('ds-table__pagination-panel', className);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<nav className={classes} aria-label="Pagination">
|
|
25
|
+
<RowCountInfo totalRows={totalRows} />
|
|
26
|
+
<PaginationControls totalPages={totalPages} onPageChange={onPageChange} />
|
|
27
|
+
<PageSizeSelector availableSizes={availableSizes} initialPageSize={initialPageSize} onPageSizeChange={onPageSizeChange} />
|
|
28
|
+
</nav>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useGridApi } from 'Utils/hooks/useGridApi';
|
|
3
|
+
|
|
4
|
+
export type RowCountInfoProps = {
|
|
5
|
+
totalRows?: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const RowCountInfo = (props: RowCountInfoProps) => {
|
|
9
|
+
const { totalRows: passedTotalRows } = props;
|
|
10
|
+
|
|
11
|
+
const { gridApi, isGridApiReady } = useGridApi();
|
|
12
|
+
|
|
13
|
+
const getTrueDisplayedRowCount = () => {
|
|
14
|
+
const displayedRowCount = gridApi?.getDisplayedRowCount() || 0;
|
|
15
|
+
const pageSize = gridApi?.paginationGetPageSize() || 0;
|
|
16
|
+
const result = Math.min(displayedRowCount, pageSize);
|
|
17
|
+
const currentPage = gridApi?.paginationGetCurrentPage() || 0;
|
|
18
|
+
const totalPages = gridApi?.paginationGetTotalPages();
|
|
19
|
+
|
|
20
|
+
if (totalPages === 1) {
|
|
21
|
+
return displayedRowCount;
|
|
22
|
+
}
|
|
23
|
+
if (gridApi && displayedRowCount && pageSize && currentPage + 1 === totalPages) {
|
|
24
|
+
return displayedRowCount % pageSize || pageSize;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
const [displayedRowCount, setDisplayedRowCount] = useState(() => getTrueDisplayedRowCount());
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (isGridApiReady) {
|
|
32
|
+
const result = getTrueDisplayedRowCount();
|
|
33
|
+
setDisplayedRowCount(result);
|
|
34
|
+
}
|
|
35
|
+
}, [isGridApiReady]);
|
|
36
|
+
|
|
37
|
+
if (!isGridApiReady) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// TODO when writing tests, see how this behaves with or without pagination
|
|
42
|
+
let totalRows: number | undefined;
|
|
43
|
+
if (typeof passedTotalRows !== 'undefined') {
|
|
44
|
+
totalRows = passedTotalRows;
|
|
45
|
+
}
|
|
46
|
+
else if (gridApi && gridApi.paginationGetTotalPages() > 1) {
|
|
47
|
+
totalRows = gridApi.paginationGetRowCount();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
gridApi?.addEventListener('paginationChanged', () => {
|
|
51
|
+
setDisplayedRowCount(getTrueDisplayedRowCount());
|
|
52
|
+
});
|
|
53
|
+
gridApi?.addEventListener('filterChanged', () => {
|
|
54
|
+
setDisplayedRowCount(getTrueDisplayedRowCount());
|
|
55
|
+
});
|
|
56
|
+
gridApi?.addEventListener('rowDataUpdated', () => {
|
|
57
|
+
setDisplayedRowCount(getTrueDisplayedRowCount());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const message = totalRows && totalRows !== displayedRowCount
|
|
61
|
+
? `Showing ${displayedRowCount} of ${totalRows} results`
|
|
62
|
+
: `Showing ${displayedRowCount} results`;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<span className="ds-table__row-count-info">{message}</span>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.ds-table__pagination-panel {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
align-items: center;
|
|
5
|
+
width: 100%;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ds-table__pagination-controls {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 3px;
|
|
14
|
+
|
|
15
|
+
&__number-input-container {
|
|
16
|
+
display: inline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__number-input {
|
|
20
|
+
width: 39px;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ds-table__pagination-controls, .ds-table__row-count-info {
|
|
25
|
+
font-size: var(--font-size-2-13);
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react-vite';
|
|
2
|
+
import { Tooltip } from './Tooltip';
|
|
3
|
+
import { Button } from 'Components/button/Button';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Tooltip> = {
|
|
6
|
+
title: 'Components/Tooltip/DestructuredTooltip',
|
|
7
|
+
component: Tooltip,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'A composable set of components (Tooltip, Tooltip.Trigger, Tooltip.Content) that you can use to create a tooltip.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Default = {
|
|
20
|
+
args: {
|
|
21
|
+
children: [
|
|
22
|
+
<>
|
|
23
|
+
<Tooltip.Trigger>
|
|
24
|
+
<Button type="primary">Click me!</Button>
|
|
25
|
+
</Tooltip.Trigger>
|
|
26
|
+
|
|
27
|
+
<Tooltip.Content>
|
|
28
|
+
<div>Content</div>
|
|
29
|
+
</Tooltip.Content>
|
|
30
|
+
</>,
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default meta;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expect, test, describe } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom/vitest';
|
|
4
|
+
import { Tooltip } from './Tooltip';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
describe('Tooltip component', () => {
|
|
8
|
+
test('renders tooltip trigger', () => {
|
|
9
|
+
render(
|
|
10
|
+
<Tooltip>
|
|
11
|
+
<Tooltip.Trigger>
|
|
12
|
+
<button>Hover me</button>
|
|
13
|
+
</Tooltip.Trigger>
|
|
14
|
+
<Tooltip.Content>
|
|
15
|
+
Tooltip content
|
|
16
|
+
</Tooltip.Content>
|
|
17
|
+
</Tooltip>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(screen.getByText('Hover me')).toBeInTheDocument();
|
|
21
|
+
expect(screen.getByRole('button')).toHaveClass('ds-tooltip__trigger');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('shows tooltip on hover', async () => {
|
|
25
|
+
render(
|
|
26
|
+
<Tooltip delayDuration={0}>
|
|
27
|
+
<Tooltip.Trigger>
|
|
28
|
+
<button>Hover me</button>
|
|
29
|
+
</Tooltip.Trigger>
|
|
30
|
+
<Tooltip.Content>
|
|
31
|
+
Tooltip content
|
|
32
|
+
</Tooltip.Content>
|
|
33
|
+
</Tooltip>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await userEvent.hover(screen.getByText('Hover me'));
|
|
37
|
+
await expect(screen.getAllByText('Tooltip content')[0]).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('exposes Content and Trigger as static properties', () => {
|
|
41
|
+
expect(Tooltip.Content).toBeDefined();
|
|
42
|
+
expect(Tooltip.Trigger).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
2
|
+
import { TooltipContent } from './TooltipContent';
|
|
3
|
+
import { TooltipTrigger } from './TooltipTrigger';
|
|
4
|
+
|
|
5
|
+
export const Tooltip = (props: TooltipPrimitive.TooltipProps) => {
|
|
6
|
+
const { children, delayDuration = 400, ...rest } = props;
|
|
7
|
+
return (
|
|
8
|
+
<TooltipPrimitive.Provider>
|
|
9
|
+
<TooltipPrimitive.Root delayDuration={delayDuration} {...rest}>
|
|
10
|
+
{children}
|
|
11
|
+
</TooltipPrimitive.Root>
|
|
12
|
+
</TooltipPrimitive.Provider>
|
|
13
|
+
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
Tooltip.Content = TooltipContent;
|
|
18
|
+
Tooltip.Trigger = TooltipTrigger;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import { PopupParentContext } from 'Utils/PopupParentContext';
|
|
5
|
+
|
|
6
|
+
export type TooltipContentProps = {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
portalProps?: TooltipPrimitive.TooltipPortalProps;
|
|
9
|
+
shouldShowArrow?: boolean;
|
|
10
|
+
} & TooltipPrimitive.TooltipContentProps;
|
|
11
|
+
|
|
12
|
+
export const TooltipContent = (props: TooltipContentProps) => {
|
|
13
|
+
const {
|
|
14
|
+
children,
|
|
15
|
+
portalProps,
|
|
16
|
+
shouldShowArrow = true,
|
|
17
|
+
className,
|
|
18
|
+
...rest
|
|
19
|
+
} = props;
|
|
20
|
+
const popupParentRef = useContext(PopupParentContext);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<TooltipPrimitive.Portal
|
|
24
|
+
container={popupParentRef.current}
|
|
25
|
+
{...portalProps}
|
|
26
|
+
>
|
|
27
|
+
<TooltipPrimitive.Content
|
|
28
|
+
{...rest}
|
|
29
|
+
className={classNames('ds-tooltip__content', className)}
|
|
30
|
+
>
|
|
31
|
+
{shouldShowArrow && (
|
|
32
|
+
<TooltipPrimitive.Arrow
|
|
33
|
+
className="ds-tooltip__content-arrow"
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
{children}
|
|
37
|
+
</TooltipPrimitive.Content>
|
|
38
|
+
</TooltipPrimitive.Portal>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
3
|
+
|
|
4
|
+
export const TooltipTrigger = (props: TooltipPrimitive.TooltipTriggerProps) => {
|
|
5
|
+
const { children, className = '', ...rest } = props;
|
|
6
|
+
return (
|
|
7
|
+
<TooltipPrimitive.Trigger asChild className={classNames('ds-tooltip__trigger', className)} {...rest}>
|
|
8
|
+
{children}
|
|
9
|
+
</TooltipPrimitive.Trigger>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react-vite';
|
|
2
|
+
import { Button } from 'Components/button/Button';
|
|
3
|
+
import { TooltipWrapper as TooltipWrapperComponent } from './TooltipWrapper';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof TooltipWrapperComponent> = {
|
|
6
|
+
title: 'Components/Tooltip/TooltipWrapper',
|
|
7
|
+
component: TooltipWrapperComponent,
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: 'Instead of manually creating a tooltip (using Tooltip, Tooltip.TooltipTrigger, Tooltip.TooltipContent), you can use this wrapper to easily create a tooltip.',
|
|
12
|
+
},
|
|
13
|
+
} },
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Default = {
|
|
18
|
+
args: {
|
|
19
|
+
tooltipContent: 'Tooltip content',
|
|
20
|
+
children: <Button type="primary">Click me!</Button>,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { expect, test, describe } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom/vitest';
|
|
4
|
+
import { TooltipWrapper } from './TooltipWrapper';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
describe('TooltipWrapper component', () => {
|
|
8
|
+
test('renders children with tooltip wrapper', () => {
|
|
9
|
+
render(
|
|
10
|
+
<TooltipWrapper tooltipContent="Helpful tooltip text">
|
|
11
|
+
<button>Click me</button>
|
|
12
|
+
</TooltipWrapper>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByRole('button')).toHaveClass('ds-tooltip__trigger');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('accepts string tooltip content', () => {
|
|
20
|
+
render(
|
|
21
|
+
<TooltipWrapper tooltipContent="Tooltip text">
|
|
22
|
+
<button>Button</button>
|
|
23
|
+
</TooltipWrapper>,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByText('Button')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('accepts JSX tooltip content', () => {
|
|
30
|
+
render(
|
|
31
|
+
<TooltipWrapper
|
|
32
|
+
tooltipContent={(
|
|
33
|
+
<div>
|
|
34
|
+
<strong>Bold text</strong>
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<button>Button</button>
|
|
39
|
+
</TooltipWrapper>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(screen.getByText('Button')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('passes triggerProps to trigger', () => {
|
|
46
|
+
render(
|
|
47
|
+
<TooltipWrapper
|
|
48
|
+
tooltipContent="Tooltip text"
|
|
49
|
+
triggerProps={{ className: 'foobar' }}
|
|
50
|
+
>
|
|
51
|
+
<button>Tooltip trigger</button>
|
|
52
|
+
</TooltipWrapper>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText('Tooltip trigger')).toBeInTheDocument();
|
|
56
|
+
expect(screen.getByText('Tooltip trigger')).toHaveClass('foobar');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('shows tooltip on hover', async () => {
|
|
60
|
+
render(
|
|
61
|
+
<TooltipWrapper
|
|
62
|
+
tooltipContent="Tooltip text"
|
|
63
|
+
delayDuration={0}
|
|
64
|
+
>
|
|
65
|
+
<button>Tooltip trigger</button>
|
|
66
|
+
</TooltipWrapper>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await userEvent.hover(screen.getByText('Tooltip trigger'));
|
|
70
|
+
await expect(screen.getAllByText('Tooltip text')[0]).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('passes contentProps to content', async () => {
|
|
74
|
+
render(
|
|
75
|
+
<TooltipWrapper
|
|
76
|
+
tooltipContent="Tooltip text"
|
|
77
|
+
contentProps={{ className: 'foobar', shouldShowArrow: false }}
|
|
78
|
+
delayDuration={0}
|
|
79
|
+
>
|
|
80
|
+
<button>Tooltip trigger</button>
|
|
81
|
+
</TooltipWrapper>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await userEvent.hover(screen.getByText('Tooltip trigger'));
|
|
85
|
+
await expect(screen.getAllByText('Tooltip text')[0]).toBeInTheDocument();
|
|
86
|
+
expect(screen.getAllByText('Tooltip text')[0]).toHaveClass('foobar');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('renders with complex children', () => {
|
|
90
|
+
render(
|
|
91
|
+
<TooltipWrapper tooltipContent="Info">
|
|
92
|
+
<div className="wrapper">
|
|
93
|
+
<span>Content</span>
|
|
94
|
+
</div>
|
|
95
|
+
</TooltipWrapper>,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(screen.getByText('Content')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from 'radix-ui';
|
|
2
|
+
import { Tooltip } from './Tooltip';
|
|
3
|
+
import type { TooltipContentProps } from './TooltipContent';
|
|
4
|
+
|
|
5
|
+
export type TooltipWrapperProps = {
|
|
6
|
+
children: TooltipPrimitive.TooltipTriggerProps['children'];
|
|
7
|
+
tooltipContent: TooltipPrimitive.TooltipContentProps['children'];
|
|
8
|
+
triggerProps?: TooltipPrimitive.TooltipTriggerProps;
|
|
9
|
+
contentProps?: Omit<TooltipContentProps, 'children'>;
|
|
10
|
+
delayDuration?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TooltipWrapper = (props: TooltipWrapperProps) => {
|
|
14
|
+
const {
|
|
15
|
+
children,
|
|
16
|
+
tooltipContent,
|
|
17
|
+
triggerProps,
|
|
18
|
+
contentProps,
|
|
19
|
+
delayDuration = 400,
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Tooltip delayDuration={delayDuration}>
|
|
24
|
+
<Tooltip.Trigger {...triggerProps}>
|
|
25
|
+
{children}
|
|
26
|
+
</Tooltip.Trigger>
|
|
27
|
+
<Tooltip.Content {...contentProps}>
|
|
28
|
+
{tooltipContent}
|
|
29
|
+
</Tooltip.Content>
|
|
30
|
+
</Tooltip>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.ds-tooltip {
|
|
2
|
+
&__content {
|
|
3
|
+
color: var(--tooltip-color-text);
|
|
4
|
+
background: var(--tooltip-color-background);
|
|
5
|
+
padding: var(--tooltip-spacing-vertical) var(--tooltip-spacing-horizontal);
|
|
6
|
+
border-radius: var(--border-radius-small);
|
|
7
|
+
font-size: var(--type-body-p-size);
|
|
8
|
+
font-family: var(--type-body-p-family);
|
|
9
|
+
font-weight: var(--type-body-p-weight);
|
|
10
|
+
|
|
11
|
+
&-arrow {
|
|
12
|
+
fill: var(--tooltip-color-background);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/index.scss
CHANGED
|
@@ -18,4 +18,6 @@
|
|
|
18
18
|
@use "./components/slideoverManager/slideoverManager.scss";
|
|
19
19
|
@use "./components/slideover/slideover.scss";
|
|
20
20
|
@use "./components/table/table.scss";
|
|
21
|
+
@use "./components/table/pagination/pagination.scss";
|
|
22
|
+
@use "./components/tooltip/tooltip.scss";
|
|
21
23
|
@import 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap';
|
package/src/index.ts
CHANGED
|
@@ -16,3 +16,5 @@ export { SlideoverUtils } from 'Utils/SlideoverUtils';
|
|
|
16
16
|
export { RadioButtonInput } from 'Components/formField/inputs/radio/RadioButtonInput';
|
|
17
17
|
export { Table } from 'Components/table/Table';
|
|
18
18
|
export { GridApiContext } from 'Components/table/GridApiContext';
|
|
19
|
+
export { Tooltip } from 'Components/tooltip/Tooltip';
|
|
20
|
+
export { TooltipWrapper } from 'Components/tooltip/TooltipWrapper';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GridApiContext } from 'Components/table/GridApiContext';
|
|
2
|
+
import { useContext, useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export const useGridApi = () => {
|
|
5
|
+
const gridApi = useContext(GridApiContext);
|
|
6
|
+
const [isGridApiReady, setIsGridApiReady] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (gridApi) {
|
|
10
|
+
setIsGridApiReady(true);
|
|
11
|
+
}
|
|
12
|
+
}, [gridApi]);
|
|
13
|
+
|
|
14
|
+
return { gridApi, isGridApiReady };
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { useComponentDidMount } from './useComponentDidMount';
|
|
3
|
+
|
|
4
|
+
export const useIsMounted = () => {
|
|
5
|
+
const isMounted = useRef(true);
|
|
6
|
+
useComponentDidMount(() => {
|
|
7
|
+
return () => {
|
|
8
|
+
isMounted.current = false;
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
return isMounted;
|
|
12
|
+
};
|