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>(
|
const [portalLayerRef, portalLayer] = useRefAndState<HTMLDivElement>(
|
||||||
undefined as $IntentionalAny,
|
undefined as $IntentionalAny,
|
||||||
)
|
)
|
||||||
|
const [containerRef, container] = useRefAndState<HTMLDivElement>(
|
||||||
|
undefined as $IntentionalAny,
|
||||||
|
)
|
||||||
|
|
||||||
useKeyboardShortcuts()
|
useKeyboardShortcuts()
|
||||||
const inside = usePrism(() => {
|
const inside = usePrism(() => {
|
||||||
const visiblityState = val(studio.atomP.ahistoric.visibilityState)
|
const visiblityState = val(studio.atomP.ahistoric.visibilityState)
|
||||||
|
@ -75,7 +79,7 @@ export default function UIRoot() {
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<ProvideTheme>
|
<ProvideTheme>
|
||||||
<PortalContext.Provider value={portalLayer}>
|
<PortalContext.Provider value={portalLayer}>
|
||||||
<TooltipContext portalTarget={portalLayer}>
|
<TooltipContext>
|
||||||
<Container>
|
<Container>
|
||||||
<PortalLayer ref={portalLayerRef} />
|
<PortalLayer ref={portalLayerRef} />
|
||||||
{shouldShowGlobalToolbar && <GlobalToolbar />}
|
{shouldShowGlobalToolbar && <GlobalToolbar />}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
import {GoChevronLeft, GoChevronRight} from 'react-icons/all'
|
import {GoChevronLeft, GoChevronRight} from 'react-icons/all'
|
||||||
import LengthEditorPopover from './LengthEditorPopover'
|
import LengthEditorPopover from './LengthEditorPopover'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
|
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
|
||||||
|
|
||||||
const coverWidth = 1000
|
const coverWidth = 1000
|
||||||
|
|
||||||
|
@ -136,7 +137,12 @@ const LengthIndicator: React.FC<IProps> = ({layoutP}) => {
|
||||||
{},
|
{},
|
||||||
() => {
|
() => {
|
||||||
return (
|
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 type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
|
import {transparentize} from 'polished'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {popoverBackgroundColor} from './Popover'
|
|
||||||
import PopoverArrow, {popoverArrowColor} from './PopoverArrow'
|
import PopoverArrow, {popoverArrowColor} from './PopoverArrow'
|
||||||
|
|
||||||
|
export const popoverBackgroundColor = transparentize(0.05, `#2a2a31`)
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: ${popoverBackgroundColor};
|
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'
|
} from 'react'
|
||||||
|
|
||||||
const ctx = createContext<{
|
const ctx = createContext<{
|
||||||
portalTarget: HTMLElement
|
|
||||||
cur: IDerivation<number>
|
cur: IDerivation<number>
|
||||||
set: (id: number, delay: number) => void
|
set: (id: number, delay: number) => void
|
||||||
}>(null!)
|
}>(null!)
|
||||||
|
@ -40,10 +39,7 @@ export const useTooltipOpenState = (): [
|
||||||
return [isOpen, setIsOpen]
|
return [isOpen, setIsOpen]
|
||||||
}
|
}
|
||||||
|
|
||||||
const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({
|
const TooltipContext: React.FC<{}> = ({children}) => {
|
||||||
children,
|
|
||||||
portalTarget,
|
|
||||||
}) => {
|
|
||||||
const currentTooltipId = useMemo(() => new Box(-1), [])
|
const currentTooltipId = useMemo(() => new Box(-1), [])
|
||||||
const cur = currentTooltipId.derivation
|
const cur = currentTooltipId.derivation
|
||||||
|
|
||||||
|
@ -65,9 +61,7 @@ const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return <ctx.Provider value={{cur, set}}>{children}</ctx.Provider>
|
||||||
<ctx.Provider value={{cur, set, portalTarget}}>{children}</ctx.Provider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TooltipContext
|
export default TooltipContext
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import noop from '@theatre/shared/utils/noop'
|
import React, {useCallback, useContext, useState} from 'react'
|
||||||
import React, {useCallback, useState} from 'react'
|
import {createPortal} from 'react-dom'
|
||||||
import {PopoverContextProvider} from './PopoverContext'
|
import {PortalContext} from 'reakit'
|
||||||
|
import TooltipWrapper from './TooltipWrapper'
|
||||||
|
|
||||||
type OpenFn = (e: React.MouseEvent, target: HTMLElement) => void
|
type OpenFn = (e: React.MouseEvent, target: HTMLElement) => void
|
||||||
type CloseFn = () => void
|
type CloseFn = () => void
|
||||||
|
@ -20,7 +21,7 @@ export default function usePopover(
|
||||||
closeWhenPointerIsDistant?: boolean
|
closeWhenPointerIsDistant?: boolean
|
||||||
pointerDistanceThreshold?: number
|
pointerDistanceThreshold?: number
|
||||||
},
|
},
|
||||||
render: () => React.ReactNode,
|
render: () => React.ReactElement,
|
||||||
): [node: React.ReactNode, open: OpenFn, close: CloseFn, isOpen: boolean] {
|
): [node: React.ReactNode, open: OpenFn, close: CloseFn, isOpen: boolean] {
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
@ -38,23 +39,14 @@ export default function usePopover(
|
||||||
setState({isOpen: false})
|
setState({isOpen: false})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const portalLayer = useContext(PortalContext)
|
||||||
|
|
||||||
const node = state.isOpen ? (
|
const node = state.isOpen ? (
|
||||||
<PopoverContextProvider
|
createPortal(
|
||||||
children={render}
|
<TooltipWrapper children={render} target={state.target} />,
|
||||||
triggerPoint={state.clickPoint}
|
portalLayer!,
|
||||||
pointerDistanceThreshold={opts.pointerDistanceThreshold}
|
)
|
||||||
onPointerOutOfThreshold={
|
|
||||||
opts.closeWhenPointerIsDistant === false ? noop : close
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
// <Popover
|
|
||||||
// children={render}
|
|
||||||
// triggerPoint={state.clickPoint}
|
|
||||||
// target={state.target}
|
|
||||||
// onPointerOutOfThreshold={
|
|
||||||
// }
|
|
||||||
// />
|
|
||||||
<></>
|
<></>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {MutableRefObject} from 'react'
|
import type {MutableRefObject} from 'react'
|
||||||
|
import {useContext} from 'react'
|
||||||
import {useEffect} from 'react'
|
import {useEffect} from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import TooltipWrapper from './TooltipWrapper'
|
import TooltipWrapper from './TooltipWrapper'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
|
||||||
import {createPortal} from 'react-dom'
|
import {createPortal} from 'react-dom'
|
||||||
import {useTooltipOpenState} from './TooltipContext'
|
import {useTooltipOpenState} from './TooltipContext'
|
||||||
|
import {PortalContext} from 'reakit'
|
||||||
|
|
||||||
export default function useTooltip(
|
export default function useTooltip(
|
||||||
opts: {enabled?: boolean; delay?: number},
|
opts: {enabled?: boolean; delay?: number},
|
||||||
|
@ -42,11 +43,13 @@ export default function useTooltip(
|
||||||
}
|
}
|
||||||
}, [targetRef, enabled, opts.delay])
|
}, [targetRef, enabled, opts.delay])
|
||||||
|
|
||||||
|
const portalLayer = useContext(PortalContext)
|
||||||
|
|
||||||
const node =
|
const node =
|
||||||
enabled && isOpen && targetNode ? (
|
enabled && isOpen && targetNode ? (
|
||||||
createPortal(
|
createPortal(
|
||||||
<TooltipWrapper children={render} target={targetNode} />,
|
<TooltipWrapper children={render} target={targetNode} />,
|
||||||
getStudio()!.ui.containerShadow,
|
portalLayer!,
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
Loading…
Reference in a new issue