Skip to content

Commit

Permalink
Make the side navigation resizeable and remember it (#517)
Browse files Browse the repository at this point in the history
* Make the side navigation resizeable and remember it

* Add docs, fix bug in query params not removing args

* Typo

* Fix reversed styles
  • Loading branch information
tajo authored Oct 3, 2023
1 parent 0a37ccf commit 9727dc8
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-seahorses-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ladle/react": minor
---

Make the side navigation resizeable and remember it
20 changes: 20 additions & 0 deletions e2e/addons/tests/query-parameters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,23 @@ test("when click the menu and open the story, remain non-ladle query parameters"
expect(url).toContain("story=query-parameters--query-parameters");
expect(url).toContain("foo=bar");
});

test("preserve user query params after ladle query param update", async ({
page,
}) => {
await page.goto("/?story=query-parameters--query-parameters");
await page.waitForSelector("[data-storyloaded]");
const button = page.locator('[data-testid="addon-width"]');
await button.click();
const medium = page.locator("#width-medium");
await medium.click();
const url = page.url();
expect(url).toContain("story=query-parameters--query-parameters");
expect(url).toContain("foo=bar");
expect(url).toContain("width=768");
const unset = page.locator("#width-unset");
await unset.click();
const newUrl = page.url();
expect(newUrl).toContain("foo=bar");
expect(newUrl).not.toContain("width=768");
});
41 changes: 40 additions & 1 deletion packages/ladle/lib/app/ladle.css
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ blockquote.ladle-markdown {
padding-left: 40px;
}

.ladle-resize-handle {
display: none;
}

@media (min-width: 768px) {
html,
body {
Expand All @@ -590,6 +594,31 @@ blockquote.ladle-markdown {
overflow: hidden;
}

.ladle-resize-handle {
transition: box-shadow 0.15s ease-in;
display: block;
width: 16px;
height: 100%;
cursor: col-resize;
background-color: var(--ladle-bg-color-secondary);
}

.ladle-resize-handle:hover {
box-shadow: inset 3px 0 0 0 var(--ladle-color-accent);
}

[data-reversed] .ladle-resize-handle:hover {
box-shadow: inset -3px 0 0 0 var(--ladle-color-accent);
}

.ladle-resize-active {
box-shadow: inset 3px 0 0 0 var(--ladle-color-accent);
}

[data-reversed] .ladle-resize-active {
box-shadow: inset -3px 0 0 0 var(--ladle-color-accent);
}

.ladle-aside {
box-sizing: content-box;
}
Expand All @@ -602,13 +631,19 @@ blockquote.ladle-markdown {
.ladle-aside {
font-size: 16px;
flex: 0 0 12em;
padding: 3em 2.5em;
padding: 3em 2em;
padding-left: calc(2em - 16px);
min-width: 15em;
background-color: var(--ladle-bg-color-secondary);
min-height: max-content;
align-self: stretch;
}

[data-reversed] .ladle-aside {
padding-left: 2em;
padding-right: calc(2em - 16px);
}

[data-mode="full"] .ladle-main,
[data-mode="full"] .ladle-aside {
overflow: auto;
Expand All @@ -630,6 +665,10 @@ blockquote.ladle-markdown {
background-color: var(--ladle-bg-color-secondary);
}

.ladle-aside input::placeholder {
user-select: none;
}

.ladle-addons {
position: fixed;
margin-bottom: 0;
Expand Down
9 changes: 8 additions & 1 deletion packages/ladle/lib/app/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ const removeDefaultValues = (params: Partial<GlobalState>) => {

export const modifyParams = (globalState: GlobalState) => {
if (!globalState.controlInitialized) return;
const queryParams = queryString.parse(location.search);
const userQueryParams: { [key: string]: string } = {};
Object.keys(queryParams).forEach((key) => {
if (!key.startsWith("arg-")) {
userQueryParams[key] = queryParams[key] as string;
}
});
const params = {
...queryString.parse(location.search),
...userQueryParams,
mode: globalState.mode,
rtl: globalState.rtl,
source: globalState.source,
Expand Down
26 changes: 22 additions & 4 deletions packages/ladle/lib/app/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,40 @@ export const Page = () => {
};

export const Down = ({ rotate }: { rotate?: boolean }) => {
const width = "16px";
const height = "16px";
return (
<div
aria-hidden
style={{
width: "16px",
width,
height,
marginInlineEnd: "0.1em",
marginBottom: "-0.1em",
}}
>
{rotate ? (
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none">
<svg
style={{
width,
height,
}}
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 6l6 6l-6 6"></path>
</svg>
) : (
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none">
<svg
style={{
width,
height,
}}
viewBox="0 0 24 24"
stroke="currentColor"
fill="none"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 9l6 6l6 -6"></path>
</svg>
Expand Down
30 changes: 30 additions & 0 deletions packages/ladle/lib/app/src/local-storage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type Settings = {
appId?: string;
sidebarWidth?: number;
};

// @ts-ignore
const APP_ID = import.meta.env.VITE_LADLE_APP_ID;
const storageKey = `ladle-settings-${APP_ID}`;
const defaultValue = { appId: APP_ID };

export const updateSettings = (settings: Settings) => {
const storageValue = localStorage.getItem(storageKey);
let storageSettings = defaultValue;
try {
if (storageValue) storageSettings = JSON.parse(storageValue);
} catch (e) {}
localStorage.setItem(
storageKey,
JSON.stringify({ ...storageSettings, ...settings }),
);
};

export const getSettings = (): Settings => {
const storageValue = localStorage.getItem(storageKey);
let storageSettings = defaultValue;
try {
if (storageValue) storageSettings = JSON.parse(storageValue);
} catch (e) {}
return storageSettings as Settings;
};
136 changes: 112 additions & 24 deletions packages/ladle/lib/app/src/sidebar/main.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import * as React from "react";
import { useHotkeys } from "react-hotkeys-hook";
import cx from "classnames";
import config from "../get-config";
import TreeView from "./tree-view";
import { debounce } from "./utils";
import { getSettings, updateSettings } from "../local-storage";
import type { UpdateStory } from "../../../shared/types";

const DEFAULT_WIDTH = 240;
const MIN_WIDTH = 192;
const MAX_WIDTH = 920;

const debouncedUpdateSettings = debounce(updateSettings, 250);

const Main = ({
stories,
story,
Expand All @@ -16,8 +25,60 @@ const Main = ({
hotkeys: boolean;
}) => {
const [search, setSearch] = React.useState("");
const [width, setWidth] = React.useState(
getSettings().sidebarWidth || DEFAULT_WIDTH,
);
const [resizeActive, setResizeActive] = React.useState(false);
const handleRef = React.useRef<HTMLDivElement>(null);
const searchEl = React.useRef(null);
const treeRoot = React.useRef<HTMLUListElement | null>(null);

React.useEffect(() => {
const parentStyles = window.getComputedStyle(
// @ts-ignore
handleRef.current.parentElement,
);
const direction = parentStyles.getPropertyValue("flex-direction");
if (direction === "row-reverse") {
document.documentElement.setAttribute("data-reversed", "");
}
}, []);

React.useEffect(() => {
const mouseMoveHandler = (e: MouseEvent) => {
if (!resizeActive) {
return;
}
setWidth((previousWidth) => {
const newWidth = document.documentElement.hasAttribute("data-reversed")
? previousWidth + e.movementX
: previousWidth - e.movementX;
if (newWidth < MIN_WIDTH) {
debouncedUpdateSettings({ sidebarWidth: MIN_WIDTH });
return MIN_WIDTH;
}
if (newWidth > MAX_WIDTH) {
debouncedUpdateSettings({ sidebarWidth: MAX_WIDTH });
return MAX_WIDTH;
}
debouncedUpdateSettings({ sidebarWidth: newWidth });
return newWidth;
});
};
const mouseUpHandler = () => {
if (resizeActive) {
document.body.style.cursor = "auto";
setResizeActive(false);
}
};
window.addEventListener("mousemove", mouseMoveHandler);
window.addEventListener("mouseup", mouseUpHandler);
return () => {
window.removeEventListener("mousemove", mouseMoveHandler);
window.removeEventListener("mouseup", mouseUpHandler);
};
}, [resizeActive, setResizeActive, setWidth, handleRef.current]);

useHotkeys(
config.hotkeys.search,
() => (searchEl.current as any as HTMLInputElement).focus(),
Expand All @@ -32,33 +93,60 @@ const Main = ({
);

return (
<nav role="navigation" className="ladle-aside">
<input
placeholder="Search"
aria-label="Search stories"
value={search}
ref={searchEl}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
(treeRoot as any).current.firstChild.focus();
<>
<div
role="separator"
aria-orientation="vertical"
ref={handleRef}
className={cx("ladle-resize-handle", {
"ladle-resize-active": resizeActive,
})}
onDragStart={(e) => e.preventDefault()}
onDragEnd={(e) => e.preventDefault()}
onDrop={(e) => e.preventDefault()}
onDragOver={(e) => e.preventDefault()}
onDragEnter={(e) => e.preventDefault()}
onDragLeave={(e) => e.preventDefault()}
onMouseDown={(e) => {
e.preventDefault();
if (!resizeActive) {
document.body.style.cursor = "col-resize";
setResizeActive(true);
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSearch(e.target.value)
}
/>
<TreeView
searchRef={searchEl}
stories={filteredStories}
story={story}
hotkeys={hotkeys}
updateStory={updateStory}
searchActive={search !== ""}
setTreeRootRef={(root: HTMLUListElement | null) =>
(treeRoot.current = root)
}
/>
</nav>
<nav
role="navigation"
className="ladle-aside"
style={{ minWidth: `${width}px` }}
>
<input
placeholder="Search"
aria-label="Search stories"
value={search}
ref={searchEl}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
(treeRoot as any).current.firstChild.focus();
}
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSearch(e.target.value)
}
/>
<TreeView
searchRef={searchEl}
stories={filteredStories}
story={story}
hotkeys={hotkeys}
updateStory={updateStory}
searchActive={search !== ""}
setTreeRootRef={(root: HTMLUListElement | null) =>
(treeRoot.current = root)
}
/>
</nav>
</>
);
};

Expand Down
19 changes: 19 additions & 0 deletions packages/ladle/lib/app/src/sidebar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,22 @@ export const toggleIsExpanded = (
return newNode;
});
};

export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number,
): (...funcArgs: Parameters<T>) => void {
let timerId: number | undefined;

return function (...args: Parameters<T>) {
if (timerId !== undefined) {
clearTimeout(timerId);
}

timerId = window.setTimeout(() => {
// @ts-ignore
fn.apply(this, args);
timerId = undefined;
}, delay);
};
}
Loading

0 comments on commit 9727dc8

Please sign in to comment.