fix(core): fix menu shaking (#8187)

This commit is contained in:
EYHN
2024-09-11 03:42:13 +00:00
parent 7a546ff8a1
commit f009371e06
4 changed files with 3 additions and 146 deletions

View File

@@ -1,95 +0,0 @@
import { useCallback, useState } from 'react';
import { useRefEffect } from '../../../hooks';
export const useMenuContentController = ({
onOpenChange,
side,
defaultOpen,
sideOffset,
open: controlledOpen,
}: {
defaultOpen?: boolean;
side?: 'top' | 'bottom' | 'left' | 'right';
onOpenChange?: (open: boolean) => void;
open?: boolean;
sideOffset?: number;
} = {}) => {
const [open, setOpen] = useState(defaultOpen ?? false);
const actualOpen = controlledOpen ?? open;
const contentSide = side ?? 'bottom';
const [contentOffset, setContentOffset] = useState<number>(0);
const handleOpenChange = useCallback(
(open: boolean) => {
setOpen(open);
onOpenChange?.(open);
},
[onOpenChange]
);
const contentRef = useRefEffect<HTMLDivElement>(
contentElement => {
if (!actualOpen) return;
const wrapperElement = contentElement.parentNode as HTMLDivElement;
const updateContentOffset = () => {
if (!contentElement) return;
const contentRect = wrapperElement.getBoundingClientRect();
if (contentSide === 'bottom') {
setContentOffset(prev => {
const viewportHeight = window.innerHeight;
const newOffset = Math.min(
viewportHeight - (contentRect.bottom - prev),
0
);
return newOffset;
});
} else if (contentSide === 'top') {
setContentOffset(prev => {
const newOffset = Math.min(contentRect.top + prev, 0);
return newOffset;
});
} else if (contentSide === 'left') {
setContentOffset(prev => {
const newOffset = Math.min(contentRect.left + prev, 0);
return newOffset;
});
} else if (contentSide === 'right') {
setContentOffset(prev => {
const viewportWidth = window.innerWidth;
const newOffset = Math.min(
viewportWidth - (contentRect.right - prev),
0
);
return newOffset;
});
}
};
let animationFrame: number = 0;
const requestUpdateContentOffset = () => {
cancelAnimationFrame(animationFrame);
animationFrame = requestAnimationFrame(updateContentOffset);
};
const observer = new ResizeObserver(requestUpdateContentOffset);
observer.observe(wrapperElement);
window.addEventListener('resize', requestUpdateContentOffset);
requestUpdateContentOffset();
return () => {
observer.disconnect();
window.removeEventListener('resize', requestUpdateContentOffset);
cancelAnimationFrame(animationFrame);
};
},
[actualOpen, contentSide]
);
return {
handleOpenChange,
contentSide,
contentOffset: (sideOffset ?? 0) + contentOffset,
contentRef,
open: actualOpen,
};
};

View File

@@ -4,7 +4,6 @@ import React from 'react';
import type { MenuProps } from '../menu.types'; import type { MenuProps } from '../menu.types';
import * as styles from '../styles.css'; import * as styles from '../styles.css';
import { useMenuContentController } from './controller';
import * as desktopStyles from './styles.css'; import * as desktopStyles from './styles.css';
export const DesktopMenu = ({ export const DesktopMenu = ({
@@ -12,36 +11,18 @@ export const DesktopMenu = ({
items, items,
noPortal, noPortal,
portalOptions, portalOptions,
rootOptions: { rootOptions: { defaultOpen, modal, ...rootOptions } = {},
onOpenChange,
defaultOpen,
modal,
open: rootOpen,
...rootOptions
} = {},
contentOptions: { contentOptions: {
className = '', className = '',
style: contentStyle = {}, style: contentStyle = {},
side,
sideOffset,
...otherContentOptions ...otherContentOptions
} = {}, } = {},
}: MenuProps) => { }: MenuProps) => {
const { handleOpenChange, contentSide, contentOffset, contentRef, open } =
useMenuContentController({
open: rootOpen,
defaultOpen,
onOpenChange,
side,
sideOffset: (sideOffset ?? 0) + 5,
});
const ContentWrapper = noPortal ? React.Fragment : DropdownMenu.Portal; const ContentWrapper = noPortal ? React.Fragment : DropdownMenu.Portal;
return ( return (
<DropdownMenu.Root <DropdownMenu.Root
onOpenChange={handleOpenChange}
defaultOpen={defaultOpen} defaultOpen={defaultOpen}
modal={modal ?? false} modal={modal ?? false}
open={open}
{...rootOptions} {...rootOptions}
> >
<DropdownMenu.Trigger <DropdownMenu.Trigger
@@ -62,11 +43,7 @@ export const DesktopMenu = ({
className className
)} )}
align="start" align="start"
ref={contentRef}
side={contentSide}
style={{ zIndex: 'var(--affine-z-index-popover)', ...contentStyle }} style={{ zIndex: 'var(--affine-z-index-popover)', ...contentStyle }}
avoidCollisions={false}
sideOffset={contentOffset}
{...otherContentOptions} {...otherContentOptions}
> >
{items} {items}

View File

@@ -6,22 +6,15 @@ import { useMemo } from 'react';
import type { MenuSubProps } from '../menu.types'; import type { MenuSubProps } from '../menu.types';
import * as styles from '../styles.css'; import * as styles from '../styles.css';
import { useMenuItem } from '../use-menu-item'; import { useMenuItem } from '../use-menu-item';
import { useMenuContentController } from './controller';
export const DesktopMenuSub = ({ export const DesktopMenuSub = ({
children: propsChildren, children: propsChildren,
items, items,
portalOptions, portalOptions,
subOptions: { subOptions: { defaultOpen, ...otherSubOptions } = {},
defaultOpen,
onOpenChange,
open: rootOpen,
...otherSubOptions
} = {},
triggerOptions, triggerOptions,
subContentOptions: { subContentOptions: {
className: subContentClassName = '', className: subContentClassName = '',
sideOffset,
style: contentStyle, style: contentStyle,
...otherSubContentOptions ...otherSubContentOptions
} = {}, } = {},
@@ -32,35 +25,18 @@ export const DesktopMenuSub = ({
suffixIcon: <ArrowRightSmallIcon />, suffixIcon: <ArrowRightSmallIcon />,
}); });
const { handleOpenChange, contentOffset, contentRef, open } =
useMenuContentController({
defaultOpen,
open: rootOpen,
onOpenChange,
side: 'right',
sideOffset: (sideOffset ?? 0) + 12,
});
return ( return (
<DropdownMenu.Sub <DropdownMenu.Sub defaultOpen={defaultOpen} {...otherSubOptions}>
defaultOpen={defaultOpen}
onOpenChange={handleOpenChange}
open={open}
{...otherSubOptions}
>
<DropdownMenu.SubTrigger className={className} {...otherProps}> <DropdownMenu.SubTrigger className={className} {...otherProps}>
{children} {children}
</DropdownMenu.SubTrigger> </DropdownMenu.SubTrigger>
<DropdownMenu.Portal {...portalOptions}> <DropdownMenu.Portal {...portalOptions}>
<DropdownMenu.SubContent <DropdownMenu.SubContent
sideOffset={contentOffset}
ref={contentRef}
className={useMemo( className={useMemo(
() => clsx(styles.menuContent, subContentClassName), () => clsx(styles.menuContent, subContentClassName),
[subContentClassName] [subContentClassName]
)} )}
style={{ zIndex: 'var(--affine-z-index-popover)', ...contentStyle }} style={{ zIndex: 'var(--affine-z-index-popover)', ...contentStyle }}
avoidCollisions={false}
{...otherSubContentOptions} {...otherSubContentOptions}
> >
{items} {items}

View File

@@ -26,7 +26,6 @@ export const workspaceTypeIcon = style({
color: cssVar('iconSecondary'), color: cssVar('iconSecondary'),
}); });
export const scrollbar = style({ export const scrollbar = style({
transform: 'translateX(8px)',
width: '4px', width: '4px',
}); });
export const workspaceCard = style({ export const workspaceCard = style({