From 33ca348b0298e640bc6d953a52a05b2755bf78b5 Mon Sep 17 00:00:00 2001 From: Nathan Panchout Date: Tue, 30 Jun 2026 16:04:09 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20open=20and=20share=20a=20?= =?UTF-8?q?presentation=20at=20a=20given=20slide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support ?view=present&slide=N deep-links (clamped, URL kept in sync) and a "Copy link to slide" action in the floating bar. Ignore the deep-link on mobile. Closes #2397 --- .../components/PresenterFloatingBar.tsx | 173 ++++++++++++------ .../components/PresenterOverlay.tsx | 27 ++- .../components/PresenterRoot.tsx | 58 +++++- .../hooks/useCopyPresenterLink.tsx | 26 +++ 4 files changed, 224 insertions(+), 60 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-presenter/hooks/useCopyPresenterLink.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx index 5544a01b2..b4421eff9 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx @@ -1,14 +1,17 @@ import { Button } from '@gouvfr-lasuite/cunningham-react'; +import { DropdownMenu, DropdownMenuItem } from '@gouvfr-lasuite/ui-kit'; import { ChevronLeft, ChevronRight, + Link, Maximize, Minimize, + Share, XMark, } from '@gouvfr-lasuite/ui-kit/icons'; -import { useEffect, useRef } from 'react'; +import { MouseEvent, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { css } from 'styled-components'; +import { createGlobalStyle, css } from 'styled-components'; import { Box, Text } from '@/components'; @@ -18,13 +21,14 @@ interface PresenterFloatingBarProps { isFullscreen: boolean; onPrev: () => void; onNext: () => void; + onCopyLink: () => void; onToggleFullscreen: () => void; onClose: () => void; } const barCss = css` position: fixed; - bottom: 1.5rem; + bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 1; @@ -45,7 +49,28 @@ const separatorCss = css` width: 1px; height: 1.25rem; background: var(--c--theme--colors--greyscale-200, #e5e5e5); - margin: 0 0.25rem; +`; + +// The ui-kit DropdownMenu renders its popover in a React Aria portal at the +// document root, outside the presenter overlay's stacking context — so its +// z-index (and width) can only be raised above the overlay via a global style. +// Mounted only while the menu is open (see render below). +const presenterDropdownLayerZIndex = 1002; + +const PresenterDropdownLayerStyle = createGlobalStyle` + .react-aria-Popover { + z-index: ${presenterDropdownLayerZIndex}; + } + + .react-aria-Popover .c__dropdown-menu--tiny { + width: 150px; + min-width: 150px; + } + + .react-aria-Popover .c__dropdown-menu--tiny .c__dropdown-menu-item { + box-sizing: border-box; + height: 32px; + } `; export const PresenterFloatingBar = ({ @@ -54,12 +79,14 @@ export const PresenterFloatingBar = ({ isFullscreen, onPrev, onNext, + onCopyLink, onToggleFullscreen, onClose, }: PresenterFloatingBarProps) => { const { t } = useTranslation(); const isFirst = index <= 0; const isLast = index >= total - 1; + const [isActionsOpen, setIsActionsOpen] = useState(false); // Move focus into the dialog on open so keyboard users land on the // controls (an ARIA dialog must move focus inside itself; here on the @@ -76,53 +103,97 @@ export const PresenterFloatingBar = ({ return () => cancelAnimationFrame(id); }, []); + // The DropdownMenu is controlled (isOpen/onOpenChange); stop the click from + // also reaching the overlay so the menu toggles cleanly. + const toggleActions = (event: MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setIsActionsOpen((isOpen) => !isOpen); + }; + + const actionOptions = useMemo( + () => [ + { + label: t('Copy link to slide'), + icon: , + callback: onCopyLink, + }, + ], + [onCopyLink, t], + ); + return ( - -