Skip to content

Commit 9dab4d6

Browse files
committed
fixup! ✨(frontend) add floating bar with collapse button
1 parent 0fd41d3 commit 9dab4d6

File tree

11 files changed

+174
-3
lines changed

11 files changed

+174
-3
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import clsx from 'clsx';
22
import { useEffect, useState } from 'react';
33

44
import { Box, Loading } from '@/components';
5-
import { DocHeader } from '@/docs/doc-header/';
5+
import { DocHeader, FloatingBar } from '@/docs/doc-header/';
66
import {
77
Doc,
88
LinkReach,
@@ -12,7 +12,6 @@ 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';
1615
import { useSkeletonStore } from '@/features/skeletons';
1716
import { useAnalytics } from '@/libs';
1817
import { useResponsiveStore } from '@/stores';

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {
99
getDocLinkReach,
1010
useIsCollaborativeEditable,
1111
} from '@/docs/doc-management';
12-
import { useFloatingBarStore } from '@/features/floating-bar';
1312
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
1413
import { useResponsiveStore } from '@/stores';
1514

15+
import { useFloatingBarStore } from '../floating-bar';
16+
1617
import { AlertNetwork } from './AlertNetwork';
1718
import { AlertPublic } from './AlertPublic';
1819
import { AlertRestore } from './AlertRestore';
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Button } from '@gouvfr-lasuite/cunningham-react';
2+
import { useTranslation } from 'react-i18next';
3+
import { css } from 'styled-components';
4+
5+
import { Box, Text } from '@/components';
6+
import { useCunninghamTheme } from '@/cunningham';
7+
import { getEmojiAndTitle, useDocStore, useTrans } from '@/docs/doc-management';
8+
import { useLeftPanelStore } from '@/features/left-panel';
9+
10+
import LeftPanelIcon from '../assets/left-panel.svg';
11+
import { useFloatingBarStore } from '../stores';
12+
13+
export const CollapsePanel = () => {
14+
const { t } = useTranslation();
15+
const { colorsTokens } = useCunninghamTheme();
16+
const { isPanelOpen, togglePanel } = useLeftPanelStore();
17+
const { isDocHeaderVisible } = useFloatingBarStore();
18+
const { currentDoc } = useDocStore();
19+
const { untitledDocument } = useTrans();
20+
21+
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(
22+
currentDoc?.title ?? '',
23+
);
24+
const docTitle = titleWithoutEmoji || untitledDocument;
25+
const buttonTitle = emoji ? `${emoji} ${docTitle}` : docTitle;
26+
const shouldShowButtonTitle = !isPanelOpen && !isDocHeaderVisible;
27+
const ariaLabel = isPanelOpen
28+
? t('Hide the side panel for {{title}}', { title: docTitle })
29+
: t('Show the side panel for {{title}}', { title: docTitle });
30+
31+
return (
32+
<Box
33+
$css={css`
34+
display: inline-flex;
35+
padding: var(--c--globals--spacings--xxxs);
36+
align-items: center;
37+
gap: var(--c--globals--spacings--xxxs);
38+
border-radius: var(--c--globals--spacings--xs);
39+
border: 1px solid var(--c--contextuals--border--surface--primary);
40+
background: var(--c--contextuals--background--surface--primary);
41+
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05);
42+
`}
43+
>
44+
<Button
45+
size="small"
46+
onClick={() => togglePanel()}
47+
aria-label={ariaLabel}
48+
aria-expanded={isPanelOpen}
49+
color="neutral"
50+
variant="tertiary"
51+
icon={<LeftPanelIcon width={24} height={24} aria-hidden="true" />}
52+
data-testid="floating-bar-toggle-left-panel"
53+
>
54+
{shouldShowButtonTitle ? (
55+
<Text $size="sm" $weight={700} $color={colorsTokens['gray-1000']}>
56+
{buttonTitle}
57+
</Text>
58+
) : undefined}
59+
</Button>
60+
</Box>
61+
);
62+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { RuleSet, css } from 'styled-components';
2+
3+
import { Box } from '@/components';
4+
import { useCunninghamTheme } from '@/cunningham';
5+
import { useResponsiveStore } from '@/stores';
6+
7+
import { FloatingBarLeft } from './FloatingBarLeft';
8+
9+
export const FLOATING_BAR_HEIGHT = '64px';
10+
export const FLOATING_BAR_Z_INDEX = 1000;
11+
const FLOATING_BAR_BLUR_RADIUS = '1px';
12+
const FLOATING_BAR_GRADIENT =
13+
'linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0) 100%)';
14+
15+
/**
16+
* Sticky bar trick (desktop):
17+
* - MainContent has padding `base`; we extend the bar width and apply
18+
* matching negative margins (mainContentPadding) so it aligns with the
19+
* scroll area edges.
20+
* - `top: calc(-mainContentPadding)` keeps sticky positioning visually
21+
* aligned with the content start.
22+
*
23+
* Mobile: returns null to avoid header overlap.
24+
*/
25+
const getFloatingBarStyles = (
26+
mainContentPadding: string,
27+
barSpacing: string,
28+
blurRadius: string,
29+
): RuleSet => css`
30+
position: sticky;
31+
top: calc(-${mainContentPadding});
32+
left: 0;
33+
right: 0;
34+
width: calc(100% + ${mainContentPadding} + ${mainContentPadding});
35+
min-height: ${FLOATING_BAR_HEIGHT};
36+
padding: ${barSpacing};
37+
margin-left: calc(-${mainContentPadding});
38+
margin-right: calc(-${mainContentPadding});
39+
margin-top: calc(-${mainContentPadding});
40+
z-index: ${FLOATING_BAR_Z_INDEX};
41+
display: flex;
42+
align-items: flex-start;
43+
justify-content: flex-start;
44+
background: ${FLOATING_BAR_GRADIENT};
45+
backdrop-filter: blur(${blurRadius});
46+
-webkit-backdrop-filter: blur(${blurRadius});
47+
48+
> * {
49+
position: relative;
50+
z-index: 1;
51+
}
52+
`;
53+
54+
export const FloatingBar = () => {
55+
const { spacingsTokens } = useCunninghamTheme();
56+
const { isDesktop } = useResponsiveStore();
57+
const mainContentPadding =
58+
spacingsTokens['base'] || 'var(--c--globals--spacings--base)';
59+
const barSpacing = spacingsTokens['sm'] || 'var(--c--globals--spacings--sm)';
60+
61+
if (!isDesktop) {
62+
return null;
63+
}
64+
65+
return (
66+
<Box
67+
data-testid="floating-bar"
68+
$css={getFloatingBarStyles(
69+
mainContentPadding,
70+
barSpacing,
71+
FLOATING_BAR_BLUR_RADIUS,
72+
)}
73+
>
74+
<FloatingBarLeft />
75+
</Box>
76+
);
77+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { CollapsePanel } from './CollapsePanel';
2+
3+
export const FloatingBarLeft = () => {
4+
return <CollapsePanel />;
5+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './FloatingBar';
2+
export * from './FloatingBarLeft';
3+
export * from './CollapsePanel';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export {
2+
FloatingBar,
3+
FLOATING_BAR_HEIGHT,
4+
FLOATING_BAR_Z_INDEX,
5+
} from './components/FloatingBar';
6+
export * from './stores';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useFloatingBarStore';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { create } from 'zustand';
2+
3+
interface FloatingBarState {
4+
isDocHeaderVisible: boolean;
5+
setIsDocHeaderVisible: (visible: boolean) => void;
6+
}
7+
8+
export const useFloatingBarStore = create<FloatingBarState>((set) => ({
9+
isDocHeaderVisible: true,
10+
setIsDocHeaderVisible: (visible) => {
11+
set({ isDocHeaderVisible: visible });
12+
},
13+
}));

0 commit comments

Comments
 (0)