Răsfoiți Sursa

Merge pull request #13500 from overleaf/ii-review-panel-migration-nav

[web] Create review panel navigation

GitOrigin-RevId: abed4171e9fd52de2f137a6cf7456791bc2ca161
ilkin-overleaf 3 ani în urmă
părinte
comite
423f2604db

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -175,6 +175,7 @@
   "created": "",
   "created_at": "",
   "creating": "",
+  "current_file": "",
   "current_password": "",
   "currently_seeing_only_24_hrs_history": "",
   "currently_subscribed_to_plan": "",
@@ -655,6 +656,7 @@
   "overleaf": "",
   "overleaf_history_system": "",
   "overleaf_labs": "",
+  "overview": "",
   "overwrite": "",
   "owned_by_x": "",
   "owner": "",

+ 36 - 28
services/web/frontend/js/features/source-editor/components/review-panel/current-file-container.tsx

@@ -3,34 +3,42 @@ import Container from './container'
 function CurrentFileContainer() {
   return (
     <Container>
-      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
-      tempor incididunt ut labore et dolore magna aliqua. Et malesuada fames ac
-      turpis egestas integer eget aliquet nibh. Et leo duis ut diam quam nulla
-      porttitor massa id. Risus quis varius quam quisque id diam vel quam
-      elementum. Nibh venenatis cras sed felis. Sit amet commodo nulla facilisi
-      nullam vehicula ipsum a arcu. Dui ut ornare lectus sit amet est placerat
-      in. Aliquam ultrices sagittis orci a. Leo a diam sollicitudin tempor id eu
-      nisl nunc mi. Quis ipsum suspendisse ultrices gravida dictum fusce. Ut
-      etiam sit amet nisl purus in mollis nunc sed. Rhoncus est pellentesque
-      elit ullamcorper dignissim cras. Faucibus turpis in eu mi bibendum. Proin
-      libero nunc consequat interdum. Ac placerat vestibulum lectus mauris
-      ultrices eros in cursus turpis. Ac felis donec et odio. Nullam ac tortor
-      vitae purus faucibus. Consectetur lorem donec massa sapien faucibus et
-      molestie. Praesent elementum facilisis leo vel fringilla est ullamcorper
-      eget nulla. Adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus
-      urna. Cursus metus aliquam eleifend mi in nulla posuere sollicitudin
-      aliquam. Eget nullam non nisi est sit amet facilisis magna. Donec
-      adipiscing tristique risus nec feugiat in fermentum posuere. Gravida
-      rutrum quisque non tellus orci ac auctor augue. Euismod in pellentesque
-      massa placerat duis ultricies lacus. Pellentesque diam volutpat commodo
-      sed egestas. Tempus iaculis urna id volutpat lacus laoreet. Lorem ipsum
-      dolor sit amet consectetur. Tincidunt id aliquet risus feugiat in ante
-      metus. Risus ultricies tristique nulla aliquet enim tortor at auctor urna.
-      Purus in mollis nunc sed. In ante metus dictum at. Magna eget est lorem
-      ipsum dolor sit. Fusce id velit ut tortor pretium viverra. Augue neque
-      gravida in fermentum et sollicitudin ac. Et malesuada fames ac turpis.
-      Felis bibendum ut tristique et egestas quis ipsum suspendisse ultrices.
-      Varius vel pharetra vel turpis nunc eget.
+      <div
+        id="review-panel-current-file"
+        role="tabpanel"
+        tabIndex={0}
+        aria-labelledby="review-panel-tab-current-file"
+      >
+        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+        tempor incididunt ut labore et dolore magna aliqua. Et malesuada fames
+        ac turpis egestas integer eget aliquet nibh. Et leo duis ut diam quam
+        nulla porttitor massa id. Risus quis varius quam quisque id diam vel
+        quam elementum. Nibh venenatis cras sed felis. Sit amet commodo nulla
+        facilisi nullam vehicula ipsum a arcu. Dui ut ornare lectus sit amet est
+        placerat in. Aliquam ultrices sagittis orci a. Leo a diam sollicitudin
+        tempor id eu nisl nunc mi. Quis ipsum suspendisse ultrices gravida
+        dictum fusce. Ut etiam sit amet nisl purus in mollis nunc sed. Rhoncus
+        est pellentesque elit ullamcorper dignissim cras. Faucibus turpis in eu
+        mi bibendum. Proin libero nunc consequat interdum. Ac placerat
+        vestibulum lectus mauris ultrices eros in cursus turpis. Ac felis donec
+        et odio. Nullam ac tortor vitae purus faucibus. Consectetur lorem donec
+        massa sapien faucibus et molestie. Praesent elementum facilisis leo vel
+        fringilla est ullamcorper eget nulla. Adipiscing vitae proin sagittis
+        nisl rhoncus mattis rhoncus urna. Cursus metus aliquam eleifend mi in
+        nulla posuere sollicitudin aliquam. Eget nullam non nisi est sit amet
+        facilisis magna. Donec adipiscing tristique risus nec feugiat in
+        fermentum posuere. Gravida rutrum quisque non tellus orci ac auctor
+        augue. Euismod in pellentesque massa placerat duis ultricies lacus.
+        Pellentesque diam volutpat commodo sed egestas. Tempus iaculis urna id
+        volutpat lacus laoreet. Lorem ipsum dolor sit amet consectetur.
+        Tincidunt id aliquet risus feugiat in ante metus. Risus ultricies
+        tristique nulla aliquet enim tortor at auctor urna. Purus in mollis nunc
+        sed. In ante metus dictum at. Magna eget est lorem ipsum dolor sit.
+        Fusce id velit ut tortor pretium viverra. Augue neque gravida in
+        fermentum et sollicitudin ac. Et malesuada fames ac turpis. Felis
+        bibendum ut tristique et egestas quis ipsum suspendisse ultrices. Varius
+        vel pharetra vel turpis nunc eget.
+      </div>
     </Container>
   )
 }

+ 44 - 2
services/web/frontend/js/features/source-editor/components/review-panel/nav.tsx

@@ -1,7 +1,49 @@
+import { useTranslation } from 'react-i18next'
+import classnames from 'classnames'
+import Icon from '../../../../shared/components/icon'
+import {
+  useReviewPanelValueContext,
+  useReviewPanelUpdaterFnsContext,
+} from '../../context/review-panel/review-panel-context'
+import { isCurrentFileView, isOverviewView } from '../../utils/sub-view'
+
 function Nav() {
+  const { t } = useTranslation()
+  const { subView } = useReviewPanelValueContext()
+  const { handleSetSubview } = useReviewPanelUpdaterFnsContext()
+
   return (
-    <div className="rp-nav" style={{ background: '#8eaeb6', padding: '5px' }}>
-      Nav
+    <div className="rp-nav" role="tablist">
+      <button
+        type="button"
+        id="review-panel-tab-current-file"
+        role="tab"
+        aria-selected={isCurrentFileView(subView)}
+        aria-controls="review-panel-current-file"
+        tabIndex={isCurrentFileView(subView) ? 0 : -1}
+        className={classnames('rp-nav-item', {
+          'rp-nav-item-active': isCurrentFileView(subView),
+        })}
+        onClick={() => handleSetSubview('cur_file')}
+      >
+        <Icon type="file-text-o" />
+        <span className="rp-nav-label">{t('current_file')}</span>
+      </button>
+      <button
+        type="button"
+        id="review-panel-tab-overview"
+        role="tab"
+        aria-selected={isOverviewView(subView)}
+        aria-controls="review-panel-overview"
+        tabIndex={isOverviewView(subView) ? 0 : -1}
+        className={classnames('rp-nav-item', {
+          'rp-nav-item-active': isOverviewView(subView),
+        })}
+        onClick={() => handleSetSubview('overview')}
+      >
+        <Icon type="list" />
+        <span className="rp-nav-label">{t('overview')}</span>
+      </button>
     </div>
   )
 }

+ 23 - 1
services/web/frontend/js/features/source-editor/components/review-panel/overview-container.tsx

@@ -3,7 +3,29 @@ import Container from './container'
 function OverviewContainer() {
   return (
     <Container>
-      <em>ReviewPanelOverviewContainer</em>
+      <div
+        id="review-panel-overview"
+        role="tabpanel"
+        tabIndex={0}
+        aria-labelledby="review-panel-tab-overview"
+      >
+        Faucibus turpis in eu mi bibendum. Proin libero nunc consequat interdum.
+        Ac placerat vestibulum lectus mauris ultrices eros in cursus turpis. Ac
+        felis donec et odio. Nullam ac tortor vitae purus faucibus. Consectetur
+        lorem donec massa sapien faucibus et molestie. Praesent elementum
+        facilisis leo vel fringilla est ullamcorper eget nulla. Adipiscing vitae
+        proin sagittis nisl rhoncus mattis rhoncus urna. Cursus metus aliquam
+        eleifend mi in nulla posuere sollicitudin aliquam. Eget nullam non nisi
+        est sit amet facilisis magna. Donec adipiscing tristique risus nec
+        feugiat in fermentum posuere. Gravida rutrum quisque non tellus orci ac
+        auctor augue. Euismod in pellentesque massa placerat duis ultricies
+        lacus. Pellentesque diam volutpat commodo sed egestas. Tempus iaculis
+        urna id volutpat lacus laoreet. Lorem ipsum dolor sit amet consectetur.
+        Tincidunt id aliquet risus feugiat in ante metus. Risus ultricies
+        tristique nulla aliquet enim tortor at auctor urna. Purus in mollis nunc
+        sed. In ante metus dictum at. Magna eget est lorem ipsum dolor sit.
+        Fusce id velit ut tortor pretium viverra.
+      </div>
     </Container>
   )
 }

+ 2 - 1
services/web/frontend/js/features/source-editor/components/review-panel/review-panel.tsx

@@ -6,6 +6,7 @@ import {
 } from '../../context/review-panel/review-panel-context'
 import CurrentFileContainer from './current-file-container'
 import OverviewContainer from './overview-container'
+import { isCurrentFileView } from '../../utils/sub-view'
 
 type ReviewPanelViewProps = {
   parentDomNode: Element
@@ -16,7 +17,7 @@ function ReviewPanelView({ parentDomNode }: ReviewPanelViewProps) {
 
   return ReactDOM.createPortal(
     <>
-      {subView === 'cur_file' ? (
+      {isCurrentFileView(subView) ? (
         <CurrentFileContainer />
       ) : (
         <OverviewContainer />

+ 13 - 3
services/web/frontend/js/features/source-editor/context/review-panel/hooks/use-angular-review-panel-state.ts

@@ -1,7 +1,9 @@
-import { useMemo } from 'react'
+import { useMemo, useCallback } from 'react'
 import useScopeValue from '../../../../../shared/hooks/use-scope-value'
 import { ReviewPanelState } from '../types/review-panel-state'
+import { sendMB } from '../../../../../infrastructure/event-tracking'
 import * as ReviewPanel from '../types/review-panel-state'
+import { SubView } from '../../../../../../../types/review-panel'
 
 function useAngularReviewPanelState(): ReviewPanelState {
   const [subView, setSubView] = useScopeValue<ReviewPanel.Value<'subView'>>(
@@ -50,6 +52,14 @@ function useAngularReviewPanelState(): ReviewPanelState {
   const [toggleReviewPanel] =
     useScopeValue<ReviewPanel.Value<'toggleReviewPanel'>>('toggleReviewPanel')
 
+  const handleSetSubview = useCallback(
+    (subView: SubView) => {
+      setSubView(subView)
+      sendMB('rp-subview-change', { subView })
+    },
+    [setSubView]
+  )
+
   const values = useMemo<ReviewPanelState['values']>(
     () => ({
       collapsed,
@@ -87,11 +97,11 @@ function useAngularReviewPanelState(): ReviewPanelState {
 
   const updaterFns = useMemo<ReviewPanelState['updaterFns']>(
     () => ({
-      setSubView,
+      handleSetSubview,
       setCollapsed,
       setShouldCollapse,
     }),
-    [setSubView, setCollapsed, setShouldCollapse]
+    [handleSetSubview, setCollapsed, setShouldCollapse]
   )
 
   return { values, updaterFns }

+ 1 - 3
services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts

@@ -27,12 +27,10 @@ export interface ReviewPanelState {
     toggleReviewPanel: () => void
   }
   updaterFns: {
+    handleSetSubview: (subView: SubView) => void
     setCollapsed: React.Dispatch<
       React.SetStateAction<ReviewPanelState['values']['collapsed']>
     >
-    setSubView: React.Dispatch<
-      React.SetStateAction<ReviewPanelState['values']['subView']>
-    >
     setShouldCollapse: React.Dispatch<
       React.SetStateAction<ReviewPanelState['values']['shouldCollapse']>
     >

+ 4 - 0
services/web/frontend/js/features/source-editor/utils/sub-view.ts

@@ -0,0 +1,4 @@
+import { SubView } from '../../../../../types/review-panel'
+
+export const isCurrentFileView = (view: SubView) => view === 'cur_file'
+export const isOverviewView = (view: SubView) => view === 'overview'

+ 21 - 20
services/web/frontend/stylesheets/app/editor/review-panel.less

@@ -56,7 +56,7 @@
 }
 
 .rp-collapse-arrow() {
-  display: inline-block;
+  display: inline-flex;
   transform: rotateZ(0deg);
   transition: transform 0.15s ease;
   &-on {
@@ -1214,14 +1214,6 @@ button when (@is-overleaf-light = true) {
   box-sizing: content-box;
   flex-direction: column;
 
-  .review-panel-toolbar {
-    & .review-panel-toolbar-collapse-button {
-      border: none;
-      background: none;
-      padding: 0;
-    }
-  }
-
   .review-panel-toggler {
     display: flex;
     flex-direction: column;
@@ -1235,6 +1227,22 @@ button when (@is-overleaf-light = true) {
     }
   }
 
+  .review-panel-toolbar {
+    position: sticky;
+    top: 0;
+
+    .review-panel-toolbar-collapse-button {
+      border: none;
+      background: none;
+      padding: 0;
+    }
+  }
+
+  .rp-nav {
+    position: sticky;
+    bottom: 0;
+  }
+
   .rp-state-current-file & {
     .review-panel-tools {
       display: flex;
@@ -1246,19 +1254,9 @@ button when (@is-overleaf-light = true) {
       left: 0;
       right: 0;
     }
-
-    .review-panel-toolbar {
-      position: sticky;
-      top: 0;
-    }
-
-    .rp-nav {
-      position: sticky;
-      bottom: 0;
-    }
   }
 
-  .rp-state-overview {
+  .rp-state-overview & {
     position: sticky;
     top: 0;
     display: flex;
@@ -1266,6 +1264,9 @@ button when (@is-overleaf-light = true) {
   }
 
   .rp-nav-item {
+    border-right: 0;
+    border-bottom: 0;
+    border-left: 0;
     background: none;
   }
 

+ 35 - 0
services/web/test/frontend/features/review-panel/review-panel.spec.tsx

@@ -131,4 +131,39 @@ describe('<ReviewPanel />', function () {
       })
     })
   })
+
+  describe('navigation', function () {
+    it('renders navigation', function () {
+      cy.get('@review-panel').within(() => {
+        cy.findByRole('tab', { name: /current file/i })
+        cy.findByRole('tab', { name: /overview/i })
+      })
+    })
+
+    it('selects the active tab', function () {
+      cy.get('@review-panel').within(() => {
+        cy.findByRole('tab', { name: /current file/i }).should(
+          'have.attr',
+          'aria-selected',
+          'true'
+        )
+        cy.findByRole('tab', { name: /overview/i }).should(
+          'have.attr',
+          'aria-selected',
+          'false'
+        )
+        cy.findByRole('tab', { name: /overview/i }).click()
+        cy.findByRole('tab', { name: /current file/i }).should(
+          'have.attr',
+          'aria-selected',
+          'false'
+        )
+        cy.findByRole('tab', { name: /overview/i }).should(
+          'have.attr',
+          'aria-selected',
+          'true'
+        )
+      })
+    })
+  })
 })