2026-08-01 21:16:36 +03:00
|
|
|
import React from 'react';
|
|
|
|
|
|
|
|
|
|
export const IPAD_LEFT_SIDEBAR_WIDTH = 320;
|
|
|
|
|
export const IPAD_RIGHT_SIDEBAR_WIDTH = 380;
|
|
|
|
|
const IPAD_SIDEBAR_MIN_WIDTH = 280;
|
|
|
|
|
const IPAD_SIDEBAR_MAX_WIDTH = 560;
|
2026-08-02 16:25:16 +03:00
|
|
|
/** The workspace panel holds diffs, a file editor and a terminal, so it earns
|
|
|
|
|
far more room than the sessions list ever needs. */
|
|
|
|
|
export const IPAD_WORKSPACE_SIDEBAR_MAX_WIDTH = 900;
|
2026-08-01 21:16:36 +03:00
|
|
|
|
|
|
|
|
/** Drag-resize for the iPad sidebars: same live-width mechanics as the desktop
|
|
|
|
|
Sidebar (imperative styles during the drag, committed to state at the end),
|
|
|
|
|
but with a finger-sized grab strip instead of a 3px hover handle. */
|
2026-08-02 16:25:16 +03:00
|
|
|
export function useIpadSidebarResize(
|
|
|
|
|
side: 'left' | 'right',
|
|
|
|
|
storageKey: string,
|
|
|
|
|
defaultWidth: number,
|
|
|
|
|
maxWidth: number = IPAD_SIDEBAR_MAX_WIDTH,
|
|
|
|
|
) {
|
2026-08-01 21:16:36 +03:00
|
|
|
const asideRef = React.useRef<HTMLElement | null>(null);
|
|
|
|
|
const [width, setWidth] = React.useState(() => {
|
|
|
|
|
if (typeof window === 'undefined') return defaultWidth;
|
|
|
|
|
const stored = Number.parseInt(window.localStorage.getItem(storageKey) ?? '', 10);
|
|
|
|
|
if (!Number.isFinite(stored)) return defaultWidth;
|
2026-08-02 16:25:16 +03:00
|
|
|
return Math.min(maxWidth, Math.max(IPAD_SIDEBAR_MIN_WIDTH, stored));
|
2026-08-01 21:16:36 +03:00
|
|
|
});
|
|
|
|
|
const [isResizing, setIsResizing] = React.useState(false);
|
|
|
|
|
const startXRef = React.useRef(0);
|
|
|
|
|
const startWidthRef = React.useRef(width);
|
|
|
|
|
const liveWidthRef = React.useRef<number | null>(null);
|
|
|
|
|
const pointerIdRef = React.useRef<number | null>(null);
|
|
|
|
|
|
|
|
|
|
const clampWidth = React.useCallback((value: number) => (
|
2026-08-02 16:25:16 +03:00
|
|
|
Math.min(maxWidth, Math.max(IPAD_SIDEBAR_MIN_WIDTH, Math.round(value)))
|
|
|
|
|
), [maxWidth]);
|
2026-08-01 21:16:36 +03:00
|
|
|
|
|
|
|
|
const applyLiveWidth = React.useCallback((nextWidth: number) => {
|
|
|
|
|
const aside = asideRef.current;
|
|
|
|
|
if (!aside) return;
|
|
|
|
|
aside.style.width = `${nextWidth}px`;
|
|
|
|
|
aside.style.minWidth = `${nextWidth}px`;
|
|
|
|
|
aside.style.maxWidth = `${nextWidth}px`;
|
|
|
|
|
aside.style.setProperty('--oc-ipad-sidebar-width', `${nextWidth}px`);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handlePointerDown = React.useCallback((event: React.PointerEvent) => {
|
|
|
|
|
try {
|
|
|
|
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
pointerIdRef.current = event.pointerId;
|
|
|
|
|
startXRef.current = event.clientX;
|
|
|
|
|
startWidthRef.current = width;
|
|
|
|
|
liveWidthRef.current = width;
|
|
|
|
|
setIsResizing(true);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}, [width]);
|
|
|
|
|
|
|
|
|
|
const handlePointerMove = React.useCallback((event: React.PointerEvent) => {
|
|
|
|
|
if (pointerIdRef.current !== event.pointerId) return;
|
|
|
|
|
const delta = event.clientX - startXRef.current;
|
|
|
|
|
const next = clampWidth(startWidthRef.current + (side === 'left' ? delta : -delta));
|
|
|
|
|
if (liveWidthRef.current === next) return;
|
|
|
|
|
liveWidthRef.current = next;
|
|
|
|
|
applyLiveWidth(next);
|
|
|
|
|
}, [applyLiveWidth, clampWidth, side]);
|
|
|
|
|
|
|
|
|
|
const handlePointerEnd = React.useCallback((event: React.PointerEvent) => {
|
|
|
|
|
if (pointerIdRef.current !== event.pointerId) return;
|
|
|
|
|
try {
|
|
|
|
|
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
const finalWidth = clampWidth(liveWidthRef.current ?? startWidthRef.current);
|
|
|
|
|
pointerIdRef.current = null;
|
|
|
|
|
liveWidthRef.current = null;
|
|
|
|
|
setIsResizing(false);
|
|
|
|
|
setWidth(finalWidth);
|
|
|
|
|
try {
|
|
|
|
|
window.localStorage.setItem(storageKey, String(finalWidth));
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}, [clampWidth, storageKey]);
|
|
|
|
|
|
|
|
|
|
const handleProps = React.useMemo(() => ({
|
|
|
|
|
onPointerDown: handlePointerDown,
|
|
|
|
|
onPointerMove: handlePointerMove,
|
|
|
|
|
onPointerUp: handlePointerEnd,
|
|
|
|
|
onPointerCancel: handlePointerEnd,
|
|
|
|
|
}), [handlePointerDown, handlePointerEnd, handlePointerMove]);
|
|
|
|
|
|
|
|
|
|
return { asideRef, width, isResizing, handleProps };
|
|
|
|
|
}
|
|
|
|
|
|