Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ and this project adheres to

### Fixed

🐛(frontend) fix broadcast store sync #1846
- 🐛(frontend) fix broadcast store sync #1846
- ✨(frontend) add onboarding modal with help menu button #1868

## [v4.5.0] - 2026-01-28

Expand Down
36 changes: 36 additions & 0 deletions src/frontend/apps/e2e/__tests__/app-impress/onboarding.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect, test } from '@playwright/test';

test.describe('Onboarding modal', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await expect(page.getByTestId('left-panel-desktop')).toBeVisible();
});

test('opens onboarding modal from help menu and can navigate/close', async ({
page,
}) => {
await page.getByRole('button', { name: 'Open onboarding menu' }).click();

await page.getByRole('menuitem', { name: 'Onboarding' }).click();

// Modal is rendered by the UI kit
const modal = page.getByTestId('onboarding-modal');
await expect(modal).toBeVisible();

await expect(page.getByTestId('onboarding-step-0')).toHaveAttribute(
'tabindex',
'0',
);

// Go to next step and ensure step focusable state updates
await page.getByTestId('onboarding-next').click();
await expect(page.getByTestId('onboarding-step-1')).toHaveAttribute(
'tabindex',
'0',
);

// Close the modal using Escape (works regardless of close button label)
await page.keyboard.press('Escape');
await expect(modal).toBeHidden();
});
});
2 changes: 1 addition & 1 deletion src/frontend/apps/impress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@fontsource/material-icons": "5.2.7",
"@gouvfr-lasuite/cunningham-react": "4.1.0",
"@gouvfr-lasuite/integration": "1.0.3",
"@gouvfr-lasuite/ui-kit": "0.18.7",
"@gouvfr-lasuite/ui-kit": "0.19.5",
"@hocuspocus/provider": "3.4.3",
"@mantine/core": "8.3.12",
"@mantine/hooks": "8.3.12",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useLeftPanelStore } from '../stores';

import { LeftPanelContent } from './LeftPanelContent';
import { LeftPanelHeader } from './LeftPanelHeader';
import { LeftPanelHelpMenu } from './LeftPanelHelpMenu';

const MobileLeftPanelStyle = createGlobalStyle`
body {
Expand Down Expand Up @@ -57,6 +58,14 @@ export const LeftPanel = () => {
<LeftPanelHeader />
</Box>
<LeftPanelContent />
<SeparatedSection showSeparator={false}>
<Box
$padding={{ horizontal: 'sm', vertical: 'xs' }}
$justify="flex-start"
>
<LeftPanelHelpMenu />
</Box>
</SeparatedSection>
</Box>
)}

Expand Down Expand Up @@ -91,6 +100,14 @@ export const LeftPanel = () => {
>
<LeftPanelHeader />
<LeftPanelContent />
<SeparatedSection showSeparator={false}>
<Box
$padding={{ horizontal: 'sm', vertical: 'xs' }}
$justify="flex-start"
>
<LeftPanelHelpMenu />
</Box>
</SeparatedSection>
<SeparatedSection showSeparator={false}>
<Box
$justify="center"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { OnBoarding } from '@/features/on-boarding/components/OnBoarding';

export const LeftPanelHelpMenu = () => {
return <OnBoarding />;
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Button } from '@gouvfr-lasuite/cunningham-react';
import { DropdownMenu, OnboardingModal } from '@gouvfr-lasuite/ui-kit';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';

import { Box, Icon } from '@/components';

import { useOnboardingMenuOptions } from '../hooks/useOnboardingMenuOptions';
import { useOnboardingSteps } from '../hooks/useOnboardingSteps';

export const OnBoarding = () => {
const { t } = useTranslation();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);

const openModal = useCallback(() => {
setIsModalOpen(true);
}, []);

const closeModal = useCallback(() => {
setIsModalOpen(false);
}, []);

const toggleMenu = () => {
setIsMenuOpen((open) => !open);
};

const steps = useOnboardingSteps();
const options = useOnboardingMenuOptions({ onOpenOnboarding: openModal });

return (
<>
<Box
$css={css`
.c__dropdown-menu-trigger {
display: flex;
justify-content: flex-start;
}
`}
>
<DropdownMenu
options={options}
isOpen={isMenuOpen}
onOpenChange={setIsMenuOpen}
>
<Button
aria-label={t('Open onboarding menu')}
color="neutral"
variant="tertiary"
iconPosition="left"
icon={
<Icon
$withThemeInherited
iconName="help_outline"
aria-hidden="true"
/>
}
onClick={toggleMenu}
/>
</DropdownMenu>
</Box>

<OnboardingModal
isOpen={isModalOpen}
appName={t('Discover Docs')}
mainTitle={t('Learn the core principles')}
steps={steps}
onClose={closeModal}
onComplete={closeModal}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PropsWithChildren } from 'react';
import { css } from 'styled-components';

import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';

export interface OnboardingStepIconProps {
size?: string;
}

export const OnboardingStepIcon = ({
size = '32px',
children,
}: PropsWithChildren<OnboardingStepIconProps>) => {
const { colorsTokens } = useCunninghamTheme();

return (
<Box
$css={css`
width: ${size};
height: ${size};
flex: 0 0 ${size};
display: flex;
align-items: center;
justify-content: center;
color: ${colorsTokens['gray-550']};

svg {
width: 24px;
height: 24px;
display: block;
}
`}
>
{children}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { css } from 'styled-components';

import { Box } from '@/components';

export interface OnboardingStepImageProps {
src: string;
alt: string;
}

export const OnboardingStepImage = ({ src, alt }: OnboardingStepImageProps) => {
return (
<Box
$css={css`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;

img {
max-width: 100%;
height: auto;
display: block;
}
`}
>
<img src={src} alt={alt} />
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type DropdownMenuOption } from '@gouvfr-lasuite/ui-kit';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import WandAndStarsIcon from '../assets/wand-and-stars.svg';

export interface UseOnboardingMenuOptionsParams {
onOpenOnboarding: () => void;
}

export const useOnboardingMenuOptions = ({
onOpenOnboarding,
}: UseOnboardingMenuOptionsParams) => {
const { t } = useTranslation();

return useMemo<DropdownMenuOption[]>(
() => [
{
label: t('Onboarding'),
icon: <WandAndStarsIcon aria-hidden="true" />,
callback: onOpenOnboarding,
},
],
[onOpenOnboarding, t],
);
};
Loading
Loading