bvaughn / react-resizable-panels

https://react-resizable-panels.vercel.app/
MIT License
3.58k stars 124 forks source link

Feature Request: Greedy Panels #318

Closed BrodyRas closed 3 months ago

BrodyRas commented 3 months ago

Problem

I'm creating a list of collapsible hierarchies (similar to the left panel in VS Code), and this package is working well for it! There is one slight issue in functionality, which I want to discuss:

Seemingly, the "preference" for which panel takes up the remaining space when a panel collapsed is the next panel in order i.e. if <Panel order=3> collapses, then <Panel order=4> takes the remaining space. I'm wondering if there's a way to customize this behavior. Giving preference to the previous panel, instead of the next one, would provide the functionality I'm looking for: collapsing a panel shoves it to the bottom of the PanelGroup.

I tried to do some trickery to make this work (i.e. giving the PanelGroup flexDirection: column-reverse, reversing the order values, etc), but that didn't quite work (reversed the dragging direction 😅). If there's a quick fix I'm missing, I'll be happy to learn what it is ❤️ Thanks for the help!

Feature Request

Create an option for PanelGroups which can reverse the direction of preferred panels on collapse/expansion, or provide the Panels a "greediness" option, which controls how much of the new space they take

Current Behavior

Collapsing a panel "pulls up" the panel below it, giving it's space to the next panel. current

Desired Behavior

Collapsing a panel "pushes" it down, giving it's space to the previous panel. desired

bvaughn commented 3 months ago

Collapsing a panel "pushes" it down, giving it's space to the previous panel.

Seems like the video you showed from VS Code should already be possible using panelRef.collapse?

BrodyRas commented 3 months ago

I am using panelRef.collapse, it works great! But the issue is how the remaining space is filled. VS Code "pushes down", giving space to the panel above. This package seems to "pull up", giving space to the panel below.

BrodyRas commented 3 months ago

Watch the first GIF, while I'm toggling the "Tab Hierarchy" panel: I'd expect that tab to go down to the bottom, but instead it pulls up the "Third Hierarchy" tab.

bvaughn commented 3 months ago

Ah, I see what you're saying.

Have you consider using panelGroup.setLayout() for this? I think that would let you do what you're describing.

BrodyRas commented 3 months ago

Oh, that makes sense! On click, I'll add the current panel's percentage to the previous panel's percentage, and zero out the current panel. Thanks!

bvaughn commented 3 months ago

Yup! :)

BrodyRas commented 3 months ago

Sorry to reopen, I think the layout is working, but for some reason, it seems like the panels are 1 render behind after using setLayout(). Is there another step to trigger the render?

See the video: clicking on the "Tab Hierarchy" calls setLayout() with the updated layout (as described in the previous comment). But nothing happens visually. Then, when I attempt to resize the panel, it jumps to where it should've been: 0.

lateRender

bvaughn commented 3 months ago

Huh. Can you share a repro?

BrodyRas commented 3 months ago

Here's a reproduction with the simplest case:

const DummyPanels = () => {
    const groupRef = useRef(null)

    // Give the clicked panel's percentage to the previous panel
    const handleClick = (order: number) => {
        const anyGroupRef = groupRef as any
        const layout = anyGroupRef.current.getLayout()
        const currentSize = layout[order]
        layout[order] = 0
        layout[order - 1] += currentSize
        anyGroupRef.current.setLayout(layout)
    }

    return <PanelGroup ref={groupRef} direction='vertical'>
        <Panel order={0} collapsible><></></Panel>
        <CollapseOrDragHandle title='First Panel' onMouseDown={() => { handleClick(1) }} />
        <Panel order={1} collapsible>First Panel's content</Panel>
        <CollapseOrDragHandle title='Second Panel' onMouseDown={() => { handleClick(2) }} />
        <Panel order={2} collapsible>Second Panel's content</Panel>
        <CollapseOrDragHandle title='Third Panel' onMouseDown={() => { handleClick(3) }} />
        <Panel order={3} collapsible>Third Panel's content</Panel>
    </PanelGroup>
}

interface CollapseOrDragHandleProps {
    title: string
    onMouseDown: any
}

const CollapseOrDragHandle = ({ title, onMouseDown }: CollapseOrDragHandleProps) => {
    return <>
        {/* Drag to Resize */}
        <PanelResizeHandle>
            <Box sx={{ height: '10px', backgroundColor: 'gray' }} />
        </PanelResizeHandle>
        {/* Click to Collapse */}
        <Box sx={{ backgroundColor: 'gray', cursor: 'pointer' }} onMouseDown={onMouseDown}>{title}</Box>
    </>
}

The "handle" is two Boxes: dragging the top will resize the panel, and clicking the bottom will collapse the panel. Notice how clicking will technically trigger a new layout, but you don't see it until making another change (i.e. dragging that panel, or another panel)

repro

BrodyRas commented 3 months ago

If there's a better way to structure the clickable handles w/ title text, I'm happy to hear it too ❤️ should the clickable title be a part of the panel itself, and the resize handle only manages resizing?

cloudkite commented 3 months ago

@BrodyRas setLayout does an equality check see https://github.com/bvaughn/react-resizable-panels/blob/ef22e598ecdd100766a40f0a43e4d01bde6341ce/packages/react-resizable-panels/src/PanelGroup.ts#L185

So I believe you might need to pass a new array to setLayout ie

let nextLayout = [...group.getLayout()];
// modify nextLayout
nextLayout[0] = 20;
group.setLayout(nextLayout);
bvaughn commented 3 months ago

Yeah, mutating state is always a good idea to avoid because of equality checks like this. Should work okay if you pass it a new layout array!

BrodyRas commented 3 months ago

D'oh, that makes sense. Silly mistake, I definitely thought I was just grabbing the values of the array, not mutating the group's state 😅

That fixed it 👍 Thanks so much for the help!

bvaughn commented 3 months ago

Glad you got it sorted out!