diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts index ae8c6fe4..01e9a86c 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts @@ -302,7 +302,9 @@ test.describe('Doc grid move', () => { await input.click(); await input.fill(titleDoc2); - await expect(page.getByRole('option').getByText(titleDoc2)).toBeVisible(); + await expect( + page.getByRole('option').first().getByText(titleDoc2), + ).toBeVisible(); // Select the first result await page.keyboard.press('Enter'); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index 06855942..4d5a57bd 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -32,7 +32,10 @@ test.describe('Doc Header', () => { 1, ); - const collapseButton = page.getByTestId('floating-bar-toggle-left-panel'); + const cardCollapse = page.locator('.--docs--left-panel-collapse-button'); + const collapseButton = cardCollapse.getByTestId( + 'floating-bar-toggle-left-panel', + ); await expect(collapseButton).toBeVisible(); // Panel open @@ -42,7 +45,7 @@ test.describe('Doc Header', () => { // Collapse panel await collapseButton.click(); await expect(collapseButton).toHaveAttribute('aria-expanded', 'false'); - await expect(collapseButton.getByText(docTitle)).toBeHidden(); + await expect(cardCollapse.getByText(docTitle)).toBeHidden(); // When the title is not visible in the viewport, the button should show the title const editor = await writeInEditor({ page, text: 'Lorem ipsum' }); @@ -50,12 +53,12 @@ test.describe('Doc Header', () => { await editor.press('Enter'); } await writeInEditor({ page, text: 'Lorem ipsum 2' }); - await expect(collapseButton.getByText(docTitle)).toBeVisible(); + await expect(cardCollapse.getByText(docTitle)).toBeVisible(); // Expand panel and check the title is hidden again await collapseButton.click(); await expect(collapseButton).toHaveAttribute('aria-expanded', 'true'); - await expect(collapseButton.getByText(docTitle)).toBeHidden(); + await expect(cardCollapse.getByText(docTitle)).toBeHidden(); }); test('it checks the element are correctly displayed', async ({ diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx index 441e3e44..30c09d9a 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx @@ -45,22 +45,13 @@ export const QuickSearch = ({ }: PropsWithChildren) => { const ref = useRef(null); const listId = useId(); - const NO_SELECTION_VALUE = '__none__'; - const [userInteracted, setUserInteracted] = useState(false); - const [selectedValue, setSelectedValue] = useState(NO_SELECTION_VALUE); - const isExpanded = userInteracted; - - const handleValueChange = (val: string) => { - if (userInteracted) { - setSelectedValue(val); - } - }; - - const handleUserInteract = () => { - if (!userInteracted) { - setUserInteracted(true); - } - }; + /** + * Hack to prevent cmdk from auto-selecting the first element on open + * + * TODO: Find a clean solution to prevent cmdk from auto-selecting + * the first element on open + */ + const [selectedValue, _] = useState('__none__'); return ( <> @@ -71,9 +62,8 @@ export const QuickSearch = ({ shouldFilter={false} ref={ref} tabIndex={-1} - value={selectedValue} - onValueChange={handleValueChange} disablePointerSelection + value={selectedValue} > {showInput && ( {inputContent} diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearchGroup.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearchGroup.tsx index c8eada9e..61be75c9 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearchGroup.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearchGroup.tsx @@ -18,7 +18,7 @@ export const QuickSearchGroup = ({ renderElement, }: Props) => { return ( - + void; isExpanded?: boolean; }; export const QuickSearchInput = ({ @@ -24,8 +23,6 @@ export const QuickSearchInput = ({ children, withSeparator: separator = true, listId, - onUserInteract, - isExpanded, }: PropsWithChildren) => { const { t } = useTranslation(); const { spacingsTokens } = useCunninghamTheme(); @@ -46,19 +43,13 @@ export const QuickSearchInput = ({ $align="center" className="quick-search-input" $gap={spacingsTokens['2xs']} - $padding={{ horizontal: 'base', vertical: 'sm' }} + $padding={{ horizontal: 'base', vertical: 'xxs' }} > - {separator && } + {separator && } ); }; diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx index 5b583ccf..1aba150b 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx @@ -19,7 +19,6 @@ export const QuickSearchStyle = createGlobalStyle` border: none; width: 100%; font-size: 17px; - padding: var(--c--globals--spacings--xs); background: white; outline: none; color: var(--c--contextuals--content--semantic--neutral--primary); diff --git a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx index 0536714e..1fde46d6 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx @@ -1,30 +1,31 @@ import { t } from 'i18next'; -import { useEffect, useMemo } from 'react'; +import { useEffect, useState } from 'react'; import { InView } from 'react-intersection-observer'; +import { Box } from '@/components/'; import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search'; +import { Doc, useInfiniteDocs } from '@/docs/doc-management'; -import { Doc, useInfiniteDocs } from '../../doc-management'; - -import { DocSearchFiltersValues } from './DocSearchFilters'; import { DocSearchItem } from './DocSearchItem'; type DocSearchContentProps = { + groupName: string; search: string; - filters: DocSearchFiltersValues; filterResults?: (doc: Doc) => boolean; + isSearchNotMandatory?: boolean; onSelect: (doc: Doc) => void; onLoadingChange?: (loading: boolean) => void; renderSearchElement?: (doc: Doc) => React.ReactNode; }; export const DocSearchContent = ({ + groupName, search, - filters, filterResults, onSelect, onLoadingChange, renderSearchElement, + isSearchNotMandatory, }: DocSearchContentProps) => { const { data, @@ -35,33 +36,56 @@ export const DocSearchContent = ({ hasNextPage, } = useInfiniteDocs({ page: 1, - title: search, - ...filters, + ...(search ? { title: search } : {}), }); const loading = isFetching || isRefetching || isLoading; + const [docsData, setDocsData] = useState>({ + groupName: '', + groupKey: 'docs', + elements: [], + emptyString: t('Loading documents...'), + endActions: [], + }); + + useEffect(() => { + if (loading) { + return; + } - const docsData: QuickSearchData = useMemo(() => { let docs = data?.pages.flatMap((page) => page.results) || []; if (filterResults) { docs = docs.filter(filterResults); } - return { - groupName: docs.length > 0 ? t('Select a document') : '', + setDocsData({ + groupName: docs.length > 0 ? groupName : '', groupKey: 'docs', - elements: search ? docs : [], + elements: search || isSearchNotMandatory ? docs : [], emptyString: t('No document found'), endActions: hasNextPage ? [ { - content: void fetchNextPage()} />, + content: ( + + void fetchNextPage()} /> + + ), }, ] : [], - }; - }, [search, data?.pages, fetchNextPage, hasNextPage, filterResults]); + }); + }, [ + search, + data?.pages, + fetchNextPage, + hasNextPage, + filterResults, + groupName, + isSearchNotMandatory, + loading, + ]); useEffect(() => { onLoadingChange?.(loading); diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx index 37564668..25aca5aa 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx @@ -5,17 +5,15 @@ import { useModal, } from '@gouvfr-lasuite/cunningham-react'; import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit'; -import Image from 'next/image'; import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { createGlobalStyle, css } from 'styled-components'; import { useDebouncedCallback } from 'use-debounce'; -import { Box, ButtonCloseModal, Text } from '@/components'; +import { Box, ButtonCloseModal, HorizontalSeparator, Text } from '@/components'; import { QuickSearch } from '@/components/quick-search'; import { Doc, useMoveDoc, useTrans } from '@/docs/doc-management'; -import { DocSearchContent, DocSearchTarget } from '@/docs/doc-search'; -import EmptySearchIcon from '@/docs/doc-search/assets/illustration-docs-empty.png'; +import { DocSearchContent } from '@/docs/doc-search'; import { AlertModalRequestAccess } from '@/docs/doc-share'; import { useResponsiveStore } from '@/stores'; @@ -35,20 +33,17 @@ export const DocMoveModalStyle = createGlobalStyle` overflow-y: auto; } - .c__modal__title { - padding-inline: var(--c--globals--spacings--md); - padding-block: var(--c--globals--spacings--base); - border-bottom: 1px solid var(--c--contextuals--border--surface--primary); + div.c__modal__title { + padding: 0; + margin-bottom: 0; } .c__modal__footer { margin-top: 0rem; + border-top: 1px solid var(--c--contextuals--border--surface--primary); } .quick-search-input{ padding-inline: var(--c--globals--spacings--md); } - .c__modal__footer{ - border-top: 1px solid var(--c--contextuals--border--surface--primary); - } .quick-search-container [cmdk-item] { border-radius: 4px; } @@ -79,7 +74,8 @@ export const DocMoveModal = ({ const modalRequest = useModal(); const { mutate: moveDoc } = useMoveDoc(true); const [search, setSearch] = useState(''); - const { isDesktop } = useResponsiveStore(); + const { isDesktop, isTablet, isMobile } = useResponsiveStore(); + const isModal = (isDesktop || isTablet) && !isMobile; const handleInputSearch = useDebouncedCallback(setSearch, 700); const handleSelect = (docSelected: Doc) => { @@ -106,11 +102,15 @@ export const DocMoveModal = ({ isOpen={isOpen} onClose={onClose} closeOnClickOutside - size={isDesktop ? ModalSize.LARGE : ModalSize.FULL} + size={isModal ? ModalSize.MEDIUM : ModalSize.FULL} hideCloseButton aria-label={t('Move Modal')} rightActions={ - + - + > + {shouldShowButtonTitle && ( + + {buttonTitle} + + )} + ); };