From eed3332efb0a2a341170c4c592085f715d3e5ea6 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Tue, 30 Jan 2024 15:49:16 +0100 Subject: [PATCH 01/72] update use-cases yml --- frontend/testing/cypress/use-cases.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/testing/cypress/use-cases.yaml b/frontend/testing/cypress/use-cases.yaml index 5693c831525..b639b945589 100644 --- a/frontend/testing/cypress/use-cases.yaml +++ b/frontend/testing/cypress/use-cases.yaml @@ -16,7 +16,7 @@ schedules: displayName: Bruksmønster branches: include: - - master + - main always: true steps: From e765596cf7aecc1b2e1634c0f5b338d4b16346b5 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 19 Apr 2024 08:29:20 +0200 Subject: [PATCH 02/72] Immutable -> Idempotent in readme --- development/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development/README.md b/development/README.md index b98fde6b4b6..6141433da4e 100644 --- a/development/README.md +++ b/development/README.md @@ -2,7 +2,7 @@ ## Setup script -Run the setup script in this folder with node. This script should be immutable, so that it can be run multiple times +Run the setup script in this folder with node. This script should be idempotent, so that it can be run multiple times without breaking anything. This is great for introducing new environment variables and so on. It will not overwrite any changes you do to the `.env`-file, and it's wise to run this after you change something. Then, for instance, it will update passwords and ownership in Gitea for that user. From 2e6e21af984e0072208982e80e88fbefeef49e45 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 19 Apr 2024 13:41:19 +0200 Subject: [PATCH 03/72] Add link to editor on app name --- .../components/RepoList/RepoList.module.css | 32 ++++++++++++++++--- .../components/RepoList/RepoList.tsx | 10 ++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/frontend/dashboard/components/RepoList/RepoList.module.css b/frontend/dashboard/components/RepoList/RepoList.module.css index 4006659a806..183a67fe321 100644 --- a/frontend/dashboard/components/RepoList/RepoList.module.css +++ b/frontend/dashboard/components/RepoList/RepoList.module.css @@ -1,7 +1,7 @@ .actionLink { align-items: center; display: flex; - margin-right: 1rem; + margin-right: 0.5rem; min-width: 40px; } @@ -17,14 +17,27 @@ font-size: 2rem; } +.nameLink { + color: rgba(0, 0, 0, 0.87); +} + .editLink { - color: #165db8; + margin-right: -0.02rem; + color: var(--fds-semantic-text-action-first-default); } .editLink:hover { - color: #165db8; + color: var(--fds-semantic-text-action-first-default); } +/*.editLink {*/ +/* color: #165db8;*/ +/*}*/ + +/*.editLink:hover {*/ +/* color: #165db8;*/ +/*}*/ + .favoriteIcon { font-size: 26px; color: #000000; @@ -37,17 +50,26 @@ } .repoLink { - color: #57823d; + color: var(--fds-semantic-text-success-default); } .repoLink:hover { - color: #57823d; + color: var(--fds-semantic-text-success-default); } +/*.repoLink {*/ +/* color: #57823d;*/ +/*}*/ + +/*.repoLink:hover {*/ +/* color: #57823d;*/ +/*}*/ + .textWithTooltip { overflow: hidden; text-overflow: ellipsis; } + .columnHeader { background-color: white !important; } diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 23ab9ed0a0c..303642e9ace 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -154,6 +154,16 @@ export const RepoList = ({ headerName: t('dashboard.name'), width: 200, renderCell: TextWithTooltip, + valueGetter: (params: GridValueGetterParams) => { + const repoFullName = params.row.full_name as string; + const [org, repo] = repoFullName.split('/'); + const editUrl = getRepoEditUrl({ org, repo }); + return ( + + {repo} + + ); + }, }, { field: 'owner.created_by', From c06c3185d6121d8cacb216d85b06258d34ddbfd4 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 19 Apr 2024 13:58:04 +0200 Subject: [PATCH 04/72] Add title to tag --- frontend/dashboard/components/RepoList/RepoList.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 303642e9ace..82df96c55db 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -157,9 +157,14 @@ export const RepoList = ({ valueGetter: (params: GridValueGetterParams) => { const repoFullName = params.row.full_name as string; const [org, repo] = repoFullName.split('/'); + const isDatamodelling = repoFullName.endsWith('-datamodels'); const editUrl = getRepoEditUrl({ org, repo }); + const editTextKey = t( + isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service', + ); + return ( - + {repo} ); From 53158851b16bda334222824e585f8afe6c9ded5f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 22 Apr 2024 16:41:29 +0200 Subject: [PATCH 05/72] Begin switching MUI DataGrid component with DS Table --- .../DeploymentEnvironmentStatus.tsx | 2 +- .../overview/components/DeploymentLogList.tsx | 2 +- .../overview/components/DeploymentStatus.tsx | 2 +- .../MergeConflictWarning.module.css | 2 +- .../features/simpleMerge/RepoModal.module.css | 2 +- .../FavoriteReposList/FavoriteReposList.tsx | 2 + .../components/OrgRepoList/OrgReposList.tsx | 2 + .../components/RepoList/ActionLinks.tsx | 65 +++++++++++++++ .../dashboard/components/RepoList/BodyRow.tsx | 60 ++++++++++++++ .../components/RepoList/HeaderRow.tsx | 18 +++++ .../components/RepoList/NewRepoList.tsx | 19 +++++ .../components/RepoList/RepoList.module.css | 80 ++++++++----------- .../components/RepoList/RepoList.tsx | 6 +- .../src/DateUtils/DateUtils.test.ts | 2 +- .../src/DateUtils/DateUtils.ts | 4 +- .../components/AltinnConfirmDialog.module.css | 2 +- .../src/primitives/Primitives.module.css | 2 +- .../DesignView/DesignView.module.css | 2 +- .../InputPopover/InputPopover.module.css | 2 +- .../DesignView/DesignView.module.css | 2 +- 20 files changed, 217 insertions(+), 61 deletions(-) create mode 100644 frontend/dashboard/components/RepoList/ActionLinks.tsx create mode 100644 frontend/dashboard/components/RepoList/BodyRow.tsx create mode 100644 frontend/dashboard/components/RepoList/HeaderRow.tsx create mode 100644 frontend/dashboard/components/RepoList/NewRepoList.tsx diff --git a/frontend/app-development/features/appPublish/components/DeploymentEnvironmentStatus.tsx b/frontend/app-development/features/appPublish/components/DeploymentEnvironmentStatus.tsx index f6c0408344a..a5239fae2af 100644 --- a/frontend/app-development/features/appPublish/components/DeploymentEnvironmentStatus.tsx +++ b/frontend/app-development/features/appPublish/components/DeploymentEnvironmentStatus.tsx @@ -26,7 +26,7 @@ export const DeploymentEnvironmentStatus = ({ const formatDateTime = (dateAsString: string): string => { return t('general.date_time_format', { - date: DateUtils.formatDateDDMMYY(dateAsString), + date: DateUtils.formatDateDDMMYYYY(dateAsString), time: DateUtils.formatTimeHHmm(dateAsString), }); }; diff --git a/frontend/app-development/features/overview/components/DeploymentLogList.tsx b/frontend/app-development/features/overview/components/DeploymentLogList.tsx index fdd87391edf..86dfa9bff66 100644 --- a/frontend/app-development/features/overview/components/DeploymentLogList.tsx +++ b/frontend/app-development/features/overview/components/DeploymentLogList.tsx @@ -28,7 +28,7 @@ export const DeploymentLogList = ({ const formatDateTime = (dateAsString: string): string => { return t('general.date_time_format', { - date: DateUtils.formatDateDDMMYY(dateAsString), + date: DateUtils.formatDateDDMMYYYY(dateAsString), time: DateUtils.formatTimeHHmm(dateAsString), }); }; diff --git a/frontend/app-development/features/overview/components/DeploymentStatus.tsx b/frontend/app-development/features/overview/components/DeploymentStatus.tsx index 5b5ace9d099..af94db200fc 100644 --- a/frontend/app-development/features/overview/components/DeploymentStatus.tsx +++ b/frontend/app-development/features/overview/components/DeploymentStatus.tsx @@ -29,7 +29,7 @@ export const DeploymentStatus = ({ const formatDateTime = (dateAsString: string): string => { return t('general.date_time_format', { - date: DateUtils.formatDateDDMMYY(dateAsString), + date: DateUtils.formatDateDDMMYYYY(dateAsString), time: DateUtils.formatTimeHHmm(dateAsString), }); }; diff --git a/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css b/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css index 480b89cbf57..ce60bf1ca18 100644 --- a/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css +++ b/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css @@ -6,6 +6,6 @@ border: 1px solid #c9c9c9; border-radius: 3px; } -.buttonContainer { +.actionButtonContainer { justify-content: flex-end; } diff --git a/frontend/app-development/features/simpleMerge/RepoModal.module.css b/frontend/app-development/features/simpleMerge/RepoModal.module.css index 7d7d5dd1821..57234bad858 100644 --- a/frontend/app-development/features/simpleMerge/RepoModal.module.css +++ b/frontend/app-development/features/simpleMerge/RepoModal.module.css @@ -11,7 +11,7 @@ margin: 0; padding: 0; } -.buttonContainer { +.actionButtonContainer { display: flex; gap: 24px; } diff --git a/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx b/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx index 8847a7eedfd..7f63db98f08 100644 --- a/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx +++ b/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useStarredReposQuery } from '../../hooks/queries'; import { Heading } from '@digdir/design-system-react'; import { DATAGRID_DEFAULT_PAGE_SIZE } from 'dashboard/constants'; +import { NewRepoList } from '../RepoList/NewRepoList'; export const FavoriteReposList = () => { const { t } = useTranslation(); @@ -14,6 +15,7 @@ export const FavoriteReposList = () => { {t('dashboard.favourites')} + { {getReposLabel({ selectedContext, orgs: organizations, t })} + !repo.name.endsWith('-datamodels'))} /> !repo.name.endsWith('-datamodels'))} isLoading={isLoadingSearchResults || areStarredReposPending} diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx new file mode 100644 index 00000000000..10bee4115fe --- /dev/null +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -0,0 +1,65 @@ +import { Button, DropdownMenu } from '@digdir/design-system-react'; +import React from 'react'; +import classes from './RepoList.module.css'; +import cn from 'classnames'; +import { + ExternalLinkIcon, + FilesIcon, + MenuElipsisVerticalIcon, + PencilIcon, +} from '@navikt/aksel-icons'; +import { useTranslation } from 'react-i18next'; +import { getRepoEditUrl } from '../../utils/urlUtils'; +import { Repository } from 'app-shared/types/Repository'; + +interface ActionLinksProps { + repo: Repository; +} + +export const ActionLinks: React.FC = ({ repo }) => { + const { t } = useTranslation(); + + const repoFullName = repo.full_name as string; + const [org, repoName] = repoFullName.split('/'); + const isDatamodelling = repoFullName.endsWith('-datamodels'); + const editUrl = getRepoEditUrl({ org, repo: repoName }); + const editTextKey = t(isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service'); + + return ( + <> + + + + + + + + + {} + {t('dashboard.make_copy')} + + + {} + {t('dashboard.open_in_new')} + + + + + ); +}; diff --git a/frontend/dashboard/components/RepoList/BodyRow.tsx b/frontend/dashboard/components/RepoList/BodyRow.tsx new file mode 100644 index 00000000000..038949a223e --- /dev/null +++ b/frontend/dashboard/components/RepoList/BodyRow.tsx @@ -0,0 +1,60 @@ +import { Button, Table } from '@digdir/design-system-react'; +import React from 'react'; +import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; +import { DateUtils } from '@studio/pure-functions'; +import { ActionLinks } from './ActionLinks'; +import classes from './RepoList.module.css'; +import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; +import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; +import { getRepoEditUrl } from '../../utils/urlUtils'; +import { useTranslation } from 'react-i18next'; + +interface BodyRowProps { + repo: RepositoryWithStarred; +} + +export const BodyRow: React.FC = ({ repo }) => { + const { t } = useTranslation(); + const { mutate: setStarredRepo } = useSetStarredRepoMutation(); + const { mutate: unsetStarredRepo } = useUnsetStarredRepoMutation(); + + const handleToggleFav = (event: React.MouseEvent) => { + event.stopPropagation(); + if (repo.hasStarred) { + unsetStarredRepo(repo); + } else { + setStarredRepo(repo); + } + }; + + const repoFullName = repo.full_name as string; + const [org, repoName] = repoFullName.split('/'); + const isDatamodelling = repoFullName.endsWith('-datamodels'); + const editUrl = getRepoEditUrl({ org, repo: repoName }); + const editTextKey = t(isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service'); + + return ( + (window.location.href = editUrl)} + className={classes.bodyRow} + > + + + + {repo.name} + {repo.owner.full_name || repo.owner.login} + {DateUtils.formatDateDDMMYYYY(repo.updated_at)} + {repo.description} + + + + + ); +}; diff --git a/frontend/dashboard/components/RepoList/HeaderRow.tsx b/frontend/dashboard/components/RepoList/HeaderRow.tsx new file mode 100644 index 00000000000..56565cd4da0 --- /dev/null +++ b/frontend/dashboard/components/RepoList/HeaderRow.tsx @@ -0,0 +1,18 @@ +import { Table } from '@digdir/design-system-react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import classes from './RepoList.module.css'; + +export const HeaderRow = () => { + const { t } = useTranslation(); + return ( + + + {t('dashboard.name')} + {t('dashboard.created_by')} + {t('dashboard.last_modified')} + {t('dashboard.description')} + + + ); +}; diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx new file mode 100644 index 00000000000..4f4adcae187 --- /dev/null +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -0,0 +1,19 @@ +import { Table } from '@digdir/design-system-react'; +import React from 'react'; +import classes from './RepoList.module.css'; +import { BodyRow } from './BodyRow'; +import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; +import { HeaderRow } from './HeaderRow'; + +export const NewRepoList = ({ repos }) => { + return ( + + + + + + {repos?.map((repo: RepositoryWithStarred) => )} + +
+ ); +}; diff --git a/frontend/dashboard/components/RepoList/RepoList.module.css b/frontend/dashboard/components/RepoList/RepoList.module.css index 183a67fe321..3b7ba9c1eeb 100644 --- a/frontend/dashboard/components/RepoList/RepoList.module.css +++ b/frontend/dashboard/components/RepoList/RepoList.module.css @@ -1,69 +1,59 @@ -.actionLink { - align-items: center; - display: flex; - margin-right: 0.5rem; - min-width: 40px; -} +.repoList { + width: 100%; -.actionLink:hover { - text-decoration: none; -} + > * > * > * { + border-color: var(--fds-semantic-border-action-second-subtle); + } -.actionLink:hover span { - text-decoration: underline; + /*To be removed*/ + margin-bottom: 100px; } -.dropdownIcon { - font-size: 2rem; +.bodyRow { + cursor: pointer; } -.nameLink { - color: rgba(0, 0, 0, 0.87); +.favoriteIcon { + font-size: 26px; + color: #000000; } -.editLink { - margin-right: -0.02rem; - color: var(--fds-semantic-text-action-first-default); +.favoriteCell { + width: 40px; } -.editLink:hover { - color: var(--fds-semantic-text-action-first-default); +.actionButtonContainer { + display: flex; + justify-content: flex-end; } -/*.editLink {*/ -/* color: #165db8;*/ -/*}*/ - -/*.editLink:hover {*/ -/* color: #165db8;*/ -/*}*/ - -.favoriteIcon { - font-size: 26px; - color: #000000; +.repoButton { + margin-right: 9px; } -.linkIcon { +.repoIcon { font-size: 2rem; - height: 32px; - width: 32px; + color: #57823d; + padding-right: 7px; + padding-bottom: 5px; } -.repoLink { - color: var(--fds-semantic-text-success-default); +.repoIcon:hover { + color: #57823d; } -.repoLink:hover { - color: var(--fds-semantic-text-success-default); -} +.dropdownIcon, +.dropdownText { + color: var(--fds-semantic-text-neutral-subtle); -/*.repoLink {*/ -/* color: #57823d;*/ -/*}*/ + a { + color: var(--fds-semantic-text-neutral-subtle); + } +} -/*.repoLink:hover {*/ -/* color: #57823d;*/ -/*}*/ +.dropdownText:hover { + color: var(--fds-semantic-text-neutral-subtle); +} .textWithTooltip { overflow: hidden; diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 82df96c55db..33fd0515f13 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -214,8 +214,8 @@ export const RepoList = ({ return [ } + className={cn(classes.actionLink, classes.repoIcon)} + icon={} key={`dashboard.repository${params.row.id}`} label={t('dashboard.repository_in_list', { appName: repo })} onClick={() => (window.location.href = params.row.html_url)} @@ -238,7 +238,7 @@ export const RepoList = ({ {t(editTextKey)} diff --git a/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.test.ts b/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.test.ts index de19d944763..adafd1e3a19 100644 --- a/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.test.ts +++ b/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.test.ts @@ -11,7 +11,7 @@ test('that formatTimeHHmm works', () => { }); test('that formatDateDDMMYY works', () => { - const formatted = DateUtils.formatDateDDMMYY( + const formatted = DateUtils.formatDateDDMMYYYY( 'Tue Jan 10 2023 13:23:45 GMT+0100 (Central European Standard Time)', 'Europe/Oslo', ); diff --git a/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.ts b/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.ts index 53a4b5a897b..9624b268de9 100644 --- a/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.ts +++ b/frontend/libs/studio-pure-functions/src/DateUtils/DateUtils.ts @@ -7,7 +7,7 @@ export class DateUtils { timeZone, }); - static formatDateDDMMYY = (dateasstring: string, timeZone?: string) => + static formatDateDDMMYYYY = (dateasstring: string, timeZone?: string) => new Date(dateasstring).toLocaleDateString('no-NB', { year: 'numeric', month: '2-digit', @@ -17,7 +17,7 @@ export class DateUtils { static formatDateTime = (dateasstring: string, timeZone?: string) => [ - DateUtils.formatDateDDMMYY(dateasstring, timeZone), + DateUtils.formatDateDDMMYYYY(dateasstring, timeZone), DateUtils.formatTimeHHmm(dateasstring, timeZone), ].join(' '); diff --git a/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css b/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css index e66a243b79a..00a24c38a76 100644 --- a/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css +++ b/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css @@ -9,7 +9,7 @@ margin-top: 0; } -.buttonContainer { +.actionButtonContainer { display: flex; flex-direction: row; gap: var(--fds-spacing-2); diff --git a/frontend/packages/shared/src/primitives/Primitives.module.css b/frontend/packages/shared/src/primitives/Primitives.module.css index 6c8994e973a..580abb02f5b 100644 --- a/frontend/packages/shared/src/primitives/Primitives.module.css +++ b/frontend/packages/shared/src/primitives/Primitives.module.css @@ -4,7 +4,7 @@ gap: 16px; padding-bottom: 16px; } -.buttonContainer { +.actionButtonContainer { display: flex; flex-direction: row; gap: 12px; diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css index 4b68a52d424..8a783464477 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css @@ -13,7 +13,7 @@ margin: var(--margin-top) var(--margin-horizontal) 0 var(--margin-horizontal); } -.buttonContainer { +.actionButtonContainer { display: flex; justify-content: center; padding-top: var(--fds-spacing-5); diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css index d9d4486293f..aedb90e17c3 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css @@ -10,7 +10,7 @@ margin-top: 10px; } -.buttonContainer { +.actionButtonContainer { display: flex; justify-content: flex-end; margin-top: 20px; diff --git a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css index 4b68a52d424..8a783464477 100644 --- a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css +++ b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css @@ -13,7 +13,7 @@ margin: var(--margin-top) var(--margin-horizontal) 0 var(--margin-horizontal); } -.buttonContainer { +.actionButtonContainer { display: flex; justify-content: center; padding-top: var(--fds-spacing-5); From cf66136054bc6465de445ccf864beaf1116d8ed7 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 23 Apr 2024 12:03:05 +0200 Subject: [PATCH 06/72] Add StudioTableWithPagination to Storybook --- .../StudioTableWithPagination.mdx | 15 ++++++ .../StudioTableWithPagination.stories.tsx | 46 +++++++++++++++++++ .../StudioTableWithPagination.tsx | 34 ++++++++++++++ .../StudioTableWithPagination/index.ts | 1 + 4 files changed, 96 insertions(+) create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx new file mode 100644 index 00000000000..dd1c95073a7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx @@ -0,0 +1,15 @@ +import { Canvas, Meta } from '@storybook/blocks'; +import { Heading, Paragraph } from '@digdir/design-system-react'; +import * as StudioTableWithPaginationStories from './StudioTableWithPagination.stories'; + + + + + StudioTableWithPagination + + + StudioTableWithPagination is a extension of the digdir-designsystemet `Table` and `Pagination` components, that simplifies the + combination of the two components + + + diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx new file mode 100644 index 00000000000..f0611e172d3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import type { Meta, StoryFn } from '@storybook/react'; +import { StudioTableWithPagination } from './StudioTableWithPagination'; +import { Button } from '@digdir/design-system-react'; +import { StarFillIcon } from '@navikt/aksel-icons'; + +type Story = StoryFn; + +const meta: Meta = { + title: 'Studio/StudioTableWithPagination', + component: StudioTableWithPagination, + argTypes: { + pagination: { + control: 'radio', + options: ['true', 'false'], + }, + }, +}; +export const Preview: Story = (args): React.ReactElement => ; + +const starButton = () => ( + +); + +const rows = [ + [starButton(), 'Lila Patel', 'Software Engineer', 'Pending'], + [starButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved'], + [starButton(), 'Olivia Chen', 'Data Analyst', 'Pending'], + [starButton(), 'Noah Adebayo', 'UX Designer', 'Approved'], + [starButton(), 'Sophia Ivanov', 'Product Manager', 'Pending'], + [starButton(), 'William Torres', 'Sales Representative', 'Approved'], + [starButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending'], + [starButton(), 'James Kim', 'Financial Analyst', 'Approved'], + [starButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending'], +]; + +const columns = ['', 'Name', 'Role', 'Status']; + +Preview.args = { + columns: columns, + rows: rows, + size: 'small', +}; +export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx new file mode 100644 index 00000000000..d23a167185b --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -0,0 +1,34 @@ +import { Table } from '@digdir/design-system-react'; +import React, { forwardRef } from 'react'; + +type StudioTableWithPaginationProps = { + columns: string[]; + rows: React.ReactNode[][]; + size: 'small' | 'medium' | 'large'; +}; + +export const StudioTableWithPagination = forwardRef< + HTMLTableElement, + StudioTableWithPaginationProps +>(({ columns, rows, size = 'medium' }, ref) => { + return ( + + + + {columns.map((cell, i) => ( + {cell} + ))} + + + + {rows.map((row, i) => ( + + {row.map((cell, i) => ( + {cell} + ))} + + ))} + +
+ ); +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts new file mode 100644 index 00000000000..9d11bc50d89 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts @@ -0,0 +1 @@ +export { StudioTableWithPagination } from './StudioTableWithPagination'; From c486a9f6a23f2666f836de5c9a2d7fb51e950c2e Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 23 Apr 2024 12:40:45 +0200 Subject: [PATCH 07/72] Make pagination work --- .../StudioTableWithPagination.module.css | 2 + .../StudioTableWithPagination.tsx | 69 ++++++++++++++----- 2 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css new file mode 100644 index 00000000000..27bdc0eb432 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -0,0 +1,2 @@ +.component { +} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index d23a167185b..ad83c62f397 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,34 +1,65 @@ -import { Table } from '@digdir/design-system-react'; -import React, { forwardRef } from 'react'; +import { Pagination, Table } from '@digdir/design-system-react'; +import React, { forwardRef, useEffect, useState } from 'react'; +import classes from './StudioTableWithPagination.module.css'; type StudioTableWithPaginationProps = { columns: string[]; rows: React.ReactNode[][]; size: 'small' | 'medium' | 'large'; + rowsPerPage: number; }; export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, size = 'medium' }, ref) => { +>(({ columns, rows, size = 'medium', rowsPerPage = 3 }, ref) => { + const [currentPage, setCurrentPage] = useState(1); + const [currentRows, setCurrentRows] = useState(rows); + + let totalPages = 1; + if (rowsPerPage > 0) { + totalPages = Math.ceil(rows.length / rowsPerPage); + } + + useEffect(() => { + if (rowsPerPage > 0) { + const startIndex = (currentPage - 1) * rowsPerPage; + const endIndex = startIndex + rowsPerPage; + setCurrentRows(rows.slice(startIndex, endIndex)); + } + }, [currentPage, rowsPerPage, rows]); + return ( - - - - {columns.map((cell, i) => ( - {cell} - ))} - - - - {rows.map((row, i) => ( - - {row.map((cell, i) => ( - {cell} +
+
+ + + {columns.map((cell, i) => ( + {cell} ))} - ))} - -
+ + + {currentRows.map((row, i) => ( + + {row.map((cell, i) => ( + {cell} + ))} + + ))} + + + {rowsPerPage > 0 && ( + `Side ${num}`} + /> + )} + ); }); From 9666ecce36f2ff88b63ae8df13276a28d62fc11b Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 23 Apr 2024 13:08:49 +0200 Subject: [PATCH 08/72] Improve table --- .../StudioTableWithPagination.module.css | 3 +- .../StudioTableWithPagination.stories.tsx | 37 ++++++++++++------- .../StudioTableWithPagination.tsx | 5 ++- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css index 27bdc0eb432..6b0e33ae967 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -1,2 +1,3 @@ -.component { +.pagination { + margin-top: 16px; } diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index f0611e172d3..f79fd97b114 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -1,8 +1,9 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { StudioTableWithPagination } from './StudioTableWithPagination'; -import { Button } from '@digdir/design-system-react'; -import { StarFillIcon } from '@navikt/aksel-icons'; +import { Button, Link } from '@digdir/design-system-react'; +import { BarChartFillIcon, PersonFillIcon } from '@navikt/aksel-icons'; +import cn from 'classnames'; type Story = StoryFn; @@ -18,25 +19,33 @@ const meta: Meta = { }; export const Preview: Story = (args): React.ReactElement => ; -const starButton = () => ( +const iconButton = (icon) => ( ); +const link = () => Link; + const rows = [ - [starButton(), 'Lila Patel', 'Software Engineer', 'Pending'], - [starButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved'], - [starButton(), 'Olivia Chen', 'Data Analyst', 'Pending'], - [starButton(), 'Noah Adebayo', 'UX Designer', 'Approved'], - [starButton(), 'Sophia Ivanov', 'Product Manager', 'Pending'], - [starButton(), 'William Torres', 'Sales Representative', 'Approved'], - [starButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending'], - [starButton(), 'James Kim', 'Financial Analyst', 'Approved'], - [starButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending'], + [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], + [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], + [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], + [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], + [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], + [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], + [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], + [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], + [ + iconButton(), + 'Mia Sánchez', + 'Customer Support Specialist', + 'Pending', + link(), + ], ]; -const columns = ['', 'Name', 'Role', 'Status']; +const columns = ['', 'Name', 'Role', 'Status', '']; Preview.args = { columns: columns, diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index ad83c62f397..fa18f6cf608 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -16,7 +16,7 @@ export const StudioTableWithPagination = forwardRef< const [currentPage, setCurrentPage] = useState(1); const [currentRows, setCurrentRows] = useState(rows); - let totalPages = 1; + let totalPages = 0; if (rowsPerPage > 0) { totalPages = Math.ceil(rows.length / rowsPerPage); } @@ -31,7 +31,7 @@ export const StudioTableWithPagination = forwardRef< return (
- +
{columns.map((cell, i) => ( @@ -51,6 +51,7 @@ export const StudioTableWithPagination = forwardRef<
{rowsPerPage > 0 && ( Date: Tue, 23 Apr 2024 15:59:51 +0200 Subject: [PATCH 09/72] Make a separate component out of StudioTable --- .../StudioTable/StudioTable.module.css | 3 + .../components/StudioTable/StudioTable.tsx | 31 ++++++ .../src/components/StudioTable/index.ts | 1 + .../StudioTableWithPagination.mdx | 3 +- .../StudioTableWithPagination.module.css | 4 +- .../StudioTableWithPagination.stories.tsx | 34 +------ .../StudioTableWithPagination.tsx | 96 ++++++++++--------- .../StudioTableWithPagination/mockData.tsx | 25 +++++ 8 files changed, 119 insertions(+), 78 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css create mode 100644 frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTable/index.ts create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css new file mode 100644 index 00000000000..a62a8e028f7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css @@ -0,0 +1,3 @@ +.table { + width: 100%; +} diff --git a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx new file mode 100644 index 00000000000..e761c4ea46a --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx @@ -0,0 +1,31 @@ +import classes from './StudioTable.module.css'; +import { Table } from '@digdir/design-system-react'; +import React from 'react'; + +type StudioTableProps = { + size: 'small' | 'medium' | 'large'; + columns: string[]; + rows: React.ReactNode[][]; + width?: string; +}; + +export const StudioTable: React.FC = ({ size, columns, rows, width }) => { + return ( + + + + {columns?.map((cell, i) => {cell})} + + + + {rows?.map((row, i) => ( + + {row.map((cell, i) => ( + {cell} + ))} + + ))} + +
+ ); +}; diff --git a/frontend/libs/studio-components/src/components/StudioTable/index.ts b/frontend/libs/studio-components/src/components/StudioTable/index.ts new file mode 100644 index 00000000000..2d94aa57cd9 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTable/index.ts @@ -0,0 +1 @@ +export { StudioTable } from './StudioTable'; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx index dd1c95073a7..76cf4001940 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx @@ -8,8 +8,7 @@ import * as StudioTableWithPaginationStories from './StudioTableWithPagination.s StudioTableWithPagination - StudioTableWithPagination is a extension of the digdir-designsystemet `Table` and `Pagination` components, that simplifies the - combination of the two components + StudioTableWithPagination brings together Digdir Designsystemet's `Table`, `Pagination` and `NativeSelect` components. diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css index 6b0e33ae967..6cfcea74ed4 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -1,3 +1,5 @@ -.pagination { +.paginationContainer { + display: flex; + justify-content: space-between; margin-top: 16px; } diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index f79fd97b114..2a55bbe0e34 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -1,9 +1,7 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { StudioTableWithPagination } from './StudioTableWithPagination'; -import { Button, Link } from '@digdir/design-system-react'; -import { BarChartFillIcon, PersonFillIcon } from '@navikt/aksel-icons'; -import cn from 'classnames'; +import { columns, rows } from './mockData'; type Story = StoryFn; @@ -19,37 +17,11 @@ const meta: Meta = { }; export const Preview: Story = (args): React.ReactElement => ; -const iconButton = (icon) => ( - -); - -const link = () => Link; - -const rows = [ - [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], - [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], - [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], - [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], - [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], - [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], - [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], - [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], - [ - iconButton(), - 'Mia Sánchez', - 'Customer Support Specialist', - 'Pending', - link(), - ], -]; - -const columns = ['', 'Name', 'Role', 'Status', '']; - Preview.args = { columns: columns, rows: rows, size: 'small', + initialRowPerPage: 5, + width: '700px', }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index fa18f6cf608..81dc90bed40 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,66 +1,74 @@ -import { Pagination, Table } from '@digdir/design-system-react'; +import { NativeSelect, Pagination } from '@digdir/design-system-react'; import React, { forwardRef, useEffect, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; +import { StudioTable } from '../StudioTable'; type StudioTableWithPaginationProps = { columns: string[]; rows: React.ReactNode[][]; size: 'small' | 'medium' | 'large'; - rowsPerPage: number; + initialRowPerPage: number; + width?: string; }; export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, size = 'medium', rowsPerPage = 3 }, ref) => { +>(({ columns, rows, size = 'medium', initialRowPerPage = 0, width }, ref) => { const [currentPage, setCurrentPage] = useState(1); const [currentRows, setCurrentRows] = useState(rows); - - let totalPages = 0; - if (rowsPerPage > 0) { - totalPages = Math.ceil(rows.length / rowsPerPage); - } + const [rowPerPage, setRowPerPage] = useState(initialRowPerPage); + const [totalPages, setTotalPages] = useState(null); useEffect(() => { - if (rowsPerPage > 0) { - const startIndex = (currentPage - 1) * rowsPerPage; - const endIndex = startIndex + rowsPerPage; + if (rowPerPage > 0) { + const startIndex = (currentPage - 1) * rowPerPage; + const endIndex = startIndex + rowPerPage; setCurrentRows(rows.slice(startIndex, endIndex)); + setTotalPages(Math.ceil(rows.length / rowPerPage)); + console.log(Math.round(rows.length / rowPerPage)); + } + }, [currentPage, rowPerPage, rows]); + + useEffect(() => { + if (currentRows.length === 0) setCurrentPage(1); + }, [currentRows]); + + const handleRowPerPage = (e) => { + if (e.target.value === 'initialValue') { + setRowPerPage(initialRowPerPage); + } else { + setRowPerPage(Number(e.target.value)); } - }, [currentPage, rowsPerPage, rows]); + }; return ( -
- - - - {columns.map((cell, i) => ( - {cell} - ))} - - - - {currentRows.map((row, i) => ( - - {row.map((cell, i) => ( - {cell} - ))} - - ))} - -
- {rowsPerPage > 0 && ( - `Side ${num}`} - /> - )} -
+ <> + +
+ {initialRowPerPage > 0 && ( + + + + + + + + + )} + {totalPages > 1 && ( + `Side ${num}`} + hideLabels + /> + )} +
+ ); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx new file mode 100644 index 00000000000..4ec2398624b --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -0,0 +1,25 @@ +import { Button, Link } from '@digdir/design-system-react'; +import { FaceSmileFillIcon, FaceSmileIcon, StarFillIcon, StarIcon } from '@navikt/aksel-icons'; +import React from 'react'; + +const iconButton = (icon) => ( + +); + +const link = () => Link; + +export const rows = [ + [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], + [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], + [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], + [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], + [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], + [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], + [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], + [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], + [iconButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending', link()], +]; + +export const columns = ['', 'Name', 'Role', 'Status', '']; From d8bbb9e7f965d5b959194c830d0459ab5b50681f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 11:02:49 +0200 Subject: [PATCH 10/72] Add sorting --- .../components/StudioTable/StudioTable.tsx | 15 +++- .../SelectRowsPerPage.tsx | 28 +++++++ .../StudioTableWithPagination.module.css | 22 ++++- .../StudioTableWithPagination.stories.tsx | 2 +- .../StudioTableWithPagination.tsx | 84 ++++++++----------- .../StudioTableWithPagination/mockData.tsx | 9 ++ .../StudioTableWithPagination/utils.tsx | 24 ++++++ .../src/hooks/useSortedRows.tsx | 39 +++++++++ 8 files changed, 172 insertions(+), 51 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx create mode 100644 frontend/libs/studio-components/src/hooks/useSortedRows.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx index e761c4ea46a..63482343142 100644 --- a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx @@ -7,14 +7,25 @@ type StudioTableProps = { columns: string[]; rows: React.ReactNode[][]; width?: string; + handleSorting: (number) => void; }; -export const StudioTable: React.FC = ({ size, columns, rows, width }) => { +export const StudioTable: React.FC = ({ + size, + columns, + rows, + width, + handleSorting, +}) => { return ( - {columns?.map((cell, i) => {cell})} + {columns?.map((cell, i) => ( + handleSorting(i)}> + {cell} + + ))} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx new file mode 100644 index 00000000000..53e7d2c09cc --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx @@ -0,0 +1,28 @@ +import classes from './StudioTableWithPagination.module.css'; +import { Label, NativeSelect } from '@digdir/design-system-react'; +import React from 'react'; +import { getLabelSize } from './utils'; + +export const SelectRowsPerPage = ({ setRowPerPage, size }) => { + const labelSize = getLabelSize(size); + + return ( +
+ setRowPerPage(Number(e.target.value))} + size={size} + > + + + + + + + +
+ ); +}; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css index 6cfcea74ed4..b7123d1916a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -1,5 +1,25 @@ .paginationContainer { display: flex; justify-content: space-between; - margin-top: 16px; + margin-top: var(--fds-spacing-4); +} + +.pagination { + * { + margin-right: 0; + } +} + +.selectorContainer { + display: flex; + gap: var(--fds-spacing-3); + margin: auto 0; +} + +.selector { + min-width: 70px; +} + +.label { + margin: auto 0; } diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index 2a55bbe0e34..cea078b9640 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -22,6 +22,6 @@ Preview.args = { rows: rows, size: 'small', initialRowPerPage: 5, - width: '700px', + width: '900px', }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 81dc90bed40..715b7ba927a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,7 +1,10 @@ -import { NativeSelect, Pagination } from '@digdir/design-system-react'; +import { Pagination } from '@digdir/design-system-react'; import React, { forwardRef, useEffect, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; import { StudioTable } from '../StudioTable'; +import { calcCurrentRows } from './utils'; +import { useSortedRows } from '../../hooks/useSortedRows'; +import { SelectRowsPerPage } from './SelectRowsPerPage'; type StudioTableWithPaginationProps = { columns: string[]; @@ -14,61 +17,48 @@ type StudioTableWithPaginationProps = { export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, size = 'medium', initialRowPerPage = 0, width }, ref) => { +>(({ columns, rows, size = 'medium', initialRowPerPage = 5, width }, ref) => { const [currentPage, setCurrentPage] = useState(1); - const [currentRows, setCurrentRows] = useState(rows); const [rowPerPage, setRowPerPage] = useState(initialRowPerPage); - const [totalPages, setTotalPages] = useState(null); + const { sortedRows, handleSorting } = useSortedRows(rows); - useEffect(() => { - if (rowPerPage > 0) { - const startIndex = (currentPage - 1) * rowPerPage; - const endIndex = startIndex + rowPerPage; - setCurrentRows(rows.slice(startIndex, endIndex)); - setTotalPages(Math.ceil(rows.length / rowPerPage)); - console.log(Math.round(rows.length / rowPerPage)); - } - }, [currentPage, rowPerPage, rows]); + const totalPages = Math.ceil(sortedRows.length / rowPerPage); + const currentRows = calcCurrentRows(currentPage, rowPerPage, sortedRows); useEffect(() => { - if (currentRows.length === 0) setCurrentPage(1); - }, [currentRows]); - - const handleRowPerPage = (e) => { - if (e.target.value === 'initialValue') { - setRowPerPage(initialRowPerPage); - } else { - setRowPerPage(Number(e.target.value)); + if (currentRows.length === 0 && currentPage > 1) { + setCurrentPage(1); } - }; + }, [currentRows, currentPage]); return ( <> - -
- {initialRowPerPage > 0 && ( - - - - - - - - - )} - {totalPages > 1 && ( - `Side ${num}`} - hideLabels - /> - )} -
+ + {initialRowPerPage > 0 && ( +
+ + {totalPages > 1 && ( + `Side ${num}`} + hideLabels + compact + /> + )} +
+ )} ); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index 4ec2398624b..019ddedc988 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -20,6 +20,15 @@ export const rows = [ [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], [iconButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending', link()], + [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], + [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], + [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], + [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], + [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], + [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], + [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], + [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], + [iconButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending', link()], ]; export const columns = ['', 'Name', 'Role', 'Status', '']; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx new file mode 100644 index 00000000000..99fe84339d4 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx @@ -0,0 +1,24 @@ +export const getLabelSize = (size: string) => { + let labelSize; + switch (size) { + case 'xsmall': + labelSize = 'xsmall'; + break; + case 'small': + labelSize = 'xsmall'; + break; + case 'large': + labelSize = 'medium'; + break; + default: + labelSize = 'small'; + } + + return labelSize; +}; + +export const calcCurrentRows = (currentPage, rowPerPage, rows) => { + const startIndex = (currentPage - 1) * rowPerPage; + const endIndex = startIndex + rowPerPage; + return rows.slice(startIndex, endIndex); +}; diff --git a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx new file mode 100644 index 00000000000..2e422bcc106 --- /dev/null +++ b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx @@ -0,0 +1,39 @@ +import { useState, useCallback } from 'react'; + +export const useSortedRows = (rows, initialSortColumn = null, initialSortDirection = 'asc') => { + const [sortColumn, setSortColumn] = useState(initialSortColumn); + const [sortDirection, setSortDirection] = useState(initialSortDirection); + + const handleSorting = useCallback( + (columnIndex) => { + if (sortColumn === columnIndex) { + setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); + } else { + setSortColumn(columnIndex); + setSortDirection('asc'); + } + }, + [sortColumn], + ); + + const sortedRows = useCallback( + () => + sortColumn !== null + ? [...rows].sort((a, b) => { + const columnA = a[sortColumn]; + const columnB = b[sortColumn]; + if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; + if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; + return 0; + }) + : rows, + [rows, sortColumn, sortDirection], + ); + + return { + sortedRows: sortedRows(), + sortColumn, + sortDirection, + handleSorting, + }; +}; From a770e67b7f1e4673ee75a4ec9e010d22554d9809 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 11:27:48 +0200 Subject: [PATCH 11/72] Clean up code --- .../StudioTable/StudioTable.module.css | 3 -- .../src/components/StudioTable/index.ts | 1 - .../StudioTable.tsx | 15 +++------- .../StudioTableWithPagination.module.css | 4 +++ .../StudioTableWithPagination.stories.tsx | 3 +- .../StudioTableWithPagination.tsx | 28 ++++++++----------- 6 files changed, 20 insertions(+), 34 deletions(-) delete mode 100644 frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css delete mode 100644 frontend/libs/studio-components/src/components/StudioTable/index.ts rename frontend/libs/studio-components/src/components/{StudioTable => StudioTableWithPagination}/StudioTable.tsx (72%) diff --git a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css b/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css deleted file mode 100644 index a62a8e028f7..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.table { - width: 100%; -} diff --git a/frontend/libs/studio-components/src/components/StudioTable/index.ts b/frontend/libs/studio-components/src/components/StudioTable/index.ts deleted file mode 100644 index 2d94aa57cd9..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioTable } from './StudioTable'; diff --git a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx similarity index 72% rename from frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx rename to frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index 63482343142..fd69a189bea 100644 --- a/frontend/libs/studio-components/src/components/StudioTable/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -1,4 +1,4 @@ -import classes from './StudioTable.module.css'; +import classes from './StudioTableWithPagination.module.css'; import { Table } from '@digdir/design-system-react'; import React from 'react'; @@ -6,19 +6,12 @@ type StudioTableProps = { size: 'small' | 'medium' | 'large'; columns: string[]; rows: React.ReactNode[][]; - width?: string; - handleSorting: (number) => void; + handleSorting?: (number) => void; }; -export const StudioTable: React.FC = ({ - size, - columns, - rows, - width, - handleSorting, -}) => { +export const StudioTable: React.FC = ({ size, columns, rows, handleSorting }) => { return ( -
+
{columns?.map((cell, i) => ( diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css index b7123d1916a..18497fd76c1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -1,3 +1,7 @@ +.table { + width: 100%; +} + .paginationContainer { display: flex; justify-content: space-between; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index cea078b9640..bc9ce8bfd02 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -21,7 +21,6 @@ Preview.args = { columns: columns, rows: rows, size: 'small', - initialRowPerPage: 5, - width: '900px', + initialRowsPerPage: 5, }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 715b7ba927a..6a0a5226017 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,7 +1,7 @@ import { Pagination } from '@digdir/design-system-react'; import React, { forwardRef, useEffect, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; -import { StudioTable } from '../StudioTable'; +import { StudioTable } from './StudioTable'; import { calcCurrentRows } from './utils'; import { useSortedRows } from '../../hooks/useSortedRows'; import { SelectRowsPerPage } from './SelectRowsPerPage'; @@ -10,39 +10,33 @@ type StudioTableWithPaginationProps = { columns: string[]; rows: React.ReactNode[][]; size: 'small' | 'medium' | 'large'; - initialRowPerPage: number; - width?: string; + initialRowsPerPage: number; }; export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, size = 'medium', initialRowPerPage = 5, width }, ref) => { +>(({ columns, rows, size = 'medium', initialRowsPerPage = 5 }, ref) => { const [currentPage, setCurrentPage] = useState(1); - const [rowPerPage, setRowPerPage] = useState(initialRowPerPage); + const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); const { sortedRows, handleSorting } = useSortedRows(rows); - const totalPages = Math.ceil(sortedRows.length / rowPerPage); - const currentRows = calcCurrentRows(currentPage, rowPerPage, sortedRows); + const totalPages = Math.ceil(sortedRows.length / rowsPerPage); + const currentRows = calcCurrentRows(currentPage, rowsPerPage, sortedRows); useEffect(() => { - if (currentRows.length === 0 && currentPage > 1) { + // If user is out of bounds, move them to page 1 + if (currentRows.length === 0) { setCurrentPage(1); } }, [currentRows, currentPage]); return ( <> - - {initialRowPerPage > 0 && ( + + {initialRowsPerPage > 0 && (
- + {totalPages > 1 && ( Date: Wed, 24 Apr 2024 12:31:59 +0200 Subject: [PATCH 12/72] Add numerical values to mockData --- .../StudioTableWithPagination/StudioTable.tsx | 17 +++- .../StudioTableWithPagination.stories.tsx | 1 + .../StudioTableWithPagination.tsx | 11 ++- .../StudioTableWithPagination/mockData.tsx | 80 ++++++++++++++----- .../studio-components/src/components/index.ts | 1 + 5 files changed, 86 insertions(+), 24 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index fd69a189bea..dc438ce5a34 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -6,16 +6,27 @@ type StudioTableProps = { size: 'small' | 'medium' | 'large'; columns: string[]; rows: React.ReactNode[][]; - handleSorting?: (number) => void; + sortable: boolean; + handleSorting?: (arg: number) => void; }; -export const StudioTable: React.FC = ({ size, columns, rows, handleSorting }) => { +export const StudioTable: React.FC = ({ + size, + columns, + rows, + sortable, + handleSorting, +}) => { return (
{columns?.map((cell, i) => ( - handleSorting(i)}> + handleSorting(i)} + > {cell} ))} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index bc9ce8bfd02..2570f0208a1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -22,5 +22,6 @@ Preview.args = { rows: rows, size: 'small', initialRowsPerPage: 5, + sortable: true, }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 6a0a5226017..0530b245c85 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -9,6 +9,7 @@ import { SelectRowsPerPage } from './SelectRowsPerPage'; type StudioTableWithPaginationProps = { columns: string[]; rows: React.ReactNode[][]; + sortable: boolean; size: 'small' | 'medium' | 'large'; initialRowsPerPage: number; }; @@ -16,7 +17,7 @@ type StudioTableWithPaginationProps = { export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, size = 'medium', initialRowsPerPage = 5 }, ref) => { +>(({ columns, rows, sortable = true, size = 'medium', initialRowsPerPage = 5 }, ref) => { const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); const { sortedRows, handleSorting } = useSortedRows(rows); @@ -33,7 +34,13 @@ export const StudioTableWithPagination = forwardRef< return ( <> - + {initialRowsPerPage > 0 && (
diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index 019ddedc988..ce4a47cf4c9 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -11,24 +11,66 @@ const iconButton = (icon) => ( const link = () => Link; export const rows = [ - [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], - [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], - [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], - [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], - [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], - [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], - [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], - [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], - [iconButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending', link()], - [iconButton(), 'Lila Patel', 'Software Engineer', 'Pending', link()], - [iconButton(), 'Ethan Nakamura', 'Marketing Specialist', 'Approved', link()], - [iconButton(), 'Olivia Chen', 'Data Analyst', 'Pending', link()], - [iconButton(), 'Noah Adebayo', 'UX Designer', 'Approved', link()], - [iconButton(), 'Sophia Ivanov', 'Product Manager', 'Pending', link()], - [iconButton(), 'William Torres', 'Sales Representative', 'Approved', link()], - [iconButton(), 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], - [iconButton(), 'James Kim', 'Financial Analyst', 'Approved', link()], - [iconButton(), 'Mia Sánchez', 'Customer Support Specialist', 'Pending', link()], + [iconButton(), '123456', 'Lila Patel', 'Software Engineer', 'Pending', link()], + [ + iconButton(), + '234567', + 'Ethan Nakamura', + 'Marketing Specialist', + 'Approved', + link(), + ], + [iconButton(), '345678', 'Olivia Chen', 'Data Analyst', 'Pending', link()], + [iconButton(), '456789', 'Noah Adebayo', 'UX Designer', 'Approved', link()], + [iconButton(), '567890', 'Sophia Ivanov', 'Product Manager', 'Pending', link()], + [ + iconButton(), + '678901', + 'William Torres', + 'Sales Representative', + 'Approved', + link(), + ], + [iconButton(), '789012', 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], + [iconButton(), '890123', 'James Kim', 'Financial Analyst', 'Approved', link()], + [ + iconButton(), + '901234', + 'Mia Sánchez', + 'Customer Support Specialist', + 'Pending', + link(), + ], + [iconButton(), '012345', 'Lila Patel', 'Software Engineer', 'Pending', link()], + [ + iconButton(), + '123450', + 'Ethan Nakamura', + 'Marketing Specialist', + 'Approved', + link(), + ], + [iconButton(), '234501', 'Olivia Chen', 'Data Analyst', 'Pending', link()], + [iconButton(), '345012', 'Noah Adebayo', 'UX Designer', 'Approved', link()], + [iconButton(), '450123', 'Sophia Ivanov', 'Product Manager', 'Pending', link()], + [ + iconButton(), + '501234', + 'William Torres', + 'Sales Representative', + 'Approved', + link(), + ], + [iconButton(), '012345', 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], + [iconButton(), '123450', 'James Kim', 'Financial Analyst', 'Approved', link()], + [ + iconButton(), + '234501', + 'Mia Sánchez', + 'Customer Support Specialist', + 'Pending', + link(), + ], ]; -export const columns = ['', 'Name', 'Role', 'Status', '']; +export const columns = ['', 'Employee ID', 'Name', 'Role', 'Status', '']; diff --git a/frontend/libs/studio-components/src/components/index.ts b/frontend/libs/studio-components/src/components/index.ts index ac0f557a681..48bd31ede73 100644 --- a/frontend/libs/studio-components/src/components/index.ts +++ b/frontend/libs/studio-components/src/components/index.ts @@ -17,6 +17,7 @@ export * from './StudioPageSpinner'; export * from './StudioProperty'; export * from './StudioSectionHeader'; export * from './StudioSpinner'; +export * from './StudioTableWithPagination'; export * from './StudioTextarea'; export * from './StudioTextfield'; export * from './StudioToggleableTextfield'; From 903ca420acd91582389636023a714745221fb112 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 14:29:30 +0200 Subject: [PATCH 13/72] Begin implementing table in dashboard --- .../components/RepoList/ActionLinks.tsx | 6 +- .../components/RepoList/FavoriteButton.tsx | 25 ++++++++ .../RepoList/NewRepoList.module.css | 10 +++ .../components/RepoList/NewRepoList.tsx | 47 ++++++++++---- .../components/RepoList/RepoList.module.css | 62 ++++++++----------- .../components/RepoList/RepoList.tsx | 21 +------ .../StudioTableWithPagination.mdx | 3 +- .../StudioTableWithPagination.module.css | 1 + .../StudioTableWithPagination.tsx | 6 +- 9 files changed, 106 insertions(+), 75 deletions(-) create mode 100644 frontend/dashboard/components/RepoList/FavoriteButton.tsx create mode 100644 frontend/dashboard/components/RepoList/NewRepoList.module.css diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx index 10bee4115fe..ddfbc5cc881 100644 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -1,6 +1,6 @@ import { Button, DropdownMenu } from '@digdir/design-system-react'; import React from 'react'; -import classes from './RepoList.module.css'; +import classes from './NewRepoList.module.css'; import cn from 'classnames'; import { ExternalLinkIcon, @@ -26,7 +26,7 @@ export const ActionLinks: React.FC = ({ repo }) => { const editTextKey = t(isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service'); return ( - <> +
); }; diff --git a/frontend/dashboard/components/RepoList/FavoriteButton.tsx b/frontend/dashboard/components/RepoList/FavoriteButton.tsx new file mode 100644 index 00000000000..71a168baeb6 --- /dev/null +++ b/frontend/dashboard/components/RepoList/FavoriteButton.tsx @@ -0,0 +1,25 @@ +import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; +import classes from './RepoList.module.css'; +import { Button } from '@digdir/design-system-react'; +import React from 'react'; +import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; + +export const FavoriteButton = ({ repo }) => { + const { mutate: setStarredRepo } = useSetStarredRepoMutation(); + const { mutate: unsetStarredRepo } = useUnsetStarredRepoMutation(); + + const handleToggleFav = (event: React.MouseEvent) => { + event.stopPropagation(); + if (repo.hasStarred) { + unsetStarredRepo(repo); + } else { + setStarredRepo(repo); + } + }; + + return ( + + ); +}; diff --git a/frontend/dashboard/components/RepoList/NewRepoList.module.css b/frontend/dashboard/components/RepoList/NewRepoList.module.css new file mode 100644 index 00000000000..917c05f0de8 --- /dev/null +++ b/frontend/dashboard/components/RepoList/NewRepoList.module.css @@ -0,0 +1,10 @@ +.actionLinks { + display: flex; + justify-content: flex-end; +} + +.repoIcon { + font-size: 2rem; + padding-right: 7px; + padding-bottom: 5px; +} diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx index 4f4adcae187..54294e42383 100644 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -1,19 +1,40 @@ -import { Table } from '@digdir/design-system-react'; import React from 'react'; -import classes from './RepoList.module.css'; -import { BodyRow } from './BodyRow'; +import { StudioTableWithPagination } from '@studio/components/src/components/StudioTableWithPagination'; +import { DateUtils } from '@studio/pure-functions'; import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; -import { HeaderRow } from './HeaderRow'; +import { ActionLinks } from './ActionLinks'; +import { FavoriteButton } from './FavoriteButton'; + +type NewRepoListProps = { + repos: RepositoryWithStarred[]; +}; + +export const NewRepoList = ({ repos }: NewRepoListProps): React.ReactElement => { + const rows = []; + + repos?.map((repo) => { + const row = []; + + row.push( + , + repo.name, + repo.owner.full_name || repo.owner.login, + DateUtils.formatDateDDMMYYYY(repo.updated_at), + repo.description, + , + ); + + rows.push(row); + }); + + const columns = ['', 'Navn', 'Opprettet av', 'Sist Endret', 'Beskrivelse', '']; -export const NewRepoList = ({ repos }) => { return ( -
- - - - - {repos?.map((repo: RepositoryWithStarred) => )} - -
+ ); }; diff --git a/frontend/dashboard/components/RepoList/RepoList.module.css b/frontend/dashboard/components/RepoList/RepoList.module.css index 3b7ba9c1eeb..4006659a806 100644 --- a/frontend/dashboard/components/RepoList/RepoList.module.css +++ b/frontend/dashboard/components/RepoList/RepoList.module.css @@ -1,65 +1,53 @@ -.repoList { - width: 100%; - - > * > * > * { - border-color: var(--fds-semantic-border-action-second-subtle); - } +.actionLink { + align-items: center; + display: flex; + margin-right: 1rem; + min-width: 40px; +} - /*To be removed*/ - margin-bottom: 100px; +.actionLink:hover { + text-decoration: none; } -.bodyRow { - cursor: pointer; +.actionLink:hover span { + text-decoration: underline; } -.favoriteIcon { - font-size: 26px; - color: #000000; +.dropdownIcon { + font-size: 2rem; } -.favoriteCell { - width: 40px; +.editLink { + color: #165db8; } -.actionButtonContainer { - display: flex; - justify-content: flex-end; +.editLink:hover { + color: #165db8; } -.repoButton { - margin-right: 9px; +.favoriteIcon { + font-size: 26px; + color: #000000; } -.repoIcon { +.linkIcon { font-size: 2rem; - color: #57823d; - padding-right: 7px; - padding-bottom: 5px; + height: 32px; + width: 32px; } -.repoIcon:hover { +.repoLink { color: #57823d; } -.dropdownIcon, -.dropdownText { - color: var(--fds-semantic-text-neutral-subtle); - - a { - color: var(--fds-semantic-text-neutral-subtle); - } -} - -.dropdownText:hover { - color: var(--fds-semantic-text-neutral-subtle); +.repoLink:hover { + color: #57823d; } .textWithTooltip { overflow: hidden; text-overflow: ellipsis; } - .columnHeader { background-color: white !important; } diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 33fd0515f13..23ab9ed0a0c 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -154,21 +154,6 @@ export const RepoList = ({ headerName: t('dashboard.name'), width: 200, renderCell: TextWithTooltip, - valueGetter: (params: GridValueGetterParams) => { - const repoFullName = params.row.full_name as string; - const [org, repo] = repoFullName.split('/'); - const isDatamodelling = repoFullName.endsWith('-datamodels'); - const editUrl = getRepoEditUrl({ org, repo }); - const editTextKey = t( - isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service', - ); - - return ( -
- {repo} - - ); - }, }, { field: 'owner.created_by', @@ -214,8 +199,8 @@ export const RepoList = ({ return [ } + className={cn(classes.actionLink, classes.repoLink)} + icon={} key={`dashboard.repository${params.row.id}`} label={t('dashboard.repository_in_list', { appName: repo })} onClick={() => (window.location.href = params.row.html_url)} @@ -238,7 +223,7 @@ export const RepoList = ({ {t(editTextKey)} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx index 76cf4001940..cf6bc9fb47d 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx @@ -8,7 +8,8 @@ import * as StudioTableWithPaginationStories from './StudioTableWithPagination.s StudioTableWithPagination - StudioTableWithPagination brings together Digdir Designsystemet's `Table`, `Pagination` and `NativeSelect` components. + StudioTableWithPagination brings together Digdir Designsystemet's `Table`, `Pagination` and + `NativeSelect` components. diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css index 18497fd76c1..c895ea1411e 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css @@ -18,6 +18,7 @@ display: flex; gap: var(--fds-spacing-3); margin: auto 0; + margin-bottom: 100px; } .selector { diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 0530b245c85..b69d2b3b7ac 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -9,9 +9,9 @@ import { SelectRowsPerPage } from './SelectRowsPerPage'; type StudioTableWithPaginationProps = { columns: string[]; rows: React.ReactNode[][]; - sortable: boolean; - size: 'small' | 'medium' | 'large'; - initialRowsPerPage: number; + sortable?: boolean; + size?: 'small' | 'medium' | 'large'; + initialRowsPerPage?: number; }; export const StudioTableWithPagination = forwardRef< From a17c739e6157b1471992a760c1ef006f4a0c226f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 14:51:43 +0200 Subject: [PATCH 14/72] Remove unused files --- .../components/RepoList/ActionLinks.tsx | 6 +- .../dashboard/components/RepoList/BodyRow.tsx | 60 ------------------- .../components/RepoList/FavoriteButton.tsx | 7 ++- .../components/RepoList/HeaderRow.tsx | 18 ------ 4 files changed, 9 insertions(+), 82 deletions(-) delete mode 100644 frontend/dashboard/components/RepoList/BodyRow.tsx delete mode 100644 frontend/dashboard/components/RepoList/HeaderRow.tsx diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx index ddfbc5cc881..96b69365a95 100644 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -12,11 +12,11 @@ import { useTranslation } from 'react-i18next'; import { getRepoEditUrl } from '../../utils/urlUtils'; import { Repository } from 'app-shared/types/Repository'; -interface ActionLinksProps { +type ActionLinksProps = { repo: Repository; -} +}; -export const ActionLinks: React.FC = ({ repo }) => { +export const ActionLinks = ({ repo }: ActionLinksProps): React.ReactElement => { const { t } = useTranslation(); const repoFullName = repo.full_name as string; diff --git a/frontend/dashboard/components/RepoList/BodyRow.tsx b/frontend/dashboard/components/RepoList/BodyRow.tsx deleted file mode 100644 index 038949a223e..00000000000 --- a/frontend/dashboard/components/RepoList/BodyRow.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Button, Table } from '@digdir/design-system-react'; -import React from 'react'; -import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; -import { DateUtils } from '@studio/pure-functions'; -import { ActionLinks } from './ActionLinks'; -import classes from './RepoList.module.css'; -import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; -import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; -import { getRepoEditUrl } from '../../utils/urlUtils'; -import { useTranslation } from 'react-i18next'; - -interface BodyRowProps { - repo: RepositoryWithStarred; -} - -export const BodyRow: React.FC = ({ repo }) => { - const { t } = useTranslation(); - const { mutate: setStarredRepo } = useSetStarredRepoMutation(); - const { mutate: unsetStarredRepo } = useUnsetStarredRepoMutation(); - - const handleToggleFav = (event: React.MouseEvent) => { - event.stopPropagation(); - if (repo.hasStarred) { - unsetStarredRepo(repo); - } else { - setStarredRepo(repo); - } - }; - - const repoFullName = repo.full_name as string; - const [org, repoName] = repoFullName.split('/'); - const isDatamodelling = repoFullName.endsWith('-datamodels'); - const editUrl = getRepoEditUrl({ org, repo: repoName }); - const editTextKey = t(isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service'); - - return ( - (window.location.href = editUrl)} - className={classes.bodyRow} - > - - - - {repo.name} - {repo.owner.full_name || repo.owner.login} - {DateUtils.formatDateDDMMYYYY(repo.updated_at)} - {repo.description} - - - - - ); -}; diff --git a/frontend/dashboard/components/RepoList/FavoriteButton.tsx b/frontend/dashboard/components/RepoList/FavoriteButton.tsx index 71a168baeb6..371f524d17e 100644 --- a/frontend/dashboard/components/RepoList/FavoriteButton.tsx +++ b/frontend/dashboard/components/RepoList/FavoriteButton.tsx @@ -3,8 +3,13 @@ import classes from './RepoList.module.css'; import { Button } from '@digdir/design-system-react'; import React from 'react'; import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; +import { Repository } from 'app-shared/types/Repository'; -export const FavoriteButton = ({ repo }) => { +type FavoriteButtonProps = { + repo: Repository; +}; + +export const FavoriteButton = ({ repo }: FavoriteButtonProps): React.ReactElement => { const { mutate: setStarredRepo } = useSetStarredRepoMutation(); const { mutate: unsetStarredRepo } = useUnsetStarredRepoMutation(); diff --git a/frontend/dashboard/components/RepoList/HeaderRow.tsx b/frontend/dashboard/components/RepoList/HeaderRow.tsx deleted file mode 100644 index 56565cd4da0..00000000000 --- a/frontend/dashboard/components/RepoList/HeaderRow.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Table } from '@digdir/design-system-react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import classes from './RepoList.module.css'; - -export const HeaderRow = () => { - const { t } = useTranslation(); - return ( - - - {t('dashboard.name')} - {t('dashboard.created_by')} - {t('dashboard.last_modified')} - {t('dashboard.description')} - - - ); -}; From 7cddfa01d6b0078089788165e7734a2730535eb7 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 14:54:27 +0200 Subject: [PATCH 15/72] Fix prop type for FavoriteButton --- frontend/dashboard/components/RepoList/ActionLinks.tsx | 2 +- frontend/dashboard/components/RepoList/FavoriteButton.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx index 96b69365a95..6a1124c1cc5 100644 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -10,7 +10,7 @@ import { } from '@navikt/aksel-icons'; import { useTranslation } from 'react-i18next'; import { getRepoEditUrl } from '../../utils/urlUtils'; -import { Repository } from 'app-shared/types/Repository'; +import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; type ActionLinksProps = { repo: Repository; diff --git a/frontend/dashboard/components/RepoList/FavoriteButton.tsx b/frontend/dashboard/components/RepoList/FavoriteButton.tsx index 371f524d17e..68d378e5cc0 100644 --- a/frontend/dashboard/components/RepoList/FavoriteButton.tsx +++ b/frontend/dashboard/components/RepoList/FavoriteButton.tsx @@ -1,12 +1,11 @@ import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; -import classes from './RepoList.module.css'; import { Button } from '@digdir/design-system-react'; import React from 'react'; import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; -import { Repository } from 'app-shared/types/Repository'; +import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; type FavoriteButtonProps = { - repo: Repository; + repo: RepositoryWithStarred; }; export const FavoriteButton = ({ repo }: FavoriteButtonProps): React.ReactElement => { From 79344712e2be5a3d27adf906e326828f0d138d90 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 15:28:43 +0200 Subject: [PATCH 16/72] Change label size switch statement to object map --- .../components/RepoList/ActionLinks.tsx | 2 +- .../components/RepoList/NewRepoList.tsx | 19 +++++++++++------ .../SelectRowsPerPage.tsx | 4 ++-- .../StudioTableWithPagination/utils.tsx | 21 ++++--------------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx index 6a1124c1cc5..96b69365a95 100644 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -10,7 +10,7 @@ import { } from '@navikt/aksel-icons'; import { useTranslation } from 'react-i18next'; import { getRepoEditUrl } from '../../utils/urlUtils'; -import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; +import { Repository } from 'app-shared/types/Repository'; type ActionLinksProps = { repo: Repository; diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx index 54294e42383..fae711cba69 100644 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -4,6 +4,7 @@ import { DateUtils } from '@studio/pure-functions'; import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; import { ActionLinks } from './ActionLinks'; import { FavoriteButton } from './FavoriteButton'; +import { Paragraph } from '@digdir/design-system-react'; type NewRepoListProps = { repos: RepositoryWithStarred[]; @@ -30,11 +31,17 @@ export const NewRepoList = ({ repos }: NewRepoListProps): React.ReactElement => const columns = ['', 'Navn', 'Opprettet av', 'Sist Endret', 'Beskrivelse', '']; return ( - + <> + {rows.length > 0 ? ( + + ) : ( + Ingen applikasjoner funnet + )} + ); }; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx index 53e7d2c09cc..d8d0e67a094 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx @@ -1,10 +1,10 @@ import classes from './StudioTableWithPagination.module.css'; import { Label, NativeSelect } from '@digdir/design-system-react'; import React from 'react'; -import { getLabelSize } from './utils'; +import { labelSizeMap } from './utils'; export const SelectRowsPerPage = ({ setRowPerPage, size }) => { - const labelSize = getLabelSize(size); + const labelSize = labelSizeMap[size]; return (
diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx index 99fe84339d4..47da416bc2c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx @@ -1,20 +1,7 @@ -export const getLabelSize = (size: string) => { - let labelSize; - switch (size) { - case 'xsmall': - labelSize = 'xsmall'; - break; - case 'small': - labelSize = 'xsmall'; - break; - case 'large': - labelSize = 'medium'; - break; - default: - labelSize = 'small'; - } - - return labelSize; +export const labelSizeMap = { + small: 'xsmall', + medium: 'small', + large: 'medium', }; export const calcCurrentRows = (currentPage, rowPerPage, rows) => { From 5fe031fb540ea6cc770e90268e63ed285b2482fa Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 18:52:38 +0200 Subject: [PATCH 17/72] Remove useEffect from StudioTabelWithPagination --- .../StudioTableWithPagination.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index b69d2b3b7ac..fcdeea29543 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -24,13 +24,7 @@ export const StudioTableWithPagination = forwardRef< const totalPages = Math.ceil(sortedRows.length / rowsPerPage); const currentRows = calcCurrentRows(currentPage, rowsPerPage, sortedRows); - - useEffect(() => { - // If user is out of bounds, move them to page 1 - if (currentRows.length === 0) { - setCurrentPage(1); - } - }, [currentRows, currentPage]); + if (currentRows.length === 0) setCurrentPage(1); return ( <> From 46ab5a72bccfe0d05dfa347bc4001806053d2995 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 19:46:45 +0200 Subject: [PATCH 18/72] Small fixes --- .../StudioTableWithPagination.tsx | 2 +- .../StudioTableWithPagination/mockData.tsx | 283 ++++++++++++++++-- 2 files changed, 258 insertions(+), 27 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index fcdeea29543..c93ca41a544 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,5 +1,5 @@ import { Pagination } from '@digdir/design-system-react'; -import React, { forwardRef, useEffect, useState } from 'react'; +import React, { forwardRef, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; import { StudioTable } from './StudioTable'; import { calcCurrentRows } from './utils'; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index ce4a47cf4c9..231af39a37f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -2,75 +2,306 @@ import { Button, Link } from '@digdir/design-system-react'; import { FaceSmileFillIcon, FaceSmileIcon, StarFillIcon, StarIcon } from '@navikt/aksel-icons'; import React from 'react'; -const iconButton = (icon) => ( +const IconButton = ({ icon }) => ( ); -const link = () => Link; +const AltinnLink = () => Link; export const rows = [ - [iconButton(), '123456', 'Lila Patel', 'Software Engineer', 'Pending', link()], [ - iconButton(), + } />, + '123456', + 'Lila Patel', + 'Software Engineer', + 'Pending', + , + ], + [ + } />, '234567', 'Ethan Nakamura', 'Marketing Specialist', 'Approved', - link(), + , + ], + [ + } />, + '345678', + 'Olivia Chen', + 'Data Analyst', + 'Pending', + , + ], + [ + } />, + '456789', + 'Noah Adebayo', + 'UX Designer', + 'Approved', + , + ], + [ + } />, + '567890', + 'Sophia Ivanov', + 'Product Manager', + 'Pending', + , ], - [iconButton(), '345678', 'Olivia Chen', 'Data Analyst', 'Pending', link()], - [iconButton(), '456789', 'Noah Adebayo', 'UX Designer', 'Approved', link()], - [iconButton(), '567890', 'Sophia Ivanov', 'Product Manager', 'Pending', link()], [ - iconButton(), + } />, '678901', 'William Torres', 'Sales Representative', 'Approved', - link(), + , + ], + [ + } />, + '789012', + 'Ava Gupta', + 'Human Resources Manager', + 'Pending', + , + ], + [ + } />, + '890123', + 'James Kim', + 'Financial Analyst', + 'Approved', + AltinnLink(), ], - [iconButton(), '789012', 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], - [iconButton(), '890123', 'James Kim', 'Financial Analyst', 'Approved', link()], [ - iconButton(), + } />, '901234', 'Mia Sánchez', 'Customer Support Specialist', 'Pending', - link(), + , ], - [iconButton(), '012345', 'Lila Patel', 'Software Engineer', 'Pending', link()], [ - iconButton(), + } />, + '012345', + 'Lila Patel', + 'Software Engineer', + 'Pending', + , + ], + [ + } />, '123450', 'Ethan Nakamura', 'Marketing Specialist', 'Approved', - link(), + , + ], + [ + } />, + '234501', + 'Olivia Chen', + 'Data Analyst', + 'Pending', + , + ], + [ + } />, + '345012', + 'Noah Adebayo', + 'UX Designer', + 'Approved', + , ], - [iconButton(), '234501', 'Olivia Chen', 'Data Analyst', 'Pending', link()], - [iconButton(), '345012', 'Noah Adebayo', 'UX Designer', 'Approved', link()], - [iconButton(), '450123', 'Sophia Ivanov', 'Product Manager', 'Pending', link()], [ - iconButton(), + } />, + '450123', + 'Sophia Ivanov', + 'Product Manager', + 'Pending', + , + ], + [ + } />, '501234', 'William Torres', 'Sales Representative', 'Approved', - link(), + , ], - [iconButton(), '012345', 'Ava Gupta', 'Human Resources Manager', 'Pending', link()], - [iconButton(), '123450', 'James Kim', 'Financial Analyst', 'Approved', link()], [ - iconButton(), + } />, + '012345', + 'Ava Gupta', + 'Human Resources Manager', + 'Pending', + , + ], + [ + } />, + '123450', + 'James Kim', + 'Financial Analyst', + 'Approved', + , + ], + [ + } />, '234501', 'Mia Sánchez', 'Customer Support Specialist', 'Pending', - link(), + , ], ]; +// export const rows = [ +// { +// icon: }/>, +// id: '123456', +// name: 'Lila Patel', +// position: 'Software Engineer', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '234567', +// name: 'Ethan Nakamura', +// position: 'Marketing Specialist', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '345678', +// name: 'Olivia Chen', +// position: 'Data Analyst', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '456789', +// name: 'Noah Adebayo', +// position: 'UX Designer', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '567890', +// name: 'Sophia Ivanov', +// position: 'Product Manager', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '678901', +// name: 'William Torres', +// position: 'Sales Representative', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '789012', +// name: 'Ava Gupta', +// position: 'Human Resources Manager', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '890123', +// name: 'James Kim', +// position: 'Financial Analyst', +// status: 'Approved', +// link: AltinnLink() +// }, +// { +// icon: }/>, +// id: '901234', +// name: 'Mia Sánchez', +// position: 'Customer Support Specialist', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '012345', +// name: 'Lila Patel', +// position: 'Software Engineer', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '123450', +// name: 'Ethan Nakamura', +// position: 'Marketing Specialist', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '234501', +// name: 'Olivia Chen', +// position: 'Data Analyst', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '345012', +// name: 'Noah Adebayo', +// position: 'UX Designer', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '450123', +// name: 'Sophia Ivanov', +// position: 'Product Manager', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '501234', +// name: 'William Torres', +// position: 'Sales Representative', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '012345', +// name: 'Ava Gupta', +// position: 'Human Resources Manager', +// status: 'Pending', +// link: +// }, +// { +// icon: }/>, +// id: '123450', +// name: 'James Kim', +// position: 'Financial Analyst', +// status: 'Approved', +// link: +// }, +// { +// icon: }/>, +// id: '234501', +// name: 'Mia Sánchez', +// position: 'Customer Support Specialist', +// status: 'Pending', +// link: +// }, +// ]; + export const columns = ['', 'Employee ID', 'Name', 'Role', 'Status', '']; From 6298d0837520d2d858f900abc0719bae25dca198 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 20:00:52 +0200 Subject: [PATCH 19/72] Fix linting issues --- .../components/RepoList/ActionLinks.tsx | 2 +- .../components/RepoList/FavoriteButton.tsx | 2 +- .../components/RepoList/NewRepoList.tsx | 2 +- .../SelectRowsPerPage.tsx | 5 ++ .../StudioTableWithPagination/StudioTable.tsx | 4 +- .../StudioTableWithPagination.tsx | 88 +++++++++++-------- 6 files changed, 60 insertions(+), 43 deletions(-) diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx index 96b69365a95..40c9c446913 100644 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ b/frontend/dashboard/components/RepoList/ActionLinks.tsx @@ -10,7 +10,7 @@ import { } from '@navikt/aksel-icons'; import { useTranslation } from 'react-i18next'; import { getRepoEditUrl } from '../../utils/urlUtils'; -import { Repository } from 'app-shared/types/Repository'; +import type { Repository } from 'app-shared/types/Repository'; type ActionLinksProps = { repo: Repository; diff --git a/frontend/dashboard/components/RepoList/FavoriteButton.tsx b/frontend/dashboard/components/RepoList/FavoriteButton.tsx index 68d378e5cc0..5488c386d4b 100644 --- a/frontend/dashboard/components/RepoList/FavoriteButton.tsx +++ b/frontend/dashboard/components/RepoList/FavoriteButton.tsx @@ -2,7 +2,7 @@ import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; import { Button } from '@digdir/design-system-react'; import React from 'react'; import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; -import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; +import type { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; type FavoriteButtonProps = { repo: RepositoryWithStarred; diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx index fae711cba69..17ea7f74d22 100644 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StudioTableWithPagination } from '@studio/components/src/components/StudioTableWithPagination'; import { DateUtils } from '@studio/pure-functions'; -import { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; +import type { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; import { ActionLinks } from './ActionLinks'; import { FavoriteButton } from './FavoriteButton'; import { Paragraph } from '@digdir/design-system-react'; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx index d8d0e67a094..8ff588e1db3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx @@ -3,6 +3,11 @@ import { Label, NativeSelect } from '@digdir/design-system-react'; import React from 'react'; import { labelSizeMap } from './utils'; +type SelectRowsPerPageProps = { + setRowPerPage: (value: ((prevState: number) => number) | number) => void; + size: 'small' | 'medium' | 'large'; +}; + export const SelectRowsPerPage = ({ setRowPerPage, size }) => { const labelSize = labelSizeMap[size]; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index dc438ce5a34..0c73fdd09c4 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -33,8 +33,8 @@ export const StudioTable: React.FC = ({ - {rows?.map((row, i) => ( - + {rows?.map((row, index) => ( + {row.map((cell, i) => ( {cell} ))} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index c93ca41a544..c4a86ff5ea0 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -17,43 +17,55 @@ type StudioTableWithPaginationProps = { export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->(({ columns, rows, sortable = true, size = 'medium', initialRowsPerPage = 5 }, ref) => { - const [currentPage, setCurrentPage] = useState(1); - const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); - const { sortedRows, handleSorting } = useSortedRows(rows); +>( + ( + { + columns, + rows, + sortable = true, + size = 'medium', + initialRowsPerPage = 5, + }: StudioTableWithPaginationProps, + ref, + ): React.ReactElement => { + const [currentPage, setCurrentPage] = useState(1); + const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); + const { sortedRows, handleSorting } = useSortedRows(rows); - const totalPages = Math.ceil(sortedRows.length / rowsPerPage); - const currentRows = calcCurrentRows(currentPage, rowsPerPage, sortedRows); - if (currentRows.length === 0) setCurrentPage(1); + const totalPages = Math.ceil(sortedRows.length / rowsPerPage); + const currentRows = calcCurrentRows(currentPage, rowsPerPage, sortedRows); + if (currentRows.length === 0) setCurrentPage(1); - return ( - <> - - {initialRowsPerPage > 0 && ( -
- - {totalPages > 1 && ( - `Side ${num}`} - hideLabels - compact - /> - )} -
- )} - - ); -}); + return ( + <> + + {initialRowsPerPage > 0 && ( +
+ + {totalPages > 1 && ( + `Side ${num}`} + hideLabels + compact + /> + )} +
+ )} + + ); + }, +); From de3f73c38fa1f596827f3d2851f73013613eb767 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 24 Apr 2024 20:03:33 +0200 Subject: [PATCH 20/72] Fix double type declaration --- .../StudioTableWithPagination.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index c4a86ff5ea0..1d4b927b245 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -19,13 +19,7 @@ export const StudioTableWithPagination = forwardRef< StudioTableWithPaginationProps >( ( - { - columns, - rows, - sortable = true, - size = 'medium', - initialRowsPerPage = 5, - }: StudioTableWithPaginationProps, + { columns, rows, sortable = true, size = 'medium', initialRowsPerPage = 5 }, ref, ): React.ReactElement => { const [currentPage, setCurrentPage] = useState(1); From 98c37e3e5c42041d59e411a837b1c4d24bbf5c42 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 25 Apr 2024 09:52:14 +0200 Subject: [PATCH 21/72] Make table read array of objects --- .../SelectRowsPerPage.tsx | 9 +- .../StudioTableWithPagination/StudioTable.tsx | 30 +- .../StudioTableWithPagination.tsx | 7 +- .../StudioTableWithPagination/mockData.tsx | 435 ++++++------------ 4 files changed, 169 insertions(+), 312 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx index 8ff588e1db3..fafbbddcde2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx @@ -3,13 +3,18 @@ import { Label, NativeSelect } from '@digdir/design-system-react'; import React from 'react'; import { labelSizeMap } from './utils'; +type LabelSize = 'small' | 'medium' | 'large' | 'xsmall'; + type SelectRowsPerPageProps = { setRowPerPage: (value: ((prevState: number) => number) | number) => void; size: 'small' | 'medium' | 'large'; }; -export const SelectRowsPerPage = ({ setRowPerPage, size }) => { - const labelSize = labelSizeMap[size]; +export const SelectRowsPerPage = ({ + setRowPerPage, + size, +}: SelectRowsPerPageProps): React.ReactElement => { + const labelSize = labelSizeMap[size] as LabelSize; return (
diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index 0c73fdd09c4..7809f797488 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -1,24 +1,22 @@ import classes from './StudioTableWithPagination.module.css'; import { Table } from '@digdir/design-system-react'; -import React from 'react'; +import React, { forwardRef } from 'react'; +import { TableSize } from './StudioTableWithPagination'; type StudioTableProps = { - size: 'small' | 'medium' | 'large'; + size: TableSize; columns: string[]; - rows: React.ReactNode[][]; + rows: Record[]; sortable: boolean; - handleSorting?: (arg: number) => void; + handleSorting?: (columnIndex: number) => void; }; -export const StudioTable: React.FC = ({ - size, - columns, - rows, - sortable, - handleSorting, -}) => { +export const StudioTable: React.FC = forwardRef< + HTMLTableElement, + StudioTableProps +>(({ size, columns, rows, sortable, handleSorting }, ref) => { return ( - +
{columns?.map((cell, i) => ( @@ -33,9 +31,9 @@ export const StudioTable: React.FC = ({ - {rows?.map((row, index) => ( - - {row.map((cell, i) => ( + {rows?.map((row) => ( + + {Object.values(row).map((cell, i) => ( {cell} ))} @@ -43,4 +41,4 @@ export const StudioTable: React.FC = ({
); -}; +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 1d4b927b245..19ba1e2823f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -6,11 +6,13 @@ import { calcCurrentRows } from './utils'; import { useSortedRows } from '../../hooks/useSortedRows'; import { SelectRowsPerPage } from './SelectRowsPerPage'; +export type TableSize = 'small' | 'medium' | 'large'; + type StudioTableWithPaginationProps = { columns: string[]; - rows: React.ReactNode[][]; + rows: Record[]; sortable?: boolean; - size?: 'small' | 'medium' | 'large'; + size?: TableSize; initialRowsPerPage?: number; }; @@ -38,7 +40,6 @@ export const StudioTableWithPagination = forwardRef< size={size} sortable={sortable} handleSorting={handleSorting} - ref={ref} /> {initialRowsPerPage > 0 && (
diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index 231af39a37f..b5faef9d658 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -11,297 +11,150 @@ const IconButton = ({ icon }) => ( const AltinnLink = () => Link; export const rows = [ - [ - } />, - '123456', - 'Lila Patel', - 'Software Engineer', - 'Pending', - , - ], - [ - } />, - '234567', - 'Ethan Nakamura', - 'Marketing Specialist', - 'Approved', - , - ], - [ - } />, - '345678', - 'Olivia Chen', - 'Data Analyst', - 'Pending', - , - ], - [ - } />, - '456789', - 'Noah Adebayo', - 'UX Designer', - 'Approved', - , - ], - [ - } />, - '567890', - 'Sophia Ivanov', - 'Product Manager', - 'Pending', - , - ], - [ - } />, - '678901', - 'William Torres', - 'Sales Representative', - 'Approved', - , - ], - [ - } />, - '789012', - 'Ava Gupta', - 'Human Resources Manager', - 'Pending', - , - ], - [ - } />, - '890123', - 'James Kim', - 'Financial Analyst', - 'Approved', - AltinnLink(), - ], - [ - } />, - '901234', - 'Mia Sánchez', - 'Customer Support Specialist', - 'Pending', - , - ], - [ - } />, - '012345', - 'Lila Patel', - 'Software Engineer', - 'Pending', - , - ], - [ - } />, - '123450', - 'Ethan Nakamura', - 'Marketing Specialist', - 'Approved', - , - ], - [ - } />, - '234501', - 'Olivia Chen', - 'Data Analyst', - 'Pending', - , - ], - [ - } />, - '345012', - 'Noah Adebayo', - 'UX Designer', - 'Approved', - , - ], - [ - } />, - '450123', - 'Sophia Ivanov', - 'Product Manager', - 'Pending', - , - ], - [ - } />, - '501234', - 'William Torres', - 'Sales Representative', - 'Approved', - , - ], - [ - } />, - '012345', - 'Ava Gupta', - 'Human Resources Manager', - 'Pending', - , - ], - [ - } />, - '123450', - 'James Kim', - 'Financial Analyst', - 'Approved', - , - ], - [ - } />, - '234501', - 'Mia Sánchez', - 'Customer Support Specialist', - 'Pending', - , - ], + { + icon: } />, + id: '123456', + name: 'Lila Patel', + position: 'Software Engineer', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '234567', + name: 'Ethan Nakamura', + position: 'Marketing Specialist', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '345678', + name: 'Olivia Chen', + position: 'Data Analyst', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '456789', + name: 'Noah Adebayo', + position: 'UX Designer', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '567890', + name: 'Sophia Ivanov', + position: 'Product Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '678901', + name: 'William Torres', + position: 'Sales Representative', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '789012', + name: 'Ava Gupta', + position: 'Human Resources Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '890123', + name: 'James Kim', + position: 'Financial Analyst', + status: 'Approved', + link: AltinnLink(), + }, + { + icon: } />, + id: '901234', + name: 'Mia Sánchez', + position: 'Customer Support Specialist', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '012345', + name: 'Lila Patel', + position: 'Software Engineer', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '123450', + name: 'Ethan Nakamura', + position: 'Marketing Specialist', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '234501', + name: 'Olivia Chen', + position: 'Data Analyst', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '345012', + name: 'Noah Adebayo', + position: 'UX Designer', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '450123', + name: 'Sophia Ivanov', + position: 'Product Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '501234', + name: 'William Torres', + position: 'Sales Representative', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '012345', + name: 'Ava Gupta', + position: 'Human Resources Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: '123450', + name: 'James Kim', + position: 'Financial Analyst', + status: 'Approved', + link: , + }, + { + icon: } />, + id: '234501', + name: 'Mia Sánchez', + position: 'Customer Support Specialist', + status: 'Pending', + link: , + }, ]; -// export const rows = [ -// { -// icon: }/>, -// id: '123456', -// name: 'Lila Patel', -// position: 'Software Engineer', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '234567', -// name: 'Ethan Nakamura', -// position: 'Marketing Specialist', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '345678', -// name: 'Olivia Chen', -// position: 'Data Analyst', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '456789', -// name: 'Noah Adebayo', -// position: 'UX Designer', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '567890', -// name: 'Sophia Ivanov', -// position: 'Product Manager', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '678901', -// name: 'William Torres', -// position: 'Sales Representative', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '789012', -// name: 'Ava Gupta', -// position: 'Human Resources Manager', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '890123', -// name: 'James Kim', -// position: 'Financial Analyst', -// status: 'Approved', -// link: AltinnLink() -// }, -// { -// icon: }/>, -// id: '901234', -// name: 'Mia Sánchez', -// position: 'Customer Support Specialist', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '012345', -// name: 'Lila Patel', -// position: 'Software Engineer', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '123450', -// name: 'Ethan Nakamura', -// position: 'Marketing Specialist', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '234501', -// name: 'Olivia Chen', -// position: 'Data Analyst', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '345012', -// name: 'Noah Adebayo', -// position: 'UX Designer', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '450123', -// name: 'Sophia Ivanov', -// position: 'Product Manager', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '501234', -// name: 'William Torres', -// position: 'Sales Representative', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '012345', -// name: 'Ava Gupta', -// position: 'Human Resources Manager', -// status: 'Pending', -// link: -// }, -// { -// icon: }/>, -// id: '123450', -// name: 'James Kim', -// position: 'Financial Analyst', -// status: 'Approved', -// link: -// }, -// { -// icon: }/>, -// id: '234501', -// name: 'Mia Sánchez', -// position: 'Customer Support Specialist', -// status: 'Pending', -// link: -// }, -// ]; - export const columns = ['', 'Employee ID', 'Name', 'Role', 'Status', '']; From 769dff3b3976976ea2fc290a7a0ff4523cb73f92 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 25 Apr 2024 11:50:49 +0200 Subject: [PATCH 22/72] Tidy up StudioTabel and make sorting work again --- .../components/RepoList/NewRepoList.tsx | 9 +- .../StudioTableWithPagination/StudioTable.tsx | 28 +++-- .../StudioTableWithPagination.stories.tsx | 4 +- .../StudioTableWithPagination.tsx | 11 +- .../StudioTableWithPagination/mockData.tsx | 109 ++++++++++++------ .../src/hooks/useSortedRows.tsx | 61 +++++----- 6 files changed, 134 insertions(+), 88 deletions(-) diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx index 17ea7f74d22..66f450f538e 100644 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -28,7 +28,14 @@ export const NewRepoList = ({ repos }: NewRepoListProps): React.ReactElement => rows.push(row); }); - const columns = ['', 'Navn', 'Opprettet av', 'Sist Endret', 'Beskrivelse', '']; + const columns = [ + { key: '0', value: '' }, + { key: '1', value: 'Navn' }, + { key: '2', value: 'Opprettet av' }, + { key: '3', value: 'Sist Endret' }, + { key: '4', value: 'Beskrivelse' }, + { key: '5', value: '' }, + ]; return ( <> diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index 7809f797488..5621e6983d0 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -5,36 +5,40 @@ import { TableSize } from './StudioTableWithPagination'; type StudioTableProps = { size: TableSize; - columns: string[]; + columns: Record<'key' | 'value', string>[]; rows: Record[]; - sortable: boolean; - handleSorting?: (columnIndex: number) => void; + isSortable: boolean; + handleSorting?: (columnKey: string) => void; }; export const StudioTable: React.FC = forwardRef< HTMLTableElement, StudioTableProps ->(({ size, columns, rows, sortable, handleSorting }, ref) => { +>(({ size, columns, rows, isSortable, handleSorting }, ref) => { + const columnHasValue = (value) => { + return Boolean(value); + }; + return ( - {columns?.map((cell, i) => ( + {columns.map(({ key, value }) => ( handleSorting(i)} + key={key} + sortable={isSortable && columnHasValue(value)} + onSortClick={() => handleSorting(key)} > - {cell} + {value} ))} - {rows?.map((row) => ( + {rows.map((row) => ( - {Object.values(row).map((cell, i) => ( - {cell} + {columns.map(({ key }) => ( + {row[key]} ))} ))} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx index 2570f0208a1..cca9b191539 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx @@ -20,8 +20,8 @@ export const Preview: Story = (args): React.ReactElement => []; type StudioTableWithPaginationProps = { - columns: string[]; - rows: Record[]; - sortable?: boolean; + columns: Record<'key' | 'value', string>[]; + rows: Rows; + isSortable?: boolean; size?: TableSize; initialRowsPerPage?: number; }; @@ -21,7 +22,7 @@ export const StudioTableWithPagination = forwardRef< StudioTableWithPaginationProps >( ( - { columns, rows, sortable = true, size = 'medium', initialRowsPerPage = 5 }, + { columns, rows, isSortable = true, size = 'medium', initialRowsPerPage = 5 }, ref, ): React.ReactElement => { const [currentPage, setCurrentPage] = useState(1); @@ -38,7 +39,7 @@ export const StudioTableWithPagination = forwardRef< columns={columns} rows={currentRows} size={size} - sortable={sortable} + isSortable={isSortable} handleSorting={handleSorting} /> {initialRowsPerPage > 0 && ( diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index b5faef9d658..6c187f9d484 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -10,151 +10,186 @@ const IconButton = ({ icon }) => ( const AltinnLink = () => Link; +function generateRandomId(length) { + let result = ''; + const characters = '0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + export const rows = [ { icon: } />, - id: '123456', + id: generateRandomId(6), name: 'Lila Patel', - position: 'Software Engineer', + role: 'Software Engineer', status: 'Pending', link: , }, { icon: } />, - id: '234567', + id: generateRandomId(6), name: 'Ethan Nakamura', - position: 'Marketing Specialist', + role: 'Marketing Specialist', status: 'Approved', link: , }, { icon: } />, - id: '345678', + id: generateRandomId(6), name: 'Olivia Chen', - position: 'Data Analyst', + role: 'Data Analyst', status: 'Pending', link: , }, { icon: } />, - id: '456789', + id: generateRandomId(6), name: 'Noah Adebayo', - position: 'UX Designer', + role: 'UX Designer', status: 'Approved', link: , }, { icon: } />, - id: '567890', + id: generateRandomId(6), name: 'Sophia Ivanov', - position: 'Product Manager', + role: 'Product Manager', status: 'Pending', link: , }, { icon: } />, - id: '678901', + id: generateRandomId(6), name: 'William Torres', - position: 'Sales Representative', + role: 'Sales Representative', status: 'Approved', link: , }, { icon: } />, - id: '789012', + id: generateRandomId(6), name: 'Ava Gupta', - position: 'Human Resources Manager', + role: 'Human Resources Manager', status: 'Pending', link: , }, { icon: } />, - id: '890123', + id: generateRandomId(6), name: 'James Kim', - position: 'Financial Analyst', + role: 'Financial Analyst', status: 'Approved', link: AltinnLink(), }, { icon: } />, - id: '901234', + id: generateRandomId(6), name: 'Mia Sánchez', - position: 'Customer Support Specialist', + role: 'Customer Support Specialist', status: 'Pending', link: , }, { icon: } />, - id: '012345', + id: generateRandomId(6), name: 'Lila Patel', - position: 'Software Engineer', + role: 'Software Engineer', status: 'Pending', link: , }, { icon: } />, - id: '123450', + id: generateRandomId(6), name: 'Ethan Nakamura', - position: 'Marketing Specialist', + role: 'Marketing Specialist', status: 'Approved', link: , }, { icon: } />, - id: '234501', + id: generateRandomId(6), name: 'Olivia Chen', - position: 'Data Analyst', + role: 'Data Analyst', status: 'Pending', link: , }, { icon: } />, - id: '345012', + id: generateRandomId(6), name: 'Noah Adebayo', - position: 'UX Designer', + role: 'UX Designer', status: 'Approved', link: , }, { icon: } />, - id: '450123', + id: generateRandomId(6), name: 'Sophia Ivanov', - position: 'Product Manager', + role: 'Product Manager', status: 'Pending', link: , }, { icon: } />, - id: '501234', + id: generateRandomId(6), name: 'William Torres', - position: 'Sales Representative', + role: 'Sales Representative', status: 'Approved', link: , }, { icon: } />, - id: '012345', + id: generateRandomId(6), name: 'Ava Gupta', - position: 'Human Resources Manager', + role: 'Human Resources Manager', status: 'Pending', link: , }, { icon: } />, - id: '123450', + id: generateRandomId(6), name: 'James Kim', - position: 'Financial Analyst', + role: 'Financial Analyst', status: 'Approved', link: , }, { icon: } />, - id: '234501', + id: generateRandomId(6), name: 'Mia Sánchez', - position: 'Customer Support Specialist', + role: 'Customer Support Specialist', status: 'Pending', link: , }, ]; -export const columns = ['', 'Employee ID', 'Name', 'Role', 'Status', '']; +export const columns = [ + { + key: 'icon', + value: '', + }, + { + key: 'id', + value: 'Employee ID', + }, + { + key: 'name', + value: 'Name', + }, + { + key: 'role', + value: 'Role', + }, + { + key: 'status', + value: 'Status', + }, + { + key: 'link', + value: '', + }, +]; diff --git a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx index 2e422bcc106..dc4e29b9a20 100644 --- a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx +++ b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx @@ -1,39 +1,38 @@ -import { useState, useCallback } from 'react'; +import { useState } from 'react'; +import { Rows } from '../components/StudioTableWithPagination/StudioTableWithPagination'; -export const useSortedRows = (rows, initialSortColumn = null, initialSortDirection = 'asc') => { - const [sortColumn, setSortColumn] = useState(initialSortColumn); - const [sortDirection, setSortDirection] = useState(initialSortDirection); +export const useSortedRows = (rows: Rows) => { + const [sortColumn, setSortColumn] = useState(null); + const [sortDirection, setSortDirection] = useState('asc'); - const handleSorting = useCallback( - (columnIndex) => { - if (sortColumn === columnIndex) { - setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); - } else { - setSortColumn(columnIndex); - setSortDirection('asc'); - } - }, - [sortColumn], - ); + const toggleSortDirection = () => { + setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); + }; + + const handleSorting = (columnKey) => { + if (sortColumn === columnKey) { + toggleSortDirection(); + } else { + setSortColumn(columnKey); + setSortDirection('asc'); + } + }; - const sortedRows = useCallback( - () => - sortColumn !== null - ? [...rows].sort((a, b) => { - const columnA = a[sortColumn]; - const columnB = b[sortColumn]; - if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; - if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; - return 0; - }) - : rows, - [rows, sortColumn, sortDirection], - ); + let sortedRows; + if (sortColumn !== null) { + sortedRows = [...rows].sort((a, b) => { + const columnA = a[sortColumn]; + const columnB = b[sortColumn]; + if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; + if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + } else { + sortedRows = rows; + } return { - sortedRows: sortedRows(), - sortColumn, - sortDirection, + sortedRows, handleSorting, }; }; From 65eb078f09e52513daaf735a7f31fdfcf9521912 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 25 Apr 2024 12:00:15 +0200 Subject: [PATCH 23/72] Undo unintended CSS class name changes for unrelated files --- .../features/simpleMerge/MergeConflictWarning.module.css | 2 +- .../app-development/features/simpleMerge/RepoModal.module.css | 2 +- .../shared/src/components/AltinnConfirmDialog.module.css | 2 +- frontend/packages/shared/src/primitives/Primitives.module.css | 2 +- .../src/containers/DesignView/DesignView.module.css | 2 +- .../NavigationMenu/InputPopover/InputPopover.module.css | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css b/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css index ce60bf1ca18..480b89cbf57 100644 --- a/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css +++ b/frontend/app-development/features/simpleMerge/MergeConflictWarning.module.css @@ -6,6 +6,6 @@ border: 1px solid #c9c9c9; border-radius: 3px; } -.actionButtonContainer { +.buttonContainer { justify-content: flex-end; } diff --git a/frontend/app-development/features/simpleMerge/RepoModal.module.css b/frontend/app-development/features/simpleMerge/RepoModal.module.css index 57234bad858..7d7d5dd1821 100644 --- a/frontend/app-development/features/simpleMerge/RepoModal.module.css +++ b/frontend/app-development/features/simpleMerge/RepoModal.module.css @@ -11,7 +11,7 @@ margin: 0; padding: 0; } -.actionButtonContainer { +.buttonContainer { display: flex; gap: 24px; } diff --git a/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css b/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css index 00a24c38a76..e66a243b79a 100644 --- a/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css +++ b/frontend/packages/shared/src/components/AltinnConfirmDialog.module.css @@ -9,7 +9,7 @@ margin-top: 0; } -.actionButtonContainer { +.buttonContainer { display: flex; flex-direction: row; gap: var(--fds-spacing-2); diff --git a/frontend/packages/shared/src/primitives/Primitives.module.css b/frontend/packages/shared/src/primitives/Primitives.module.css index 580abb02f5b..6c8994e973a 100644 --- a/frontend/packages/shared/src/primitives/Primitives.module.css +++ b/frontend/packages/shared/src/primitives/Primitives.module.css @@ -4,7 +4,7 @@ gap: 16px; padding-bottom: 16px; } -.actionButtonContainer { +.buttonContainer { display: flex; flex-direction: row; gap: 12px; diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css index 8a783464477..4b68a52d424 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.module.css @@ -13,7 +13,7 @@ margin: var(--margin-top) var(--margin-horizontal) 0 var(--margin-horizontal); } -.actionButtonContainer { +.buttonContainer { display: flex; justify-content: center; padding-top: var(--fds-spacing-5); diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css index aedb90e17c3..d9d4486293f 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.module.css @@ -10,7 +10,7 @@ margin-top: 10px; } -.actionButtonContainer { +.buttonContainer { display: flex; justify-content: flex-end; margin-top: 20px; From 0c58bd4b167effddb178c4b23f730033efe2a22c Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:04:41 +0200 Subject: [PATCH 24/72] Undo unintended changes in DesignView.module.css --- .../ux-editor/src/containers/DesignView/DesignView.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css index 8a783464477..4b68a52d424 100644 --- a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css +++ b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.module.css @@ -13,7 +13,7 @@ margin: var(--margin-top) var(--margin-horizontal) 0 var(--margin-horizontal); } -.actionButtonContainer { +.buttonContainer { display: flex; justify-content: center; padding-top: var(--fds-spacing-5); From e3aecbdaa3001d038ef1706f96a6c103b46156f4 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 25 Apr 2024 12:20:44 +0200 Subject: [PATCH 25/72] Change columns 'key' attribute to 'accessor' to avoid confusiong with React's keyword --- .../StudioTableWithPagination/StudioTable.tsx | 16 ++++++++-------- .../StudioTableWithPagination.tsx | 3 ++- .../StudioTableWithPagination/mockData.tsx | 12 ++++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx index 5621e6983d0..d594da9808f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx @@ -1,12 +1,12 @@ import classes from './StudioTableWithPagination.module.css'; import { Table } from '@digdir/design-system-react'; import React, { forwardRef } from 'react'; -import { TableSize } from './StudioTableWithPagination'; +import { Columns, Rows, TableSize } from './StudioTableWithPagination'; type StudioTableProps = { size: TableSize; - columns: Record<'key' | 'value', string>[]; - rows: Record[]; + columns: Columns; + rows: Rows; isSortable: boolean; handleSorting?: (columnKey: string) => void; }; @@ -23,11 +23,11 @@ export const StudioTable: React.FC = forwardRef<
- {columns.map(({ key, value }) => ( + {columns.map(({ accessor, value }) => ( handleSorting(key)} + onSortClick={() => handleSorting(accessor)} > {value} @@ -37,8 +37,8 @@ export const StudioTable: React.FC = forwardRef< {rows.map((row) => ( - {columns.map(({ key }) => ( - {row[key]} + {columns.map(({ accessor }) => ( + {row[accessor]} ))} ))} diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index a678b31073c..0c44d3d0da3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -8,9 +8,10 @@ import { SelectRowsPerPage } from './SelectRowsPerPage'; export type TableSize = 'small' | 'medium' | 'large'; export type Rows = Record[]; +export type Columns = Record<'accessor' | 'value', string>[]; type StudioTableWithPaginationProps = { - columns: Record<'key' | 'value', string>[]; + columns: Columns; rows: Rows; isSortable?: boolean; size?: TableSize; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx index 6c187f9d484..d7b24744eb1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx @@ -169,27 +169,27 @@ export const rows = [ export const columns = [ { - key: 'icon', + accessor: 'icon', value: '', }, { - key: 'id', + accessor: 'id', value: 'Employee ID', }, { - key: 'name', + accessor: 'name', value: 'Name', }, { - key: 'role', + accessor: 'role', value: 'Role', }, { - key: 'status', + accessor: 'status', value: 'Status', }, { - key: 'link', + accessor: 'link', value: '', }, ]; From 897a806fb05d337120d9e3a38dcaefb881504b79 Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:53:23 +0200 Subject: [PATCH 26/72] Undo Update README.md - not relevant --- development/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development/README.md b/development/README.md index 6141433da4e..b98fde6b4b6 100644 --- a/development/README.md +++ b/development/README.md @@ -2,7 +2,7 @@ ## Setup script -Run the setup script in this folder with node. This script should be idempotent, so that it can be run multiple times +Run the setup script in this folder with node. This script should be immutable, so that it can be run multiple times without breaking anything. This is great for introducing new environment variables and so on. It will not overwrite any changes you do to the `.env`-file, and it's wise to run this after you change something. Then, for instance, it will update passwords and ownership in Gitea for that user. From bddcb552e0e6bffbd3fc06568ae6d5c08a0da72f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 26 Apr 2024 08:10:34 +0200 Subject: [PATCH 27/72] Rename props --- .../components/RepoList/NewRepoList.tsx | 12 ++++++------ .../StudioTableWithPagination.mdx | 3 +-- .../StudioTableWithPagination.tsx | 8 ++++---- .../StudioTableWithPagination/mockData.tsx | 18 +++++++++--------- .../StudioTableWithPagination/utils.tsx | 8 +++++--- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx index 66f450f538e..663f3314f24 100644 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ b/frontend/dashboard/components/RepoList/NewRepoList.tsx @@ -29,12 +29,12 @@ export const NewRepoList = ({ repos }: NewRepoListProps): React.ReactElement => }); const columns = [ - { key: '0', value: '' }, - { key: '1', value: 'Navn' }, - { key: '2', value: 'Opprettet av' }, - { key: '3', value: 'Sist Endret' }, - { key: '4', value: 'Beskrivelse' }, - { key: '5', value: '' }, + { accessor: '0', value: '' }, + { accessor: '1', value: 'Navn' }, + { accessor: '2', value: 'Opprettet av' }, + { accessor: '3', value: 'Sist Endret' }, + { accessor: '4', value: 'Beskrivelse' }, + { accessor: '5', value: '' }, ]; return ( diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx index cf6bc9fb47d..c1f91fa0fc6 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx @@ -8,8 +8,7 @@ import * as StudioTableWithPaginationStories from './StudioTableWithPagination.s StudioTableWithPagination - StudioTableWithPagination brings together Digdir Designsystemet's `Table`, `Pagination` and - `NativeSelect` components. + StudioTableWithPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 0c44d3d0da3..3f793126ff0 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -2,7 +2,7 @@ import { Pagination } from '@digdir/design-system-react'; import React, { forwardRef, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; import { StudioTable } from './StudioTable'; -import { calcCurrentRows } from './utils'; +import { calcRowsToRender } from './utils'; import { useSortedRows } from '../../hooks/useSortedRows'; import { SelectRowsPerPage } from './SelectRowsPerPage'; @@ -31,14 +31,14 @@ export const StudioTableWithPagination = forwardRef< const { sortedRows, handleSorting } = useSortedRows(rows); const totalPages = Math.ceil(sortedRows.length / rowsPerPage); - const currentRows = calcCurrentRows(currentPage, rowsPerPage, sortedRows); - if (currentRows.length === 0) setCurrentPage(1); + const rowsToRender = calcRowsToRender(currentPage, rowsPerPage, sortedRows); + if (rowsToRender.length === 0) setCurrentPage(1); return ( <> } />, id: generateRandomId(6), - name: 'Lila Patel', + name: 'Lucas Wright', role: 'Software Engineer', status: 'Pending', link: , @@ -104,7 +104,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Ethan Nakamura', + name: 'Chloe Tanaka', role: 'Marketing Specialist', status: 'Approved', link: , @@ -112,7 +112,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Olivia Chen', + name: 'Emily Zhao', role: 'Data Analyst', status: 'Pending', link: , @@ -120,7 +120,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Noah Adebayo', + name: 'Jacob Martinez', role: 'UX Designer', status: 'Approved', link: , @@ -128,7 +128,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Sophia Ivanov', + name: 'Isabella Johnson', role: 'Product Manager', status: 'Pending', link: , @@ -136,7 +136,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'William Torres', + name: 'Elijah Lee', role: 'Sales Representative', status: 'Approved', link: , @@ -144,7 +144,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Ava Gupta', + name: 'Charlotte Silva', role: 'Human Resources Manager', status: 'Pending', link: , @@ -152,7 +152,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'James Kim', + name: 'Benjamin Lopez', role: 'Financial Analyst', status: 'Approved', link: , @@ -160,7 +160,7 @@ export const rows = [ { icon: } />, id: generateRandomId(6), - name: 'Mia Sánchez', + name: 'Amelia Schmidt', role: 'Customer Support Specialist', status: 'Pending', link: , diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx index 47da416bc2c..59f793a1d54 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx @@ -4,8 +4,10 @@ export const labelSizeMap = { large: 'medium', }; -export const calcCurrentRows = (currentPage, rowPerPage, rows) => { - const startIndex = (currentPage - 1) * rowPerPage; - const endIndex = startIndex + rowPerPage; +export const calcRowsToRender = (currentPage, rowsPerPage, rows) => { + if (rowsPerPage === 0) return rows; + + const startIndex = (currentPage - 1) * rowsPerPage; + const endIndex = startIndex + rowsPerPage; return rows.slice(startIndex, endIndex); }; From d385397b82427ddbe921b3697be76ce7f8361bf5 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 26 Apr 2024 08:24:04 +0200 Subject: [PATCH 28/72] Move StudioTable into StudioTableWithPagination --- .../StudioTableWithPagination/StudioTable.tsx | 48 ------------------- .../StudioTableWithPagination.mdx | 1 + .../StudioTableWithPagination.tsx | 48 ++++++++++++------- .../src/hooks/useSortedRows.tsx | 3 +- 4 files changed, 34 insertions(+), 66 deletions(-) delete mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx deleted file mode 100644 index d594da9808f..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTable.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import classes from './StudioTableWithPagination.module.css'; -import { Table } from '@digdir/design-system-react'; -import React, { forwardRef } from 'react'; -import { Columns, Rows, TableSize } from './StudioTableWithPagination'; - -type StudioTableProps = { - size: TableSize; - columns: Columns; - rows: Rows; - isSortable: boolean; - handleSorting?: (columnKey: string) => void; -}; - -export const StudioTable: React.FC = forwardRef< - HTMLTableElement, - StudioTableProps ->(({ size, columns, rows, isSortable, handleSorting }, ref) => { - const columnHasValue = (value) => { - return Boolean(value); - }; - - return ( -
- - - {columns.map(({ accessor, value }) => ( - handleSorting(accessor)} - > - {value} - - ))} - - - - {rows.map((row) => ( - - {columns.map(({ accessor }) => ( - {row[accessor]} - ))} - - ))} - -
- ); -}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx index c1f91fa0fc6..77d78daf9a2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx @@ -9,6 +9,7 @@ import * as StudioTableWithPaginationStories from './StudioTableWithPagination.s StudioTableWithPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. + It dynamically renders a table by passing an array of objects to the `column` and `row` props. The pagination can be removed by passing `0` to `initialRowsPerPage`. diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index 3f793126ff0..f44fd54970c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,20 +1,15 @@ -import { Pagination } from '@digdir/design-system-react'; +import { Pagination, Table } from '@digdir/design-system-react'; import React, { forwardRef, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; -import { StudioTable } from './StudioTable'; import { calcRowsToRender } from './utils'; import { useSortedRows } from '../../hooks/useSortedRows'; import { SelectRowsPerPage } from './SelectRowsPerPage'; -export type TableSize = 'small' | 'medium' | 'large'; -export type Rows = Record[]; -export type Columns = Record<'accessor' | 'value', string>[]; - type StudioTableWithPaginationProps = { - columns: Columns; - rows: Rows; + columns: Record<'accessor' | 'value', string>[]; + rows: Record[]; isSortable?: boolean; - size?: TableSize; + size?: 'small' | 'medium' | 'large'; initialRowsPerPage?: number; }; @@ -34,15 +29,36 @@ export const StudioTableWithPagination = forwardRef< const rowsToRender = calcRowsToRender(currentPage, rowsPerPage, sortedRows); if (rowsToRender.length === 0) setCurrentPage(1); + const columnHasValue = (value) => { + return Boolean(value); + }; + return ( <> - + + + + {columns.map(({ accessor, value }) => ( + handleSorting(accessor)} + > + {value} + + ))} + + + + {rowsToRender.map((row) => ( + + {columns.map(({ accessor }) => ( + {row[accessor]} + ))} + + ))} + +
{initialRowsPerPage > 0 && (
diff --git a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx index dc4e29b9a20..24c2d730919 100644 --- a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx +++ b/frontend/libs/studio-components/src/hooks/useSortedRows.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; -import { Rows } from '../components/StudioTableWithPagination/StudioTableWithPagination'; -export const useSortedRows = (rows: Rows) => { +export const useSortedRows = (rows) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); From ab44e575b5a5d787981e8f6d604d356f3907aa0a Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Fri, 26 Apr 2024 10:40:48 +0200 Subject: [PATCH 29/72] small tips and tricks --- .../SelectRowsPerPage.tsx | 30 ++-- .../StudioTableWithPagination.tsx | 133 +++++++++--------- .../StudioTableWithPagination/utils.tsx | 8 +- 3 files changed, 90 insertions(+), 81 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx index fafbbddcde2..b4737eb1b27 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx @@ -1,36 +1,44 @@ import classes from './StudioTableWithPagination.module.css'; import { Label, NativeSelect } from '@digdir/design-system-react'; -import React from 'react'; -import { labelSizeMap } from './utils'; +import React, { useId } from 'react'; -type LabelSize = 'small' | 'medium' | 'large' | 'xsmall'; +type LabelSize = 'small' | 'medium' | 'xsmall'; +type LabelSizeKeys = 'small' | 'medium' | 'large'; +export const labelSizeMap: Record = { + small: 'xsmall', + medium: 'small', + large: 'medium', +}; type SelectRowsPerPageProps = { + rowPerPageOptions?: number[]; setRowPerPage: (value: ((prevState: number) => number) | number) => void; size: 'small' | 'medium' | 'large'; }; export const SelectRowsPerPage = ({ + rowPerPageOptions = [5, 10, 20, 50, 100], setRowPerPage, size, }: SelectRowsPerPageProps): React.ReactElement => { - const labelSize = labelSizeMap[size] as LabelSize; + const labelId = useId(); + const labelSize = labelSizeMap[size]; return (
setRowPerPage(Number(e.target.value))} size={size} > - - - - - + {rowPerPageOptions.map((row) => ( + + ))} -
diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index f44fd54970c..a35f0dd06c9 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -1,84 +1,89 @@ import { Pagination, Table } from '@digdir/design-system-react'; import React, { forwardRef, useState } from 'react'; import classes from './StudioTableWithPagination.module.css'; -import { calcRowsToRender } from './utils'; +import { getRowsToRender } from './utils'; import { useSortedRows } from '../../hooks/useSortedRows'; import { SelectRowsPerPage } from './SelectRowsPerPage'; +export type Rows = Record[]; + +type PaginationTranslation = { + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; +}; + type StudioTableWithPaginationProps = { columns: Record<'accessor' | 'value', string>[]; - rows: Record[]; + rows: Rows; isSortable?: boolean; size?: 'small' | 'medium' | 'large'; - initialRowsPerPage?: number; + pagination: { + initialRowsPerPage?: number; + paginationTranslation: PaginationTranslation; + }; }; export const StudioTableWithPagination = forwardRef< HTMLTableElement, StudioTableWithPaginationProps ->( - ( - { columns, rows, isSortable = true, size = 'medium', initialRowsPerPage = 5 }, - ref, - ): React.ReactElement => { - const [currentPage, setCurrentPage] = useState(1); - const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); - const { sortedRows, handleSorting } = useSortedRows(rows); +>(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { + const { initialRowsPerPage = 5 } = pagination; + const { nextButtonText, previousButtonText, itemLabel } = pagination.paginationTranslation; - const totalPages = Math.ceil(sortedRows.length / rowsPerPage); - const rowsToRender = calcRowsToRender(currentPage, rowsPerPage, sortedRows); - if (rowsToRender.length === 0) setCurrentPage(1); + const [currentPage, setCurrentPage] = useState(1); + const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); + const { sortedRows, handleSorting } = useSortedRows(rows); - const columnHasValue = (value) => { - return Boolean(value); - }; + const totalPages = Math.ceil(sortedRows.length / rowsPerPage); + const rowsToRender = getRowsToRender(currentPage, rowsPerPage, sortedRows); + if (rowsToRender.length === 0) setCurrentPage(1); - return ( - <> - - - - {columns.map(({ accessor, value }) => ( - handleSorting(accessor)} - > - {value} - + return ( + <> +
+ + + {columns.map(({ accessor, value }) => ( + handleSorting(accessor)} + > + {value} + + ))} + + + + {rowsToRender.map((row) => ( + + {columns.map(({ accessor }) => ( + {row[accessor]} ))} - - - {rowsToRender.map((row) => ( - - {columns.map(({ accessor }) => ( - {row[accessor]} - ))} - - ))} - -
- {initialRowsPerPage > 0 && ( -
- - {totalPages > 1 && ( - `Side ${num}`} - hideLabels - compact - /> - )} -
- )} - - ); - }, -); + ))} + + + {initialRowsPerPage > 0 && ( +
+ + {totalPages > 1 && ( + + )} +
+ )} + + ); +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx index 59f793a1d54..1e6fbbeb6dc 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx @@ -1,10 +1,6 @@ -export const labelSizeMap = { - small: 'xsmall', - medium: 'small', - large: 'medium', -}; +import { Rows } from './StudioTableWithPagination'; -export const calcRowsToRender = (currentPage, rowsPerPage, rows) => { +export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { if (rowsPerPage === 0) return rows; const startIndex = (currentPage - 1) * rowsPerPage; From e00e7015c85c13d7bc3211e29a7695b14b971017 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 26 Apr 2024 10:52:53 +0200 Subject: [PATCH 30/72] Add minimal testing --- .../StudioTableWithPagination/StudioTableWithPagination.test.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx new file mode 100644 index 00000000000..e69de29bb2d From aea25d486d03533651e6c4621e3f167081fc873c Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 26 Apr 2024 10:53:26 +0200 Subject: [PATCH 31/72] Add minimal testing --- .../StudioTableWithPagination.test.tsx | 48 +++++++++++++++++++ .../StudioTableWithPagination.tsx | 6 +-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx index e69de29bb2d..ed169dea2a4 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx @@ -0,0 +1,48 @@ +import { act, render, screen } from '@testing-library/react'; +import React from 'react'; +import { StudioTableWithPagination } from './StudioTableWithPagination'; +import { columns, rows } from './mockData'; +// import userEvent from "@testing-library/user-event/"; + +describe('StudioTableWithPagination', () => { + it('should render the table with sorting and pagination', () => { + render(); + + expect(screen.getByRole('button', { name: 'Name' })); + expect(screen.getByRole('cell', { name: 'Lila Patel' })); + expect(screen.getByRole('combobox')); + expect(screen.getByRole('button', { name: 'Side 1' })); + expect(screen.getByRole('button', { name: 'Side 2' })); + }); + + it('should render the table without sorting and pagination', () => { + render( + , + ); + + expect(screen.getByRole('columnheader', { name: 'Name' })); + expect(screen.getByRole('cell', { name: 'Lila Patel' })); + expect(screen.queryByRole('combobox')).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Side 1' })).not.toBeInTheDocument(); + }); + + // it("should sort the columns", async () => { + // const user = userEvent.setup(); + // render(); + // + // await user.click(screen.getByRole("button", {name: "Name"})); + // const cellsAfterFirstClick = screen.getAllByRole("cell"); + // expect(cellsAfterFirstClick[2]).toHaveTextContent("Amelia Schmidt"); + // + // await act(async () => { + // await user.click(screen.getByRole("button", {name: "Name"})); + // }) + // const cellsAfterSecondClick = screen.getAllByRole("cell"); + // expect(cellsAfterSecondClick[2]).toHaveTextContent("William Torres"); + // }) +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx index f44fd54970c..73f42ea9492 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx @@ -29,10 +29,6 @@ export const StudioTableWithPagination = forwardRef< const rowsToRender = calcRowsToRender(currentPage, rowsPerPage, sortedRows); if (rowsToRender.length === 0) setCurrentPage(1); - const columnHasValue = (value) => { - return Boolean(value); - }; - return ( <> @@ -41,7 +37,7 @@ export const StudioTableWithPagination = forwardRef< {columns.map(({ accessor, value }) => ( handleSorting(accessor)} > {value} From 2594f0c3b8b55323bab63899d6e9e1fa5b164a60 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 26 Apr 2024 12:21:02 +0200 Subject: [PATCH 32/72] Undo changes in dashboard/components --- .../DataModelsRepoList/DatamodelsRepoList.tsx | 4 +- .../FavoriteReposList/FavoriteReposList.tsx | 2 - .../MakeCopyModal/MakeCopyModal.test.tsx | 33 ++++++------ .../MakeCopyModal/MakeCopyModal.tsx | 6 +-- .../components/MakeCopyModal/index.ts | 3 +- .../components/OrgRepoList/OrgReposList.tsx | 6 +-- .../RepoList/NewRepoList.module.css | 10 ---- .../components/RepoList/NewRepoList.tsx | 54 ------------------- .../components/RepoList/RepoList.test.tsx | 23 ++++---- .../components/RepoList/RepoList.tsx | 21 ++++---- .../RepoNameInput/RepoNameInput.tsx | 3 +- .../components/ResourceItem/index.ts | 3 +- .../ResourcesRepoList.test.tsx | 12 ++--- .../ResourcesRepoList/ResourcesRepoList.tsx | 6 +-- .../ServiceOwnerSelector.tsx | 18 ++----- .../StudioTableWithPagination.stories.tsx | 9 +++- .../StudioTableWithPagination.tsx | 6 +-- 17 files changed, 71 insertions(+), 148 deletions(-) delete mode 100644 frontend/dashboard/components/RepoList/NewRepoList.module.css delete mode 100644 frontend/dashboard/components/RepoList/NewRepoList.tsx diff --git a/frontend/dashboard/components/DataModelsRepoList/DatamodelsRepoList.tsx b/frontend/dashboard/components/DataModelsRepoList/DatamodelsRepoList.tsx index 30f91027633..efe6c5f2c56 100644 --- a/frontend/dashboard/components/DataModelsRepoList/DatamodelsRepoList.tsx +++ b/frontend/dashboard/components/DataModelsRepoList/DatamodelsRepoList.tsx @@ -4,8 +4,8 @@ import { getReposLabel } from '../../utils/repoUtils'; import { getUidFilter } from '../../utils/filterUtils'; import { useAugmentReposWithStarred } from '../../hooks/useAugmentReposWithStarred'; import { useTranslation } from 'react-i18next'; -import type { User } from 'app-shared/types/Repository'; -import type { Organization } from 'app-shared/types/Organization'; +import { User } from 'app-shared/types/Repository'; +import { Organization } from 'app-shared/types/Organization'; import { useSearchReposQuery } from 'dashboard/hooks/queries/useSearchReposQuery'; import { useSelectedContext } from 'dashboard/hooks/useSelectedContext'; import { Heading } from '@digdir/design-system-react'; diff --git a/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx b/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx index 7f63db98f08..8847a7eedfd 100644 --- a/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx +++ b/frontend/dashboard/components/FavoriteReposList/FavoriteReposList.tsx @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'; import { useStarredReposQuery } from '../../hooks/queries'; import { Heading } from '@digdir/design-system-react'; import { DATAGRID_DEFAULT_PAGE_SIZE } from 'dashboard/constants'; -import { NewRepoList } from '../RepoList/NewRepoList'; export const FavoriteReposList = () => { const { t } = useTranslation(); @@ -15,7 +14,6 @@ export const FavoriteReposList = () => { {t('dashboard.favourites')} - { test('should not show error message when clicking confirm and name is added', async () => { renderWithMockServices(); - await user.type(screen.getByRole('textbox'), 'new-repo-name'); - await user.click( - screen.getByRole('button', { - name: textMock('dashboard.make_copy'), - }), - ), - expect( - screen.queryByText(textMock('dashboard.field_cannot_be_empty')), - ).not.toBeInTheDocument(); + await act(() => user.type(screen.getByRole('textbox'), 'new-repo-name')); + await act(() => + user.click( + screen.getByRole('button', { + name: textMock('dashboard.make_copy'), + }), + ), + ); + + expect(screen.queryByText(textMock('dashboard.field_cannot_be_empty'))).not.toBeInTheDocument(); expect(queriesMock.copyApp).toHaveBeenCalledTimes(1); expect(queriesMock.copyApp).toHaveBeenCalledWith('org', 'app', 'new-repo-name'); }); @@ -52,7 +53,7 @@ describe('MakeCopyModal', () => { const confirmButton = screen.getByRole('button', { name: /dashboard\.make_copy/i, }); - await user.click(confirmButton); + await act(() => user.click(confirmButton)); const errorMessageElement = screen.getAllByText(textMock('dashboard.field_cannot_be_empty')); expect(errorMessageElement.length).toBeGreaterThan(0); }); @@ -63,8 +64,8 @@ describe('MakeCopyModal', () => { name: textMock('dashboard.make_copy'), }); const inputField = screen.getByRole('textbox'); - await user.type(inputField, 'this-new-name-is-way-too-long-to-be-valid'); - await user.click(confirmButton); + await act(() => user.type(inputField, 'this-new-name-is-way-too-long-to-be-valid')); + await act(() => user.click(confirmButton)); const errorMessageElements = screen.getAllByText( textMock('dashboard.service_name_is_too_long'), ); @@ -77,8 +78,8 @@ describe('MakeCopyModal', () => { name: textMock('dashboard.make_copy'), }); const inputField = screen.getByRole('textbox'); - await user.type(inputField, 'this name is invalid'); - await user.click(confirmButton); + await act(() => user.type(inputField, 'this name is invalid')); + await act(() => user.click(confirmButton)); const errorMessageElements = screen.getAllByText( textMock('dashboard.service_name_has_illegal_characters'), ); diff --git a/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.tsx b/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.tsx index 17e9966ceca..3839916ba53 100644 --- a/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.tsx +++ b/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.tsx @@ -10,7 +10,7 @@ import classes from './MakeCopyModal.module.css'; import { SimpleContainer } from 'app-shared/primitives'; import { useTranslation } from 'react-i18next'; import { useCopyAppMutation } from 'dashboard/hooks/mutations/useCopyAppMutation'; -import type { AxiosError } from 'axios'; +import { AxiosError } from 'axios'; import { ServerCodes } from 'app-shared/enums/ServerCodes'; export interface IMakeCopyModalProps { @@ -117,9 +117,7 @@ export const MakeCopyModal = ({ anchorEl, handleClose, serviceFullName }: IMakeC /> {errorMessage &&
{errorMessage}
} - {isCopyAppPending && ( - - )} + {isCopyAppPending && } ); diff --git a/frontend/dashboard/components/MakeCopyModal/index.ts b/frontend/dashboard/components/MakeCopyModal/index.ts index 383fa4f6d04..e36147335fe 100644 --- a/frontend/dashboard/components/MakeCopyModal/index.ts +++ b/frontend/dashboard/components/MakeCopyModal/index.ts @@ -1,2 +1 @@ -export type { IMakeCopyModalProps } from './MakeCopyModal'; -export { MakeCopyModal } from './MakeCopyModal'; +export { IMakeCopyModalProps, MakeCopyModal } from './MakeCopyModal'; diff --git a/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx b/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx index b7a3f3cc8c9..7d93ad1ae4e 100644 --- a/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx +++ b/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx @@ -3,15 +3,14 @@ import { RepoList } from '../RepoList'; import { getReposLabel } from '../../utils/repoUtils'; import { getUidFilter } from '../../utils/filterUtils'; import { useTranslation } from 'react-i18next'; -import type { User } from 'app-shared/types/Repository'; -import type { Organization } from 'app-shared/types/Organization'; +import { User } from 'app-shared/types/Repository'; +import { Organization } from 'app-shared/types/Organization'; import { useReposSearch } from 'dashboard/hooks/useReposSearch'; import { useSelectedContext } from 'dashboard/hooks/useSelectedContext'; import { Heading } from '@digdir/design-system-react'; import { DATAGRID_DEFAULT_PAGE_SIZE } from 'dashboard/constants'; import { useAugmentReposWithStarred } from 'dashboard/hooks/useAugmentReposWithStarred'; import { useStarredReposQuery } from 'dashboard/hooks/queries'; -import { NewRepoList } from '../RepoList/NewRepoList'; type OrgReposListProps = { user: User; @@ -43,7 +42,6 @@ export const OrgReposList = ({ user, organizations }: OrgReposListProps) => { {getReposLabel({ selectedContext, orgs: organizations, t })} - !repo.name.endsWith('-datamodels'))} /> !repo.name.endsWith('-datamodels'))} isLoading={isLoadingSearchResults || areStarredReposPending} diff --git a/frontend/dashboard/components/RepoList/NewRepoList.module.css b/frontend/dashboard/components/RepoList/NewRepoList.module.css deleted file mode 100644 index 917c05f0de8..00000000000 --- a/frontend/dashboard/components/RepoList/NewRepoList.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.actionLinks { - display: flex; - justify-content: flex-end; -} - -.repoIcon { - font-size: 2rem; - padding-right: 7px; - padding-bottom: 5px; -} diff --git a/frontend/dashboard/components/RepoList/NewRepoList.tsx b/frontend/dashboard/components/RepoList/NewRepoList.tsx deleted file mode 100644 index 663f3314f24..00000000000 --- a/frontend/dashboard/components/RepoList/NewRepoList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { StudioTableWithPagination } from '@studio/components/src/components/StudioTableWithPagination'; -import { DateUtils } from '@studio/pure-functions'; -import type { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; -import { ActionLinks } from './ActionLinks'; -import { FavoriteButton } from './FavoriteButton'; -import { Paragraph } from '@digdir/design-system-react'; - -type NewRepoListProps = { - repos: RepositoryWithStarred[]; -}; - -export const NewRepoList = ({ repos }: NewRepoListProps): React.ReactElement => { - const rows = []; - - repos?.map((repo) => { - const row = []; - - row.push( - , - repo.name, - repo.owner.full_name || repo.owner.login, - DateUtils.formatDateDDMMYYYY(repo.updated_at), - repo.description, - , - ); - - rows.push(row); - }); - - const columns = [ - { accessor: '0', value: '' }, - { accessor: '1', value: 'Navn' }, - { accessor: '2', value: 'Opprettet av' }, - { accessor: '3', value: 'Sist Endret' }, - { accessor: '4', value: 'Beskrivelse' }, - { accessor: '5', value: '' }, - ]; - - return ( - <> - {rows.length > 0 ? ( - - ) : ( - Ingen applikasjoner funnet - )} - - ); -}; diff --git a/frontend/dashboard/components/RepoList/RepoList.test.tsx b/frontend/dashboard/components/RepoList/RepoList.test.tsx index aab3273f61d..4a62888cc04 100644 --- a/frontend/dashboard/components/RepoList/RepoList.test.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.test.tsx @@ -1,11 +1,10 @@ import React from 'react'; -import { screen, render, within } from '@testing-library/react'; +import { act, screen, render, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MockServicesContextWrapper } from '../../dashboardTestUtils'; import { searchRepositoryResponseMock } from '../../data-mocks/searchRepositoryResponseMock'; -import type { IRepoListProps } from './RepoList'; -import { RepoList } from './RepoList'; -import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import { IRepoListProps, RepoList } from './RepoList'; +import { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { textMock } from '../../../testing/mocks/i18nMock'; import { repository } from 'app-shared/mocks/mocks'; import { nbNO } from '@mui/x-data-grid/locales'; @@ -62,7 +61,7 @@ describe('RepoList', () => { const sortBtn = document.querySelector( 'button[aria-label="' + localeText.columnHeaderSortIconLabel + '"]', ); - await user.click(sortBtn); + await act(() => user.click(sortBtn)); expect(handleSortMock).not.toHaveBeenCalled(); }); @@ -78,7 +77,7 @@ describe('RepoList', () => { const sortBtn = document.querySelector( 'button[aria-label="' + localeText.columnHeaderSortIconLabel + '"]', ); - await user.click(sortBtn); + await act(() => user.click(sortBtn)); expect(handleSortMock).toHaveBeenCalledWith([{ field: 'name', sort: 'asc' }], { reason: undefined, @@ -95,9 +94,7 @@ describe('RepoList', () => { ], isServerSort: true, }); - const unstar = await screen.findByRole('menuitem', { - name: textMock('dashboard.unstar', { appName: repository.name }), - }); + const unstar = await screen.findByRole('menuitem', { name: textMock('dashboard.unstar') }); const gridActionsCellItem = within(unstar).getByRole('img'); expect(gridActionsCellItem).toBeInTheDocument(); }); @@ -113,7 +110,7 @@ describe('RepoList', () => { name: localeText.MuiTablePagination.getItemAriaLabel('next'), }); expect(nextPageButton).toBeInTheDocument(); - await user.click(nextPageButton); + await act(() => user.click(nextPageButton)); expect(onPageChange).toHaveBeenCalledWith(1); @@ -121,7 +118,7 @@ describe('RepoList', () => { name: localeText.MuiTablePagination.getItemAriaLabel('previous'), }); expect(previousPageButton).toBeInTheDocument(); - await user.click(previousPageButton); + await act(() => user.click(previousPageButton)); expect(onPageChange).toHaveBeenCalledWith(1); }); @@ -138,10 +135,10 @@ describe('RepoList', () => { const pageSizeSelect = screen.getByRole('combobox', { name: localeText.MuiTablePagination.labelRowsPerPage.toString(), }); - await user.click(pageSizeSelect); + await act(() => user.click(pageSizeSelect)); const pageSizeOption = screen.getByRole('option', { name: newPageSize.toString() }); - await user.click(pageSizeOption); + await act(() => user.click(pageSizeOption)); expect(onPageSizeChange).toHaveBeenCalledWith(newPageSize); }); diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 23ab9ed0a0c..8b9b5fb627e 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -15,12 +15,15 @@ import type { RepositoryWithStarred } from 'dashboard/utils/repoUtils/repoUtils' import { MakeCopyModal } from '../MakeCopyModal'; import { getRepoEditUrl } from '../../utils/urlUtils'; import { useTranslation } from 'react-i18next'; -import type { DATAGRID_PAGE_SIZE_TYPE } from '../../constants'; -import { DATAGRID_DEFAULT_PAGE_SIZE, DATAGRID_PAGE_SIZE_OPTIONS } from '../../constants'; +import { + DATAGRID_DEFAULT_PAGE_SIZE, + DATAGRID_PAGE_SIZE_OPTIONS, + DATAGRID_PAGE_SIZE_TYPE, +} from '../../constants'; import classes from './RepoList.module.css'; -import type { User } from 'app-shared/types/Repository'; -import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; - +import { User } from 'app-shared/types/Repository'; +import { useSetStarredRepoMutation } from '../../hooks/mutations'; +import { useUnsetStarredRepoMutation } from '../../hooks/mutations'; import { PencilIcon, FilesIcon, @@ -133,9 +136,7 @@ export const RepoList = ({ key={repo.id} id={`fav-repo-${repo.id}`} onClick={handleToggleFav} - label={t(repo.hasStarred ? 'dashboard.unstar' : 'dashboard.star', { - appName: repo.name, - })} + label={repo.hasStarred ? t('dashboard.unstar') : t('dashboard.star')} icon={ repo.hasStarred ? ( @@ -202,7 +203,7 @@ export const RepoList = ({ className={cn(classes.actionLink, classes.repoLink)} icon={} key={`dashboard.repository${params.row.id}`} - label={t('dashboard.repository_in_list', { appName: repo })} + label={t('dashboard.repository')} onClick={() => (window.location.href = params.row.html_url)} showInMenu={false} edge='end' @@ -216,7 +217,7 @@ export const RepoList = ({ /> } key={`dashboard.edit_app${params.row.id}`} - label={t('dashboard.edit_app', { appName: repo })} + label={t('dashboard.edit_app')} onClick={() => (window.location.href = editUrl)} showInMenu={false} > diff --git a/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx b/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx index 8c33fda9c95..deb6e367e86 100644 --- a/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx +++ b/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import type { TextfieldProps } from '@digdir/design-system-react'; -import { Textfield } from '@digdir/design-system-react'; +import { Textfield, TextfieldProps } from '@digdir/design-system-react'; import { useTranslation } from 'react-i18next'; type RepoNameInputProps = { diff --git a/frontend/dashboard/components/ResourceItem/index.ts b/frontend/dashboard/components/ResourceItem/index.ts index 198b5f1a796..c83fd50e2c5 100644 --- a/frontend/dashboard/components/ResourceItem/index.ts +++ b/frontend/dashboard/components/ResourceItem/index.ts @@ -1,2 +1 @@ -export type { ResourceItemProps } from './ResourceItem'; -export { ResourceItem } from './ResourceItem'; +export { ResourceItem, ResourceItemProps } from './ResourceItem'; diff --git a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx index be8ddb29a86..6a897070e7a 100644 --- a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx +++ b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ResourcesRepoList } from './ResourcesRepoList'; -import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { textMock } from '../../../testing/mocks/i18nMock'; import { useParams } from 'react-router-dom'; -import type { User } from 'app-shared/types/Repository'; +import { User } from 'app-shared/types/Repository'; import { MockServicesContextWrapper } from 'dashboard/dashboardTestUtils'; const originalWindowLocation = window.location; @@ -25,7 +25,7 @@ const getResourceListResponse = [ en: '', }, createdBy: '', - lastChanged: new Date(), + lastChanged: new Date().toISOString(), hasPolicy: true, identifier: 'test-ressurs', }, @@ -77,7 +77,7 @@ describe('RepoList', () => { }); await waitFor(() => { - expect(screen.getByText(textMock('dashboard.loading_resource_list'))).toBeInTheDocument(); + expect(screen.getByText(textMock('general.loading'))).toBeInTheDocument(); }); }); @@ -141,7 +141,7 @@ describe('RepoList', () => { expect(screen.getByTestId('resource-table-wrapper')).toBeInTheDocument(); }); - await user.click(screen.getByText(textMock('resourceadm.dashboard_table_row_edit'))); + await act(() => user.click(screen.getByText(textMock('resourceadm.dashboard_table_row_edit')))); expect(window.location.assign).toHaveBeenCalledWith( '/resourceadm/ttd/ttd-resources/resource/test-ressurs/about', diff --git a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx index d7110b777cc..985d18747a7 100644 --- a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx +++ b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx @@ -5,12 +5,12 @@ import { ResourceTable } from 'resourceadm/components/ResourceTable'; import { useGetResourceListQuery } from 'resourceadm/hooks/queries/useGetResourceListQuery'; import { getResourceDashboardURL, getResourcePageURL } from 'resourceadm/utils/urlUtils'; import { getReposLabel } from 'dashboard/utils/repoUtils'; -import type { Organization } from 'app-shared/types/Organization'; +import { Organization } from 'app-shared/types/Organization'; import { useTranslation } from 'react-i18next'; import { StudioSpinner } from '@studio/components'; import { Alert, Heading, Link } from '@digdir/design-system-react'; import { useSearchReposQuery } from 'dashboard/hooks/queries'; -import type { User } from 'app-shared/types/Repository'; +import { User } from 'app-shared/types/Repository'; import { getUidFilter } from 'dashboard/utils/filterUtils'; type ResourcesRepoListProps = { @@ -64,7 +64,7 @@ export const ResourcesRepoList = ({ })} {isLoadingResourceList ? ( - + ) : (
diff --git a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx index a087f162f81..41d08ab8d38 100644 --- a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx +++ b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx @@ -1,10 +1,10 @@ import React, { useId } from 'react'; import { Label, NativeSelect } from '@digdir/design-system-react'; import { useTranslation } from 'react-i18next'; -import type { Organization } from 'app-shared/types/Organization'; -import type { User } from 'app-shared/types/Repository'; +import { Organization } from 'app-shared/types/Organization'; +import { User } from 'app-shared/types/Repository'; -export type ServiceOwnerSelectorProps = { +type ServiceOwnerSelectorProps = { selectedOrgOrUser: string; user: User; organizations: Organization[]; @@ -26,22 +26,12 @@ export const ServiceOwnerSelector = ({ const selectableOrganizations: SelectableItem[] = mapOrganizationToSelectableItems(organizations); const selectableOptions: SelectableItem[] = [selectableUser, ...selectableOrganizations]; - const defaultValue: string = - selectableOptions.find((item) => item.value === selectedOrgOrUser)?.value ?? - selectableUser.value; - return (
- + {selectableOptions.map(({ value, label }) => (
- {isCopyAppPending && } + {isCopyAppPending && ( + + )} ); diff --git a/frontend/dashboard/components/MakeCopyModal/index.ts b/frontend/dashboard/components/MakeCopyModal/index.ts index e36147335fe..383fa4f6d04 100644 --- a/frontend/dashboard/components/MakeCopyModal/index.ts +++ b/frontend/dashboard/components/MakeCopyModal/index.ts @@ -1 +1,2 @@ -export { IMakeCopyModalProps, MakeCopyModal } from './MakeCopyModal'; +export type { IMakeCopyModalProps } from './MakeCopyModal'; +export { MakeCopyModal } from './MakeCopyModal'; diff --git a/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx b/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx index 7d93ad1ae4e..58a8c6a0c0b 100644 --- a/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx +++ b/frontend/dashboard/components/OrgRepoList/OrgReposList.tsx @@ -3,8 +3,8 @@ import { RepoList } from '../RepoList'; import { getReposLabel } from '../../utils/repoUtils'; import { getUidFilter } from '../../utils/filterUtils'; import { useTranslation } from 'react-i18next'; -import { User } from 'app-shared/types/Repository'; -import { Organization } from 'app-shared/types/Organization'; +import type { User } from 'app-shared/types/Repository'; +import type { Organization } from 'app-shared/types/Organization'; import { useReposSearch } from 'dashboard/hooks/useReposSearch'; import { useSelectedContext } from 'dashboard/hooks/useSelectedContext'; import { Heading } from '@digdir/design-system-react'; diff --git a/frontend/dashboard/components/RepoList/RepoList.test.tsx b/frontend/dashboard/components/RepoList/RepoList.test.tsx index 4a62888cc04..aab3273f61d 100644 --- a/frontend/dashboard/components/RepoList/RepoList.test.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { act, screen, render, within } from '@testing-library/react'; +import { screen, render, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MockServicesContextWrapper } from '../../dashboardTestUtils'; import { searchRepositoryResponseMock } from '../../data-mocks/searchRepositoryResponseMock'; -import { IRepoListProps, RepoList } from './RepoList'; -import { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import type { IRepoListProps } from './RepoList'; +import { RepoList } from './RepoList'; +import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { textMock } from '../../../testing/mocks/i18nMock'; import { repository } from 'app-shared/mocks/mocks'; import { nbNO } from '@mui/x-data-grid/locales'; @@ -61,7 +62,7 @@ describe('RepoList', () => { const sortBtn = document.querySelector( 'button[aria-label="' + localeText.columnHeaderSortIconLabel + '"]', ); - await act(() => user.click(sortBtn)); + await user.click(sortBtn); expect(handleSortMock).not.toHaveBeenCalled(); }); @@ -77,7 +78,7 @@ describe('RepoList', () => { const sortBtn = document.querySelector( 'button[aria-label="' + localeText.columnHeaderSortIconLabel + '"]', ); - await act(() => user.click(sortBtn)); + await user.click(sortBtn); expect(handleSortMock).toHaveBeenCalledWith([{ field: 'name', sort: 'asc' }], { reason: undefined, @@ -94,7 +95,9 @@ describe('RepoList', () => { ], isServerSort: true, }); - const unstar = await screen.findByRole('menuitem', { name: textMock('dashboard.unstar') }); + const unstar = await screen.findByRole('menuitem', { + name: textMock('dashboard.unstar', { appName: repository.name }), + }); const gridActionsCellItem = within(unstar).getByRole('img'); expect(gridActionsCellItem).toBeInTheDocument(); }); @@ -110,7 +113,7 @@ describe('RepoList', () => { name: localeText.MuiTablePagination.getItemAriaLabel('next'), }); expect(nextPageButton).toBeInTheDocument(); - await act(() => user.click(nextPageButton)); + await user.click(nextPageButton); expect(onPageChange).toHaveBeenCalledWith(1); @@ -118,7 +121,7 @@ describe('RepoList', () => { name: localeText.MuiTablePagination.getItemAriaLabel('previous'), }); expect(previousPageButton).toBeInTheDocument(); - await act(() => user.click(previousPageButton)); + await user.click(previousPageButton); expect(onPageChange).toHaveBeenCalledWith(1); }); @@ -135,10 +138,10 @@ describe('RepoList', () => { const pageSizeSelect = screen.getByRole('combobox', { name: localeText.MuiTablePagination.labelRowsPerPage.toString(), }); - await act(() => user.click(pageSizeSelect)); + await user.click(pageSizeSelect); const pageSizeOption = screen.getByRole('option', { name: newPageSize.toString() }); - await act(() => user.click(pageSizeOption)); + await user.click(pageSizeOption); expect(onPageSizeChange).toHaveBeenCalledWith(newPageSize); }); diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index 8b9b5fb627e..23ab9ed0a0c 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -15,15 +15,12 @@ import type { RepositoryWithStarred } from 'dashboard/utils/repoUtils/repoUtils' import { MakeCopyModal } from '../MakeCopyModal'; import { getRepoEditUrl } from '../../utils/urlUtils'; import { useTranslation } from 'react-i18next'; -import { - DATAGRID_DEFAULT_PAGE_SIZE, - DATAGRID_PAGE_SIZE_OPTIONS, - DATAGRID_PAGE_SIZE_TYPE, -} from '../../constants'; +import type { DATAGRID_PAGE_SIZE_TYPE } from '../../constants'; +import { DATAGRID_DEFAULT_PAGE_SIZE, DATAGRID_PAGE_SIZE_OPTIONS } from '../../constants'; import classes from './RepoList.module.css'; -import { User } from 'app-shared/types/Repository'; -import { useSetStarredRepoMutation } from '../../hooks/mutations'; -import { useUnsetStarredRepoMutation } from '../../hooks/mutations'; +import type { User } from 'app-shared/types/Repository'; +import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; + import { PencilIcon, FilesIcon, @@ -136,7 +133,9 @@ export const RepoList = ({ key={repo.id} id={`fav-repo-${repo.id}`} onClick={handleToggleFav} - label={repo.hasStarred ? t('dashboard.unstar') : t('dashboard.star')} + label={t(repo.hasStarred ? 'dashboard.unstar' : 'dashboard.star', { + appName: repo.name, + })} icon={ repo.hasStarred ? ( @@ -203,7 +202,7 @@ export const RepoList = ({ className={cn(classes.actionLink, classes.repoLink)} icon={} key={`dashboard.repository${params.row.id}`} - label={t('dashboard.repository')} + label={t('dashboard.repository_in_list', { appName: repo })} onClick={() => (window.location.href = params.row.html_url)} showInMenu={false} edge='end' @@ -217,7 +216,7 @@ export const RepoList = ({ /> } key={`dashboard.edit_app${params.row.id}`} - label={t('dashboard.edit_app')} + label={t('dashboard.edit_app', { appName: repo })} onClick={() => (window.location.href = editUrl)} showInMenu={false} > diff --git a/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx b/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx index deb6e367e86..8c33fda9c95 100644 --- a/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx +++ b/frontend/dashboard/components/RepoNameInput/RepoNameInput.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Textfield, TextfieldProps } from '@digdir/design-system-react'; +import type { TextfieldProps } from '@digdir/design-system-react'; +import { Textfield } from '@digdir/design-system-react'; import { useTranslation } from 'react-i18next'; type RepoNameInputProps = { diff --git a/frontend/dashboard/components/ResourceItem/index.ts b/frontend/dashboard/components/ResourceItem/index.ts index c83fd50e2c5..198b5f1a796 100644 --- a/frontend/dashboard/components/ResourceItem/index.ts +++ b/frontend/dashboard/components/ResourceItem/index.ts @@ -1 +1,2 @@ -export { ResourceItem, ResourceItemProps } from './ResourceItem'; +export type { ResourceItemProps } from './ResourceItem'; +export { ResourceItem } from './ResourceItem'; diff --git a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx index 6a897070e7a..be8ddb29a86 100644 --- a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx +++ b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ResourcesRepoList } from './ResourcesRepoList'; -import { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { textMock } from '../../../testing/mocks/i18nMock'; import { useParams } from 'react-router-dom'; -import { User } from 'app-shared/types/Repository'; +import type { User } from 'app-shared/types/Repository'; import { MockServicesContextWrapper } from 'dashboard/dashboardTestUtils'; const originalWindowLocation = window.location; @@ -25,7 +25,7 @@ const getResourceListResponse = [ en: '', }, createdBy: '', - lastChanged: new Date().toISOString(), + lastChanged: new Date(), hasPolicy: true, identifier: 'test-ressurs', }, @@ -77,7 +77,7 @@ describe('RepoList', () => { }); await waitFor(() => { - expect(screen.getByText(textMock('general.loading'))).toBeInTheDocument(); + expect(screen.getByText(textMock('dashboard.loading_resource_list'))).toBeInTheDocument(); }); }); @@ -141,7 +141,7 @@ describe('RepoList', () => { expect(screen.getByTestId('resource-table-wrapper')).toBeInTheDocument(); }); - await act(() => user.click(screen.getByText(textMock('resourceadm.dashboard_table_row_edit')))); + await user.click(screen.getByText(textMock('resourceadm.dashboard_table_row_edit'))); expect(window.location.assign).toHaveBeenCalledWith( '/resourceadm/ttd/ttd-resources/resource/test-ressurs/about', diff --git a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx index 985d18747a7..d7110b777cc 100644 --- a/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx +++ b/frontend/dashboard/components/ResourcesRepoList/ResourcesRepoList.tsx @@ -5,12 +5,12 @@ import { ResourceTable } from 'resourceadm/components/ResourceTable'; import { useGetResourceListQuery } from 'resourceadm/hooks/queries/useGetResourceListQuery'; import { getResourceDashboardURL, getResourcePageURL } from 'resourceadm/utils/urlUtils'; import { getReposLabel } from 'dashboard/utils/repoUtils'; -import { Organization } from 'app-shared/types/Organization'; +import type { Organization } from 'app-shared/types/Organization'; import { useTranslation } from 'react-i18next'; import { StudioSpinner } from '@studio/components'; import { Alert, Heading, Link } from '@digdir/design-system-react'; import { useSearchReposQuery } from 'dashboard/hooks/queries'; -import { User } from 'app-shared/types/Repository'; +import type { User } from 'app-shared/types/Repository'; import { getUidFilter } from 'dashboard/utils/filterUtils'; type ResourcesRepoListProps = { @@ -64,7 +64,7 @@ export const ResourcesRepoList = ({ })} {isLoadingResourceList ? ( - + ) : (
diff --git a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx index 41d08ab8d38..a087f162f81 100644 --- a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx +++ b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx @@ -1,10 +1,10 @@ import React, { useId } from 'react'; import { Label, NativeSelect } from '@digdir/design-system-react'; import { useTranslation } from 'react-i18next'; -import { Organization } from 'app-shared/types/Organization'; -import { User } from 'app-shared/types/Repository'; +import type { Organization } from 'app-shared/types/Organization'; +import type { User } from 'app-shared/types/Repository'; -type ServiceOwnerSelectorProps = { +export type ServiceOwnerSelectorProps = { selectedOrgOrUser: string; user: User; organizations: Organization[]; @@ -26,12 +26,22 @@ export const ServiceOwnerSelector = ({ const selectableOrganizations: SelectableItem[] = mapOrganizationToSelectableItems(organizations); const selectableOptions: SelectableItem[] = [selectableUser, ...selectableOrganizations]; + const defaultValue: string = + selectableOptions.find((item) => item.value === selectedOrgOrUser)?.value ?? + selectableUser.value; + return (
- + {selectableOptions.map(({ value, label }) => (
+ + + {columns.map(({ accessor, value }) => ( + handleSorting(accessor)} + > + {value} + + ))} + + + + {rowsToRender.map((row) => ( + + {columns.map(({ accessor }) => ( + {row[accessor]} + ))} + + ))} + +
+ {initialRowsPerPage > 0 && ( +
+ + {totalPages > 1 && ( + + )} +
+ )} + + ); +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts new file mode 100644 index 00000000000..89acd928246 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts @@ -0,0 +1 @@ +export { StudioTableControlledInternally } from './StudioTableWithPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/mockData.tsx new file mode 100644 index 00000000000..17956329bcf --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/mockData.tsx @@ -0,0 +1,195 @@ +import { Button, Link } from '@digdir/design-system-react'; +import { FaceSmileFillIcon, FaceSmileIcon, StarFillIcon, StarIcon } from '@navikt/aksel-icons'; +import React from 'react'; + +const IconButton = ({ icon }) => ( + +); + +const AltinnLink = () => Link; + +function generateRandomId(length) { + let result = ''; + const characters = '0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +export const rows = [ + { + icon: } />, + id: generateRandomId(6), + name: 'Lila Patel', + role: 'Software Engineer', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Ethan Nakamura', + role: 'Marketing Specialist', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Olivia Chen', + role: 'Data Analyst', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Noah Adebayo', + role: 'UX Designer', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Sophia Ivanov', + role: 'Product Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'William Torres', + role: 'Sales Representative', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Ava Gupta', + role: 'Human Resources Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'James Kim', + role: 'Financial Analyst', + status: 'Approved', + link: AltinnLink(), + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Mia Sánchez', + role: 'Customer Support Specialist', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Lucas Wright', + role: 'Software Engineer', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Chloe Tanaka', + role: 'Marketing Specialist', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Emily Zhao', + role: 'Data Analyst', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Jacob Martinez', + role: 'UX Designer', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Isabella Johnson', + role: 'Product Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Elijah Lee', + role: 'Sales Representative', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Charlotte Silva', + role: 'Human Resources Manager', + status: 'Pending', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Benjamin Lopez', + role: 'Financial Analyst', + status: 'Approved', + link: , + }, + { + icon: } />, + id: generateRandomId(6), + name: 'Amelia Schmidt', + role: 'Customer Support Specialist', + status: 'Pending', + link: , + }, +]; + +export const columns = [ + { + accessor: 'icon', + value: '', + }, + { + accessor: 'id', + value: 'Employee ID', + }, + { + accessor: 'name', + value: 'Name', + }, + { + accessor: 'role', + value: 'Role', + }, + { + accessor: 'status', + value: 'Status', + }, + { + accessor: 'link', + value: '', + }, +]; diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx new file mode 100644 index 00000000000..1e6fbbeb6dc --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx @@ -0,0 +1,9 @@ +import { Rows } from './StudioTableWithPagination'; + +export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { + if (rowsPerPage === 0) return rows; + + const startIndex = (currentPage - 1) * rowsPerPage; + const endIndex = startIndex + rowsPerPage; + return rows.slice(startIndex, endIndex); +}; From 3fa9a58e1b0b5fe578b2321fddb930a497a795c1 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 29 Apr 2024 10:50:54 +0200 Subject: [PATCH 34/72] Make StudioTableRemotePagination functional in Storybook --- .../StudioTableControlledInternally.mdx | 15 --- ...tudioTableControlledInternally.stories.tsx | 34 ----- .../StudioTableControlledInternally.tsx | 89 ------------- .../StudioTableControlledExternally/index.ts | 1 - .../SelectRowsPerPage.tsx | 11 +- .../StudioTableLocalPagination.mdx} | 10 +- .../StudioTableLocalPagination.module.css} | 0 .../StudioTableLocalPagination.stories.tsx} | 12 +- .../StudioTableLocalPagination.tsx | 28 +++++ .../StudioTableLocalPagination/index.ts | 1 + .../mockData.tsx | 0 .../utils.tsx | 2 +- .../StudioTableRemotePagination.mdx | 19 +++ .../StudioTableRemotePagination.module.css} | 0 .../StudioTableRemotePagination.stories.tsx | 53 ++++++++ .../StudioTableRemotePagination.test.tsx} | 15 +-- .../StudioTableRemotePagination.tsx | 117 ++++++++++++++++++ .../StudioTableRemotePagination/index.ts | 1 + .../mockData.tsx | 54 ++++---- .../SelectRowsPerPage.tsx | 46 ------- .../StudioTableWithPagination.tsx | 89 ------------- .../StudioTableWithPagination/index.ts | 1 - .../StudioTableWithPagination/utils.tsx | 9 -- .../studio-components/src/components/index.ts | 3 +- ...{useSortedRows.tsx => useTableSorting.tsx} | 4 +- 25 files changed, 269 insertions(+), 345 deletions(-) delete mode 100644 frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.mdx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.stories.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts rename frontend/libs/studio-components/src/components/{StudioTableControlledExternally => StudioTableLocalPagination}/SelectRowsPerPage.tsx (78%) rename frontend/libs/studio-components/src/components/{StudioTableWithPagination/StudioTableWithPagination.mdx => StudioTableLocalPagination/StudioTableLocalPagination.mdx} (51%) rename frontend/libs/studio-components/src/components/{StudioTableControlledExternally/StudioTableControlledInternally.module.css => StudioTableLocalPagination/StudioTableLocalPagination.module.css} (100%) rename frontend/libs/studio-components/src/components/{StudioTableWithPagination/StudioTableWithPagination.stories.tsx => StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx} (63%) create mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/index.ts rename frontend/libs/studio-components/src/components/{StudioTableControlledExternally => StudioTableLocalPagination}/mockData.tsx (100%) rename frontend/libs/studio-components/src/components/{StudioTableControlledExternally => StudioTableLocalPagination}/utils.tsx (84%) create mode 100644 frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx rename frontend/libs/studio-components/src/components/{StudioTableWithPagination/StudioTableWithPagination.module.css => StudioTableRemotePagination/StudioTableRemotePagination.module.css} (100%) create mode 100644 frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx rename frontend/libs/studio-components/src/components/{StudioTableWithPagination/StudioTableWithPagination.test.tsx => StudioTableRemotePagination/StudioTableRemotePagination.test.tsx} (80%) create mode 100644 frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts rename frontend/libs/studio-components/src/components/{StudioTableWithPagination => StudioTableRemotePagination}/mockData.tsx (100%) delete mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts delete mode 100644 frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx rename frontend/libs/studio-components/src/hooks/{useSortedRows.tsx => useTableSorting.tsx} (90%) diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.mdx b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.mdx deleted file mode 100644 index 77d78daf9a2..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.mdx +++ /dev/null @@ -1,15 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; -import { Heading, Paragraph } from '@digdir/design-system-react'; -import * as StudioTableWithPaginationStories from './StudioTableWithPagination.stories'; - - - - - StudioTableWithPagination - - - StudioTableWithPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. - It dynamically renders a table by passing an array of objects to the `column` and `row` props. The pagination can be removed by passing `0` to `initialRowsPerPage`. - - - diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.stories.tsx deleted file mode 100644 index 9ba39294ee4..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import type { Meta, StoryFn } from '@storybook/react'; -import { StudioTableWithPagination } from './StudioTableWithPagination'; -import { columns, rows } from './mockData'; - -type Story = StoryFn; - -const meta: Meta = { - title: 'Studio/StudioTableWithPagination', - component: StudioTableWithPagination, - argTypes: { - pagination: { - control: 'radio', - options: ['true', 'false'], - }, - }, -}; -export const Preview: Story = (args): React.ReactElement => ; - -Preview.args = { - columns: columns, - rows: rows, - size: 'medium', - isSortable: true, - pagination: { - initialRowsPerPage: 5, - paginationTranslation: { - nextButtonText: 'Neste', - previousButtonText: 'Forrige', - itemLabel: (num) => `Side ${num}`, - }, - }, -}; -export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.tsx b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.tsx deleted file mode 100644 index 95473d6dda6..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Pagination, Table } from '@digdir/design-system-react'; -import React, { forwardRef, useState } from 'react'; -import classes from './StudioTableWithPagination.module.css'; -import { getRowsToRender } from './utils'; -import { useSortedRows } from '../../hooks/useSortedRows'; -import { SelectRowsPerPage } from './SelectRowsPerPage'; - -export type Rows = Record[]; - -type PaginationTranslation = { - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; -}; - -type StudioTableWithPaginationProps = { - columns: Record<'accessor' | 'value', string>[]; - rows: Rows; - isSortable?: boolean; - size?: 'small' | 'medium' | 'large'; - pagination?: { - initialRowsPerPage?: number; - paginationTranslation: PaginationTranslation; - }; -}; - -export const StudioTableWithPagination = forwardRef< - HTMLTableElement, - StudioTableWithPaginationProps ->(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - const { initialRowsPerPage = 5 } = pagination; - const { nextButtonText, previousButtonText, itemLabel } = pagination.paginationTranslation; - - const [currentPage, setCurrentPage] = useState(1); - const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); - const { sortedRows, handleSorting } = useSortedRows(rows); - - const totalPages = Math.ceil(sortedRows.length / rowsPerPage); - const rowsToRender = getRowsToRender(currentPage, rowsPerPage, sortedRows); - if (rowsToRender.length === 0) setCurrentPage(1); - - return ( - <> - - - - {columns.map(({ accessor, value }) => ( - handleSorting(accessor)} - > - {value} - - ))} - - - - {rowsToRender.map((row) => ( - - {columns.map(({ accessor }) => ( - {row[accessor]} - ))} - - ))} - -
- {initialRowsPerPage > 0 && ( -
- - {totalPages > 1 && ( - - )} -
- )} - - ); -}); diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts b/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts deleted file mode 100644 index 89acd928246..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioTableControlledInternally } from './StudioTableWithPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx similarity index 78% rename from frontend/libs/studio-components/src/components/StudioTableControlledExternally/SelectRowsPerPage.tsx rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx index b4737eb1b27..dbf83a7f6f2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/SelectRowsPerPage.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx @@ -1,14 +1,7 @@ -import classes from './StudioTableWithPagination.module.css'; +import classes from './StudioTableControlledInternally.module.css'; import { Label, NativeSelect } from '@digdir/design-system-react'; import React, { useId } from 'react'; - -type LabelSize = 'small' | 'medium' | 'xsmall'; -type LabelSizeKeys = 'small' | 'medium' | 'large'; -export const labelSizeMap: Record = { - small: 'xsmall', - medium: 'small', - large: 'medium', -}; +import { labelSizeMap } from '../StudioTableRemotePagination/StudioTableRemotePagination'; type SelectRowsPerPageProps = { rowPerPageOptions?: number[]; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx similarity index 51% rename from frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 77d78daf9a2..7a27689dfea 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -1,15 +1,15 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; -import * as StudioTableWithPaginationStories from './StudioTableWithPagination.stories'; +import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination.stories'; - + - StudioTableWithPagination + StudioTableLocalPagination - StudioTableWithPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. + StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. The pagination can be removed by passing `0` to `initialRowsPerPage`. - + diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.module.css b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css similarity index 100% rename from frontend/libs/studio-components/src/components/StudioTableControlledExternally/StudioTableControlledInternally.module.css rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx similarity index 63% rename from frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index 9ba39294ee4..776763122f3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -1,13 +1,13 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; -import { StudioTableWithPagination } from './StudioTableWithPagination'; +import { StudioTableLocalPagination } from './StudioTableLocalPagination'; import { columns, rows } from './mockData'; -type Story = StoryFn; +type Story = StoryFn; const meta: Meta = { - title: 'Studio/StudioTableWithPagination', - component: StudioTableWithPagination, + title: 'Studio/StudioTableLocalPagination', + component: StudioTableLocalPagination, argTypes: { pagination: { control: 'radio', @@ -15,7 +15,9 @@ const meta: Meta = { }, }, }; -export const Preview: Story = (args): React.ReactElement => ; +export const Preview: Story = (args): React.ReactElement => ( + +); Preview.args = { columns: columns, diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx new file mode 100644 index 00000000000..444364933d7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -0,0 +1,28 @@ +import React, { forwardRef } from 'react'; +import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; + +export type Rows = Record[]; + +type PaginationTranslation = { + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; +}; + +type StudioTableWithPaginationProps = { + columns: Record<'accessor' | 'value', string>[]; + rows: Rows; + isSortable?: boolean; + size?: 'small' | 'medium' | 'large'; + pagination?: { + initialRowsPerPage?: number; + paginationTranslation: PaginationTranslation; + }; +}; + +export const StudioTableLocalPagination = forwardRef< + HTMLTableElement, + StudioTableWithPaginationProps +>(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { + return ; +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/index.ts new file mode 100644 index 00000000000..378813de81c --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/index.ts @@ -0,0 +1 @@ +export { StudioTableLocalPagination } from './StudioTableLocalPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx similarity index 100% rename from frontend/libs/studio-components/src/components/StudioTableControlledExternally/mockData.tsx rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx similarity index 84% rename from frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx rename to frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx index 1e6fbbeb6dc..02ed7949fec 100644 --- a/frontend/libs/studio-components/src/components/StudioTableControlledExternally/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx @@ -1,4 +1,4 @@ -import { Rows } from './StudioTableWithPagination'; +import { Rows } from './StudioTableLocalPagination'; export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { if (rowsPerPage === 0) return rows; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx new file mode 100644 index 00000000000..8c5618736a3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -0,0 +1,19 @@ +import { Canvas, Meta } from '@storybook/blocks'; +import { Heading, Paragraph } from '@digdir/design-system-react'; +import * as StudioTableRemotePaginationStories from './StudioTableRemotePagination.stories'; + + + + + StudioTableRemotePagination + + + StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. + It dynamically renders a table by passing an array of objects to the `column` and `row` props. + + Use this component when the pagination logic is handled remotely (for example an external API). + + The pagination can be disabled by removing the `pagination` prop. + + + diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css similarity index 100% rename from frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.module.css rename to frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx new file mode 100644 index 00000000000..600d94a5a1c --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import type { Meta, StoryFn } from '@storybook/react'; +import { StudioTableRemotePagination } from './StudioTableRemotePagination'; +import { columns, rows } from './mockData'; +import { useTableSorting } from '../../hooks/useTableSorting'; +import { getRowsToRender } from '../StudioTableLocalPagination/utils'; + +type Story = StoryFn; + +const meta: Meta = { + title: 'Studio/StudioTableRemotePagination', + component: StudioTableRemotePagination, + argTypes: { + pagination: { + control: 'radio', + options: ['true', 'false'], + }, + }, +}; +export const Preview: Story = (args) => { + const [currentPage, setCurrentPage] = useState(1); + const [currentPageSize, setCurrentPageSize] = useState(2); + + const { handleSorting, sortedRows } = useTableSorting(rows); + + const rowsToRender = getRowsToRender(currentPage, currentPageSize, sortedRows); + const totalPages = Math.ceil(rows.length / currentPageSize); + if (rowsToRender.length === 0) setCurrentPage(1); + + const paginationProps = { + currentPage, + totalPages, + pageSize: currentPageSize, + pageSizeOptions: [2, 5, 10, 20, 50], + onPageChange: setCurrentPage, + onPageSizeChange: setCurrentPageSize, + itemLabel: (num) => `Side ${num}`, + nextButtonText: 'Neste', + previousButtonText: 'Forrige', + }; + + return ( + + ); +}; + +export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx similarity index 80% rename from frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx rename to frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index ed169dea2a4..2522df69260 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -1,12 +1,12 @@ -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import React from 'react'; -import { StudioTableWithPagination } from './StudioTableWithPagination'; +import { StudioTableRemotePagination } from './StudioTableRemotePagination'; import { columns, rows } from './mockData'; // import userEvent from "@testing-library/user-event/"; describe('StudioTableWithPagination', () => { it('should render the table with sorting and pagination', () => { - render(); + render(); expect(screen.getByRole('button', { name: 'Name' })); expect(screen.getByRole('cell', { name: 'Lila Patel' })); @@ -16,14 +16,7 @@ describe('StudioTableWithPagination', () => { }); it('should render the table without sorting and pagination', () => { - render( - , - ); + render(); expect(screen.getByRole('columnheader', { name: 'Name' })); expect(screen.getByRole('cell', { name: 'Lila Patel' })); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx new file mode 100644 index 00000000000..8c92cbf634c --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -0,0 +1,117 @@ +import { Label, NativeSelect, Pagination, Table } from '@digdir/design-system-react'; +import React, { forwardRef, useId } from 'react'; +import classes from './StudioTableRemotePagination.module.css'; + +type LabelSize = 'small' | 'medium' | 'xsmall'; +type LabelSizeKeys = 'small' | 'medium' | 'large'; +export const labelSizeMap: Record = { + small: 'xsmall', + medium: 'small', + large: 'medium', +}; + +export type Rows = Record[]; + +type StudioTableRemotePaginationProps = { + columns: Record<'accessor' | 'value', string>[]; + rows: Rows; + size?: 'small' | 'medium' | 'large'; + onSortClick?: (columnKey: string | number) => void; + pagination?: { + currentPage: number; + totalPages: number; + pageSize: number; + pageSizeOptions: number[]; + onPageChange: (currentPage: number) => void; + onPageSizeChange: (currentSize: number) => void; + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; + }; +}; + +export const StudioTableRemotePagination = forwardRef< + HTMLTableElement, + StudioTableRemotePaginationProps +>(({ columns, rows, size = 'medium', onSortClick, pagination }, ref): React.ReactElement => { + const isSortable = !!onSortClick; + const isPaginationActive = !!pagination; + + const { + currentPage, + totalPages, + pageSizeOptions, + onPageChange: handlePageChange, + onPageSizeChange: handlePageSizeChange, + nextButtonText, + previousButtonText, + itemLabel, + } = pagination; + + const labelId = useId(); + const labelSize = labelSizeMap[size]; + + return ( + <> + + + + {columns.map(({ accessor, value }) => ( + onSortClick(accessor)} + > + {value} + + ))} + + + + {rows.map((row) => ( + + {columns.map(({ accessor }) => ( + {row[accessor]} + ))} + + ))} + +
+ {isPaginationActive && ( +
+
+ handlePageSizeChange(Number(e.target.value))} + size={size} + > + {pageSizeOptions.map((rows) => ( + + ))} + + +
+ {totalPages > 1 && ( + + )} +
+ )} + + ); +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts new file mode 100644 index 00000000000..e11dc7695c1 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts @@ -0,0 +1 @@ +export { StudioTableRemotePagination } from './StudioTableRemotePagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx similarity index 100% rename from frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx rename to frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index 17956329bcf..374ba77ae44 100644 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -20,6 +20,33 @@ function generateRandomId(length) { return result; } +export const columns = [ + { + accessor: 'icon', + value: '', + }, + { + accessor: 'id', + value: 'Employee ID', + }, + { + accessor: 'name', + value: 'Name', + }, + { + accessor: 'role', + value: 'Role', + }, + { + accessor: 'status', + value: 'Status', + }, + { + accessor: 'link', + value: '', + }, +]; + export const rows = [ { icon: } />, @@ -166,30 +193,3 @@ export const rows = [ link: , }, ]; - -export const columns = [ - { - accessor: 'icon', - value: '', - }, - { - accessor: 'id', - value: 'Employee ID', - }, - { - accessor: 'name', - value: 'Name', - }, - { - accessor: 'role', - value: 'Role', - }, - { - accessor: 'status', - value: 'Status', - }, - { - accessor: 'link', - value: '', - }, -]; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx deleted file mode 100644 index b4737eb1b27..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/SelectRowsPerPage.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import classes from './StudioTableWithPagination.module.css'; -import { Label, NativeSelect } from '@digdir/design-system-react'; -import React, { useId } from 'react'; - -type LabelSize = 'small' | 'medium' | 'xsmall'; -type LabelSizeKeys = 'small' | 'medium' | 'large'; -export const labelSizeMap: Record = { - small: 'xsmall', - medium: 'small', - large: 'medium', -}; - -type SelectRowsPerPageProps = { - rowPerPageOptions?: number[]; - setRowPerPage: (value: ((prevState: number) => number) | number) => void; - size: 'small' | 'medium' | 'large'; -}; - -export const SelectRowsPerPage = ({ - rowPerPageOptions = [5, 10, 20, 50, 100], - setRowPerPage, - size, -}: SelectRowsPerPageProps): React.ReactElement => { - const labelId = useId(); - const labelSize = labelSizeMap[size]; - - return ( -
- setRowPerPage(Number(e.target.value))} - size={size} - > - {rowPerPageOptions.map((row) => ( - - ))} - - -
- ); -}; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx deleted file mode 100644 index 95473d6dda6..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/StudioTableWithPagination.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Pagination, Table } from '@digdir/design-system-react'; -import React, { forwardRef, useState } from 'react'; -import classes from './StudioTableWithPagination.module.css'; -import { getRowsToRender } from './utils'; -import { useSortedRows } from '../../hooks/useSortedRows'; -import { SelectRowsPerPage } from './SelectRowsPerPage'; - -export type Rows = Record[]; - -type PaginationTranslation = { - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; -}; - -type StudioTableWithPaginationProps = { - columns: Record<'accessor' | 'value', string>[]; - rows: Rows; - isSortable?: boolean; - size?: 'small' | 'medium' | 'large'; - pagination?: { - initialRowsPerPage?: number; - paginationTranslation: PaginationTranslation; - }; -}; - -export const StudioTableWithPagination = forwardRef< - HTMLTableElement, - StudioTableWithPaginationProps ->(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - const { initialRowsPerPage = 5 } = pagination; - const { nextButtonText, previousButtonText, itemLabel } = pagination.paginationTranslation; - - const [currentPage, setCurrentPage] = useState(1); - const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); - const { sortedRows, handleSorting } = useSortedRows(rows); - - const totalPages = Math.ceil(sortedRows.length / rowsPerPage); - const rowsToRender = getRowsToRender(currentPage, rowsPerPage, sortedRows); - if (rowsToRender.length === 0) setCurrentPage(1); - - return ( - <> - - - - {columns.map(({ accessor, value }) => ( - handleSorting(accessor)} - > - {value} - - ))} - - - - {rowsToRender.map((row) => ( - - {columns.map(({ accessor }) => ( - {row[accessor]} - ))} - - ))} - -
- {initialRowsPerPage > 0 && ( -
- - {totalPages > 1 && ( - - )} -
- )} - - ); -}); diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts deleted file mode 100644 index 9d11bc50d89..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioTableWithPagination } from './StudioTableWithPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx deleted file mode 100644 index 1e6fbbeb6dc..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableWithPagination/utils.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Rows } from './StudioTableWithPagination'; - -export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { - if (rowsPerPage === 0) return rows; - - const startIndex = (currentPage - 1) * rowsPerPage; - const endIndex = startIndex + rowsPerPage; - return rows.slice(startIndex, endIndex); -}; diff --git a/frontend/libs/studio-components/src/components/index.ts b/frontend/libs/studio-components/src/components/index.ts index e731d4a05e2..e98bbc2069f 100644 --- a/frontend/libs/studio-components/src/components/index.ts +++ b/frontend/libs/studio-components/src/components/index.ts @@ -18,7 +18,8 @@ export * from './StudioPageSpinner'; export * from './StudioProperty'; export * from './StudioSectionHeader'; export * from './StudioSpinner'; -export * from './StudioTableWithPagination'; +export * from './StudioTableRemotePagination'; +export * from './StudioTableLocalPagination'; export * from './StudioTextarea'; export * from './StudioTextfield'; export * from './StudioToggleableTextfield'; diff --git a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx similarity index 90% rename from frontend/libs/studio-components/src/hooks/useSortedRows.tsx rename to frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 24c2d730919..242cce3f635 100644 --- a/frontend/libs/studio-components/src/hooks/useSortedRows.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -export const useSortedRows = (rows) => { +export const useTableSorting = (rows) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); @@ -8,7 +8,7 @@ export const useSortedRows = (rows) => { setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); }; - const handleSorting = (columnKey) => { + const handleSorting = (columnKey: string) => { if (sortColumn === columnKey) { toggleSortDirection(); } else { From 866fcfc70521a38c216c57fce1e5f4fd919bf76e Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 29 Apr 2024 11:40:12 +0200 Subject: [PATCH 35/72] Small fixes --- .../StudioTableLocalPagination.tsx | 3 +-- .../components/StudioTableLocalPagination/utils.tsx | 2 +- .../StudioTableRemotePagination.mdx | 2 +- .../StudioTableRemotePagination.stories.tsx | 10 +++++----- .../StudioTableRemotePagination.tsx | 2 ++ .../studio-components/src/hooks/useTableSorting.tsx | 3 ++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 444364933d7..d5b9445457b 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,7 +1,6 @@ import React, { forwardRef } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; - -export type Rows = Record[]; +import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; type PaginationTranslation = { nextButtonText: string; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx index 02ed7949fec..275f14a977a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx @@ -1,4 +1,4 @@ -import { Rows } from './StudioTableLocalPagination'; +import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { if (rowsPerPage === 0) return rows; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 8c5618736a3..70256f57bdb 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -11,7 +11,7 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. - Use this component when the pagination logic is handled remotely (for example an external API). + Use this component when the pagination logic is handled remotely (for example via an external API). The pagination can be disabled by removing the `pagination` prop. diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 600d94a5a1c..724c59ac328 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -19,21 +19,21 @@ const meta: Meta = { }; export const Preview: Story = (args) => { const [currentPage, setCurrentPage] = useState(1); - const [currentPageSize, setCurrentPageSize] = useState(2); + const [pageSize, setPageSize] = useState(5); const { handleSorting, sortedRows } = useTableSorting(rows); - const rowsToRender = getRowsToRender(currentPage, currentPageSize, sortedRows); - const totalPages = Math.ceil(rows.length / currentPageSize); + const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); + const totalPages = Math.ceil(rows.length / pageSize); if (rowsToRender.length === 0) setCurrentPage(1); const paginationProps = { currentPage, totalPages, - pageSize: currentPageSize, + pageSize: pageSize, pageSizeOptions: [2, 5, 10, 20, 50], onPageChange: setCurrentPage, - onPageSizeChange: setCurrentPageSize, + onPageSizeChange: setPageSize, itemLabel: (num) => `Side ${num}`, nextButtonText: 'Neste', previousButtonText: 'Forrige', diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 8c92cbf634c..1b24caaa853 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -40,6 +40,7 @@ export const StudioTableRemotePagination = forwardRef< const { currentPage, totalPages, + pageSize, pageSizeOptions, onPageChange: handlePageChange, onPageSizeChange: handlePageSizeChange, @@ -83,6 +84,7 @@ export const StudioTableRemotePagination = forwardRef< handlePageSizeChange(Number(e.target.value))} size={size} > diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 242cce3f635..196ae1082eb 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; +import { Rows } from '../components/StudioTableRemotePagination/StudioTableRemotePagination'; -export const useTableSorting = (rows) => { +export const useTableSorting = (rows: Rows) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); From 7013e21769bd2315229366b80198e444693b8e56 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 29 Apr 2024 11:56:42 +0200 Subject: [PATCH 36/72] Make StudioTableLocalPagination functional in Storybook --- .../StudioTableLocalPagination.stories.tsx | 10 ++-- .../StudioTableLocalPagination.tsx | 54 ++++++++++++++----- .../StudioTableRemotePagination.stories.tsx | 3 +- .../StudioTableRemotePagination.tsx | 3 -- 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index 776763122f3..aa0667042ca 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -25,12 +25,10 @@ Preview.args = { size: 'medium', isSortable: true, pagination: { - initialRowsPerPage: 5, - paginationTranslation: { - nextButtonText: 'Neste', - previousButtonText: 'Forrige', - itemLabel: (num) => `Side ${num}`, - }, + pageSizeOptions: [5, 10, 20, 50], + nextButtonText: 'Neste', + previousButtonText: 'Forrige', + itemLabel: (num) => `Side ${num}`, }, }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index d5b9445457b..cb6b5d6bdf3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,27 +1,55 @@ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; +import { useTableSorting } from '../../hooks/useTableSorting'; +import { getRowsToRender } from './utils'; -type PaginationTranslation = { - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; -}; - -type StudioTableWithPaginationProps = { +type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; rows: Rows; - isSortable?: boolean; size?: 'small' | 'medium' | 'large'; + isSortable?: boolean; pagination?: { - initialRowsPerPage?: number; - paginationTranslation: PaginationTranslation; + pageSizeOptions: number[]; + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; }; }; export const StudioTableLocalPagination = forwardRef< HTMLTableElement, - StudioTableWithPaginationProps + StudioTableLocalPaginationProps >(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - return ; + const { pageSizeOptions, itemLabel, nextButtonText, previousButtonText } = pagination; + + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(pageSizeOptions[0]); + + const { handleSorting, sortedRows } = useTableSorting(rows); + + const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); + const totalPages = Math.ceil(rows.length / pageSize); + if (rowsToRender.length === 0) setCurrentPage(1); + + const paginationProps = { + currentPage, + totalPages, + pageSizeOptions, + onPageChange: setCurrentPage, + onPageSizeChange: setPageSize, + itemLabel, + nextButtonText, + previousButtonText, + }; + + return ( + + ); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 724c59ac328..cedb84bc084 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -30,8 +30,7 @@ export const Preview: Story = (args) => { const paginationProps = { currentPage, totalPages, - pageSize: pageSize, - pageSizeOptions: [2, 5, 10, 20, 50], + pageSizeOptions: [5, 10, 20, 50], onPageChange: setCurrentPage, onPageSizeChange: setPageSize, itemLabel: (num) => `Side ${num}`, diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 1b24caaa853..c3e5f339e0a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -20,7 +20,6 @@ type StudioTableRemotePaginationProps = { pagination?: { currentPage: number; totalPages: number; - pageSize: number; pageSizeOptions: number[]; onPageChange: (currentPage: number) => void; onPageSizeChange: (currentSize: number) => void; @@ -40,7 +39,6 @@ export const StudioTableRemotePagination = forwardRef< const { currentPage, totalPages, - pageSize, pageSizeOptions, onPageChange: handlePageChange, onPageSizeChange: handlePageSizeChange, @@ -84,7 +82,6 @@ export const StudioTableRemotePagination = forwardRef< handlePageSizeChange(Number(e.target.value))} size={size} > From 477a21028b22262cf4092f3a40dfb759cb5ac9bd Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 29 Apr 2024 16:34:16 +0200 Subject: [PATCH 37/72] Fix both tables, so that they work in all cases with or without pagination and sorting --- .../StudioTableLocalPagination.mdx | 6 +- .../StudioTableLocalPagination.stories.tsx | 2 +- .../StudioTableLocalPagination.tsx | 28 ++- .../StudioTableLocalPagination/mockData.tsx | 195 ------------------ .../StudioTableLocalPagination/utils.tsx | 8 +- .../StudioTableRemotePagination.mdx | 4 +- .../StudioTableRemotePagination.stories.tsx | 2 + .../StudioTableRemotePagination.tsx | 26 +-- 8 files changed, 48 insertions(+), 223 deletions(-) delete mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 7a27689dfea..313494c10dd 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -9,7 +9,11 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. - It dynamically renders a table by passing an array of objects to the `column` and `row` props. The pagination can be removed by passing `0` to `initialRowsPerPage`. + It dynamically renders a table by passing an array of objects to the `column` and `row` props. + + The pagination can be disabled by removing the `pagination` prop. + + Use this component when you want the component to handle the pagination for you. diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index aa0667042ca..3b761a9da7e 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { StudioTableLocalPagination } from './StudioTableLocalPagination'; -import { columns, rows } from './mockData'; +import { columns, rows } from '../StudioTableRemotePagination/mockData'; type Story = StoryFn; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index cb6b5d6bdf3..dfcbd5dcf45 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useState } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; import { useTableSorting } from '../../hooks/useTableSorting'; @@ -21,18 +21,29 @@ export const StudioTableLocalPagination = forwardRef< HTMLTableElement, StudioTableLocalPaginationProps >(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - const { pageSizeOptions, itemLabel, nextButtonText, previousButtonText } = pagination; - + const { pageSizeOptions, itemLabel, nextButtonText, previousButtonText } = pagination || {}; const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(pageSizeOptions[0]); + const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); + + let handleSorting: (columnKey: string) => void; + let sortedRows: Rows; + let rowsToRender: Rows; + + if (isSortable) { + ({ handleSorting, sortedRows } = useTableSorting(rows)); + rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); + } else { + handleSorting = undefined; + rowsToRender = getRowsToRender(currentPage, pageSize, rows); + } - const { handleSorting, sortedRows } = useTableSorting(rows); + useEffect(() => { + if (rowsToRender.length === 0) setCurrentPage(1); + }, [rowsToRender]); - const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); const totalPages = Math.ceil(rows.length / pageSize); - if (rowsToRender.length === 0) setCurrentPage(1); - const paginationProps = { + const paginationProps = pagination && { currentPage, totalPages, pageSizeOptions, @@ -50,6 +61,7 @@ export const StudioTableLocalPagination = forwardRef< size={size} onSortClick={handleSorting} pagination={paginationProps} + ref={ref} /> ); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx deleted file mode 100644 index 17956329bcf..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/mockData.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { Button, Link } from '@digdir/design-system-react'; -import { FaceSmileFillIcon, FaceSmileIcon, StarFillIcon, StarIcon } from '@navikt/aksel-icons'; -import React from 'react'; - -const IconButton = ({ icon }) => ( - -); - -const AltinnLink = () => Link; - -function generateRandomId(length) { - let result = ''; - const characters = '0123456789'; - const charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - -export const rows = [ - { - icon: } />, - id: generateRandomId(6), - name: 'Lila Patel', - role: 'Software Engineer', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Ethan Nakamura', - role: 'Marketing Specialist', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Olivia Chen', - role: 'Data Analyst', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Noah Adebayo', - role: 'UX Designer', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Sophia Ivanov', - role: 'Product Manager', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'William Torres', - role: 'Sales Representative', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Ava Gupta', - role: 'Human Resources Manager', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'James Kim', - role: 'Financial Analyst', - status: 'Approved', - link: AltinnLink(), - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Mia Sánchez', - role: 'Customer Support Specialist', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Lucas Wright', - role: 'Software Engineer', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Chloe Tanaka', - role: 'Marketing Specialist', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Emily Zhao', - role: 'Data Analyst', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Jacob Martinez', - role: 'UX Designer', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Isabella Johnson', - role: 'Product Manager', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Elijah Lee', - role: 'Sales Representative', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Charlotte Silva', - role: 'Human Resources Manager', - status: 'Pending', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Benjamin Lopez', - role: 'Financial Analyst', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Amelia Schmidt', - role: 'Customer Support Specialist', - status: 'Pending', - link: , - }, -]; - -export const columns = [ - { - accessor: 'icon', - value: '', - }, - { - accessor: 'id', - value: 'Employee ID', - }, - { - accessor: 'name', - value: 'Name', - }, - { - accessor: 'role', - value: 'Role', - }, - { - accessor: 'status', - value: 'Status', - }, - { - accessor: 'link', - value: '', - }, -]; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx index 275f14a977a..456da476adc 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx @@ -1,9 +1,9 @@ import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; -export const getRowsToRender = (currentPage: number, rowsPerPage: number, rows: Rows): Rows => { - if (rowsPerPage === 0) return rows; +export const getRowsToRender = (currentPage: number, pageSize: number, rows: Rows): Rows => { + if (!!pageSize === false) return rows; - const startIndex = (currentPage - 1) * rowsPerPage; - const endIndex = startIndex + rowsPerPage; + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; return rows.slice(startIndex, endIndex); }; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 70256f57bdb..8a2637c7209 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -11,9 +11,9 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. - Use this component when the pagination logic is handled remotely (for example via an external API). - The pagination can be disabled by removing the `pagination` prop. + + Use this component when the pagination logic is handled remotely (for example via an external API). diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index cedb84bc084..9a73ce534a7 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -18,6 +18,7 @@ const meta: Meta = { }, }; export const Preview: Story = (args) => { + // Start simulation of API logic const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(5); @@ -37,6 +38,7 @@ export const Preview: Story = (args) => { nextButtonText: 'Neste', previousButtonText: 'Forrige', }; + // End simulation of API logic return ( = { export type Rows = Record[]; +export type PaginationProps = { + currentPage: number; + totalPages: number; + pageSizeOptions: number[]; + onPageChange: (currentPage: number) => void; + onPageSizeChange: (currentSize: number) => void; + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; +}; + type StudioTableRemotePaginationProps = { columns: Record<'accessor' | 'value', string>[]; rows: Rows; size?: 'small' | 'medium' | 'large'; - onSortClick?: (columnKey: string | number) => void; - pagination?: { - currentPage: number; - totalPages: number; - pageSizeOptions: number[]; - onPageChange: (currentPage: number) => void; - onPageSizeChange: (currentSize: number) => void; - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; - }; + onSortClick?: (columnKey: string) => void; + pagination?: PaginationProps; }; export const StudioTableRemotePagination = forwardRef< @@ -45,7 +47,7 @@ export const StudioTableRemotePagination = forwardRef< nextButtonText, previousButtonText, itemLabel, - } = pagination; + } = pagination || {}; const labelId = useId(); const labelSize = labelSizeMap[size]; From 49c32dce82a3364ca3514c7a1e7664ff378c3287 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 29 Apr 2024 16:42:52 +0200 Subject: [PATCH 38/72] Remove unused files --- .../SelectRowsPerPage.tsx | 39 ------------------- .../StudioTableLocalPagination.module.css | 30 -------------- .../StudioTableLocalPagination.tsx | 2 +- .../StudioTableRemotePagination.module.css | 1 - .../StudioTableRemotePagination.stories.tsx | 2 +- .../utils.tsx | 2 +- 6 files changed, 3 insertions(+), 73 deletions(-) delete mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css rename frontend/libs/studio-components/src/components/{StudioTableLocalPagination => StudioTableRemotePagination}/utils.tsx (76%) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx deleted file mode 100644 index dbf83a7f6f2..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/SelectRowsPerPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import classes from './StudioTableControlledInternally.module.css'; -import { Label, NativeSelect } from '@digdir/design-system-react'; -import React, { useId } from 'react'; -import { labelSizeMap } from '../StudioTableRemotePagination/StudioTableRemotePagination'; - -type SelectRowsPerPageProps = { - rowPerPageOptions?: number[]; - setRowPerPage: (value: ((prevState: number) => number) | number) => void; - size: 'small' | 'medium' | 'large'; -}; - -export const SelectRowsPerPage = ({ - rowPerPageOptions = [5, 10, 20, 50, 100], - setRowPerPage, - size, -}: SelectRowsPerPageProps): React.ReactElement => { - const labelId = useId(); - const labelSize = labelSizeMap[size]; - - return ( -
- setRowPerPage(Number(e.target.value))} - size={size} - > - {rowPerPageOptions.map((row) => ( - - ))} - - -
- ); -}; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css deleted file mode 100644 index c895ea1411e..00000000000 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.module.css +++ /dev/null @@ -1,30 +0,0 @@ -.table { - width: 100%; -} - -.paginationContainer { - display: flex; - justify-content: space-between; - margin-top: var(--fds-spacing-4); -} - -.pagination { - * { - margin-right: 0; - } -} - -.selectorContainer { - display: flex; - gap: var(--fds-spacing-3); - margin: auto 0; - margin-bottom: 100px; -} - -.selector { - min-width: 70px; -} - -.label { - margin: auto 0; -} diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index dfcbd5dcf45..0e873158758 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -2,7 +2,7 @@ import React, { forwardRef, useEffect, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; import { useTableSorting } from '../../hooks/useTableSorting'; -import { getRowsToRender } from './utils'; +import { getRowsToRender } from '../StudioTableRemotePagination/utils'; type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css index c895ea1411e..18497fd76c1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css @@ -18,7 +18,6 @@ display: flex; gap: var(--fds-spacing-3); margin: auto 0; - margin-bottom: 100px; } .selector { diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 9a73ce534a7..cbaf52b2862 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryFn } from '@storybook/react'; import { StudioTableRemotePagination } from './StudioTableRemotePagination'; import { columns, rows } from './mockData'; import { useTableSorting } from '../../hooks/useTableSorting'; -import { getRowsToRender } from '../StudioTableLocalPagination/utils'; +import { getRowsToRender } from './utils'; type Story = StoryFn; diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx similarity index 76% rename from frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx rename to frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx index 456da476adc..45fc8f4cfc5 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx @@ -1,4 +1,4 @@ -import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; +import { Rows } from './StudioTableRemotePagination'; export const getRowsToRender = (currentPage: number, pageSize: number, rows: Rows): Rows => { if (!!pageSize === false) return rows; From ba1b4b42e4fb505558f64eae8311d8b8e4f7105a Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 30 Apr 2024 09:05:43 +0200 Subject: [PATCH 39/72] Remove dashboard helper functions from this branch --- .../components/RepoList/ActionLinks.tsx | 65 ------------------- .../components/RepoList/FavoriteButton.tsx | 29 --------- 2 files changed, 94 deletions(-) delete mode 100644 frontend/dashboard/components/RepoList/ActionLinks.tsx delete mode 100644 frontend/dashboard/components/RepoList/FavoriteButton.tsx diff --git a/frontend/dashboard/components/RepoList/ActionLinks.tsx b/frontend/dashboard/components/RepoList/ActionLinks.tsx deleted file mode 100644 index 40c9c446913..00000000000 --- a/frontend/dashboard/components/RepoList/ActionLinks.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Button, DropdownMenu } from '@digdir/design-system-react'; -import React from 'react'; -import classes from './NewRepoList.module.css'; -import cn from 'classnames'; -import { - ExternalLinkIcon, - FilesIcon, - MenuElipsisVerticalIcon, - PencilIcon, -} from '@navikt/aksel-icons'; -import { useTranslation } from 'react-i18next'; -import { getRepoEditUrl } from '../../utils/urlUtils'; -import type { Repository } from 'app-shared/types/Repository'; - -type ActionLinksProps = { - repo: Repository; -}; - -export const ActionLinks = ({ repo }: ActionLinksProps): React.ReactElement => { - const { t } = useTranslation(); - - const repoFullName = repo.full_name as string; - const [org, repoName] = repoFullName.split('/'); - const isDatamodelling = repoFullName.endsWith('-datamodels'); - const editUrl = getRepoEditUrl({ org, repo: repoName }); - const editTextKey = t(isDatamodelling ? 'dashboard.edit_datamodels' : 'dashboard.edit_service'); - - return ( -
- ); -}; diff --git a/frontend/dashboard/components/RepoList/FavoriteButton.tsx b/frontend/dashboard/components/RepoList/FavoriteButton.tsx deleted file mode 100644 index 5488c386d4b..00000000000 --- a/frontend/dashboard/components/RepoList/FavoriteButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; -import { Button } from '@digdir/design-system-react'; -import React from 'react'; -import { useSetStarredRepoMutation, useUnsetStarredRepoMutation } from '../../hooks/mutations'; -import type { RepositoryWithStarred } from '../../utils/repoUtils/repoUtils'; - -type FavoriteButtonProps = { - repo: RepositoryWithStarred; -}; - -export const FavoriteButton = ({ repo }: FavoriteButtonProps): React.ReactElement => { - const { mutate: setStarredRepo } = useSetStarredRepoMutation(); - const { mutate: unsetStarredRepo } = useUnsetStarredRepoMutation(); - - const handleToggleFav = (event: React.MouseEvent) => { - event.stopPropagation(); - if (repo.hasStarred) { - unsetStarredRepo(repo); - } else { - setStarredRepo(repo); - } - }; - - return ( - - ); -}; From 606b6dfa314177e861c09bf530adff016e684962 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 30 Apr 2024 13:35:58 +0200 Subject: [PATCH 40/72] Improve documentation --- .../StudioTableLocalPagination.mdx | 80 ++++++- .../StudioTableLocalPagination.stories.tsx | 32 +-- .../StudioTableRemotePagination.mdx | 82 ++++++- .../StudioTableRemotePagination.stories.tsx | 10 +- .../StudioTableRemotePagination.tsx | 2 +- .../StudioTableRemotePagination/mockData.tsx | 210 +++++++----------- 6 files changed, 255 insertions(+), 161 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 313494c10dd..7f040306fb2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -8,12 +8,82 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination StudioTableLocalPagination - StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. - It dynamically renders a table by passing an array of objects to the `column` and `row` props. + StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the column and row props. - The pagination can be disabled by removing the `pagination` prop. - - Use this component when you want the component to handle the pagination for you. + * Use this component when you have a complete set of data, and want the component to handle pagination internally. Otherwise, use `StudioTableRemotePagination`. + * Pagination and sorting can be removed by not passing props to pagination and onSortClick. + + + Type definitions + + +```tsx +type StudioTableLocalPaginationProps = { + columns: Record<'accessor' | 'value', string>[]; + rows: Rows; + size?: 'small' | 'medium' | 'large'; + isSortable?: boolean; + pagination?: { + pageSizeOptions: number[]; + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; + }; +}; + +type Rows = (Record & Record<"id", string | number>)[]; +``` + + + Column format + + + Columns must have both an `accessor` and a `value` property. + +```tsx +const columns = [ + { + accessor: "icon", + value: "", + }, + { + accessor: "name", + value: "Name", + }, + { + accessor: "creator", + value: "Created by", + }, + { + accessor: "lastChanged", + value: "Last changed", + } +] +``` +Row format + + * Rows must have an id that is unique. + * The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. + +``` + const rows = [ + { + id: 1, + icon: }/>, + name: "Coordinated register notification", + creator: "Brønnøysund Register Centre", + lastChanged: new Date("2023-04-12").toLocaleDateString(), + }, + { + id: 2, + icon: }/>, + name: "Application for authorisation and license as a healthcare personnel", + creator: "The Norwegian Directorate of Health", + lastChanged: new Date("2023-04-05").toLocaleDateString(), + }, + ] +``` + diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index 3b761a9da7e..ca7b3becc0e 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -9,26 +9,26 @@ const meta: Meta = { title: 'Studio/StudioTableLocalPagination', component: StudioTableLocalPagination, argTypes: { - pagination: { + size: { control: 'radio', - options: ['true', 'false'], + options: ['small', 'medium', 'large'], }, }, }; -export const Preview: Story = (args): React.ReactElement => ( - + +export const Preview: Story = (args) => ( + `Side ${num}`, + }} + /> ); -Preview.args = { - columns: columns, - rows: rows, - size: 'medium', - isSortable: true, - pagination: { - pageSizeOptions: [5, 10, 20, 50], - nextButtonText: 'Neste', - previousButtonText: 'Forrige', - itemLabel: (num) => `Side ${num}`, - }, -}; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 8a2637c7209..af32aeed65b 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -11,9 +11,85 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. - The pagination can be disabled by removing the `pagination` prop. - - Use this component when the pagination logic is handled remotely (for example via an external API). + * Use this component when data is fetched in chunks, and the pagination logic is handled remotely. Otherwise, use `StudioTableLocalPagination`. + * Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. + + + Type definitions + + +```tsx +type StudioTableRemotePaginationProps = { + columns: Record<'accessor' | 'value', string>[]; + rows: Rows; + size?: 'small' | 'medium' | 'large'; + onSortClick?: (columnKey: string) => void; + pagination?: PaginationProps; +}; + +type Rows = (Record & Record<"id", string | number>)[]; + +type PaginationProps = { + currentPage: number; + totalPages: number; + pageSizeOptions: number[]; + onPageChange: (currentPage: number) => void; + onPageSizeChange: (currentSize: number) => void; + nextButtonText: string; + previousButtonText: string; + itemLabel: (num: number) => string; +}; +``` + + + Column format + + + Columns must have both an `accessor` and a `value` property. + +```tsx +const columns = [ + { + accessor: "icon", + value: "", + }, + { + accessor: "name", + value: "Name", + }, + { + accessor: "creator", + value: "Created by", + }, + { + accessor: "lastChanged", + value: "Last changed", + } +] +``` +Row format + + * Rows must have an id that is unique. + * The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. + +```tsx +const rows = [ + { + id: 1, + icon: , + name: "Coordinated register notification", + creator: "Brønnøysund Register Centre", + lastChanged: new Date("2023-04-12").toLocaleDateString(), + }, + { + id: 2, + icon: , + name: "Application for authorisation and license as a healthcare personnel", + creator: "The Norwegian Directorate of Health", + lastChanged: new Date("2023-04-05").toLocaleDateString(), + }, + ] +``` diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index cbaf52b2862..4fbedd606da 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -11,14 +11,15 @@ const meta: Meta = { title: 'Studio/StudioTableRemotePagination', component: StudioTableRemotePagination, argTypes: { - pagination: { + size: { control: 'radio', - options: ['true', 'false'], + options: ['small', 'medium', 'large'], }, }, }; + export const Preview: Story = (args) => { - // Start simulation of API logic + // Example of external logic const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(5); @@ -38,13 +39,12 @@ export const Preview: Story = (args) => { nextButtonText: 'Neste', previousButtonText: 'Forrige', }; - // End simulation of API logic return ( diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 2ac3f855caf..9eb5ca797bc 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -10,7 +10,7 @@ export const labelSizeMap: Record = { large: 'medium', }; -export type Rows = Record[]; +export type Rows = (Record & Record<'id', string | number>)[]; export type PaginationProps = { currentPage: number; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index 374ba77ae44..faff946ada1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -1,5 +1,5 @@ -import { Button, Link } from '@digdir/design-system-react'; -import { FaceSmileFillIcon, FaceSmileIcon, StarFillIcon, StarIcon } from '@navikt/aksel-icons'; +import { Button } from '@digdir/design-system-react'; +import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; import React from 'react'; const IconButton = ({ icon }) => ( @@ -8,188 +8,136 @@ const IconButton = ({ icon }) => ( ); -const AltinnLink = () => Link; - -function generateRandomId(length) { - let result = ''; - const characters = '0123456789'; - const charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - export const columns = [ { accessor: 'icon', value: '', }, - { - accessor: 'id', - value: 'Employee ID', - }, { accessor: 'name', value: 'Name', }, { - accessor: 'role', - value: 'Role', + accessor: 'creator', + value: 'Created by', }, { - accessor: 'status', - value: 'Status', - }, - { - accessor: 'link', - value: '', + accessor: 'lastChanged', + value: 'Last changed', }, ]; export const rows = [ { + id: 1, icon: } />, - id: generateRandomId(6), - name: 'Lila Patel', - role: 'Software Engineer', - status: 'Pending', - link: , + name: 'Coordinated register notification', + creator: 'Brønnøysund Register Centre', + lastChanged: new Date('2023-04-12').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Ethan Nakamura', - role: 'Marketing Specialist', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Olivia Chen', - role: 'Data Analyst', - status: 'Pending', - link: , + id: 2, + icon: } />, + name: 'Application for authorisation and license as a healthcare personnel', + creator: 'The Norwegian Directorate of Health', + lastChanged: new Date('2023-04-05').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Noah Adebayo', - role: 'UX Designer', - status: 'Approved', - link: , + id: 3, + icon: } />, + name: 'Produkter og tjenester fra Brønnøysundregistrene', + creator: 'Brønnøysund Register Centre', + lastChanged: new Date('2023-04-16').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Sophia Ivanov', - role: 'Product Manager', - status: 'Pending', - link: , + id: 4, + icon: } />, + name: 'Contact form - Norwegian Tax Administration (private individual)', + creator: 'Tax Administration', + lastChanged: new Date('2023-04-08').toLocaleDateString(), }, { + id: 5, icon: } />, - id: generateRandomId(6), - name: 'William Torres', - role: 'Sales Representative', - status: 'Approved', - link: , + name: 'Contact form - Norwegian Tax Administration (commercial)', + creator: 'Tax Administration', + lastChanged: new Date('2023-04-01').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Ava Gupta', - role: 'Human Resources Manager', - status: 'Pending', - link: , + id: 6, + icon: } />, + name: 'A-melding – all forms', + creator: 'Brønnøysund Register Centre', + lastChanged: new Date('2023-04-14').toLocaleDateString(), }, { + id: 7, icon: } />, - id: generateRandomId(6), - name: 'James Kim', - role: 'Financial Analyst', - status: 'Approved', - link: AltinnLink(), + name: 'Application for VAT registration', + creator: 'Tax Administration', + lastChanged: new Date('2023-04-03').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Mia Sánchez', - role: 'Customer Support Specialist', - status: 'Pending', - link: , + id: 8, + icon: } />, + name: 'Reporting of occupational injuries and diseases', + creator: 'Norwegian Labour Inspection Authority', + lastChanged: new Date('2023-04-11').toLocaleDateString(), }, { + id: 9, icon: } />, - id: generateRandomId(6), - name: 'Lucas Wright', - role: 'Software Engineer', - status: 'Pending', - link: , + name: 'Application for a residence permit', + creator: 'Norwegian Directorate of Immigration', + lastChanged: new Date('2023-04-06').toLocaleDateString(), }, { + id: 10, icon: } />, - id: generateRandomId(6), - name: 'Chloe Tanaka', - role: 'Marketing Specialist', - status: 'Approved', - link: , + name: 'Application for a work permit', + creator: 'Norwegian Directorate of Immigration', + lastChanged: new Date('2023-04-15').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Emily Zhao', - role: 'Data Analyst', - status: 'Pending', - link: , + id: 11, + icon: } />, + name: 'Notification of change of address', + creator: 'Norwegian Tax Administration', + lastChanged: new Date('2023-04-09').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Jacob Martinez', - role: 'UX Designer', - status: 'Approved', - link: , + id: 12, + icon: } />, + name: 'Application for a Norwegian national ID number', + creator: 'Norwegian Tax Administration', + lastChanged: new Date('2023-04-02').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Isabella Johnson', - role: 'Product Manager', - status: 'Pending', - link: , + id: 13, + icon: } />, + name: 'Reporting of temporary layoffs', + creator: 'Norwegian Labour and Welfare Administration', + lastChanged: new Date('2023-04-07').toLocaleDateString(), }, { + id: 14, icon: } />, - id: generateRandomId(6), - name: 'Elijah Lee', - role: 'Sales Representative', - status: 'Approved', - link: , - }, - { - icon: } />, - id: generateRandomId(6), - name: 'Charlotte Silva', - role: 'Human Resources Manager', - status: 'Pending', - link: , + name: 'Application for parental benefit', + creator: 'Norwegian Labour and Welfare Administration', + lastChanged: new Date('2023-04-13').toLocaleDateString(), }, { - icon: } />, - id: generateRandomId(6), - name: 'Benjamin Lopez', - role: 'Financial Analyst', - status: 'Approved', - link: , + id: 15, + icon: } />, + name: 'Reporting of VAT', + creator: 'Tax Administration', + lastChanged: new Date('2023-04-04').toLocaleDateString(), }, { + id: 16, icon: } />, - id: generateRandomId(6), - name: 'Amelia Schmidt', - role: 'Customer Support Specialist', - status: 'Pending', - link: , + name: 'Application for a certificate of good conduct', + creator: 'Norwegian Police', + lastChanged: new Date('2023-04-10').toLocaleDateString(), }, ]; From 986213c7b6cbd50972dc24c3758da332787c6e0a Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 30 Apr 2024 13:56:57 +0200 Subject: [PATCH 41/72] Add pageSizeLabel prop --- .../StudioTableLocalPagination.mdx | 12 ++++++------ .../StudioTableLocalPagination.stories.tsx | 7 ++++--- .../StudioTableLocalPagination.tsx | 5 ++++- .../StudioTableRemotePagination.mdx | 6 +++--- .../StudioTableRemotePagination.module.css | 11 ----------- .../StudioTableRemotePagination.stories.tsx | 7 ++++--- .../StudioTableRemotePagination.tsx | 4 +++- 7 files changed, 24 insertions(+), 28 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 7f040306fb2..1b31351ed39 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -11,7 +11,7 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the column and row props. * Use this component when you have a complete set of data, and want the component to handle pagination internally. Otherwise, use `StudioTableRemotePagination`. - * Pagination and sorting can be removed by not passing props to pagination and onSortClick. + * Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. @@ -19,7 +19,6 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination Type definitions - ```tsx type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; @@ -28,6 +27,7 @@ type StudioTableLocalPaginationProps = { isSortable?: boolean; pagination?: { pageSizeOptions: number[]; + pageSizeLabel: string; nextButtonText: string; previousButtonText: string; itemLabel: (num: number) => string; @@ -36,7 +36,7 @@ type StudioTableLocalPaginationProps = { type Rows = (Record & Record<"id", string | number>)[]; ``` - + Column format @@ -72,18 +72,18 @@ const columns = [ const rows = [ { id: 1, - icon: }/>, + icon: , name: "Coordinated register notification", creator: "Brønnøysund Register Centre", lastChanged: new Date("2023-04-12").toLocaleDateString(), }, { id: 2, - icon: }/>, + icon: , name: "Application for authorisation and license as a healthcare personnel", creator: "The Norwegian Directorate of Health", lastChanged: new Date("2023-04-05").toLocaleDateString(), }, - ] + ] ``` diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index ca7b3becc0e..90445107629 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -24,9 +24,10 @@ export const Preview: Story = (args) => ( isSortable={true} pagination={{ pageSizeOptions: [5, 10, 20, 50], - nextButtonText: 'Neste', - previousButtonText: 'Forrige', - itemLabel: (num) => `Side ${num}`, + pageSizeLabel: 'Rows per page', + nextButtonText: 'Next', + previousButtonText: 'Previous', + itemLabel: (num) => `Page ${num}`, }} /> ); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 0e873158758..801dd7cab5a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -11,6 +11,7 @@ type StudioTableLocalPaginationProps = { isSortable?: boolean; pagination?: { pageSizeOptions: number[]; + pageSizeLabel: string; nextButtonText: string; previousButtonText: string; itemLabel: (num: number) => string; @@ -21,7 +22,8 @@ export const StudioTableLocalPagination = forwardRef< HTMLTableElement, StudioTableLocalPaginationProps >(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - const { pageSizeOptions, itemLabel, nextButtonText, previousButtonText } = pagination || {}; + const { pageSizeOptions, pageSizeLabel, itemLabel, nextButtonText, previousButtonText } = + pagination || {}; const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); @@ -47,6 +49,7 @@ export const StudioTableLocalPagination = forwardRef< currentPage, totalPages, pageSizeOptions, + pageSizeLabel, onPageChange: setCurrentPage, onPageSizeChange: setPageSize, itemLabel, diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index af32aeed65b..16f9cdd0da3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -11,7 +11,7 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. - * Use this component when data is fetched in chunks, and the pagination logic is handled remotely. Otherwise, use `StudioTableLocalPagination`. + * Use this component when data is fetched in chunks, and the pagination logic is handled externally. Otherwise, use `StudioTableLocalPagination`. * Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. @@ -20,7 +20,6 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati Type definitions - ```tsx type StudioTableRemotePaginationProps = { columns: Record<'accessor' | 'value', string>[]; @@ -36,6 +35,7 @@ type PaginationProps = { currentPage: number; totalPages: number; pageSizeOptions: number[]; + pageSizeLabel: string; onPageChange: (currentPage: number) => void; onPageSizeChange: (currentSize: number) => void; nextButtonText: string; @@ -43,7 +43,7 @@ type PaginationProps = { itemLabel: (num: number) => string; }; ``` - + Column format diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css index 18497fd76c1..30b8342c233 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css @@ -8,20 +8,9 @@ margin-top: var(--fds-spacing-4); } -.pagination { - * { - margin-right: 0; - } -} - .selectorContainer { display: flex; gap: var(--fds-spacing-3); - margin: auto 0; -} - -.selector { - min-width: 70px; } .label { diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 4fbedd606da..f97cc20479a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -33,11 +33,12 @@ export const Preview: Story = (args) => { currentPage, totalPages, pageSizeOptions: [5, 10, 20, 50], + pageSizeLabel: 'Rows per page', onPageChange: setCurrentPage, onPageSizeChange: setPageSize, - itemLabel: (num) => `Side ${num}`, - nextButtonText: 'Neste', - previousButtonText: 'Forrige', + itemLabel: (num) => `Page ${num}`, + nextButtonText: 'Next', + previousButtonText: 'Previous', }; return ( diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 9eb5ca797bc..3d47010bcc5 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -16,6 +16,7 @@ export type PaginationProps = { currentPage: number; totalPages: number; pageSizeOptions: number[]; + pageSizeLabel: string; onPageChange: (currentPage: number) => void; onPageSizeChange: (currentSize: number) => void; nextButtonText: string; @@ -42,6 +43,7 @@ export const StudioTableRemotePagination = forwardRef< currentPage, totalPages, pageSizeOptions, + pageSizeLabel, onPageChange: handlePageChange, onPageSizeChange: handlePageSizeChange, nextButtonText, @@ -94,7 +96,7 @@ export const StudioTableRemotePagination = forwardRef< ))}
- - - - - - - - - {} - {t('dashboard.make_copy')} - - - {} - {t('dashboard.open_in_new')} - - - -
{totalPages > 1 && ( From d9d8e79e062a648ea4fcf160e28d82dbe5b46fd1 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 30 Apr 2024 14:20:08 +0200 Subject: [PATCH 42/72] Begin testing --- .../StudioTableRemotePagination.test.tsx | 95 +++++++++++++------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index 2522df69260..453e2b7febb 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -1,41 +1,76 @@ -import { render, screen } from '@testing-library/react'; import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { StudioTableRemotePagination } from './StudioTableRemotePagination'; -import { columns, rows } from './mockData'; -// import userEvent from "@testing-library/user-event/"; -describe('StudioTableWithPagination', () => { - it('should render the table with sorting and pagination', () => { +describe('StudioTableRemotePagination', () => { + const columns = [ + { accessor: 'name', value: 'Name' }, + { accessor: 'age', value: 'Age' }, + ]; + + const rows = [ + { id: 1, name: 'John Doe', age: 25 }, + { id: 2, name: 'Jane Smith', age: 30 }, + ]; + + const paginationProps = { + currentPage: 1, + totalPages: 2, + pageSizeOptions: [5, 10, 20], + pageSizeLabel: 'Rows per page', + onPageChange: jest.fn(), + onPageSizeChange: jest.fn(), + nextButtonText: 'Next', + previousButtonText: 'Previous', + itemLabel: (num) => `Page ${num}`, + }; + + it('renders the table with columns and rows', () => { render(); - expect(screen.getByRole('button', { name: 'Name' })); - expect(screen.getByRole('cell', { name: 'Lila Patel' })); - expect(screen.getByRole('combobox')); - expect(screen.getByRole('button', { name: 'Side 1' })); - expect(screen.getByRole('button', { name: 'Side 2' })); + expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: 'Age' })).toBeInTheDocument(); + expect(screen.getByRole('cell', { name: 'John Doe' })).toBeInTheDocument(); + expect(screen.getByRole('cell', { name: 'Jane Smith' })).toBeInTheDocument(); }); - it('should render the table without sorting and pagination', () => { - render(); + it('triggers the onSortClick callback when a sortable column header is clicked', async () => { + const onSortClick = jest.fn(); + render(); + + await userEvent.click(screen.getByRole('columnheader', { name: 'Name' })); - expect(screen.getByRole('columnheader', { name: 'Name' })); - expect(screen.getByRole('cell', { name: 'Lila Patel' })); - expect(screen.queryByRole('combobox')).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Side 1' })).not.toBeInTheDocument(); + expect(onSortClick).toHaveBeenCalledWith('name'); }); - // it("should sort the columns", async () => { - // const user = userEvent.setup(); - // render(); - // - // await user.click(screen.getByRole("button", {name: "Name"})); - // const cellsAfterFirstClick = screen.getAllByRole("cell"); - // expect(cellsAfterFirstClick[2]).toHaveTextContent("Amelia Schmidt"); - // - // await act(async () => { - // await user.click(screen.getByRole("button", {name: "Name"})); - // }) - // const cellsAfterSecondClick = screen.getAllByRole("cell"); - // expect(cellsAfterSecondClick[2]).toHaveTextContent("William Torres"); - // }) + it('renders the pagination controls when pagination prop is provided', () => { + render( + , + ); + + expect(screen.getByRole('combobox', { name: 'Rows per page' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Previous' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument(); + }); + + it('triggers the onPageChange callback when a page is clicked', async () => { + render( + , + ); + + await userEvent.click(screen.getByRole('button', { name: 'Next' })); + + expect(paginationProps.onPageChange).toHaveBeenCalledWith(2); + }); + + it('triggers the onPageSizeChange callback when the page size is changed', async () => { + render( + , + ); + + await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); + + expect(paginationProps.onPageSizeChange).toHaveBeenCalledWith(10); + }); }); From 00c4e94212ea1bdd82a202b909e22c61ba47f913 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 30 Apr 2024 15:29:55 +0200 Subject: [PATCH 43/72] Begin writing test for StudioTableLocalPagination --- .../StudioTableLocalPagination.test.tsx | 71 +++++++++++++++++++ .../StudioTableRemotePagination.test.tsx | 34 ++++----- 2 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx new file mode 100644 index 00000000000..fd2c91c37af --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { StudioTableLocalPagination } from './StudioTableLocalPagination'; +import { columns, rows } from '../StudioTableRemotePagination/mockData'; + +describe('StudioTableLocalPagination', () => { + const paginationProps = { + pageSizeOptions: [2, 5, 10], + pageSizeLabel: 'Rows per page', + nextButtonText: 'Next', + previousButtonText: 'Previous', + itemLabel: (num) => `Page ${num}`, + }; + + it('renders the table with columns and rows', () => { + render(); + + 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('triggers sorting when a sortable column header is clicked', async () => { + render(); + + await userEvent.click(screen.getByRole('button', { name: 'Age' })); + + const tableBody = screen.getByRole('rowgroup'); + const rows = within(tableBody).getAllByRole('row'); + expect(within(rows[0]).getByRole('cell', { name: 'John Doe' })).toBeInTheDocument(); + expect(within(rows[1]).getByRole('cell', { name: 'Jane Smith' })).toBeInTheDocument(); + }); + + it('renders pagination controls when pagination prop is provided', () => { + render( + , + ); + + expect(screen.getByRole('combobox', { name: 'Rows per page' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument(); + }); + + it('changes page when next button is clicked', async () => { + render( + , + ); + + await userEvent.click(screen.getByRole('button', { name: 'Next' })); + + expect(screen.getByRole('cell', { name: 'Bob Johnson' })).toBeInTheDocument(); + expect(screen.getByRole('cell', { name: 'Alice Brown' })).toBeInTheDocument(); + }); + + it('changes page size when page size option is selected', async () => { + render( + , + ); + + await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '5'); + + const tableBody = screen.getByRole('rowgroup'); + const rows = within(tableBody).getAllByRole('row'); + expect(rows).toHaveLength(4); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index 453e2b7febb..9211ecd55fe 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -2,18 +2,9 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { StudioTableRemotePagination } from './StudioTableRemotePagination'; +import { columns, rows } from './mockData'; describe('StudioTableRemotePagination', () => { - const columns = [ - { accessor: 'name', value: 'Name' }, - { accessor: 'age', value: 'Age' }, - ]; - - const rows = [ - { id: 1, name: 'John Doe', age: 25 }, - { id: 2, name: 'Jane Smith', age: 30 }, - ]; - const paginationProps = { currentPage: 1, totalPages: 2, @@ -30,18 +21,24 @@ describe('StudioTableRemotePagination', () => { render(); expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument(); - expect(screen.getByRole('columnheader', { name: 'Age' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'John Doe' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'Jane Smith' })).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('triggers the onSortClick callback when a sortable column header is clicked', async () => { - const onSortClick = jest.fn(); - render(); + it('triggers the handleSorting function when a sortable column header is clicked', async () => { + const handleSorting = jest.fn(); + render( + , + ); - await userEvent.click(screen.getByRole('columnheader', { name: 'Name' })); + await userEvent.click(screen.getByRole('button', { name: 'Name' })); - expect(onSortClick).toHaveBeenCalledWith('name'); + expect(handleSorting).toHaveBeenCalledWith('name'); }); it('renders the pagination controls when pagination prop is provided', () => { @@ -50,7 +47,6 @@ describe('StudioTableRemotePagination', () => { ); expect(screen.getByRole('combobox', { name: 'Rows per page' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Previous' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument(); }); From 64c2997f89005d5837b71c0c669e7e1d074297bc Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 13:13:02 +0200 Subject: [PATCH 44/72] Finish tests and solve linting errors --- .../StudioTableLocalPagination.mdx | 81 ++++++++++--------- .../StudioTableLocalPagination.test.tsx | 34 +++++--- .../StudioTableLocalPagination.tsx | 11 ++- .../StudioTableRemotePagination.mdx | 69 +++++++++------- .../StudioTableRemotePagination.test.tsx | 7 ++ .../StudioTableRemotePagination.tsx | 10 +-- .../StudioTableRemotePagination/mockData.tsx | 9 ++- .../StudioTableRemotePagination/utils.tsx | 2 +- .../src/hooks/useTableSorting.tsx | 2 +- 9 files changed, 128 insertions(+), 97 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 1b31351ed39..4a410171aaa 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -7,11 +7,11 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination StudioTableLocalPagination +StudioTableLocalPagination is a wrapper for StudioTableRemotePagination. - StudioTableLocalPagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the column and row props. - - * Use this component when you have a complete set of data, and want the component to handle pagination internally. Otherwise, use `StudioTableRemotePagination`. - * Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. + - Use this component when you are fetching a complete set of data, and want the component to + handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`. - + Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. @@ -19,6 +19,7 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination Type definitions + ```tsx type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; @@ -34,56 +35,58 @@ type StudioTableLocalPaginationProps = { }; }; -type Rows = (Record & Record<"id", string | number>)[]; +type Rows = (Record & Record<'id', string | number>)[]; ``` Column format - - Columns must have both an `accessor` and a `value` property. - +Columns must have both an `accessor` and a `value` property. + ```tsx const columns = [ { - accessor: "icon", - value: "", + accessor: 'icon', + value: '', }, { - accessor: "name", - value: "Name", + accessor: 'name', + value: 'Name', }, { - accessor: "creator", - value: "Created by", + accessor: 'creator', + value: 'Created by', }, { - accessor: "lastChanged", - value: "Last changed", - } -] + accessor: 'lastChanged', + value: 'Last changed', + }, +]; ``` -Row format + + + Row format + - * Rows must have an id that is unique. - * The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. + * Rows must have an id that is unique. * The accessors in the columns array is used to display the + row properties. Therefore, each property name has to exactly match the accessor. -``` - const rows = [ - { - id: 1, - icon: , - name: "Coordinated register notification", - creator: "Brønnøysund Register Centre", - lastChanged: new Date("2023-04-12").toLocaleDateString(), - }, - { - id: 2, - icon: , - name: "Application for authorisation and license as a healthcare personnel", - creator: "The Norwegian Directorate of Health", - lastChanged: new Date("2023-04-05").toLocaleDateString(), - }, - ] -``` +```tsx +const rows = [ + { + id: 1, + icon: , + name: 'Coordinated register notification', + creator: 'Brønnøysund Register Centre', + lastChanged: new Date('2023-04-12').toLocaleDateString(), + }, + { + id: 2, + icon: , + name: 'Application for authorisation and license as a healthcare personnel', + creator: 'The Norwegian Directorate of Health', + lastChanged: new Date('2023-04-05').toLocaleDateString(), + }, +]; +``` diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index fd2c91c37af..02b24de3ed1 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -6,7 +6,7 @@ import { columns, rows } from '../StudioTableRemotePagination/mockData'; describe('StudioTableLocalPagination', () => { const paginationProps = { - pageSizeOptions: [2, 5, 10], + pageSizeOptions: [5, 10], pageSizeLabel: 'Rows per page', nextButtonText: 'Next', previousButtonText: 'Previous', @@ -29,12 +29,17 @@ describe('StudioTableLocalPagination', () => { it('triggers sorting when a sortable column header is clicked', async () => { render(); - await userEvent.click(screen.getByRole('button', { name: 'Age' })); + await userEvent.click(screen.getByRole('button', { name: 'Name' })); - const tableBody = screen.getByRole('rowgroup'); - const rows = within(tableBody).getAllByRole('row'); - expect(within(rows[0]).getByRole('cell', { name: 'John Doe' })).toBeInTheDocument(); - expect(within(rows[1]).getByRole('cell', { name: 'Jane Smith' })).toBeInTheDocument(); + const firstBodyRow = screen.getAllByRole('row')[1]; + expect( + within(firstBodyRow).getByRole('cell', { name: 'A-melding – all forms' }), + ).toBeInTheDocument(); + + const secondBodyRow = screen.getAllByRole('row')[2]; + expect( + within(secondBodyRow).getByRole('cell', { name: 'Application for VAT registration' }), + ).toBeInTheDocument(); }); it('renders pagination controls when pagination prop is provided', () => { @@ -53,8 +58,13 @@ describe('StudioTableLocalPagination', () => { await userEvent.click(screen.getByRole('button', { name: 'Next' })); - expect(screen.getByRole('cell', { name: 'Bob Johnson' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'Alice Brown' })).toBeInTheDocument(); + 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 page size option is selected', async () => { @@ -62,10 +72,10 @@ describe('StudioTableLocalPagination', () => { , ); - await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '5'); + await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); - const tableBody = screen.getByRole('rowgroup'); - const rows = within(tableBody).getAllByRole('row'); - expect(rows).toHaveLength(4); + const tableBody = screen.getAllByRole('rowgroup')[1]; + const tableBodyRows = within(tableBody).getAllByRole('row'); + expect(tableBodyRows).toHaveLength(10); }); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 801dd7cab5a..2f75dfb725d 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, useEffect, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; -import { Rows } from '../StudioTableRemotePagination/StudioTableRemotePagination'; +import type { Rows } from '../StudioTableRemotePagination/'; import { useTableSorting } from '../../hooks/useTableSorting'; import { getRowsToRender } from '../StudioTableRemotePagination/utils'; @@ -27,15 +27,12 @@ export const StudioTableLocalPagination = forwardRef< const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); - let handleSorting: (columnKey: string) => void; - let sortedRows: Rows; + const { handleSorting, sortedRows } = useTableSorting(rows); let rowsToRender: Rows; if (isSortable) { - ({ handleSorting, sortedRows } = useTableSorting(rows)); rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); } else { - handleSorting = undefined; rowsToRender = getRowsToRender(currentPage, pageSize, rows); } @@ -62,9 +59,11 @@ export const StudioTableLocalPagination = forwardRef< columns={columns} rows={rowsToRender} size={size} - onSortClick={handleSorting} + onSortClick={isSortable && handleSorting} pagination={paginationProps} ref={ref} /> ); }); + +StudioTableLocalPagination.displayName = 'StudioTableLocalPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 16f9cdd0da3..01115ba8348 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -8,11 +8,14 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination - StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. - It dynamically renders a table by passing an array of objects to the `column` and `row` props. - - * Use this component when data is fetched in chunks, and the pagination logic is handled externally. Otherwise, use `StudioTableLocalPagination`. - * Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. + StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` + components. It dynamically renders a table by passing an array of objects to the `column` and + `row` props. + + + - Use this component when data is fetched in chunks, and the pagination logic is handled + externally. Otherwise, use `StudioTableLocalPagination`. - Pagination and sorting can be removed + by not passing props to `pagination` and `onSortClick`. @@ -20,6 +23,7 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati Type definitions + ```tsx type StudioTableRemotePaginationProps = { columns: Record<'accessor' | 'value', string>[]; @@ -29,7 +33,7 @@ type StudioTableRemotePaginationProps = { pagination?: PaginationProps; }; -type Rows = (Record & Record<"id", string | number>)[]; +type Rows = (Record & Record<'id', string | number>)[]; type PaginationProps = { currentPage: number; @@ -47,49 +51,52 @@ type PaginationProps = { Column format - - Columns must have both an `accessor` and a `value` property. - +Columns must have both an `accessor` and a `value` property. + ```tsx const columns = [ { - accessor: "icon", - value: "", + accessor: 'icon', + value: '', }, { - accessor: "name", - value: "Name", + accessor: 'name', + value: 'Name', }, { - accessor: "creator", - value: "Created by", + accessor: 'creator', + value: 'Created by', }, { - accessor: "lastChanged", - value: "Last changed", - } -] + accessor: 'lastChanged', + value: 'Last changed', + }, +]; ``` -Row format + + + Row format + - * Rows must have an id that is unique. - * The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. + * Rows must have an id that is unique. * The accessors in the columns array is used to display the + row properties. Therefore, each property name has to exactly match the accessor. + ```tsx const rows = [ { id: 1, - icon: , - name: "Coordinated register notification", - creator: "Brønnøysund Register Centre", - lastChanged: new Date("2023-04-12").toLocaleDateString(), + icon: , + name: 'Coordinated register notification', + creator: 'Brønnøysund Register Centre', + lastChanged: new Date('2023-04-12').toLocaleDateString(), }, { id: 2, - icon: , - name: "Application for authorisation and license as a healthcare personnel", - creator: "The Norwegian Directorate of Health", - lastChanged: new Date("2023-04-05").toLocaleDateString(), + icon: , + name: 'Application for authorisation and license as a healthcare personnel', + creator: 'The Norwegian Directorate of Health', + lastChanged: new Date('2023-04-05').toLocaleDateString(), }, - ] +]; ``` diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index 9211ecd55fe..9df640a1d78 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -50,6 +50,13 @@ describe('StudioTableRemotePagination', () => { expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument(); }); + it('does not render the pagination controls when pagination prop is not provided', () => { + render(); + + expect(screen.queryByRole('combobox', { name: 'Rows per page' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument(); + }); + it('triggers the onPageChange callback when a page is clicked', async () => { render( , diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 3d47010bcc5..cf01c6537c6 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -84,14 +84,13 @@ export const StudioTableRemotePagination = forwardRef<
handlePageSizeChange(Number(e.target.value))} size={size} > - {pageSizeOptions.map((rows) => ( - ))} @@ -101,7 +100,6 @@ export const StudioTableRemotePagination = forwardRef<
{totalPages > 1 && ( ); }); + +StudioTableRemotePagination.displayName = 'StudioTableRemotePagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index faff946ada1..072db31e7fc 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -1,8 +1,13 @@ import { Button } from '@digdir/design-system-react'; import { StarFillIcon, StarIcon } from '@navikt/aksel-icons'; import React from 'react'; +import type { Rows } from './StudioTableRemotePagination'; -const IconButton = ({ icon }) => ( +type IconButtonProps = { + icon: React.ReactNode; +}; + +const IconButton = ({ icon }: IconButtonProps): React.ReactElement => ( @@ -27,7 +32,7 @@ export const columns = [ }, ]; -export const rows = [ +export const rows: Rows = [ { id: 1, icon: } />, diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx index 45fc8f4cfc5..41805ed962c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx @@ -1,4 +1,4 @@ -import { Rows } from './StudioTableRemotePagination'; +import type { Rows } from './StudioTableRemotePagination'; export const getRowsToRender = (currentPage: number, pageSize: number, rows: Rows): Rows => { if (!!pageSize === false) return rows; diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 196ae1082eb..e68fdfaa10d 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Rows } from '../components/StudioTableRemotePagination/StudioTableRemotePagination'; +import type { Rows } from '../components/StudioTableRemotePagination/StudioTableRemotePagination'; export const useTableSorting = (rows: Rows) => { const [sortColumn, setSortColumn] = useState(null); From 78ebbab0fcf19b0f166d6c822c560151a5edafe0 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 13:24:17 +0200 Subject: [PATCH 45/72] Export type Rows from StudioTableRemotePagination/index.ts --- .../src/components/StudioTableRemotePagination/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts index e11dc7695c1..7d6f8f83c2d 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts @@ -1 +1 @@ -export { StudioTableRemotePagination } from './StudioTableRemotePagination'; +export { StudioTableRemotePagination, Rows } from './StudioTableRemotePagination'; From 74d0a4aa59f839a14aac0b62699fc18fadea1f6c Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 14:01:52 +0200 Subject: [PATCH 46/72] Add testing to useTableSorting --- .../StudioTableLocalPagination.test.tsx | 18 +++- .../StudioTableLocalPagination.tsx | 7 +- .../src/hooks/useTableSorting.test.tsx | 84 +++++++++++++++++++ .../src/hooks/useTableSorting.tsx | 2 +- 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index 02b24de3ed1..31e3cc522da 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -6,7 +6,7 @@ import { columns, rows } from '../StudioTableRemotePagination/mockData'; describe('StudioTableLocalPagination', () => { const paginationProps = { - pageSizeOptions: [5, 10], + pageSizeOptions: [5, 10, 50], pageSizeLabel: 'Rows per page', nextButtonText: 'Next', previousButtonText: 'Previous', @@ -78,4 +78,20 @@ describe('StudioTableLocalPagination', () => { const tableBodyRows = within(tableBody).getAllByRole('row'); expect(tableBodyRows).toHaveLength(10); }); + + it('sets currentPage to 1 when no rows are displayed', async () => { + render( + , + ); + + await userEvent.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 userEvent.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); + }); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 2f75dfb725d..a6a71d56636 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useState } from 'react'; +import React, { forwardRef, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; import type { Rows } from '../StudioTableRemotePagination/'; import { useTableSorting } from '../../hooks/useTableSorting'; @@ -36,10 +36,7 @@ export const StudioTableLocalPagination = forwardRef< rowsToRender = getRowsToRender(currentPage, pageSize, rows); } - useEffect(() => { - if (rowsToRender.length === 0) setCurrentPage(1); - }, [rowsToRender]); - + if (rowsToRender.length === 0) setCurrentPage(1); const totalPages = Math.ceil(rows.length / pageSize); const paginationProps = pagination && { diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx new file mode 100644 index 00000000000..de2dd361413 --- /dev/null +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -0,0 +1,84 @@ +import { useTableSorting } from './useTableSorting'; +import { Rows } from '../components'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +type TestComponentProps = { + rows: Rows; +}; + +type Row = Record & Record<'id', string | number>; + +const TestComponent = ({ rows }: TestComponentProps) => { + const { sortedRows, handleSorting } = useTableSorting(rows); + return ( +
+ + +
    + {sortedRows.map((row: Row) => ( +
  • + {row.name} - {row.creator} +
  • + ))} +
+
+ ); +}; + +describe('useTableSorting', () => { + const rows: Rows = [ + { + id: 1, + name: 'A form', + creator: 'Digdir', + }, + { + id: 2, + name: 'B form', + creator: 'Brreg', + }, + { + id: 3, + name: 'C form', + creator: 'Skatt', + }, + ]; + + it('should render the initial state', () => { + render(); + expect(screen.getByText('A form - Digdir')).toBeInTheDocument(); + expect(screen.getByText('B form - Brreg')).toBeInTheDocument(); + expect(screen.getByText('C form - Skatt')).toBeInTheDocument(); + }); + + it('should sort rows in ascending order when a column is clicked', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByText('Sort by Name')); + expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('A form - Digdir'); + expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('B form - Brreg'); + expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('C form - Skatt'); + }); + + it('should sort rows in descending order when the same column is clicked again', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByText('Sort by Creator')); + await user.click(screen.getByText('Sort by Creator')); + expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('C form - Skatt'); + expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('A form - Digdir'); + expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('B form - Brreg'); + }); + + it('should reset the sort direction to ascending when a different column is clicked', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByText('Sort by Name')); + await user.click(screen.getByText('Sort by Creator')); + expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('B form - Brreg'); + expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('A form - Digdir'); + expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('C form - Skatt'); + }); +}); diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index e68fdfaa10d..b32837c06f4 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import type { Rows } from '../components/StudioTableRemotePagination/StudioTableRemotePagination'; +import type { Rows } from '../components'; export const useTableSorting = (rows: Rows) => { const [sortColumn, setSortColumn] = useState(null); From 3728bba26a924b67c9691e645983bcd8ad549406 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 14:30:49 +0200 Subject: [PATCH 47/72] Use 'export type' when exporting Rows --- .../StudioTableLocalPagination/StudioTableLocalPagination.tsx | 2 +- .../src/components/StudioTableRemotePagination/index.ts | 3 ++- .../libs/studio-components/src/hooks/useTableSorting.test.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index a6a71d56636..54ca04b7e3d 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,8 +1,8 @@ import React, { forwardRef, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; -import type { Rows } from '../StudioTableRemotePagination/'; import { useTableSorting } from '../../hooks/useTableSorting'; import { getRowsToRender } from '../StudioTableRemotePagination/utils'; +import { Rows } from '../StudioTableRemotePagination'; type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts index 7d6f8f83c2d..da6888f6aee 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/index.ts @@ -1 +1,2 @@ -export { StudioTableRemotePagination, Rows } from './StudioTableRemotePagination'; +export { StudioTableRemotePagination } from './StudioTableRemotePagination'; +export type { Rows } from './StudioTableRemotePagination'; diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index de2dd361413..cb266bffd37 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -1,8 +1,8 @@ import { useTableSorting } from './useTableSorting'; -import { Rows } from '../components'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { Rows } from '../components'; type TestComponentProps = { rows: Rows; From 8ff51b599e3805eed4c0733ecd2d8a6170772a49 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 14:41:56 +0200 Subject: [PATCH 48/72] Use 'export type' when exporting Rows --- .../StudioTableLocalPagination/StudioTableLocalPagination.tsx | 2 +- .../libs/studio-components/src/hooks/useTableSorting.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 54ca04b7e3d..41ea31e8494 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,8 +1,8 @@ import React, { forwardRef, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; +import type { Rows } from '../StudioTableRemotePagination'; import { useTableSorting } from '../../hooks/useTableSorting'; import { getRowsToRender } from '../StudioTableRemotePagination/utils'; -import { Rows } from '../StudioTableRemotePagination'; type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index cb266bffd37..cd336913151 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -2,7 +2,7 @@ import { useTableSorting } from './useTableSorting'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { Rows } from '../components'; +import type { Rows } from '../components'; type TestComponentProps = { rows: Rows; From 98e9a3d707a65634ca1aac34c32207bad51f966b Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 15:17:47 +0200 Subject: [PATCH 49/72] Fix component documentation --- .../StudioTableLocalPagination.mdx | 21 +++++++++++++----- .../StudioTableRemotePagination.mdx | 22 ++++++++++++++----- .../StudioTableRemotePagination/utils.tsx | 2 +- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 4a410171aaa..e39b2d8e635 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -9,9 +9,15 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination StudioTableLocalPagination is a wrapper for StudioTableRemotePagination. - - Use this component when you are fetching a complete set of data, and want the component to - handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`. - - Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. +
    +
  • + Use this component when you are fetching a complete set of data, and want the component to + handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`. +
  • +
  • + Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. +
  • +
@@ -68,8 +74,13 @@ const columns = [ Row format - * Rows must have an id that is unique. * The accessors in the columns array is used to display the - row properties. Therefore, each property name has to exactly match the accessor. +
    +
  • Rows must have an id that is unique.
  • +
  • + The accessors in the columns array is used to display the row properties. Therefore, each + property name has to exactly match the accessor. +
  • +
```tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 01115ba8348..6310df44f50 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -7,15 +7,22 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination + StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. It dynamically renders a table by passing an array of objects to the `column` and `row` props. - - Use this component when data is fetched in chunks, and the pagination logic is handled - externally. Otherwise, use `StudioTableLocalPagination`. - Pagination and sorting can be removed - by not passing props to `pagination` and `onSortClick`. +
    +
  • + Use this component when data is fetched in chunks, and the pagination logic is handled + externally. Otherwise, use `StudioTableLocalPagination`. +
  • +
  • + Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. +
  • +
@@ -78,8 +85,13 @@ const columns = [ Row format - * Rows must have an id that is unique. * The accessors in the columns array is used to display the - row properties. Therefore, each property name has to exactly match the accessor. +
    +
  • Rows must have an id that is unique.
  • +
  • + The accessors in the columns array is used to display the row properties. Therefore, each + property name has to exactly match the accessor. +
  • +
```tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx index 41805ed962c..a292352dab7 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.tsx @@ -1,7 +1,7 @@ import type { Rows } from './StudioTableRemotePagination'; export const getRowsToRender = (currentPage: number, pageSize: number, rows: Rows): Rows => { - if (!!pageSize === false) return rows; + if (!pageSize) return rows; const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; From e65d9c3098a0766f30609b21bf76e764c9bc6569 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 15:34:49 +0200 Subject: [PATCH 50/72] Test case where isSortable is false --- .../StudioTableLocalPagination.mdx | 2 +- .../StudioTableLocalPagination.test.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index e39b2d8e635..23ac3cd02a8 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -15,7 +15,7 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`.
  • - Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. + Pagination and sorting can be removed by not passing props to `pagination` and setting `isSortable` to `false`.
  • diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index 31e3cc522da..7c93fc2552f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -26,6 +26,16 @@ describe('StudioTableLocalPagination', () => { ).toBeInTheDocument(); }); + it('renders the complete table when isSortable is set to false', () => { + render(); + expect( + screen.getByRole('cell', { name: 'Coordinated register notification' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('cell', { name: 'Application for a certificate of good conduct' }), + ).toBeInTheDocument(); + }); + it('triggers sorting when a sortable column header is clicked', async () => { render(); From f5df33b8f3b879abab04f0dc2679f448d3b8b0f8 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 2 May 2024 15:42:20 +0200 Subject: [PATCH 51/72] Fix codestyle --- .../StudioTableLocalPagination/StudioTableLocalPagination.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 23ac3cd02a8..0630739f9fb 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -15,7 +15,8 @@ import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`.
  • - Pagination and sorting can be removed by not passing props to `pagination` and setting `isSortable` to `false`. + Pagination and sorting can be removed by not passing props to `pagination` and setting + `isSortable` to `false`.
  • From 4e6039213556d83ebe913f1e6957903fcf1853f7 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 3 May 2024 08:35:17 +0200 Subject: [PATCH 52/72] Handle case where rows array is empty --- .../StudioTableLocalPagination.tsx | 5 ++++- .../StudioTableRemotePagination.stories.tsx | 7 +++++-- .../StudioTableRemotePagination.tsx | 4 ++-- .../libs/studio-components/src/hooks/useTableSorting.tsx | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 41ea31e8494..8c8a1b06775 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -36,7 +36,10 @@ export const StudioTableLocalPagination = forwardRef< rowsToRender = getRowsToRender(currentPage, pageSize, rows); } - if (rowsToRender.length === 0) setCurrentPage(1); + if (!rowsToRender.length && (sortedRows.length || rows.length)) { + setCurrentPage(1); + } + const totalPages = Math.ceil(rows.length / pageSize); const paginationProps = pagination && { diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index f97cc20479a..830fd162bd4 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -27,7 +27,10 @@ export const Preview: Story = (args) => { const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); const totalPages = Math.ceil(rows.length / pageSize); - if (rowsToRender.length === 0) setCurrentPage(1); + + if (!rowsToRender.length && (sortedRows.length || rows.length)) { + setCurrentPage(1); + } const paginationProps = { currentPage, @@ -36,7 +39,7 @@ export const Preview: Story = (args) => { pageSizeLabel: 'Rows per page', onPageChange: setCurrentPage, onPageSizeChange: setPageSize, - itemLabel: (num) => `Page ${num}`, + itemLabel: (num: number) => `Page ${num}`, nextButtonText: 'Next', previousButtonText: 'Previous', }; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index cf01c6537c6..422b3893a13 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -36,8 +36,8 @@ export const StudioTableRemotePagination = forwardRef< HTMLTableElement, StudioTableRemotePaginationProps >(({ columns, rows, size = 'medium', onSortClick, pagination }, ref): React.ReactElement => { - const isSortable = !!onSortClick; - const isPaginationActive = !!pagination; + const isSortable = !!onSortClick && !!rows.length; + const isPaginationActive = !!pagination && !!rows.length; const { currentPage, diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index b32837c06f4..c6eca51c386 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -18,7 +18,7 @@ export const useTableSorting = (rows: Rows) => { } }; - let sortedRows; + let sortedRows: Rows; if (sortColumn !== null) { sortedRows = [...rows].sort((a, b) => { const columnA = a[sortColumn]; From ffcd4a737a5ef4b73d643e096b033530806b6df5 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 09:48:37 +0200 Subject: [PATCH 53/72] Fix documentation for StudioTableRemotePagination --- .../StudioTableRemotePagination.mdx | 49 +++---------------- .../StudioTableRemotePagination.tsx | 6 ++- .../StudioTableRemotePagination/mockData.tsx | 24 +++++++++ 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 6310df44f50..5df63df3d42 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -1,6 +1,8 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; import * as StudioTableRemotePaginationStories from './StudioTableRemotePagination.stories'; +import { StudioTableRemotePagination } from "./StudioTableRemotePagination" +import { propInfoColumns, propInfoRows } from "./mockData" @@ -10,50 +12,15 @@ import * as StudioTableRemotePaginationStories from './StudioTableRemotePaginati StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` - components. It dynamically renders a table by passing an array of objects to the `column` and - `row` props. - - -
      -
    • - Use this component when data is fetched in chunks, and the pagination logic is handled - externally. Otherwise, use `StudioTableLocalPagination`. -
    • -
    • - Pagination and sorting can be removed by not passing props to `pagination` and `onSortClick`. -
    • -
    + components. + Use this component when data is fetched in chunks, and the pagination logic is handled + externally. Otherwise, use `StudioTableLocalPagination`.
    - + +
    - - Type definitions - - -```tsx -type StudioTableRemotePaginationProps = { - columns: Record<'accessor' | 'value', string>[]; - rows: Rows; - size?: 'small' | 'medium' | 'large'; - onSortClick?: (columnKey: string) => void; - pagination?: PaginationProps; -}; - -type Rows = (Record & Record<'id', string | number>)[]; - -type PaginationProps = { - currentPage: number; - totalPages: number; - pageSizeOptions: number[]; - pageSizeLabel: string; - onPageChange: (currentPage: number) => void; - onPageSizeChange: (currentSize: number) => void; - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; -}; -``` + Column format diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 422b3893a13..c6fffd07b14 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -10,6 +10,8 @@ export const labelSizeMap: Record = { large: 'medium', }; +export type Columns = Record<'accessor' | 'value', string>[]; + export type Rows = (Record & Record<'id', string | number>)[]; export type PaginationProps = { @@ -24,8 +26,8 @@ export type PaginationProps = { itemLabel: (num: number) => string; }; -type StudioTableRemotePaginationProps = { - columns: Record<'accessor' | 'value', string>[]; +export type StudioTableRemotePaginationProps = { + columns: Columns; rows: Rows; size?: 'small' | 'medium' | 'large'; onSortClick?: (columnKey: string) => void; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index 072db31e7fc..e608f61c1c3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -146,3 +146,27 @@ export const rows: Rows = [ lastChanged: new Date('2023-04-10').toLocaleDateString(), }, ]; + +export const propInfoColumns = [ + { + accessor: 'id', + value: 'Props', + }, + { + accessor: 'description', + value: 'Description', + }, +]; + +export const propInfoRows = [ + { + id: 'onSortClick', + description: + 'Function to handle column header click for sorting. If not provided, sorting buttons are hidden.', + }, + { + id: 'pagination', + description: + 'Object that defines pagination settings for the table. If not provided, pagination is hidden.', + }, +]; From 9529305eda0fd829884f442c629a578d13959587 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 10:02:00 +0200 Subject: [PATCH 54/72] Fix documentation for StudioTableLocalPagination --- .../StudioTableLocalPagination.mdx | 41 ++++--------------- .../StudioTableRemotePagination.mdx | 7 ++-- .../StudioTableRemotePagination/mockData.tsx | 14 ++++++- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 0630739f9fb..fa8ab026e36 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -1,49 +1,22 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination.stories'; +import { propInfoColumns, propInfoRowsLocalPagination } from "../StudioTableRemotePagination/mockData" +import {StudioTableLocalPagination} from "./StudioTableLocalPagination"; StudioTableLocalPagination -StudioTableLocalPagination is a wrapper for StudioTableRemotePagination. -
      -
    • - Use this component when you are fetching a complete set of data, and want the component to - handle pagination and sorting internally. Otherwise, use `StudioTableRemotePagination`. -
    • -
    • - Pagination and sorting can be removed by not passing props to `pagination` and setting - `isSortable` to `false`. -
    • -
    -
    + The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. + - + +
    - - Type definitions - - -```tsx -type StudioTableLocalPaginationProps = { - columns: Record<'accessor' | 'value', string>[]; - rows: Rows; - size?: 'small' | 'medium' | 'large'; - isSortable?: boolean; - pagination?: { - pageSizeOptions: number[]; - pageSizeLabel: string; - nextButtonText: string; - previousButtonText: string; - itemLabel: (num: number) => string; - }; -}; - -type Rows = (Record & Record<'id', string | number>)[]; -``` + Column format diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 5df63df3d42..4ff11135c51 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -2,7 +2,7 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; import * as StudioTableRemotePaginationStories from './StudioTableRemotePagination.stories'; import { StudioTableRemotePagination } from "./StudioTableRemotePagination" -import { propInfoColumns, propInfoRows } from "./mockData" +import { propInfoColumns, propInfoRowsRemotePagination } from "./mockData" @@ -13,11 +13,10 @@ import { propInfoColumns, propInfoRows } from "./mockData" StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. - Use this component when data is fetched in chunks, and the pagination logic is handled - externally. Otherwise, use `StudioTableLocalPagination`. + This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. - +
    diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index e608f61c1c3..cdd00f2c8a3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -158,7 +158,7 @@ export const propInfoColumns = [ }, ]; -export const propInfoRows = [ +export const propInfoRowsRemotePagination = [ { id: 'onSortClick', description: @@ -170,3 +170,15 @@ export const propInfoRows = [ 'Object that defines pagination settings for the table. If not provided, pagination is hidden.', }, ]; + +export const propInfoRowsLocalPagination = [ + { + id: 'isSortable', + description: 'Indicates whether the component is sortable. Defaults to true.', + }, + { + id: 'pagination', + description: + 'Object that defines pagination settings for the table. If not provided, pagination is hidden.', + }, +]; From 1d2b5c039e7fb94c0b4f707c5ce26aa892d8147b Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 10:07:17 +0200 Subject: [PATCH 55/72] Add optional props heading --- .../StudioTableLocalPagination/StudioTableLocalPagination.mdx | 3 +++ .../StudioTableRemotePagination.mdx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index fa8ab026e36..a5e03dba492 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -13,6 +13,9 @@ import {StudioTableLocalPagination} from "./StudioTableLocalPagination"; The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. + + Optional props: +
    diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 4ff11135c51..2f4305965ca 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -16,6 +16,9 @@ import { propInfoColumns, propInfoRowsRemotePagination } from "./mockData" This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. + + Optional props: +
    From b2d01276b66c40b7bafcaef037a4ce0a668a5add Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 10:20:52 +0200 Subject: [PATCH 56/72] Fix StudioTableLocalPagination tests --- .../StudioTableLocalPagination.test.tsx | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index 7c93fc2552f..fbf392b07f8 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -5,7 +5,7 @@ import { StudioTableLocalPagination } from './StudioTableLocalPagination'; import { columns, rows } from '../StudioTableRemotePagination/mockData'; describe('StudioTableLocalPagination', () => { - const paginationProps = { + const paginationProps: StudioTableLocalPaginationProps['pagination'] = { pageSizeOptions: [5, 10, 50], pageSizeLabel: 'Rows per page', nextButtonText: 'Next', @@ -26,14 +26,10 @@ describe('StudioTableLocalPagination', () => { ).toBeInTheDocument(); }); - it('renders the complete table when isSortable is set to false', () => { + it('does not render sorting buttons when isSortable is set to false', () => { render(); - expect( - screen.getByRole('cell', { name: 'Coordinated register notification' }), - ).toBeInTheDocument(); - expect( - screen.getByRole('cell', { name: 'Application for a certificate of good conduct' }), - ).toBeInTheDocument(); + + expect(screen.queryByRole('button', { name: 'Name' })).not.toBeInTheDocument(); }); it('triggers sorting when a sortable column header is clicked', async () => { @@ -41,17 +37,26 @@ describe('StudioTableLocalPagination', () => { await userEvent.click(screen.getByRole('button', { name: 'Name' })); - const firstBodyRow = screen.getAllByRole('row')[1]; + const [, firstBodyRow, secondBodyRow] = screen.getAllByRole('row'); expect( within(firstBodyRow).getByRole('cell', { name: 'A-melding – all forms' }), ).toBeInTheDocument(); - const secondBodyRow = screen.getAllByRole('row')[2]; expect( within(secondBodyRow).getByRole('cell', { name: 'Application for VAT registration' }), ).toBeInTheDocument(); }); + it('renders the complete table when pagination is undefined', () => { + render(); + 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', () => { render( , @@ -77,7 +82,23 @@ describe('StudioTableLocalPagination', () => { ).toBeInTheDocument(); }); - it('changes page size when page size option is selected', async () => { + it('changes page when "Page 2" button is clicked', async () => { + render( + , + ); + + await userEvent.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( , ); From 5a03d28bd413e269b2b87ea580cf94b7dd64643b Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 10:43:40 +0200 Subject: [PATCH 57/72] Add config object to useTableSorting --- .../StudioTableLocalPagination.test.tsx | 1 + .../StudioTableLocalPagination.tsx | 11 +++-------- .../StudioTableRemotePagination.stories.tsx | 4 ++-- .../src/hooks/useTableSorting.test.tsx | 2 +- .../studio-components/src/hooks/useTableSorting.tsx | 9 ++++++++- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index fbf392b07f8..a28dc5b2f26 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { StudioTableLocalPagination } from './StudioTableLocalPagination'; +import type { StudioTableLocalPaginationProps } from './StudioTableLocalPagination'; import { columns, rows } from '../StudioTableRemotePagination/mockData'; describe('StudioTableLocalPagination', () => { diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 8c8a1b06775..be1605f8f6c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -4,7 +4,7 @@ import type { Rows } from '../StudioTableRemotePagination'; import { useTableSorting } from '../../hooks/useTableSorting'; import { getRowsToRender } from '../StudioTableRemotePagination/utils'; -type StudioTableLocalPaginationProps = { +export type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; rows: Rows; size?: 'small' | 'medium' | 'large'; @@ -27,14 +27,9 @@ export const StudioTableLocalPagination = forwardRef< const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); - const { handleSorting, sortedRows } = useTableSorting(rows); - let rowsToRender: Rows; + const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); - if (isSortable) { - rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); - } else { - rowsToRender = getRowsToRender(currentPage, pageSize, rows); - } + const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); if (!rowsToRender.length && (sortedRows.length || rows.length)) { setCurrentPage(1); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 830fd162bd4..cdf79a2f6a8 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -23,9 +23,9 @@ export const Preview: Story = (args) => { const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(5); - const { handleSorting, sortedRows } = useTableSorting(rows); + const { handleSorting, sortedRows } = useTableSorting(rows, { enable: true }); - const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows); + const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); const totalPages = Math.ceil(rows.length / pageSize); if (!rowsToRender.length && (sortedRows.length || rows.length)) { diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index cd336913151..ed606b9457d 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -11,7 +11,7 @@ type TestComponentProps = { type Row = Record & Record<'id', string | number>; const TestComponent = ({ rows }: TestComponentProps) => { - const { sortedRows, handleSorting } = useTableSorting(rows); + const { sortedRows, handleSorting } = useTableSorting(rows, { enable: true }); return (
    diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index c6eca51c386..15228b43851 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,10 +1,17 @@ import { useState } from 'react'; import type { Rows } from '../components'; -export const useTableSorting = (rows: Rows) => { +export const useTableSorting = (rows: Rows, config) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); + if (!config.enable) { + return { + sortedRows: undefined, + handleSorting: undefined, + }; + } + const toggleSortDirection = () => { setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); }; From 5d18f2927b257578d51e317f07500cacf7ac1704 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 14:01:12 +0200 Subject: [PATCH 58/72] Add empty table message prop --- .../StudioTableLocalPagination.stories.tsx | 1 + .../StudioTableLocalPagination.tsx | 73 ++++---- .../StudioTableRemotePagination.module.css | 5 + .../StudioTableRemotePagination.stories.tsx | 1 + .../StudioTableRemotePagination.tsx | 166 ++++++++++-------- 5 files changed, 135 insertions(+), 111 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index 90445107629..4a9594593b6 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -21,6 +21,7 @@ export const Preview: Story = (args) => ( columns={columns} rows={rows} size={args.size} + emptyTableMessage={'No data found'} isSortable={true} pagination={{ pageSizeOptions: [5, 10, 20, 50], diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index be1605f8f6c..22cc6a99e81 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -8,6 +8,7 @@ export type StudioTableLocalPaginationProps = { columns: Record<'accessor' | 'value', string>[]; rows: Rows; size?: 'small' | 'medium' | 'large'; + emptyTableMessage?: string; isSortable?: boolean; pagination?: { pageSizeOptions: number[]; @@ -21,44 +22,50 @@ export type StudioTableLocalPaginationProps = { export const StudioTableLocalPagination = forwardRef< HTMLTableElement, StudioTableLocalPaginationProps ->(({ columns, rows, isSortable = true, size = 'medium', pagination }, ref): React.ReactElement => { - const { pageSizeOptions, pageSizeLabel, itemLabel, nextButtonText, previousButtonText } = - pagination || {}; - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); +>( + ( + { columns, rows, isSortable = true, size = 'medium', emptyTableMessage, pagination }, + ref, + ): React.ReactElement => { + const { pageSizeOptions, pageSizeLabel, itemLabel, nextButtonText, previousButtonText } = + pagination || {}; + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); - const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); + const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); - const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); + const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - if (!rowsToRender.length && (sortedRows.length || rows.length)) { - setCurrentPage(1); - } + if (!rowsToRender.length && (sortedRows.length || rows.length)) { + setCurrentPage(1); + } - const totalPages = Math.ceil(rows.length / pageSize); + const totalPages = Math.ceil(rows.length / pageSize); - const paginationProps = pagination && { - currentPage, - totalPages, - pageSizeOptions, - pageSizeLabel, - onPageChange: setCurrentPage, - onPageSizeChange: setPageSize, - itemLabel, - nextButtonText, - previousButtonText, - }; + const paginationProps = pagination && { + currentPage, + totalPages, + pageSizeOptions, + pageSizeLabel, + onPageChange: setCurrentPage, + onPageSizeChange: setPageSize, + itemLabel, + nextButtonText, + previousButtonText, + }; - return ( - - ); -}); + return ( + + ); + }, +); StudioTableLocalPagination.displayName = 'StudioTableLocalPagination'; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css index 30b8342c233..fd8067641d3 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css @@ -2,6 +2,11 @@ width: 100%; } +.emptyTableMessage { + padding: var(--fds-spacing-3); + text-align: center; +} + .paginationContainer { display: flex; justify-content: space-between; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index cdf79a2f6a8..0ffe0084755 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -49,6 +49,7 @@ export const Preview: Story = (args) => { columns={columns} rows={rowsToRender} size={args.size} + emptyTableMessage={'No data found'} onSortClick={handleSorting} pagination={paginationProps} /> diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index c6fffd07b14..497d768af6e 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -1,9 +1,9 @@ -import { Label, NativeSelect, Pagination, Table } from '@digdir/design-system-react'; +import { Label, NativeSelect, Pagination, Paragraph, Table } from '@digdir/design-system-react'; import React, { forwardRef, useId } from 'react'; import classes from './StudioTableRemotePagination.module.css'; -type LabelSize = 'small' | 'medium' | 'xsmall'; type LabelSizeKeys = 'small' | 'medium' | 'large'; +type LabelSize = 'small' | 'medium' | 'xsmall'; export const labelSizeMap: Record = { small: 'xsmall', medium: 'small', @@ -11,7 +11,6 @@ export const labelSizeMap: Record = { }; export type Columns = Record<'accessor' | 'value', string>[]; - export type Rows = (Record & Record<'id', string | number>)[]; export type PaginationProps = { @@ -30,6 +29,7 @@ export type StudioTableRemotePaginationProps = { columns: Columns; rows: Rows; size?: 'small' | 'medium' | 'large'; + emptyTableMessage?: string; onSortClick?: (columnKey: string) => void; pagination?: PaginationProps; }; @@ -37,86 +37,96 @@ export type StudioTableRemotePaginationProps = { export const StudioTableRemotePagination = forwardRef< HTMLTableElement, StudioTableRemotePaginationProps ->(({ columns, rows, size = 'medium', onSortClick, pagination }, ref): React.ReactElement => { - const isSortable = !!onSortClick && !!rows.length; - const isPaginationActive = !!pagination && !!rows.length; +>( + ( + { columns, rows, size = 'medium', emptyTableMessage, onSortClick, pagination }, + ref, + ): React.ReactElement => { + const isSortable = !!onSortClick && !!rows.length; + const isPaginationActive = !!pagination && !!rows.length; - const { - currentPage, - totalPages, - pageSizeOptions, - pageSizeLabel, - onPageChange: handlePageChange, - onPageSizeChange: handlePageSizeChange, - nextButtonText, - previousButtonText, - itemLabel, - } = pagination || {}; + const { + currentPage, + totalPages, + pageSizeOptions, + pageSizeLabel, + onPageChange: handlePageChange, + onPageSizeChange: handlePageSizeChange, + nextButtonText, + previousButtonText, + itemLabel, + } = pagination || {}; - const labelId = useId(); - const labelSize = labelSizeMap[size]; + const labelId = useId(); + const labelSize = labelSizeMap[size]; - return ( - <> - - - - {columns.map(({ accessor, value }) => ( - onSortClick(accessor)} - > - {value} - - ))} - - - - {rows.map((row) => ( - - {columns.map(({ accessor }) => ( - {row[accessor]} + return ( + <> +
    + + + {columns.map(({ accessor, value }) => ( + onSortClick(accessor)} + > + {value} + ))} - ))} - -
    - {isPaginationActive && ( -
    -
    - handlePageSizeChange(Number(e.target.value))} - size={size} - > - {pageSizeOptions.map((pageSizeOption) => ( - - ))} - - + + + {rows.map((row) => ( + + {columns.map(({ accessor }) => ( + {row[accessor]} + ))} + + ))} + + + {!rows.length && ( + + {emptyTableMessage} + + )} + {isPaginationActive && ( +
    +
    + handlePageSizeChange(Number(e.target.value))} + size={size} + > + {pageSizeOptions.map((pageSizeOption) => ( + + ))} + + +
    + {totalPages > 1 && ( + + )}
    - {totalPages > 1 && ( - - )} -
    - )} - - ); -}); + )} + + ); + }, +); StudioTableRemotePagination.displayName = 'StudioTableRemotePagination'; From c6e5e2ef62ae99ca7eddf049bfd3971b2b05c40d Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 7 May 2024 15:14:40 +0200 Subject: [PATCH 59/72] Refactor conditions for isSortable and isPaginationActive --- .../StudioTableLocalPagination.tsx | 5 +++-- .../StudioTableRemotePagination.module.css | 2 +- .../StudioTableRemotePagination.tsx | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 22cc6a99e81..c4b2fe7508b 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -29,14 +29,15 @@ export const StudioTableLocalPagination = forwardRef< ): React.ReactElement => { const { pageSizeOptions, pageSizeLabel, itemLabel, nextButtonText, previousButtonText } = pagination || {}; + const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); - const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - if (!rowsToRender.length && (sortedRows.length || rows.length)) { + // Move pages if the current page gets removed when changing page size + if (!rowsToRender.length && (sortedRows?.length || rows?.length)) { setCurrentPage(1); } diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css index fd8067641d3..7443079f715 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.module.css @@ -10,7 +10,7 @@ .paginationContainer { display: flex; justify-content: space-between; - margin-top: var(--fds-spacing-4); + margin-top: var(--fds-spacing-5); } .selectorContainer { diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 497d768af6e..825858c672a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -42,8 +42,8 @@ export const StudioTableRemotePagination = forwardRef< { columns, rows, size = 'medium', emptyTableMessage, onSortClick, pagination }, ref, ): React.ReactElement => { - const isSortable = !!onSortClick && !!rows.length; - const isPaginationActive = !!pagination && !!rows.length; + const isSortable = onSortClick && rows.length > 0; + const isPaginationActive = pagination && rows.length > 0; const { currentPage, From 1aca0a4c7fb89f8d3d25f54e83515cac4c09373f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 07:55:21 +0200 Subject: [PATCH 60/72] Rename reziseLabelMap and its types --- .../StudioTableRemotePagination.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx index 825858c672a..4b9641a8086 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.tsx @@ -2,9 +2,9 @@ import { Label, NativeSelect, Pagination, Paragraph, Table } from '@digdir/desig import React, { forwardRef, useId } from 'react'; import classes from './StudioTableRemotePagination.module.css'; -type LabelSizeKeys = 'small' | 'medium' | 'large'; -type LabelSize = 'small' | 'medium' | 'xsmall'; -export const labelSizeMap: Record = { +type tableSize = 'small' | 'medium' | 'large'; +type labelSize = 'xsmall' | 'small' | 'medium'; +export const resizeLabelMap: Record = { small: 'xsmall', medium: 'small', large: 'medium', @@ -58,7 +58,7 @@ export const StudioTableRemotePagination = forwardRef< } = pagination || {}; const labelId = useId(); - const labelSize = labelSizeMap[size]; + const labelSize = resizeLabelMap[size]; return ( <> From 163831b15852069aecd73a6b1baa403654a40262 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 08:15:40 +0200 Subject: [PATCH 61/72] Use spread operator to show which props are processed in StudioTableLocalPagination --- .../StudioTableLocalPagination.tsx | 15 ++++----------- .../StudioTableRemotePagination.test.tsx | 4 ++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index c4b2fe7508b..1e00a734d4a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -27,11 +27,8 @@ export const StudioTableLocalPagination = forwardRef< { columns, rows, isSortable = true, size = 'medium', emptyTableMessage, pagination }, ref, ): React.ReactElement => { - const { pageSizeOptions, pageSizeLabel, itemLabel, nextButtonText, previousButtonText } = - pagination || {}; - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(pagination ? pageSizeOptions[0] : undefined); + const [pageSize, setPageSize] = useState(pagination?.pageSizeOptions[0] ?? undefined); const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); @@ -43,16 +40,12 @@ export const StudioTableLocalPagination = forwardRef< const totalPages = Math.ceil(rows.length / pageSize); - const paginationProps = pagination && { + const studioTableRemotePaginationProps = pagination && { + ...pagination, currentPage, totalPages, - pageSizeOptions, - pageSizeLabel, onPageChange: setCurrentPage, onPageSizeChange: setPageSize, - itemLabel, - nextButtonText, - previousButtonText, }; return ( @@ -62,7 +55,7 @@ export const StudioTableLocalPagination = forwardRef< size={size} emptyTableMessage={emptyTableMessage} onSortClick={isSortable && handleSorting} - pagination={paginationProps} + pagination={studioTableRemotePaginationProps} ref={ref} /> ); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index 9df640a1d78..521f2342e0a 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { StudioTableRemotePagination } from './StudioTableRemotePagination'; +import { PaginationProps, StudioTableRemotePagination } from './StudioTableRemotePagination'; import { columns, rows } from './mockData'; describe('StudioTableRemotePagination', () => { - const paginationProps = { + const paginationProps: PaginationProps = { currentPage: 1, totalPages: 2, pageSizeOptions: [5, 10, 20], From fec567277bd3402227aec3921fef1e30e03447d1 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 09:56:26 +0200 Subject: [PATCH 62/72] Move 'lets' into states --- .../StudioTableLocalPagination.tsx | 30 ++++++++++++++----- .../src/hooks/useTableSorting.tsx | 29 ++++++++++-------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 1e00a734d4a..aeb08c3f3cf 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useState } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; import { StudioTableRemotePagination } from '../StudioTableRemotePagination'; import type { Rows } from '../StudioTableRemotePagination'; import { useTableSorting } from '../../hooks/useTableSorting'; @@ -31,12 +31,26 @@ export const StudioTableLocalPagination = forwardRef< const [pageSize, setPageSize] = useState(pagination?.pageSizeOptions[0] ?? undefined); const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); - const rowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - // Move pages if the current page gets removed when changing page size - if (!rowsToRender.length && (sortedRows?.length || rows?.length)) { - setCurrentPage(1); - } + const initialRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); + const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); + + const handlePageSizeChange = (newPageSize: number) => { + const newRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); + + // Move user back to page one, if current page is removed + if (!newRowsToRender.length) { + setCurrentPage(1); + } + + setPageSize(newPageSize); + setRowsToRender(newRowsToRender); + }; + + const handlePageChange = (newPage: number) => { + setCurrentPage(newPage); + setRowsToRender(getRowsToRender(newPage, pageSize, sortedRows || rows)); + }; const totalPages = Math.ceil(rows.length / pageSize); @@ -44,8 +58,8 @@ export const StudioTableLocalPagination = forwardRef< ...pagination, currentPage, totalPages, - onPageChange: setCurrentPage, - onPageSizeChange: setPageSize, + onPageChange: handlePageChange, + onPageSizeChange: handlePageSizeChange, }; return ( diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 15228b43851..6349618c07a 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,9 +1,10 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import type { Rows } from '../components'; export const useTableSorting = (rows: Rows, config) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); + const [sortedRows, setSortedRows] = useState(rows); if (!config.enable) { return { @@ -25,18 +26,20 @@ export const useTableSorting = (rows: Rows, config) => { } }; - let sortedRows: Rows; - if (sortColumn !== null) { - sortedRows = [...rows].sort((a, b) => { - const columnA = a[sortColumn]; - const columnB = b[sortColumn]; - if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; - if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; - return 0; - }); - } else { - sortedRows = rows; - } + useEffect(() => { + if (sortColumn !== null) { + const newSortedRows = [...rows].sort((a, b) => { + const columnA = a[sortColumn]; + const columnB = b[sortColumn]; + if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; + if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + setSortedRows(newSortedRows); + } else { + setSortedRows(rows); + } + }, [sortColumn, sortDirection, rows]); return { sortedRows, From 171acb5e071a43aa36355c447181e3e23e3855f1 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 10:49:43 +0200 Subject: [PATCH 63/72] Remove storybook formatting errors --- .../StudioTableLocalPagination.mdx | 24 ++++++++---------- .../StudioTableLocalPagination.tsx | 4 +-- .../StudioTableRemotePagination.mdx | 25 +++++++------------ 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index a5e03dba492..5007e67030c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -9,13 +9,11 @@ import {StudioTableLocalPagination} from "./StudioTableLocalPagination"; StudioTableLocalPagination - - The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. - +The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. - Optional props: - + Optional props: +
    @@ -50,15 +48,13 @@ const columns = [ Row format - -
      -
    • Rows must have an id that is unique.
    • -
    • - The accessors in the columns array is used to display the row properties. Therefore, each - property name has to exactly match the accessor. -
    • -
    -
    +
      +
    • Rows must have an id that is unique.
    • +
    • + The accessors in the columns array is used to display the row properties. Therefore, each + property name has to exactly match the accessor. +
    • +
    ```tsx const rows = [ diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index aeb08c3f3cf..e41402a2b7f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -33,12 +33,12 @@ export const StudioTableLocalPagination = forwardRef< const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); const initialRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); + const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); const handlePageSizeChange = (newPageSize: number) => { const newRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); - // Move user back to page one, if current page is removed + // Move user back to page 1 if current page is removed if (!newRowsToRender.length) { setCurrentPage(1); } diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 2f4305965ca..6f45afca158 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -9,16 +9,11 @@ import { propInfoColumns, propInfoRowsRemotePagination } from "./mockData" StudioTableRemotePagination - - - StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` - components. - This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. - +StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. Optional props: - +
    @@ -53,15 +48,13 @@ const columns = [ Row format - -
      -
    • Rows must have an id that is unique.
    • -
    • - The accessors in the columns array is used to display the row properties. Therefore, each - property name has to exactly match the accessor. -
    • -
    -
    +
      +
    • Rows must have an id that is unique.
    • +
    • + The accessors in the columns array is used to display the row properties. Therefore, each + property name has to exactly match the accessor. +
    • +
    ```tsx const rows = [ From 4c791f56a6aaafd3d22242b0e6835daf8a3c9297 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 12:32:48 +0200 Subject: [PATCH 64/72] Update StudioTableRemotePagination documentation --- .../StudioTableRemotePagination.mdx | 8 +------- .../StudioTableRemotePagination.stories.tsx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 6f45afca158..ad630d9a546 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -11,12 +11,6 @@ import { propInfoColumns, propInfoRowsRemotePagination } from "./mockData" StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. - - Optional props: - - -
    - @@ -49,7 +43,7 @@ const columns = [ Row format
      -
    • Rows must have an id that is unique.
    • +
    • Rows must have an `id` that is unique.
    • The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx index 0ffe0084755..a7bb2fa1ee2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.stories.tsx @@ -11,9 +11,27 @@ const meta: Meta = { title: 'Studio/StudioTableRemotePagination', component: StudioTableRemotePagination, 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.', + }, + onSortClick: { + description: + 'Function to be invoked when a sortable column header is clicked. If not provided, sorting buttons are hidden.', + }, + pagination: { + description: + 'An object containing pagination-related props. If not provided, pagination is hidden.', }, }, }; From c07a0e6ca41f570985f6c3c65929eee51305c2cb Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 12:43:02 +0200 Subject: [PATCH 65/72] Update StudioTableLocalPagination documentation --- .../StudioTableLocalPagination.mdx | 8 +------- .../StudioTableLocalPagination.stories.tsx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 5007e67030c..05896c98dba 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -11,12 +11,6 @@ import {StudioTableLocalPagination} from "./StudioTableLocalPagination"; The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. - - Optional props: - - -
      - @@ -49,7 +43,7 @@ const columns = [ Row format
        -
      • Rows must have an id that is unique.
      • +
      • Rows must have an `id` that is unique.
      • The accessors in the columns array is used to display the row properties. Therefore, each property name has to exactly match the accessor. diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx index 4a9594593b6..6a3687d8aa5 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.stories.tsx @@ -9,9 +9,27 @@ 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.', }, }, }; From 20a23005ad2e7dcfd53580f66e22ac5af09d94a6 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 13:32:53 +0200 Subject: [PATCH 66/72] Fix handlePageSizeChange --- .../StudioTableLocalPagination.tsx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index e41402a2b7f..0b2a0ce432f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -35,21 +35,22 @@ export const StudioTableLocalPagination = forwardRef< const initialRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); + const handlePageChange = (newPage: number) => { + setCurrentPage(newPage); + setRowsToRender(getRowsToRender(newPage, pageSize, sortedRows || rows)); + }; + const handlePageSizeChange = (newPageSize: number) => { - const newRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); + setPageSize(newPageSize); - // Move user back to page 1 if current page is removed - if (!newRowsToRender.length) { + const updatedRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); + if (!updatedRowsToRender.length) { + // Set table to page 1 if current page becomes obsolete + setRowsToRender(getRowsToRender(1, newPageSize, sortedRows || rows)); setCurrentPage(1); + } else { + setRowsToRender(updatedRowsToRender); } - - setPageSize(newPageSize); - setRowsToRender(newRowsToRender); - }; - - const handlePageChange = (newPage: number) => { - setCurrentPage(newPage); - setRowsToRender(getRowsToRender(newPage, pageSize, sortedRows || rows)); }; const totalPages = Math.ceil(rows.length / pageSize); From f21e0a271629e9220f40d5541ccb7734050f8a90 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 8 May 2024 14:29:26 +0200 Subject: [PATCH 67/72] Write test for utility function --- .../utils.test.tsx | 49 ++++++++++++ .../src/hooks/useTableSorting.test.tsx | 80 +++++++------------ .../src/hooks/useTableSorting.tsx | 8 +- 3 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx new file mode 100644 index 00000000000..d62e87b9697 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx @@ -0,0 +1,49 @@ +import { getRowsToRender } from './utils'; + +describe('getRowsToRender', () => { + const rows = [ + { id: 1, name: 'Row 1' }, + { id: 2, name: 'Row 2' }, + { id: 3, name: 'Row 3' }, + { id: 4, name: 'Row 4' }, + { id: 5, name: 'Row 5' }, + ]; + + it('should return all rows when pageSize is 0', () => { + const currentPage = 1; + const pageSize = 0; + const result = getRowsToRender(currentPage, pageSize, rows); + expect(result).toEqual(rows); + }); + + it('should return the correct rows for the first page', () => { + const currentPage = 1; + const pageSize = 2; + const result = getRowsToRender(currentPage, pageSize, rows); + expect(result).toEqual([ + { id: 1, name: 'Row 1' }, + { id: 2, name: 'Row 2' }, + ]); + }); + + it('should return the correct rows for the last page', () => { + const currentPage = 3; + const pageSize = 2; + const result = getRowsToRender(currentPage, pageSize, rows); + expect(result).toEqual([{ id: 5, name: 'Row 5' }]); + }); + + it('should return an empty array when currentPage is out of range', () => { + const currentPage = 4; + const pageSize = 2; + const result = getRowsToRender(currentPage, pageSize, rows); + expect(result).toEqual([]); + }); + + it('should return an empty array when rows is empty', () => { + const currentPage = 1; + const pageSize = 2; + const result = getRowsToRender(currentPage, pageSize, []); + expect(result).toEqual([]); + }); +}); diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index ed606b9457d..b947dc13a1f 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -1,32 +1,7 @@ import { useTableSorting } from './useTableSorting'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; import type { Rows } from '../components'; -type TestComponentProps = { - rows: Rows; -}; - -type Row = Record & Record<'id', string | number>; - -const TestComponent = ({ rows }: TestComponentProps) => { - const { sortedRows, handleSorting } = useTableSorting(rows, { enable: true }); - return ( -
        - - -
          - {sortedRows.map((row: Row) => ( -
        • - {row.name} - {row.creator} -
        • - ))} -
        -
        - ); -}; - describe('useTableSorting', () => { const rows: Rows = [ { @@ -47,38 +22,43 @@ describe('useTableSorting', () => { ]; it('should render the initial state', () => { - render(); - expect(screen.getByText('A form - Digdir')).toBeInTheDocument(); - expect(screen.getByText('B form - Brreg')).toBeInTheDocument(); - expect(screen.getByText('C form - Skatt')).toBeInTheDocument(); + const { result } = renderHook(() => useTableSorting(rows, { enable: true })); + expect(result.current.sortedRows).toEqual(rows); }); it('should sort rows in ascending order when a column is clicked', async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByText('Sort by Name')); - expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('A form - Digdir'); - expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('B form - Brreg'); - expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('C form - Skatt'); + const { result } = renderHook(() => useTableSorting(rows, { enable: true })); + await waitFor(() => result.current.handleSorting('creator')); + + const CreatorsAscending: string[] = []; + result.current.sortedRows.forEach((row) => { + CreatorsAscending.push(row.creator); + }); + + expect(CreatorsAscending[0]).toEqual('Brreg'); + expect(CreatorsAscending[1]).toEqual('Digdir'); + expect(CreatorsAscending[2]).toEqual('Skatt'); }); it('should sort rows in descending order when the same column is clicked again', async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByText('Sort by Creator')); - await user.click(screen.getByText('Sort by Creator')); - expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('C form - Skatt'); - expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('A form - Digdir'); - expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('B form - Brreg'); + const { result } = renderHook(() => useTableSorting(rows, { enable: true })); + await waitFor(() => result.current.handleSorting('creator')); + await waitFor(() => result.current.handleSorting('creator')); + + const CreatorsDescending: string[] = []; + result.current.sortedRows.forEach((row) => { + CreatorsDescending.push(row.creator); + }); + + expect(CreatorsDescending[0]).toEqual('Skatt'); + expect(CreatorsDescending[1]).toEqual('Digdir'); + expect(CreatorsDescending[2]).toEqual('Brreg'); }); it('should reset the sort direction to ascending when a different column is clicked', async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByText('Sort by Name')); - await user.click(screen.getByText('Sort by Creator')); - expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('B form - Brreg'); - expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('A form - Digdir'); - expect(screen.getAllByRole('listitem')[2]).toHaveTextContent('C form - Skatt'); + const { result } = renderHook(() => useTableSorting(rows, { enable: true })); + await waitFor(() => result.current.handleSorting('creator')); + await waitFor(() => result.current.handleSorting('id')); + expect(result.current.sortedRows).toEqual(rows); }); }); diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 6349618c07a..a1a5a137c5e 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -29,10 +29,10 @@ export const useTableSorting = (rows: Rows, config) => { useEffect(() => { if (sortColumn !== null) { const newSortedRows = [...rows].sort((a, b) => { - const columnA = a[sortColumn]; - const columnB = b[sortColumn]; - if (columnA < columnB) return sortDirection === 'asc' ? -1 : 1; - if (columnA > columnB) return sortDirection === 'asc' ? 1 : -1; + const rowA = a[sortColumn]; + const rowB = b[sortColumn]; + if (rowA > rowB) return sortDirection === 'asc' ? 1 : -1; + if (rowA < rowB) return sortDirection === 'asc' ? -1 : 1; return 0; }); setSortedRows(newSortedRows); From 6d55742c0e5d8c1d76b4bfda612e0a22362b3857 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 13 May 2024 10:26:44 +0200 Subject: [PATCH 68/72] Fix sorting triggering for StudioTableLocalPagination and fix tests --- .../StudioTableLocalPagination.test.tsx | 17 ++++- .../StudioTableLocalPagination.tsx | 9 ++- .../StudioTableRemotePagination.test.tsx | 28 +++++++- .../StudioTableRemotePagination/mockData.tsx | 68 +++++-------------- .../utils.test.tsx | 27 +++++--- .../src/hooks/useTableSorting.test.tsx | 20 +++--- .../src/hooks/useTableSorting.tsx | 16 ++--- 7 files changed, 96 insertions(+), 89 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index a28dc5b2f26..0d47ffd62be 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -48,7 +48,7 @@ describe('StudioTableLocalPagination', () => { ).toBeInTheDocument(); }); - it('renders the complete table when pagination is undefined', () => { + it('renders the complete table when pagination prop is not provided', () => { render(); expect( screen.getByRole('cell', { name: 'Coordinated register notification' }), @@ -67,7 +67,7 @@ describe('StudioTableLocalPagination', () => { expect(screen.getByRole('button', { name: 'Next' })).toBeInTheDocument(); }); - it('changes page when next button is clicked', async () => { + it('changes page when the "Next" button is clicked', async () => { render( , ); @@ -83,7 +83,7 @@ describe('StudioTableLocalPagination', () => { ).toBeInTheDocument(); }); - it('changes page when "Page 2" button is clicked', async () => { + it('changes page when the "Page 2" button is clicked', async () => { render( , ); @@ -126,4 +126,15 @@ describe('StudioTableLocalPagination', () => { 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( + , + ); + expect(screen.getByRole('paragraph', { value: 'No rows to display' })).toBeInTheDocument(); + }); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 0b2a0ce432f..4576c9e7941 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -32,9 +32,14 @@ export const StudioTableLocalPagination = forwardRef< const { handleSorting, sortedRows } = useTableSorting(rows, { enable: isSortable }); - const initialRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); + const initialRowsToRender = getRowsToRender(currentPage, pageSize, rows); const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); + useEffect(() => { + setRowsToRender(getRowsToRender(currentPage, pageSize, sortedRows || rows)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sortedRows]); + const handlePageChange = (newPage: number) => { setCurrentPage(newPage); setRowsToRender(getRowsToRender(newPage, pageSize, sortedRows || rows)); @@ -45,7 +50,7 @@ export const StudioTableLocalPagination = forwardRef< const updatedRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); if (!updatedRowsToRender.length) { - // Set table to page 1 if current page becomes obsolete + // If the new page size results in an empty page, reset to the first page setRowsToRender(getRowsToRender(1, newPageSize, sortedRows || rows)); setCurrentPage(1); } else { diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index 521f2342e0a..faf6e9c263f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { PaginationProps, StudioTableRemotePagination } from './StudioTableRemotePagination'; +import { StudioTableRemotePagination } from './StudioTableRemotePagination'; +import type { PaginationProps } from './StudioTableRemotePagination'; import { columns, rows } from './mockData'; describe('StudioTableRemotePagination', () => { @@ -57,7 +58,7 @@ describe('StudioTableRemotePagination', () => { expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument(); }); - it('triggers the onPageChange callback when a page is clicked', async () => { + it('triggers the onPageChange function when "Next" is clicked', async () => { render( , ); @@ -67,7 +68,17 @@ describe('StudioTableRemotePagination', () => { expect(paginationProps.onPageChange).toHaveBeenCalledWith(2); }); - it('triggers the onPageSizeChange callback when the page size is changed', async () => { + it('triggers the onPageChange function when "Page 2" is clicked', async () => { + render( + , + ); + + await userEvent.click(screen.getByRole('button', { name: 'Page 2' })); + + expect(paginationProps.onPageChange).toHaveBeenCalledWith(2); + }); + + it('triggers the onPageSizeChange function when the page size is changed', async () => { render( , ); @@ -76,4 +87,15 @@ describe('StudioTableRemotePagination', () => { expect(paginationProps.onPageSizeChange).toHaveBeenCalledWith(10); }); + + it('displays the empty table message when there are no rows to display', () => { + render( + , + ); + expect(screen.getByRole('paragraph', { value: 'No rows to display' })).toBeInTheDocument(); + }); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx index cdd00f2c8a3..58a8ec69646 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/mockData.tsx @@ -38,147 +38,111 @@ export const rows: Rows = [ icon: } />, name: 'Coordinated register notification', creator: 'Brønnøysund Register Centre', - lastChanged: new Date('2023-04-12').toLocaleDateString(), + lastChanged: '12-04-2023', }, { id: 2, icon: } />, name: 'Application for authorisation and license as a healthcare personnel', creator: 'The Norwegian Directorate of Health', - lastChanged: new Date('2023-04-05').toLocaleDateString(), + lastChanged: '05-04-2023', }, { id: 3, icon: } />, name: 'Produkter og tjenester fra Brønnøysundregistrene', creator: 'Brønnøysund Register Centre', - lastChanged: new Date('2023-04-16').toLocaleDateString(), + lastChanged: '16-04-2023', }, { id: 4, icon: } />, name: 'Contact form - Norwegian Tax Administration (private individual)', creator: 'Tax Administration', - lastChanged: new Date('2023-04-08').toLocaleDateString(), + lastChanged: '08-04-2023', }, { id: 5, icon: } />, name: 'Contact form - Norwegian Tax Administration (commercial)', creator: 'Tax Administration', - lastChanged: new Date('2023-04-01').toLocaleDateString(), + lastChanged: '01-04-2023', }, { id: 6, icon: } />, name: 'A-melding – all forms', creator: 'Brønnøysund Register Centre', - lastChanged: new Date('2023-04-14').toLocaleDateString(), + lastChanged: '14-04-2023', }, { id: 7, icon: } />, name: 'Application for VAT registration', creator: 'Tax Administration', - lastChanged: new Date('2023-04-03').toLocaleDateString(), + lastChanged: '03-04-2023', }, { id: 8, icon: } />, name: 'Reporting of occupational injuries and diseases', creator: 'Norwegian Labour Inspection Authority', - lastChanged: new Date('2023-04-11').toLocaleDateString(), + lastChanged: '11-04-2023', }, { id: 9, icon: } />, name: 'Application for a residence permit', creator: 'Norwegian Directorate of Immigration', - lastChanged: new Date('2023-04-06').toLocaleDateString(), + lastChanged: '06-04-2023', }, { id: 10, icon: } />, name: 'Application for a work permit', creator: 'Norwegian Directorate of Immigration', - lastChanged: new Date('2023-04-15').toLocaleDateString(), + lastChanged: '15-04-2023', }, { id: 11, icon: } />, name: 'Notification of change of address', creator: 'Norwegian Tax Administration', - lastChanged: new Date('2023-04-09').toLocaleDateString(), + lastChanged: '09-04-2023', }, { id: 12, icon: } />, name: 'Application for a Norwegian national ID number', creator: 'Norwegian Tax Administration', - lastChanged: new Date('2023-04-02').toLocaleDateString(), + lastChanged: '02-04-2023', }, { id: 13, icon: } />, name: 'Reporting of temporary layoffs', creator: 'Norwegian Labour and Welfare Administration', - lastChanged: new Date('2023-04-07').toLocaleDateString(), + lastChanged: '07-04-2023', }, { id: 14, icon: } />, name: 'Application for parental benefit', creator: 'Norwegian Labour and Welfare Administration', - lastChanged: new Date('2023-04-13').toLocaleDateString(), + lastChanged: '13-04-2023', }, { id: 15, icon: } />, name: 'Reporting of VAT', creator: 'Tax Administration', - lastChanged: new Date('2023-04-04').toLocaleDateString(), + lastChanged: '04-04-2023', }, { id: 16, icon: } />, name: 'Application for a certificate of good conduct', creator: 'Norwegian Police', - lastChanged: new Date('2023-04-10').toLocaleDateString(), - }, -]; - -export const propInfoColumns = [ - { - accessor: 'id', - value: 'Props', - }, - { - accessor: 'description', - value: 'Description', - }, -]; - -export const propInfoRowsRemotePagination = [ - { - id: 'onSortClick', - description: - 'Function to handle column header click for sorting. If not provided, sorting buttons are hidden.', - }, - { - id: 'pagination', - description: - 'Object that defines pagination settings for the table. If not provided, pagination is hidden.', - }, -]; - -export const propInfoRowsLocalPagination = [ - { - id: 'isSortable', - description: 'Indicates whether the component is sortable. Defaults to true.', - }, - { - id: 'pagination', - description: - 'Object that defines pagination settings for the table. If not provided, pagination is hidden.', + lastChanged: '10-04-2023', }, ]; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx index d62e87b9697..730c9e196f2 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx @@ -12,15 +12,17 @@ describe('getRowsToRender', () => { it('should return all rows when pageSize is 0', () => { const currentPage = 1; const pageSize = 0; - const result = getRowsToRender(currentPage, pageSize, rows); - expect(result).toEqual(rows); + // eslint-disable-next-line testing-library/render-result-naming-convention + const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual(rows); }); it('should return the correct rows for the first page', () => { const currentPage = 1; const pageSize = 2; - const result = getRowsToRender(currentPage, pageSize, rows); - expect(result).toEqual([ + // eslint-disable-next-line testing-library/render-result-naming-convention + const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([ { id: 1, name: 'Row 1' }, { id: 2, name: 'Row 2' }, ]); @@ -29,21 +31,24 @@ describe('getRowsToRender', () => { it('should return the correct rows for the last page', () => { const currentPage = 3; const pageSize = 2; - const result = getRowsToRender(currentPage, pageSize, rows); - expect(result).toEqual([{ id: 5, name: 'Row 5' }]); + // eslint-disable-next-line testing-library/render-result-naming-convention + const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([{ id: 5, name: 'Row 5' }]); }); it('should return an empty array when currentPage is out of range', () => { const currentPage = 4; const pageSize = 2; - const result = getRowsToRender(currentPage, pageSize, rows); - expect(result).toEqual([]); + // eslint-disable-next-line testing-library/render-result-naming-convention + const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([]); }); - it('should return an empty array when rows is empty', () => { + it('should return an empty array when rows is an empty array', () => { const currentPage = 1; const pageSize = 2; - const result = getRowsToRender(currentPage, pageSize, []); - expect(result).toEqual([]); + // eslint-disable-next-line testing-library/render-result-naming-convention + const rowsToRender = getRowsToRender(currentPage, pageSize, []); + expect(rowsToRender).toEqual([]); }); }); diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index b947dc13a1f..69c71e63e2d 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -30,14 +30,14 @@ describe('useTableSorting', () => { const { result } = renderHook(() => useTableSorting(rows, { enable: true })); await waitFor(() => result.current.handleSorting('creator')); - const CreatorsAscending: string[] = []; + const creatorsAscending: string[] = []; result.current.sortedRows.forEach((row) => { - CreatorsAscending.push(row.creator); + creatorsAscending.push(String(row.creator)); }); - expect(CreatorsAscending[0]).toEqual('Brreg'); - expect(CreatorsAscending[1]).toEqual('Digdir'); - expect(CreatorsAscending[2]).toEqual('Skatt'); + expect(creatorsAscending[0]).toEqual('Brreg'); + expect(creatorsAscending[1]).toEqual('Digdir'); + expect(creatorsAscending[2]).toEqual('Skatt'); }); it('should sort rows in descending order when the same column is clicked again', async () => { @@ -45,14 +45,14 @@ describe('useTableSorting', () => { await waitFor(() => result.current.handleSorting('creator')); await waitFor(() => result.current.handleSorting('creator')); - const CreatorsDescending: string[] = []; + const creatorsDescending: string[] = []; result.current.sortedRows.forEach((row) => { - CreatorsDescending.push(row.creator); + creatorsDescending.push(String(row.creator)); }); - expect(CreatorsDescending[0]).toEqual('Skatt'); - expect(CreatorsDescending[1]).toEqual('Digdir'); - expect(CreatorsDescending[2]).toEqual('Brreg'); + expect(creatorsDescending[0]).toEqual('Skatt'); + expect(creatorsDescending[1]).toEqual('Digdir'); + expect(creatorsDescending[2]).toEqual('Brreg'); }); it('should reset the sort direction to ascending when a different column is clicked', async () => { diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index a1a5a137c5e..70380add821 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,18 +1,11 @@ import { useState, useEffect } from 'react'; import type { Rows } from '../components'; -export const useTableSorting = (rows: Rows, config) => { +export const useTableSorting = (rows: Rows, config: Record<'enable', boolean>) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); const [sortedRows, setSortedRows] = useState(rows); - if (!config.enable) { - return { - sortedRows: undefined, - handleSorting: undefined, - }; - } - const toggleSortDirection = () => { setSortDirection((prevDirection) => (prevDirection === 'asc' ? 'desc' : 'asc')); }; @@ -41,6 +34,13 @@ export const useTableSorting = (rows: Rows, config) => { } }, [sortColumn, sortDirection, rows]); + if (!config.enable) { + return { + sortedRows: undefined, + handleSorting: undefined, + }; + } + return { sortedRows, handleSorting, From 06f5f7fdedfe194940244eb880b7aba511c753c8 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 13 May 2024 11:08:26 +0200 Subject: [PATCH 69/72] Clean up StudioTableLocalPagination --- .../StudioTableLocalPagination.mdx | 12 ++++++-- .../StudioTableLocalPagination.tsx | 30 +++++++------------ .../StudioTableRemotePagination.mdx | 10 +++++-- .../src/hooks/useTableSorting.tsx | 4 +-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 05896c98dba..65c1ebcba5d 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -1,15 +1,21 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; import * as StudioTableLocalPaginationStories from './StudioTableLocalPagination.stories'; -import { propInfoColumns, propInfoRowsLocalPagination } from "../StudioTableRemotePagination/mockData" -import {StudioTableLocalPagination} from "./StudioTableLocalPagination"; +import { + propInfoColumns, + propInfoRowsLocalPagination, +} from '../StudioTableRemotePagination/mockData'; +import { StudioTableLocalPagination } from './StudioTableLocalPagination'; StudioTableLocalPagination -The StudioTableLocalPagination component handles pagination internally, eliminating the need for manual control. It seamlessly manages pagination logic for you. + + The StudioTableLocalPagination component handles pagination internally, eliminating the need for + manual control. It seamlessly manages pagination logic for you. + diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 4576c9e7941..23893fd4844 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -36,27 +36,17 @@ export const StudioTableLocalPagination = forwardRef< const [rowsToRender, setRowsToRender] = useState(initialRowsToRender); useEffect(() => { - setRowsToRender(getRowsToRender(currentPage, pageSize, sortedRows || rows)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortedRows]); + const newRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - const handlePageChange = (newPage: number) => { - setCurrentPage(newPage); - setRowsToRender(getRowsToRender(newPage, pageSize, sortedRows || rows)); - }; - - const handlePageSizeChange = (newPageSize: number) => { - setPageSize(newPageSize); - - const updatedRowsToRender = getRowsToRender(currentPage, newPageSize, sortedRows || rows); - if (!updatedRowsToRender.length) { - // If the new page size results in an empty page, reset to the first page - setRowsToRender(getRowsToRender(1, newPageSize, sortedRows || rows)); + const outOfRange = !newRowsToRender.length && currentPage > 1; + if (outOfRange) { setCurrentPage(1); - } else { - setRowsToRender(updatedRowsToRender); + setRowsToRender(getRowsToRender(1, pageSize, sortedRows || rows)); + return; } - }; + + setRowsToRender(newRowsToRender); + }, [sortedRows, currentPage, pageSize]); const totalPages = Math.ceil(rows.length / pageSize); @@ -64,8 +54,8 @@ export const StudioTableLocalPagination = forwardRef< ...pagination, currentPage, totalPages, - onPageChange: handlePageChange, - onPageSizeChange: handlePageSizeChange, + onPageChange: setCurrentPage, + onPageSizeChange: setPageSize, }; return ( diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index ad630d9a546..488ca8df562 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -1,15 +1,19 @@ import { Canvas, Meta } from '@storybook/blocks'; import { Heading, Paragraph } from '@digdir/design-system-react'; import * as StudioTableRemotePaginationStories from './StudioTableRemotePagination.stories'; -import { StudioTableRemotePagination } from "./StudioTableRemotePagination" -import { propInfoColumns, propInfoRowsRemotePagination } from "./mockData" +import { StudioTableRemotePagination } from './StudioTableRemotePagination'; +import { propInfoColumns, propInfoRowsRemotePagination } from './mockData'; StudioTableRemotePagination -StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. This component is useful when data is retrieved in chunks, and the pagination logic is managed externally. + + StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` + components. This component is useful when data is retrieved in chunks, and the pagination logic is + managed externally.{' '} + diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx index 70380add821..9f39bb88bf8 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import type { Rows } from '../components'; -export const useTableSorting = (rows: Rows, config: Record<'enable', boolean>) => { +export const useTableSorting = (rows: Rows, options: Record<'enable', boolean>) => { const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState('asc'); const [sortedRows, setSortedRows] = useState(rows); @@ -34,7 +34,7 @@ export const useTableSorting = (rows: Rows, config: Record<'enable', boolean>) = } }, [sortColumn, sortDirection, rows]); - if (!config.enable) { + if (!options.enable) { return { sortedRows: undefined, handleSorting: undefined, From 67f626e16cc929f99c8470c684cd8e68a2bba3a8 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 13 May 2024 11:57:19 +0200 Subject: [PATCH 70/72] Fix empty message test --- .../StudioTableLocalPagination.mdx | 4 ++-- .../StudioTableLocalPagination.test.tsx | 2 +- .../StudioTableLocalPagination.tsx | 6 +++--- .../StudioTableRemotePagination.mdx | 6 +++--- .../StudioTableRemotePagination.test.tsx | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx index 65c1ebcba5d..0ea49497a9c 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.mdx @@ -63,14 +63,14 @@ const rows = [ icon: , name: 'Coordinated register notification', creator: 'Brønnøysund Register Centre', - lastChanged: new Date('2023-04-12').toLocaleDateString(), + lastChanged: '12-04-2023', }, { id: 2, icon: , name: 'Application for authorisation and license as a healthcare personnel', creator: 'The Norwegian Directorate of Health', - lastChanged: new Date('2023-04-05').toLocaleDateString(), + lastChanged: '05-04-2023', }, ]; ``` diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index 0d47ffd62be..3e226139344 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -135,6 +135,6 @@ describe('StudioTableLocalPagination', () => { emptyTableMessage='No rows to display' />, ); - expect(screen.getByRole('paragraph', { value: 'No rows to display' })).toBeInTheDocument(); + expect(screen.getByText('No rows to display')).toBeInTheDocument(); }); }); diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx index 23893fd4844..f515200afc7 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.tsx @@ -38,15 +38,15 @@ export const StudioTableLocalPagination = forwardRef< useEffect(() => { const newRowsToRender = getRowsToRender(currentPage, pageSize, sortedRows || rows); - const outOfRange = !newRowsToRender.length && currentPage > 1; - if (outOfRange) { + const isOutOfRange = !newRowsToRender.length && currentPage > 1; + if (isOutOfRange) { setCurrentPage(1); setRowsToRender(getRowsToRender(1, pageSize, sortedRows || rows)); return; } setRowsToRender(newRowsToRender); - }, [sortedRows, currentPage, pageSize]); + }, [sortedRows, rows, currentPage, pageSize]); const totalPages = Math.ceil(rows.length / pageSize); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx index 488ca8df562..93b406caa6b 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.mdx @@ -12,7 +12,7 @@ import { propInfoColumns, propInfoRowsRemotePagination } from './mockData'; StudioTableRemotePagination brings together Digdir Designsystemet's `Table` and `Pagination` components. This component is useful when data is retrieved in chunks, and the pagination logic is - managed externally.{' '} + managed externally. @@ -61,14 +61,14 @@ const rows = [ icon: , name: 'Coordinated register notification', creator: 'Brønnøysund Register Centre', - lastChanged: new Date('2023-04-12').toLocaleDateString(), + lastChanged: '12-04-2023', }, { id: 2, icon: , name: 'Application for authorisation and license as a healthcare personnel', creator: 'The Norwegian Directorate of Health', - lastChanged: new Date('2023-04-05').toLocaleDateString(), + lastChanged: '05-04-2023', }, ]; ``` diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index faf6e9c263f..deb790a7af9 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -96,6 +96,6 @@ describe('StudioTableRemotePagination', () => { emptyTableMessage='No rows to display' />, ); - expect(screen.getByRole('paragraph', { value: 'No rows to display' })).toBeInTheDocument(); + expect(screen.getByText('No rows to display')).toBeInTheDocument(); }); }); From dc67f05791122febd083eae6927cb703e6e45c31 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 14 May 2024 12:04:17 +0200 Subject: [PATCH 71/72] Fix PR comments --- .../StudioTableLocalPagination.test.tsx | 19 ++++++++++++------- .../StudioTableRemotePagination.test.tsx | 12 ++++++++---- .../src/hooks/useTableSorting.test.tsx | 6 ++++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx index 3e226139344..fb4e811e188 100644 --- a/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableLocalPagination/StudioTableLocalPagination.test.tsx @@ -35,10 +35,11 @@ describe('StudioTableLocalPagination', () => { it('triggers sorting when a sortable column header is clicked', async () => { render(); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Name' })); - + 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(); @@ -71,8 +72,9 @@ describe('StudioTableLocalPagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); expect( screen.queryByRole('cell', { name: 'Coordinated register notification' }), @@ -87,8 +89,9 @@ describe('StudioTableLocalPagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Page 2' })); + await user.click(screen.getByRole('button', { name: 'Page 2' })); expect( screen.queryByRole('cell', { name: 'Coordinated register notification' }), @@ -103,8 +106,9 @@ describe('StudioTableLocalPagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); + await user.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); const tableBody = screen.getAllByRole('rowgroup')[1]; const tableBodyRows = within(tableBody).getAllByRole('row'); @@ -115,13 +119,14 @@ describe('StudioTableLocalPagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Page 4' })); + 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 userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '50'); + 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); diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx index deb790a7af9..2b1ed44ce3f 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/StudioTableRemotePagination.test.tsx @@ -36,8 +36,9 @@ describe('StudioTableRemotePagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Name' })); + await user.click(screen.getByRole('button', { name: 'Name' })); expect(handleSorting).toHaveBeenCalledWith('name'); }); @@ -62,8 +63,9 @@ describe('StudioTableRemotePagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await user.click(screen.getByRole('button', { name: 'Next' })); expect(paginationProps.onPageChange).toHaveBeenCalledWith(2); }); @@ -72,8 +74,9 @@ describe('StudioTableRemotePagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.click(screen.getByRole('button', { name: 'Page 2' })); + await user.click(screen.getByRole('button', { name: 'Page 2' })); expect(paginationProps.onPageChange).toHaveBeenCalledWith(2); }); @@ -82,8 +85,9 @@ describe('StudioTableRemotePagination', () => { render( , ); + const user = userEvent.setup(); - await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); + await user.selectOptions(screen.getByRole('combobox', { name: 'Rows per page' }), '10'); expect(paginationProps.onPageSizeChange).toHaveBeenCalledWith(10); }); diff --git a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx index 69c71e63e2d..d43c5534cfe 100644 --- a/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx +++ b/frontend/libs/studio-components/src/hooks/useTableSorting.test.tsx @@ -61,4 +61,10 @@ describe('useTableSorting', () => { await waitFor(() => result.current.handleSorting('id')); expect(result.current.sortedRows).toEqual(rows); }); + + it("should make 'sortedRows' and 'handleSorting' undefined when enable is false", () => { + const { result } = renderHook(() => useTableSorting(rows, { enable: false })); + expect(result.current.sortedRows).toBeUndefined(); + expect(result.current.handleSorting).toBeUndefined(); + }); }); From dd8a207ac22f74b974cdc5eb80bada6465b5ef71 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 14 May 2024 12:19:31 +0200 Subject: [PATCH 72/72] Add 'rowsToRender' setting to .eslintrc.js --- frontend/libs/studio-components/.eslintrc.js | 4 +++- .../StudioTableRemotePagination/utils.test.tsx | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/libs/studio-components/.eslintrc.js b/frontend/libs/studio-components/.eslintrc.js index ba3eb447227..95317e6a918 100644 --- a/frontend/libs/studio-components/.eslintrc.js +++ b/frontend/libs/studio-components/.eslintrc.js @@ -34,6 +34,8 @@ module.exports = { }, }, ], - extends: ['plugin:storybook/recommended'], + settings: { + 'testing-library/custom-renders': ['rowsToRender'], + }, }; diff --git a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx index 730c9e196f2..3bd2712999e 100644 --- a/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTableRemotePagination/utils.test.tsx @@ -12,7 +12,6 @@ describe('getRowsToRender', () => { it('should return all rows when pageSize is 0', () => { const currentPage = 1; const pageSize = 0; - // eslint-disable-next-line testing-library/render-result-naming-convention const rowsToRender = getRowsToRender(currentPage, pageSize, rows); expect(rowsToRender).toEqual(rows); }); @@ -20,8 +19,8 @@ describe('getRowsToRender', () => { it('should return the correct rows for the first page', () => { const currentPage = 1; const pageSize = 2; - // eslint-disable-next-line testing-library/render-result-naming-convention const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([ { id: 1, name: 'Row 1' }, { id: 2, name: 'Row 2' }, @@ -31,24 +30,24 @@ describe('getRowsToRender', () => { it('should return the correct rows for the last page', () => { const currentPage = 3; const pageSize = 2; - // eslint-disable-next-line testing-library/render-result-naming-convention const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([{ id: 5, name: 'Row 5' }]); }); it('should return an empty array when currentPage is out of range', () => { const currentPage = 4; const pageSize = 2; - // eslint-disable-next-line testing-library/render-result-naming-convention const rowsToRender = getRowsToRender(currentPage, pageSize, rows); + expect(rowsToRender).toEqual([]); }); it('should return an empty array when rows is an empty array', () => { const currentPage = 1; const pageSize = 2; - // eslint-disable-next-line testing-library/render-result-naming-convention const rowsToRender = getRowsToRender(currentPage, pageSize, []); + expect(rowsToRender).toEqual([]); }); });