Skip to content
Open
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
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@ and this project adheres to
- ✨(buildpack) add PaaS deployment support, tested with Scalingo #2293
- 🔧(backend) allow configuring settings OIDC_OP_USER_ENDPOINT_FORMAT
- ⚡️(helm) create a dedicated svc and deployment for yprovider converter #2368
- ✨(frontend) add the presenter mode

### Changed

- ♻️(backend) allow global search in sub documents
- ✨(backend) add a breadcrumb in the search response
- ♿️(frontend) add aria-hidden to decorative avatar SVGs in share modal #2324

### Fixed

- 🐛(docs) run migration 0027 without superuser role
- 🐛(backend) prevent admins/owners from overwriting other users comments
- 🐛(backend) use computed_link_reach in handle_onboarding_document #2305

### Changed

- ♿️(frontend) add aria-hidden to decorative avatar SVGs in share modal #2324

## [v5.1.0] - 2026-05-11

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

import { createDoc, goToGridDoc, mockedDocument } from './utils-common';
import { openSuggestionMenu, writeInEditor } from './utils-editor';

const openPresenter = async (page: Page) => {
await page.getByLabel('Open the document options').click();
await page.getByRole('menuitem', { name: 'Present' }).click();

const overlay = page.getByRole('dialog', { name: 'Presenter mode' });
await expect(overlay).toBeVisible();
return overlay;
};

const insertDivider = async (page: Page) => {
const { suggestionMenu } = await openSuggestionMenu({ page });
await suggestionMenu.getByText('Divider', { exact: true }).click();
};

const writeMultiSlideDoc = async (page: Page) => {
const editor = await writeInEditor({ page, text: 'Slide one' });
await editor.press('Enter');
await insertDivider(page);
await editor.press('Enter');
await writeInEditor({ page, text: 'Slide two' });
await editor.press('Enter');
await insertDivider(page);
await editor.press('Enter');
await writeInEditor({ page, text: 'Slide three' });
};

test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test.describe('Presenter Mode', () => {
test('opens the presenter overlay from the doc options menu and closes with Escape', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-open', browserName, 1);
await writeInEditor({ page, text: 'Hello presenter' });

const overlay = await openPresenter(page);

await expect(
overlay.getByRole('toolbar', { name: 'Presenter controls' }),
).toBeVisible();
await expect(overlay.getByText('Hello presenter')).toBeVisible();

// The presenter calls requestFullscreen on open. usePresenterShortcuts
// deliberately ignores Escape while in fullscreen (the browser owns Esc
// there to exit). Playwright grants requestFullscreen but, unlike a real
// browser, does NOT dispatch a fullscreen exit on Escape — so we must
// exit fullscreen ourselves before pressing Escape to test the close
// path. We also wait for the React state to settle after the exit so
// the keydown listener is re-bound with isFullscreen=false.
await page.evaluate(async () => {
if (document.fullscreenElement) {
await document.exitFullscreen();
}
});
await page.waitForTimeout(200);
await page.keyboard.press('Escape');
Comment on lines +58 to +64
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace fixed 200ms sleep with a deterministic fullscreen/state wait.

waitForTimeout(200) is flaky in CI and can race with React state updates. Wait for fullscreen exit/state observable condition instead.

💡 Suggested fix
     await page.evaluate(async () => {
       if (document.fullscreenElement) {
         await document.exitFullscreen();
       }
     });
-    await page.waitForTimeout(200);
+    await page.waitForFunction(() => document.fullscreenElement === null);
+    await expect(
+      overlay.getByRole('button', { name: 'Enter fullscreen' }),
+    ).toBeVisible();
     await page.keyboard.press('Escape');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await page.evaluate(async () => {
if (document.fullscreenElement) {
await document.exitFullscreen();
}
});
await page.waitForTimeout(200);
await page.keyboard.press('Escape');
await page.evaluate(async () => {
if (document.fullscreenElement) {
await document.exitFullscreen();
}
});
await page.waitForFunction(() => document.fullscreenElement === null);
await expect(
overlay.getByRole('button', { name: 'Enter fullscreen' }),
).toBeVisible();
await page.keyboard.press('Escape');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/e2e/__tests__/app-impress/presenter-mode.spec.ts` around
lines 58 - 64, The test currently uses a fixed sleep (page.waitForTimeout(200))
after calling document.exitFullscreen(), which is flaky; replace that sleep with
a deterministic wait that observes fullscreen state instead. After the
page.evaluate block that checks document.fullscreenElement and calls
document.exitFullscreen(), remove page.waitForTimeout and add a wait using
Playwright's waiting primitive (e.g., page.waitForFunction or equivalent) to
wait until document.fullscreenElement is null (or until the app-specific
presenter/fullscreen DOM state updates), then proceed to
page.keyboard.press('Escape').

await expect(overlay).toBeHidden();
});

test('renders a single-slide doc with counter 1/1 and disabled nav buttons', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-single', browserName, 1);
await writeInEditor({ page, text: 'Slide A' });

const overlay = await openPresenter(page);

await expect(overlay.getByText('1 / 1')).toBeVisible();
await expect(
overlay.getByRole('button', { name: 'Previous slide' }),
).toBeDisabled();
await expect(
overlay.getByRole('button', { name: 'Next slide' }),
).toBeDisabled();
await expect(overlay.getByText('Slide A')).toBeVisible();

await overlay.getByRole('button', { name: 'Close presenter' }).click();
await expect(overlay).toBeHidden();
});

test('navigates between slides via the floating bar buttons', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-nav-bar', browserName, 1);
await writeMultiSlideDoc(page);

const overlay = await openPresenter(page);

const prev = overlay.getByRole('button', { name: 'Previous slide' });
const next = overlay.getByRole('button', { name: 'Next slide' });

await expect(overlay.getByText('1 / 3')).toBeVisible();
await expect(overlay.getByText('Slide one')).toBeVisible();
await expect(prev).toBeDisabled();
await expect(next).toBeEnabled();

await next.click();
await expect(overlay.getByText('2 / 3')).toBeVisible();
await expect(overlay.getByText('Slide two')).toBeVisible();

await next.click();
await expect(overlay.getByText('3 / 3')).toBeVisible();
await expect(overlay.getByText('Slide three')).toBeVisible();
await expect(next).toBeDisabled();
await expect(prev).toBeEnabled();

await prev.click();
await expect(overlay.getByText('2 / 3')).toBeVisible();
await expect(overlay.getByText('Slide two')).toBeVisible();
});

test('navigates between slides via keyboard shortcuts', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-nav-keyboard', browserName, 1);
await writeMultiSlideDoc(page);

const overlay = await openPresenter(page);

await expect(overlay.getByText('1 / 3')).toBeVisible();

await page.keyboard.press('ArrowRight');
await expect(overlay.getByText('2 / 3')).toBeVisible();

await page.keyboard.press('End');
await expect(overlay.getByText('3 / 3')).toBeVisible();

await page.keyboard.press('Home');
await expect(overlay.getByText('1 / 3')).toBeVisible();

// ArrowLeft on the first slide is clamped — counter stays at 1 / 3.
await page.keyboard.press('ArrowLeft');
await expect(overlay.getByText('1 / 3')).toBeVisible();
});

test('scales each slide to fit the viewport (outer width = 900 × scale)', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-scaling', browserName, 1);
await writeInEditor({ page, text: 'A short slide' });

const overlay = await openPresenter(page);
const currentSlide = overlay.getByRole('group').filter({ hasNotText: '' });
await expect(currentSlide.first()).toBeVisible();

const dims = await currentSlide.first().evaluate((el) => {
// DOM: <outer role="group"><stage><inner (scaled)>...
const stage = el.firstElementChild as HTMLElement | null;
const inner = stage?.firstElementChild as HTMLElement | null;
const innerStyle = inner ? getComputedStyle(inner) : null;
return {
outerWidth: el.getBoundingClientRect().width,
innerTransform: innerStyle?.transform ?? 'none',
};
});

// Inner has `transform: scale(<n>)`; match the first scale matrix value.
const m = /matrix\(([-\d.]+)/.exec(dims.innerTransform);
expect(
m,
`expected a scale matrix, got: ${dims.innerTransform}`,
).not.toBeNull();

const scale = m ? parseFloat(m[1]) : NaN;

// Scale is always clamped to [MIN_SCALE, MAX_SCALE] = [0.7, 1.5].
// The exact value depends on viewport: sparse content typically saturates
// the height-based scale (→ MAX), but at default Playwright viewport
// (1280) the width path can constrain to scaleW = (1280 - 2*paddingX)/900.
// We assert the bounds, not a specific value.
expect(scale).toBeGreaterThanOrEqual(0.7);
expect(scale).toBeLessThanOrEqual(1.5);

// The core invariant: outer width = DESIGN_WIDTH (900) × scale,
// within a 5px tolerance for sub-pixel rounding.
expect(Math.abs(dims.outerWidth - 900 * scale)).toBeLessThan(5);
Comment on lines +186 to +188
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the design-width constant value used by the scaler.
fd -t f 'constants.ts' --full-path doc-presenter --exec rg -nP 'DESIGN_WIDTH|WINDOW_RADIUS|MIN_SCALE|MAX_SCALE|PADDING' {}
rg -nP 'DESIGN_WIDTH' src/frontend/apps/impress/src/features/docs/doc-presenter

Repository: suitenumerique/docs

Length of output: 834


🏁 Script executed:

#!/bin/bash
# Inspect the assertion in the spec and look for 800/900 and constant usage.
sed -n '160,210p' src/frontend/apps/e2e/__tests__/app-impress/presenter-mode.spec.ts

rg -n "800|900|PRESENTER_SLIDE_DESIGN_WIDTH|DESIGN_WIDTH" src/frontend/apps/e2e/__tests__/app-impress/presenter-mode.spec.ts

# Show the constant definition to confirm the source of truth.
sed -n '1,80p' src/frontend/apps/impress/src/features/docs/doc-presenter/constants.ts

Repository: suitenumerique/docs

Length of output: 3931


Hard-coded 900 matches the source design width, but avoid duplicating the literal. The spec’s invariant uses 900 * scale, and the source-of-truth constant PRESENTER_SLIDE_DESIGN_WIDTH is set to 900, so the assertion isn’t inconsistent with the implementation. Prefer importing PRESENTER_SLIDE_DESIGN_WIDTH in the e2e test instead of duplicating 900 for future-proofing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/e2e/__tests__/app-impress/presenter-mode.spec.ts` around
lines 186 - 188, The test hard-codes 900 for the slide design width; import and
use the source-of-truth constant PRESENTER_SLIDE_DESIGN_WIDTH instead of the
literal so the assertion uses PRESENTER_SLIDE_DESIGN_WIDTH * scale; update the
assertion in presenter-mode.spec.ts (where dims.outerWidth is compared) to
reference PRESENTER_SLIDE_DESIGN_WIDTH and ensure the import is added at the top
of the test file.

});

test('tall slide produces a vertical scrollbar on the outer wrapper with the top visible', async ({
page,
browserName,
}) => {
await createDoc(page, 'presenter-tall', browserName, 1);

// Build a tall single-slide doc: many headings + paragraphs so the
// natural content height blows past viewport height at MIN_SCALE.
const editor = await writeInEditor({ page, text: 'TOP MARKER' });
for (let i = 0; i < 40; i += 1) {
await editor.press('Enter');
await editor.pressSequentially(`Filler line ${i} to make the slide tall`);
}

const overlay = await openPresenter(page);
const slide = overlay.getByRole('group').filter({ hasNotText: '' }).first();
await expect(slide).toBeVisible();

// The first block ('TOP MARKER') must be at y=0 of the slide wrapper
// (i.e. visible at the top, not clipped). This is the regression we fix.
const topVisible = await slide.evaluate((el) => {
el.scrollTop = 0;
const first = el.querySelector('.bn-block-content');
if (!first) {
return { ok: false, reason: 'no first block' };
}
const slideRect = el.getBoundingClientRect();
const blockRect = first.getBoundingClientRect();
return {
ok: blockRect.top >= slideRect.top - 1,
slideTop: slideRect.top,
blockTop: blockRect.top,
scrollHeight: el.scrollHeight,
clientHeight: el.clientHeight,
};
});

expect(topVisible.ok, JSON.stringify(topVisible)).toBe(true);
expect(topVisible.scrollHeight ?? 0).toBeGreaterThan(
topVisible.clientHeight ?? 0,
);
});
});

test.describe('Presenter Mode mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });

test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test('hides the Present option on small mobile viewports', async ({
page,
}) => {
await mockedDocument(page, {
abilities: {
destroy: true,
link_configuration: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
accesses_manage: true,
accesses_view: true,
update: true,
partial_update: true,
retrieve: true,
},
});

await goToGridDoc(page);

await page.getByLabel('Open the document options').click();
await expect(page.getByRole('menuitem', { name: 'Present' })).toBeHidden();
});
});
10 changes: 7 additions & 3 deletions src/frontend/apps/impress/src/components/modal/SideModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Modal, ModalSize } from '@gouvfr-lasuite/cunningham-react';
import { ComponentPropsWithRef, PropsWithChildren } from 'react';
import {
Modal,
ModalDefaultVariantProps,
ModalSize,
} from '@gouvfr-lasuite/cunningham-react';
import { PropsWithChildren } from 'react';
import { createGlobalStyle } from 'styled-components';

interface SideModalStyleProps {
Expand Down Expand Up @@ -35,7 +39,7 @@ const SideModalStyle = createGlobalStyle<SideModalStyleProps>`
}
`;

type SideModalType = Omit<ComponentPropsWithRef<typeof Modal>, 'size'>;
type SideModalType = Omit<ModalDefaultVariantProps, 'size'>;

type SideModalProps = SideModalType & Partial<SideModalStyleProps>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button, useModal } from '@gouvfr-lasuite/cunningham-react';
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
import { Present } from '@gouvfr-lasuite/ui-kit/icons';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { useState } from 'react';
Expand Down Expand Up @@ -79,6 +80,14 @@ const ModalExport =
)
: null;

const PresenterOverlay = dynamic(
() =>
import('@/docs/doc-presenter').then((mod) => ({
default: mod.PresenterOverlay,
})),
{ ssr: false },
);

interface DocToolBoxProps {
doc: Doc;
}
Expand All @@ -93,6 +102,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {

const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const [isPresenterOpen, setIsPresenterOpen] = useState(false);
const selectHistoryModal = useModal();
const modalShare = useModal();

Expand Down Expand Up @@ -176,6 +186,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
showSeparator: true,
show: !emoji && doc.abilities.partial_update && !isTopRoot,
},
{
label: t('Present'),
icon: <Present />,
callback: () => {
setIsPresenterOpen(true);
},
show: !doc.deleted_at && !isSmallMobile,
testId: `docs-actions-present-${doc.id}`,
},
Comment on lines +189 to +197
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

@gouvfr-lasuite/ui-kit icons component props width height aria-hidden

💡 Result:

I couldn’t find any authoritative, component-level documentation for a dedicated “Icon” component inside @gouvfr-lasuite/ui-kit that explicitly lists props named width, height, or aria-hidden. What I can support from sources: 1) @gouvfr-lasuite/ui-kit does include an “Icon” component (described as “Material icons”), but the sources retrieved here don’t show its prop types or a public API table for width/height/aria-hidden. [1][2] 2) For accessibility, a common and standards-backed rule is that decorative icons should be hidden from the accessibility tree using aria-hidden="true". This behavior is described by MDN: aria-hidden="true" removes the element (and its children) from the accessibility tree. [3] Practical answer (what you should do, given the missing API props evidence): - Use aria-hidden="true" on purely decorative icons (e.g., SVG/Material icon spans used only for decoration), per MDN guidance on aria-hidden. [3] - For width/height: since no @gouvfr-lasuite/ui-kit prop evidence was found, size the icon via CSS (e.g., setting font-size for Material Icons or applying width/height to the underlying SVG/element) or via whatever size prop is actually implemented by the library (not found in the retrieved sources). If you paste the actual TS/React code snippet you’re using with the Icon component (or the Icon component import path, e.g. where Icon is exported from in @gouvfr-lasuite/ui-kit), I can run a more targeted lookup for the exact prop names and confirm whether width/height are supported as component props or only via styling.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact surrounding code in DocToolBox.tsx
FILE="src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx"
if [ -f "$FILE" ]; then
  echo "=== $FILE (lines around 175-215) ==="
  sed -n '165,215p' "$FILE" | nl -ba | sed 's/^/ /'
else
  echo "Missing file: $FILE"
fi

# 2) Find the import source for Present and other icons in this file
echo "=== Imports in $FILE (top part) ==="
sed -n '1,120p' "$FILE" | nl -ba | sed 's/^/ /'

# 3) Locate where Present is used/imported elsewhere in the repo
echo "=== Repo-wide search for Present icon usage ==="
rg -n "Present\s*[>,)]|<Present\b|from ['\"]`@gouvfr-lasuite/ui-kit/icons`['\"][^;]*|`@gouvfr-lasuite/ui-kit/icons`" -S .

# 4) If node_modules is present in the sandbox, inspect ui-kit types/implementation for Present
if [ -d "node_modules/@gouvfr-lasuite/ui-kit" ]; then
  echo "=== node_modules/@gouvfr-lasuite/ui-kit present types/implementation ==="
  # Try to find Present export
  rg -n "export (const|function|class) Present|Present\s*:" node_modules/@gouvfr-lasuite/ui-kit -S || true
  # Try to find icon component prop types for width/height/aria-hidden
  rg -n "aria-hidden|width\s*\?|height\s*\?" node_modules/@gouvfr-lasuite/ui-kit -S || true
else
  echo "node_modules/@gouvfr-lasuite/ui-kit not present in this sandbox."
fi

Repository: suitenumerique/docs

Length of output: 236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx"

echo "=== Verify file exists ==="
ls -la "$FILE" 2>/dev/null || { echo "Missing file: $FILE"; exit 1; }

echo "=== $FILE (lines 175-215) ==="
sed -n '175,215p' "$FILE" | cat -n

echo "=== $FILE imports (lines 1-120) ==="
sed -n '1,120p' "$FILE" | cat -n

echo "=== Repo-wide search: where Present is imported/used ==="
rg -n --hidden --glob '!.git/*' --glob '!**/dist/**' --glob '!**/build/**' \
  "from ['\"]`@gouvfr-lasuite/ui-kit/icons`['\"][^;]*Present|<Present\b|Present\s*\(" .

echo "=== Repo-wide search: icon size/accessibility patterns used elsewhere ==="
rg -n --hidden --glob '!.git/*' --glob '!**/dist/**' --glob '!**/build/**' \
  "width=\{24\}\s*height=\{24\}|aria-hidden=\{?\"true\"\\}?" \
  src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx

# Inspect node_modules types/impl if available (best-effort)
if [ -d "node_modules/@gouvfr-lasuite/ui-kit" ]; then
  echo "=== node_modules/@gouvfr-lasuite/ui-kit exists; searching for Present ==="
  rg -n "Present" node_modules/@gouvfr-lasuite/ui-kit -S | head -n 50 || true
  rg -n "export .*Present|Present\s*:" node_modules/@gouvfr-lasuite/ui-kit -S | head -n 50 || true
else
  echo "=== node_modules/@gouvfr-lasuite/ui-kit not present ==="
fi

Repository: suitenumerique/docs

Length of output: 7833


🌐 Web query:

@gouvfr-lasuite/ui-kit/icons Present width height aria-hidden

💡 Result:

I couldn’t find any authoritative, source-backed documentation stating specific default width/height values for the icons provided by @gouvfr-lasuite/ui-kit/icons. What I can confirm from the official accessibility guidance that relates to the “aria-hidden” part: - For decorative icons, using aria-hidden="true" is recommended; aria-hidden="true" removes the element (and its children) from the accessibility tree [1]. - If an icon conveys meaning or duplicates/overlaps accessible text, aria-hidden should be overridden (or not used) so assistive tech users aren’t misled [1]. So, for the query “Present width height aria-hidden”: - aria-hidden: set aria-hidden="true" when the icon is purely decorative; otherwise provide an accessible name/label and don’t hide it [1]. - width/height: the sources found do not specify a fixed width/height for @gouvfr-lasuite/ui-kit/icons; you likely need to refer to the actual Icon component/props (or the underlying SVG/icon size utilities) in that repository, which weren’t surfaced by the searches I ran. If you share the exact component name or the code snippet you’re using (e.g., the React <Icon...> usage or the SVG markup), I can pinpoint the exact default width/height behavior and whether aria-hidden is set by default in that implementation.

Citations:


🌐 Web query:

site:npmjs.com @gouvfr-lasuite/ui-kit/icons Present

💡 Result:

There is no public npm package named @gouvfr-lasuite/ui-kit/icons. The French State Design System (DSFR - Système de Design de l'État) provides its official icons and pictograms through the primary package @gouvfr/dsfr [1]. In this package, icons are located within the dist/icons directory of the @gouvfr/dsfr distribution [1][2]. To use them, developers typically: 1. Include the necessary CSS files from dist/utility/icons/ (e.g., icons-system.min.css) [1][2]. 2. Reference the icons using standard CSS classes as described in the official DSFR documentation [1][2]. If you are working with specific framework integrations, you may be using community-maintained wrappers such as @codegouvfr/react-dsfr [3] or @betagouv/ngx-dsfr [2], which provide their own methods for handling DSFR assets and components. Please verify your dependency name or consult the official Systeme de Design documentation for the most accurate implementation details.

Citations:


🌐 Web query:

site:github.com @gouvfr-lasuite/ui-kit Present icon component props width height aria-hidden

💡 Result:

I couldn’t find any public GitHub repository/package named @gouvfr-lasuite/ui-kit, nor any “Present” icon React component exposing width, height, or aria-hidden props.

What is public under the La Suite numérique GitHub org is suitenumerique/cunningham (their design system). Its React package is @gouvfr-lasuite/cunningham-react, and icons are imported as CSS “Material Icons” (not as per-icon React components), e.g. via:

  • @import "@gouvfr-lasuite/cunningham-react/icons"; (github.com)

So, in the public codebase I can access, there isn’t a “Present” icon component with width/height/aria-hidden props to document.

If you share the exact GitHub repo URL (or the file path for the icon component), I can extract the precise props from the source.

Citations:


🌐 Web query:

"export" "Present" "@gouvfr-lasuite/ui-kit" "icons"

💡 Result:

I can’t find (in the sources I retrieved) any documentation or code that matches the exact query phrase “export Present @gouvfr-lasuite/ui-kit icons”. What I can confirm is how icons are provided/used by the La Suite UI Kit package. 1) Icons in @gouvfr-lasuite/ui-kit - The npm package @gouvfr-lasuite/ui-kit includes an Icon component described as “Material icons” [1]. - The examples show importing @gouvfr-lasuite/ui-kit “style” separately, e.g. import "@gouvfr-lasuite/ui-kit/style" [1][2]. 2) Where to look in the upstream repo - The ui-kit GitHub repository has an icon/ folder, which is likely where the icon assets/components live [2]. 3) About “export” and “Present” in your query - In the sources retrieved, “export” appears only in the generic sense of importing modules or exporting dist assets for the separate @gouvfr-lasuite/integration package, not for an “export Present … icons” pattern tied to @gouvfr-lasuite/ui-kit [3][4]. - “Present” is not found as part of an @gouvfr-lasuite/ui-kit icon export/usage specification in the retrieved results. If you paste the exact file/snippet you’re trying to interpret (or the error message you’re seeing), I can do a targeted search for the specific “Present”/“export” usage within the ui-kit codebase (e.g., whether it’s a React component, an icon name, a barrel export in an index.ts, etc.).

Citations:


Make the Present dropdown icon consistent (24×24 + decorative a11y)

In DocToolBox.tsx, every other dropdown option icon is rendered with width={24} height={24} aria-hidden="true", but Present is currently icon: <Present />. Update Present so it’s sized to 24×24 and hidden from assistive tech as a decorative icon (using the ui-kit’s supported API or by applying equivalent attributes to the rendered element).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx`
around lines 189 - 197, The Present icon in DocToolBox.tsx is inconsistent with
other dropdown icons: replace the raw <Present /> used in the menu item (the
object whose callback calls setIsPresenterOpen) with the ui-kit-styled version
used elsewhere — e.g. render Present with width={24} height={24} and
aria-hidden="true" (or use the ui-kit Icon wrapper API if available) so the
dropdown entry matches other icons and is marked decorative for assistive tech;
keep the rest of the menu item (show, testId, callback) unchanged.

{
label: t('Copy link'),
icon: <AddLinkSVG width={24} height={24} aria-hidden="true" />,
Expand Down Expand Up @@ -320,6 +339,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
doc={doc}
/>
)}
{isPresenterOpen && (
<PresenterOverlay
doc={doc}
onClose={() => {
setIsPresenterOpen(false);
restoreFocus();
}}
/>
)}
</Box>
);
};
Loading
Loading