Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create StudioTable components #12731

Merged
merged 78 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
eed3332
update use-cases yml
nkylstad Jan 30, 2024
e765596
Immutable -> Idempotent in readme
ErlingHauan Apr 19, 2024
2e6e21a
Add link to editor on app name
ErlingHauan Apr 19, 2024
c06c318
Add title to <a> tag
ErlingHauan Apr 19, 2024
5315885
Begin switching MUI DataGrid component with DS Table
ErlingHauan Apr 22, 2024
cf66136
Add StudioTableWithPagination to Storybook
ErlingHauan Apr 23, 2024
c486a9f
Make pagination work
ErlingHauan Apr 23, 2024
9666ecc
Improve table
ErlingHauan Apr 23, 2024
0ed9b68
Make a separate component out of StudioTable
ErlingHauan Apr 23, 2024
d8bbb9e
Add sorting
ErlingHauan Apr 24, 2024
a770e67
Clean up code
ErlingHauan Apr 24, 2024
694280f
Add numerical values to mockData
ErlingHauan Apr 24, 2024
903ca42
Begin implementing table in dashboard
ErlingHauan Apr 24, 2024
a17c739
Remove unused files
ErlingHauan Apr 24, 2024
7cddfa0
Fix prop type for FavoriteButton
ErlingHauan Apr 24, 2024
7934471
Change label size switch statement to object map
ErlingHauan Apr 24, 2024
5fe031f
Remove useEffect from StudioTabelWithPagination
ErlingHauan Apr 24, 2024
46ab5a7
Small fixes
ErlingHauan Apr 24, 2024
6298d08
Fix linting issues
ErlingHauan Apr 24, 2024
de3f73c
Fix double type declaration
ErlingHauan Apr 24, 2024
98c37e3
Make table read array of objects
ErlingHauan Apr 25, 2024
769dff3
Tidy up StudioTabel and make sorting work again
ErlingHauan Apr 25, 2024
65eb078
Undo unintended CSS class name changes for unrelated files
ErlingHauan Apr 25, 2024
0c58bd4
Undo unintended changes in DesignView.module.css
ErlingHauan Apr 25, 2024
e3aecbd
Change columns 'key' attribute to 'accessor' to avoid confusiong with…
ErlingHauan Apr 25, 2024
897a806
Undo Update README.md - not relevant
ErlingHauan Apr 25, 2024
bddcb55
Rename props
ErlingHauan Apr 26, 2024
d385397
Move StudioTable into StudioTableWithPagination
ErlingHauan Apr 26, 2024
ab44e57
small tips and tricks
framitdavid Apr 26, 2024
e00e701
Add minimal testing
ErlingHauan Apr 26, 2024
aea25d4
Add minimal testing
ErlingHauan Apr 26, 2024
e5d4fd0
Merge outside changes
ErlingHauan Apr 26, 2024
7e6a1c8
Merge branch 'main' of https://github.com/Altinn/altinn-studio into d…
ErlingHauan Apr 26, 2024
2594f0c
Undo changes in dashboard/components
ErlingHauan Apr 26, 2024
b022250
Undo changes in dashboard/components
ErlingHauan Apr 26, 2024
3fa9a58
Make StudioTableRemotePagination functional in Storybook
ErlingHauan Apr 29, 2024
866fcfc
Small fixes
ErlingHauan Apr 29, 2024
7013e21
Make StudioTableLocalPagination functional in Storybook
ErlingHauan Apr 29, 2024
477a210
Fix both tables, so that they work in all cases with or without pagin…
ErlingHauan Apr 29, 2024
49c32dc
Remove unused files
ErlingHauan Apr 29, 2024
ba1b4b4
Remove dashboard helper functions from this branch
ErlingHauan Apr 30, 2024
606b6df
Improve documentation
ErlingHauan Apr 30, 2024
7a6853f
Merge branch 'main', remote-tracking branch 'origin' into dashboard-a…
ErlingHauan Apr 30, 2024
986213c
Add pageSizeLabel prop
ErlingHauan Apr 30, 2024
d9d8e79
Begin testing
ErlingHauan Apr 30, 2024
00c4e94
Begin writing test for StudioTableLocalPagination
ErlingHauan Apr 30, 2024
64c2997
Finish tests and solve linting errors
ErlingHauan May 2, 2024
78ebbab
Export type Rows from StudioTableRemotePagination/index.ts
ErlingHauan May 2, 2024
74d0a4a
Add testing to useTableSorting
ErlingHauan May 2, 2024
344c3bd
Merge branch 'main' into studio-table-components
ErlingHauan May 2, 2024
3728bba
Use 'export type' when exporting Rows
ErlingHauan May 2, 2024
0588a60
Merge branch 'studio-table-components' of https://github.com/Altinn/a…
ErlingHauan May 2, 2024
8ff51b5
Use 'export type' when exporting Rows
ErlingHauan May 2, 2024
98e9a3d
Fix component documentation
ErlingHauan May 2, 2024
e65d9c3
Test case where isSortable is false
ErlingHauan May 2, 2024
f5df33b
Fix codestyle
ErlingHauan May 2, 2024
4e60392
Handle case where rows array is empty
ErlingHauan May 3, 2024
ffcd4a7
Fix documentation for StudioTableRemotePagination
ErlingHauan May 7, 2024
9529305
Fix documentation for StudioTableLocalPagination
ErlingHauan May 7, 2024
1d2b5c0
Add optional props heading
ErlingHauan May 7, 2024
b2d0127
Fix StudioTableLocalPagination tests
ErlingHauan May 7, 2024
5a03d28
Add config object to useTableSorting
ErlingHauan May 7, 2024
5d18f29
Add empty table message prop
ErlingHauan May 7, 2024
c6e5e2e
Refactor conditions for isSortable and isPaginationActive
ErlingHauan May 7, 2024
1aca0a4
Rename reziseLabelMap and its types
ErlingHauan May 8, 2024
163831b
Use spread operator to show which props are processed in StudioTableL…
ErlingHauan May 8, 2024
fec5672
Move 'lets' into states
ErlingHauan May 8, 2024
171acb5
Remove storybook formatting errors
ErlingHauan May 8, 2024
4c791f5
Update StudioTableRemotePagination documentation
ErlingHauan May 8, 2024
c07a0e6
Update StudioTableLocalPagination documentation
ErlingHauan May 8, 2024
20a2300
Fix handlePageSizeChange
ErlingHauan May 8, 2024
f21e0a2
Write test for utility function
ErlingHauan May 8, 2024
6d55742
Fix sorting triggering for StudioTableLocalPagination and fix tests
ErlingHauan May 13, 2024
06f5f7f
Clean up StudioTableLocalPagination
ErlingHauan May 13, 2024
67f626e
Fix empty message test
ErlingHauan May 13, 2024
a2f3929
Merge branch 'main' of https://github.com/Altinn/altinn-studio into s…
ErlingHauan May 13, 2024
dc67f05
Fix PR comments
ErlingHauan May 14, 2024
dd8a207
Add 'rowsToRender' setting to .eslintrc.js
ErlingHauan May 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion frontend/libs/studio-components/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ module.exports = {
},
},
],

extends: ['plugin:storybook/recommended'],
settings: {
'testing-library/custom-renders': ['rowsToRender'],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Canvas, Meta } from '@storybook/blocks';
import { Heading, Paragraph } from '@digdir/design-system-react';
import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination.stories';
import { StudioTableLocalPagination } from './StudioTableLocalPagination';

<Meta of={StudioTableLocalPaginationStories} />

<Heading level={1} size='small'>
StudioTableLocalPagination
</Heading>
<Paragraph>
The StudioTableLocalPagination component handles pagination internally, eliminating the need for
manual control. It seamlessly manages pagination logic for you.
</Paragraph>

<Canvas of={StudioTableLocalPaginationStories.Preview} />

<Heading level={2} size='xsmall'>
Column format
</Heading>
<Paragraph>Columns must have both an `accessor` and a `value` property.</Paragraph>

```tsx
const columns = [
{
accessor: 'icon',
value: '',
},
{
accessor: 'name',
value: 'Name',
},
{
accessor: 'creator',
value: 'Created by',
},
{
accessor: 'lastChanged',
value: 'Last changed',
},
];
```

<Heading level={2} size='xsmall'>
Row format
</Heading>
<ul>
<li>Rows must have an `id` that is unique.</li>
<li>
The accessors in the columns array is used to display the row properties. Therefore, each
property name has to exactly match the accessor.
</li>
</ul>

```tsx
const rows = [
{
id: 1,
icon: <StarFillIcon />,
name: 'Coordinated register notification',
creator: 'Brønnøysund Register Centre',
lastChanged: '12-04-2023',
},
{
id: 2,
icon: <StarFillIcon />,
name: 'Application for authorisation and license as a healthcare personnel',
creator: 'The Norwegian Directorate of Health',
lastChanged: '05-04-2023',
},
];
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import type { Meta, StoryFn } from '@storybook/react';
import { StudioTableLocalPagination } from './StudioTableLocalPagination';
import { columns, rows } from '../StudioTableRemotePagination/mockData';

type Story = StoryFn<typeof StudioTableLocalPagination>;

const meta: Meta = {
title: 'Studio/StudioTableLocalPagination',
component: StudioTableLocalPagination,
argTypes: {
columns: {
description: 'An array of objects representing the table columns.',
},
rows: {
description: 'An array of objects representing the table rows.',
},
size: {
control: 'radio',
options: ['small', 'medium', 'large'],
description: 'The size of the table.',
},
emptyTableMessage: {
description: 'The message to display when the table is empty.',
},
isSortable: {
description:
'Boolean that sets sorting to true or false. If set to false, the sorting buttons are hidden.',
},
pagination: {
description:
'An object containing pagination-related props. If not provided, pagination is hidden.',
},
},
};

export const Preview: Story = (args) => (
<StudioTableLocalPagination
columns={columns}
rows={rows}
size={args.size}
emptyTableMessage={'No data found'}
isSortable={true}
pagination={{
pageSizeOptions: [5, 10, 20, 50],
pageSizeLabel: 'Rows per page',
nextButtonText: 'Next',
previousButtonText: 'Previous',
itemLabel: (num) => `Page ${num}`,
}}
/>
);

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
framitdavid marked this conversation as resolved.
Show resolved Hide resolved
import { StudioTableLocalPagination } from './StudioTableLocalPagination';
import type { StudioTableLocalPaginationProps } from './StudioTableLocalPagination';
import { columns, rows } from '../StudioTableRemotePagination/mockData';

describe('StudioTableLocalPagination', () => {
const paginationProps: StudioTableLocalPaginationProps['pagination'] = {
pageSizeOptions: [5, 10, 50],
pageSizeLabel: 'Rows per page',
nextButtonText: 'Next',
previousButtonText: 'Previous',
itemLabel: (num) => `Page ${num}`,
};

it('renders the table with columns and rows', () => {
render(<StudioTableLocalPagination columns={columns} rows={rows} />);

expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument();
expect(screen.getByRole('columnheader', { name: 'Created by' })).toBeInTheDocument();
expect(
screen.getByRole('cell', { name: 'Coordinated register notification' }),
).toBeInTheDocument();
expect(
screen.getByRole('cell', { name: 'The Norwegian Directorate of Health' }),
).toBeInTheDocument();
});

it('does not render sorting buttons when isSortable is set to false', () => {
render(<StudioTableLocalPagination columns={columns} rows={rows} isSortable={false} />);

expect(screen.queryByRole('button', { name: 'Name' })).not.toBeInTheDocument();
});

it('triggers sorting when a sortable column header is clicked', async () => {
render(<StudioTableLocalPagination columns={columns} rows={rows} isSortable />);
const user = userEvent.setup();

await user.click(screen.getByRole('button', { name: 'Name' }));
const [, firstBodyRow, secondBodyRow] = screen.getAllByRole('row');

expect(
within(firstBodyRow).getByRole('cell', { name: 'A-melding – all forms' }),
).toBeInTheDocument();

expect(
within(secondBodyRow).getByRole('cell', { name: 'Application for VAT registration' }),
).toBeInTheDocument();
});

it('renders the complete table when pagination prop is not provided', () => {
render(<StudioTableLocalPagination columns={columns} rows={rows} />);
expect(
screen.getByRole('cell', { name: 'Coordinated register notification' }),
).toBeInTheDocument();
expect(
screen.getByRole('cell', { name: 'Application for a certificate of good conduct' }),
).toBeInTheDocument();
});

it('renders pagination controls when pagination prop is provided', () => {
ErlingHauan marked this conversation as resolved.
Show resolved Hide resolved
render(
<StudioTableLocalPagination columns={columns} rows={rows} pagination={paginationProps} />,
);

expect(screen.getByRole('combobox', { name: 'Rows per page' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument();
});

it('changes page when the "Next" button is clicked', async () => {
render(
<StudioTableLocalPagination columns={columns} rows={rows} pagination={paginationProps} />,
);
const user = userEvent.setup();

await user.click(screen.getByRole('button', { name: 'Next' }));

expect(
screen.queryByRole('cell', { name: 'Coordinated register notification' }),
).not.toBeInTheDocument();
expect(screen.getByRole('cell', { name: 'A-melding – all forms' })).toBeInTheDocument();
expect(
screen.getByRole('cell', { name: 'Application for VAT registration' }),
).toBeInTheDocument();
});

it('changes page when the "Page 2" button is clicked', async () => {
render(
<StudioTableLocalPagination columns={columns} rows={rows} pagination={paginationProps} />,
);
const user = userEvent.setup();

await user.click(screen.getByRole('button', { name: 'Page 2' }));

expect(
screen.queryByRole('cell', { name: 'Coordinated register notification' }),
).not.toBeInTheDocument();
expect(screen.getByRole('cell', { name: 'A-melding – all forms' })).toBeInTheDocument();
expect(
screen.getByRole('cell', { name: 'Application for VAT registration' }),
).toBeInTheDocument();
});

it('changes page size when a different page size option is selected', async () => {
render(
<StudioTableLocalPagination columns={columns} rows={rows} pagination={paginationProps} />,
);
const user = userEvent.setup();

await user.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10');

const tableBody = screen.getAllByRole('rowgroup')[1];
const tableBodyRows = within(tableBody).getAllByRole('row');
expect(tableBodyRows).toHaveLength(10);
});

it('sets currentPage to 1 when no rows are displayed', async () => {
render(
<StudioTableLocalPagination columns={columns} rows={rows} pagination={paginationProps} />,
);
const user = userEvent.setup();

await user.click(screen.getByRole('button', { name: 'Page 4' }));
const lastPageBody = screen.getAllByRole('rowgroup')[1];
const lastPageRow = within(lastPageBody).getAllByRole('row');
expect(lastPageRow.length).toBe(1);

await user.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '50');
const tableBody = screen.getAllByRole('rowgroup')[1];
const tableBodyRows = within(tableBody).getAllByRole('row');
expect(tableBodyRows.length).toBeGreaterThan(10);
});

it('displays the empty table message when there are no rows to display', () => {
render(
<StudioTableLocalPagination
columns={columns}
rows={[]}
emptyTableMessage='No rows to display'
/>,
);
expect(screen.getByText('No rows to display')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { forwardRef, useEffect, useState } from 'react';
Fixed Show fixed Hide fixed
import { StudioTableRemotePagination } from '../StudioTableRemotePagination';
import type { Rows } from '../StudioTableRemotePagination';
import { useTableSorting } from '../../hooks/useTableSorting';
import { getRowsToRender } from '../StudioTableRemotePagination/utils';

export type StudioTableLocalPaginationProps = {
columns: Record<'accessor' | 'value', string>[];
rows: Rows;
size?: 'small' | 'medium' | 'large';
emptyTableMessage?: string;
isSortable?: boolean;
pagination?: {
pageSizeOptions: number[];
pageSizeLabel: string;
nextButtonText: string;
previousButtonText: string;
itemLabel: (num: number) => string;
};
};

export const StudioTableLocalPagination = forwardRef<
HTMLTableElement,
StudioTableLocalPaginationProps
>(
(
{ columns, rows, isSortable = true, size = 'medium', emptyTableMessage, pagination },
ref,
): React.ReactElement => {
const [currentPage, setCurrentPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(pagination?.pageSizeOptions[0] ?? undefined);

const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable });

const initialRowsToRender = getRowsToRender(currentPage, pageSize, rows);
const [rowsToRender, setRowsToRender] = useState<Rows>(initialRowsToRender);

useEffect(() => {
const newRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows);

const isOutOfRange = !newRowsToRender.length && currentPage > 1;
if (isOutOfRange) {
setCurrentPage(1);
setRowsToRender(getRowsToRender(1, pageSize, sortedRows || rows));
return;
}

setRowsToRender(newRowsToRender);
}, [sortedRows, rows, currentPage, pageSize]);
framitdavid marked this conversation as resolved.
Show resolved Hide resolved

const totalPages = Math.ceil(rows.length / pageSize);

const studioTableRemotePaginationProps = pagination && {
...pagination,
currentPage,
totalPages,
onPageChange: setCurrentPage,
onPageSizeChange: setPageSize,
};

return (
<StudioTableRemotePagination
columns={columns}
rows={rowsToRender}
size={size}
emptyTableMessage={emptyTableMessage}
onSortClick={isSortable && handleSorting}
pagination={studioTableRemotePaginationProps}
ref={ref}
/>
);
},
);

StudioTableLocalPagination.displayName = 'StudioTableLocalPagination';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { StudioTableLocalPagination } from './StudioTableLocalPagination';
Loading
Loading