Skip to content

Commit 08af57d

Browse files
committed
fixup! ✨(frontend) add resizable left panel on desktop with persistence
1 parent acb1d71 commit 08af57d

File tree

3 files changed

+130
-138
lines changed

3 files changed

+130
-138
lines changed

src/frontend/apps/impress/src/components/main-layout/MainLayoutContent.tsx

Lines changed: 0 additions & 92 deletions
This file was deleted.
Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,50 @@
1-
import { useCallback, useEffect, useRef, useState } from 'react';
1+
import { useEffect, useRef, useState } from 'react';
22
import {
33
ImperativePanelHandle,
44
Panel,
55
PanelGroup,
66
PanelResizeHandle,
77
} from 'react-resizable-panels';
8+
import { createGlobalStyle } from 'styled-components';
89

910
import { useCunninghamTheme } from '@/cunningham';
1011

12+
interface PanelStyleProps {
13+
$isResizing: boolean;
14+
}
15+
16+
const PanelStyle = createGlobalStyle<PanelStyleProps>`
17+
${({ $isResizing }) => $isResizing && `body * { transition: none !important; }`}
18+
`;
19+
20+
// Convert a target pixel width to a percentage of the current viewport width.
21+
// react-resizable-panels expects sizes in %, not px.
22+
const calculateDefaultSize = (targetWidth: number) => {
23+
const windowWidth = window.innerWidth;
24+
return (targetWidth / windowWidth) * 100;
25+
};
26+
1127
type ResizableLeftPanelProps = {
1228
leftPanel: React.ReactNode;
1329
children: React.ReactNode;
1430
minPanelSizePx?: number;
1531
maxPanelSizePx?: number;
16-
onResizingChange?: (isResizing: boolean) => void;
1732
};
1833

1934
export const ResizableLeftPanel = ({
2035
leftPanel,
2136
children,
2237
minPanelSizePx = 300,
2338
maxPanelSizePx = 450,
24-
onResizingChange,
2539
}: ResizableLeftPanelProps) => {
40+
const [isResizing, setIsResizing] = useState(false);
2641
const { colorsTokens } = useCunninghamTheme();
2742
const ref = useRef<ImperativePanelHandle>(null);
2843
const resizeTimeoutRef = useRef<number | undefined>(undefined);
2944

3045
const [minPanelSize, setMinPanelSize] = useState(0);
3146
const [maxPanelSize, setMaxPanelSize] = useState(0);
3247

33-
// Convert a target pixel width to a percentage of the current viewport width.
34-
// react-resizable-panels expects sizes in %, not px.
35-
const calculateDefaultSize = useCallback((targetWidth: number) => {
36-
const windowWidth = window.innerWidth;
37-
return (targetWidth / windowWidth) * 100;
38-
}, []);
39-
4048
// Single resize listener that handles both panel size updates and transition disabling
4149
useEffect(() => {
4250
const handleResize = () => {
@@ -49,12 +57,12 @@ export const ResizableLeftPanel = ({
4957
setMaxPanelSize(max);
5058

5159
// Temporarily disable transitions to avoid flicker
52-
onResizingChange?.(true);
60+
setIsResizing(true);
5361
if (resizeTimeoutRef.current) {
5462
clearTimeout(resizeTimeoutRef.current);
5563
}
5664
resizeTimeoutRef.current = window.setTimeout(() => {
57-
onResizingChange?.(false);
65+
setIsResizing(false);
5866
}, 150);
5967
};
6068

@@ -68,29 +76,35 @@ export const ResizableLeftPanel = ({
6876
clearTimeout(resizeTimeoutRef.current);
6977
}
7078
};
71-
}, [calculateDefaultSize, onResizingChange, minPanelSizePx, maxPanelSizePx]);
79+
}, [minPanelSizePx, maxPanelSizePx]);
7280

7381
return (
74-
<PanelGroup autoSaveId="docs-left-panel-persistence" direction="horizontal">
75-
<Panel
76-
ref={ref}
77-
order={0}
78-
defaultSize={minPanelSize}
79-
minSize={minPanelSize}
80-
maxSize={maxPanelSize}
82+
<>
83+
<PanelStyle $isResizing={isResizing} />
84+
<PanelGroup
85+
autoSaveId="docs-left-panel-persistence"
86+
direction="horizontal"
8187
>
82-
{leftPanel}
83-
</Panel>
84-
<PanelResizeHandle
85-
style={{
86-
borderRightWidth: '1px',
87-
borderRightStyle: 'solid',
88-
borderRightColor: colorsTokens['greyscale-200'],
89-
width: '1px',
90-
cursor: 'col-resize',
91-
}}
92-
/>
93-
<Panel order={1}>{children}</Panel>
94-
</PanelGroup>
88+
<Panel
89+
ref={ref}
90+
order={0}
91+
defaultSize={minPanelSize}
92+
minSize={minPanelSize}
93+
maxSize={maxPanelSize}
94+
>
95+
{leftPanel}
96+
</Panel>
97+
<PanelResizeHandle
98+
style={{
99+
borderRightWidth: '1px',
100+
borderRightStyle: 'solid',
101+
borderRightColor: colorsTokens['greyscale-200'],
102+
width: '1px',
103+
cursor: 'col-resize',
104+
}}
105+
/>
106+
<Panel order={1}>{children}</Panel>
107+
</PanelGroup>
108+
</>
95109
);
96110
};
Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import { PropsWithChildren, useState } from 'react';
1+
import { PropsWithChildren } from 'react';
2+
import { useTranslation } from 'react-i18next';
23
import { css } from 'styled-components';
34

45
import { Box } from '@/components';
5-
import { MainLayoutContent } from '@/components/main-layout/MainLayoutContent';
6+
import { useCunninghamTheme } from '@/cunningham';
67
import { Header } from '@/features/header';
78
import { HEADER_HEIGHT } from '@/features/header/conf';
9+
import { LeftPanel, ResizableLeftPanel } from '@/features/left-panel';
10+
import { useResponsiveStore } from '@/stores';
11+
12+
import { MAIN_LAYOUT_ID } from './conf';
813

914
type MainLayoutProps = {
1015
backgroundColor?: 'white' | 'grey';
@@ -16,17 +21,8 @@ export function MainLayout({
1621
backgroundColor = 'white',
1722
enableResizablePanel = false,
1823
}: PropsWithChildren<MainLayoutProps>) {
19-
const [isResizing, setIsResizing] = useState(false);
20-
2124
return (
22-
<Box
23-
className={`--docs--main-layout ${isResizing ? 'resizing' : ''}`}
24-
$css={css`
25-
&.resizing * {
26-
transition: none !important;
27-
}
28-
`}
29-
>
25+
<Box className="--docs--main-layout">
3026
<Header />
3127
<Box
3228
$direction="row"
@@ -37,11 +33,85 @@ export function MainLayout({
3733
<MainLayoutContent
3834
backgroundColor={backgroundColor}
3935
enableResizablePanel={enableResizablePanel}
40-
onResizingChange={setIsResizing}
4136
>
4237
{children}
4338
</MainLayoutContent>
4439
</Box>
4540
</Box>
4641
);
4742
}
43+
44+
export interface MainLayoutContentProps {
45+
backgroundColor: 'white' | 'grey';
46+
enableResizablePanel?: boolean;
47+
}
48+
49+
export function MainLayoutContent({
50+
children,
51+
backgroundColor,
52+
enableResizablePanel = false,
53+
}: PropsWithChildren<MainLayoutContentProps>) {
54+
const { isDesktop } = useResponsiveStore();
55+
const { colorsTokens } = useCunninghamTheme();
56+
const { t } = useTranslation();
57+
const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor;
58+
59+
const mainContent = (
60+
<Box
61+
as="main"
62+
role="main"
63+
aria-label={t('Main content')}
64+
id={MAIN_LAYOUT_ID}
65+
$align="center"
66+
$flex={1}
67+
$width="100%"
68+
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
69+
$padding={{
70+
all: isDesktop ? 'base' : '0',
71+
}}
72+
$background={
73+
currentBackgroundColor === 'white'
74+
? colorsTokens['greyscale-000']
75+
: colorsTokens['greyscale-050']
76+
}
77+
$css={css`
78+
overflow-y: auto;
79+
overflow-x: clip;
80+
`}
81+
>
82+
{children}
83+
</Box>
84+
);
85+
86+
if (!isDesktop) {
87+
return (
88+
<>
89+
<LeftPanel />
90+
{mainContent}
91+
</>
92+
);
93+
}
94+
95+
if (enableResizablePanel) {
96+
return (
97+
<ResizableLeftPanel leftPanel={<LeftPanel />}>
98+
{mainContent}
99+
</ResizableLeftPanel>
100+
);
101+
}
102+
103+
return (
104+
<>
105+
<Box
106+
$css={css`
107+
width: 300px;
108+
min-width: 300px;
109+
border-right: 1px solid ${colorsTokens['greyscale-200']};
110+
`}
111+
>
112+
<LeftPanel />
113+
</Box>
114+
{mainContent}
115+
</>
116+
);
117+
}

0 commit comments

Comments
 (0)