@indico-data/design-system 2.47.2 → 2.48.0
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/lib/components/index.d.ts +1 -0
- package/lib/components/pagination/Pagination.d.ts +2 -0
- package/lib/components/pagination/Pagination.stories.d.ts +6 -0
- package/lib/components/pagination/__tests__/Pagination.test.d.ts +1 -0
- package/lib/components/pagination/index.d.ts +1 -0
- package/lib/components/pagination/types.d.ts +6 -0
- package/lib/components/table/__tests__/Table.test.d.ts +1 -0
- package/lib/components/table/components/TablePagination.d.ts +9 -0
- package/lib/components/table/components/__tests__/TablePagination.test.d.ts +1 -0
- package/lib/components/table/sampleData.d.ts +2 -0
- package/lib/components/table/types.d.ts +5 -4
- package/lib/index.css +64 -11
- package/lib/index.d.ts +6 -6
- package/lib/index.esm.css +64 -11
- package/lib/index.esm.js +69 -14
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +68 -13
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/pagination/Pagination.mdx +31 -0
- package/src/components/pagination/Pagination.stories.tsx +80 -0
- package/src/components/pagination/Pagination.tsx +117 -0
- package/src/components/pagination/__tests__/Pagination.test.tsx +91 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/styles/Pagination.scss +22 -0
- package/src/components/pagination/types.ts +6 -0
- package/src/components/pill/Pill.stories.tsx +5 -0
- package/src/components/pill/styles/Pill.scss +9 -0
- package/src/components/table/Table.mdx +2 -0
- package/src/components/table/Table.stories.tsx +20 -28
- package/src/components/table/Table.tsx +9 -1
- package/src/components/table/__tests__/Table.test.tsx +10 -0
- package/src/components/table/components/TablePagination.tsx +44 -0
- package/src/components/table/components/__tests__/TablePagination.test.tsx +17 -0
- package/src/components/table/sampleData.ts +110 -0
- package/src/components/table/styles/Table.scss +40 -9
- package/src/components/table/styles/_variables.scss +1 -0
- package/src/components/table/types.ts +6 -6
- package/src/setup/setupIcons.ts +4 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/variables/themes/dark.scss +6 -3
- package/src/types.ts +8 -1
package/lib/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type PermafrostComponent = {
|
|
|
6
6
|
};
|
|
7
7
|
import { IconSizes, IconName } from './components/icons/types';
|
|
8
8
|
export type { IconSizes, IconName };
|
|
9
|
-
export type SemanticColor = 'primary' | 'secondary' | 'warning' | 'error' | 'success' | 'info';
|
|
9
|
+
export type SemanticColor = 'primary' | 'secondary' | 'warning' | 'error' | 'success' | 'info' | 'pending';
|
|
10
10
|
import { SelectOption } from './components/forms/select/types';
|
|
11
11
|
export type { SelectOption };
|
|
12
12
|
import { TableProps, TableColumn, Direction, Alignment } from './components/table/types';
|
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls, Story } from '@storybook/blocks';
|
|
2
|
+
import * as Pagination from './Pagination.stories';
|
|
3
|
+
import { Container, Row, Col } from '../grid';
|
|
4
|
+
import { fas } from '@fortawesome/free-solid-svg-icons';
|
|
5
|
+
import { registerFontAwesomeIcons } from '@/setup/setupIcons';
|
|
6
|
+
import { indiconDefinitions } from '@/components/icons/indicons';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<Meta title="Layout/Pagination" name="Pagination" of={Pagination} />
|
|
10
|
+
|
|
11
|
+
# Pagination
|
|
12
|
+
|
|
13
|
+
<Canvas of={Pagination.Default} />
|
|
14
|
+
|
|
15
|
+
### The following props are available for the Pagination component:
|
|
16
|
+
|
|
17
|
+
<Controls of={Pagination.Default} />
|
|
18
|
+
|
|
19
|
+
### Usage
|
|
20
|
+
|
|
21
|
+
The pagination component is used to navigate through a list of items. It is already baked into the Table component. To manage the pagination, there is an `onChange` callback that returns the next page, previous page, or the page entered in the input field. This will then be used by your implementation to update the page.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
|
|
25
|
+
<Pagination totalPages={10} currentPage={1} onChange={(page) => console.log(page)} />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
|
|
31
|
+
The input field allows the user to enter an invalid number but it will warn them by highlighting the input in red. When the user clicks out, it will then revert to its original value.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Pagination } from './Pagination';
|
|
3
|
+
import { Col, Container, Row } from '../grid';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Layout/Pagination',
|
|
8
|
+
component: Pagination,
|
|
9
|
+
argTypes: {
|
|
10
|
+
className: {
|
|
11
|
+
control: false,
|
|
12
|
+
description: 'The css class name for the pagination component',
|
|
13
|
+
table: {
|
|
14
|
+
category: 'Props',
|
|
15
|
+
type: {
|
|
16
|
+
summary: 'css class',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
totalPages: {
|
|
21
|
+
control: 'number',
|
|
22
|
+
description: 'The total number of pages to be displayed',
|
|
23
|
+
table: {
|
|
24
|
+
category: 'Props',
|
|
25
|
+
type: {
|
|
26
|
+
summary: 'number',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
currentPage: {
|
|
31
|
+
control: 'number',
|
|
32
|
+
description: 'The current page displayed in the input field.',
|
|
33
|
+
table: {
|
|
34
|
+
category: 'Props',
|
|
35
|
+
type: {
|
|
36
|
+
summary: 'number',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
onChange: {
|
|
41
|
+
action: 'change',
|
|
42
|
+
description: 'The callback function that is called when the page changes.',
|
|
43
|
+
table: {
|
|
44
|
+
category: 'Callbacks',
|
|
45
|
+
type: {
|
|
46
|
+
summary: '(page: number) => void',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default meta;
|
|
54
|
+
|
|
55
|
+
type Story = StoryObj<typeof Pagination>;
|
|
56
|
+
|
|
57
|
+
export const Default: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
totalPages: 10,
|
|
60
|
+
currentPage: 1,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
render: (args) => {
|
|
64
|
+
const [currentPage, setCurrentPage] = useState(args.currentPage);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setCurrentPage(args.currentPage);
|
|
68
|
+
}, [args.currentPage]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Container>
|
|
72
|
+
<Row>
|
|
73
|
+
<Col sm={4}>
|
|
74
|
+
<Pagination {...args} currentPage={currentPage} onChange={setCurrentPage} />
|
|
75
|
+
</Col>
|
|
76
|
+
</Row>
|
|
77
|
+
</Container>
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { PaginationProps } from './types';
|
|
4
|
+
import { Container, Row, Col } from '../grid';
|
|
5
|
+
import { Input } from '../forms/input';
|
|
6
|
+
import { Button } from '../button';
|
|
7
|
+
|
|
8
|
+
export const Pagination = ({
|
|
9
|
+
totalPages,
|
|
10
|
+
currentPage = 1,
|
|
11
|
+
onChange,
|
|
12
|
+
className,
|
|
13
|
+
...rest
|
|
14
|
+
}: PaginationProps) => {
|
|
15
|
+
const [inputValue, setInputValue] = useState(currentPage.toString());
|
|
16
|
+
const totalPagesText = `of ${totalPages}`;
|
|
17
|
+
const classes = classNames('pagination', className);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setInputValue(currentPage.toString());
|
|
21
|
+
}, [currentPage]);
|
|
22
|
+
|
|
23
|
+
const handleNextPage = () => {
|
|
24
|
+
if (currentPage < totalPages) {
|
|
25
|
+
onChange?.(currentPage + 1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handlePreviousPage = () => {
|
|
30
|
+
if (currentPage > 1) {
|
|
31
|
+
onChange?.(currentPage - 1);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const validateAndUpdatePage = (value: string) => {
|
|
36
|
+
// If empty or invalid, reset to current page
|
|
37
|
+
if (!value) {
|
|
38
|
+
setInputValue(currentPage.toString());
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const page = Number(value);
|
|
43
|
+
if (!isNaN(page) && page > 0 && page <= totalPages) {
|
|
44
|
+
onChange?.(page);
|
|
45
|
+
} else {
|
|
46
|
+
setInputValue(currentPage.toString());
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isNextButtonDisabled = currentPage === totalPages;
|
|
51
|
+
const isPreviousButtonDisabled = currentPage === 1;
|
|
52
|
+
|
|
53
|
+
const hasError = Number(inputValue) > totalPages || Number(inputValue) < 1;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={classes} {...rest}>
|
|
57
|
+
<Container>
|
|
58
|
+
<Row gutterWidth={12} align="center">
|
|
59
|
+
<Col xs="content">
|
|
60
|
+
<div className="pagination__previous">
|
|
61
|
+
<Button
|
|
62
|
+
data-testid="pagination-previous-button"
|
|
63
|
+
ariaLabel="Previous Page"
|
|
64
|
+
variant="link"
|
|
65
|
+
onClick={handlePreviousPage}
|
|
66
|
+
iconLeft="chevron-left"
|
|
67
|
+
isDisabled={isPreviousButtonDisabled}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</Col>
|
|
71
|
+
<Col xs="content">
|
|
72
|
+
<div className="pagination__current-page">
|
|
73
|
+
<Input
|
|
74
|
+
data-testid="pagination-current-page-input"
|
|
75
|
+
className={classNames('pagination__current-page-input', {
|
|
76
|
+
'has-error': hasError,
|
|
77
|
+
})}
|
|
78
|
+
value={inputValue}
|
|
79
|
+
name="currentPage"
|
|
80
|
+
label="Current Page"
|
|
81
|
+
hasHiddenLabel
|
|
82
|
+
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
83
|
+
if (e.key === 'Enter') {
|
|
84
|
+
validateAndUpdatePage(e.currentTarget.value);
|
|
85
|
+
}
|
|
86
|
+
}}
|
|
87
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
88
|
+
const value = e.currentTarget.value;
|
|
89
|
+
// Allow empty value or numbers
|
|
90
|
+
if (value === '' || /^\d*$/.test(value)) {
|
|
91
|
+
setInputValue(value);
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
onBlur={(e) => validateAndUpdatePage(e.currentTarget.value)}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</Col>
|
|
98
|
+
<Col xs="content">
|
|
99
|
+
<p className="pagination__page-total">{totalPagesText}</p>
|
|
100
|
+
</Col>
|
|
101
|
+
<Col xs="content">
|
|
102
|
+
<div className="pagination__next">
|
|
103
|
+
<Button
|
|
104
|
+
data-testid="pagination-next-button"
|
|
105
|
+
ariaLabel="Next Page"
|
|
106
|
+
variant="link"
|
|
107
|
+
onClick={handleNextPage}
|
|
108
|
+
iconLeft="chevron-right"
|
|
109
|
+
isDisabled={isNextButtonDisabled}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</Col>
|
|
113
|
+
</Row>
|
|
114
|
+
</Container>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { Pagination } from '../Pagination';
|
|
3
|
+
|
|
4
|
+
describe('Pagination', () => {
|
|
5
|
+
it('fires the onChange callback when the user clicks the next button', () => {
|
|
6
|
+
const onChange = jest.fn();
|
|
7
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={onChange} />);
|
|
8
|
+
fireEvent.click(screen.getByTestId('pagination-next-button'));
|
|
9
|
+
expect(onChange).toHaveBeenCalledWith(2);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('fires the onChange callback when the user clicks the previous button', () => {
|
|
13
|
+
const onChange = jest.fn();
|
|
14
|
+
render(<Pagination totalPages={10} currentPage={2} onChange={onChange} />);
|
|
15
|
+
fireEvent.click(screen.getByTestId('pagination-previous-button'));
|
|
16
|
+
expect(onChange).toHaveBeenCalledWith(1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not fire the onchange callback when the user clicks next and they are already on the last page', () => {
|
|
20
|
+
const onChange = jest.fn();
|
|
21
|
+
render(<Pagination totalPages={10} currentPage={10} onChange={onChange} />);
|
|
22
|
+
fireEvent.click(screen.getByTestId('pagination-next-button'));
|
|
23
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('does not fire the onchange callback when the user clicks previous and they are already on the first page', () => {
|
|
27
|
+
const onChange = jest.fn();
|
|
28
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={onChange} />);
|
|
29
|
+
fireEvent.click(screen.getByTestId('pagination-previous-button'));
|
|
30
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('fires the onchange callback when the user enters a value in the input field', () => {
|
|
34
|
+
const onChange = jest.fn();
|
|
35
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={onChange} />);
|
|
36
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
37
|
+
target: { value: '2' },
|
|
38
|
+
});
|
|
39
|
+
fireEvent.keyDown(screen.getByTestId('pagination-current-page-input'), {
|
|
40
|
+
key: 'Enter',
|
|
41
|
+
code: 'Enter',
|
|
42
|
+
});
|
|
43
|
+
expect(onChange).toHaveBeenCalledWith(2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not fire the onChange callback when the user enters an invalid value in the input field', () => {
|
|
47
|
+
const onChange = jest.fn();
|
|
48
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={onChange} />);
|
|
49
|
+
|
|
50
|
+
// Test non-numeric input
|
|
51
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
52
|
+
target: { value: 'abc' },
|
|
53
|
+
});
|
|
54
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
55
|
+
|
|
56
|
+
// Test empty input
|
|
57
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
58
|
+
target: { value: '' },
|
|
59
|
+
});
|
|
60
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
61
|
+
|
|
62
|
+
// Test out of range input
|
|
63
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
64
|
+
target: { value: '0' },
|
|
65
|
+
});
|
|
66
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
67
|
+
|
|
68
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
69
|
+
target: { value: '11' },
|
|
70
|
+
});
|
|
71
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('disables the next button when the user is on the last page', () => {
|
|
75
|
+
render(<Pagination totalPages={10} currentPage={10} onChange={() => {}} />);
|
|
76
|
+
expect(screen.getByTestId('pagination-next-button')).toBeDisabled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('disables the previous button when the user is on the first page', () => {
|
|
80
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={() => {}} />);
|
|
81
|
+
expect(screen.getByTestId('pagination-previous-button')).toBeDisabled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('adds the has-error class to the input field when the user enters an invalid value', () => {
|
|
85
|
+
render(<Pagination totalPages={10} currentPage={1} onChange={() => {}} />);
|
|
86
|
+
fireEvent.change(screen.getByTestId('pagination-current-page-input'), {
|
|
87
|
+
target: { value: '11' },
|
|
88
|
+
});
|
|
89
|
+
expect(screen.getByTestId('pagination-current-page-input')).toHaveClass('has-error');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Pagination } from './Pagination';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.pagination {
|
|
2
|
+
.form-control {
|
|
3
|
+
margin-bottom: 0;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.pagination__current-page {
|
|
8
|
+
max-width: 50px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.pagination__current-page-input {
|
|
12
|
+
text-align: center;
|
|
13
|
+
font-weight: var(--pf-font-weight-heavy);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.pagination {
|
|
17
|
+
.pagination__current-page-input {
|
|
18
|
+
&.has-error {
|
|
19
|
+
border-color: var(--pf-error-color);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
--pf-pill-neutral-background-color: var(--pf-gray-color-900);
|
|
60
60
|
--pf-pill-neutral-font-color: var(--pf-gray-color-100);
|
|
61
61
|
--pf-pill-neutral-border-color: var(--pf-gray-color-700);
|
|
62
|
+
|
|
63
|
+
--pf-pill-pending-background-color: var(--pf-pending-color);
|
|
64
|
+
--pf-pill-pending-font-color: var(--pf-white-color);
|
|
65
|
+
--pf-pill-pending-border-color: var(--pf-red-color-350);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
.pill {
|
|
@@ -121,4 +125,9 @@
|
|
|
121
125
|
color: var(--pf-pill-secondary-font-color);
|
|
122
126
|
border-color: var(--pf-pill-secondary-border-color);
|
|
123
127
|
}
|
|
128
|
+
&--pending {
|
|
129
|
+
background-color: var(--pf-pill-pending-background-color);
|
|
130
|
+
color: var(--pf-pill-pending-font-color);
|
|
131
|
+
border-color: var(--pf-pill-pending-border-color);
|
|
132
|
+
}
|
|
124
133
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { Table } from './Table';
|
|
3
|
-
import {
|
|
3
|
+
import { columns, sampleData, SampleDataRow } from './sampleData';
|
|
4
|
+
import { registerFontAwesomeIcons } from '@/setup/setupIcons';
|
|
5
|
+
import { indiconDefinitions } from '@/components/icons/indicons';
|
|
6
|
+
|
|
7
|
+
registerFontAwesomeIcons(...Object.values(indiconDefinitions));
|
|
4
8
|
|
|
5
9
|
const meta: Meta = {
|
|
6
10
|
title: 'Layout/Table',
|
|
@@ -239,6 +243,14 @@ const meta: Meta = {
|
|
|
239
243
|
defaultValue: { summary: 'false' },
|
|
240
244
|
},
|
|
241
245
|
},
|
|
246
|
+
totalEntriesText: {
|
|
247
|
+
control: 'text',
|
|
248
|
+
description:
|
|
249
|
+
'The text to display in the total entries section. This is hidden if 1. No pagination exists, 2. No string is passed.',
|
|
250
|
+
table: {
|
|
251
|
+
category: 'Styling',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
242
254
|
// hidden props
|
|
243
255
|
onRowDoubleClicked: {
|
|
244
256
|
table: {
|
|
@@ -495,6 +507,11 @@ const meta: Meta = {
|
|
|
495
507
|
disable: true,
|
|
496
508
|
},
|
|
497
509
|
},
|
|
510
|
+
currentPage: {
|
|
511
|
+
table: {
|
|
512
|
+
disable: true,
|
|
513
|
+
},
|
|
514
|
+
},
|
|
498
515
|
},
|
|
499
516
|
};
|
|
500
517
|
|
|
@@ -525,33 +542,8 @@ export const Default: Story = {
|
|
|
525
542
|
subHeaderComponent: null,
|
|
526
543
|
paginationPerPage: 10,
|
|
527
544
|
isFullHeight: false,
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
name: 'Name',
|
|
531
|
-
selector: (row) => row.name,
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
name: 'Class',
|
|
535
|
-
selector: (row) => row.class,
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
name: 'Age',
|
|
539
|
-
selector: (row) => row.age,
|
|
540
|
-
sortable: true,
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: 'Weapon',
|
|
544
|
-
selector: (row) => row.weapon,
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: 'Backstory',
|
|
548
|
-
selector: (row) => row.backstory,
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
name: 'Favorite Meal',
|
|
552
|
-
selector: (row) => row.favoriteMeal,
|
|
553
|
-
},
|
|
554
|
-
],
|
|
545
|
+
totalEntriesText: 'Showing 12 of 12 entries.',
|
|
546
|
+
columns: columns,
|
|
555
547
|
data: sampleData,
|
|
556
548
|
},
|
|
557
549
|
render: ({ ...args }) => <Table {...args} />,
|
|
@@ -6,6 +6,7 @@ import DataTable, {
|
|
|
6
6
|
|
|
7
7
|
import { LoadingComponent } from './LoadingComponent';
|
|
8
8
|
import { TableProps } from './types';
|
|
9
|
+
import { TablePagination } from './components/TablePagination';
|
|
9
10
|
|
|
10
11
|
export const Table = <T,>(props: TableProps<T>) => {
|
|
11
12
|
const {
|
|
@@ -19,6 +20,8 @@ export const Table = <T,>(props: TableProps<T>) => {
|
|
|
19
20
|
isFullHeight = false,
|
|
20
21
|
subHeaderAlign = 'left',
|
|
21
22
|
className,
|
|
23
|
+
paginationTotalRows,
|
|
24
|
+
totalEntriesText,
|
|
22
25
|
...rest
|
|
23
26
|
} = props;
|
|
24
27
|
|
|
@@ -32,7 +35,7 @@ export const Table = <T,>(props: TableProps<T>) => {
|
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
return (
|
|
35
|
-
<div className={tableWrapperClassName}>
|
|
38
|
+
<div className={tableWrapperClassName} data-testid="table">
|
|
36
39
|
<DataTable
|
|
37
40
|
responsive={responsive}
|
|
38
41
|
direction={direction as RDTDirection}
|
|
@@ -44,6 +47,11 @@ export const Table = <T,>(props: TableProps<T>) => {
|
|
|
44
47
|
noDataComponent={noDataComponent}
|
|
45
48
|
progressPending={isLoading}
|
|
46
49
|
progressComponent={<LoadingComponent />}
|
|
50
|
+
pagination
|
|
51
|
+
paginationComponent={(props) => (
|
|
52
|
+
<TablePagination {...props} totalEntriesText={totalEntriesText} />
|
|
53
|
+
)}
|
|
54
|
+
paginationTotalRows={paginationTotalRows}
|
|
47
55
|
{...rest}
|
|
48
56
|
/>
|
|
49
57
|
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { Table } from '../Table';
|
|
3
|
+
import { sampleData, columns } from '../sampleData';
|
|
4
|
+
|
|
5
|
+
describe('Table', () => {
|
|
6
|
+
it('renders the total entries text', () => {
|
|
7
|
+
render(<Table columns={columns} data={sampleData} totalEntriesText="100 entries" />);
|
|
8
|
+
expect(screen.getByTestId('table-pagination-total-entries')).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Pagination as PaginationComponent } from '../../pagination';
|
|
2
|
+
import { Row, Col } from '../../grid';
|
|
3
|
+
interface TablePaginationProps {
|
|
4
|
+
rowsPerPage: number;
|
|
5
|
+
rowCount: number;
|
|
6
|
+
onChangePage: (page: number, perPage: number) => void;
|
|
7
|
+
currentPage: number;
|
|
8
|
+
totalEntriesText?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const TablePagination = ({
|
|
12
|
+
rowsPerPage,
|
|
13
|
+
rowCount,
|
|
14
|
+
onChangePage,
|
|
15
|
+
currentPage,
|
|
16
|
+
totalEntriesText,
|
|
17
|
+
}: TablePaginationProps) => {
|
|
18
|
+
const totalPages = Math.ceil(rowCount / rowsPerPage);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="table__pagination">
|
|
22
|
+
<Row align="center" justify="between">
|
|
23
|
+
<Col xs="content">
|
|
24
|
+
{totalEntriesText && (
|
|
25
|
+
<span
|
|
26
|
+
data-testid="table-pagination-total-entries"
|
|
27
|
+
className="table__pagination-total-entries"
|
|
28
|
+
>
|
|
29
|
+
{totalEntriesText}
|
|
30
|
+
</span>
|
|
31
|
+
)}
|
|
32
|
+
</Col>
|
|
33
|
+
<Col xs="content">
|
|
34
|
+
<PaginationComponent
|
|
35
|
+
data-testid="table-pagination-component"
|
|
36
|
+
totalPages={totalPages}
|
|
37
|
+
currentPage={currentPage}
|
|
38
|
+
onChange={(page) => onChangePage(page, rowsPerPage)}
|
|
39
|
+
/>
|
|
40
|
+
</Col>
|
|
41
|
+
</Row>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { TablePagination } from '../TablePagination';
|
|
3
|
+
|
|
4
|
+
describe('TablePagination', () => {
|
|
5
|
+
it('renders total entries text', () => {
|
|
6
|
+
render(
|
|
7
|
+
<TablePagination
|
|
8
|
+
rowsPerPage={10}
|
|
9
|
+
rowCount={100}
|
|
10
|
+
onChangePage={() => {}}
|
|
11
|
+
currentPage={1}
|
|
12
|
+
totalEntriesText="100 entries"
|
|
13
|
+
/>,
|
|
14
|
+
);
|
|
15
|
+
expect(screen.getByTestId('table-pagination-total-entries')).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
});
|