mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
|
|
/**
|
|
* How the 3D map's bottom row fits between the side panels: `full` with every
|
|
* label, `compact` with the layer pill on its icons, `stacked` with the grade
|
|
* legend on a row of its own above the dock as well, and `side` with the layer
|
|
* pill standing on the map's trailing edge, leaving the row to the dock (a map
|
|
* as narrow as the iPad's beside the tour pane).
|
|
*/
|
|
export type ControlRowFit = 'full' | 'compact' | 'stacked' | 'side';
|
|
|
|
const STEPS: readonly ControlRowFit[] = ['full', 'compact', 'stacked', 'side'];
|
|
|
|
/**
|
|
* Steps the row down while its content is wider than the room between the
|
|
* panels, and back up once the room has grown to what the larger step
|
|
* needed, so it never flips back and forth at one width. `height` is the
|
|
* height the controls take from the bottom edge, every row of them, for what
|
|
* floats above them (the via ferrata card).
|
|
*
|
|
* `row` is the one row that must not overflow; `stack` wraps everything the
|
|
* fit shows, the legend's own row included.
|
|
*/
|
|
export function useControlRowFit() {
|
|
const [row, setRow] = useState<HTMLDivElement | null>(null);
|
|
const [stack, setStack] = useState<HTMLDivElement | null>(null);
|
|
const [fit, setFit] = useState<ControlRowFit>('full');
|
|
const [height, setHeight] = useState<number | null>(null);
|
|
// The width the content needed at each step when it had to step down from it.
|
|
const needed = useRef<Partial<Record<ControlRowFit, number>>>({});
|
|
|
|
const check = useCallback(() => {
|
|
if (!row) return;
|
|
setFit((current) => {
|
|
const index = STEPS.indexOf(current);
|
|
const room = row.clientWidth;
|
|
const larger = STEPS[index - 1];
|
|
const neededLarger = larger ? needed.current[larger] : undefined;
|
|
if (larger && neededLarger !== undefined && room >= neededLarger) return larger;
|
|
const smaller = STEPS[index + 1];
|
|
if (smaller && row.scrollWidth > room + 1) {
|
|
needed.current[current] = row.scrollWidth;
|
|
return smaller;
|
|
}
|
|
return current;
|
|
});
|
|
}, [row]);
|
|
|
|
useLayoutEffect(() => {
|
|
check();
|
|
});
|
|
|
|
useLayoutEffect(() => {
|
|
if (!row || typeof ResizeObserver === 'undefined') return;
|
|
const observer = new ResizeObserver(() => check());
|
|
observer.observe(row);
|
|
return () => observer.disconnect();
|
|
}, [row, check]);
|
|
|
|
useLayoutEffect(() => {
|
|
if (!stack || typeof ResizeObserver === 'undefined') return;
|
|
const measure = () => setHeight(stack.offsetHeight || null);
|
|
measure();
|
|
const observer = new ResizeObserver(measure);
|
|
observer.observe(stack);
|
|
return () => observer.disconnect();
|
|
}, [stack]);
|
|
|
|
return { fit, height, attachRow: setRow, attachStack: setStack };
|
|
}
|