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

Rewrite layout algorithm internals [draft] #182

Merged
merged 35 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6620dfa
Installed Jest and deps
bvaughn Aug 14, 2023
d224696
Add Jest
bvaughn Aug 14, 2023
2cda40e
Stashing in-progress changes
bvaughn Aug 14, 2023
a2929c1
Update PNPM installation for GH Actions
bvaughn Aug 14, 2023
4904c7a
Partially migrated Panel/PanelGroup/PanelResizeHandle to new internal…
bvaughn Aug 27, 2023
9b4246d
Fixed ESLint config linkage to TSConfig file
bvaughn Aug 27, 2023
15bb5c5
Reorgnanized util files
bvaughn Aug 27, 2023
bc8f249
Partially migrate imperative examples to new Panel and PanelGroup
bvaughn Oct 21, 2023
9806d6b
Implement PanelGroup setLayout API
bvaughn Oct 21, 2023
30ed78c
Wired up onLayout and onResize/onExpand/onCollapse callbacks
bvaughn Oct 21, 2023
a9edc8f
Added unit tests for calculateUnsafeDefaultLayout
bvaughn Oct 21, 2023
e1a97bf
Added tests for PanelGroup DEV warnings and adjustLayoutByDelta util …
bvaughn Oct 21, 2023
5c544bb
Add more tests for DEV warnings and imperative APIs
bvaughn Oct 22, 2023
ebe73f1
Migrate last remaining docs page to new API
bvaughn Oct 22, 2023
2f9afe0
Added more imperative Panel API tests and fixed and edge case
bvaughn Oct 22, 2023
441b1c7
Delete old components and outdated spec tests
bvaughn Oct 22, 2023
b2d8d4f
Revert lockfile changes to see if it fixes CI/TypeScript
bvaughn Oct 22, 2023
1b71562
Try to use NPM overrides to lock @lezer/common package
bvaughn Oct 22, 2023
cb9be11
CI scripts use frozen lockfiles
bvaughn Oct 22, 2023
a9bf439
Regenerated lockfile
bvaughn Oct 22, 2023
39ad041
autoInstallPeers: false
bvaughn Oct 22, 2023
c331ce0
Fixed up some e2e tets
bvaughn Oct 22, 2023
adc45fd
Fixed up some e2e tets
bvaughn Oct 22, 2023
5d66627
Fixed more ARIA issues and e2e tests
bvaughn Oct 22, 2023
93e24e5
Fixed e2e tests broken by ARIA selector changes
bvaughn Nov 5, 2023
1807769
Filled in some more dev warning tests
bvaughn Nov 5, 2023
03aa6c8
Removed bad import
bvaughn Nov 5, 2023
d26c1bd
Added more panel validation tests
bvaughn Nov 5, 2023
38be49a
Filled in more unit tests
bvaughn Nov 12, 2023
87a0a8b
Fixed regression in serialization util key function
bvaughn Nov 12, 2023
e539c87
Fixed some of the e2e tests
bvaughn Nov 12, 2023
69de414
Added ResizeObserver
bvaughn Nov 12, 2023
0333dac
Prettier
bvaughn Nov 12, 2023
82c0085
Filled in CHANGELOG
bvaughn Nov 12, 2023
f6b66e0
CHANGELOG edit
bvaughn Nov 12, 2023
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
4 changes: 2 additions & 2 deletions .github/workflows/e2e-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
with:
version: 7
- name: Install dependencies
run: pnpm install -r
run: pnpm install --frozen-lockfile --recursive
- name: Install Playwright dependencies
run: npx playwright install
- name: Build NPM package
run: pnpm prerelease
- name: Run Playwright tests
run: cd packages/react-resizable-panels-website && pnpm test:e2e
run: cd packages/react-resizable-panels-website && pnpm test:e2e
2 changes: 1 addition & 1 deletion .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
with:
version: 7
- name: Install dependencies
run: pnpm install -r
run: pnpm install --frozen-lockfile --recursive
- name: Run ESLint
run: pnpm lint
17 changes: 17 additions & 0 deletions .github/workflows/jest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: "Jest"
on: [pull_request]
jobs:
tests-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: pnpm/action-setup@v2
with:
version: 7
- name: Install dependencies
run: pnpm install --frozen-lockfile --recursive
- name: Build NPM packages
run: pnpm run prerelease
- name: Run tests
run: cd packages/react-resizable-panels && pnpm run test
2 changes: 1 addition & 1 deletion .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
with:
version: 7
- name: Install dependencies
run: pnpm install -r
run: pnpm install --frozen-lockfile --recursive
- name: Run Prettier
run: pnpm run prettier:ci
2 changes: 1 addition & 1 deletion .github/workflows/typescript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
with:
version: 7
- name: Install dependencies
run: pnpm install -r
run: pnpm install --frozen-lockfile --recursive
- name: Build NPM package
run: pnpm prerelease
- name: Run TypeScript
Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
auto-install-peers=false
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<img width="328" alt="React Resizable Panels logo" src="https://user-images.githubusercontent.com/29597/210075327-faeb4ca8-31df-4dc8-a649-01d0ee3cd315.png" />

## react-resizable-panels

React components for resizable panel groups/layouts.

* [View the website](https://react-resizable-panels.vercel.app/)
* [Try it on CodeSandbox](https://codesandbox.io/s/react-resizable-panels-zf7hwd)
* [Read the documentation](https://github.com/bvaughn/react-resizable-panels/tree/main/packages/react-resizable-panels)
* [View the changelog](https://github.com/bvaughn/react-resizable-panels/blob/main/packages/react-resizable-panels/CHANGELOG.md)
- [View the website](https://react-resizable-panels.vercel.app/)
- [Try it on CodeSandbox](https://codesandbox.io/s/react-resizable-panels-zf7hwd)
- [Read the documentation](https://github.com/bvaughn/react-resizable-panels/tree/main/packages/react-resizable-panels)
- [View the changelog](https://github.com/bvaughn/react-resizable-panels/blob/main/packages/react-resizable-panels/CHANGELOG.md)

Supported input methods include mouse, touch, and keyboard (via [Window Splitter](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)).

Expand All @@ -26,13 +27,13 @@ The `Panel` API doesn't _require_ `id` and `order` props because they aren't nec
<PanelGroup direction="horizontal">
{renderSideBar && (
<>
<Panel id="sidebar" minSize={25} order={1}>
<Panel id="sidebar" minSizePercentage={25} order={1}>
<Sidebar />
</Panel>
<PanelResizeHandle />
</>
)}
<Panel minSize={25} order={2}>
<Panel minSizePercentage={25} order={2}>
<Main />
</Panel>
</PanelGroup>
Expand All @@ -43,6 +44,7 @@ The `Panel` API doesn't _require_ `id` and `order` props because they aren't nec
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

```tsx
import ResizablePanels from "@/app/ResizablePanels";
import { cookies } from "next/headers";
Expand All @@ -60,32 +62,29 @@ export function ServerComponent() {
```

##### Client component

```tsx
"use client";

import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";

export function ClientComponent({
defaultLayout = [33, 67]
defaultLayout = [33, 67],
}: {
defaultLayout: number[] | undefined
defaultLayout: number[] | undefined;
}) {
const onLayout = (sizes: number[]) => {
document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`;
};

return (
<PanelGroup direction="horizontal" onLayout={onLayout}>
<Panel defaultSize={defaultLayout[0]}>
{/* ... */}
</Panel>
<Panel defaultSizePercentage={defaultLayout[0]}>{/* ... */}</Panel>
<PanelResizeHandle className="w-2 bg-blue-800" />
<Panel defaultSize={defaultLayout[1]}>
{/* ... */}
</Panel>
<Panel defaultSizePercentage={defaultLayout[1]}>{/* ... */}</Panel>
</PanelGroup>
);
}
```

A demo of this is available [here](https://github.com/bvaughn/react-resizable-panels-demo-ssr).
A demo of this is available [here](https://github.com/bvaughn/react-resizable-panels-demo-ssr).
24 changes: 14 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,23 @@
"typescript:watch": "tsc --noEmit --watch"
},
"devDependencies": {
"@babel/preset-typescript": "^7.21.5",
"@playwright/test": "^1.35.0",
"@types/node": "^18.15.11",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"@typescript-eslint/type-utils": "^5.57.0",
"eslint": "^8.37.0",
"@babel/preset-typescript": "^7.22.5",
"@playwright/test": "^1.37.0",
"@types/jest": "^29.5.3",
"@types/node": "^18.17.5",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@typescript-eslint/type-utils": "^5.62.0",
"eslint": "^8.47.0",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
"parcel": "^2.9.3",
"prettier": "latest",
"process": "^0.11.10",
"typescript": ">=3.0.0"
"ts-jest": "^29.1.1",
"typescript": "^5.1.6"
},
"dependencies": {
"@parcel/config-default": "^2.9.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PanelResizeHandle } from "react-resizable-panels";
import Icon from "./Icon";
import styles from "./ResizeHandle.module.css";

export default function ResizeHandle({
export function ResizeHandle({
className = "",
collapsed = false,
id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
import {
ImperativePanelGroupHandle,
ImperativePanelHandle,
Units,
getAvailableGroupSizePixels,
MixedSizes,
} from "react-resizable-panels";

import { urlPanelGroupToPanelGroup, urlToUrlData } from "../../utils/UrlData";
Expand Down Expand Up @@ -55,8 +54,10 @@ function EndToEndTesting() {
const [panelIds, setPanelIds] = useState<string[]>([]);
const [panelGroupId, setPanelGroupId] = useState("");
const [panelGroupIds, setPanelGroupIds] = useState<string[]>([]);
const [size, setSize] = useState(0);
const [units, setUnits] = useState("");
const [sizePercentage, setSizePercentage] = useState<number | undefined>(
undefined
);
const [sizePixels, setSizePixels] = useState<number | undefined>(undefined);
const [layoutString, setLayoutString] = useState("");

const debugLogRef = useRef<ImperativeDebugLogHandle>(null);
Expand All @@ -80,10 +81,6 @@ function EndToEndTesting() {
);
setPanelGroupIds(panelGroupIds);
setPanelGroupId(panelGroupIds[0]);

// const panelGroupElement = document.querySelector("[data-panel-group]")!;
// const units = panelGroupElement.getAttribute("data-panel-group-units")!;
// setUnits(units);
};

window.addEventListener("popstate", (event) => {
Expand Down Expand Up @@ -113,12 +110,11 @@ function EndToEndTesting() {
panelId
) as ImperativePanelHandle;
if (panel != null) {
const percentage = panel.getSize("percentages");
const pixels = panel.getSize("pixels");
const { sizePercentage, sizePixels } = panel.getSize();

panelElement.textContent = `${percentage.toFixed(
panelElement.textContent = `${sizePercentage.toFixed(
1
)}%\n${pixels.toFixed(1)}px`;
)}%\n${sizePixels.toFixed(1)}px`;
}
}
}, 0);
Expand Down Expand Up @@ -174,12 +170,16 @@ function EndToEndTesting() {

const onSizeInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.value;
setSize(parseFloat(value));
};

const onUnitsSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
const value = event.currentTarget.value;
setUnits(value);
if (value.endsWith("%")) {
setSizePercentage(parseFloat(value));
setSizePixels(undefined);
} else if (value.endsWith("px")) {
setSizePercentage(undefined);
setSizePixels(parseFloat(value));
} else {
throw Error(`Invalid size: ${value}`);
}
};

const onCollapseButtonClick = () => {
Expand All @@ -202,18 +202,28 @@ function EndToEndTesting() {
const idToRefMap = idToRefMapRef.current;
const panel = idToRefMap.get(panelId);
if (panel && assertImperativePanelHandle(panel)) {
panel.resize(size, (units as any) || undefined);
panel.resize({ sizePercentage, sizePixels });
}
};

const onSetLayoutButton = () => {
const idToRefMap = idToRefMapRef.current;
const panelGroup = idToRefMap.get(panelGroupId);
if (panelGroup && assertImperativePanelGroupHandle(panelGroup)) {
panelGroup.setLayout(
JSON.parse(layoutString),
(units as any) || undefined
const trimmedLayoutString = layoutString.substring(
1,
layoutString.length - 1
);
const layout = trimmedLayoutString.split(",").map((text) => {
if (text.endsWith("%")) {
return { sizePercentage: parseFloat(text) };
} else if (text.endsWith("px")) {
return { sizePixels: parseFloat(text) };
} else {
throw Error(`Invalid layout: ${layoutString}`);
}
}) satisfies Partial<MixedSizes>[];
panelGroup.setLayout(layout);
}
};

Expand Down Expand Up @@ -251,8 +261,8 @@ function EndToEndTesting() {
className={styles.Input}
id="sizeInput"
onChange={onSizeInputChange}
placeholder="Size"
type="number"
placeholder="Size (% or px)"
type="text"
/>
<button
id="resizeButton"
Expand All @@ -262,18 +272,6 @@ function EndToEndTesting() {
<Icon type="resize" />
</button>
<div className={styles.Spacer} />
<select
className={styles.Input}
defaultValue={units}
id="unitsSelect"
onChange={onUnitsSelectChange}
placeholder="Units"
>
<option value=""></option>
<option value="percentages">percentages</option>
<option value="pixels">pixels</option>
</select>
<div className={styles.Spacer} />
<select
className={styles.Input}
id="panelGroupIdSelect"
Expand Down Expand Up @@ -302,8 +300,8 @@ function EndToEndTesting() {
</button>
</div>
</div>
<div className={styles.Children}>{children}</div>
<DebugLog apiRef={debugLogRef} />
<div className={styles.Children}>{children}</div>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useReducer } from "react";

import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";

import {
Expand Down Expand Up @@ -56,25 +57,30 @@ function Content() {
dispatch({ type: "open", file });
};

const toggleCollapsed = (collapsed: boolean) => {
dispatch({ type: "toggleCollapsed", collapsed });
const onCollapse = () => {
dispatch({ type: "toggleCollapsed", collapsed: false });
};

const onExpand = () => {
dispatch({ type: "toggleCollapsed", collapsed: true });
};

return (
<div className={sharedStyles.PanelGroupWrapper}>
<PanelGroup className={styles.IDE} direction="horizontal" units="pixels">
<PanelGroup className={styles.IDE} direction="horizontal">
<div className={styles.Toolbar}>
<Icon className={styles.ToolbarIconActive} type="files" />
<Icon className={styles.ToolbarIcon} type="search" />
</div>
<Panel
className={sharedStyles.PanelColumn}
collapsedSize={36}
collapsedSizePixels={36}
collapsible={true}
defaultSize={150}
maxSize={150}
minSize={60}
onCollapse={toggleCollapsed}
defaultSizePixels={150}
maxSizePixels={150}
minSizePixels={60}
onCollapse={onCollapse}
onExpand={onExpand}
>
<div className={styles.FileList}>
<div className={styles.DirectoryEntry}>
Expand Down Expand Up @@ -103,7 +109,7 @@ function Content() {
: styles.ResizeHandle
}
/>
<Panel className={sharedStyles.PanelColumn} minSize={10}>
<Panel className={sharedStyles.PanelColumn} minSizePercentage={50}>
<div className={styles.SourceTabs}>
{Array.from(openFiles).map((file) => (
<div
Expand Down Expand Up @@ -169,7 +175,7 @@ const FILES: File[] = FILE_PATHS.map(([path, code]) => {
const CODE = `
<PanelGroup direction="horizontal">
<SideTabBar />
<Panel collapsedSize={5} collapsible={true} minSize={10}>
<Panel collapsible={true} collapsedSizePixels={35} minSizePercentage={10}>
<SourceBrowser />
</Panel>
<PanelResizeHandle />
Expand Down
Loading