Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-panel resize support #274

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ Supported input methods include mouse, touch, and keyboard (via [Window Splitter

No. Pixel-based constraints [added significant complexity](https://github.com/bvaughn/react-resizable-panels/pull/176) to the initialization and validation logic and so I've decided not to support them. You may be able to implement a version of this yourself following [a pattern like this](https://github.com/bvaughn/react-resizable-panels/issues/46#issuecomment-1368108416) but it is not officially supported by this library.

### How can I fix layout/sizing problems with conditionally rendered panels?

The `Panel` API doesn't _require_ `id` and `order` props because they aren't necessary for static layouts. When panels are conditionally rendered though, it's best to supply these values.

```tsx
<PanelGroup direction="horizontal">
{renderSideBar && (
<>
<Panel id="sidebar" minSize={25} order={1}>
<Sidebar />
</Panel>
<PanelResizeHandle />
</>
)}
<Panel minSize={25} order={2}>
<Main />
</Panel>
</PanelGroup>
```

### Can a attach a ref to the DOM elements?

No. I think exposing two refs (one for the component's imperative API and one for a DOM element) would be awkward. This library does export several utility methods for accessing the underlying DOM elements though. For example:
Expand Down Expand Up @@ -74,31 +94,11 @@ This likely means that you haven't applied any CSS to style the resize handles.
<PanelResizeHandle className="w-2 bg-blue-800" />
```

### How can I fix layout/sizing problems with conditionally rendered panels?

The `Panel` API doesn't _require_ `id` and `order` props because they aren't necessary for static layouts. When panels are conditionally rendered though, it's best to supply these values.

```tsx
<PanelGroup direction="horizontal">
{renderSideBar && (
<>
<Panel id="sidebar" minSize={25} order={1}>
<Sidebar />
</Panel>
<PanelResizeHandle />
</>
)}
<Panel minSize={25} order={2}>
<Main />
</Panel>
</PanelGroup>
```

### How can I use persistent layouts with SSR?

By default, this library uses `localStorage` to persist layouts. With server rendering, this can cause a flicker when the default layout (rendered on the server) is replaced with the persisted layout (in `localStorage`). The way to avoid this flicker is to also persist the layout with a cookie like so:

##### Server component
#### Server component

```tsx
import ResizablePanels from "@/app/ResizablePanels";
Expand All @@ -116,7 +116,7 @@ export function ServerComponent() {
}
```

##### Client component
#### Client component

```tsx
"use client";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ if (process.env.DEBUG) {
headless: false,

launchOptions: {
slowMo: DEBUG ? 250 : undefined,
slowMo: DEBUG ? 50 : undefined,
},
};
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-resizable-panels-website/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
--color-link: #dcadff;
--color-panel-background: #192230;
--color-panel-background-alternate: #202124;
--color-resize-bar-drag: #39414d;
--color-resize-bar-hover: #39414d66;
--color-solid-resize-bar: #39414d;
--color-scroll-thumb: #39414d;
--color-warning-background: #7400cc;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,18 @@
.ResizeHandleOuter {
flex: 0 0 1.75rem;
.ResizeHandle {
flex: 0 0 0.25rem;
display: flex;
justify-content: stretch;
align-items: stretch;
padding: 0.5rem;
outline: none;

--background-color: transparent;
}
.ResizeHandleOuter[data-resize-handle-active],
.ResizeHandleInner[data-collapsed] {
--background-color: var(--color-solid-resize-bar);
}

@media (max-width: 500px) {
.ResizeHandleOuter {
flex: 0 0 2.5rem;
}
}

.ResizeHandleInner {
flex: 1;
border-radius: 0.75rem;
background-color: var(--background-color);
transition: background-color 0.2s linear;
position: relative;
}

.ResizeHandleInner::after {
height: 1rem;
width: 1rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
color: var(--color-solid-resize-bar);
position: absolute;
left: calc(50% - 0.5rem);
top: calc(50% - 0.5rem);
}

.HorizontalIcon,
.VerticalIcon {
color: var(--color-solid-resize-bar);
position: absolute;
left: calc(50% - 0.5rem);
top: calc(50% - 0.5rem);
.ResizeHandle[data-resize-handle-active="keyboard"],
.ResizeHandle[data-resize-handle-active="pointer"] {
background-color: var(--color-resize-bar-drag);
}

.ResizeHandleOuter[data-panel-group-direction="horizontal"] .HorizontalIcon,
.ResizeHandleOuter[data-panel-group-direction="vertical"] .VerticalIcon {
display: block;
}
.ResizeHandleOuter[data-panel-group-direction="vertical"] .HorizontalIcon,
.ResizeHandleOuter[data-panel-group-direction="horizontal"] .VerticalIcon {
display: none;
}
.ResizeHandleOuter[data-resize-handle-active] .HorizontalIcon,
.ResizeHandleOuter[data-resize-handle-active] .VerticalIcon {
display: none;
@media (pointer: coarse) {
.ResizeHandle {
flex: 0 0 1rem;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { PanelResizeHandle } from "react-resizable-panels";

import Icon from "./Icon";
import styles from "./ResizeHandle.module.css";

export function ResizeHandle({
Expand All @@ -14,16 +13,8 @@ export function ResizeHandle({
}) {
return (
<PanelResizeHandle
className={[styles.ResizeHandleOuter, className].join(" ")}
className={[styles.ResizeHandle, className].join(" ")}
id={id}
>
<div
className={styles.ResizeHandleInner}
data-collapsed={collapsed || undefined}
>
<Icon className={styles.HorizontalIcon} type="resize-horizontal" />
<Icon className={styles.VerticalIcon} type="resize-vertical" />
</div>
</PanelResizeHandle>
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.VisibleCursor {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 2rem;
height: 2rem;
border: 2px dotted #ffffffaa;
border-radius: 1rem;
margin-left: -1rem;
margin-top: -1rem;
transition: background-color 250ms;
}
.VisibleCursor[data-state="down"] {
background-color: #ff000066;
}
.VisibleCursor[data-state="up"] {
background-color: #00000066;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useLayoutEffect } from "react";
import styles from "./VisibleCursor.module.css";
import { getResizeEventCoordinates } from "../../../react-resizable-panels/src/utils/events/getResizeEventCoordinates";

export function VisibleCursor() {
useLayoutEffect(() => {
const element = document.createElement("div");
element.classList.add(styles.VisibleCursor!);
element.setAttribute("data-state", "up");

document.body.appendChild(element);

const onMouseDown = () => {
element.setAttribute("data-state", "down");
};

const onMouseMove = (event: MouseEvent | TouchEvent) => {
const { x, y } = getResizeEventCoordinates(event);
element.style.left = x + "px";
element.style.top = y + "px";
};

const onMouseUp = () => {
element.setAttribute("data-state", "up");
};

document.addEventListener("mousedown", onMouseDown, true);
document.addEventListener("mousemove", onMouseMove, true);
document.addEventListener("mouseup", onMouseUp, true);
document.addEventListener("touchcancel", onMouseUp, true);
document.addEventListener("touchend", onMouseUp, true);
document.addEventListener("touchmove", onMouseMove, true);
document.addEventListener("touchstart", onMouseDown, true);

return () => {
document.body.removeChild(element);

document.removeEventListener("mousedown", onMouseDown, true);
document.removeEventListener("mousemove", onMouseMove, true);
document.removeEventListener("mouseup", onMouseUp, true);
document.removeEventListener("touchcancel", onMouseUp, true);
document.removeEventListener("touchend", onMouseUp, true);
document.removeEventListener("touchmove", onMouseMove, true);
document.removeEventListener("touchstart", onMouseDown, true);
};
});

return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { urlPanelGroupToPanelGroup, urlToUrlData } from "../../utils/UrlData";
import DebugLog, { ImperativeDebugLogHandle } from "../examples/DebugLog";
import "./styles.css";
import styles from "./styles.module.css";
import { VisibleCursor } from "../../components/VisibleCursor";

// Special route that can be configured via URL parameters.

Expand Down Expand Up @@ -296,6 +297,7 @@ export default function Page() {
return (
<ErrorBoundary>
<EndToEndTesting />
<VisibleCursor />
</ErrorBoundary>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
background-color: var(--color-button-background-hover);
}

@media (max-width: 500px) {
@media (pointer: coarse) {
.ResizeHandle,
.ResizeHandleCollapsed {
width: 1rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export default function NestedRoute() {
<Example
code={CODE}
exampleNode={<Content />}
headerNode={<p>This example shows nested groups.</p>}
headerNode={
<p>
This example shows nested groups. Click near the intersection of two
groups to resize in multiple directions at once.
</p>
}
title="Nested groups"
/>
);
Expand Down
Loading
Loading