Simplified the popovers
This commit is contained in:
parent
69f787d5cf
commit
af2ff59d5e
8 changed files with 33 additions and 196 deletions
|
@ -57,6 +57,10 @@ export default function UIRoot() {
|
|||
const [portalLayerRef, portalLayer] = useRefAndState<HTMLDivElement>(
|
||||
undefined as $IntentionalAny,
|
||||
)
|
||||
const [containerRef, container] = useRefAndState<HTMLDivElement>(
|
||||
undefined as $IntentionalAny,
|
||||
)
|
||||
|
||||
useKeyboardShortcuts()
|
||||
const inside = usePrism(() => {
|
||||
const visiblityState = val(studio.atomP.ahistoric.visibilityState)
|
||||
|
@ -75,7 +79,7 @@ export default function UIRoot() {
|
|||
<GlobalStyle />
|
||||
<ProvideTheme>
|
||||
<PortalContext.Provider value={portalLayer}>
|
||||
<TooltipContext portalTarget={portalLayer}>
|
||||
<TooltipContext>
|
||||
<Container>
|
||||
<PortalLayer ref={portalLayerRef} />
|
||||
{shouldShowGlobalToolbar && <GlobalToolbar />}
|
||||
|
|
|
@ -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<IProps> = ({layoutP}) => {
|
|||
{},
|
||||
() => {
|
||||
return (
|
||||
<LengthEditorPopover layoutP={layoutP} onRequestClose={closePopover} />
|
||||
<BasicPopover>
|
||||
<LengthEditorPopover
|
||||
layoutP={layoutP}
|
||||
onRequestClose={closePopover}
|
||||
/>
|
||||
</BasicPopover>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<HTMLElement | null>(null)
|
||||
const arrowRef = useRef<HTMLDivElement>(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(
|
||||
<Container ref={setContainer} className={props.className}>
|
||||
<PopoverArrow ref={arrowRef} />
|
||||
{props.children()}
|
||||
</Container>,
|
||||
getStudio()!.ui.containerShadow,
|
||||
)
|
||||
}
|
||||
|
||||
export default Popover
|
|
@ -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<PopoverContext>(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 (
|
||||
<ctx.Provider
|
||||
value={{triggerPoint, pointerDistanceThreshold, onPointerOutOfThreshold}}
|
||||
>
|
||||
{children}
|
||||
</ctx.Provider>
|
||||
)
|
||||
}
|
|
@ -10,7 +10,6 @@ import React, {
|
|||
} from 'react'
|
||||
|
||||
const ctx = createContext<{
|
||||
portalTarget: HTMLElement
|
||||
cur: IDerivation<number>
|
||||
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 (
|
||||
<ctx.Provider value={{cur, set, portalTarget}}>{children}</ctx.Provider>
|
||||
)
|
||||
return <ctx.Provider value={{cur, set}}>{children}</ctx.Provider>
|
||||
}
|
||||
|
||||
export default TooltipContext
|
||||
|
|
|
@ -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<State>({
|
||||
isOpen: false,
|
||||
|
@ -38,23 +39,14 @@ export default function usePopover(
|
|||
setState({isOpen: false})
|
||||
}, [])
|
||||
|
||||
const portalLayer = useContext(PortalContext)
|
||||
|
||||
const node = state.isOpen ? (
|
||||
<PopoverContextProvider
|
||||
children={render}
|
||||
triggerPoint={state.clickPoint}
|
||||
pointerDistanceThreshold={opts.pointerDistanceThreshold}
|
||||
onPointerOutOfThreshold={
|
||||
opts.closeWhenPointerIsDistant === false ? noop : close
|
||||
}
|
||||
/>
|
||||
createPortal(
|
||||
<TooltipWrapper children={render} target={state.target} />,
|
||||
portalLayer!,
|
||||
)
|
||||
) : (
|
||||
// <Popover
|
||||
// children={render}
|
||||
// triggerPoint={state.clickPoint}
|
||||
// target={state.target}
|
||||
// onPointerOutOfThreshold={
|
||||
// }
|
||||
// />
|
||||
<></>
|
||||
)
|
||||
|
||||
|
|
|
@ -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(
|
||||
<TooltipWrapper children={render} target={targetNode} />,
|
||||
getStudio()!.ui.containerShadow,
|
||||
portalLayer!,
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
|
|
Loading…
Reference in a new issue