mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-19 11:02:26 +03:00
refactor: refactor popper
This commit is contained in:
parent
82c3d773d6
commit
f16bfecab8
@ -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 }) => {
|
||||
<Popover
|
||||
trigger="click"
|
||||
visible={!!point}
|
||||
anchor={anchor}
|
||||
anchorEl={anchorEl}
|
||||
popoverDirection="none"
|
||||
content={
|
||||
<PopoverContainer>
|
||||
|
@ -91,7 +91,7 @@ export const PendantHistoryPanel = ({
|
||||
return (
|
||||
<Popover
|
||||
key={item.id}
|
||||
ref={ref => {
|
||||
popperHandlerRef={ref => {
|
||||
popoverHandlerRef.current[item.id] = ref;
|
||||
}}
|
||||
placement="bottom-start"
|
||||
|
@ -17,7 +17,7 @@ export const PendantPopover = (
|
||||
const popoverHandlerRef = useRef<PopperHandler>();
|
||||
return (
|
||||
<Popover
|
||||
ref={popoverHandlerRef}
|
||||
popperHandlerRef={popoverHandlerRef}
|
||||
pointerEnterDelay={300}
|
||||
pointerLeaveDelay={200}
|
||||
placement="bottom-start"
|
||||
|
@ -56,7 +56,7 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => {
|
||||
|
||||
return (
|
||||
<Popover
|
||||
ref={ref => {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</MuiFade>
|
||||
|
@ -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<PopoverProps>
|
||||
>((props, ref) => {
|
||||
export const Popover = (props: PropsWithChildren<PopoverProps>) => {
|
||||
const { popoverDirection, placement, content, children, style } = props;
|
||||
return (
|
||||
<Popper
|
||||
{...props}
|
||||
ref={ref}
|
||||
content={
|
||||
<PopoverContainer
|
||||
style={style}
|
||||
@ -49,4 +45,4 @@ export const Popover = forwardRef<
|
||||
{children}
|
||||
</Popper>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -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<PopperHandler, PopperProps>(
|
||||
(
|
||||
{
|
||||
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<VirtualElement>(null);
|
||||
const [visible, setVisible] = useState(defaultVisible);
|
||||
const [arrowRef, setArrowRef] = useState<HTMLElement>(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<VirtualElement>(null);
|
||||
const [visible, setVisible] = useState(defaultVisible);
|
||||
const [arrowRef, setArrowRef] = useState<HTMLElement>(null);
|
||||
const popperRef = useRef();
|
||||
const pointerLeaveTimer = useRef<number>();
|
||||
const pointerEnterTimer = useRef<number>();
|
||||
|
||||
const pointerLeaveTimer = useRef<number>();
|
||||
const pointerEnterTimer = useRef<number>();
|
||||
|
||||
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 (
|
||||
<ClickAwayListener
|
||||
onClickAway={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
{isAnchorCustom ? null : (
|
||||
<div
|
||||
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
|
||||
onClick={() => {
|
||||
if (
|
||||
!hasClickTrigger ||
|
||||
visibleControlledByParent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setVisible(!visible);
|
||||
}}
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={anchorStyle}
|
||||
className={anchorClassName}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
<MuiPopper
|
||||
open={
|
||||
visibleControlledByParent ? propsVisible : visible
|
||||
}
|
||||
sx={{ zIndex: zIndex || theme.affine.zIndex.popover }}
|
||||
anchorEl={isAnchorCustom ? propsAnchor : anchorEl}
|
||||
placement={placement}
|
||||
container={container}
|
||||
keepMounted={keepMounted}
|
||||
transition
|
||||
modifiers={[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
enabled: showArrow,
|
||||
options: {
|
||||
element: arrowRef,
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<div
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={popoverStyle}
|
||||
className={popoverClassName}
|
||||
>
|
||||
{showArrow && (
|
||||
<PopperArrow
|
||||
placement={placement}
|
||||
ref={setArrowRef}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
</Grow>
|
||||
)}
|
||||
</MuiPopper>
|
||||
</Container>
|
||||
</ClickAwayListener>
|
||||
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 (
|
||||
<ClickAwayListener
|
||||
onClickAway={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
{isAnchorCustom ? null : (
|
||||
<div
|
||||
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
|
||||
onClick={() => {
|
||||
if (!hasClickTrigger || visibleControlledByParent) {
|
||||
return;
|
||||
}
|
||||
setVisible(!visible);
|
||||
}}
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={anchorStyle}
|
||||
className={anchorClassName}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
<BasicStyledPopper
|
||||
popperRef={popperRef}
|
||||
open={visibleControlledByParent ? propsVisible : visible}
|
||||
zIndex={zIndex}
|
||||
anchorEl={isAnchorCustom ? propsAnchorEl : anchorEl}
|
||||
placement={placement}
|
||||
transition
|
||||
modifiers={[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
enabled: showArrow,
|
||||
options: {
|
||||
element: arrowRef,
|
||||
},
|
||||
},
|
||||
]}
|
||||
{...popperProps}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<div
|
||||
onPointerEnter={onPointerEnterHandler}
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={popoverStyle}
|
||||
className={popoverClassName}
|
||||
>
|
||||
{showArrow && (
|
||||
<PopperArrow
|
||||
placement={placement}
|
||||
ref={setArrowRef}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
</Grow>
|
||||
)}
|
||||
</BasicStyledPopper>
|
||||
</Container>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
};
|
||||
|
||||
// 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,
|
||||
};
|
||||
});
|
||||
|
@ -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<PopperHandler>;
|
||||
} & Omit<PopperUnstyledProps, 'open' | 'ref'>;
|
||||
|
@ -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<PopperProps & TooltipProps>
|
||||
>((props, ref) => {
|
||||
export const Tooltip = (
|
||||
props: PropsWithChildren<PopperProps & TooltipProps>
|
||||
) => {
|
||||
const { content, placement = 'top-start' } = props;
|
||||
const style = useTooltipStyle();
|
||||
// If there is no content, hide forever
|
||||
const visibleProp = content ? {} : { visible: false };
|
||||
return (
|
||||
<Popper
|
||||
ref={ref}
|
||||
{...visibleProp}
|
||||
placement="top"
|
||||
{...props}
|
||||
@ -39,4 +37,4 @@ export const Tooltip = forwardRef<
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user