Skip to content
This repository was archived by the owner on Jan 12, 2026. It is now read-only.

Commit 415fe1c

Browse files
Add collapse and resize
1 parent d6f0c13 commit 415fe1c

File tree

3 files changed

+164
-9
lines changed

3 files changed

+164
-9
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import ChevronLeft from '@mui/icons-material/ChevronLeft';
2+
3+
import IconButton from '@mui/material/IconButton';
4+
import useTheme from '@mui/material/styles/useTheme';
5+
6+
interface Props {
7+
collapsed: boolean;
8+
onClick: () => void;
9+
}
10+
11+
export function CollapseToggle({ collapsed, onClick }: Props) {
12+
const { transitions } = useTheme();
13+
14+
return (
15+
<IconButton
16+
className={CollapseToggle.className}
17+
onClick={onClick}
18+
sx={{
19+
'&:hover': {
20+
bgcolor: 'nav.evidentBg',
21+
},
22+
bgcolor: 'nav.evidentBg',
23+
border: '1px solid',
24+
borderColor: 'nav.evidentDivider',
25+
position: 'absolute',
26+
right: 0,
27+
top: '50%',
28+
transform: collapsed ? 'translate(50%, -50%) rotate(180deg)' : 'translate(50%, -50%)',
29+
transition: `opacity 150ms ${transitions.easing.easeInOut}, transform 150ms ${transitions.easing.easeInOut}`,
30+
zIndex: 999,
31+
}}
32+
>
33+
<ChevronLeft />
34+
</IconButton>
35+
);
36+
}
37+
38+
CollapseToggle.className = 'shared-collapse-toggle';
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
'use client';
2+
3+
import React, { TransitionEvent, useEffect, useRef, useState } from 'react';
4+
5+
import Box, { BoxProps } from '@mui/material/Box';
6+
import Stack from '@mui/material/Stack';
7+
8+
import { duration } from '@mui/material/styles/createTransitions';
9+
10+
import useTheme from '@mui/material/styles/useTheme';
11+
12+
import { CollapseToggle } from './collapseToggle';
13+
14+
interface Props extends Omit<BoxProps, 'width' | 'minWidth'> {
15+
collapsible?: boolean;
16+
minWidth?: number;
17+
resizable?: boolean;
18+
width?: number;
19+
}
20+
21+
export function CollapsibleResizableContainer({
22+
children,
23+
collapsible,
24+
resizable,
25+
...props
26+
}: Props) {
27+
const { transitions } = useTheme();
28+
const [width, setWidth] = useState<number | undefined>(undefined); // Initial width of the container
29+
const containerRef = useRef<HTMLDivElement>(null);
30+
const [isCollapsed, setIsCollapsed] = useState(false);
31+
const [applyMinWidth, setApplyMinWidth] = useState(false);
32+
const disableTransition = useRef(false);
33+
34+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
35+
if (!containerRef.current) {
36+
return;
37+
}
38+
39+
const startX = e.clientX;
40+
const startWidth = width ?? containerRef.current?.clientWidth;
41+
disableTransition.current = true;
42+
43+
const onMouseMove = (e: MouseEvent) => {
44+
setWidth(startWidth + (e.clientX - startX));
45+
};
46+
47+
const onMouseUp = () => {
48+
document.removeEventListener('mousemove', onMouseMove);
49+
document.removeEventListener('mouseup', onMouseUp);
50+
document.body.style.userSelect = '';
51+
disableTransition.current = false;
52+
};
53+
54+
document.addEventListener('mousemove', onMouseMove);
55+
document.addEventListener('mouseup', onMouseUp);
56+
document.body.style.userSelect = 'none';
57+
};
58+
59+
const handleTransitionEnd = (event: TransitionEvent<HTMLDivElement>) => {
60+
if (event.propertyName !== 'width') {
61+
return;
62+
}
63+
64+
if (!isCollapsed) {
65+
setApplyMinWidth(true);
66+
}
67+
};
68+
69+
useEffect(() => {
70+
if (isCollapsed) {
71+
setApplyMinWidth(false);
72+
}
73+
}, [isCollapsed]);
74+
75+
const parsedWidth = Math.max(width ?? props.width ?? 0, props.minWidth ?? 0);
76+
77+
return (
78+
<Box
79+
ref={containerRef}
80+
position="relative"
81+
{...props}
82+
minWidth={applyMinWidth ? (isCollapsed ? 24 : props.minWidth) : undefined}
83+
onTransitionEnd={handleTransitionEnd}
84+
sx={{
85+
...props.sx,
86+
transition: disableTransition.current
87+
? undefined
88+
: `width ${duration.standard}ms ${transitions.easing.easeInOut}`,
89+
[`.${CollapseToggle.className}`]: {
90+
opacity: 0,
91+
},
92+
[`&:hover .${CollapseToggle.className}`]: {
93+
opacity: 1,
94+
},
95+
}}
96+
width={isCollapsed ? 24 : parsedWidth}
97+
>
98+
<Stack flex="1 1 auto" minHeight={0} overflow="hidden">
99+
<Stack flex="1 1 auto" minHeight={0} overflow="visible" width={parsedWidth}>
100+
{children}
101+
</Stack>
102+
</Stack>
103+
{resizable && (
104+
<Box
105+
bottom={0}
106+
height="100%"
107+
onMouseDown={handleMouseDown}
108+
position="absolute"
109+
right={-5}
110+
sx={{ cursor: 'ew-resize' }}
111+
title="Resize"
112+
top={0}
113+
width={10}
114+
zIndex={1}
115+
/>
116+
)}
117+
{collapsible && (
118+
<CollapseToggle collapsed={isCollapsed} onClick={() => setIsCollapsed(!isCollapsed)} />
119+
)}
120+
</Box>
121+
);
122+
}

src/components/form/configUIForm.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { FormFooter, FormWrapper, GuideComponent } from '../display';
2828
import { PluginTable } from '../inputs';
2929
import { Sections } from './sections/sections';
3030
import { PluginConfig } from '../../types';
31+
import { CollapsibleResizableContainer } from '../controls/collapsibleResizableContainer';
3132

3233
const FormStepper = React.lazy(() =>
3334
import('../display/formStepper').then((module) => ({
@@ -189,13 +190,7 @@ export function ConfigUIForm({ prepareSubmitValues }: ConfigUIFormProps) {
189190
<ThemeProvider theme={theme}>
190191
<CssBaseline />
191192
<FormProvider {...form}>
192-
<Stack
193-
direction="row"
194-
sx={{
195-
gap: 3,
196-
flexWrap: 'wrap',
197-
}}
198-
>
193+
<Stack direction="row" spacing={3}>
199194
<Box
200195
sx={{
201196
flex: '1 1 0',
@@ -251,9 +246,9 @@ export function ConfigUIForm({ prepareSubmitValues }: ConfigUIFormProps) {
251246
</Stack>
252247
</form>
253248
</Box>
254-
<Box sx={{ width: 500, minWidth: 360 }}>
249+
<CollapsibleResizableContainer collapsible={true} minWidth={360} width={500}>
255250
<GuideComponent pluginUiMessageHandler={pluginUiMessageHandler} />
256-
</Box>
251+
</CollapsibleResizableContainer>
257252
</Stack>
258253
</FormProvider>
259254
</ThemeProvider>

0 commit comments

Comments
 (0)