Skip to content

Commit 0803259

Browse files
committed
✨(frontend) add floating bar with collapse button
Add sticky floating bar at top of document with leftpanelcollapse btn
1 parent 607bae0 commit 0803259

File tree

20 files changed

+384
-51
lines changed

20 files changed

+384
-51
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ and this project adheres to
66

77
## [Unreleased]
88

9-
### Added
9+
### Added
1010

11+
- ✨(frontend) add floating bar with leftpanel collapse button #1876
1112
✨(frontend) Can print a doc #1832
1213

1314
### Changed

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ test.describe('Doc Comments', () => {
4141
// We add a comment with the first user
4242
const editor = await writeInEditor({ page, text: 'Hello World' });
4343
await editor.getByText('Hello').selectText();
44-
await page.getByRole('button', { name: 'Comment' }).click();
44+
await page.getByRole('button', { name: 'Comment', exact: true }).click();
4545

4646
const thread = page.locator('.bn-thread');
4747
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -124,7 +124,7 @@ test.describe('Doc Comments', () => {
124124
// Checks add react reaction
125125
const editor = await writeInEditor({ page, text: 'Hello' });
126126
await editor.getByText('Hello').selectText();
127-
await page.getByRole('button', { name: 'Comment' }).click();
127+
await page.getByRole('button', { name: 'Comment', exact: true }).click();
128128

129129
const thread = page.locator('.bn-thread');
130130
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -191,7 +191,7 @@ test.describe('Doc Comments', () => {
191191

192192
/* Delete the last comment remove the thread */
193193
await editor.getByText('Hello').selectText();
194-
await page.getByRole('button', { name: 'Comment' }).click();
194+
await page.getByRole('button', { name: 'Comment', exact: true }).click();
195195

196196
await thread.getByRole('paragraph').first().fill('This is a new comment');
197197
await thread.locator('[data-test="save"]').click();
@@ -249,7 +249,9 @@ test.describe('Doc Comments', () => {
249249
editor.getByText('Hello, I can edit the document'),
250250
).toBeVisible();
251251
await otherEditor.getByText('Hello').selectText();
252-
await otherPage.getByRole('button', { name: 'Comment' }).click();
252+
await otherPage
253+
.getByRole('button', { name: 'Comment', exact: true })
254+
.click();
253255
const otherThread = otherPage.locator('.bn-thread');
254256
await otherThread
255257
.getByRole('paragraph')
@@ -280,7 +282,7 @@ test.describe('Doc Comments', () => {
280282
await expect(otherThread).toBeHidden();
281283
await otherEditor.getByText('Hello').selectText();
282284
await expect(
283-
otherPage.getByRole('button', { name: 'Comment' }),
285+
otherPage.getByRole('button', { name: 'Comment', exact: true }),
284286
).toBeHidden();
285287

286288
await otherPage.reload();
@@ -334,7 +336,7 @@ test.describe('Doc Comments', () => {
334336
// We add a comment in the first document
335337
const editor1 = await writeInEditor({ page, text: 'Document One' });
336338
await editor1.getByText('Document One').selectText();
337-
await page.getByRole('button', { name: 'Comment' }).click();
339+
await page.getByRole('button', { name: 'Comment', exact: true }).click();
338340

339341
const thread1 = page.locator('.bn-thread');
340342
await thread1.getByRole('paragraph').first().fill('Comment in Doc One');
@@ -388,7 +390,7 @@ test.describe('Doc Comments mobile', () => {
388390
// Checks add react reaction
389391
const editor = await writeInEditor({ page, text: 'Hello' });
390392
await editor.getByText('Hello').selectText();
391-
await page.getByRole('button', { name: 'Comment' }).click();
393+
await page.getByRole('button', { name: 'Comment', exact: true }).click();
392394

393395
const thread = page.locator('.bn-thread');
394396
await thread.getByRole('paragraph').first().fill('This is a comment');

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

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,66 @@ test.beforeEach(async ({ page }) => {
2424
});
2525

2626
test.describe('Doc Editor', () => {
27+
test('shows floating bar and collapse button on desktop', async ({
28+
page,
29+
browserName,
30+
}) => {
31+
await createDoc(page, 'doc-floating-bar', browserName, 1);
32+
33+
await expect(page.getByTestId('floating-bar')).toBeVisible();
34+
35+
const collapseButton = page.getByTestId('floating-bar-toggle-left-panel');
36+
await expect(collapseButton).toBeVisible();
37+
});
38+
39+
test('toggles panel collapse from floating bar button', async ({
40+
page,
41+
browserName,
42+
}) => {
43+
const [docTitle] = await createDoc(
44+
page,
45+
'doc-floating-bar',
46+
browserName,
47+
1,
48+
);
49+
50+
const collapseButton = page.getByTestId('floating-bar-toggle-left-panel');
51+
await expect(collapseButton).toBeVisible();
52+
const initialExpanded = await collapseButton.getAttribute('aria-expanded');
53+
expect(
54+
initialExpanded === 'true' || initialExpanded === 'false',
55+
).toBeTruthy();
56+
const isInitiallyExpanded = initialExpanded === 'true';
57+
58+
if (isInitiallyExpanded) {
59+
await expect(collapseButton).not.toContainText(docTitle);
60+
} else {
61+
await expect(collapseButton).toContainText(docTitle);
62+
}
63+
64+
await collapseButton.click();
65+
await expect(collapseButton).toHaveAttribute(
66+
'aria-expanded',
67+
isInitiallyExpanded ? 'false' : 'true',
68+
);
69+
if (isInitiallyExpanded) {
70+
await expect(collapseButton).toContainText(docTitle);
71+
} else {
72+
await expect(collapseButton).not.toContainText(docTitle);
73+
}
74+
75+
await collapseButton.click();
76+
await expect(collapseButton).toHaveAttribute(
77+
'aria-expanded',
78+
isInitiallyExpanded ? 'true' : 'false',
79+
);
80+
if (isInitiallyExpanded) {
81+
await expect(collapseButton).not.toContainText(docTitle);
82+
} else {
83+
await expect(collapseButton).toContainText(docTitle);
84+
}
85+
});
86+
2787
test('it checks toolbar buttons are displayed', async ({
2888
page,
2989
browserName,
@@ -410,7 +470,7 @@ test.describe('Doc Editor', () => {
410470
const editor = page.locator('.ProseMirror');
411471
await editor.getByText('Hello').selectText();
412472

413-
await page.getByRole('button', { name: 'AI' }).click();
473+
await page.getByRole('button', { name: 'AI', exact: true }).click();
414474

415475
await expect(
416476
page.getByRole('menuitem', { name: 'Use as prompt' }),
@@ -494,11 +554,13 @@ test.describe('Doc Editor', () => {
494554
await editor.getByText('Hello').selectText();
495555

496556
if (!ai_transform && !ai_translate) {
497-
await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
557+
await expect(
558+
page.getByRole('button', { name: 'AI', exact: true }),
559+
).toBeHidden();
498560
return;
499561
}
500562

501-
await page.getByRole('button', { name: 'AI' }).click();
563+
await page.getByRole('button', { name: 'AI', exact: true }).click();
502564

503565
if (ai_transform) {
504566
await expect(

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ export const toggleHeaderMenu = async (page: Page) => {
118118
await toggleButton.click();
119119
};
120120

121+
/**
122+
* Ensures the left panel is expanded when on a doc page.
123+
* The panel can be collapsed in Playwright, which hides the "New doc" button.
124+
*/
125+
export const ensureLeftPanelExpanded = async (page: Page) => {
126+
const collapseToggle = page.getByTestId('floating-bar-toggle-left-panel');
127+
if (!(await collapseToggle.isVisible())) {
128+
return;
129+
}
130+
const isExpanded =
131+
(await collapseToggle.getAttribute('aria-expanded')) === 'true';
132+
if (!isExpanded) {
133+
await collapseToggle.click();
134+
await expect(page.getByTestId('new-doc-button')).toBeVisible();
135+
}
136+
};
137+
121138
export const createDoc = async (
122139
page: Page,
123140
docName: string,
@@ -130,6 +147,8 @@ export const createDoc = async (
130147
for (let i = 0; i < randomDocs.length; i++) {
131148
if (isMobile) {
132149
await openHeaderMenu(page);
150+
} else {
151+
await ensureLeftPanelExpanded(page);
133152
}
134153

135154
await page

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Page, expect } from '@playwright/test';
33
import {
44
BrowserName,
55
closeHeaderMenu,
6+
ensureLeftPanelExpanded,
67
openHeaderMenu,
78
randomName,
89
updateDocTitle,
@@ -54,6 +55,7 @@ export const createRootSubPage = async (
5455
};
5556

5657
export const clickOnAddRootSubPage = async (page: Page) => {
58+
await ensureLeftPanelExpanded(page);
5759
const rootItem = page.getByTestId('doc-tree-root-item');
5860
await expect(rootItem).toBeVisible();
5961
await rootItem.hover();
@@ -71,6 +73,7 @@ export const addChild = async ({
7173
docParent: string;
7274
docName: string;
7375
}) => {
76+
await ensureLeftPanelExpanded(page);
7477
let item = page.getByTestId('doc-tree-root-item');
7578

7679
const isParent = await item

src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from '@/docs/doc-management';
1313
import { TableContent } from '@/docs/doc-table-content/';
1414
import { useAuth } from '@/features/auth/';
15+
import { FloatingBar } from '@/features/floating-bar';
1516
import { useSkeletonStore } from '@/features/skeletons';
1617
import { useAnalytics } from '@/libs';
1718
import { useResponsiveStore } from '@/stores';
@@ -35,6 +36,7 @@ export const DocEditorContainer = ({
3536

3637
return (
3738
<>
39+
{isDesktop && <FloatingBar />}
3840
<Box
3941
$maxWidth="868px"
4042
$width="100%"

src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
3535
<>
3636
<Box
3737
$width="100%"
38-
$padding={{ top: isDesktop ? '50px' : 'md' }}
38+
$padding={{ top: isDesktop ? '0' : 'md' }}
3939
$gap={spacingsTokens['base']}
4040
aria-label={t('It is the card information about the document.')}
4141
className="--docs--doc-header"

src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const TableContent = () => {
6161
$width={!isOpen ? '40px' : '200px'}
6262
$height={!isOpen ? '40px' : 'auto'}
6363
$maxHeight="calc(50vh - 60px)"
64-
$zIndex={1000}
64+
$zIndex={2000}
6565
$align="center"
6666
$padding={isOpen ? 'xs' : '0'}
6767
$justify="center"
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Button } from '@gouvfr-lasuite/cunningham-react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
import { Text } from '@/components';
5+
import { useCunninghamTheme } from '@/cunningham';
6+
import { getEmojiAndTitle, useDocStore, useTrans } from '@/docs/doc-management';
7+
import { useLeftPanelStore } from '@/features/left-panel';
8+
9+
import LeftPanelIcon from '../assets/left-panel.svg';
10+
11+
export const CollapsePanel = () => {
12+
const { t } = useTranslation();
13+
const { colorsTokens } = useCunninghamTheme();
14+
const { isPanelOpen, togglePanel } = useLeftPanelStore();
15+
const { currentDoc } = useDocStore();
16+
const { untitledDocument } = useTrans();
17+
18+
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(
19+
currentDoc?.title ?? '',
20+
);
21+
const docTitle = titleWithoutEmoji || untitledDocument;
22+
const buttonTitle = emoji ? `${emoji} ${docTitle}` : docTitle;
23+
24+
return (
25+
<Button
26+
size="medium"
27+
onClick={() => togglePanel()}
28+
aria-label={t(
29+
isPanelOpen
30+
? 'Hide the side panel for {{title}}'
31+
: 'Show the side panel for {{title}}',
32+
{ title: docTitle },
33+
)}
34+
aria-expanded={isPanelOpen}
35+
color="neutral"
36+
variant="tertiary"
37+
icon={<LeftPanelIcon width={24} height={24} aria-hidden="true" />}
38+
data-testid="floating-bar-toggle-left-panel"
39+
>
40+
{!isPanelOpen ? (
41+
<Text $size="sm" $weight={700} $color={colorsTokens['gray-1000']}>
42+
{buttonTitle}
43+
</Text>
44+
) : undefined}
45+
</Button>
46+
);
47+
};

0 commit comments

Comments
 (0)