Skip to content

Commit f79b18a

Browse files
committed
✨(frontend) Move doc modal
We can now move a doc to another doc from a search modal. It will make it easier to move a doc without having to scroll through the doc grid to find the destination doc. We kept most of the logic implemented in the doc grid dnd.
1 parent c68dd63 commit f79b18a

File tree

12 files changed

+468
-120
lines changed

12 files changed

+468
-120
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to
1414
- ✨(frontend) Can print a doc #1832
1515
- ✨(backend) manage reconciliation requests for user accounts #1878
1616
- 👷(CI) add GHCR workflow for forked repo testing #1851
17+
- ✨(frontend) Move doc modal #1886
1718
- ✨(backend) allow the duplication of subpages #1893
1819
- ✨(backend) Onboarding docs for new users #1891
1920

src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts renamed to src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { expect, test } from '@playwright/test';
22

3-
import { createDoc, mockedListDocs, toggleHeaderMenu } from './utils-common';
3+
import {
4+
createDoc,
5+
getGridRow,
6+
mockedListDocs,
7+
toggleHeaderMenu,
8+
verifyDocName,
9+
} from './utils-common';
410
import { createRootSubPage } from './utils-sub-pages';
511

6-
test.describe('Doc grid dnd', () => {
7-
test('it creates a doc', async ({ page, browserName }) => {
12+
test.describe('Doc grid move', () => {
13+
test('it checks drag and drop functionality', async ({
14+
page,
15+
browserName,
16+
}) => {
817
await page.goto('/');
918
const header = page.locator('header').first();
1019
await createDoc(page, 'Draggable doc', browserName, 1);
@@ -29,7 +38,7 @@ test.describe('Doc grid dnd', () => {
2938
await expect(draggableElement).toBeVisible();
3039
await expect(dropZone).toBeVisible();
3140

32-
// Obtenir les positions des éléments
41+
// Get the position of the elements
3342
const draggableBoundingBox = await draggableElement.boundingBox();
3443
const dropZoneBoundingBox = await dropZone.boundingBox();
3544

@@ -46,7 +55,7 @@ test.describe('Doc grid dnd', () => {
4655
);
4756
await page.mouse.down();
4857

49-
// Déplacer vers la zone cible
58+
// Move to the target zone
5059
await page.mouse.move(
5160
dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2,
5261
dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2,
@@ -161,6 +170,55 @@ test.describe('Doc grid dnd', () => {
161170

162171
await page.mouse.up();
163172
});
173+
174+
test('it moves a doc from the doc search modal', async ({
175+
page,
176+
browserName,
177+
}) => {
178+
await page.goto('/');
179+
180+
const [titleDoc1] = await createDoc(page, 'Draggable doc', browserName, 1);
181+
await page.getByRole('button', { name: 'Back to homepage' }).click();
182+
183+
const [titleDoc2] = await createDoc(page, 'Droppable doc', browserName, 1);
184+
await page.getByRole('button', { name: 'Back to homepage' }).click();
185+
186+
const docsGrid = page.getByTestId('docs-grid');
187+
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
188+
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
189+
190+
const row = await getGridRow(page, titleDoc1);
191+
await row.getByText(`more_horiz`).click();
192+
193+
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
194+
195+
await expect(
196+
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
197+
).toBeVisible();
198+
199+
const input = page.getByRole('combobox', { name: 'Quick search input' });
200+
await input.click();
201+
await input.fill(titleDoc2);
202+
203+
await expect(page.getByRole('option').getByText(titleDoc2)).toBeVisible();
204+
205+
// Select the first result
206+
await page.keyboard.press('Enter');
207+
// The CTA should get the focus
208+
await page.keyboard.press('Tab');
209+
// Validate the move action
210+
await page.keyboard.press('Enter');
211+
212+
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
213+
await docsGrid
214+
.getByRole('link', { name: `Open document ${titleDoc2}` })
215+
.click();
216+
217+
await verifyDocName(page, titleDoc2);
218+
219+
const docTree = page.getByTestId('doc-tree');
220+
await expect(docTree.getByText(titleDoc1)).toBeVisible();
221+
});
164222
});
165223

166224
test.describe('Doc grid dnd mobile', () => {

src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const QuickSearch = ({
3636
onFilter,
3737
inputContent,
3838
inputValue,
39-
loading,
4039
showInput = true,
4140
label,
4241
placeholder,
@@ -75,7 +74,6 @@ export const QuickSearch = ({
7574
>
7675
{showInput && (
7776
<QuickSearchInput
78-
loading={loading}
7977
withSeparator={hasChildrens(children)}
8078
inputValue={inputValue}
8179
onFilter={onFilter}

src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Loader } from '@gouvfr-lasuite/cunningham-react';
21
import { Command } from 'cmdk';
3-
import { ReactNode } from 'react';
2+
import { PropsWithChildren } from 'react';
43
import { useTranslation } from 'react-i18next';
54

65
import { HorizontalSeparator } from '@/components';
@@ -9,19 +8,16 @@ import { useCunninghamTheme } from '@/cunningham';
98
import { Box } from '../Box';
109
import { Icon } from '../Icon';
1110

12-
type Props = {
13-
loading?: boolean;
11+
type QuickSearchInputProps = {
1412
inputValue?: string;
1513
onFilter?: (str: string) => void;
1614
placeholder?: string;
17-
children?: ReactNode;
1815
withSeparator?: boolean;
1916
listId?: string;
2017
onUserInteract?: () => void;
2118
isExpanded?: boolean;
2219
};
2320
export const QuickSearchInput = ({
24-
loading,
2521
inputValue,
2622
onFilter,
2723
placeholder,
@@ -30,7 +26,7 @@ export const QuickSearchInput = ({
3026
listId,
3127
onUserInteract,
3228
isExpanded,
33-
}: Props) => {
29+
}: PropsWithChildren<QuickSearchInputProps>) => {
3430
const { t } = useTranslation();
3531
const { spacingsTokens } = useCunninghamTheme();
3632

@@ -52,14 +48,7 @@ export const QuickSearchInput = ({
5248
$gap={spacingsTokens['2xs']}
5349
$padding={{ horizontal: 'base', vertical: 'sm' }}
5450
>
55-
{!loading && (
56-
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
57-
)}
58-
{loading && (
59-
<div>
60-
<Loader size="small" />
61-
</div>
62-
)}
51+
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
6352
<Command.Input
6453
autoFocus={true}
6554
aria-label={t('Quick search input')}

src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const QuickSearchStyle = createGlobalStyle`
2222
padding: var(--c--globals--spacings--xs);
2323
background: white;
2424
outline: none;
25-
color: var(--c--globals--colors--gray-1000);
25+
color: var(--c--contextuals--content--semantic--neutral--primary);
2626
border-radius: var(--c--globals--spacings--0);
2727
2828
&::placeholder {

src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ import { DocSearchItem } from './DocSearchItem';
1212
type DocSearchContentProps = {
1313
search: string;
1414
filters: DocSearchFiltersValues;
15+
filterResults?: (doc: Doc) => boolean;
1516
onSelect: (doc: Doc) => void;
1617
onLoadingChange?: (loading: boolean) => void;
18+
renderSearchElement?: (doc: Doc) => React.ReactNode;
1719
};
1820

1921
export const DocSearchContent = ({
2022
search,
2123
filters,
24+
filterResults,
2225
onSelect,
2326
onLoadingChange,
27+
renderSearchElement,
2428
}: DocSearchContentProps) => {
2529
const {
2630
data,
@@ -38,7 +42,11 @@ export const DocSearchContent = ({
3842
const loading = isFetching || isRefetching || isLoading;
3943

4044
const docsData: QuickSearchData<Doc> = useMemo(() => {
41-
const docs = data?.pages.flatMap((page) => page.results) || [];
45+
let docs = data?.pages.flatMap((page) => page.results) || [];
46+
47+
if (filterResults) {
48+
docs = docs.filter(filterResults);
49+
}
4250

4351
return {
4452
groupName: docs.length > 0 ? t('Select a document') : '',
@@ -52,7 +60,7 @@ export const DocSearchContent = ({
5260
]
5361
: [],
5462
};
55-
}, [search, data?.pages, fetchNextPage, hasNextPage]);
63+
}, [search, data?.pages, fetchNextPage, hasNextPage, filterResults]);
5664

5765
useEffect(() => {
5866
onLoadingChange?.(loading);
@@ -62,7 +70,9 @@ export const DocSearchContent = ({
6270
<QuickSearchGroup
6371
onSelect={onSelect}
6472
group={docsData}
65-
renderElement={(doc) => <DocSearchItem doc={doc} />}
73+
renderElement={
74+
renderSearchElement ?? ((doc) => <DocSearchItem doc={doc} />)
75+
}
6676
/>
6777
);
6878
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './DocSearchContent';
12
export * from './DocSearchModal';
23
export * from './DocSearchFilters';
34
export * from './DocSearchSubPageContent';

0 commit comments

Comments
 (0)