@indico-data/design-system 2.47.3 → 2.49.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/Table.stories.d.ts +1 -0
- package/lib/components/table/__tests__/Table.test.d.ts +1 -0
- package/lib/components/table/components/HorizontalStickyHeader.d.ts +10 -0
- package/lib/components/table/components/TablePagination.d.ts +9 -0
- package/lib/components/table/components/__tests__/HorizontalStickyHeader.test.d.ts +1 -0
- package/lib/components/table/components/__tests__/TablePagination.test.d.ts +1 -0
- package/lib/components/table/components/helpers.d.ts +6 -0
- package/lib/components/table/hooks/usePinnedColumnsManager.d.ts +8 -0
- package/lib/components/table/sampleData.d.ts +6 -0
- package/lib/components/table/types.d.ts +16 -5
- package/lib/components/table/utils/processColumns.d.ts +2 -0
- package/lib/index.css +78 -17
- package/lib/index.d.ts +16 -5
- package/lib/index.esm.css +78 -17
- package/lib/index.esm.js +305 -14
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +304 -13
- package/lib/index.js.map +1 -1
- package/lib/utils/getPreviousHeadersWidth.d.ts +1 -0
- 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/table/Table.mdx +136 -0
- package/src/components/table/Table.stories.tsx +91 -30
- package/src/components/table/Table.tsx +25 -2
- package/src/components/table/__tests__/Table.test.tsx +10 -0
- package/src/components/table/components/HorizontalStickyHeader.tsx +57 -0
- package/src/components/table/components/TablePagination.tsx +44 -0
- package/src/components/table/components/__tests__/HorizontalStickyHeader.test.tsx +104 -0
- package/src/components/table/components/__tests__/TablePagination.test.tsx +17 -0
- package/src/components/table/components/helpers.ts +90 -0
- package/src/components/table/hooks/usePinnedColumnsManager.ts +146 -0
- package/src/components/table/sampleData.tsx +436 -0
- package/src/components/table/styles/Table.scss +72 -24
- package/src/components/table/styles/_variables.scss +3 -0
- package/src/components/table/types.ts +19 -7
- package/src/components/table/utils/processColumns.tsx +35 -0
- package/src/setup/setupIcons.ts +4 -0
- package/src/setup/setupTests.ts +8 -0
- package/src/styles/index.scss +1 -0
- package/src/utils/getPreviousHeadersWidth.ts +12 -0
- package/src/components/table/sampleData.ts +0 -171
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getPreviousHeadersWidth: (position: number) => number;
|
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
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
2
|
import * as TableStories from './Table.stories';
|
|
3
3
|
|
|
4
|
+
|
|
5
|
+
|
|
4
6
|
<Meta title="Layout/Table" name="Table" />
|
|
5
7
|
|
|
6
8
|
# Table
|
|
@@ -19,6 +21,9 @@ The `conditionalRowStyles` prop allows you to apply custom styles to specific ro
|
|
|
19
21
|
|
|
20
22
|
- **`highlighted`**: This class is used to visually emphasize specific rows. When applied, the row will inherit styles defined in the design system, making the row stand out with distinct border and background colors.
|
|
21
23
|
|
|
24
|
+
### Issues with storybook
|
|
25
|
+
You may notice the stories may behave a little strangely on storybook, this is not the case in the application. One such bug would be the checkboxes pinning on the default example when toggling props. Another will be the select all checkbox not being locked on the second pinned column example. We have not investigated as this is likely a quirk with the storybook environment.
|
|
26
|
+
|
|
22
27
|
### Example Usage
|
|
23
28
|
|
|
24
29
|
Here's an example of how to use the `conditionalRowStyles` prop to apply the `checked` and `highlighted` classes:
|
|
@@ -42,3 +47,134 @@ const conditionalRowStyles = [
|
|
|
42
47
|
conditionalRowStyles={conditionalRowStyles}
|
|
43
48
|
/>;
|
|
44
49
|
```
|
|
50
|
+
|
|
51
|
+
## Pinned Columns
|
|
52
|
+
|
|
53
|
+
The `canPinColumns` prop allows you to pin columns to the left hand side of the table. This is useful for displaying important columns that should be visible at all times.
|
|
54
|
+
|
|
55
|
+
## Known Bugs
|
|
56
|
+
A limitation of the library has caused us to compromise on the column widths. We had to choose between allowing the pinning of Fixed Width columns, or allowing the pinning of Auto Widths columns. As it stands right now, we went with Fixed Width columns. As a result, you will experience pixel drift on the third+ columns if you do not use fixed width. An example on how to use the fixed width is the following.
|
|
57
|
+
```jsx
|
|
58
|
+
columns={[
|
|
59
|
+
{
|
|
60
|
+
isPinned: false,
|
|
61
|
+
name: 'Name',
|
|
62
|
+
selector: () => {},
|
|
63
|
+
width: '150px'
|
|
64
|
+
},
|
|
65
|
+
]}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Note
|
|
69
|
+
|
|
70
|
+
It will require both `canPinColumns` to be set to `true` and the columns that you wish to have the pin icon contain `isPinned: true` or `isPinned: false` to function. If `isPinned` is undefined, no icon will appear.
|
|
71
|
+
<Canvas of={TableStories.PinnedColumns} />
|
|
72
|
+
```jsx
|
|
73
|
+
<Table
|
|
74
|
+
canPinColumns
|
|
75
|
+
columns={[
|
|
76
|
+
{
|
|
77
|
+
isPinned: false,
|
|
78
|
+
name: 'Name',
|
|
79
|
+
selector: () => {},
|
|
80
|
+
width: '150px'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
isPinned: true,
|
|
84
|
+
name: 'Class',
|
|
85
|
+
selector: () => {},
|
|
86
|
+
width: '150px'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
isPinned: true,
|
|
90
|
+
name: 'Age',
|
|
91
|
+
selector: () => {},
|
|
92
|
+
width: '150px'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Weapon',
|
|
96
|
+
selector: () => {}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Backstory',
|
|
100
|
+
selector: () => {}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Favorite Meal',
|
|
104
|
+
selector: () => {}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Homeland',
|
|
108
|
+
selector: () => {}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Alignment',
|
|
112
|
+
selector: () => {}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
isPinned: false,
|
|
116
|
+
name: 'Special Ability',
|
|
117
|
+
selector: () => {}
|
|
118
|
+
}
|
|
119
|
+
]}
|
|
120
|
+
data={[
|
|
121
|
+
{
|
|
122
|
+
age: 120,
|
|
123
|
+
alignment: 'Neutral Good',
|
|
124
|
+
backstory: 'Raised by wolves in the deep forests.',
|
|
125
|
+
class: 'Ranger',
|
|
126
|
+
favoriteMeal: 'Venison stew',
|
|
127
|
+
homeland: 'Silverleaf Forest',
|
|
128
|
+
name: 'Thalion',
|
|
129
|
+
specialAbility: 'Beast Speech',
|
|
130
|
+
test: 'test',
|
|
131
|
+
weapon: 'Longbow'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
age: 35,
|
|
135
|
+
alignment: 'Lawful Good',
|
|
136
|
+
backstory: 'A former soldier seeking redemption.',
|
|
137
|
+
class: 'Fighter',
|
|
138
|
+
favoriteMeal: 'Roasted boar',
|
|
139
|
+
homeland: 'Kingdom of Valorhaven',
|
|
140
|
+
name: 'Brom',
|
|
141
|
+
specialAbility: 'Battle Master',
|
|
142
|
+
test: 'test',
|
|
143
|
+
weapon: 'Greatsword'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
age: 60,
|
|
147
|
+
alignment: 'Lawful Good',
|
|
148
|
+
backstory: 'A devoted follower of the goddess of life.',
|
|
149
|
+
class: 'Cleric',
|
|
150
|
+
favoriteMeal: 'Vegetable soup',
|
|
151
|
+
homeland: 'Temple of Dawn',
|
|
152
|
+
name: 'Elysia',
|
|
153
|
+
specialAbility: 'Divine Healing',
|
|
154
|
+
test: 'test',
|
|
155
|
+
weapon: 'Mace'
|
|
156
|
+
},
|
|
157
|
+
//... more rows
|
|
158
|
+
]}
|
|
159
|
+
dense
|
|
160
|
+
direction="ltr"
|
|
161
|
+
fixedHeader
|
|
162
|
+
noDataComponent={null}
|
|
163
|
+
onPinnedColumnsChange={() => {}}
|
|
164
|
+
pagination
|
|
165
|
+
paginationPerPage={10}
|
|
166
|
+
paginationRowsPerPageOptions={[
|
|
167
|
+
5,
|
|
168
|
+
10,
|
|
169
|
+
15,
|
|
170
|
+
20
|
|
171
|
+
]}
|
|
172
|
+
responsive
|
|
173
|
+
selectableRows
|
|
174
|
+
subHeaderAlign="center"
|
|
175
|
+
subHeaderComponent={null}
|
|
176
|
+
subHeaderWrap
|
|
177
|
+
title="Character List"
|
|
178
|
+
totalEntriesText="Showing 12 of 12 entries."
|
|
179
|
+
/>
|
|
180
|
+
```
|
|
@@ -1,6 +1,11 @@
|
|
|
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
|
+
import { useState } from 'react';
|
|
7
|
+
|
|
8
|
+
registerFontAwesomeIcons(...Object.values(indiconDefinitions));
|
|
4
9
|
|
|
5
10
|
const meta: Meta = {
|
|
6
11
|
title: 'Layout/Table',
|
|
@@ -8,7 +13,8 @@ const meta: Meta = {
|
|
|
8
13
|
argTypes: {
|
|
9
14
|
columns: {
|
|
10
15
|
control: false,
|
|
11
|
-
description:
|
|
16
|
+
description:
|
|
17
|
+
'The columns to display in the table. All columns require a unique id property. For pinned columns, please see the pinned example below.',
|
|
12
18
|
table: {
|
|
13
19
|
category: 'Data',
|
|
14
20
|
type: { summary: 'array' },
|
|
@@ -239,6 +245,29 @@ const meta: Meta = {
|
|
|
239
245
|
defaultValue: { summary: 'false' },
|
|
240
246
|
},
|
|
241
247
|
},
|
|
248
|
+
totalEntriesText: {
|
|
249
|
+
control: 'text',
|
|
250
|
+
description:
|
|
251
|
+
'The text to display in the total entries section. This is hidden if 1. No pagination exists, 2. No string is passed.',
|
|
252
|
+
table: {
|
|
253
|
+
category: 'Styling',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
canPinColumns: {
|
|
257
|
+
control: 'boolean',
|
|
258
|
+
description:
|
|
259
|
+
'Allows the pinning of columns to the left hand side. This is required when using the column pin API',
|
|
260
|
+
table: {
|
|
261
|
+
category: 'Add-Ons',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
onPinnedColumnsChange: {
|
|
265
|
+
control: false,
|
|
266
|
+
description: 'Callback that receives the IDs of the pinned columns when they change.',
|
|
267
|
+
table: {
|
|
268
|
+
category: 'Add-Ons',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
242
271
|
// hidden props
|
|
243
272
|
onRowDoubleClicked: {
|
|
244
273
|
table: {
|
|
@@ -495,6 +524,11 @@ const meta: Meta = {
|
|
|
495
524
|
disable: true,
|
|
496
525
|
},
|
|
497
526
|
},
|
|
527
|
+
currentPage: {
|
|
528
|
+
table: {
|
|
529
|
+
disable: true,
|
|
530
|
+
},
|
|
531
|
+
},
|
|
498
532
|
},
|
|
499
533
|
};
|
|
500
534
|
|
|
@@ -504,6 +538,7 @@ type Story = StoryObj<typeof Table<SampleDataRow>>;
|
|
|
504
538
|
|
|
505
539
|
export const Default: Story = {
|
|
506
540
|
args: {
|
|
541
|
+
canPinColumns: false,
|
|
507
542
|
pagination: true,
|
|
508
543
|
selectableRows: true,
|
|
509
544
|
isDisabled: false,
|
|
@@ -523,36 +558,62 @@ export const Default: Story = {
|
|
|
523
558
|
clearSelectedRows: false,
|
|
524
559
|
subHeader: false,
|
|
525
560
|
subHeaderComponent: null,
|
|
561
|
+
fixedHeader: true,
|
|
526
562
|
paginationPerPage: 10,
|
|
527
563
|
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
|
-
],
|
|
564
|
+
totalEntriesText: 'Showing 12 of 12 entries.',
|
|
565
|
+
columns: columns,
|
|
555
566
|
data: sampleData,
|
|
556
567
|
},
|
|
557
|
-
render: ({ ...args }) =>
|
|
568
|
+
render: ({ ...args }) => {
|
|
569
|
+
return <Table {...args} columns={columns} />;
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
export const PinnedColumns: Story = {
|
|
574
|
+
args: {
|
|
575
|
+
canPinColumns: true,
|
|
576
|
+
pagination: true,
|
|
577
|
+
selectableRows: true,
|
|
578
|
+
isDisabled: false,
|
|
579
|
+
isLoading: false,
|
|
580
|
+
direction: 'ltr',
|
|
581
|
+
striped: false,
|
|
582
|
+
subHeaderAlign: 'center',
|
|
583
|
+
subHeaderWrap: true,
|
|
584
|
+
paginationRowsPerPageOptions: [5, 10, 15, 20],
|
|
585
|
+
responsive: true,
|
|
586
|
+
title: 'Character List',
|
|
587
|
+
dense: true,
|
|
588
|
+
noHeader: false,
|
|
589
|
+
noTableHead: false,
|
|
590
|
+
noDataComponent: null,
|
|
591
|
+
expandableRows: false,
|
|
592
|
+
clearSelectedRows: false,
|
|
593
|
+
subHeader: false,
|
|
594
|
+
subHeaderComponent: null,
|
|
595
|
+
fixedHeader: true,
|
|
596
|
+
paginationPerPage: 10,
|
|
597
|
+
isFullHeight: false,
|
|
598
|
+
totalEntriesText: 'Showing 12 of 12 entries.',
|
|
599
|
+
columns: columns,
|
|
600
|
+
data: sampleData,
|
|
601
|
+
},
|
|
602
|
+
render: ({ ...args }) => {
|
|
603
|
+
const initialPinnedColumnIds = ['name', 'class'];
|
|
604
|
+
const [pinnedColumnIds, setPinnedColumnIds] = useState<string[]>(initialPinnedColumnIds);
|
|
605
|
+
|
|
606
|
+
const columnsWithPinning = columns.map((column) => ({
|
|
607
|
+
...column,
|
|
608
|
+
isPinned: pinnedColumnIds.includes(column.id as string),
|
|
609
|
+
}));
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<Table
|
|
613
|
+
{...args}
|
|
614
|
+
columns={columnsWithPinning}
|
|
615
|
+
onPinnedColumnsChange={(ids) => setPinnedColumnIds(ids)}
|
|
616
|
+
/>
|
|
617
|
+
);
|
|
618
|
+
},
|
|
558
619
|
};
|