Files
TREK/client/src/components/Assistant/assistantFrame.ts
T
Maurice 51c59e28b8 feat(assistant): TREK Studio's assistant floats in the planner's window instead of a column
The column took the page's room and cut the spread off. The Studio now opens
the same movable, resizable window as the planner, kept under its own key and
below the Studio's bar; its frame moved into assistantFrame.ts and
AssistantFrameEdges so both windows share it.
2026-10-23 13:11:25 +02:00

51 lines
2.0 KiB
TypeScript

import { useState } from 'react';
import { type WindowSize, useDragResize } from '../../hooks/useDragResize';
/**
* The floating window's frame, shared by the planner's window and TREK
* Studio's: moved by its head, resized from any edge or corner, kept inside
* the screen and below the navbar, remembered under its own key.
*/
const MIN_SIZE: WindowSize = { width: 340, height: 420 };
const DEFAULT_SIZE: WindowSize = { width: 400, height: 600 };
/** Never closer to the screen's edge than this. */
const MARGIN = 16;
/** The default distance from the edges, and how near a corner a released window snaps into it. */
const GAP = 24;
const expandedSize = (viewport: WindowSize): WindowSize => ({
width: Math.min(720, viewport.width - 2 * MARGIN),
height: Math.round(viewport.height * 0.8),
});
/** The fixed navbar's height (`--nav-h`, a calc() and 0 where there is none), measured. */
function navbarHeight(): number {
if (typeof document === 'undefined') return 0;
const probe = document.createElement('div');
probe.style.cssText = 'position:fixed;visibility:hidden;height:var(--nav-h, 0px)';
document.body.appendChild(probe);
const height = probe.offsetHeight;
probe.remove();
return height;
}
/** The frame of a floating assistant window, its place and size kept under `storageKey`. */
export function useAssistantFrame(storageKey: string, topInsetPx?: number) {
// The navbar sits on the same layer and later in the page: the head never goes under it.
const [topInset] = useState(() => topInsetPx ?? navbarHeight());
return useDragResize({
storageKey,
minSize: MIN_SIZE,
defaultSize: DEFAULT_SIZE,
expandedSize,
margin: MARGIN,
gap: GAP,
topInset,
});
}
/** The glass window's own classes; `active` while it is being moved or resized. */
export const FRAME_CLASS =
'fixed z-[var(--z-nav)] flex-col overflow-visible rounded-xl border border-edge-faint bg-surface-elevated shadow-[var(--sidebar-shadow)] backdrop-blur-2xl backdrop-saturate-150';