💄(app-desk) integrate the create team design

- Integrate the create team design based from the
mockup
- Manage the different states of the create team
This commit is contained in:
Anthony LC
2024-02-15 10:31:41 +01:00
committed by Anthony LC
parent f818715a45
commit 4566e132e1
19 changed files with 288 additions and 72 deletions

View File

@@ -259,7 +259,7 @@ const config = {
'border-radius': '0',
},
button: {
'border-radius': '2px',
'border-radius': '4px',
primary: {
background: {
color: 'var(--c--theme--colors--primary-text)',
@@ -270,29 +270,47 @@ const config = {
'color-hover': '#ffffff',
'color-active': '#ffffff',
},
secondary: {
background: {
'color-hover': 'var(--c--theme--colors--primary-100)',
'color-active': 'var(--c--theme--colors--primary-200)',
},
border: {
'color-hover': 'var(--c--theme--colors--primary-300)',
},
color: 'var(--c--theme--colors--primary-text)',
},
},
'forms-checkbox': {
'border-radius': '0',
},
'forms-datepicker': {
'border-radius': '0',
},
'forms-fileuploader': {
'border-radius': '0',
},
'forms-input': {
'border-radius': '4px',
'background-color': '#ffffff',
'border-color': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-labelledbox': {
'label-color': {
big: 'var(--c--theme--colors--primary-text)',
},
},
'forms-select': {
'border-radius': '0',
},
'forms-switch': {
'handle-border-radius': '2px',
'rail-border-radius': '4px',
},
'forms-input': {
'border-radius': '0',
},
'forms-select': {
'border-radius': '0',
},
'forms-datepicker': {
'border-radius': '0',
},
'forms-textarea': {
'border-radius': '0',
},
'forms-fileuploader': {
'border-radius': '0',
},
},
},
},

View File

@@ -9,8 +9,6 @@ describe('Page', () => {
it('checks Page rendering', () => {
render(<Page />, { wrapper: AppWrapper });
expect(screen.getByRole('status')).toBeInTheDocument();
expect(
screen.getByRole('button', {
name: /Create a new team/i,

View File

@@ -0,0 +1,29 @@
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_179_19260)">
<path
d="M12.6406 26.02C14.5606 26.06 16.3406 27.02 17.5406 28.7C19.0006 30.76 21.4206 32 24.0006 32C26.5806 32 29.0006 30.76 30.4606 28.68C31.6606 27 33.4406 26.04 35.3606 26C33.9206 23.56 28.1606 22 24.0006 22C19.8606 22 14.0806 23.56 12.6406 26.02Z"
fill="currentColor"
/>
<path
d="M8 26C11.32 26 14 23.32 14 20C14 16.68 11.32 14 8 14C4.68 14 2 16.68 2 20C2 23.32 4.68 26 8 26Z"
fill="currentColor"
/>
<path
d="M40 26C43.32 26 46 23.32 46 20C46 16.68 43.32 14 40 14C36.68 14 34 16.68 34 20C34 23.32 36.68 26 40 26Z"
fill="currentColor"
/>
<path
d="M24 20C27.32 20 30 17.32 30 14C30 10.68 27.32 8 24 8C20.68 8 18 10.68 18 14C18 17.32 20.68 20 24 20Z"
fill="currentColor"
/>
<path
d="M42 28H35.46C33.92 28 32.76 28.9 32.1 29.84C32.02 29.96 29.38 34 24 34C21.14 34 17.94 32.72 15.9 29.84C15.12 28.74 13.9 28 12.54 28H6C3.8 28 2 29.8 2 32V40H16V35.48C18.3 37.08 21.08 38 24 38C26.92 38 29.7 37.08 32 35.48V40H46V32C46 29.8 44.2 28 42 28Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_179_19260">
<rect width="48" height="48" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -17,6 +17,8 @@ export interface BoxProps {
$position?: CSSProperties['position'];
$radius?: CSSProperties['borderRadius'];
$width?: CSSProperties['width'];
$maxWidth?: CSSProperties['maxWidth'];
$minWidth?: CSSProperties['minWidth'];
}
export type BoxType = ComponentPropsWithRef<typeof Box>;
@@ -36,5 +38,7 @@ export const Box = styled('div')<BoxProps>`
${({ $position }) => $position && `position: ${$position};`}
${({ $radius }) => $radius && `border-radius: ${$radius};`}
${({ $width }) => $width && `width: ${$width};`}
${({ $maxWidth }) => $maxWidth && `max-width: ${$maxWidth};`}
${({ $minWidth }) => $minWidth && `min-width: ${$minWidth};`}
${({ $css }) => $css && `${$css};`}
`;

View File

@@ -0,0 +1,28 @@
import { PropsWithChildren } from 'react';
import { useCunninghamTheme } from '@/cunningham';
import { Box, BoxType } from '.';
export const Card = ({
children,
$css,
...props
}: PropsWithChildren<BoxType>) => {
const { colorsTokens } = useCunninghamTheme();
return (
<Box
$background="white"
$radius="4px"
$css={`
box-shadow: 2px 2px 5px ${colorsTokens()['primary-300']}88;
border: 1px solid #e3e3e3;
${$css}
`}
{...props}
>
{children}
</Box>
);
};

View File

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

View File

@@ -25,7 +25,4 @@
--c--components--forms-select--value-color--disabled: var(
--c--theme--colors--greyscale-400
);
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--primary-500
);
}

View File

@@ -17,7 +17,7 @@
}
.labelled-box label {
color: var(--c--theme--colors--primary-500);
color: var(--c--theme--colors--primary-text);
}
.labelled-box--disabled label {
@@ -53,7 +53,7 @@
.c__input__wrapper:hover,
.c__textarea__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__textarea__wrapper--disabled:hover,
@@ -325,6 +325,7 @@ input:-webkit-autofill:focus {
--c--components--button--secondary--background--color-hover
);
color: var(--c--components--button--secondary--color-hover);
border: 1px solid var(--c--components--button--secondary--border--color-hover);
}
.c__button--tertiary {

View File

@@ -387,7 +387,7 @@
--c--theme--font--families--accent: marianne;
--c--theme--font--families--base: marianne;
--c--components--alert--border-radius: 0;
--c--components--button--border-radius: 2px;
--c--components--button--border-radius: 4px;
--c--components--button--primary--background--color: var(
--c--theme--colors--primary-text
);
@@ -400,14 +400,36 @@
--c--components--button--primary--color: #fff;
--c--components--button--primary--color-hover: #fff;
--c--components--button--primary--color-active: #fff;
--c--components--button--secondary--background--color-hover: var(
--c--theme--colors--primary-100
);
--c--components--button--secondary--background--color-active: var(
--c--theme--colors--primary-200
);
--c--components--button--secondary--border--color-hover: var(
--c--theme--colors--primary-300
);
--c--components--button--secondary--color: var(
--c--theme--colors--primary-text
);
--c--components--forms-checkbox--border-radius: 0;
--c--components--forms-datepicker--border-radius: 0;
--c--components--forms-fileuploader--border-radius: 0;
--c--components--forms-input--border-radius: 4px;
--c--components--forms-input--background-color: #fff;
--c--components--forms-input--border-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-input--box-shadow-color: var(
--c--theme--colors--primary-text
);
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--primary-text
);
--c--components--forms-select--border-radius: 0;
--c--components--forms-switch--handle-border-radius: 2px;
--c--components--forms-switch--rail-border-radius: 4px;
--c--components--forms-input--border-radius: 0;
--c--components--forms-select--border-radius: 0;
--c--components--forms-datepicker--border-radius: 0;
--c--components--forms-textarea--border-radius: 0;
--c--components--forms-fileuploader--border-radius: 0;
}
.clr-secondary-text {

View File

@@ -398,7 +398,7 @@ export const tokens = {
components: {
alert: { 'border-radius': '0' },
button: {
'border-radius': '2px',
'border-radius': '4px',
primary: {
background: {
color: 'var(--c--theme--colors--primary-text)',
@@ -409,17 +409,33 @@ export const tokens = {
'color-hover': '#ffffff',
'color-active': '#ffffff',
},
secondary: {
background: {
'color-hover': 'var(--c--theme--colors--primary-100)',
'color-active': 'var(--c--theme--colors--primary-200)',
},
border: { 'color-hover': 'var(--c--theme--colors--primary-300)' },
color: 'var(--c--theme--colors--primary-text)',
},
},
'forms-checkbox': { 'border-radius': '0' },
'forms-datepicker': { 'border-radius': '0' },
'forms-fileuploader': { 'border-radius': '0' },
'forms-input': {
'border-radius': '4px',
'background-color': '#ffffff',
'border-color': 'var(--c--theme--colors--primary-text)',
'box-shadow-color': 'var(--c--theme--colors--primary-text)',
},
'forms-labelledbox': {
'label-color': { big: 'var(--c--theme--colors--primary-text)' },
},
'forms-select': { 'border-radius': '0' },
'forms-switch': {
'handle-border-radius': '2px',
'rail-border-radius': '4px',
},
'forms-input': { 'border-radius': '0' },
'forms-select': { 'border-radius': '0' },
'forms-datepicker': { 'border-radius': '0' },
'forms-textarea': { 'border-radius': '0' },
'forms-fileuploader': { 'border-radius': '0' },
},
},
},

View File

@@ -13,7 +13,9 @@ export const Panel = () => {
return (
<Box
$width="28rem"
$width="100%"
$maxWidth="20rem"
$minWidth="14rem"
$css={`
border-right: 1px solid ${colorsTokens()['primary-300']};
`}

View File

@@ -18,9 +18,10 @@ export const PanelTeam = ({ team }: TeamProps) => {
const commonProps = {
className: 'p-t',
width: 36,
width: 52,
style: {
borderRadius: '10px',
flexShrink: 0,
},
};

View File

@@ -1,8 +1,6 @@
{
"fr": {
"translation": {
"Create a new team": "Créer un nouveau groupe",
"Team name": "Nom du groupe",
"Create a team": "Créer un groupe",
"Marianne Logo": "Logo Marianne",
"Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité",
@@ -33,27 +31,21 @@
"Something bad happens, please refresh the page": "Une erreur inattendue s'est produite, rechargez la page.",
"0 group to display": "0 groupe à afficher.",
"Create your first team by clicking on the \"Create a new team\" button": "Créez votre premier groupe en cliquant sur le bouton \"Créer un nouveau groupe\".",
"Create new team card": "Carte créer une nouvelle équipe",
"Something bad happens, please retry.": "Une erreur inattendue s'est produite, rechargez la page.",
"0 group to display.": "0 groupe à afficher.",
"Create your first team by clicking on the \"Create a new team\" button.": "Créez votre premier groupe en cliquant sur le bouton \"Créer un nouveau groupe\".",
"Something bad happens, please refresh the page.": "Une erreur inattendue s'est produite, rechargez la page.",
"icon group": "icône groupe",
"Members of “{{teamName}}“": "Membres de “{{teamName}}“",
"Add people to the “{{teamName}}“ group.": "Ajouter des personnes au groupe “{{teamName}}“.",
"{{count}} member_one": "{{count}} membre",
"{{count}} member_many": "{{count}} membres",
"{{count}} member_other": "{{count}} membres",
"Created at {{created_at}}": "Créé le {{created_at}}",
"Last update at {{updated_at}}": "Dernière modification le {{updated_at}}",
"People": "People",
"People Description": "Description de People",
"404 - Page not found": "404 - Page introuvable",
"Something bad happens, please retry": "Une erreur inattendue s'est produite, rechargez la page.",
"Panel create new team": "Panneau de création d'un nouveau groupe",
"icon group": "icône groupe",
"Name the team": "Nommer le groupe",
"Team name": "Nom du groupe",
"Cancel": "Annuler",
"Create the team": "Créer le groupe",
"Something bad happens, please retry": "Une erreur inattendue s'est produite, rechargez la page.",
"Add people to the “{{teamName}}“ group": "Ajouter des personnes au groupe «{{teamName}}»."
"Create a new team": "Créer un nouveau groupe"
}
}
}

View File

@@ -5,6 +5,10 @@ body {
padding: 0;
}
* {
box-sizing: border-box;
}
::-webkit-scrollbar {
width: 20px;
}

View File

@@ -3,7 +3,7 @@ import type { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { StyledLink } from '@/components';
import { Box, StyledLink } from '@/components';
import { NextPageWithLayout } from '@/types/next';
import TeamLayout from './teams/TeamLayout';
@@ -16,9 +16,11 @@ const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
return (
<StyledLink href="/teams/create">
<StyledButton>{t('Create a new team')}</StyledButton>
</StyledLink>
<Box $align="center" $justify="center" $height="inherit">
<StyledLink href="/teams/create">
<StyledButton>{t('Create a new team')}</StyledButton>
</StyledLink>
</Box>
);
};

View File

@@ -15,11 +15,8 @@ export default function TeamLayout({ children }: PropsWithChildren) {
<Panel />
<Box
$background={colorsTokens()['primary-bg']}
$justify="center"
$align="center"
$width="100%"
$gap="5rem"
$css="overflow:auto;"
$height="inherit"
>
{children}
</Box>

View File

@@ -1,7 +1,10 @@
import { Button, Field, Input } from '@openfun/cunningham-react';
import { Button, Input, Loader } from '@openfun/cunningham-react';
import { ReactElement, useState } from 'react';
import { useTranslation } from 'react-i18next';
import IconGroup from '@/assets/icons/icon-group2.svg';
import { Box, Card, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useCreateTeam } from '@/features/teams';
import { NextPageWithLayout } from '@/types/next';
@@ -9,20 +12,60 @@ import TeamLayout from './TeamLayout';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { mutate: createTeam } = useCreateTeam();
const { mutate: createTeam, isError, isPending } = useCreateTeam();
const [teamName, setTeamName] = useState('');
const { colorsTokens } = useCunninghamTheme();
return (
<Field>
<Input
type="text"
label={t('Team name')}
onChange={(e) => setTeamName(e.target.value)}
/>
<Button fullWidth onClick={() => createTeam(teamName)} className="mt-s">
{t('Create a team')}
</Button>
</Field>
<Box className="p-l" $justify="center" $align="start" $height="inherit">
<Card
className="p-b"
$height="70%"
$justify="space-between"
$width="100%"
$maxWidth="24rem"
$minWidth="22rem"
aria-label={t('Create new team card')}
>
<Box $gap="1rem">
<Box $align="center">
<IconGroup
width={44}
color={colorsTokens()['primary-text']}
aria-label={t('icon group')}
/>
<Text as="h3" $textAlign="center">
{t('Name the team')}
</Text>
</Box>
<Input
fullWidth
type="text"
label={t('Team name')}
onChange={(e) => setTeamName(e.target.value)}
rightIcon={<span className="material-icons">edit</span>}
/>
{isError && (
<Text className="mt-s" $theme="danger" $textAlign="center">
{t('Something bad happens, please retry.')}
</Text>
)}
{isPending && (
<Box $align="center">
<Loader />
</Box>
)}
</Box>
<Box $justify="space-between" $direction="row" $align="center">
<StyledLink href="/">
<Button color="secondary">{t('Cancel')}</Button>
</StyledLink>
<Button onClick={() => createTeam(teamName)} disabled={!teamName}>
{t('Create the team')}
</Button>
</Box>
</Card>
</Box>
);
};

View File

@@ -0,0 +1,61 @@
import { expect, test } from '@playwright/test';
import { keyCloakSignIn } from './common';
test.beforeEach(async ({ page, browserName }) => {
await page.goto('/');
await keyCloakSignIn(page, browserName);
});
test.describe('Teams', () => {
test('checks all the create team elements are visible', async ({ page }) => {
const buttonCreateHomepage = page.getByRole('button', {
name: 'Create a new team',
});
await buttonCreateHomepage.click();
await expect(buttonCreateHomepage).toBeHidden();
const card = page.getByLabel('Create new team card').first();
await expect(card.getByLabel('Team name')).toBeVisible();
await expect(card.getByLabel('icon group')).toBeVisible();
await expect(
card.getByRole('heading', {
name: 'Name the team',
level: 3,
}),
).toBeVisible();
await expect(
card.getByRole('button', {
name: 'Create the team',
}),
).toBeVisible();
await expect(
card.getByRole('button', {
name: 'Cancel',
}),
).toBeVisible();
});
test('checks the cancel button interaction', async ({ page }) => {
const buttonCreateHomepage = page.getByRole('button', {
name: 'Create a new team',
});
await buttonCreateHomepage.click();
await expect(buttonCreateHomepage).toBeHidden();
const card = page.getByLabel('Create new team card').first();
await card
.getByRole('button', {
name: 'Cancel',
})
.click();
await expect(buttonCreateHomepage).toBeVisible();
});
});

View File

@@ -10,7 +10,7 @@ test.beforeEach(async ({ page, browserName }) => {
});
test.describe.configure({ mode: 'serial' });
test.describe('Teams', () => {
test.describe('Teams Panel', () => {
test('001 - checks all the elements are visible', async ({ page }) => {
const panel = page.getByLabel('Teams panel').first();
@@ -35,10 +35,10 @@ test.describe('Teams', () => {
).toBeVisible();
});
test('002 - check sort button', async ({ page, browserName }) => {
test('002 - checks the sort button', async ({ page, browserName }) => {
const panel = page.getByLabel('Teams panel').first();
await page.getByRole('button', { name: 'Add a team' }).click();
await panel.getByRole('button', { name: 'Add a team' }).click();
const randomTeams = Array.from({ length: 3 }, () => {
return `team-sort-${browserName}-${Math.floor(Math.random() * 1000)}`;
@@ -46,7 +46,7 @@ test.describe('Teams', () => {
for (let i = 0; i < 3; i++) {
await page.getByText('Team name').fill(`${randomTeams[i]}-${i}`);
await page.getByRole('button', { name: 'Create a team' }).click();
await page.getByRole('button', { name: 'Create the team' }).click();
await expect(
panel.locator('li').nth(0).getByText(`${randomTeams[i]}-${i}`),
).toBeVisible();
@@ -65,18 +65,18 @@ test.describe('Teams', () => {
}
});
test('003 - check the infinite scrool', async ({ page, browserName }) => {
test('003 - checks the infinite scrool', async ({ page, browserName }) => {
test.setTimeout(90000);
const panel = page.getByLabel('Teams panel').first();
await page.getByRole('button', { name: 'Add a team' }).click();
await panel.getByRole('button', { name: 'Add a team' }).click();
const randomTeams = Array.from({ length: 40 }, () => {
return `team-infinite-${browserName}-${Math.floor(Math.random() * 10000)}`;
});
for (let i = 0; i < 40; i++) {
await page.getByText('Team name').fill(`${randomTeams[i]}-${i}`);
await page.getByRole('button', { name: 'Create Team' }).click();
await page.getByRole('button', { name: 'Create the team' }).click();
await expect(
panel.locator('li').getByText(`${randomTeams[i]}-${i}`),
).toBeVisible();