diff --git a/packages/sanity/src/structure/panes/document/timeline/expandableTimelineItemMenu.tsx b/packages/sanity/src/structure/panes/document/timeline/expandableTimelineItemMenu.tsx index 7cc5a4723cf..45139114fde 100644 --- a/packages/sanity/src/structure/panes/document/timeline/expandableTimelineItemMenu.tsx +++ b/packages/sanity/src/structure/panes/document/timeline/expandableTimelineItemMenu.tsx @@ -1,9 +1,38 @@ -import {Menu} from '@sanity/ui' +import {Menu, usePortal} from '@sanity/ui' import {type MouseEvent, useCallback} from 'react' import {ContextMenuButton, useTranslation} from 'sanity' import {MenuButton, MenuItem} from '../../../../ui-components' import {structureLocaleNamespace} from '../../../i18n' +import {TIMELINE_LIST_WRAPPER_ID} from './timeline' +import {TIMELINE_MENU_PORTAL} from './timelineMenu' + +/** + * This is a hack to force the scrollbar to not appear when the list is expanding, + * if we don't do this the scrollbar will appear for a brief moment when the list is expanding and then disappear + * when the list is fully expanded. + */ +function hideScrollbarOnExpand(isExpanded: boolean) { + // Do nothing if the list is already expanded + if (isExpanded) return + + const listWrapper = document.getElementById(TIMELINE_LIST_WRAPPER_ID) + + if (listWrapper) { + const firstChildren = listWrapper.children[0] as HTMLElement + const hasScrollbar = firstChildren.scrollHeight > firstChildren.clientHeight + if (!hasScrollbar) { + // + const currentStyle = getComputedStyle(firstChildren).overflowY + // Add overflow hidden to the listWrapper to avoid the scrollbar to appear when expanding + firstChildren.style.overflowY = 'hidden' + setTimeout(() => { + // Reset the overflow style after the list is expanded + firstChildren.style.overflowY = currentStyle + }, 0) + } + } +} export function ExpandableTimelineItemMenu({ chunkId, @@ -15,14 +44,17 @@ export function ExpandableTimelineItemMenu({ onExpand: () => void }) { const {t} = useTranslation(structureLocaleNamespace) + const portalContext = usePortal() + const handleExpandClick = useCallback( (e: MouseEvent) => { - // TODO: Avoid the click event to propagate to the parent button and closing the popover e.stopPropagation() + hideScrollbarOnExpand(isExpanded) onExpand() }, - [onExpand], + [onExpand, isExpanded], ) + return ( } + popover={{ + // when used inside the timeline menu we want to keep the element inside the popover, to avoid closing the popover when clicking expand. + portal: portalContext.elements?.[TIMELINE_MENU_PORTAL] ? TIMELINE_MENU_PORTAL : true, + placement: 'bottom-end', + fallbackPlacements: ['left', 'left-end', 'left-start'], + }} /> ) } diff --git a/packages/sanity/src/structure/panes/document/timeline/timeline.tsx b/packages/sanity/src/structure/panes/document/timeline/timeline.tsx index edbc9d0b4a4..b7dc3e8be36 100644 --- a/packages/sanity/src/structure/panes/document/timeline/timeline.tsx +++ b/packages/sanity/src/structure/panes/document/timeline/timeline.tsx @@ -26,13 +26,15 @@ interface TimelineProps { listMaxHeight?: string } +export const TIMELINE_LIST_WRAPPER_ID = 'timeline-list-wrapper' + export const Timeline = ({ chunks, hasMoreChunks, lastChunk: selectedChunk, onLoadMore, onSelect, - listMaxHeight = 'calc(100vh - 198px)', + listMaxHeight = 'calc(100vh - 280px)', }: TimelineProps) => { const [mounted, setMounted] = useState(false) const {t} = useTranslation('studio') @@ -153,7 +155,7 @@ export const Timeline = ({ )} {filteredChunks.length > 0 && ( - + (null) - const popoverRef = useRef(null) + const [popoverRef, setPopoverRef] = useState(null) + const toast = useToast() const chunks = useTimelineSelector(timelineStore, (state) => state.chunks) @@ -64,7 +68,7 @@ export function TimelineMenu({chunk, mode, placement}: TimelineMenuProps) { ) useGlobalKeyDown(handleGlobalKeyDown) - useClickOutsideEvent(open && handleClose, () => [button, popoverRef.current]) + useClickOutsideEvent(open && handleClose, () => [button, popoverRef]) const selectRev = useCallback( (revChunk: Chunk) => { @@ -169,35 +173,38 @@ export function TimelineMenu({chunk, mode, placement}: TimelineMenuProps) { const buttonLabel = mode === 'rev' ? revLabel : sinceLabel return ( - - - + + + ) }