Преглед изворни кода

Merge pull request #34311 from overleaf/mj-review-panel-with-tabs

[web] Move review panel header into tabs row when open

GitOrigin-RevId: e4594040f7a8ca67778fdd3e31cab909b3120adb
Mathias Jakobsen пре 2 месеци
родитељ
комит
1a7b5e66f5

+ 8 - 0
services/web/frontend/js/features/ide-react/context/tabs-context.tsx

@@ -58,6 +58,8 @@ const TabsContext = React.createContext<
       setContextMenuTarget: React.Dispatch<
         React.SetStateAction<TabsContextMenuTarget | null>
       >
+      headerSlot: HTMLElement | null
+      setHeaderSlot: React.Dispatch<React.SetStateAction<HTMLElement | null>>
     }
   | undefined
 >(undefined)
@@ -80,6 +82,8 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
   const [contextMenuTarget, setContextMenuTarget] =
     useState<TabsContextMenuTarget | null>(null)
 
+  const [headerSlot, setHeaderSlot] = useState<HTMLElement | null>(null)
+
   const tabs = useMemo(() => {
     if (!tabsEnabled) {
       return []
@@ -297,6 +301,8 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
       contextMenuTarget,
       setContextMenuTarget,
       closeToRight,
+      headerSlot,
+      setHeaderSlot,
     }),
     [
       tabs,
@@ -308,6 +314,8 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
       contextMenuTarget,
       setContextMenuTarget,
       closeToRight,
+      headerSlot,
+      setHeaderSlot,
     ]
   )
 

+ 7 - 0
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 <div className="review-panel-header" aria-hidden />
+}
+
+export default memo(ReviewPanelHeaderBuffer)

+ 20 - 0
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(<ReviewPanelHeader />, headerSlot)
+}
+
+export default ReviewPanelTabsHeaderPortal

+ 2 - 0
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({
       <MathPreviewTooltip />
       <EditorContextMenu />
       {features.trackChangesVisible && <ReviewTooltipMenu />}
+      {features.trackChangesVisible && <ReviewPanelTabsHeaderPortal />}
       {features.trackChangesVisible && <ReviewPanelRoot />}
       {features.trackChangesVisible && <UpgradeTrackChangesModal />}
 

+ 4 - 1
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 && <ReviewPanelHeader />}
+      {showReviewPanelHeader &&
+        (tabsVisible ? <ReviewPanelHeaderBuffer /> : <ReviewPanelHeader />)}
       <div
         id="ol-cm-toolbar-wrapper"
         className={classNames('ol-cm-toolbar-wrapper', {

+ 2 - 0
services/web/frontend/js/features/source-editor/components/tabs/tabs-container.tsx

@@ -19,6 +19,7 @@ export const TabsContainer = () => {
     moveTab,
     makeTabPermanent,
     setContextMenuTarget,
+    setHeaderSlot,
   } = useTabsContext()
   const { openEntity } = useFileTreeOpenContext()
   const [hovered, setHovered] = useState<boolean>(false)
@@ -87,6 +88,7 @@ export const TabsContainer = () => {
 
   return (
     <div className="editor-tabs-container">
+      <div className="review-panel-header-slot" ref={setHeaderSlot} />
       <div
         className={classNames('editor-tabs-row', {
           'editor-tabs-row-hovered': hovered,

+ 8 - 1
services/web/frontend/stylesheets/pages/editor/tabs.scss

@@ -1,6 +1,7 @@
 :root {
   --editor-tab-max-path-length: 250px;
   --editor-tab-divider-color: var(--neutral-70);
+  --editor-tab-row-height: var(--review-panel-header-height, 36px);
 }
 
 @include theme('light') {
@@ -11,11 +12,15 @@
   display: flex;
   flex-direction: row;
   align-items: center;
-  gap: var(--spacing-02);
   flex: 0 0 auto;
+  min-height: var(--editor-tab-row-height);
   background: var(--bg-secondary-themed);
   justify-content: space-between;
 
+  .review-panel-header-slot {
+    display: contents;
+  }
+
   .editor-tabs-labs-icon {
     flex: 0 0 auto;
     padding: 0 var(--spacing-02);
@@ -29,6 +34,7 @@
 .editor-tabs-row {
   display: flex;
   flex-direction: row;
+  height: var(--editor-tab-row-height);
   list-style: none;
   padding: 0;
   margin: 0;
@@ -47,6 +53,7 @@
   --tab-bg-color: var(--bg-secondary-themed);
   --tab-text-color: var(--content-secondary-themed);
 
+  display: flex;
   border-right: 1px solid var(--editor-tab-divider-color);
   border-left: 1px solid transparent;
   padding: var(--spacing-01) var(--spacing-02) 0 var(--spacing-02);

+ 37 - 0
services/web/test/frontend/features/source-editor/components/tabs.spec.tsx

@@ -18,6 +18,7 @@ import {
 import { EditorView } from '@codemirror/view'
 import { EditorState, Transaction } from '@codemirror/state'
 import { tabsListener } from '@/features/source-editor/extensions/tabs-listener'
+import ReviewPanelTabsHeaderPortal from '@/features/review-panel/components/review-panel-tabs-header-portal'
 
 const DOC_IDS = {
   main: 'doc-main-id',
@@ -247,6 +248,7 @@ describe('File Tabs', function () {
       >
         <FileSelectionDriver />
         <TabsContainer />
+        <ReviewPanelTabsHeaderPortal />
         <RemoteChangeButton />
       </EditorProviders>
     )
@@ -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 => {