@indico-data/design-system 2.47.3 → 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.
Files changed (39) hide show
  1. package/lib/components/index.d.ts +1 -0
  2. package/lib/components/pagination/Pagination.d.ts +2 -0
  3. package/lib/components/pagination/Pagination.stories.d.ts +6 -0
  4. package/lib/components/pagination/__tests__/Pagination.test.d.ts +1 -0
  5. package/lib/components/pagination/index.d.ts +1 -0
  6. package/lib/components/pagination/types.d.ts +6 -0
  7. package/lib/components/table/__tests__/Table.test.d.ts +1 -0
  8. package/lib/components/table/components/TablePagination.d.ts +9 -0
  9. package/lib/components/table/components/__tests__/TablePagination.test.d.ts +1 -0
  10. package/lib/components/table/sampleData.d.ts +2 -0
  11. package/lib/components/table/types.d.ts +5 -4
  12. package/lib/index.css +50 -8
  13. package/lib/index.d.ts +5 -5
  14. package/lib/index.esm.css +50 -8
  15. package/lib/index.esm.js +69 -14
  16. package/lib/index.esm.js.map +1 -1
  17. package/lib/index.js +68 -13
  18. package/lib/index.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/components/index.ts +1 -0
  21. package/src/components/pagination/Pagination.mdx +31 -0
  22. package/src/components/pagination/Pagination.stories.tsx +80 -0
  23. package/src/components/pagination/Pagination.tsx +117 -0
  24. package/src/components/pagination/__tests__/Pagination.test.tsx +91 -0
  25. package/src/components/pagination/index.ts +1 -0
  26. package/src/components/pagination/styles/Pagination.scss +22 -0
  27. package/src/components/pagination/types.ts +6 -0
  28. package/src/components/table/Table.mdx +2 -0
  29. package/src/components/table/Table.stories.tsx +20 -28
  30. package/src/components/table/Table.tsx +9 -1
  31. package/src/components/table/__tests__/Table.test.tsx +10 -0
  32. package/src/components/table/components/TablePagination.tsx +44 -0
  33. package/src/components/table/components/__tests__/TablePagination.test.tsx +17 -0
  34. package/src/components/table/sampleData.ts +110 -0
  35. package/src/components/table/styles/Table.scss +40 -9
  36. package/src/components/table/styles/_variables.scss +1 -0
  37. package/src/components/table/types.ts +6 -6
  38. package/src/setup/setupIcons.ts +4 -0
  39. package/src/styles/index.scss +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.47.3",
3
+ "version": "2.48.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -20,3 +20,4 @@ export { SingleInputDatePicker } from './forms/date/inputDatePicker';
20
20
  export { InputDateRangePicker } from './forms/date/inputDateRangePicker';
21
21
  export { Modal } from './modal';
22
22
  export { Badge } from './badge';
23
+ export { Pagination } from './pagination';
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ export interface PaginationProps {
2
+ totalPages: number;
3
+ currentPage?: number;
4
+ className?: string;
5
+ onChange?: (value: number) => void;
6
+ }
@@ -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
@@ -1,6 +1,10 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { Table } from './Table';
3
- import { SampleDataRow, sampleData } from './sampleData';
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
- columns: [
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
+ });
@@ -1,3 +1,5 @@
1
+ import { TableColumn } from './types';
2
+
1
3
  export interface SampleDataRow {
2
4
  name: string;
3
5
  class: string;
@@ -168,4 +170,112 @@ export const sampleData: SampleDataRow[] = [
168
170
  backstory: 'A thief with a quick hand.',
169
171
  favoriteMeal: 'Fish and chips',
170
172
  },
173
+ {
174
+ name: 'Thalia',
175
+ class: 'Wizard',
176
+ age: 150,
177
+ weapon: 'Crystal Staff',
178
+ backstory: 'An archivist of forbidden knowledge.',
179
+ favoriteMeal: 'Spiced wine and cheese',
180
+ },
181
+ {
182
+ name: 'Ragnar',
183
+ class: 'Fighter',
184
+ age: 45,
185
+ weapon: 'War Hammer',
186
+ backstory: 'A dwarven smith turned adventurer.',
187
+ favoriteMeal: 'Mead and roasted mutton',
188
+ },
189
+ {
190
+ name: 'Sylvana',
191
+ class: 'Ranger',
192
+ age: 95,
193
+ weapon: 'Elven Bow',
194
+ backstory: 'Guardian of the enchanted forest.',
195
+ favoriteMeal: 'Elvish waybread',
196
+ },
197
+ {
198
+ name: 'Magnus',
199
+ class: 'Warlock',
200
+ age: 38,
201
+ weapon: 'Cursed Dagger',
202
+ backstory: 'Made a deal with a demon for revenge.',
203
+ favoriteMeal: 'Blood oranges',
204
+ },
205
+ {
206
+ name: 'Echo',
207
+ class: 'Bard',
208
+ age: 23,
209
+ weapon: 'Enchanted Violin',
210
+ backstory: 'Can mimic any sound perfectly.',
211
+ favoriteMeal: 'Sweet rolls',
212
+ },
213
+ {
214
+ name: 'Korg',
215
+ class: 'Barbarian',
216
+ age: 32,
217
+ weapon: 'Stone Maul',
218
+ backstory: 'Last survivor of a petrified tribe.',
219
+ favoriteMeal: 'Raw meat',
220
+ },
221
+ {
222
+ name: 'Celeste',
223
+ class: 'Sorcerer',
224
+ age: 19,
225
+ weapon: 'Starlight Wand',
226
+ backstory: 'Born during a celestial convergence.',
227
+ favoriteMeal: 'Moon cakes',
228
+ },
229
+ {
230
+ name: 'Raven',
231
+ class: 'Rogue',
232
+ age: 27,
233
+ weapon: 'Shadow Blade',
234
+ backstory: 'Professional assassin seeking redemption.',
235
+ favoriteMeal: 'Whatever their mark is having',
236
+ },
237
+ {
238
+ name: 'Terra',
239
+ class: 'Druid',
240
+ age: 156,
241
+ weapon: 'Living Wood Staff',
242
+ backstory: 'Speaks with ancient trees.',
243
+ favoriteMeal: 'Wild mushrooms',
244
+ },
245
+ {
246
+ name: 'Ash',
247
+ class: 'Monk',
248
+ age: 29,
249
+ weapon: 'Flame Fists',
250
+ backstory: 'Raised by phoenix monks in a volcano.',
251
+ favoriteMeal: 'Spicy noodles',
252
+ },
253
+ ];
254
+
255
+ export const columns: TableColumn<SampleDataRow>[] = [
256
+ {
257
+ name: 'Name',
258
+ selector: (row: SampleDataRow) => row.name,
259
+ },
260
+ {
261
+ name: 'Class',
262
+ selector: (row) => row.class,
263
+ },
264
+ {
265
+ name: 'Age',
266
+ selector: (row) => row.age,
267
+ sortable: true,
268
+ },
269
+ {
270
+ name: 'Weapon',
271
+ selector: (row) => row.weapon,
272
+ },
273
+ {
274
+ name: 'Backstory',
275
+ selector: (row) => row.backstory,
276
+ },
277
+ {
278
+ name: 'Favorite Meal',
279
+ selector: (row) => row.favoriteMeal,
280
+ },
171
281
  ];