✨(frontend) integrate configurable Waffle
Integrate Waffle component based on LaGaufreV2 from @gouvfr-lasuite/ui-kit. Waffle will be fully configurable via the app config, allowing to be set through environment variables and api-provided configuration.
This commit is contained in:
@@ -6,6 +6,10 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(frontend) integrate configurable Waffle #1795
|
||||
|
||||
### Fixed
|
||||
|
||||
- ✅(e2e) fix e2e test for other browsers #1799
|
||||
|
||||
BIN
docs/assets/waffle.png
Normal file
BIN
docs/assets/waffle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -1,4 +1,6 @@
|
||||
# Runtime Theming 🎨
|
||||
# Customization Guide 🛠 ️
|
||||
|
||||
## Runtime Theming 🎨
|
||||
|
||||
### How to Use
|
||||
|
||||
@@ -32,7 +34,7 @@ Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom
|
||||
|
||||
----
|
||||
|
||||
# Runtime JavaScript Injection 🚀
|
||||
## Runtime JavaScript Injection 🚀
|
||||
|
||||
### How to Use
|
||||
|
||||
@@ -87,7 +89,7 @@ Then, set the `FRONTEND_JS_URL` environment variable to the URL of your custom J
|
||||
|
||||
----
|
||||
|
||||
# **Your Docs icon** 📝
|
||||
## **Your Docs icon** 📝
|
||||
|
||||
You can add your own Docs icon in the header from the theme customization file.
|
||||
|
||||
@@ -105,7 +107,7 @@ This configuration is optional. If not set, the default icon will be used.
|
||||
|
||||
----
|
||||
|
||||
# **Footer Configuration** 📝
|
||||
## **Footer Configuration** 📝
|
||||
|
||||
The footer is configurable from the theme customization file.
|
||||
|
||||
@@ -128,7 +130,7 @@ Below is a visual example of a configured footer ⬇️:
|
||||
|
||||
----
|
||||
|
||||
# **Custom Translations** 📝
|
||||
## **Custom Translations** 📝
|
||||
|
||||
The translations can be partially overridden from the theme customization file.
|
||||
|
||||
@@ -140,4 +142,36 @@ THEME_CUSTOMIZATION_FILE_PATH=<path>
|
||||
|
||||
### Example of JSON
|
||||
|
||||
The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
|
||||
----
|
||||
|
||||
## **Waffle Configuration** 🧇
|
||||
|
||||
The Waffle (La Gaufre) is a widget that displays a grid of services.
|
||||
|
||||

|
||||
|
||||
### Settings 🔧
|
||||
|
||||
```shellscript
|
||||
THEME_CUSTOMIZATION_FILE_PATH=<path>
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The Waffle can be configured in the theme customization file with the `waffle` key.
|
||||
|
||||
### Available Properties
|
||||
|
||||
See: [LaGaufreV2Props](https://github.com/suitenumerique/ui-kit/blob/main/src/components/la-gaufre/LaGaufreV2.tsx#L49)
|
||||
|
||||
### Complete Example
|
||||
|
||||
From the theme customization file: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
|
||||
|
||||
### Behavior
|
||||
|
||||
- If `data.services` is provided, the Waffle will display those services statically
|
||||
- If no data is provided, services can be fetched dynamically from an API endpoint thanks to the `apiUrl` property
|
||||
|
||||
@@ -59,45 +59,90 @@ test.describe('Header', () => {
|
||||
).toBeVisible();
|
||||
|
||||
await expect(header.getByText('English')).toBeVisible();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('checks La Gauffre interaction', async ({ page }) => {
|
||||
test('checks a custom waffle', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
theme_customization: {
|
||||
waffle: {
|
||||
data: {
|
||||
services: [
|
||||
{
|
||||
name: 'Docs E2E Custom 1',
|
||||
url: 'https://docs.numerique.gouv.fr/',
|
||||
maturity: 'stable',
|
||||
logo: 'https://lasuite.numerique.gouv.fr/assets/products/docs.svg',
|
||||
},
|
||||
{
|
||||
name: 'Docs E2E Custom 2',
|
||||
url: 'https://docs.numerique.gouv.fr/',
|
||||
maturity: 'stable',
|
||||
logo: 'https://lasuite.numerique.gouv.fr/assets/products/docs.svg',
|
||||
},
|
||||
],
|
||||
},
|
||||
showMoreLimit: 9,
|
||||
},
|
||||
},
|
||||
});
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
header.getByRole('button', { name: 'Digital LaSuite services' }),
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* La gaufre load a js file from a remote server,
|
||||
* The Waffle loads a js file from a remote server,
|
||||
* it takes some time to load the file and have the interaction available
|
||||
*/
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await header
|
||||
.getByRole('button', { name: 'Digital LaSuite services' })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs E2E Custom 1' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Docs E2E Custom 2' }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('checks the waffle dsfr', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
theme_customization: {
|
||||
waffle: {
|
||||
apiUrl: 'https://lasuite.numerique.gouv.fr/api/services',
|
||||
showMoreLimit: 9,
|
||||
},
|
||||
},
|
||||
});
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', { name: 'Digital LaSuite services' }),
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* The Waffle loads a js file from a remote server,
|
||||
* it takes some time to load the file and have the interaction available
|
||||
*/
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await header
|
||||
.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
name: 'Digital LaSuite services',
|
||||
})
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'France Transfert' }),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByRole('link', { name: 'Tchap' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Grist' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Visio' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,11 +169,6 @@ test.describe('Header mobile', () => {
|
||||
|
||||
await expect(header.getByLabel('Open the header menu')).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Les services de La Suite numérique',
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -113,9 +113,6 @@ test.describe('Home page', () => {
|
||||
});
|
||||
await expect(languageButton).toBeVisible();
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', { name: 'Les services de La Suite numé' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
header.getByRole('img', { name: 'Gouvernement Logo' }),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -26,7 +26,6 @@ const themeWhiteLabelLight = getUIKitThemesFromGlobals(whiteLabelGlobals, {
|
||||
widthHeader: '',
|
||||
widthFooter: '',
|
||||
},
|
||||
'la-gaufre': false,
|
||||
'home-proconnect': false,
|
||||
icon: {
|
||||
src: '/assets/icon-docs.svg',
|
||||
@@ -64,7 +63,6 @@ const themesDSFRLight = getUIKitThemesFromGlobals(dsfrGlobals, {
|
||||
widthFooter: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
},
|
||||
'la-gaufre': true,
|
||||
'home-proconnect': true,
|
||||
icon: {
|
||||
src: '/assets/icon-docs-dsfr.svg',
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Resource } from 'i18next';
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { Theme } from '@/cunningham/';
|
||||
import { FooterType } from '@/features/footer';
|
||||
import { HeaderType } from '@/features/header';
|
||||
import { HeaderType, WaffleType } from '@/features/header';
|
||||
import { PostHogConf } from '@/services';
|
||||
|
||||
interface ThemeCustomization {
|
||||
footer?: FooterType;
|
||||
translations?: Resource;
|
||||
header?: HeaderType;
|
||||
waffle?: WaffleType;
|
||||
}
|
||||
|
||||
export interface ConfigResponse {
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('<useCunninghamTheme />', () => {
|
||||
expect(useCunninghamTheme.getState().componentTokens.logo?.src).toBe('');
|
||||
|
||||
// Change theme
|
||||
useCunninghamTheme.getState().setTheme('dsfr-light');
|
||||
useCunninghamTheme.getState().setTheme('dsfr');
|
||||
|
||||
const { componentTokens } = useCunninghamTheme.getState();
|
||||
const logo = componentTokens.logo;
|
||||
|
||||
@@ -893,7 +893,6 @@
|
||||
--c--components--logo--alt: ;
|
||||
--c--components--logo--widthheader: ;
|
||||
--c--components--logo--widthfooter: ;
|
||||
--c--components--la-gaufre: false;
|
||||
--c--components--home-proconnect: false;
|
||||
--c--components--icon--src: /assets/icon-docs.svg;
|
||||
--c--components--icon--width: 32px;
|
||||
@@ -2594,7 +2593,6 @@
|
||||
--c--components--logo--alt: gouvernement logo;
|
||||
--c--components--logo--widthHeader: 110px;
|
||||
--c--components--logo--widthFooter: 220px;
|
||||
--c--components--la-gaufre: true;
|
||||
--c--components--home-proconnect: true;
|
||||
--c--components--icon--src: /assets/icon-docs-dsfr.svg;
|
||||
--c--components--icon--width: 32px;
|
||||
|
||||
@@ -677,7 +677,6 @@ export const tokens = {
|
||||
info: { 'background-color': '#D5E4F3', color: '#005BC0' },
|
||||
},
|
||||
logo: { src: '', alt: '', widthHeader: '', widthFooter: '' },
|
||||
'la-gaufre': false,
|
||||
'home-proconnect': false,
|
||||
icon: { src: '/assets/icon-docs.svg', width: '32px', height: 'auto' },
|
||||
favicon: {
|
||||
@@ -1973,7 +1972,6 @@ export const tokens = {
|
||||
widthHeader: '110px',
|
||||
widthFooter: '220px',
|
||||
},
|
||||
'la-gaufre': true,
|
||||
'home-proconnect': true,
|
||||
icon: {
|
||||
src: '/assets/icon-docs-dsfr.svg',
|
||||
|
||||
@@ -12,8 +12,8 @@ import { useResponsiveStore } from '@/stores';
|
||||
import { HEADER_HEIGHT } from '../conf';
|
||||
|
||||
import { ButtonTogglePanel } from './ButtonTogglePanel';
|
||||
import { LaGaufre } from './LaGaufre';
|
||||
import { Title } from './Title';
|
||||
import { Waffle } from './Waffle';
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -85,7 +85,7 @@ export const Header = () => {
|
||||
</StyledLink>
|
||||
{!isDesktop ? (
|
||||
<Box $direction="row" $gap={spacingsTokens['sm']}>
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
@@ -96,7 +96,7 @@ export const Header = () => {
|
||||
>
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Gaufre } from '@gouvfr-lasuite/integration';
|
||||
import '@gouvfr-lasuite/integration/dist/css/gaufre.css';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
const GaufreStyle = createGlobalStyle`
|
||||
.lasuite-gaufre-btn{
|
||||
box-shadow: inset 0 0 0 0 !important;
|
||||
border-radius: var(--c--components--button--border-radius) !important;
|
||||
transition: all var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out) !important;
|
||||
&:hover, &:focus-visible {
|
||||
background: var(--c--contextuals--background--semantic--contextual--primary) !important;
|
||||
}
|
||||
color: var(--c--contextuals--content--semantic--brand--tertiary) !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LaGaufre = () => {
|
||||
const { componentTokens } = useCunninghamTheme();
|
||||
|
||||
if (!componentTokens['la-gaufre']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://integration.lasuite.numerique.gouv.fr/api/v1/gaufre.js"
|
||||
strategy="lazyOnload"
|
||||
id="lasuite-gaufre-script"
|
||||
/>
|
||||
<GaufreStyle />
|
||||
<Gaufre variant="small" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { LaGaufreV2, LaGaufreV2Props } from '@gouvfr-lasuite/ui-kit';
|
||||
import React from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
|
||||
type WaffleAPIType = {
|
||||
apiUrl: LaGaufreV2Props['apiUrl'];
|
||||
data?: never;
|
||||
};
|
||||
|
||||
type WaffleDataType = {
|
||||
apiUrl?: never;
|
||||
data?: LaGaufreV2Props['data'];
|
||||
};
|
||||
|
||||
export type WaffleType = Omit<
|
||||
LaGaufreV2Props,
|
||||
'apiUrl' | 'data' | 'widgetPath'
|
||||
> &
|
||||
(WaffleAPIType | WaffleDataType) & {
|
||||
widgetPath?: string;
|
||||
};
|
||||
|
||||
const LaGaufreV2Fixed = LaGaufreV2 as React.ComponentType<WaffleType>;
|
||||
|
||||
export const Waffle = () => {
|
||||
const { data: conf } = useConfig();
|
||||
|
||||
const waffleConfig = conf?.theme_customization?.waffle;
|
||||
|
||||
if (!waffleConfig?.apiUrl && !waffleConfig?.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
& > div {
|
||||
display: flex;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<LaGaufreV2Fixed {...waffleConfig} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './ButtonTogglePanel';
|
||||
export * from './Header';
|
||||
export * from './LaGaufre';
|
||||
export * from './Waffle';
|
||||
export * from './Title';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Box } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { ButtonTogglePanel, Title } from '@/features/header/';
|
||||
import { LaGaufre } from '@/features/header/components/LaGaufre';
|
||||
import { Waffle } from '@/features/header/components/Waffle';
|
||||
import { LanguagePicker } from '@/features/language';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
@@ -81,7 +81,7 @@ export const HomeHeader = () => {
|
||||
{!isSmallMobile && (
|
||||
<Box $direction="row" $gap="1rem" $align="center">
|
||||
<LanguagePicker />
|
||||
<LaGaufre />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -139,5 +139,30 @@
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"width": "32px"
|
||||
}
|
||||
},
|
||||
"waffle": {
|
||||
"data": {
|
||||
"services": [
|
||||
{
|
||||
"name": "Docs",
|
||||
"url": "https://docs.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/docs.svg"
|
||||
},
|
||||
{
|
||||
"name": "Visio",
|
||||
"url": "https://visio.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/visio.svg"
|
||||
},
|
||||
{
|
||||
"name": "Fichiers",
|
||||
"url": "https://fichiers.numerique.gouv.fr/",
|
||||
"maturity": "stable",
|
||||
"logo": "https://lasuite.numerique.gouv.fr/assets/products/fichiers.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"showMoreLimit": 9
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user