diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bf16037..7f43fa19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to
### Added
- ✨(tracking) add UTM parameters to shared document links
+- ✨(frontend) add floating bar with leftpanel collapse button #1876
- ✨(frontend) Can print a doc #1832
- ✨(backend) manage reconciliation requests for user accounts #1878
- 👷(CI) add GHCR workflow for forked repo testing #1851
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
index b73814b4..93a04094 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
@@ -41,7 +41,7 @@ test.describe('Doc Comments', () => {
// We add a comment with the first user
const editor = await writeInEditor({ page, text: 'Hello World' });
await editor.getByText('Hello').selectText();
- await page.getByRole('button', { name: 'Comment' }).click();
+ await page.getByRole('button', { name: 'Comment', exact: true }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -124,7 +124,7 @@ test.describe('Doc Comments', () => {
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
- await page.getByRole('button', { name: 'Comment' }).click();
+ await page.getByRole('button', { name: 'Comment', exact: true }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
@@ -191,7 +191,7 @@ test.describe('Doc Comments', () => {
/* Delete the last comment remove the thread */
await editor.getByText('Hello').selectText();
- await page.getByRole('button', { name: 'Comment' }).click();
+ await page.getByRole('button', { name: 'Comment', exact: true }).click();
await thread.getByRole('paragraph').first().fill('This is a new comment');
await thread.locator('[data-test="save"]').click();
@@ -249,7 +249,9 @@ test.describe('Doc Comments', () => {
editor.getByText('Hello, I can edit the document'),
).toBeVisible();
await otherEditor.getByText('Hello').selectText();
- await otherPage.getByRole('button', { name: 'Comment' }).click();
+ await otherPage
+ .getByRole('button', { name: 'Comment', exact: true })
+ .click();
const otherThread = otherPage.locator('.bn-thread');
await otherThread
.getByRole('paragraph')
@@ -280,7 +282,7 @@ test.describe('Doc Comments', () => {
await expect(otherThread).toBeHidden();
await otherEditor.getByText('Hello').selectText();
await expect(
- otherPage.getByRole('button', { name: 'Comment' }),
+ otherPage.getByRole('button', { name: 'Comment', exact: true }),
).toBeHidden();
await otherPage.reload();
@@ -334,7 +336,7 @@ test.describe('Doc Comments', () => {
// We add a comment in the first document
const editor1 = await writeInEditor({ page, text: 'Document One' });
await editor1.getByText('Document One').selectText();
- await page.getByRole('button', { name: 'Comment' }).click();
+ await page.getByRole('button', { name: 'Comment', exact: true }).click();
const thread1 = page.locator('.bn-thread');
await thread1.getByRole('paragraph').first().fill('Comment in Doc One');
@@ -388,7 +390,7 @@ test.describe('Doc Comments mobile', () => {
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
- await page.getByRole('button', { name: 'Comment' }).click();
+ await page.getByRole('button', { name: 'Comment', exact: true }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
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 11796c65..1d9f5281 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
@@ -410,7 +410,7 @@ test.describe('Doc Editor', () => {
const editor = page.locator('.ProseMirror');
await editor.getByText('Hello').selectText();
- await page.getByRole('button', { name: 'AI' }).click();
+ await page.getByRole('button', { name: 'AI', exact: true }).click();
await expect(
page.getByRole('menuitem', { name: 'Use as prompt' }),
@@ -494,11 +494,13 @@ test.describe('Doc Editor', () => {
await editor.getByText('Hello').selectText();
if (!ai_transform && !ai_translate) {
- await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
+ await expect(
+ page.getByRole('button', { name: 'AI', exact: true }),
+ ).toBeHidden();
return;
}
- await page.getByRole('button', { name: 'AI' }).click();
+ await page.getByRole('button', { name: 'AI', exact: true }).click();
if (ai_transform) {
await expect(
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 4652f590..71f1594b 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
@@ -7,6 +7,7 @@ import {
mockedDocument,
verifyDocName,
} from './utils-common';
+import { writeInEditor } from './utils-editor';
import {
connectOtherUserToDoc,
mockedAccesses,
@@ -20,6 +21,43 @@ test.beforeEach(async ({ page }) => {
});
test.describe('Doc Header', () => {
+ test('toggles panel collapse from floating bar button', async ({
+ page,
+ browserName,
+ }) => {
+ const [docTitle] = await createDoc(
+ page,
+ 'doc-floating-bar',
+ browserName,
+ 1,
+ );
+
+ const collapseButton = page.getByTestId('floating-bar-toggle-left-panel');
+ await expect(collapseButton).toBeVisible();
+
+ // Panel open
+ await expect(collapseButton).toHaveAttribute('aria-expanded', 'true');
+ await expect(collapseButton.getByText(docTitle)).toBeHidden();
+
+ // Collapse panel
+ await collapseButton.click();
+ await expect(collapseButton).toHaveAttribute('aria-expanded', 'false');
+ await expect(collapseButton.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' });
+ for (let i = 0; i < 25; i++) {
+ await editor.press('Enter');
+ }
+ await writeInEditor({ page, text: 'Lorem ipsum 2' });
+ await expect(collapseButton.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();
+ });
+
test('it checks the element are correctly displayed', async ({
page,
browserName,
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
index f1ad090e..17de12d2 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
@@ -2,7 +2,7 @@ import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { Box, Loading } from '@/components';
-import { DocHeader } from '@/docs/doc-header/';
+import { DocHeader, FloatingBar } from '@/docs/doc-header/';
import {
Doc,
LinkReach,
@@ -35,6 +35,7 @@ export const DocEditorContainer = ({
return (
<>
+ {isDesktop && }
{
<>
{
const { untitledDocument } = useTrans();
return (
-
- {currentDoc?.title || untitledDocument}
-
+
+
+ {currentDoc?.title || untitledDocument}
+
+
);
};
@@ -65,6 +69,7 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
placement="top"
>
{
+ const { spacingsTokens } = useCunninghamTheme();
+ const { isDesktop } = useResponsiveStore();
+
+ const FLOATING_STYLES = useMemo(() => {
+ const base = spacingsTokens['base'];
+ const sm = spacingsTokens['sm'];
+ return css`
+ position: sticky;
+ top: calc(-${base});
+ left: 0;
+ right: 0;
+ width: calc(100% + ${base} + ${base});
+ min-height: 64px;
+ padding: ${sm};
+ margin-left: calc(-${base});
+ margin-right: calc(-${base});
+ margin-top: calc(-${base});
+ z-index: 1000;
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ isolation: isolate;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ background: linear-gradient(
+ 180deg,
+ #fff 0%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ backdrop-filter: blur(1px);
+ -webkit-backdrop-filter: blur(1px);
+ mask-image: linear-gradient(180deg, black 50%, transparent 100%);
+ -webkit-mask-image: linear-gradient(
+ 180deg,
+ black 50%,
+ transparent 100%
+ );
+ }
+
+ > * {
+ position: relative;
+ z-index: 1;
+ }
+ `;
+ }, [spacingsTokens]);
+
+ if (!isDesktop) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-header/components/index.ts
index b0e56b2d..1163e974 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/index.ts
@@ -1,2 +1,3 @@
export * from './DocHeader';
export * from './DocTitle';
+export * from './FloatingBar';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
index 309ebc30..a0dba7d6 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContent.tsx
@@ -61,7 +61,7 @@ export const TableContent = () => {
$width={!isOpen ? '40px' : '200px'}
$height={!isOpen ? '40px' : 'auto'}
$maxHeight="calc(50vh - 60px)"
- $zIndex={1000}
+ $zIndex={2000}
$align="center"
$padding={isOpen ? 'xs' : '0'}
$justify="center"
diff --git a/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx
index 21767a71..e7fa67db 100644
--- a/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx
+++ b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx
@@ -6,19 +6,22 @@ import { useLeftPanelStore } from '@/features/left-panel';
export const ButtonTogglePanel = () => {
const { t } = useTranslation();
- const { isPanelOpen, togglePanel } = useLeftPanelStore();
+ const { isPanelOpenMobile, togglePanel } = useLeftPanelStore();
return (