From f16bfecab88f9024d48c3fe2666b27b01da1ad0d Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 18:49:47 +0800 Subject: [PATCH] refactor: refactor popper --- .../components/command-panel/CommandPanel.tsx | 4 +- .../PendantHistoryPanel.tsx | 2 +- .../pendant-popover/PendantPopover.tsx | 2 +- .../pendant-render/PandentRender.tsx | 4 +- libs/components/ui/src/popover/Popover.tsx | 12 +- libs/components/ui/src/popper/Popper.tsx | 326 +++++++++--------- libs/components/ui/src/popper/interface.ts | 29 +- libs/components/ui/src/tooltip/Tooltip.tsx | 14 +- 8 files changed, 188 insertions(+), 205 deletions(-) diff --git a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx b/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx index 6c45bfc4e6..bc22965a71 100644 --- a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx +++ b/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx @@ -23,7 +23,7 @@ export const CommandPanel = ({ app }: { app: TldrawApp }) => { ? app.getScreenPoint([bounds.minX, bounds.minY]) : undefined; - const anchor = getAnchor({ + const anchorEl = getAnchor({ x: point?.[0] || 0, y: (point?.[1] || 0) + 40, width: bounds?.width ? bounds.width * camera.zoom : 0, @@ -101,7 +101,7 @@ export const CommandPanel = ({ app }: { app: TldrawApp }) => { diff --git a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx index ade7ced37d..3efc36721f 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx @@ -91,7 +91,7 @@ export const PendantHistoryPanel = ({ return ( { + popperHandlerRef={ref => { popoverHandlerRef.current[item.id] = ref; }} placement="bottom-start" diff --git a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx index 09ad317777..63d01064d9 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx @@ -17,7 +17,7 @@ export const PendantPopover = ( const popoverHandlerRef = useRef(); return ( { return ( { + popperHandlerRef={ref => { popoverHandlerRef.current[id] = ref; }} container={blockRenderContainerRef.current} @@ -107,7 +107,7 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => { iconStyle={{ marginTop: 4 }} trigger="click" // trigger={isKanbanView ? 'hover' : 'click'} - container={blockRenderContainerRef.current} + // container={blockRenderContainerRef.current} /> diff --git a/libs/components/ui/src/popover/Popover.tsx b/libs/components/ui/src/popover/Popover.tsx index d03a65f322..c165b45197 100644 --- a/libs/components/ui/src/popover/Popover.tsx +++ b/libs/components/ui/src/popover/Popover.tsx @@ -1,6 +1,6 @@ import type { MuiPopperPlacementType as PopperPlacementType } from '../mui'; -import React, { forwardRef, type PropsWithChildren } from 'react'; -import { type PopperHandler, Popper } from '../popper'; +import React, { type PropsWithChildren } from 'react'; +import { Popper } from '../popper'; import { PopoverContainer } from './Container'; import type { PopoverProps, PopoverDirection } from './interface'; @@ -25,15 +25,11 @@ export const placementToContainerDirection: Record< 'auto-end': 'none', }; -export const Popover = forwardRef< - PopperHandler, - PropsWithChildren ->((props, ref) => { +export const Popover = (props: PropsWithChildren) => { const { popoverDirection, placement, content, children, style } = props; return ( ); -}); +}; diff --git a/libs/components/ui/src/popper/Popper.tsx b/libs/components/ui/src/popper/Popper.tsx index f74cf89980..12a6211015 100644 --- a/libs/components/ui/src/popper/Popper.tsx +++ b/libs/components/ui/src/popper/Popper.tsx @@ -1,184 +1,182 @@ import React, { - forwardRef, + CSSProperties, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react'; -import { - MuiPopper, - MuiClickAwayListener as ClickAwayListener, - MuiGrow as Grow, -} from '../mui'; +/* eslint-disable no-restricted-imports */ +import PopperUnstyled from '@mui/base/PopperUnstyled'; +/* eslint-disable no-restricted-imports */ +import ClickAwayListener from '@mui/base/ClickAwayListener'; +/* eslint-disable no-restricted-imports */ +import Grow from '@mui/material/Grow'; + import { styled } from '../styled'; -import { PopperProps, PopperHandler, VirtualElement } from './interface'; +import { PopperProps, VirtualElement } from './interface'; import { useTheme } from '../theme'; import { PopperArrow } from './PopoverArrow'; -export const Popper = forwardRef( - ( - { - children, - content, - anchor: propsAnchor, - placement = 'top-start', - defaultVisible = false, - container, - keepMounted = false, - visible: propsVisible, - trigger = 'hover', - pointerEnterDelay = 100, - pointerLeaveDelay = 100, - onVisibleChange, - popoverStyle, - popoverClassName, - anchorStyle, - anchorClassName, - zIndex, - offset = [0, 5], - showArrow = false, - }, - ref - ) => { - const [anchorEl, setAnchorEl] = useState(null); - const [visible, setVisible] = useState(defaultVisible); - const [arrowRef, setArrowRef] = useState(null); +export const Popper = ({ + children, + content, + anchorEl: propsAnchorEl, + placement = 'top-start', + defaultVisible = false, + visible: propsVisible, + trigger = 'hover', + pointerEnterDelay = 100, + pointerLeaveDelay = 100, + onVisibleChange, + popoverStyle, + popoverClassName, + anchorStyle, + anchorClassName, + zIndex, + offset = [0, 5], + showArrow = false, + popperHandlerRef, + ...popperProps +}: PopperProps) => { + const [anchorEl, setAnchorEl] = useState(null); + const [visible, setVisible] = useState(defaultVisible); + const [arrowRef, setArrowRef] = useState(null); + const popperRef = useRef(); + const pointerLeaveTimer = useRef(); + const pointerEnterTimer = useRef(); - const pointerLeaveTimer = useRef(); - const pointerEnterTimer = useRef(); - - const visibleControlledByParent = typeof propsVisible !== 'undefined'; - const isAnchorCustom = typeof propsAnchor !== 'undefined'; - - const hasHoverTrigger = useMemo(() => { - return ( - trigger === 'hover' || - (Array.isArray(trigger) && trigger.includes('hover')) - ); - }, [trigger]); - - const hasClickTrigger = useMemo(() => { - return ( - trigger === 'click' || - (Array.isArray(trigger) && trigger.includes('click')) - ); - }, [trigger]); - - const theme = useTheme(); - - const onPointerEnterHandler = () => { - if (!hasHoverTrigger || visibleControlledByParent) { - return; - } - window.clearTimeout(pointerLeaveTimer.current); - - pointerEnterTimer.current = window.setTimeout(() => { - setVisible(true); - }, pointerEnterDelay); - }; - - const onPointerLeaveHandler = () => { - if (!hasHoverTrigger || visibleControlledByParent) { - return; - } - window.clearTimeout(pointerEnterTimer.current); - pointerLeaveTimer.current = window.setTimeout(() => { - setVisible(false); - }, pointerLeaveDelay); - }; - - useEffect(() => { - onVisibleChange?.(visible); - }, [visible, onVisibleChange]); - - useImperativeHandle(ref, () => { - return { - setVisible: (visible: boolean) => { - !visibleControlledByParent && setVisible(visible); - }, - }; - }); + const visibleControlledByParent = typeof propsVisible !== 'undefined'; + const isAnchorCustom = typeof propsAnchorEl !== 'undefined'; + const hasHoverTrigger = useMemo(() => { return ( - { - setVisible(false); - }} - > - - {isAnchorCustom ? null : ( -
setAnchorEl(dom)} - onClick={() => { - if ( - !hasClickTrigger || - visibleControlledByParent - ) { - return; - } - setVisible(!visible); - }} - onPointerEnter={onPointerEnterHandler} - onPointerLeave={onPointerLeaveHandler} - style={anchorStyle} - className={anchorClassName} - > - {children} -
- )} - - {({ TransitionProps }) => ( - -
- {showArrow && ( - - )} - {content} -
-
- )} -
-
-
+ trigger === 'hover' || + (Array.isArray(trigger) && trigger.includes('hover')) ); - } -); + }, [trigger]); + + const hasClickTrigger = useMemo(() => { + return ( + trigger === 'click' || + (Array.isArray(trigger) && trigger.includes('click')) + ); + }, [trigger]); + + const onPointerEnterHandler = () => { + if (!hasHoverTrigger || visibleControlledByParent) { + return; + } + window.clearTimeout(pointerLeaveTimer.current); + + pointerEnterTimer.current = window.setTimeout(() => { + setVisible(true); + }, pointerEnterDelay); + }; + + const onPointerLeaveHandler = () => { + if (!hasHoverTrigger || visibleControlledByParent) { + return; + } + window.clearTimeout(pointerEnterTimer.current); + pointerLeaveTimer.current = window.setTimeout(() => { + setVisible(false); + }, pointerLeaveDelay); + }; + + useEffect(() => { + onVisibleChange?.(visible); + }, [visible, onVisibleChange]); + + useImperativeHandle(popperHandlerRef, () => { + return { + setVisible: (visible: boolean) => { + !visibleControlledByParent && setVisible(visible); + }, + }; + }); + + return ( + { + setVisible(false); + }} + > + + {isAnchorCustom ? null : ( +
setAnchorEl(dom)} + onClick={() => { + if (!hasClickTrigger || visibleControlledByParent) { + return; + } + setVisible(!visible); + }} + onPointerEnter={onPointerEnterHandler} + onPointerLeave={onPointerLeaveHandler} + style={anchorStyle} + className={anchorClassName} + > + {children} +
+ )} + + {({ TransitionProps }) => ( + +
+ {showArrow && ( + + )} + {content} +
+
+ )} +
+
+
+ ); +}; // The children of ClickAwayListener must be a DOM Node to judge whether the click is outside, use node.contains const Container = styled('div')({ display: 'contents', }); + +const BasicStyledPopper = styled(PopperUnstyled)<{ + zIndex?: CSSProperties['zIndex']; +}>(({ zIndex, theme }) => { + return { + zIndex: zIndex || theme.affine.zIndex.popover, + }; +}); diff --git a/libs/components/ui/src/popper/interface.ts b/libs/components/ui/src/popper/interface.ts index 9f52cf551f..4930de15f1 100644 --- a/libs/components/ui/src/popper/interface.ts +++ b/libs/components/ui/src/popper/interface.ts @@ -1,6 +1,9 @@ -import type { CSSProperties, ReactNode } from 'react'; -import type { MuiPopperPlacementType as PopperPlacementType } from '../mui'; - +import type { CSSProperties, ReactNode, Ref } from 'react'; +/* eslint-disable no-restricted-imports */ +import { + type PopperUnstyledProps, + type PopperPlacementType, +} from '@mui/base/PopperUnstyled'; export type VirtualElement = { getBoundingClientRect: () => ClientRect | DOMRect; contextElement?: Element; @@ -21,26 +24,12 @@ export type PopperProps = { // Popover trigger children?: ReactNode; - // Position of Popover - placement?: PopperPlacementType; - - // The popover will pop up based on the anchor position - // And if this parameter is passed, children will not be rendered - anchor?: VirtualElement | (() => VirtualElement); - // Whether the default is implicit defaultVisible?: boolean; // Used to manually control the visibility of the Popover visible?: boolean; - // A HTML element or function that returns one. The container will have the portal children appended to it. - // By default, it uses the body of the top-level document object, so it's simply document.body most of the time. - container?: HTMLElement; - - // Always keep the children in the DOM. This prop can be useful in SEO situation or when you want to maximize the responsiveness of the Popper - keepMounted?: boolean; - // TODO: support focus trigger?: 'hover' | 'click' | 'focus' | ('click' | 'hover' | 'focus')[]; @@ -66,9 +55,11 @@ export type PopperProps = { anchorClassName?: string; // Popover z-index - zIndex?: number; + zIndex?: CSSProperties['zIndex']; offset?: [number, number]; showArrow?: boolean; -}; + + popperHandlerRef?: Ref; +} & Omit; diff --git a/libs/components/ui/src/tooltip/Tooltip.tsx b/libs/components/ui/src/tooltip/Tooltip.tsx index 2c896e5c0d..ff0b47262c 100644 --- a/libs/components/ui/src/tooltip/Tooltip.tsx +++ b/libs/components/ui/src/tooltip/Tooltip.tsx @@ -1,5 +1,5 @@ -import { forwardRef, type PropsWithChildren, type CSSProperties } from 'react'; -import { type PopperHandler, type PopperProps, Popper } from '../popper'; +import { type PropsWithChildren, type CSSProperties } from 'react'; +import { type PopperProps, Popper } from '../popper'; import { PopoverContainer, placementToContainerDirection } from '../popover'; import type { TooltipProps } from './interface'; import { useTheme } from '../theme'; @@ -14,17 +14,15 @@ const useTooltipStyle = (): CSSProperties => { }; }; -export const Tooltip = forwardRef< - PopperHandler, - PropsWithChildren ->((props, ref) => { +export const Tooltip = ( + props: PropsWithChildren +) => { const { content, placement = 'top-start' } = props; const style = useTooltipStyle(); // If there is no content, hide forever const visibleProp = content ? {} : { visible: false }; return ( ); -}); +};