Skip to content
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ Falls back to <code>useId</code> when not provided.</p>
<tr>
<td>collapsedSize</td>
<td><p>Panel size when collapsed; defaults to 0%.</p>
</td>
</tr>
<tr>
<td>collapsedThreshold</td>
<td><p>Distance a collapsible panel must be resized past its <code>minSize</code> to collapse,
or past its <code>collapsedSize</code> to expand.
Defaults to half the distance between <code>collapsedSize</code> and <code>minSize</code>.</p>
<p>For example if a panel declares <code>collapsedSize=&quot;5%&quot;</code>, <code>collapsedThreshold=&quot;5%&quot;</code>, and <code>minSize=&quot;25%&quot;</code>,
it will collapse when resized below 20% and expands when resized above 10%.</p>
<p>ℹ️ Interpretation rules:</p>
<ul>
<li>Numbers are interpreted as pixels (e.g. <code>minSize={200}</code> is 200 pixels)</li>
<li>Strings without explicit units are interpreted as percentage (e.g. <code>minSize=&quot;50&quot;</code> is 50 percent)</li>
<li>Use explicit units (e.g. &quot;px&quot;, &quot;%&quot;, &quot;em&quot;, &quot;rem&quot;, &quot;vh&quot;, or &quot;vw&quot;) to change interpretation</li>
</ul>
</td>
</tr>
<tr>
Expand Down
37 changes: 37 additions & 0 deletions lib/components/panel/Panel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { act, render } from "@testing-library/react";
import { createRef, Profiler } from "react";
import { describe, expect, test, vi } from "vitest";
import { calculatePanelConstraints } from "../../global/dom/calculatePanelConstraints";
import { getRegisteredGroup } from "../../global/mutable-state/groups";
import { moveSeparator } from "../../global/test/moveSeparator";
import { assert } from "../../utils/assert";
Expand All @@ -14,6 +15,42 @@ import { Separator } from "../separator/Separator";
import { Panel } from "./Panel";

describe("Panel", () => {
test.each(["5%", "5", "10px", 10])(
"collapsedThreshold %s is converted and updated",
(collapsedThreshold) => {
setDefaultElementBounds(new DOMRect(0, 0, 100, 100));

const { rerender } = render(
<Group id="group">
<Panel collapsedThreshold={collapsedThreshold} collapsible id="a" />
<Panel id="b" />
</Group>
);

expect(
calculatePanelConstraints(getRegisteredGroup("group", true)).find(
({ panelId }) => panelId === "a"
)?.collapsedThreshold
).toBe(5);
expect(document.getElementById("a")).not.toHaveAttribute(
"collapsedThreshold"
);

rerender(
<Group id="group">
<Panel collapsedThreshold="3%" collapsible id="a" />
<Panel id="b" />
</Group>
);

expect(
calculatePanelConstraints(getRegisteredGroup("group", true)).find(
({ panelId }) => panelId === "a"
)?.collapsedThreshold
).toBe(3);
}
);

describe("disabled prop", () => {
test("changes to disabled prop should not cause the Panel to remount", () => {
const { rerender } = render(
Expand Down
3 changes: 3 additions & 0 deletions lib/components/panel/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function Panel({
children,
className,
collapsedSize = "0%",
collapsedThreshold,
collapsible = false,
defaultSize,
disabled,
Expand Down Expand Up @@ -105,6 +106,7 @@ export function Panel({
panelConstraints: {
groupResizeBehavior,
collapsedSize,
collapsedThreshold,
collapsible,
defaultSize,
disabled: stableProps.disabled,
Expand All @@ -118,6 +120,7 @@ export function Panel({
}, [
groupResizeBehavior,
collapsedSize,
collapsedThreshold,
collapsible,
defaultSize,
hasOnResize,
Expand Down
17 changes: 17 additions & 0 deletions lib/components/panel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type GroupResizeBehavior =
*/
export type PanelConstraints = {
collapsedSize: number;
collapsedThreshold?: number | undefined;
collapsible: boolean;
defaultSize: number | undefined;
disabled: boolean | undefined;
Expand Down Expand Up @@ -109,6 +110,21 @@ export type PanelProps = BasePanelAttributes & {
*/
collapsedSize?: number | string | undefined;

/**
* Distance a collapsible panel must be resized past its `minSize` to collapse,
* or past its `collapsedSize` to expand.
* Defaults to half the distance between `collapsedSize` and `minSize`.
*
* For example if a panel declares `collapsedSize="5%"`, `collapsedThreshold="5%"`, and `minSize="25%"`,
* it will collapse when resized below 20% and expands when resized above 10%.
*
* ℹ️ Interpretation rules:
* - Numbers are interpreted as pixels (e.g. `minSize={200}` is 200 pixels)
* - Strings without explicit units are interpreted as percentage (e.g. `minSize="50"` is 50 percent)
* - Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation
*/
collapsedThreshold?: number | string | undefined;

/**
* This panel can be collapsed.
*
Expand Down Expand Up @@ -240,6 +256,7 @@ export type OnPanelResize = PanelProps["onResize"];
export type PanelConstraintProps = Pick<
PanelProps,
| "collapsedSize"
| "collapsedThreshold"
| "collapsible"
| "defaultSize"
| "disabled"
Expand Down
12 changes: 12 additions & 0 deletions lib/global/dom/calculatePanelConstraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export function calculatePanelConstraints(group: RegisteredGroup) {
collapsedSize = formatLayoutNumber((pixels / groupSize) * 100);
}

let collapsedThreshold: number | undefined = undefined;
if (panelConstraints.collapsedThreshold !== undefined) {
const pixels = sizeStyleToPixels({
groupSize,
panelElement: element,
styleProp: panelConstraints.collapsedThreshold
});

collapsedThreshold = formatLayoutNumber((pixels / groupSize) * 100);
}

let defaultSize: number | undefined = undefined;
if (panelConstraints.defaultSize !== undefined) {
const pixels = sizeStyleToPixels({
Expand Down Expand Up @@ -73,6 +84,7 @@ export function calculatePanelConstraints(group: RegisteredGroup) {
return {
groupResizeBehavior: panelConstraints.groupResizeBehavior,
collapsedSize,
collapsedThreshold,
collapsible: panelConstraints.collapsible === true,
defaultSize,
disabled: panelConstraints.disabled,
Expand Down
57 changes: 57 additions & 0 deletions lib/global/utils/adjustLayoutByDelta.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,63 @@ function l(numbers: number[]) {
}

describe("adjustLayoutByDelta", () => {
describe("collapsedThreshold", () => {
test.each([0, 1])("panel at index %s", (index) => {
const constraints = c([{}, {}]);
constraints[index] = {
...constraints[index],
collapsedSize: 5,
collapsedThreshold: 5,
collapsible: true,
minSize: 25
};

function resize(
size: number,
delta: number,
trigger: Args["trigger"] = "mouse-or-touch"
) {
const sizes = index === 0 ? [size, 100 - size] : [100 - size, size];

return Object.values(
adjustLayoutByDelta({
delta: index === 0 ? delta : -delta,
initialLayout: l(sizes),
panelConstraints: constraints,
prevLayout: l(sizes),
trigger
})
)[index];
}

expect(resize(25, -4)).toBe(25);
expect(resize(25, -5)).toBe(25);
expect(resize(25, -6)).toBe(5);

expect(resize(5, 4)).toBe(5);
expect(resize(5, 5)).toBe(5);
expect(resize(5, 6)).toBe(25);

expect(resize(25, -1, "keyboard")).toBe(5);
expect(resize(5, 1, "keyboard")).toBe(25);

for (const threshold of [20, 30]) {
constraints[index].collapsedThreshold = threshold;

expect(resize(25, -1, "keyboard")).toBe(5);
expect(resize(5, 1, "keyboard")).toBe(25);
expect(resize(25, -19)).toBe(25);
expect(resize(25, -20)).toBe(5);
expect(resize(25, -21)).toBe(5);
}

constraints[index].collapsedThreshold = 0;

expect(resize(25, -1)).toBe(5);
expect(resize(5, 1)).toBe(25);
});
});

test("[1++,2]", () => {
expect(
adjustLayoutByDelta({
Expand Down
Loading
Loading