From 363224d741c8b3a5c0589dcdb61c114245f7c527 Mon Sep 17 00:00:00 2001 From: Axel Boberg Date: Tue, 23 Jan 2024 00:34:50 +0100 Subject: [PATCH] Fix an issue with context menus appearing below content in the rundown and allow single types to render as their own categories Signed-off-by: Axel Boberg --- app/components/ContextMenu/index.jsx | 39 +++++++++++++++++-- app/components/ContextMenu/style.css | 21 ++++++++-- app/components/ContextMenuItem/index.jsx | 2 +- .../app/components/ContextAddMenu/index.jsx | 24 ++++++++++-- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/app/components/ContextMenu/index.jsx b/app/components/ContextMenu/index.jsx index cae523c..cb9e414 100644 --- a/app/components/ContextMenu/index.jsx +++ b/app/components/ContextMenu/index.jsx @@ -1,11 +1,37 @@ import React from 'react' +import { createPortal } from 'react-dom' + import './style.css' +/** + * A threshold for how long the context menu has + * to have been open before an event can close it + * + * This it to prevent the same event to + * both open and close a context menu + * + * @type { Number } + */ +const OPEN_THRESHOLD_MS = 100 + export const ContextMenu = ({ x, y, children, onClose = () => {} }) => { const elRef = React.useRef() + const openTimestampRef = React.useRef() + + React.useEffect(() => { + openTimestampRef.current = Date.now() + }, []) React.useEffect(() => { function closeContext () { + /* + Check how long the context menu has been opened + to prevent it from closing on the same event that + opened it + */ + if (Date.now() - openTimestampRef.current <= OPEN_THRESHOLD_MS) { + return + } onClose() } @@ -20,8 +46,15 @@ export const ContextMenu = ({ x, y, children, onClose = () => {} }) => { }, [onClose]) return ( -
- {children} -
+ <> + { + createPortal( +
+ {children} +
, + document.body + ) + } + ) } diff --git a/app/components/ContextMenu/style.css b/app/components/ContextMenu/style.css index cc1ce96..94fd9fa 100644 --- a/app/components/ContextMenu/style.css +++ b/app/components/ContextMenu/style.css @@ -8,10 +8,25 @@ color: black; border-radius: 5px; - overflow: hidden; - + box-sizing: border-box; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - + + margin-top: -3px; + + animation-name: context-menu-in; + animation-duration: 200ms; + animation-fill-mode: forwards; + + overflow: hidden; z-index: 100; +} + +@keyframes context-menu-in { + 0% { + margin-top: -3px; + } + 100% { + margin-top: 0; + } } \ No newline at end of file diff --git a/app/components/ContextMenuItem/index.jsx b/app/components/ContextMenuItem/index.jsx index 9178ba9..a2d395b 100644 --- a/app/components/ContextMenuItem/index.jsx +++ b/app/components/ContextMenuItem/index.jsx @@ -23,7 +23,7 @@ const CTX_MENU_OFFSET_Y_PX = 5 * mouse leaves the current item * @type { Number } */ -const MOUSE_LEAVE_DELAY_MS = 200 +const MOUSE_LEAVE_DELAY_MS = 150 export const ContextMenuItem = ({ text, children = [], onClick = () => {} }) => { const elRef = React.useRef() diff --git a/plugins/rundown/app/components/ContextAddMenu/index.jsx b/plugins/rundown/app/components/ContextAddMenu/index.jsx index 1a3a819..1545090 100644 --- a/plugins/rundown/app/components/ContextAddMenu/index.jsx +++ b/plugins/rundown/app/components/ContextAddMenu/index.jsx @@ -5,6 +5,8 @@ import { SharedContext } from '../../sharedContext' import { ContextMenuItem } from '../../../../../app/components/ContextMenuItem' +const NO_CATEGORY_ID = '__none__' + export function ContextAddMenu ({ onAdd = () => {} }) { const [shared] = React.useContext(SharedContext) @@ -28,7 +30,7 @@ export function ContextAddMenu ({ onAdd = () => {} }) { if (!type.name) { continue } - const categoryName = type.category || type.id + const categoryName = type.category || NO_CATEGORY_ID if (!out[categoryName]) { out[categoryName] = [] } @@ -41,10 +43,26 @@ export function ContextAddMenu ({ onAdd = () => {} }) { <> { Object.entries(categories || {}) + /* + Make sure items that don't belong to + a category are rendered first + */ + .sort((a) => a[0] === NO_CATEGORY_ID ? -1 : 1) .map(([id, category]) => { - if (category.length === 1) { - return handleClick(category[0].id)} /> + /* + Render single items that don't + belong to any specific category + */ + if (id === NO_CATEGORY_ID) { + return category.map(type => + handleClick(type.id)} /> + ) } + + /* + Render categories + as nested menus + */ return ( {