diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f3f0f33..dd95d760b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to - 🚸(frontend) redirect on current url tab after 401 #2197 - 🐛(frontend) abort check media status unmount #2194 - ✨(backend) order pinned documents by last updated at #2028 +- 🐛(frontend) fix interlinking modal clipping #2213 - 🛂(frontend) fix cannot manage member on small screen #2226 - 🐛(backend) load jwks url when OIDC_RS_PRIVATE_KEY_STR is set diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index 06de7ee26..655d3bfa8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -4,8 +4,9 @@ import { } from '@blocknote/core'; import { useBlockNoteEditor } from '@blocknote/react'; import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; +import { Popover } from '@mantine/core'; import type { KeyboardEvent } from 'react'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useId, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -90,18 +91,27 @@ export const SearchPage = ({ const { untitledDocument } = useTrans(); const isEditable = editor.isEditable; const treeContext = useTreeContext(); + const modalRef = useRef(null); + const dropdownId = useId(); + const [popoverOpened, setPopoverOpened] = useState(false); + /** * createReactInlineContentSpec add automatically the focus after * the inline content, so we need to set the focus on the input * after the component is mounted. + * We also defer opening the popover to after mount so that + * floating-ui attaches scroll/resize listeners correctly. */ useEffect(() => { - setTimeout(() => { + const timeoutId = setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); } + setPopoverOpened(true); }, 100); - }, [inputRef]); + + return () => clearTimeout(timeoutId); + }, []); const closeSearch = (insertContent: string) => { if (!isEditable) { @@ -131,9 +141,7 @@ export const SearchPage = ({ closeSearch(''); } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { // Allow arrow keys to be handled by the command menu for navigation - const commandList = e.currentTarget - .closest('.inline-content') - ?.nextElementSibling?.querySelector('[cmdk-list]'); + const commandList = modalRef.current?.querySelector('[cmdk-list]'); // Create a synthetic keyboard event for the command menu const syntheticEvent = new KeyboardEvent('keydown', { @@ -145,11 +153,9 @@ export const SearchPage = ({ e.preventDefault(); } else if (e.key === 'Enter') { // Handle Enter key to select the currently highlighted item - const selectedItem = e.currentTarget - .closest('.inline-content') - ?.nextElementSibling?.querySelector( - '[cmdk-item][data-selected="true"]', - ) as HTMLElement; + const selectedItem = modalRef.current?.querySelector( + '[cmdk-item][data-selected="true"]', + ) as HTMLElement; selectedItem?.click(); e.preventDefault(); @@ -158,204 +164,236 @@ export const SearchPage = ({ return ( - - {' '} - {trigger} - { - const value = (e.target as HTMLInputElement).value; - setSearch(value); - }} - onKeyDown={handleKeyDown} - autoComplete="off" - /> - - - - + + {' '} + + { + const value = (e.target as HTMLInputElement).value; + setSearch(value); + }} + onKeyDown={handleKeyDown} + autoComplete="off" + /> + + + + div { - margin-top: var(--c--globals--spacings--0); - & [cmdk-group-heading] { - padding: 0.4rem; - margin: 0; - } + position: relative; - & [cmdk-group-items] .ml-b { - margin-left: 0rem; - padding: 0.5rem; - font-size: 14px; - display: block; - } + .mantine-Popover-dropdown[data-position='bottom'] & { + top: -10px; + } + .mantine-Popover-dropdown[data-position='top'] & { + top: 10px; + } - & [cmdk-item] { - border-radius: 0; - } - - & .--docs--doc-search-item > div { - gap: 0.8rem; - } + & .quick-search-container [cmdk-root] { + border-radius: inherit; } `} - $margin={{ top: '0.5rem' }} > - { - if (!isEditable) { - return; - } - - updateInlineContent({ - type: 'interlinkingSearchInline', - props: { - disabled: true, - trigger, - }, - }); - - contentRef(null); - - editor.insertInlineContent([ - { - type: 'interlinkingLinkInline', - props: { - docId: doc.id, - title: doc.title || untitledDocument, - }, - }, - ]); - - editor.focus(); - }} - renderSearchElement={(doc) => { - const { emoji, titleWithoutEmoji } = getEmojiAndTitle( - doc.title || untitledDocument, - ); - - return ( - - - {emoji ? ( - {emoji} - ) : ( - - )} - - - - {titleWithoutEmoji} - - + + div { + margin-top: var(--c--globals--spacings--0); + & [cmdk-group-heading] { + padding: 0.4rem; + margin: 0; } - right={ - + + & [cmdk-group-items] .ml-b { + margin-left: 0rem; + padding: 0.5rem; + font-size: 14px; + display: block; } - /> - ); - }} - /> - - - - div { + gap: 0.8rem; + } + } + `} + $margin={{ top: '0.5rem' }} + > + { + if (!isEditable) { + return; + } + + updateInlineContent({ + type: 'interlinkingSearchInline', + props: { + disabled: true, + trigger, + }, + }); + + contentRef(null); + + editor.insertInlineContent([ + { + type: 'interlinkingLinkInline', + props: { + docId: doc.id, + title: doc.title || untitledDocument, + }, + }, + ]); + + editor.focus(); + }} + renderSearchElement={(doc) => { + const { emoji, titleWithoutEmoji } = getEmojiAndTitle( + doc.title || untitledDocument, + ); + + return ( + - {t('New sub-doc')} - - - - ), - }, - ], - }} - /> - - - + + {emoji ? ( + {emoji} + ) : ( + + )} + + + + {titleWithoutEmoji} + + + } + right={ + + } + /> + ); + }} + /> + + + + + {t('New sub-doc')} + + + + ), + }, + ], + }} + /> + + + + + ); };