Skip to content

Commit e789c48

Browse files
Merge pull request #67 from hipstersmoothie/untested
Fix controlled panel resize bugs and make collapse/expand awaitable
2 parents bbcb53b + 9d2f98a commit e789c48

File tree

14 files changed

+743
-72
lines changed

14 files changed

+743
-72
lines changed

packages/interface/src/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ export interface SharedPanelGroupProps
4545

4646
export interface PanelHandle {
4747
/** Collapse the panel */
48-
collapse: () => void;
48+
collapse: () => Promise<void>;
4949
/** Returns true if the panel is collapsed */
5050
isCollapsed: () => boolean;
5151
/** Expand the panel */
52-
expand: () => void;
52+
expand: () => Promise<void>;
5353
/** Returns true if the panel is expanded */
5454
isExpanded: () => boolean;
5555
/** The id of the panel */

packages/react/src/ReactWindowSplitter.stories.tsx

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from "react";
1+
import React, { useCallback, useMemo, useRef, useState } from "react";
22
import { spring } from "framer-motion";
33
import {
44
PanelGroup,
@@ -9,6 +9,7 @@ import {
99
PanelResizerProps,
1010
} from "./ReactWindowSplitter.js";
1111
import { PanelGroupHandle, PanelHandle } from "@window-splitter/interface";
12+
import { PercentUnit } from "@window-splitter/state";
1213

1314
export default {
1415
title: "Components/React",
@@ -703,3 +704,209 @@ export function StaticAtRest({
703704
</StyledPanelGroup>
704705
);
705706
}
707+
708+
function Flex({
709+
direction = "row",
710+
gap = "0x",
711+
align = "start",
712+
justify = "start",
713+
...props
714+
}: React.HTMLAttributes<HTMLDivElement> & {
715+
direction?: "row" | "column";
716+
gap?: string;
717+
align?: "center" | "start" | "end";
718+
justify?: "center" | "start" | "end" | "between";
719+
}) {
720+
return (
721+
<div
722+
style={{
723+
display: "flex",
724+
flexDirection: direction,
725+
gap,
726+
alignItems: align,
727+
justifyContent: justify === "between" ? "space-between" : justify,
728+
}}
729+
{...props}
730+
/>
731+
);
732+
}
733+
734+
export function SiblingCollapsiblePanels() {
735+
const sidebarHandle = useRef<PanelHandle>(null);
736+
const editorHandle = useRef<PanelHandle>(null);
737+
const previewHandle = useRef<PanelHandle>(null);
738+
const [editModeSize, setEditModeSize] = useState<number[] | undefined>();
739+
const panelGroupRef = useRef<PanelGroupHandle>(null);
740+
const [isEditMode, setIsEditMode] = useState(true);
741+
742+
const handleEditModeToggle = useCallback(async () => {
743+
const newMode = !isEditMode;
744+
setIsEditMode(newMode);
745+
746+
if (!newMode) {
747+
setEditModeSize(panelGroupRef.current?.getPercentageSizes());
748+
749+
await sidebarHandle.current?.collapse();
750+
await editorHandle.current?.collapse();
751+
await previewHandle.current?.expand();
752+
} else {
753+
if (editModeSize) {
754+
panelGroupRef.current?.setSizes(
755+
editModeSize.map((i) => `${i * 100}%` as PercentUnit)
756+
);
757+
}
758+
759+
await sidebarHandle.current?.expand();
760+
await editorHandle.current?.expand();
761+
await previewHandle.current?.expand();
762+
}
763+
}, [editModeSize, isEditMode]);
764+
765+
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(!isEditMode);
766+
const [isEditorCollapsed, setIsEditorCollapsed] = useState(!isEditMode);
767+
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(false);
768+
769+
const handleSidebarCollapseChange = useCallback(
770+
(isCollapsed: boolean) => {
771+
if (isEditMode) {
772+
setIsSidebarCollapsed(isCollapsed);
773+
}
774+
},
775+
[isEditMode]
776+
);
777+
const handleEditorCollapseChange = useCallback(
778+
(isCollapsed: boolean) => {
779+
if (isEditMode) {
780+
setIsEditorCollapsed(isCollapsed);
781+
}
782+
},
783+
[isEditMode]
784+
);
785+
const handlePreviewCollapseChange = useCallback(
786+
(isCollapsed: boolean) => {
787+
if (isEditMode) {
788+
setIsPreviewCollapsed(isCollapsed);
789+
}
790+
},
791+
[isEditMode]
792+
);
793+
794+
return (
795+
<Flex direction="column" style={{ height: "90vh", width: "800px" }}>
796+
<StyledPanelGroup handle={panelGroupRef}>
797+
<StyledPanel
798+
handle={sidebarHandle}
799+
id="sidebar"
800+
min="100px"
801+
default="200px"
802+
collapsible
803+
collapsed={isSidebarCollapsed}
804+
// collapseAnimation="ease-in-out"
805+
onCollapseChange={handleSidebarCollapseChange}
806+
>
807+
<Flex
808+
align="center"
809+
justify="center"
810+
style={{ height: "100%", width: "100%" }}
811+
>
812+
Sidebar
813+
</Flex>
814+
</StyledPanel>
815+
<StyledResizer id="resizer-1" />
816+
<StyledPanel
817+
handle={editorHandle}
818+
id="editor"
819+
min="100px"
820+
default="200px"
821+
collapsible
822+
collapsed={isEditorCollapsed}
823+
// collapseAnimation="ease-in-out"
824+
onCollapseChange={handleEditorCollapseChange}
825+
>
826+
<Flex
827+
align="center"
828+
justify="center"
829+
style={{ height: "100%", width: "100%" }}
830+
>
831+
Editor
832+
</Flex>
833+
</StyledPanel>
834+
<StyledResizer id="resizer-2" />
835+
<StyledPanel
836+
handle={previewHandle}
837+
id="preview"
838+
min="100px"
839+
collapsible
840+
collapsed={isPreviewCollapsed}
841+
// collapseAnimation="ease-in-out"
842+
onCollapseChange={handlePreviewCollapseChange}
843+
>
844+
<Flex
845+
align="center"
846+
justify="center"
847+
style={{ height: "100%", width: "100%" }}
848+
>
849+
Preview
850+
</Flex>
851+
</StyledPanel>
852+
</StyledPanelGroup>
853+
<Flex align="center" justify="between">
854+
<Flex gap="2px">
855+
<button
856+
disabled={!isEditMode}
857+
type="button"
858+
onClick={() =>
859+
setIsSidebarCollapsed(
860+
(prevSidebarCollapsed) => !prevSidebarCollapsed
861+
)
862+
}
863+
style={{
864+
backgroundColor: isSidebarCollapsed ? "blue" : undefined,
865+
}}
866+
>
867+
{isSidebarCollapsed ? "Open sidebar" : "Close sidebar"}
868+
</button>
869+
<button
870+
disabled={!isEditMode}
871+
type="button"
872+
onClick={() =>
873+
setIsEditorCollapsed(
874+
(prevEditorCollapsed) => !prevEditorCollapsed
875+
)
876+
}
877+
style={{
878+
backgroundColor: isEditorCollapsed ? "blue" : undefined,
879+
}}
880+
>
881+
{isEditorCollapsed ? "Open editor" : "Close editor"}
882+
</button>
883+
<button
884+
disabled={!isEditMode}
885+
type="button"
886+
onClick={() =>
887+
setIsPreviewCollapsed(
888+
(prevPreviewCollapsed) => !prevPreviewCollapsed
889+
)
890+
}
891+
style={{
892+
backgroundColor: isPreviewCollapsed ? "blue" : undefined,
893+
}}
894+
>
895+
{isPreviewCollapsed ? "Open preview" : "Close preview"}
896+
</button>
897+
</Flex>
898+
<Flex gap="8px">
899+
<button
900+
type="button"
901+
onClick={handleEditModeToggle}
902+
style={{
903+
backgroundColor: isEditMode ? "blue" : undefined,
904+
}}
905+
>
906+
Edit mode
907+
</button>
908+
</Flex>
909+
</Flex>
910+
</Flex>
911+
);
912+
}

packages/react/src/ReactWindowSplitter.test.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ test("Keyboard interactions with collapsed panels", async () => {
255255
fireEvent.keyDown(resizer2, { key: "Enter" });
256256
await expectTemplate(
257257
handle.current,
258-
"209px 10px 168.953125px 10px 100.03125px"
258+
"209.015625px 10px 167px 10px 101.984375px"
259259
);
260260

261261
fireEvent.keyDown(resizer2, { key: "ArrowLeft" });
@@ -264,22 +264,25 @@ test("Keyboard interactions with collapsed panels", async () => {
264264
fireEvent.keyDown(resizer2, { key: "ArrowLeft" });
265265
await expectTemplate(
266266
handle.current,
267-
"209px 10px 164.96875px 10px 104.03125px"
267+
"209.015625px 10px 163px 10px 105.984375px"
268268
);
269269

270270
fireEvent.keyDown(resizer2, { key: "ArrowLeft", shiftKey: true });
271271
await expectTemplate(
272272
handle.current,
273-
"209px 10px 149.96875px 10px 119.03125px"
273+
"209.03125px 10px 147.984375px 10px 120.984375px"
274274
);
275275

276276
fireEvent.keyDown(resizer2, { key: "Enter" });
277-
await expectTemplate(handle.current, "209px 10px 209px 10px 60px");
277+
await expectTemplate(
278+
handle.current,
279+
"209.03125px 10px 208.96875px 10px 60px"
280+
);
278281

279282
fireEvent.keyDown(resizer2, { key: "Enter" });
280283
await expectTemplate(
281284
handle.current,
282-
"209px 10px 149.96875px 10px 119.03125px"
285+
"209.046875px 10px 144.9375px 10px 124.015625px"
283286
);
284287
});
285288

packages/react/src/ReactWindowSplitter.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,11 @@ const PanelVisible = React.forwardRef<
612612
}
613613
});
614614

615-
const contraintChanged =
615+
const constraintChanged =
616616
panel && haveConstraintsChangedForPanel(panelProp, panel);
617617

618618
const onConstraintChange = useEffectEvent(() => {
619-
if (contraintChanged) {
619+
if (constraintChanged) {
620620
send({
621621
type: "updateConstraints",
622622
data: { ...panelProp, id: panel.id },
@@ -625,10 +625,10 @@ const PanelVisible = React.forwardRef<
625625
});
626626

627627
React.useEffect(() => {
628-
if (contraintChanged) {
628+
if (constraintChanged) {
629629
onConstraintChange();
630630
}
631-
}, [send, contraintChanged, onConstraintChange]);
631+
}, [send, constraintChanged, onConstraintChange]);
632632

633633
// For controlled collapse we track if the `collapse` prop changes
634634
// and update the state machine if it does.
@@ -655,16 +655,19 @@ const PanelVisible = React.forwardRef<
655655
useImperativeHandle(handle || fallbackHandleRef, () => {
656656
return {
657657
getId: () => panelId,
658-
collapse: () => {
658+
collapse: async () => {
659659
if (collapsible) {
660-
// TODO: setting controlled here might be wrong
661-
send({ type: "collapsePanel", panelId, controlled: true });
660+
await new Promise<void>((resolve) => {
661+
send({ type: "collapsePanel", panelId, controlled: true, resolve });
662+
});
662663
}
663664
},
664665
isCollapsed: () => Boolean(collapsible && panel?.collapsed),
665-
expand: () => {
666+
expand: async () => {
666667
if (collapsible) {
667-
send({ type: "expandPanel", panelId, controlled: true });
668+
await new Promise<void>((resolve) => {
669+
send({ type: "expandPanel", panelId, controlled: true, resolve });
670+
});
668671
}
669672
},
670673
isExpanded: () => Boolean(collapsible && !panel?.collapsed),
@@ -817,12 +820,12 @@ const PanelResizerVisible = React.forwardRef<
817820
},
818821
});
819822

820-
const contraintChanged =
823+
const constraintChanged =
821824
panelHandle &&
822825
haveConstraintsChangedForPanelHandle(panelHandleProp, panelHandle);
823826

824827
const onConstraintChange = useEffectEvent(() => {
825-
if (contraintChanged) {
828+
if (constraintChanged) {
826829
send({
827830
type: "updateConstraints",
828831
data: { ...panelHandleProp, id: handleId },
@@ -831,10 +834,10 @@ const PanelResizerVisible = React.forwardRef<
831834
});
832835

833836
React.useEffect(() => {
834-
if (contraintChanged) {
837+
if (constraintChanged) {
835838
onConstraintChange();
836839
}
837-
}, [send, contraintChanged, onConstraintChange]);
840+
}, [send, constraintChanged, onConstraintChange]);
838841

839842
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
840843
if (e.key === "Enter" && collapsiblePanel) {

packages/solid/src/SolidWIndowSplitter.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,32 @@ export function Panel(props: PanelProps) {
379379
() => props.handle,
380380
() => ({
381381
getId: () => panelId(),
382-
collapse: () => {
383-
if (!panel().collapsible) return;
384-
send?.({ type: "collapsePanel", panelId: panelId(), controlled: true });
382+
collapse: async () => {
383+
if (panel().collapsible) {
384+
// eslint-disable-next-line solid/reactivity
385+
return await new Promise<void>((resolve) => {
386+
send?.({
387+
type: "collapsePanel",
388+
panelId: panelId(),
389+
controlled: true,
390+
resolve,
391+
});
392+
});
393+
}
385394
},
386395
isCollapsed: () => Boolean(panel().collapsible && panel().collapsed),
387-
expand: () => {
388-
if (!panel().collapsible) return;
389-
send?.({ type: "expandPanel", panelId: panelId(), controlled: true });
396+
expand: async () => {
397+
if (panel().collapsible) {
398+
// eslint-disable-next-line solid/reactivity
399+
return await new Promise<void>((resolve) => {
400+
send?.({
401+
type: "expandPanel",
402+
panelId: panelId(),
403+
controlled: true,
404+
resolve,
405+
});
406+
});
407+
}
390408
},
391409
isExpanded: () => Boolean(panel().collapsible && !panel().collapsed),
392410
getPixelSize: () => {

0 commit comments

Comments
 (0)