website(docs): Add ability to resize playground code view and collapse settings
MODIFY
website/src/playground/Playground.tsx
website/src/playground/Playground.tsx
@@ -14,6 +14,7 @@ import DiagnosticsTab from "./tabs/DiagnosticsTab";
import FormatterCodeTab from "./tabs/FormatterCodeTab";
import FormatterIRTab from "./tabs/FormatterIRTab";
import { useWindowSize } from "./utils";
+import CodePane from "./components/CodePane";
export default function PlaygroundLoader({
setPlaygroundState,
@@ -135,7 +136,7 @@ export default function PlaygroundLoader({
<>
{!hasNarrowViewport && settingsPane}
- {!hasNarrowViewport && <div className="code-pane">{editor}</div>}
+ {!hasNarrowViewport && <CodePane>{editor}</CodePane>}
<Tabs
className="preview-pane"

ADD
website/src/playground/components/CodePane.tsx
+import type React from "react";
+import { useState, createRef, useEffect } from "react";
+import { createLocalStorage } from "../utils";
+
+interface Props {
+ children: JSX.Element;
+}
+
+const RESIZE_TOLERANCE = 10;
+const MINIMUM_SIZE = 200;
+
+const codePaneSizeStore = createLocalStorage("code-width");
+
+export default function CodePane({ children }: Props) {
+ let [rawWidth, setWidth] = useState<number | undefined>(
+ codePaneSizeStore.getNumber(),
+ );
+ let [isResizing, setIsResizing] = useState(false);
+ let [canResize, setCanResize] = useState(false);
+
+ const width =
+ rawWidth === undefined ? undefined : Math.max(MINIMUM_SIZE, rawWidth);
+
+ const ref: React.RefObject<HTMLDivElement> = createRef();
+
+ let cursor = canResize || isResizing ? "col-resize" : undefined;
+
+ useEffect(() => {
+ // Detect if we can resize
+ function onMouseMove(event: MouseEvent) {
+ const container = ref.current;
+ if (container == null) {
+ return;
+ }
+
+ const mouseX = event.pageX;
+ const containerX = container.offsetLeft + container.clientWidth;
+ const distance = Math.abs(containerX - mouseX);
+
+ if (isResizing) {
+ const width = mouseX - container.offsetLeft;
+ setWidth(width);
+ codePaneSizeStore.set(width);
+ event.preventDefault();
+ }
+
+ if (distance <= RESIZE_TOLERANCE) {
+ setCanResize(true);
+ } else if (canResize) {
+ setCanResize(false);
+ }
+ }
+
+ function onMouseDown() {
+ if (canResize) {
+ setIsResizing(true);
+ }
+ }
+
+ function onMouseUp() {
+ setIsResizing(false);
+ }
+
+ window.addEventListener("mousedown", onMouseDown);
+ window.addEventListener("mouseup", onMouseUp);
+ window.addEventListener("mousemove", onMouseMove);
+
+ return () => {
+ window.removeEventListener("mousedown", onMouseDown);
+ window.removeEventListener("mouseup", onMouseUp);
+ window.removeEventListener("mousemove", onMouseMove);
+ };
+ });
+
+ useEffect(() => {
+ if (cursor === undefined) {
+ document.body.style.removeProperty("cursor");
+ } else {
+ document.body.style.cursor = cursor;
+ }
+
+ return () => {
+ document.body.style.removeProperty("cursor");
+ };
+ }, [cursor]);
+
+ return (
+ <div
+ ref={ref}
+ className="code-pane"
+ style={{ width, flexShrink: width === undefined ? undefined : 0 }}
+ >
+ {children}
+ </div>
+ );
+}

MODIFY
website/src/playground/components/SettingsPane.tsx
website/src/playground/components/SettingsPane.tsx
@@ -8,8 +8,8 @@ import {
TrailingComma,
} from "../types";
import type { Dispatch, SetStateAction } from "react";
-import { useState } from "react";
-import { createSetter } from "../utils";
+import { useState, useEffect } from "react";
+import { createLocalStorage, createSetter } from "../utils";
interface Props {
settings: PlaygroundSettings;
@@ -17,6 +17,8 @@ interface Props {
onReset: () => void;
}
+const isCollapsedStore = createLocalStorage("settings-collapsed");
+
export default function SettingsPane({
setPlaygroundState,
onReset,
@@ -50,8 +52,25 @@ export default function SettingsPane({
"enabledNurseryRules",
);
+ const [isCollapsed, setIsCollapsed] = useState(isCollapsedStore.getBoolean());
+
+ function collapseToggle() {
+ setIsCollapsed(!isCollapsed);
+ }
+
+ useEffect(() => {
+ isCollapsedStore.set(isCollapsed);
+ }, [isCollapsed]);
+
return (
<div className="settings-pane">
+ {!isCollapsed && (
+ <div className="fields">
+ <section>
+ <button onClick={onReset} onKeyDown={onReset}>
+ Reset
+ </button>
+ </section>
<FormatterSettings
lineWidth={lineWidth}
setLineWidth={setLineWidth}
@@ -65,7 +84,6 @@ export default function SettingsPane({
setQuoteProperties={setQuoteProperties}
trailingComma={trailingComma}
setTrailingComma={setTrailingComma}
- onReset={onReset}
/>
<LinterSettings
enabledNurseryRules={enabledNurseryRules}
@@ -80,6 +98,17 @@ export default function SettingsPane({
setIsJsx={setIsJsx}
/>
</div>
+ )}
+ <div
+ className={`collapser ${isCollapsed ? "collapsed" : ""}`}
+ onMouseDown={collapseToggle}
+ onKeyDown={collapseToggle}
+ >
+ <div className="dot" />
+ <div className="dot" />
+ <div className="dot" />
+ </div>
+ </div>
);
}
@@ -158,7 +187,6 @@ function FormatterSettings({
setQuoteProperties,
trailingComma,
setTrailingComma,
- onReset,
}: {
lineWidth: number;
setLineWidth: (value: number) => void;
@@ -172,16 +200,9 @@ function FormatterSettings({
setQuoteProperties: (value: QuoteProperties) => void;
trailingComma: TrailingComma;
setTrailingComma: (value: TrailingComma) => void;
- onReset: () => void;
}) {
return (
<>
- <section>
- <button onClick={onReset} onKeyDown={onReset}>
- Reset
- </button>
- </section>
-
<h2>Formatter</h2>
<section>
<LineWidthInput lineWidth={lineWidth} setLineWidth={setLineWidth} />

MODIFY
website/src/playground/utils.ts
website/src/playground/utils.ts
@@ -69,12 +69,42 @@ export function useWindowSize(): Size {
return windowSize;
}
+export function createLocalStorage(name: string): {
+ set: (value: string | boolean | number) => void;
+ get: () => undefined | string;
+ getNumber: () => undefined | number;
+ getBoolean: () => boolean;
+} {
+ const key = `playground:${name}`;
+ return {
+ set: (value) => {
+ localStorage.setItem(key, String(value));
+ },
+ getNumber: () => {
+ const elem = localStorage.getItem(key);
+ if (elem == null) {
+ return undefined;
+ } else {
+ return Number(elem);
+ }
+ },
+ getBoolean: () => {
+ return localStorage.getItem(key) === "true";
+ },
+ get: () => {
+ return localStorage.getItem(key) || undefined;
+ },
+ };
+}
+
+const lastSearchStore = createLocalStorage("last-search");
+
export function usePlaygroundState(
defaultRomeConfig: RomeConfiguration,
): [PlaygroundState, Dispatch<SetStateAction<PlaygroundState>>, () => void] {
const searchQuery =
window.location.search === ""
- ? localStorage.getItem("last-playground-search") ?? ""
+ ? lastSearchStore.get() ?? ""
: window.location.search;
const initialSearchParams = new URLSearchParams(searchQuery);
@@ -151,7 +181,7 @@ export function usePlaygroundState(
}
const queryString = new URLSearchParams(queryStringObj).toString();
- localStorage.setItem("last-playground-search", queryString);
+ lastSearchStore.set(queryString);
let url = `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
if (queryString !== "") {

MODIFY
website/src/styles/playground.scss
website/src/styles/playground.scss
@@ -28,17 +28,55 @@ html, body {
}
.settings-pane {
- width: 250px;
flex-shrink: 0;
background-color: var(--container-color);
- box-shadow: inset -1px 0 0 var(--hard-border-color);
overflow: auto;
font-size: 14px;
+ display: flex;
@include dark-mode {
background-color: rgba(255, 255, 255, 0.2);
}
+ .fields {
+ width: 250px;
+ }
+
+ .collapser {
+ width: 5px;
+ height: 100%;
+ background-color: var(--hard-border-color);
+ flex-shrink: 0;
+ opacity: 0.5;
+ cursor: w-resize;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 5px;
+
+ .dot {
+ width: 3px;
+ height: 3px;
+ background-color: rgba(#000, 50%);
+ border-radius: 50%;
+ }
+
+ &.collapsed {
+ width: 10px;
+ cursor: e-resize;
+
+ .dot {
+ width: 5px;
+ height: 5px;
+ }
+ }
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+
section {
padding: 10px;
}

website(docs): Add ability to resize playground code view and collapse settings (#3693)