Skip to content

Commit fe39152

Browse files
committed
📱(frontend) header small mobile friendly
We adapt the header to be small mobile friendly. We added a burger menu to display the dropdown menu on small mobile.
1 parent 399cf89 commit fe39152

File tree

14 files changed

+226
-52
lines changed

14 files changed

+226
-52
lines changed

src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ test.describe('Header', () => {
1010
test('checks all the elements are visible', async ({ page }) => {
1111
const header = page.locator('header').first();
1212

13-
await expect(header.getByAltText('Gouvernement Logo')).toBeVisible();
14-
1513
await expect(header.getByAltText('Docs Logo')).toBeVisible();
1614
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
1715
'color',
@@ -67,6 +65,42 @@ test.describe('Header', () => {
6765
});
6866
});
6967

68+
test.describe('Header mobile', () => {
69+
test.use({ viewport: { width: 500, height: 1200 } });
70+
71+
test.beforeEach(async ({ page }) => {
72+
await page.goto('/');
73+
});
74+
75+
test('it checks the header when mobile', async ({ page }) => {
76+
const header = page.locator('header').first();
77+
78+
await expect(
79+
header.getByRole('button', {
80+
name: 'Les services de La Suite numérique',
81+
}),
82+
).toBeVisible();
83+
84+
await expect(
85+
page.getByRole('button', {
86+
name: 'Logout',
87+
}),
88+
).toBeHidden();
89+
90+
await expect(page.getByAltText('Language Icon')).toBeHidden();
91+
92+
await header.getByLabel('Open the header menu').click();
93+
94+
await expect(
95+
page.getByRole('button', {
96+
name: 'Logout',
97+
}),
98+
).toBeVisible();
99+
100+
await expect(page.getByAltText('Language Icon')).toBeVisible();
101+
});
102+
});
103+
70104
test.describe('Header: Log out', () => {
71105
test.use({ storageState: { cookies: [], origins: [] } });
72106

src/frontend/apps/impress/src/core/auth/AccountDropdown.tsx renamed to src/frontend/apps/impress/src/core/auth/ButtonLogin.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
44

55
import { useAuthStore } from '@/core/auth';
66

7-
export const AccountDropdown = () => {
7+
export const ButtonLogin = () => {
88
const { t } = useTranslation();
99
const { logout, authenticated, login } = useAuthStore();
1010

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './AccountDropdown';
21
export * from './api/types';
32
export * from './Auth';
3+
export * from './ButtonLogin';
44
export * from './useAuthStore';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.burgerIcon {
2+
cursor: pointer;
3+
transform: translate(-20%, 0%);
4+
}
5+
6+
.burgerIcon path {
7+
stroke-width: 40;
8+
stroke-linecap: round;
9+
fill: none;
10+
transition: all 0.5s ease-in-out;
11+
}
12+
13+
/* In menu form */
14+
.burgerIcon path:first-child,
15+
.burgerIcon path:last-child {
16+
stroke-dasharray: 240px 910px;
17+
}
18+
19+
.burgerIcon .middle_bar {
20+
stroke-dasharray: 240px 240px;
21+
}
22+
23+
/* In cross form */
24+
.open path:first-child,
25+
.open path:last-child {
26+
stroke-dashoffset: -650px;
27+
}
28+
29+
.open :nth-child(2) {
30+
stroke-dasharray: 0 220px;
31+
stroke-dashoffset: -120px;
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
4+
import { Burger } from './Burger';
5+
6+
describe('<Burger />', () => {
7+
test('Burger interactions', () => {
8+
const { rerender } = render(<Burger isOpen={true} />);
9+
10+
const burger = screen.getByRole('img');
11+
expect(burger).toBeInTheDocument();
12+
expect(burger.classList.contains('open')).toBeTruthy();
13+
14+
rerender(<Burger isOpen={false} />);
15+
16+
expect(burger.classList.contains('open')).not.toBeTruthy();
17+
});
18+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { SVGProps } from 'react';
2+
3+
import { Box } from '@/components';
4+
import { useCunninghamTheme } from '@/cunningham';
5+
6+
import styles from './Burger.module.css';
7+
import BurgerIcon from './burger.svg';
8+
9+
type BurgerProps = SVGProps<SVGSVGElement> & {
10+
isOpen: boolean;
11+
};
12+
13+
export const Burger = ({ isOpen, ...props }: BurgerProps) => {
14+
const { colorsTokens } = useCunninghamTheme();
15+
16+
return (
17+
<Box
18+
$color={colorsTokens()['primary-text']}
19+
$padding="none"
20+
$justify="center"
21+
>
22+
<BurgerIcon
23+
role="img"
24+
className={`${styles.burgerIcon} ${isOpen ? styles.open : ''}`}
25+
{...props}
26+
/>
27+
</Box>
28+
);
29+
};
30+
31+
export default Burger;
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
import { Box, DropButton } from '@/components';
5+
import { ButtonLogin } from '@/core';
6+
import { useCunninghamTheme } from '@/cunningham';
7+
import { LanguagePicker } from '@/features/language';
8+
9+
import { Burger } from './Burger/Burger';
10+
11+
export const DropdownMenu = () => {
12+
const { colorsTokens } = useCunninghamTheme();
13+
const [isDropOpen, setIsDropOpen] = useState(false);
14+
const { t } = useTranslation();
15+
16+
return (
17+
<DropButton
18+
button={
19+
<Burger
20+
isOpen={isDropOpen}
21+
width={30}
22+
height={30}
23+
aria-controls="menu"
24+
aria-label={t('Open the header menu')}
25+
/>
26+
}
27+
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
28+
isOpen={isDropOpen}
29+
>
30+
<Box $align="center" $direction="column">
31+
<Box
32+
$width="100%"
33+
$align="center"
34+
$height="36px"
35+
$justify="center"
36+
$css={`&:hover{background:${colorsTokens()['primary-150']}}`}
37+
$hasTransition
38+
$radius="2px"
39+
>
40+
<LanguagePicker />
41+
</Box>
42+
<ButtonLogin />
43+
</Box>
44+
</DropButton>
45+
);
46+
};

src/frontend/apps/impress/src/features/header/Header.tsx renamed to src/frontend/apps/impress/src/features/header/components/Header.tsx

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,40 @@
11
import Image from 'next/image';
22
import React from 'react';
33
import { useTranslation } from 'react-i18next';
4-
import styled from 'styled-components';
54

65
import { Box, StyledLink, Text } from '@/components/';
7-
import { AccountDropdown } from '@/core/auth';
8-
import { useCunninghamTheme } from '@/cunningham';
6+
import { ButtonLogin } from '@/core/auth';
7+
import { LanguagePicker } from '@/features/language';
8+
import { useResponsiveStore } from '@/stores';
99

10-
import { LanguagePicker } from '../language/';
10+
import { default as IconDocs } from '../assets/icon-docs.svg?url';
1111

12+
import { DropdownMenu } from './DropdownMenu';
1213
import { LaGaufre } from './LaGaufre';
13-
import { default as IconDocs } from './assets/icon-docs.svg?url';
14-
15-
export const HEADER_HEIGHT = '100px';
16-
17-
const RedStripe = styled.div`
18-
position: absolute;
19-
height: 5px;
20-
width: 100%;
21-
background: var(--c--theme--colors--danger-500);
22-
top: 0;
23-
`;
2414

2515
export const Header = () => {
2616
const { t } = useTranslation();
27-
const { themeTokens } = useCunninghamTheme();
28-
const logo = themeTokens().logo;
17+
const { isSmallMobile } = useResponsiveStore();
2918

3019
return (
3120
<Box
3221
as="header"
3322
$justify="center"
3423
$width="100%"
35-
$height={HEADER_HEIGHT}
3624
$zIndex="100"
25+
$padding={{ vertical: 'xtiny' }}
3726
$css="box-shadow: 0 1px 4px #00000040;"
3827
>
39-
<RedStripe />
4028
<Box
41-
$margin={{ horizontal: 'xbig' }}
29+
$margin={{
30+
left: 'big',
31+
right: isSmallMobile ? 'none' : 'big',
32+
}}
4233
$align="center"
4334
$justify="space-between"
4435
$direction="row"
4536
>
46-
<Box $gap="6rem" $direction="row">
47-
{logo && (
48-
<Image
49-
priority
50-
src={logo.src}
51-
alt={logo.alt}
52-
width={0}
53-
height={0}
54-
style={{ width: logo.widthHeader, height: 'auto' }}
55-
/>
56-
)}
37+
<Box>
5738
<StyledLink href="/">
5839
<Box
5940
$align="center"
@@ -63,32 +44,45 @@ export const Header = () => {
6344
$height="fit-content"
6445
$margin={{ top: 'auto' }}
6546
>
66-
<Image priority src={IconDocs} alt={t('Docs Logo')} width={38} />
47+
<Image priority src={IconDocs} alt={t('Docs Logo')} width={25} />
6748
<Text
68-
$padding="3px 5px"
49+
$padding="2px 3px"
6950
$size="8px"
7051
$background="#368bd6"
7152
$color="white"
7253
$position="absolute"
7354
$radius="5px"
7455
$css={`
75-
bottom: 21px;
76-
right: -21px;
56+
bottom: 13px;
57+
right: -17px;
7758
`}
7859
>
7960
BETA
8061
</Text>
81-
<Text $margin="none" as="h2" $theme="primary" $zIndex={1}>
62+
<Text
63+
$margin="none"
64+
as="h2"
65+
$theme="primary"
66+
$zIndex={1}
67+
$size="1.30rem"
68+
>
8269
{t('Docs')}
8370
</Text>
8471
</Box>
8572
</StyledLink>
8673
</Box>
87-
<Box $align="center" $gap="1.5rem" $direction="row">
88-
<AccountDropdown />
89-
<LanguagePicker />
90-
<LaGaufre />
91-
</Box>
74+
{isSmallMobile ? (
75+
<Box $direction="row" $gap="2rem">
76+
<LaGaufre />
77+
<DropdownMenu />
78+
</Box>
79+
) : (
80+
<Box $align="center" $gap="2vw" $direction="row">
81+
<ButtonLogin />
82+
<LanguagePicker />
83+
<LaGaufre />
84+
</Box>
85+
)}
9286
</Box>
9387
</Box>
9488
);

src/frontend/apps/impress/src/features/header/LaGaufre.tsx renamed to src/frontend/apps/impress/src/features/header/components/LaGaufre.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { Gaufre } from '@gouvfr-lasuite/integration';
22
import '@gouvfr-lasuite/integration/dist/css/gaufre.css';
33
import Script from 'next/script';
44
import React from 'react';
5+
import { createGlobalStyle } from 'styled-components';
6+
7+
const GaufreStyle = createGlobalStyle`
8+
.lasuite-gaufre-btn{
9+
box-shadow: inset 0 0 0 0 !important;
10+
}
11+
`;
512

613
export const LaGaufre = () => (
714
<>
@@ -10,6 +17,7 @@ export const LaGaufre = () => (
1017
strategy="lazyOnload"
1118
id="lasuite-gaufre-script"
1219
/>
13-
<Gaufre />
20+
<GaufreStyle />
21+
<Gaufre variant="small" />
1422
</>
1523
);

0 commit comments

Comments
 (0)