mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
refactor(web): menu positioning
This commit is contained in:
parent
19bd5ace7d
commit
f00628f4e5
@ -82,7 +82,7 @@ export default function AppBar({ title }) {
|
|||||||
</div>
|
</div>
|
||||||
<LinkedLogo />
|
<LinkedLogo />
|
||||||
<div className="flex-grow-1 flex justify-end w-full">
|
<div className="flex-grow-1 flex justify-end w-full">
|
||||||
<div ref={moreRef}>
|
<div className="w-auto" ref={moreRef}>
|
||||||
<Button color="black" className="rounded-full w-12 h-12" onClick={handleShowMenu} type="text">
|
<Button color="black" className="rounded-full w-12 h-12" onClick={handleShowMenu} type="text">
|
||||||
<MoreIcon />
|
<MoreIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -2,7 +2,7 @@ import { h } from 'preact';
|
|||||||
import RelativeModal from './RelativeModal';
|
import RelativeModal from './RelativeModal';
|
||||||
import { useCallback, useEffect } from 'preact/hooks';
|
import { useCallback, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
export default function Menu({ className, children, onDismiss, relativeTo }) {
|
export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) {
|
||||||
return relativeTo ? (
|
return relativeTo ? (
|
||||||
<RelativeModal
|
<RelativeModal
|
||||||
children={children}
|
children={children}
|
||||||
@ -11,6 +11,7 @@ export default function Menu({ className, children, onDismiss, relativeTo }) {
|
|||||||
onDismiss={onDismiss}
|
onDismiss={onDismiss}
|
||||||
portalRootID="menus"
|
portalRootID="menus"
|
||||||
relativeTo={relativeTo}
|
relativeTo={relativeTo}
|
||||||
|
widthRelative={widthRelative}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
@ -38,11 +39,11 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
|
|||||||
role="option"
|
role="option"
|
||||||
>
|
>
|
||||||
{Icon ? (
|
{Icon ? (
|
||||||
<div className="w-6 h-6 self-center mr-4 text-gray-500">
|
<div className="w-6 h-6 self-center mr-4 text-gray-500 flex-shrink-0">
|
||||||
<Icon />
|
<Icon />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{label}
|
<div class="whitespace-nowrap">{label}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,18 @@ import { h, Fragment } from 'preact';
|
|||||||
import { createPortal } from 'preact/compat';
|
import { createPortal } from 'preact/compat';
|
||||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
const WINDOW_PADDING = 20;
|
const WINDOW_PADDING = 10;
|
||||||
|
|
||||||
export default function RelativeModal({ className, role = 'dialog', children, onDismiss, portalRootID, relativeTo }) {
|
export default function RelativeModal({
|
||||||
const [position, setPosition] = useState({ top: -999, left: 0, width: 0 });
|
className,
|
||||||
|
role = 'dialog',
|
||||||
|
children,
|
||||||
|
onDismiss,
|
||||||
|
portalRootID,
|
||||||
|
relativeTo,
|
||||||
|
widthRelative = false,
|
||||||
|
}) {
|
||||||
|
const [position, setPosition] = useState({ top: -999, left: -999 });
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const portalRoot = portalRootID && document.getElementById(portalRootID);
|
const portalRoot = portalRootID && document.getElementById(portalRootID);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
@ -44,12 +52,15 @@ export default function RelativeModal({ className, role = 'dialog', children, on
|
|||||||
const windowWidth = window.innerWidth;
|
const windowWidth = window.innerWidth;
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect();
|
const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect();
|
||||||
const { x, y, width, height } = relativeTo.current.getBoundingClientRect();
|
const { x, y, width: relativeWidth, height } = relativeTo.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
const width = widthRelative ? relativeWidth : menuWidth;
|
||||||
|
|
||||||
let top = y + height;
|
let top = y + height;
|
||||||
let left = x;
|
let left = x;
|
||||||
// too far right
|
// too far right
|
||||||
if (left + menuWidth >= windowWidth - WINDOW_PADDING) {
|
if (left + width >= windowWidth - WINDOW_PADDING) {
|
||||||
left = windowWidth - menuWidth - WINDOW_PADDING;
|
left = windowWidth - width - WINDOW_PADDING;
|
||||||
}
|
}
|
||||||
// too far left
|
// too far left
|
||||||
else if (left < WINDOW_PADDING) {
|
else if (left < WINDOW_PADDING) {
|
||||||
@ -65,20 +76,23 @@ export default function RelativeModal({ className, role = 'dialog', children, on
|
|||||||
}
|
}
|
||||||
|
|
||||||
const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2;
|
const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2;
|
||||||
setPosition({ left, top: top + window.scrollY, width, height: maxHeight });
|
const newPosition = { left: left + window.scrollX, top: top + window.scrollY, maxHeight };
|
||||||
|
if (widthRelative) {
|
||||||
|
newPosition.width = relativeWidth;
|
||||||
|
}
|
||||||
|
setPosition(newPosition);
|
||||||
const focusable = ref.current.querySelector('[tabindex]');
|
const focusable = ref.current.querySelector('[tabindex]');
|
||||||
focusable && console.log('focusing');
|
|
||||||
focusable && focusable.focus();
|
focusable && focusable.focus();
|
||||||
}
|
}
|
||||||
}, [relativeTo && relativeTo.current, ref && ref.current]);
|
}, [relativeTo && relativeTo.current, ref && ref.current, widthRelative]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (position.width) {
|
if (position.top >= 0) {
|
||||||
setShow(true);
|
setShow(true);
|
||||||
} else {
|
} else {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}
|
}
|
||||||
}, [show, position.width, ref.current]);
|
}, [show, position.top, ref.current]);
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -91,13 +105,7 @@ export default function RelativeModal({ className, role = 'dialog', children, on
|
|||||||
onkeydown={handleKeydown}
|
onkeydown={handleKeydown}
|
||||||
role={role}
|
role={role}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={
|
style={position.top >= 0 ? position : null}
|
||||||
position.width > 0
|
|
||||||
? `min-width: ${position.width}px; ${position.height ? `max-height: ${position.height}px;` : ''} top: ${
|
|
||||||
position.top
|
|
||||||
}px; left: ${position.left}px`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,7 +95,7 @@ export default function Select({ label, onChange, options: inputOptions = [], se
|
|||||||
value={options[selected]?.label}
|
value={options[selected]?.label}
|
||||||
/>
|
/>
|
||||||
{showMenu ? (
|
{showMenu ? (
|
||||||
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref}>
|
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref} widthRelative>
|
||||||
{options.map(({ value, label }, i) => (
|
{options.map(({ value, label }, i) => (
|
||||||
<MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
|
<MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
|
||||||
))}
|
))}
|
||||||
|
Loading…
Reference in New Issue
Block a user