Skip to content
Source
Sign inSign up

usePreviewPanelInlineSize.ts

74 lines2.9 KB

1import { useState } from "react";
2​
3import { type ResizableWidthHandlers, useResizableWidth } from "./useResizableWidth";
4import { observeResize } from "../lib/observeResize";
5​
6export interface PreviewPanelInlineSize {
7 readonly width: number;
8 readonly handlers: ResizableWidthHandlers;
9 /** Ref-callback body for the panel host: measures the row the panel shares. */
10 readonly observeRow: (host: HTMLElement) => () => void;
12​
13const PREVIEW_PANEL_WIDTH_STORAGE_KEY = "t3code:preview-panel-width";
14const PREVIEW_PANEL_MIN_WIDTH = 360;
15const PREVIEW_PANEL_MAX_WIDTH_FRACTION = 0.7;
16/** Below this, the sibling chat column's composer overflows. */
17const SIBLING_COLUMN_MIN_WIDTH = 360;
18const PREVIEW_PANEL_DEFAULT_WIDTH = 540;
19​
20export function usePreviewPanelInlineSize(
21 options: {
22 readonly enabled?: boolean | undefined;
23 readonly widthStorageKey?: string | undefined;
24 readonly defaultWidth?: number | undefined;
25 readonly container?: HTMLElement | null | undefined;
26 } = {},
27): PreviewPanelInlineSize {
28 const enabled = options.enabled ?? true;
29 const container = options.container;
30 const [maxWidth, setMaxWidth] = useState(() =>
31 getPreviewPanelMaxWidth(typeof window === "undefined" ? 1280 : window.innerWidth),
32 );
33 const { width, handlers } = useResizableWidth({
34 storageKey: options.widthStorageKey ?? PREVIEW_PANEL_WIDTH_STORAGE_KEY,
35 defaultWidth: options.defaultWidth ?? PREVIEW_PANEL_DEFAULT_WIDTH,
36 minWidth: PREVIEW_PANEL_MIN_WIDTH,
37 maxWidth,
38 edge: "left",
39 });
40 const observeRow = (host: HTMLElement) => {
41 const row = container ?? (enabled ? host.parentElement : null) ?? null;
42 // Ref callbacks run before paint, so the width is clamped without an over-wide
43 // flash on mount. Only the derived cap is stored: the row resizes every frame
44 // of a sidebar drag, but an unchanged cap skips re-rendering the owner.
45 const measure = () => {
46 setMaxWidth(getPreviewPanelMaxWidth(window.innerWidth, row?.clientWidth));
47 };
48 measure();
49 let frame = 0;
50 const onResize = () => {
51 if (frame !== 0) return;
52 frame = window.requestAnimationFrame(() => {
53 frame = 0;
54 measure();
55 });
56 };
57 window.addEventListener("resize", onResize);
58 const stopObserving = row ? observeResize(row, measure) : undefined;
59 return () => {
60 window.removeEventListener("resize", onResize);
61 if (frame !== 0) window.cancelAnimationFrame(frame);
62 stopObserving?.();
63 };
64 };
65 return { width, handlers, observeRow };
67​
68function getPreviewPanelMaxWidth(viewportWidth: number, containerWidth?: number): number {
69 const fractionCap = Math.floor(viewportWidth * PREVIEW_PANEL_MAX_WIDTH_FRACTION);
70 const containerCap =
71 containerWidth === undefined ? Infinity : Math.floor(containerWidth) - SIBLING_COLUMN_MIN_WIDTH;
72 // Keep max >= min; an inverted clamp would overwrite the saved width on drag end.
73 return Math.max(PREVIEW_PANEL_MIN_WIDTH, Math.min(fractionCap, containerCap));