68 lines
3.1 KiB
TypeScript
68 lines
3.1 KiB
TypeScript
import {
|
|
atomWithLocalStorage,
|
|
getLocalStorageItem,
|
|
setLocalStorageItem,
|
|
} from './utils/atomWithLocalStorage';
|
|
import { sidebarWidthAtom } from './sidebarWidth';
|
|
|
|
export const MEDIA_SIDE_PANEL_WIDTH_KEY = 'vojo_media_side_panel_width';
|
|
// Lowest readable width for `MediaViewerBody`'s media card + action row.
|
|
// Below ~360 the file-name truncates aggressively and the action row
|
|
// starts wrapping.
|
|
export const MEDIA_SIDE_PANEL_WIDTH_MIN = 360;
|
|
// Pleasant starting width on a typical 1440-wide desktop after the
|
|
// chat-list column at its default 416 and the chat column reserve.
|
|
export const MEDIA_SIDE_PANEL_WIDTH_DEFAULT = 520;
|
|
// Hard ceiling: even on ultra-wide displays the media pane shouldn't
|
|
// dwarf the chat — matches the prior CSS `clamp(..., ..., 880px)` cap.
|
|
export const MEDIA_SIDE_PANEL_WIDTH_HARD_MAX = 880;
|
|
|
|
// Layout constants the smart-max math depends on. Kept here (not
|
|
// imported from horseshoe.ts / Sidebar.css.ts) so the clamp helper
|
|
// stays a pure function safe to call from anywhere — including a
|
|
// re-mount race where the CSS-module hasn't bound yet.
|
|
const SIDEBAR_RAIL_PX = 66; // global icon rail, fixed
|
|
const VOID_GAP_PX = 12; // VOJO_HORSESHOE_GAP_PX (page-nav<->chat and chat<->media)
|
|
// Minimum width to reserve for the chat column when media is open.
|
|
// Below ~360 the timeline text gets line-broken to single words and
|
|
// reading falls apart — the whole point of this clamp is to protect
|
|
// that reading area, not the media pane.
|
|
const CHAT_COLUMN_RESERVED_PX = 360;
|
|
|
|
const readMediaSidePanelWidth = (key: string): number => {
|
|
const raw = getLocalStorageItem<unknown>(key, MEDIA_SIDE_PANEL_WIDTH_DEFAULT);
|
|
const value =
|
|
typeof raw === 'number' && Number.isFinite(raw) ? raw : MEDIA_SIDE_PANEL_WIDTH_DEFAULT;
|
|
return Math.max(MEDIA_SIDE_PANEL_WIDTH_MIN, Math.round(value));
|
|
};
|
|
|
|
export const mediaSidePanelWidthAtom = atomWithLocalStorage<number>(
|
|
MEDIA_SIDE_PANEL_WIDTH_KEY,
|
|
readMediaSidePanelWidth,
|
|
setLocalStorageItem
|
|
);
|
|
|
|
// Smart maximum: viewport minus (left rail + chat-list column + two
|
|
// horseshoe void gaps + chat-column reservation). Both the chat-list
|
|
// and the chat-column are protected — dragging media wider can only
|
|
// eat the slack between them and the hard cap. If even the reservation
|
|
// can't fit (tiny laptop / virtual viewport), the max collapses to
|
|
// MIN so the panel still mounts at a sane size.
|
|
export const computeMediaSidePanelMax = (viewportWidth: number, pageNavWidth: number): number => {
|
|
const reserved = SIDEBAR_RAIL_PX + pageNavWidth + VOID_GAP_PX * 2 + CHAT_COLUMN_RESERVED_PX;
|
|
const available = viewportWidth - reserved;
|
|
return Math.max(MEDIA_SIDE_PANEL_WIDTH_MIN, Math.min(MEDIA_SIDE_PANEL_WIDTH_HARD_MAX, available));
|
|
};
|
|
|
|
export const clampMediaSidePanelWidth = (
|
|
px: number,
|
|
viewportWidth: number,
|
|
pageNavWidth: number
|
|
): number => {
|
|
const max = computeMediaSidePanelMax(viewportWidth, pageNavWidth);
|
|
return Math.max(MEDIA_SIDE_PANEL_WIDTH_MIN, Math.min(max, Math.round(px)));
|
|
};
|
|
|
|
// Re-export so callers don't have to import `sidebarWidthAtom` from a
|
|
// separate state file just to feed the clamp helper.
|
|
export { sidebarWidthAtom };
|