diff --git a/theatre/studio/src/UIRoot/UIRoot.tsx b/theatre/studio/src/UIRoot/UIRoot.tsx index 8bad5c9..5819c06 100644 --- a/theatre/studio/src/UIRoot/UIRoot.tsx +++ b/theatre/studio/src/UIRoot/UIRoot.tsx @@ -57,6 +57,10 @@ export default function UIRoot() { const [portalLayerRef, portalLayer] = useRefAndState( undefined as $IntentionalAny, ) + const [containerRef, container] = useRefAndState( + undefined as $IntentionalAny, + ) + useKeyboardShortcuts() const inside = usePrism(() => { const visiblityState = val(studio.atomP.ahistoric.visibilityState) @@ -75,7 +79,7 @@ export default function UIRoot() { - + {shouldShowGlobalToolbar && } diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx index bf6278e..cdf3b71 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx @@ -19,6 +19,7 @@ import { import {GoChevronLeft, GoChevronRight} from 'react-icons/all' import LengthEditorPopover from './LengthEditorPopover' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' +import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' const coverWidth = 1000 @@ -136,7 +137,12 @@ const LengthIndicator: React.FC = ({layoutP}) => { {}, () => { return ( - + + + ) }, ) diff --git a/theatre/studio/src/uiComponents/Popover/BasicPopover.tsx b/theatre/studio/src/uiComponents/Popover/BasicPopover.tsx index a9bbc88..262e3a1 100644 --- a/theatre/studio/src/uiComponents/Popover/BasicPopover.tsx +++ b/theatre/studio/src/uiComponents/Popover/BasicPopover.tsx @@ -1,10 +1,12 @@ import type {$IntentionalAny} from '@theatre/shared/utils/types' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' +import {transparentize} from 'polished' import React from 'react' import styled from 'styled-components' -import {popoverBackgroundColor} from './Popover' import PopoverArrow, {popoverArrowColor} from './PopoverArrow' +export const popoverBackgroundColor = transparentize(0.05, `#2a2a31`) + const Container = styled.div` position: absolute; background: ${popoverBackgroundColor}; diff --git a/theatre/studio/src/uiComponents/Popover/Popover.tsx b/theatre/studio/src/uiComponents/Popover/Popover.tsx deleted file mode 100644 index d7ce10f..0000000 --- a/theatre/studio/src/uiComponents/Popover/Popover.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' -import getStudio from '@theatre/studio/getStudio' -import useBoundingClientRect from '@theatre/studio/uiComponents/useBoundingClientRect' -import transparentize from 'polished/lib/color/transparentize' -import React, {useLayoutEffect, useRef, useState} from 'react' -import {createPortal} from 'react-dom' -import useWindowSize from 'react-use/esm/useWindowSize' -import styled from 'styled-components' -import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside' -import PopoverArrow from './PopoverArrow' -import {usePopoverContext} from './PopoverContext' - -export const popoverBackgroundColor = transparentize(0.05, `#2a2a31`) -const minimumDistanceOfArrowToEdgeOfPopover = 8 - -const Container = styled.ul` - position: absolute; - z-index: 10000; - background: ${popoverBackgroundColor}; - color: white; - padding: 0; - margin: 0; - cursor: default; - ${pointerEventsAutoInNormalMode}; - border-radius: 3px; -` - -const Popover: React.FC<{ - target: Element - children: () => React.ReactNode - className?: string -}> = (props) => { - const {pointerDistanceThreshold, onPointerOutOfThreshold} = - usePopoverContext() - - const [container, setContainer] = useState(null) - const arrowRef = useRef(null) - - const containerRect = useBoundingClientRect(container) - const targetRect = useBoundingClientRect(props.target) - const windowSize = useWindowSize() - - useLayoutEffect(() => { - if (!containerRect || !container || !targetRect) return - - const gap = 8 - const arrow = arrowRef.current! - - let verticalPlacement: 'bottom' | 'top' | 'overlay' = 'bottom' - let top = 0 - let left = 0 - if (targetRect.bottom + containerRect.height + gap < windowSize.height) { - verticalPlacement = 'bottom' - top = targetRect.bottom + gap - arrow.style.top = '0px' - } else if (targetRect.top > containerRect.height + gap) { - verticalPlacement = 'top' - top = targetRect.top - (containerRect.height + gap) - arrow.style.bottom = '0px' - arrow.style.transform = 'rotateZ(180deg)' - } else { - verticalPlacement = 'overlay' - } - - let arrowLeft = 0 - if (verticalPlacement !== 'overlay') { - const anchorLeft = targetRect.left + targetRect.width / 2 - if (anchorLeft < containerRect.width / 2) { - left = gap - arrowLeft = Math.max( - anchorLeft - gap, - minimumDistanceOfArrowToEdgeOfPopover, - ) - } else if (anchorLeft + containerRect.width / 2 > windowSize.width) { - left = windowSize.width - (gap + containerRect.width) - arrowLeft = Math.min( - anchorLeft - left, - containerRect.width - minimumDistanceOfArrowToEdgeOfPopover, - ) - } else { - left = anchorLeft - containerRect.width / 2 - arrowLeft = containerRect.width / 2 - } - arrow.style.left = arrowLeft + 'px' - } - - const pos = {left, top} - - container.style.left = pos.left + 'px' - container.style.top = pos.top + 'px' - - const onMouseMove = (e: MouseEvent) => { - if ( - e.clientX < pos.left - pointerDistanceThreshold || - e.clientX > pos.left + containerRect.width + pointerDistanceThreshold || - e.clientY < pos.top - pointerDistanceThreshold || - e.clientY > pos.top + containerRect.height + pointerDistanceThreshold - ) { - onPointerOutOfThreshold() - } - } - - window.addEventListener('mousemove', onMouseMove) - - return () => { - window.removeEventListener('mousemove', onMouseMove) - } - }, [ - containerRect, - container, - props.target, - targetRect, - windowSize, - onPointerOutOfThreshold, - ]) - - useOnClickOutside(container, onPointerOutOfThreshold) - - return createPortal( - - - {props.children()} - , - getStudio()!.ui.containerShadow, - ) -} - -export default Popover diff --git a/theatre/studio/src/uiComponents/Popover/PopoverContext.tsx b/theatre/studio/src/uiComponents/Popover/PopoverContext.tsx deleted file mode 100644 index 3b87c93..0000000 --- a/theatre/studio/src/uiComponents/Popover/PopoverContext.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type {$IntentionalAny} from '@theatre/shared/utils/types' -import {createContext, useContext, default as React} from 'react' - -export type PopoverContext = { - triggerPoint?: {clientX: number; clientY: number} - onPointerOutOfThreshold: () => void - /** - * How far from the menu should the pointer travel to auto close the menu - */ - pointerDistanceThreshold: number -} - -const defaultPointerDistanceThreshold = 200 - -const ctx = createContext(null as $IntentionalAny) - -export const usePopoverContext = () => useContext(ctx) - -export const PopoverContextProvider: React.FC<{ - triggerPoint: PopoverContext['triggerPoint'] - onPointerOutOfThreshold: PopoverContext['onPointerOutOfThreshold'] - pointerDistanceThreshold?: number -}> = ({ - children, - triggerPoint, - pointerDistanceThreshold = defaultPointerDistanceThreshold, - onPointerOutOfThreshold, -}) => { - return ( - - {children} - - ) -} diff --git a/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx b/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx index 1e970b2..870d382 100644 --- a/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx +++ b/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx @@ -10,7 +10,6 @@ import React, { } from 'react' const ctx = createContext<{ - portalTarget: HTMLElement cur: IDerivation set: (id: number, delay: number) => void }>(null!) @@ -40,10 +39,7 @@ export const useTooltipOpenState = (): [ return [isOpen, setIsOpen] } -const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({ - children, - portalTarget, -}) => { +const TooltipContext: React.FC<{}> = ({children}) => { const currentTooltipId = useMemo(() => new Box(-1), []) const cur = currentTooltipId.derivation @@ -65,9 +61,7 @@ const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({ } }, []) - return ( - {children} - ) + return {children} } export default TooltipContext diff --git a/theatre/studio/src/uiComponents/Popover/usePopover.tsx b/theatre/studio/src/uiComponents/Popover/usePopover.tsx index ec7f4e3..1d8b09d 100644 --- a/theatre/studio/src/uiComponents/Popover/usePopover.tsx +++ b/theatre/studio/src/uiComponents/Popover/usePopover.tsx @@ -1,6 +1,7 @@ -import noop from '@theatre/shared/utils/noop' -import React, {useCallback, useState} from 'react' -import {PopoverContextProvider} from './PopoverContext' +import React, {useCallback, useContext, useState} from 'react' +import {createPortal} from 'react-dom' +import {PortalContext} from 'reakit' +import TooltipWrapper from './TooltipWrapper' type OpenFn = (e: React.MouseEvent, target: HTMLElement) => void type CloseFn = () => void @@ -20,7 +21,7 @@ export default function usePopover( closeWhenPointerIsDistant?: boolean pointerDistanceThreshold?: number }, - render: () => React.ReactNode, + render: () => React.ReactElement, ): [node: React.ReactNode, open: OpenFn, close: CloseFn, isOpen: boolean] { const [state, setState] = useState({ isOpen: false, @@ -38,23 +39,14 @@ export default function usePopover( setState({isOpen: false}) }, []) + const portalLayer = useContext(PortalContext) + const node = state.isOpen ? ( - + createPortal( + , + portalLayer!, + ) ) : ( - // <> ) diff --git a/theatre/studio/src/uiComponents/Popover/useTooltip.tsx b/theatre/studio/src/uiComponents/Popover/useTooltip.tsx index 59df2b0..e6379c1 100644 --- a/theatre/studio/src/uiComponents/Popover/useTooltip.tsx +++ b/theatre/studio/src/uiComponents/Popover/useTooltip.tsx @@ -1,11 +1,12 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {MutableRefObject} from 'react' +import {useContext} from 'react' import {useEffect} from 'react' import React from 'react' import TooltipWrapper from './TooltipWrapper' -import getStudio from '@theatre/studio/getStudio' import {createPortal} from 'react-dom' import {useTooltipOpenState} from './TooltipContext' +import {PortalContext} from 'reakit' export default function useTooltip( opts: {enabled?: boolean; delay?: number}, @@ -42,11 +43,13 @@ export default function useTooltip( } }, [targetRef, enabled, opts.delay]) + const portalLayer = useContext(PortalContext) + const node = enabled && isOpen && targetNode ? ( createPortal( , - getStudio()!.ui.containerShadow, + portalLayer!, ) ) : ( <>