Skip to content

Commit e613084

Browse files
committed
♻️(frontend) new create button for docs
New create button for docs. It is now a link button instead of a pure button, meaning we can navigate to this new created doc and open it in another tab by doing ctrl + click. This new button has a dropdown menu that allows users to import a docx file or a markdown file. We removed the old import button.
1 parent 87fbcba commit e613084

14 files changed

Lines changed: 190 additions & 147 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ and this project adheres to
2020
- ♿️(frontend) use heading element for pinned documents section title #2380
2121
- ♿️(frontend) use anchor links for table of contents entries #2390
2222
- ♿️(frontend) improve presenter mode screen reader and keyboard support #2383
23-
♿️(frontend) link export modal name to its heading #2422
23+
- ♿️(frontend) link export modal name to its heading #2422
24+
- ♻️(frontend) new create button for docs #2423
2425

2526
### Fixed
2627

src/frontend/apps/e2e/__tests__/app-impress/doc-import.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ test.describe('Doc Import', () => {
1818

1919
await page.goto('/');
2020

21-
await expect(page.getByLabel('Open the upload dialog')).toBeHidden();
21+
await expect(page.getByLabel('Open new document options')).toBeHidden();
2222
});
2323

2424
test('it imports 2 docs with the import icon', async ({ page }) => {
2525
const fileChooserPromise = page.waitForEvent('filechooser');
26-
await page.getByLabel('Open the upload dialog').click();
26+
27+
await page.getByLabel('Open new document options').click();
28+
await page.getByRole('menuitem', { name: 'Import a document' }).click();
2729

2830
const fileChooser = await fileChooserPromise;
2931
await fileChooser.setFiles([
@@ -42,6 +44,7 @@ test.describe('Doc Import', () => {
4244
),
4345
).toBeVisible();
4446

47+
await page.getByLabel('Back to homepage').first().click();
4548
const docsGrid = page.getByTestId('docs-grid');
4649
await expect(docsGrid.getByText('test_import.docx').first()).toBeVisible();
4750
await expect(docsGrid.getByText('test_import.md').first()).toBeVisible();

src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,9 @@ export const createDoc = async (
144144
);
145145

146146
await page
147-
.getByRole('button', {
148-
name: 'New doc',
147+
.getByRole('link', {
148+
name: 'New',
149+
exact: true,
149150
})
150151
.click();
151152

src/frontend/apps/impress/next.config.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ const nextConfig = {
1818
// Enables the styled-components SWC transform
1919
styledComponents: true,
2020
},
21-
experimental: {
22-
// Tree-shake barrel files for these packages so webpack only bundles the
23-
// symbols that are actually imported, reducing chunk sizes noticeably for
24-
// Mantine and the Cunningham design system.
25-
optimizePackageImports: ['@mantine/core', '@mantine/hooks', 'lodash'],
26-
},
2721
generateBuildId: () => buildId,
2822
env: {
2923
NEXT_PUBLIC_BUILD_ID: buildId,
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 21 additions & 6 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading

src/frontend/apps/impress/src/features/docs/docs-grid/api/useImportDoc.tsx renamed to src/frontend/apps/impress/src/features/docs/doc-management/api/useImportDoc.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import {
1515
errorCauses,
1616
fetchAPI,
1717
} from '@/api';
18-
import { Doc, DocsResponse, KEY_LIST_DOC } from '@/docs/doc-management';
18+
19+
import { Doc } from '../types';
20+
21+
import { DocsResponse, KEY_LIST_DOC } from './useDocs';
1922

2023
interface ContentType {
2124
mime: string;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Button, Loader } from '@gouvfr-lasuite/cunningham-react';
2+
import { DropdownMenu, DropdownMenuItem } from '@gouvfr-lasuite/ui-kit';
3+
import { useRouter } from 'next/router';
4+
import { useCallback, useMemo, useState } from 'react';
5+
import { useTranslation } from 'react-i18next';
6+
7+
import { Text } from '@/components';
8+
import { useConfig } from '@/core/config/api/useConfig';
9+
import ArrowDownIcon from '@/icons/arrow-drop-down.svg';
10+
import PlusIcon from '@/icons/doc-plus.svg';
11+
import UploadIcon from '@/icons/upload-arrow.svg';
12+
13+
import { useImport } from '../hooks/useImport';
14+
15+
interface NewDocButtonProps {
16+
onClose?: () => void;
17+
}
18+
19+
export const NewDocButton = ({ onClose }: NewDocButtonProps) => {
20+
const router = useRouter();
21+
const { t } = useTranslation();
22+
const { currentDoc } = useDocStore();
23+
const { data: config } = useConfig();
24+
const isDropdownEnabled = config?.CONVERSION_UPLOAD_ENABLED || currentDoc;
25+
26+
return (
27+
<>
28+
<Button
29+
href="/docs/new"
30+
data-testid="new-doc-button"
31+
color="brand"
32+
onClick={(e) => {
33+
if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
34+
e.preventDefault();
35+
void router.push('/docs/new');
36+
}
37+
onClose?.();
38+
}}
39+
icon={<PlusIcon aria-hidden="true" width={24} height={24} />}
40+
style={{
41+
borderRadius: isDropdownEnabled ? '4px 0 0 4px' : '4px',
42+
borderRight:
43+
'1px solid var(--c--contextuals--background--palette--brand--primary)',
44+
}}
45+
>
46+
<Text $withThemeInherited $size="md" $weight="500">
47+
{t('New')}
48+
</Text>
49+
</Button>
50+
{isDropdownEnabled && <DropdownArrow />}
51+
</>
52+
);
53+
};
54+
55+
export function DropdownArrow() {
56+
const router = useRouter();
57+
const [isMenuOpen, setIsMenuOpen] = useState(false);
58+
const { t } = useTranslation();
59+
const {
60+
getInputProps,
61+
open: openImport,
62+
isPending: isImportPending,
63+
isEnabled: isImportEnabled,
64+
} = useImport({
65+
onImportSuccess: (doc) => {
66+
void router.push(`/docs/${doc.id}/`);
67+
},
68+
});
69+
70+
const toggleMenu = useCallback(() => {
71+
setIsMenuOpen((open) => !open);
72+
}, []);
73+
74+
const options = useMemo<DropdownMenuItem[]>(
75+
() => [
76+
{
77+
label: t('Import a document'),
78+
icon: <UploadIcon aria-hidden="true" width="24" height="24" />,
79+
callback: openImport,
80+
isHidden: !isImportEnabled,
81+
},
82+
],
83+
[t, openImport, isImportEnabled],
84+
);
85+
86+
return (
87+
<>
88+
<DropdownMenu
89+
options={options}
90+
isOpen={isMenuOpen}
91+
onOpenChange={setIsMenuOpen}
92+
>
93+
<Button
94+
aria-label={t('Open new document options')}
95+
color="brand"
96+
variant="primary"
97+
iconPosition="left"
98+
icon={
99+
isImportPending ? (
100+
<Loader size="small" />
101+
) : (
102+
<ArrowDownIcon aria-hidden="true" width="24" height="24" />
103+
)
104+
}
105+
onClick={!isImportPending ? toggleMenu : undefined}
106+
aria-disabled={isImportPending}
107+
style={{ borderRadius: '0 4px 4px 0', width: '30px' }}
108+
/>
109+
</DropdownMenu>
110+
<input {...getInputProps()} />
111+
</>
112+
);
113+
}

src/frontend/apps/impress/src/features/docs/docs-grid/hooks/useImport.tsx renamed to src/frontend/apps/impress/src/features/docs/doc-management/hooks/useImport.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ import { useDropzone } from 'react-dropzone';
99
import { useConfig } from '@/core';
1010

1111
import { ContentTypes, useImportDoc } from '../api/useImportDoc';
12+
import { Doc } from '../types';
1213

1314
interface UseImportProps {
14-
onDragOver: (isDragOver: boolean) => void;
15+
onDragOver?: (isDragOver: boolean) => void;
16+
onImportSuccess?: (doc: Doc) => void;
1517
}
1618

1719
interface AcceptedMap {
1820
[mime: string]: string[];
1921
}
2022

21-
export const useImport = ({ onDragOver }: UseImportProps) => {
23+
export const useImport = ({ onDragOver, onImportSuccess }: UseImportProps) => {
2224
const { toast } = useToastProvider();
2325
const { data: config } = useConfig();
2426

@@ -84,22 +86,26 @@ export const useImport = ({ onDragOver }: UseImportProps) => {
8486
accept: ACCEPT,
8587
maxSize: MAX_FILE_SIZE.bytes,
8688
onDrop(acceptedFiles) {
87-
onDragOver(false);
89+
onDragOver?.(false);
8890
const allowedExtensions = Object.values(ACCEPT).flat();
8991
for (const file of acceptedFiles) {
9092
const ext = `.${file.name.split('.').pop()?.toLowerCase()}`;
9193
if (!allowedExtensions.includes(ext)) {
9294
toastInvalidFileType(file.name);
9395
continue;
9496
}
95-
importDoc([file, file.type]);
97+
importDoc([file, file.type], {
98+
onSuccess: (doc: Doc) => {
99+
onImportSuccess?.(doc);
100+
},
101+
});
96102
}
97103
},
98104
onDragEnter: () => {
99-
onDragOver(true);
105+
onDragOver?.(true);
100106
},
101107
onDragLeave: () => {
102-
onDragOver(false);
108+
onDragOver?.(false);
103109
},
104110
onDropRejected(fileRejections) {
105111
fileRejections.forEach((rejection) => {

0 commit comments

Comments
 (0)