(app-desk) integrate member list design

Integrate the member list design in the team page
based on the mockup.
This commit is contained in:
Anthony LC
2024-02-29 15:59:10 +01:00
committed by Anthony LC
parent 9d30bc88f1
commit e16f51ca20
16 changed files with 334 additions and 68 deletions

View File

@@ -60,6 +60,9 @@
},
{
"username": "user-e2e-chromium",
"email": "user@chromium.e2e",
"firstName": "E2E",
"lastName": "Chromium",
"enabled": true,
"credentials": [
{
@@ -71,6 +74,9 @@
},
{
"username": "user-e2e-webkit",
"email": "user@webkit.e2e",
"firstName": "E2E",
"lastName": "Webkit",
"enabled": true,
"credentials": [
{
@@ -82,6 +88,9 @@
},
{
"username": "user-e2e-firefox",
"email": "user@firefox.e2e",
"firstName": "E2E",
"lastName": "Firefox",
"enabled": true,
"credentials": [
{

View File

@@ -78,6 +78,7 @@ const config = {
'forms-labelledbox': {
'label-color': {
small: 'var(--c--theme--colors--primary-500)',
'small-disabled': 'var(--c--theme--colors--greyscale-400)',
big: {
disabled: 'var(--c--theme--colors--greyscale-400)',
},
@@ -85,6 +86,8 @@ const config = {
},
'forms-select': {
'border-color': 'var(--c--theme--colors--primary-500)',
'border-color-disabled-hover':
'var(--c--theme--colors--greyscale-200)',
'border-radius': {
hover: 'var(--c--components--forms-select--border-radius)',
focus: 'var(--c--components--forms-select--border-radius)',
@@ -199,10 +202,10 @@ const config = {
'secondary-800': '#341f1f',
'secondary-900': '#2b1919',
'greyscale-text': '#303C4B',
'greyscale-000': '#cecece',
'greyscale-100': '#f6f6f6',
'greyscale-200': '#eeeeee',
'greyscale-300': '#e5e5e5',
'greyscale-000': '#f6f6f6',
'greyscale-100': '#eeeeee',
'greyscale-200': '#e5e5e5',
'greyscale-300': '#e1e1e1',
'greyscale-400': '#dddddd',
'greyscale-500': '#cecece',
'greyscale-600': '#7b7b7b',
@@ -284,6 +287,20 @@ const config = {
color: 'var(--c--theme--colors--primary-text)',
},
},
datagrid: {
header: {
color: 'var(--c--theme--colors--primary-text)',
size: 'var(--c--theme--font--sizes--s)',
},
body: {
'background-color': 'transparent',
'background-color-hover': '#F4F4FD',
},
pagination: {
'background-color': 'transparent',
'background-color-active': 'var(--c--theme--colors--primary-300)',
},
},
'forms-checkbox': {
'border-radius': '0',
},
@@ -305,7 +322,12 @@ const config = {
},
},
'forms-select': {
'border-radius': '0',
'border-radius': '4px',
'border-radius-hover': '4px',
'background-color': '#ffffff',
'border-color': 'var(--c--theme--colors--primary-text)',
'border-color-hover': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-switch': {
'handle-border-radius': '2px',

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20 0C8.96 0 0 8.96 0 20C0 31.04 8.96 40 20 40C31.04 40 40 31.04 40 20C40 8.96 31.04 0 20 0ZM20 6C23.32 6 26 8.68 26 12C26 15.32 23.32 18 20 18C16.68 18 14 15.32 14 12C14 8.68 16.68 6 20 6ZM20 34.4C15 34.4 10.58 31.84 8 27.96C8.06 23.98 16 21.8 20 21.8C23.98 21.8 31.94 23.98 32 27.96C29.42 31.84 25 34.4 20 34.4Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@@ -14,6 +14,7 @@ export interface BoxProps {
$gap?: CSSProperties['gap'];
$height?: CSSProperties['height'];
$justify?: CSSProperties['justifyContent'];
$overflow?: CSSProperties['overflow'];
$position?: CSSProperties['position'];
$radius?: CSSProperties['borderRadius'];
$width?: CSSProperties['width'];
@@ -35,6 +36,7 @@ export const Box = styled('div')<BoxProps>`
${({ $gap }) => $gap && `gap: ${$gap};`}
${({ $height }) => $height && `height: ${$height};`}
${({ $justify }) => $justify && `justify-content: ${$justify};`}
${({ $overflow }) => $overflow && `overflow: ${$overflow};`}
${({ $position }) => $position && `position: ${$position};`}
${({ $radius }) => $radius && `border-radius: ${$radius};`}
${({ $width }) => $width && `width: ${$width};`}

View File

@@ -1,4 +1,5 @@
export * from './Box';
export * from './Text';
export * from './Link';
export * from './TextErrors';
export * from './Card';

View File

@@ -21,7 +21,7 @@
}
.labelled-box--disabled label {
color: var(--c--components--forms-labelledbox--label-color--small--disabled);
color: var(--c--components--forms-labelledbox--label-color--small-disabled);
}
.c__field :not(.c__textarea__wrapper, div) .labelled-box label.placeholder {
@@ -120,7 +120,16 @@ input:-webkit-autofill:focus {
}
.c__select:not(.c__select--disabled) .c__select__wrapper:hover {
box-shadow: var(--c--theme--colors--primary-500) 0 0 0 2px;
box-shadow: var(--c--components--forms-input--box-shadow-color) 0 0 0 2px;
}
.c__select__wrapper:hover {
border-radius: var(--c--components--forms-select--border-radius-hover);
border-color: var(--c--components--forms-select--border-color-hover);
}
.c__select--disabled .c__select__wrapper:hover {
border-color: var(--c--components--forms-select--border-color-disabled-hover);
}
.c__select__menu__item {
@@ -135,7 +144,7 @@ input:-webkit-autofill:focus {
}
.c__select__wrapper:focus-within .labelled-box--disabled label {
color: var(--c--components--forms-labelledbox--label-color--small--disabled);
color: var(--c--components--forms-labelledbox--label-color--small-disabled);
}
.c__select__wrapper .labelled-box {
@@ -173,28 +182,60 @@ input:-webkit-autofill:focus {
/**
* DataGrid
*/
.c__datagrid > table td {
max-width: 10rem;
white-space: normal;
color: var(--c--components--datagrid--cell--color);
font-size: var(--c--components--datagrid--cell--size);
.c__datagrid__table__container {
overflow: auto;
}
.c__datagrid > table th .c__datagrid__header {
color: var(--c--theme--colors--primary-500);
.c__datagrid__table__container > table th .c__datagrid__header {
color: var(--c--components--datagrid--header--color);
font-weight: var(--c--components--datagrid--header--weight);
font-size: var(--c--components--datagrid--header--size);
padding-block: 2rem;
}
.c__datagrid > table tbody tr {
border: 1px var(--c--theme--colors--primary-100) solid;
.c__datagrid__table__container > table tbody tr {
border: none;
border-top: 1px var(--c--theme--colors--greyscale-300) solid;
border-bottom: 1px var(--c--theme--colors--greyscale-300) solid;
}
.c__datagrid__table__container > table tbody {
background-color: var(--c--components--datagrid--body--background-color);
}
.c__datagrid__table__container > table tbody tr:hover {
background-color: var(
--c--components--datagrid--body--background-color-hover
);
}
.c__datagrid__table__container > table th:first-child,
.c__datagrid__table__container > table td:first-child {
padding-left: 2rem;
}
.c__datagrid > .c__pagination {
padding-top: 1rem;
padding-right: 1rem;
justify-content: flex-end;
}
.c__pagination__list {
gap: 3px;
border-radius: 4px;
background: var(--c--components--datagrid--pagination--background-color);
}
.c__pagination__list .c__button--tertiary.c__button--active {
background-color: var(
--c--components--datagrid--pagination--background-color-active
);
color: var(--c--theme--colors--greyscale-800);
}
.c__pagination__list .c__button--tertiary:disabled {
display: none;
}
@media (width <= 380px) {
.c__datagrid > .c__pagination {
flex-direction: column;

View File

@@ -160,12 +160,18 @@
--c--components--forms-labelledbox--label-color--small: var(
--c--theme--colors--primary-500
);
--c--components--forms-labelledbox--label-color--small-disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-labelledbox--label-color--big--disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-select--border-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-select--border-color-disabled-hover: var(
--c--theme--colors--greyscale-200
);
--c--components--forms-select--border-radius--hover: var(
--c--components--forms-select--border-radius
);
@@ -337,10 +343,10 @@
--c--theme--colors--secondary-800: #341f1f;
--c--theme--colors--secondary-900: #2b1919;
--c--theme--colors--greyscale-text: #303c4b;
--c--theme--colors--greyscale-000: #cecece;
--c--theme--colors--greyscale-100: #f6f6f6;
--c--theme--colors--greyscale-200: #eee;
--c--theme--colors--greyscale-300: #e5e5e5;
--c--theme--colors--greyscale-000: #f6f6f6;
--c--theme--colors--greyscale-100: #eee;
--c--theme--colors--greyscale-200: #e5e5e5;
--c--theme--colors--greyscale-300: #e1e1e1;
--c--theme--colors--greyscale-400: #ddd;
--c--theme--colors--greyscale-500: #cecece;
--c--theme--colors--greyscale-600: #7b7b7b;
@@ -415,6 +421,16 @@
--c--components--button--secondary--color: var(
--c--theme--colors--primary-text
);
--c--components--datagrid--header--color: var(
--c--theme--colors--primary-text
);
--c--components--datagrid--header--size: var(--c--theme--font--sizes--s);
--c--components--datagrid--body--background-color: transparent;
--c--components--datagrid--body--background-color-hover: #f4f4fd;
--c--components--datagrid--pagination--background-color: transparent;
--c--components--datagrid--pagination--background-color-active: var(
--c--theme--colors--primary-300
);
--c--components--forms-checkbox--border-radius: 0;
--c--components--forms-datepicker--border-radius: 0;
--c--components--forms-fileuploader--border-radius: 0;
@@ -429,7 +445,18 @@
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--border-radius: 0;
--c--components--forms-select--border-radius: 4px;
--c--components--forms-select--border-radius-hover: 4px;
--c--components--forms-select--background-color: #fff;
--c--components--forms-select--border-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--border-color-hover: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--box-shadow-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-switch--handle-border-radius: 2px;
--c--components--forms-switch--rail-border-radius: 4px;
--c--components--forms-textarea--border-radius: 0;

View File

@@ -183,11 +183,14 @@ export const tokens = {
'forms-labelledbox': {
'label-color': {
small: 'var(--c--theme--colors--primary-500)',
'small-disabled': 'var(--c--theme--colors--greyscale-400)',
big: { disabled: 'var(--c--theme--colors--greyscale-400)' },
},
},
'forms-select': {
'border-color': 'var(--c--theme--colors--primary-500)',
'border-color-disabled-hover':
'var(--c--theme--colors--greyscale-200)',
'border-radius': {
hover: 'var(--c--components--forms-select--border-radius)',
focus: 'var(--c--components--forms-select--border-radius)',
@@ -345,10 +348,10 @@ export const tokens = {
'secondary-800': '#341f1f',
'secondary-900': '#2b1919',
'greyscale-text': '#303C4B',
'greyscale-000': '#cecece',
'greyscale-100': '#f6f6f6',
'greyscale-200': '#eeeeee',
'greyscale-300': '#e5e5e5',
'greyscale-000': '#f6f6f6',
'greyscale-100': '#eeeeee',
'greyscale-200': '#e5e5e5',
'greyscale-300': '#e1e1e1',
'greyscale-400': '#dddddd',
'greyscale-500': '#cecece',
'greyscale-600': '#7b7b7b',
@@ -421,6 +424,20 @@ export const tokens = {
color: 'var(--c--theme--colors--primary-text)',
},
},
datagrid: {
header: {
color: 'var(--c--theme--colors--primary-text)',
size: 'var(--c--theme--font--sizes--s)',
},
body: {
'background-color': 'transparent',
'background-color-hover': '#F4F4FD',
},
pagination: {
'background-color': 'transparent',
'background-color-active': 'var(--c--theme--colors--primary-300)',
},
},
'forms-checkbox': { 'border-radius': '0' },
'forms-datepicker': { 'border-radius': '0' },
'forms-fileuploader': { 'border-radius': '0' },
@@ -433,7 +450,14 @@ export const tokens = {
'forms-labelledbox': {
'label-color': { big: 'var(--c--theme--colors--primary-text)' },
},
'forms-select': { 'border-radius': '0' },
'forms-select': {
'border-radius': '4px',
'border-radius-hover': '4px',
'background-color': '#ffffff',
'border-color': 'var(--c--theme--colors--primary-text)',
'border-color-hover': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-switch': {
'handle-border-radius': '2px',
'rail-border-radius': '4px',

View File

@@ -0,0 +1,89 @@
import { DataGrid, usePagination } from '@openfun/cunningham-react';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import IconUser from '@/assets/icons/icon-user.svg';
import { Box, Card, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Team, useTeamAccesses } from '@/features/teams/api/';
import { PAGE_SIZE } from '@/features/teams/conf';
interface MemberGridProps {
team: Team;
}
export const MemberGrid = ({ team }: MemberGridProps) => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
const pagination = usePagination({
pageSize: PAGE_SIZE,
});
const { page, pageSize, setPagesCount } = pagination;
const { data, isLoading, error } = useTeamAccesses({
teamId: team.id,
page,
});
const accesses = data?.results;
useEffect(() => {
setPagesCount(data?.count ? Math.ceil(data.count / pageSize) : 0);
}, [data?.count, pageSize, setPagesCount]);
return (
<Card
className="m-b pb-s"
$overflow="auto"
$css={`
margin-top:0;
& .c__pagination__goto {
display: none;
}
& table th:first-child,
& table td:first-child {
padding-right: 0;
width: 0;
}
`}
aria-label={t('List members card')}
>
{error && <TextErrors causes={error.cause} />}
<DataGrid
columns={[
{
id: 'icon-user',
renderCell() {
return (
<Box $direction="row" $align="center">
<IconUser
aria-label={t('Member icon')}
width={20}
height={20}
color={colorsTokens()['primary-600']}
/>
</Box>
);
},
},
{
headerName: t('Names'),
field: 'user.name',
},
{
field: 'user.email',
headerName: t('Emails'),
},
{
field: 'role',
headerName: t('Roles'),
},
]}
rows={accesses || []}
isLoading={isLoading}
pagination={pagination}
/>
</Card>
);
};

View File

@@ -1,2 +1,3 @@
export * from './Panel/Panel';
export * from './TeamInfo';
export * from './Member/MemberGrid';

View File

@@ -19,6 +19,15 @@
"{{label}} button": "Bouton {{label}}",
"{{label}} icon": "Icône {{label}}",
"Recents": "Récents",
"Admin": "Admin",
"Member": "Membre",
"Owner": "Propriétaire",
"Select a role": "Choisir un rôle",
"List members card": "Carte liste des membres",
"Member icon": "Icône de membre",
"Names": "Noms",
"Emails": "Emails",
"Roles": "Rôles",
"Sort the teams": "Trier les groupes",
"Sort teams icon": "Icône trier les groupes",
"Add a team": "Ajouter un groupe",

View File

@@ -5,7 +5,7 @@ import { ReactElement } from 'react';
import { Box } from '@/components';
import { TextErrors } from '@/components/TextErrors';
import { TeamInfo, useTeam } from '@/features/teams/';
import { MemberGrid, TeamInfo, useTeam } from '@/features/teams/';
import { NextPageWithLayout } from '@/types/next';
import TeamLayout from './TeamLayout';
@@ -47,7 +47,12 @@ const Team = ({ id }: TeamProps) => {
);
}
return <TeamInfo team={team} />;
return (
<>
<TeamInfo team={team} />
<MemberGrid team={team} />
</>
);
};
Page.getLayout = function getLayout(page: ReactElement) {

View File

@@ -1,4 +1,4 @@
import { Page } from '@playwright/test';
import { Page, expect } from '@playwright/test';
export const keyCloakSignIn = async (page: Page, browserName: string) => {
const title = await page.locator('h1').first().textContent({
@@ -17,3 +17,29 @@ export const keyCloakSignIn = async (page: Page, browserName: string) => {
await page.click('input[type="submit"]', { force: true });
}
};
export const createTeam = async (
page: Page,
teamName: string,
browserName: string,
length: number,
) => {
const panel = page.getByLabel('Teams panel').first();
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
const randomTeams = Array.from({ length }, (_el, index) => {
return `${teamName}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`;
});
for (let i = 0; i < randomTeams.length; i++) {
await panel.getByRole('button', { name: 'Add a team' }).click();
await page.getByText('Team name').fill(randomTeams[i]);
await expect(buttonCreate).toBeEnabled();
await buttonCreate.click();
await expect(
panel.locator('li').nth(0).getByText(randomTeams[i]),
).toBeVisible();
}
return randomTeams;
};

View File

@@ -56,7 +56,8 @@ test.describe('Menu', () => {
const buttonMenu = menu.getByLabel(`${name} button`);
await buttonMenu.click();
// eslint-disable-next-line playwright/no-conditional-in-test
/* eslint-disable playwright/no-conditional-expect */
/* eslint-disable playwright/no-conditional-in-test */
if (isDefault) {
await expect(
page.getByRole('button', {
@@ -73,6 +74,8 @@ test.describe('Menu', () => {
const reg = new RegExp(name.toLowerCase());
await expect(page).toHaveURL(reg);
}
/* eslint-enable playwright/no-conditional-expect */
/* eslint-enable playwright/no-conditional-in-test */
});
}
});

View File

@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
import { keyCloakSignIn } from './common';
import { createTeam, keyCloakSignIn } from './common';
test.beforeEach(async ({ page, browserName }) => {
await page.goto('/');
@@ -12,13 +12,9 @@ test.describe('Team', () => {
page,
browserName,
}) => {
const panel = page.getByLabel('Teams panel').first();
await panel.getByRole('button', { name: 'Add a team' }).click();
const teamName = `My new team ${browserName}-${Math.floor(Math.random() * 1000)}`;
await page.getByText('Team name').fill(teamName);
await page.getByRole('button', { name: 'Create the team' }).click();
const teamName = (
await createTeam(page, 'team-top-box', browserName, 1)
).shift();
await expect(page.getByLabel('icon group')).toBeVisible();
await expect(
@@ -44,4 +40,35 @@ test.describe('Team', () => {
page.getByText(`Last update at ${todayFormated}`),
).toBeVisible();
});
test('checks the datagrid members', async ({ page, browserName }) => {
await createTeam(page, 'team-admin', browserName, 1);
const table = page.getByLabel('List members card').getByRole('table');
const thead = table.locator('thead');
await expect(thead.getByText(/Names/i)).toBeVisible();
await expect(thead.getByText(/Emails/i)).toBeVisible();
await expect(thead.getByText(/Roles/i)).toBeVisible();
const rows = table.getByRole('row');
expect(await rows.count()).toBe(21);
await expect(
rows.nth(1).getByRole('cell').nth(0).getByLabel('Member icon'),
).toBeVisible();
const textCellName = await rows
.nth(1)
.getByRole('cell')
.nth(1)
.textContent();
expect(textCellName).toEqual(expect.any(String));
await expect(rows.nth(1).getByRole('cell').nth(2)).toContainText('@');
expect(
['owner', 'member', 'admin'].includes(
(await rows.nth(1).getByRole('cell').nth(3).textContent()) as string,
),
).toBeTruthy();
});
});

View File

@@ -1,34 +1,8 @@
import { Page, expect, test } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { waitForElementCount } from '../helpers';
import { keyCloakSignIn } from './common';
const createTeam = async (
page: Page,
teamName: string,
browserName: string,
length: number,
) => {
const panel = page.getByLabel('Teams panel').first();
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
const randomTeams = Array.from({ length }, (_el, index) => {
return `${teamName}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`;
});
for (let i = 0; i < randomTeams.length; i++) {
await panel.getByRole('button', { name: 'Add a team' }).click();
await page.getByText('Team name').fill(randomTeams[i]);
await expect(buttonCreate).toBeEnabled();
await buttonCreate.click();
await expect(
panel.locator('li').nth(0).getByText(randomTeams[i]),
).toBeVisible();
}
return randomTeams;
};
import { createTeam, keyCloakSignIn } from './common';
test.beforeEach(async ({ page, browserName }) => {
await page.goto('/');