diff --git a/src/frontend/apps/desk/src/app/Header/Header.tsx b/src/frontend/apps/desk/src/app/Header/Header.tsx
index 7088532..2a1cc68 100644
--- a/src/frontend/apps/desk/src/app/Header/Header.tsx
+++ b/src/frontend/apps/desk/src/app/Header/Header.tsx
@@ -13,6 +13,9 @@ import { default as IconDesk } from './assets/icon-desk.svg?url';
import { default as IconFAQ } from './assets/icon-faq.svg?url';
import { default as IconGouv } from './assets/icon-gouv.svg?url';
import { default as IconMarianne } from './assets/icon-marianne.svg?url';
+import IconMyAccount from './assets/icon-my-account.png';
+
+export const HEADER_HEIGHT = '100px';
const RedStripe = styled.div`
position: absolute;
@@ -22,19 +25,22 @@ const RedStripe = styled.div`
top: 0;
`;
+const StyledHeader = styled.header`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: ${HEADER_HEIGHT};
+ width: 100%;
+ background: white;
+ box-shadow: 0 1px 4px #00000040;
+ z-index: 100;
+`;
+
const Header = () => {
const { t } = useTranslation();
return (
-
+
@@ -73,15 +79,28 @@ const Header = () => {
- }
- color="tertiary"
- className="c__button-no-bg p-0 m-0"
- />
+
+
+ John Doe
+
+
+ }
+ color="tertiary"
+ className="c__button-no-bg p-0 m-0"
+ />
+
-
+
);
};
diff --git a/src/frontend/apps/desk/src/app/__tests__/page.test.tsx b/src/frontend/apps/desk/src/app/__tests__/page.test.tsx
index dc6abfc..8cfe977 100644
--- a/src/frontend/apps/desk/src/app/__tests__/page.test.tsx
+++ b/src/frontend/apps/desk/src/app/__tests__/page.test.tsx
@@ -1,7 +1,6 @@
import '@testing-library/jest-dom';
-import { act, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
-import useAuthStore from '@/auth/useAuthStore';
import { AppWrapper } from '@/tests/utils';
import Page from '../page';
@@ -12,10 +11,8 @@ describe('Page', () => {
expect(screen.getByRole('status')).toBeInTheDocument();
- act(() => {
- useAuthStore.setState({ authenticated: true });
- });
-
- expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Desk');
+ expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
+ 'Hello Desk!',
+ );
});
});
diff --git a/src/frontend/apps/desk/src/app/menu/Menu.tsx b/src/frontend/apps/desk/src/app/menu/Menu.tsx
new file mode 100644
index 0000000..ee30649
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/Menu.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { Box } from '@/components/';
+import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
+
+import MenuItem from './MenuItems';
+import IconRecent from './assets/icon-clock.svg';
+import IconContacts from './assets/icon-contacts.svg';
+import IconGroup from './assets/icon-group.svg';
+import IconSearch from './assets/icon-search.svg';
+import IconFavorite from './assets/icon-stars.svg';
+
+export const Menu = () => {
+ const { colorsTokens } = useCunninghamTheme();
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/frontend/apps/desk/src/app/menu/MenuItems.tsx b/src/frontend/apps/desk/src/app/menu/MenuItems.tsx
new file mode 100644
index 0000000..6f3ba04
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/MenuItems.tsx
@@ -0,0 +1,66 @@
+import { Button } from '@openfun/cunningham-react';
+import React, { useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { Box } from '@/components/';
+import { useCunninghamTheme } from '@/cunningham';
+import { SVGComponent } from '@/types/components';
+
+import { Tooltip } from './Tooltip';
+
+interface MenuItemProps {
+ Icon: SVGComponent;
+ label: string;
+}
+
+const MenuItem = ({ Icon, label }: MenuItemProps) => {
+ const { t } = useTranslation();
+ const buttonRef = useRef(null);
+ const { colorsTokens } = useCunninghamTheme();
+ const [isTooltipOpen, setIsTooltipOpen] = useState(false);
+
+ return (
+ <>
+ button { padding: 0};
+ transition: all 0.2s ease-in-out
+ `}
+ $background={colorsTokens()['primary-300']}
+ $radius="10px"
+ onMouseOver={() => setIsTooltipOpen(true)}
+ onMouseOut={() => setIsTooltipOpen(false)}
+ >
+
+ }
+ color="tertiary"
+ className="c__button-no-bg p-0 m-0"
+ style={{ flexDirection: 'column', gap: '0' }}
+ />
+
+ {isTooltipOpen && (
+
+ )}
+ >
+ );
+};
+
+export default MenuItem;
diff --git a/src/frontend/apps/desk/src/app/menu/Tooltip.tsx b/src/frontend/apps/desk/src/app/menu/Tooltip.tsx
new file mode 100644
index 0000000..d151853
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/Tooltip.tsx
@@ -0,0 +1,56 @@
+import { Popover } from '@openfun/cunningham-react';
+import React, { CSSProperties, useEffect, useState } from 'react';
+
+import { Box, Text } from '@/components/';
+
+interface TooltipProps {
+ buttonRef: React.MutableRefObject;
+ textColor: CSSProperties['color'];
+ backgroundColor: CSSProperties['color'];
+ label: string;
+}
+
+export const Tooltip = ({
+ buttonRef,
+ backgroundColor,
+ textColor,
+ label,
+}: TooltipProps) => {
+ const [opacity, setOpacity] = useState(0);
+
+ useEffect(() => {
+ setOpacity(1);
+ }, []);
+
+ return (
+ ''} borderless>
+
+
+ {label}
+
+
+
+ );
+};
diff --git a/src/frontend/apps/desk/src/app/menu/assets/icon-clock.svg b/src/frontend/apps/desk/src/app/menu/assets/icon-clock.svg
new file mode 100644
index 0000000..bd375f2
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/assets/icon-clock.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/frontend/apps/desk/src/app/menu/assets/icon-contacts.svg b/src/frontend/apps/desk/src/app/menu/assets/icon-contacts.svg
new file mode 100644
index 0000000..2924525
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/assets/icon-contacts.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/frontend/apps/desk/src/app/menu/assets/icon-group.svg b/src/frontend/apps/desk/src/app/menu/assets/icon-group.svg
new file mode 100644
index 0000000..c2a7d3c
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/assets/icon-group.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/frontend/apps/desk/src/app/menu/assets/icon-search.svg b/src/frontend/apps/desk/src/app/menu/assets/icon-search.svg
new file mode 100644
index 0000000..21e2444
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/assets/icon-search.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/frontend/apps/desk/src/app/menu/assets/icon-stars.svg b/src/frontend/apps/desk/src/app/menu/assets/icon-stars.svg
new file mode 100644
index 0000000..8de1495
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/assets/icon-stars.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/frontend/apps/desk/src/app/menu/index.ts b/src/frontend/apps/desk/src/app/menu/index.ts
new file mode 100644
index 0000000..629d3d0
--- /dev/null
+++ b/src/frontend/apps/desk/src/app/menu/index.ts
@@ -0,0 +1 @@
+export * from './Menu';
diff --git a/src/frontend/apps/desk/src/app/page.tsx b/src/frontend/apps/desk/src/app/page.tsx
index 0377d1e..39e2fa2 100644
--- a/src/frontend/apps/desk/src/app/page.tsx
+++ b/src/frontend/apps/desk/src/app/page.tsx
@@ -5,9 +5,11 @@ import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import useAuthStore from '@/auth/useAuthStore';
+import { Box } from '@/components';
-import Header from './Header/Header';
+import Header, { HEADER_HEIGHT } from './Header/Header';
import { Teams } from './Teams';
+import { MENU_WIDTH, Menu } from './menu';
export default function Home() {
const { initAuth, authenticated, initialized } = useAuthStore();
@@ -28,8 +30,18 @@ export default function Home() {
return (
- {t('Hello Desk !')}
-
+
+
+
+ {t('Hello Desk !')}
+
+
+
);
}
diff --git a/src/frontend/apps/desk/src/components/Box.tsx b/src/frontend/apps/desk/src/components/Box.tsx
index 01f05e9..21181a9 100644
--- a/src/frontend/apps/desk/src/components/Box.tsx
+++ b/src/frontend/apps/desk/src/components/Box.tsx
@@ -9,11 +9,13 @@ export interface BoxProps {
$color?: CSSProperties['color'];
$css?: string;
$direction?: CSSProperties['flexDirection'];
+ $display?: CSSProperties['display'];
$flex?: boolean;
$gap?: CSSProperties['gap'];
$height?: CSSProperties['height'];
$justify?: CSSProperties['justifyContent'];
$position?: CSSProperties['position'];
+ $radius?: CSSProperties['borderRadius'];
$width?: CSSProperties['width'];
}
@@ -23,11 +25,13 @@ export const Box = styled('div')`
${({ $background }) => $background && `background: ${$background};`}
${({ $color }) => $color && `color: ${$color};`}
${({ $direction }) => $direction && `flex-direction: ${$direction};`}
+ ${({ $display }) => $display && `display: ${$display};`}
${({ $flex }) => $flex === false && `display: block;`}
${({ $gap }) => $gap && `gap: ${$gap};`}
${({ $height }) => $height && `height: ${$height};`}
${({ $justify }) => $justify && `justify-content: ${$justify};`}
${({ $position }) => $position && `position: ${$position};`}
+ ${({ $radius }) => $radius && `border-radius: ${$radius};`}
${({ $width }) => $width && `width: ${$width};`}
${({ $css }) => $css && `${$css};`}
`;
diff --git a/src/frontend/apps/desk/src/components/Text.tsx b/src/frontend/apps/desk/src/components/Text.tsx
index 67078c6..c2ab7a7 100644
--- a/src/frontend/apps/desk/src/components/Text.tsx
+++ b/src/frontend/apps/desk/src/components/Text.tsx
@@ -1,15 +1,21 @@
import { CSSProperties, ComponentPropsWithRef, ReactHTML } from 'react';
import styled from 'styled-components';
+import { tokens } from '@/cunningham';
+
import { Box, BoxProps } from './Box';
+const { sizes } = tokens.themes.default.theme.font;
+type TextSizes = keyof typeof sizes;
+
export interface TextProps extends BoxProps {
as?: keyof Pick<
ReactHTML,
'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
>;
$weight?: CSSProperties['fontWeight'];
- $size?: CSSProperties['fontSize'];
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ $size?: TextSizes | (string & {});
$theme?:
| 'primary'
| 'secondary'
@@ -33,10 +39,12 @@ export interface TextProps extends BoxProps {
export const TextStyled = styled(Box)`
${({ $weight }) => $weight && `font-weight: ${$weight};`}
- ${({ $size }) => $size && `font-size: ${$size};`}
- ${({ $color }) => $color && `color: ${$color};`}
+ ${({ $size }) =>
+ $size &&
+ `font-size: ${$size in sizes ? sizes[$size as TextSizes] : $size};`}
${({ $theme, $variation }) =>
`color: var(--c--theme--colors--${$theme}-${$variation});`}
+ ${({ $color }) => $color && `color: ${$color};`}
`;
export const Text = ({
diff --git a/src/frontend/apps/desk/src/features/header/assets/icon-my-account.png b/src/frontend/apps/desk/src/features/header/assets/icon-my-account.png
new file mode 100644
index 0000000..54bd64c
Binary files /dev/null and b/src/frontend/apps/desk/src/features/header/assets/icon-my-account.png differ
diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json
index 1c5b827..932f8bb 100644
--- a/src/frontend/apps/desk/src/i18n/translations.json
+++ b/src/frontend/apps/desk/src/i18n/translations.json
@@ -2,6 +2,7 @@
"en": { "translation": {} },
"fr": {
"translation": {
+ "Hello Desk !": "Bonjour Desk !",
"Marianne Logo": "Logo Marianne",
"Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité",
"Desk Logo": "Logo Desk",
@@ -13,7 +14,15 @@
"Cells icon": "Icône Cellules",
"Language Icon": "Icône de langue",
"Language": "Langue",
- "Hello Desk !": "Bonjour Desk !",
+ "Search": "Rechercher",
+ "Favorite": "Favorite",
+ "Recent": "Récent",
+ "Contacts": "Contacts",
+ "Groups": "Groupes",
+ "Help": "Aide",
+ "Profile picture": "Photo de l'utilisateur",
+ "{{label}} button": "Bouton {{label}}",
+ "{{label}} icon": "Icône {{label}}",
"Team name": "Nom de l’équipe",
"Create Team": "Créer une équipe"
}
diff --git a/src/frontend/apps/desk/src/types/components.ts b/src/frontend/apps/desk/src/types/components.ts
new file mode 100644
index 0000000..5885070
--- /dev/null
+++ b/src/frontend/apps/desk/src/types/components.ts
@@ -0,0 +1,5 @@
+export type SVGComponent = React.FunctionComponent<
+ React.SVGProps & {
+ title?: string;
+ }
+>;
diff --git a/src/frontend/apps/e2e/__tests__/app-desk/header.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/header.spec.ts
index 7484b28..244f3b5 100644
--- a/src/frontend/apps/e2e/__tests__/app-desk/header.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-desk/header.spec.ts
@@ -36,5 +36,12 @@ test.describe('Header', () => {
await expect(header.getByAltText('Cells icon')).toBeVisible();
await expect(header.getByAltText('Language Icon')).toBeVisible();
+
+ await expect(header.getByText('John Doe')).toBeVisible();
+ await expect(
+ header.getByRole('img', {
+ name: 'profile picture',
+ }),
+ ).toBeVisible();
});
});
diff --git a/src/frontend/apps/e2e/__tests__/app-desk/menu.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/menu.spec.ts
new file mode 100644
index 0000000..7a5d1db
--- /dev/null
+++ b/src/frontend/apps/e2e/__tests__/app-desk/menu.spec.ts
@@ -0,0 +1,26 @@
+import { expect, test } from "@playwright/test";
+
+import { keyCloakSignIn } from "./common";
+
+test.beforeEach(async ({ page }) => {
+ await page.goto("/");
+ await keyCloakSignIn(page);
+});
+
+test.describe("Menu", () => {
+ test("checks all the elements are visible", async ({ page }) => {
+ const menu = page.locator("menu").first();
+
+ await expect(menu.getByLabel("Search button")).toBeVisible();
+ await expect(menu.getByLabel("Favoris button")).toBeVisible();
+ await expect(menu.getByLabel("Recent button")).toBeVisible();
+ await expect(menu.getByLabel("Contacts button")).toBeVisible();
+ await expect(menu.getByLabel("Groups button")).toBeVisible();
+
+ await menu.getByLabel("Search button").hover();
+ await expect(menu.getByLabel("tooltip")).toHaveText("Search");
+
+ await menu.getByLabel("Contacts button").hover();
+ await expect(menu.getByLabel("tooltip")).toHaveText("Contacts");
+ });
+});