From 7edbd4efcbe5765632f1e45582bb4e8fadf34f2b Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 5 Jun 2026 11:04:49 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84(frontend)=20update=20search=20ui?= =?UTF-8?q?=20to=20fit=20the=20design=20mockup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The search ui has been updated to fit the design mockup. It includes: - the search feature - the move doc modal - the interlinking search part --- .../__tests__/app-impress/doc-editor.spec.ts | 1 - .../app-impress/doc-grid-move.spec.ts | 12 ++- .../__tests__/app-impress/doc-search.spec.ts | 37 ++++++--- .../icons/ui-kit/arrow-corner-down-right.svg | 6 ++ .../impress/src/assets/icons/ui-kit/enter.svg | 6 ++ .../assets/icons/ui-kit/keyboard_return.svg | 7 +- .../icons/ui-kit/subdirectory_arrow_right.svg | 7 +- .../src/components/filter/FilterDropdown.tsx | 2 +- .../quick-search/QuickSearchGroup.tsx | 6 +- .../quick-search/QuickSearchInput.tsx | 13 ++- .../quick-search/QuickSearchItemContent.tsx | 2 +- .../docs/doc-editor/assets/doc-found.svg | 8 -- .../Interlinking/SearchPage.tsx | 39 ++++++--- .../doc-management/api/useCreateChildDoc.tsx | 2 +- .../docs/doc-management/api/useCreateDoc.tsx | 2 +- .../components/SimpleDocItem.tsx | 13 +-- .../docs-grid/components/DocMoveModal.tsx | 79 ++++++++----------- .../apps/impress/src/layouts/MainLayout.tsx | 4 + 18 files changed, 143 insertions(+), 103 deletions(-) create mode 100644 src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-corner-down-right.svg create mode 100644 src/frontend/apps/impress/src/assets/icons/ui-kit/enter.svg delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-editor/assets/doc-found.svg diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 375e52395..c69ea90a6 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -445,7 +445,6 @@ test.describe('Doc Editor', () => { .first(); await expect(searchContainerRow).toContainText('😀'); - await expect(searchContainerRow.locator('svg').first()).toBeHidden(); await input.pressSequentially('-child'); 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 136488a5d..b5e9e6b33 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 @@ -208,7 +208,9 @@ test.describe('Doc grid move', () => { await page.getByRole('menuitem', { name: 'Move into a doc' }).click(); await expect( - page.getByRole('dialog').getByRole('heading', { name: 'Move' }), + page + .getByRole('dialog') + .getByRole('heading', { name: 'Choose a new parent doc' }), ).toBeVisible(); const input = page.getByRole('combobox', { name: 'Quick search input' }); @@ -296,7 +298,9 @@ test.describe('Doc grid move', () => { await page.getByRole('menuitem', { name: 'Move into a doc' }).click(); await expect( - page.getByRole('dialog').getByRole('heading', { name: 'Move' }), + page + .getByRole('dialog') + .getByRole('heading', { name: 'Choose a new parent doc' }), ).toBeVisible(); const input = page.getByRole('combobox', { name: 'Quick search input' }); @@ -365,7 +369,9 @@ test.describe('Doc grid move', () => { await page.getByRole('menuitem', { name: 'Move into a doc' }).click(); await expect( - page.getByRole('dialog').getByRole('heading', { name: 'Move' }), + page + .getByRole('dialog') + .getByRole('heading', { name: 'Choose a new parent doc' }), ).toBeVisible(); await input.click(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts index 31f3a67a7..8ad269fb4 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts @@ -33,11 +33,11 @@ test.describe('Document search', () => { await page.getByTestId('search-docs-button').click(); await expect( - page.getByLabel('Search modal').locator('img[alt=""]'), + page.getByRole('listbox', { name: 'Suggestions' }).locator('img'), ).toBeVisible(); await expect( - page.getByRole('heading', { name: 'Search docs' }), + page.getByRole('heading', { name: 'Search for a document' }), ).toBeVisible(); const inputSearch = page.getByPlaceholder('Type the name of a document'); @@ -48,7 +48,6 @@ test.describe('Document search', () => { const listSearch = page.getByRole('listbox').getByRole('group'); const rowdoc = listSearch.getByRole('option').first(); - await expect(rowdoc.getByText('keyboard_return')).toBeVisible(); await expect(rowdoc.getByText(/just now/)).toBeVisible(); await expect( @@ -83,7 +82,7 @@ test.describe('Document search', () => { await page.keyboard.press('Control+k'); await expect( - page.getByRole('heading', { name: 'Search docs' }), + page.getByRole('heading', { name: 'Search for a document' }), ).toBeVisible(); await page.keyboard.press('Escape'); @@ -96,7 +95,7 @@ test.describe('Document search', () => { await page.keyboard.press('Control+k'); await expect(page.getByRole('textbox', { name: 'Edit URL' })).toBeVisible(); await expect( - page.getByLabel('Search modal').getByText('search'), + page.getByRole('heading', { name: 'Search for a document' }), ).toBeHidden(); }); @@ -150,31 +149,44 @@ test.describe('Document search', () => { // Display only current doc results const updatedResultsList = page.getByRole('listbox'); + // Top parent are not displayed - only children await expect( updatedResultsList.getByRole('option', { name: secondDocTitle }), - ).toBeVisible(); + ).toBeHidden(); await expect( updatedResultsList.getByRole('option', { name: secondChildDocTitle }), ).toBeVisible(); + // Breadcrumb not displayed + await expect( + updatedResultsList + .getByRole('option', { name: secondChildDocTitle }) + .getByText(secondDocTitle), + ).toBeHidden(); await expect( updatedResultsList.getByRole('option', { name: firstDocTitle }), ).toBeHidden(); // Click on the filter to show all docs - const filters = page.getByTestId('doc-search-filters'); - await filters.click(); - await filters.getByRole('button', { name: 'Current doc' }).click(); - await page.getByRole('menuitemcheckbox', { name: 'All docs' }).click(); + await page + .getByLabel('Search results controls') + .getByText('All docs') + .click(); // Expect to see all docs in the results list + await expect( + updatedResultsList.getByRole('option', { name: firstDocTitle }), + ).toBeVisible(); await expect( updatedResultsList.getByRole('option', { name: secondDocTitle }), ).toBeVisible(); await expect( updatedResultsList.getByRole('option', { name: secondChildDocTitle }), ).toBeVisible(); + // Breadcrumb with top parent is displayed await expect( - updatedResultsList.getByRole('option', { name: firstDocTitle }), + updatedResultsList + .getByRole('option', { name: secondChildDocTitle }) + .getByText(secondDocTitle), ).toBeVisible(); await page.getByRole('button', { name: 'close' }).click(); @@ -188,6 +200,7 @@ test.describe('Document search', () => { const docUrl = page.url(); + // Another unauthenticated should be able to search in the current doc const { otherPage, cleanup } = await connectOtherUserToDoc({ browserName, docUrl, @@ -204,7 +217,7 @@ test.describe('Document search', () => { const otherPageResultsList = otherPage.getByRole('listbox'); await expect( otherPageResultsList.getByRole('option', { name: secondDocTitle }), - ).toBeVisible(); + ).toBeHidden(); await expect( otherPageResultsList.getByRole('option', { name: secondChildDocTitle }), ).toBeVisible(); diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-corner-down-right.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-corner-down-right.svg new file mode 100644 index 000000000..8eb7c8f00 --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-corner-down-right.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/enter.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/enter.svg new file mode 100644 index 000000000..de572769b --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/enter.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/keyboard_return.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/keyboard_return.svg index 190afb548..446472e70 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/keyboard_return.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/keyboard_return.svg @@ -1,3 +1,6 @@ - - + + diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/subdirectory_arrow_right.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/subdirectory_arrow_right.svg index fa4446e41..5617e5ea8 100644 --- a/src/frontend/apps/impress/src/assets/icons/ui-kit/subdirectory_arrow_right.svg +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/subdirectory_arrow_right.svg @@ -1,3 +1,6 @@ - - + + diff --git a/src/frontend/apps/impress/src/components/filter/FilterDropdown.tsx b/src/frontend/apps/impress/src/components/filter/FilterDropdown.tsx index af849c069..6330ee980 100644 --- a/src/frontend/apps/impress/src/components/filter/FilterDropdown.tsx +++ b/src/frontend/apps/impress/src/components/filter/FilterDropdown.tsx @@ -46,7 +46,7 @@ export const FilterDropdown = ({ $direction="row" $align="center" > - + {selectedOption?.label ?? options[0].label} ({ renderElement, }: Props) => { return ( - + <> ({ )} - + ); }; diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx index aa04efcf5..44ea84138 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx @@ -2,12 +2,12 @@ import { Command } from 'cmdk'; import { PropsWithChildren, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import SearchSVG from '@/assets/icons/ui-kit/zoom-rounded.svg'; import { HorizontalSeparator } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { useFocusStore } from '@/stores'; import { Box } from '../Box'; -import { Icon } from '../Icon'; type QuickSearchInputProps = { inputValue?: string; @@ -49,9 +49,14 @@ export const QuickSearchInput = ({ $align="center" className="quick-search-input" $gap={spacingsTokens['xxs']} - $padding={{ horizontal: 'base', vertical: 'xxs' }} + $padding={{ horizontal: 'base', vertical: 'xs' }} > - + - {separator && } + {separator && } ); }; diff --git a/src/frontend/apps/impress/src/components/quick-search/QuickSearchItemContent.tsx b/src/frontend/apps/impress/src/components/quick-search/QuickSearchItemContent.tsx index d95127bf5..806f691fb 100644 --- a/src/frontend/apps/impress/src/components/quick-search/QuickSearchItemContent.tsx +++ b/src/frontend/apps/impress/src/components/quick-search/QuickSearchItemContent.tsx @@ -22,7 +22,7 @@ export const QuickSearchItemContent = ({ className="--docs--quick-search-item-content" $direction="row" $align="center" - $padding={{ horizontal: '2xs', vertical: '4xs' }} + $padding={{ horizontal: 'xs', vertical: '3xs' }} $justify="space-between" $minHeight="34px" $width="100%" diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/assets/doc-found.svg b/src/frontend/apps/impress/src/features/docs/doc-editor/assets/doc-found.svg deleted file mode 100644 index 81f8629bc..000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/assets/doc-found.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - 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 4b49a0957..f685735f3 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 @@ -7,18 +7,19 @@ import { useEffect, useId, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; +import DocIcon from '@/assets/icons/ui-kit/doc.svg'; +import ArrowIcon from '@/assets/icons/ui-kit/keyboard_return.svg'; import { Box, Card, - Icon, QuickSearch, QuickSearchItemContent, Text, } from '@/components'; -import FoundPageIcon from '@/docs/doc-editor/assets/doc-found.svg'; import { DocsBlockNoteEditor } from '@/docs/doc-editor/types'; import { Doc, getEmojiAndTitle, useTrans } from '@/docs/doc-management'; -import { DocSearchContent, DocSearchTarget } from '@/docs/doc-search'; +import { DocSearchContent } from '@/docs/doc-search'; +import { useDocSearchFilterStore } from '@/docs/doc-search/stores/useDocSearchFilterStore'; import { useResponsiveStore } from '@/stores'; import { InterlinkingLinkInlineContentType } from './InterlinkingLinkInlineContent'; @@ -55,6 +56,16 @@ export const SearchPage = ({ const modalRef = useRef(null); const dropdownId = useId(); const [popoverOpened, setPopoverOpened] = useState(false); + const { setFilter } = useDocSearchFilterStore(); + + /** + * When the search page is opened, we set the search + * target to 'current' to limit the search to the current + * document and its sub-documents. + */ + useEffect(() => { + setFilter('current'); + }, [setFilter]); /** * createReactInlineContentSpec add automatically the focus after @@ -210,6 +221,9 @@ export const SearchPage = ({ .quick-search-container & [cmdk-group] { margin-top: 0 !important; } + & h2 { + padding: var(--c--globals--spacings--sm); + } & > div { margin-top: var(--c--globals--spacings--0); & [cmdk-group-heading] { @@ -249,7 +263,6 @@ export const SearchPage = ({ { @@ -295,24 +308,32 @@ export const SearchPage = ({ {emoji ? ( {emoji} ) : ( - )} {titleWithoutEmoji} } right={ - + } /> ); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateChildDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateChildDoc.tsx index c8b6ff2ee..ba5283948 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateChildDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateChildDoc.tsx @@ -1,11 +1,11 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { APIError, errorCauses, fetchAPI } from '@/api'; +import { KEY_LIST_SEARCH_DOC } from '@/docs/doc-search/api/useSearchDocs'; import { Doc } from '../types'; import { KEY_LIST_DOC } from './useDocs'; -import { KEY_LIST_SEARCH_DOC } from './useSearchDocs'; export type CreateChildDocParam = Pick & { parentId: string; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateDoc.tsx index d0dbb48b6..ceaf95c1a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateDoc.tsx @@ -5,11 +5,11 @@ import { } from '@tanstack/react-query'; import { APIError, errorCauses, fetchAPI } from '@/api'; +import { KEY_LIST_SEARCH_DOC } from '@/docs/doc-search/api/useSearchDocs'; import { Doc } from '../types'; import { KEY_LIST_DOC } from './useDocs'; -import { KEY_LIST_SEARCH_DOC } from './useSearchDocs'; type CreateDocParams = { title?: string; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index 34653f57c..821befef7 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; -import ArrowSVG from '@/assets/icons/ui-kit/subdirectory_arrow_right.svg'; +import ArrowSVG from '@/assets/icons/ui-kit/arrow-corner-down-right.svg'; import { Box, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { useDate } from '@/hooks/useDate'; @@ -105,16 +105,7 @@ export const SimpleDocItem = ({ {(showDate || breadcrumb) && (