From 3fe8bd5cc0aa250e2513297cdfda34f219b180af Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 23 Jun 2026 17:08:32 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20waffle=20positio?= =?UTF-8?q?ning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The floating waffle menu is positioned only on the bottom left of the trigger button, so when positioned on the bottom left, the floating menu is not visible. This commit fixes the positioning. We also added a patch to the ui-kit, so when the patch is released, we can remove this patch from the codebase. --- .../apps/impress/src/components/Waffle.tsx | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/frontend/apps/impress/src/components/Waffle.tsx b/src/frontend/apps/impress/src/components/Waffle.tsx index ef9e1e18e..d1c9b454a 100644 --- a/src/frontend/apps/impress/src/components/Waffle.tsx +++ b/src/frontend/apps/impress/src/components/Waffle.tsx @@ -1,17 +1,99 @@ import { LaGaufreV2 } from '@gouvfr-lasuite/ui-kit'; -import React from 'react'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { Box } from '@/components'; import { useConfig } from '@/core'; +const SHADOW_HOST_ID = 'lasuite-widget-lagaufre-shadow'; +const BUTTON_CLASS = 'lagaufre-button'; + +/** + * TODO: This is a temporary workaround to fix the placement of the waffle panel. + * @see https://github.com/suitenumerique/ui-kit/pull/260 + */ +const useWafflePanelPlacement = () => { + useEffect(() => { + const observers: MutationObserver[] = []; + + const applyPosition = (shadowRoot: ShadowRoot) => { + const dialog = shadowRoot.querySelector('.wrapper-dialog'); + if (!dialog) { + return; + } + + const button = document.querySelector(`.${BUTTON_CLASS}`); + if (!button) { + return; + } + + const buttonRect = button.getBoundingClientRect(); + const bottom = `${window.innerHeight - buttonRect.top + 10}px`; + const left = `${buttonRect.left}px`; + + if ( + dialog.style.getPropertyValue('bottom') === bottom && + dialog.style.getPropertyValue('left') === left + ) { + return; + } + + dialog.style.setProperty('position', 'fixed', 'important'); + dialog.style.setProperty('top', 'auto', 'important'); + dialog.style.setProperty('bottom', bottom, 'important'); + dialog.style.setProperty('right', 'auto', 'important'); + dialog.style.setProperty('left', left, 'important'); + }; + + const setupPanel = (host: HTMLElement) => { + const { shadowRoot } = host; + if (!shadowRoot) { + return; + } + + const innerObserver = new MutationObserver(() => + applyPosition(shadowRoot), + ); + innerObserver.observe(shadowRoot, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['style'], + }); + observers.push(innerObserver); + }; + + const existing = document.getElementById(SHADOW_HOST_ID); + if (existing) { + setupPanel(existing); + } + + const outerObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (node instanceof HTMLElement && node.id === SHADOW_HOST_ID) { + setupPanel(node); + } + } + } + }); + + outerObserver.observe(document.body, { childList: true }); + observers.push(outerObserver); + + return () => observers.forEach((o) => o.disconnect()); + }, []); +}; + export const Waffle = () => { const { t } = useTranslation(); const { data: conf } = useConfig(); const waffleConfig = conf?.theme_customization?.waffle; + useWafflePanelPlacement(); + if (!waffleConfig?.apiUrl && !waffleConfig?.data) { return null; }