diff --git a/services/web/frontend/js/features/ide-react/context/tabs-context.tsx b/services/web/frontend/js/features/ide-react/context/tabs-context.tsx index e5cfcbde2b..aa58f07289 100644 --- a/services/web/frontend/js/features/ide-react/context/tabs-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/tabs-context.tsx @@ -58,6 +58,8 @@ const TabsContext = React.createContext< setContextMenuTarget: React.Dispatch< React.SetStateAction > + headerSlot: HTMLElement | null + setHeaderSlot: React.Dispatch> } | undefined >(undefined) @@ -80,6 +82,8 @@ export const TabsProvider: FC = ({ children }) => { const [contextMenuTarget, setContextMenuTarget] = useState(null) + const [headerSlot, setHeaderSlot] = useState(null) + const tabs = useMemo(() => { if (!tabsEnabled) { return [] @@ -297,6 +301,8 @@ export const TabsProvider: FC = ({ children }) => { contextMenuTarget, setContextMenuTarget, closeToRight, + headerSlot, + setHeaderSlot, }), [ tabs, @@ -308,6 +314,8 @@ export const TabsProvider: FC = ({ children }) => { contextMenuTarget, setContextMenuTarget, closeToRight, + headerSlot, + setHeaderSlot, ] ) diff --git a/services/web/frontend/js/features/review-panel/components/review-panel-header-buffer.tsx b/services/web/frontend/js/features/review-panel/components/review-panel-header-buffer.tsx new file mode 100644 index 0000000000..c7500b4d4a --- /dev/null +++ b/services/web/frontend/js/features/review-panel/components/review-panel-header-buffer.tsx @@ -0,0 +1,7 @@ +import { FC, memo } from 'react' + +const ReviewPanelHeaderBuffer: FC = () => { + return
+} + +export default memo(ReviewPanelHeaderBuffer) diff --git a/services/web/frontend/js/features/review-panel/components/review-panel-tabs-header-portal.tsx b/services/web/frontend/js/features/review-panel/components/review-panel-tabs-header-portal.tsx new file mode 100644 index 0000000000..5e0ea456eb --- /dev/null +++ b/services/web/frontend/js/features/review-panel/components/review-panel-tabs-header-portal.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react' +import { createPortal } from 'react-dom' +import ReviewPanelHeader from './review-panel-header' +import { useTabsContext } from '@/features/ide-react/context/tabs-context' +import { useFeatureFlag } from '@/shared/context/split-test-context' +import useReviewPanelLayout from '../hooks/use-review-panel-layout' + +const ReviewPanelTabsHeaderPortal: FC = () => { + const editorTabsEnabled = useFeatureFlag('editor-tabs') + const { headerSlot } = useTabsContext() + const { showHeader } = useReviewPanelLayout() + + if (!editorTabsEnabled || !headerSlot || !showHeader) { + return null + } + + return createPortal(, headerSlot) +} + +export default ReviewPanelTabsHeaderPortal diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx index 5c98f5c506..828b299880 100644 --- a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx +++ b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx @@ -11,6 +11,7 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac import { FigureModal } from './figure-modal/figure-modal' import { ReviewPanelProviders } from '@/features/review-panel/context/review-panel-providers' import { ReviewPanelRoot } from '@/features/review-panel/components/review-panel-root' +import ReviewPanelTabsHeaderPortal from '@/features/review-panel/components/review-panel-tabs-header-portal' import ReviewTooltipMenu from '@/features/review-panel/components/review-tooltip-menu' import { CodeMirrorStateContext, @@ -104,6 +105,7 @@ function CodeMirrorEditorComponents({ {features.trackChangesVisible && } + {features.trackChangesVisible && } {features.trackChangesVisible && } {features.trackChangesVisible && } diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx index 319b5d3d1d..b9ba81a92f 100644 --- a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx +++ b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx @@ -35,6 +35,7 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useFeatureFlag } from '@/shared/context/split-test-context' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' import { useLayoutContext } from '@/shared/context/layout-context' +import ReviewPanelHeaderBuffer from '@/features/review-panel/components/review-panel-header-buffer' const sourceEditorToolbarComponents = importOverleafModules( 'sourceEditorToolbarComponents' @@ -159,6 +160,7 @@ const Toolbar = memo(function Toolbar() { }, [buildOverflow, insideTable, resizeRef]) const showActions = !state.readOnly && !insideTable + const tabsVisible = useFeatureFlag('editor-tabs') if (focusMode) { return null @@ -166,7 +168,8 @@ const Toolbar = memo(function Toolbar() { return ( <> - {showReviewPanelHeader && } + {showReviewPanelHeader && + (tabsVisible ? : )}
{ moveTab, makeTabPermanent, setContextMenuTarget, + setHeaderSlot, } = useTabsContext() const { openEntity } = useFileTreeOpenContext() const [hovered, setHovered] = useState(false) @@ -87,6 +88,7 @@ export const TabsContainer = () => { return (
+
+ ) @@ -1158,6 +1160,41 @@ describe('File Tabs', function () { }) }) + describe('Review panel header', function () { + function toggleReviewPanel() { + cy.window().then(win => { + win.dispatchEvent(new win.CustomEvent('ui.toggle-review-panel')) + }) + } + + it('does not render the review panel header when the review panel is closed', function () { + cy.then(() => selectDoc(DOC_IDS.main)) + + cy.findByRole('heading', { name: 'Review' }).should('not.exist') + }) + + it('renders the review panel header inside the tabs container when the review panel is open', function () { + cy.then(() => selectDoc(DOC_IDS.main)) + + toggleReviewPanel() + + cy.get('.editor-tabs-container').within(() => { + cy.findByRole('heading', { name: 'Review' }).should('exist') + }) + }) + + it('removes the review panel header when the review panel is closed again', function () { + cy.then(() => selectDoc(DOC_IDS.main)) + + toggleReviewPanel() + cy.findByRole('heading', { name: 'Review' }).should('exist') + + toggleReviewPanel() + + cy.findByRole('heading', { name: 'Review' }).should('not.exist') + }) + }) + describe('SplitTestBadge', function () { it('renders the labs badge icon in the tabs container', function () { cy.window().then(win => {