فهرست منبع

Merge pull request #21115 from overleaf/ii-bs5-review-panel

[web] BS5 review panel new

GitOrigin-RevId: c65d17d0053858bd74984ba746a620b89d900606
ilkin-overleaf 1 سال پیش
والد
کامیت
8bc374c916
29فایلهای تغییر یافته به همراه1020 افزوده شده و 158 حذف شده
  1. 3 0
      services/web/.storybook/utils/with-bootstrap-switcher.tsx
  2. 1 1
      services/web/frontend/js/features/project-list/components/table/project-list-table.tsx
  3. 9 9
      services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx
  4. 11 11
      services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx
  5. 67 16
      services/web/frontend/js/features/review-panel-new/components/review-panel-comment-options.tsx
  6. 19 14
      services/web/frontend/js/features/review-panel-new/components/review-panel-delete-comment-modal.tsx
  7. 7 7
      services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx
  8. 3 5
      services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx
  9. 6 6
      services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx
  10. 10 5
      services/web/frontend/js/features/review-panel-new/components/review-panel-more-comments-button.tsx
  11. 4 5
      services/web/frontend/js/features/review-panel-new/components/review-panel-overview-file.tsx
  12. 9 10
      services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-thread.tsx
  13. 17 11
      services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx
  14. 2 6
      services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-menu.tsx
  15. 8 1
      services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu-button.tsx
  16. 1 1
      services/web/frontend/js/features/review-panel-new/components/review-panel.tsx
  17. 9 14
      services/web/frontend/js/features/source-editor/components/review-panel/toolbar/track-changes-toggle.tsx
  18. 5 3
      services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
  19. 1 1
      services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx
  20. 54 0
      services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx
  21. 31 0
      services/web/frontend/stories/input-switch.stories.tsx
  22. 0 14
      services/web/frontend/stories/switch.stories.tsx
  23. 6 17
      services/web/frontend/stylesheets/app/editor/review-panel-new.less
  24. 10 0
      services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss
  25. 26 0
      services/web/frontend/stylesheets/bootstrap-5/components/form.scss
  26. 1 0
      services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
  27. 638 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss
  28. 62 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel.scss
  29. 0 1
      services/web/frontend/stylesheets/components/input-switch.less

+ 3 - 0
services/web/.storybook/utils/with-bootstrap-switcher.tsx

@@ -11,4 +11,7 @@ export const bsVersionDecorator: Meta = {
       options: ['3', '5'],
     },
   },
+  args: {
+    [bootstrapVersionArg]: '3',
+  },
 }

+ 1 - 1
services/web/frontend/js/features/project-list/components/table/project-list-table.tsx

@@ -47,7 +47,7 @@ function ProjectListTable() {
     selectOrUnselectAllProjects,
   } = useProjectListContext()
   const { handleSort } = useSort()
-  const checkAllRef = useRef<HTMLInputElement>()
+  const checkAllRef = useRef<HTMLInputElement>(null)
 
   const handleAllProjectsCheckboxChange = useCallback(
     (event: React.ChangeEvent<HTMLInputElement>) => {

+ 9 - 9
services/web/frontend/js/features/review-panel-new/components/review-panel-add-comment.tsx

@@ -9,12 +9,12 @@ import { useThreadsActionsContext } from '../context/threads-context'
 import { removeNewCommentRangeEffect } from '@/features/source-editor/extensions/add-comment'
 import useSubmittableTextInput from '../hooks/use-submittable-text-input'
 import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
-import { Button } from 'react-bootstrap'
 import { ReviewPanelEntry } from './review-panel-entry'
 import { ThreadId } from '../../../../../types/review-panel/review-panel'
 import { Decoration } from '@codemirror/view'
 import { useModalsContext } from '@/features/ide-react/context/modals-context'
 import { debugConsole } from '@/utils/debugging'
+import OLButton from '@/features/ui/components/ol/ol-button'
 
 export const ReviewPanelAddComment: FC<{
   docId: string
@@ -148,23 +148,23 @@ export const ReviewPanelAddComment: FC<{
           disabled={submitting}
         />
         <div className="review-panel-add-comment-buttons">
-          <Button
-            bsSize="sm"
-            bsStyle={null}
+          <OLButton
+            variant="ghost"
+            size="sm"
             className="review-panel-add-comment-cancel-button"
             disabled={submitting}
             onClick={handleClose}
           >
             {t('cancel')}
-          </Button>
-          <Button
+          </OLButton>
+          <OLButton
             type="submit"
-            bsSize="sm"
-            className="btn-primary"
+            variant="primary"
+            size="sm"
             disabled={content === '' || submitting}
           >
             {t('comment')}
-          </Button>
+          </OLButton>
         </div>
       </form>
     </ReviewPanelEntry>

+ 11 - 11
services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx

@@ -8,8 +8,7 @@ import {
 import { useTranslation } from 'react-i18next'
 import classnames from 'classnames'
 import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
-import { Button } from 'react-bootstrap'
-import Tooltip from '@/shared/components/tooltip'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import MaterialIcon from '@/shared/components/material-icon'
 import { formatTimeBasedOnYear } from '@/features/utils/format-date'
 import { useChangesUsersContext } from '../context/changes-users-context'
@@ -109,29 +108,30 @@ export const ReviewPanelChange = memo<{
             </div>
             {editable && permissions.write && (
               <div className="review-panel-entry-actions">
-                <Tooltip
+                <OLTooltip
                   id="accept-change"
                   overlayProps={{ placement: 'bottom' }}
                   description={t('accept_change')}
                   tooltipProps={{ className: 'review-panel-tooltip' }}
                 >
-                  <Button onClick={acceptHandler} bsStyle={null}>
+                  <button type="button" className="btn" onClick={acceptHandler}>
                     <MaterialIcon
                       type="check"
                       className="review-panel-entry-actions-icon"
                       accessibilityLabel={t('accept_change')}
                     />
-                  </Button>
-                </Tooltip>
+                  </button>
+                </OLTooltip>
 
-                <Tooltip
+                <OLTooltip
                   id="reject-change"
                   description={t('reject_change')}
                   overlayProps={{ placement: 'bottom' }}
                   tooltipProps={{ className: 'review-panel-tooltip' }}
                 >
-                  <Button
-                    bsStyle={null}
+                  <button
+                    type="button"
+                    className="btn"
                     onClick={() =>
                       aggregate
                         ? rejectChanges(change.id, aggregate.id)
@@ -143,8 +143,8 @@ export const ReviewPanelChange = memo<{
                       accessibilityLabel={t('reject_change')}
                       type="close"
                     />
-                  </Button>
-                </Tooltip>
+                  </button>
+                </OLTooltip>
               </div>
             )}
           </div>

+ 67 - 16
services/web/frontend/js/features/review-panel-new/components/review-panel-comment-options.tsx

@@ -1,29 +1,80 @@
 import ControlledDropdown from '@/shared/components/controlled-dropdown'
 import MaterialIcon from '@/shared/components/material-icon'
-import { FC, memo } from 'react'
-import { Dropdown, MenuItem } from 'react-bootstrap'
+import { FC, memo, forwardRef } from 'react'
+import {
+  Dropdown as BS3Dropdown,
+  MenuItem as BS3MenuItem,
+} from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import {
+  Dropdown,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import classnames from 'classnames'
+
+const ReviewPanelCommentDropdownToggleButton = forwardRef<
+  HTMLButtonElement,
+  React.ButtonHTMLAttributes<HTMLButtonElement>
+>((props, ref) => (
+  <button {...props} ref={ref} className={classnames(props.className, 'btn')} />
+))
+ReviewPanelCommentDropdownToggleButton.displayName =
+  'ReviewPanelCommentDropdownToggleButton'
 
 const ReviewPanelCommentOptions: FC<{
   onEdit: () => void
   onDelete: () => void
-}> = ({ onEdit, onDelete }) => {
+  id: string
+}> = ({ onEdit, onDelete, id }) => {
   const { t } = useTranslation()
 
   return (
-    <ControlledDropdown id="review-panel-comment-options" pullRight>
-      <Dropdown.Toggle noCaret bsSize="small" bsStyle={null}>
-        <MaterialIcon
-          type="more_vert"
-          className="review-panel-entry-actions-icon"
-          accessibilityLabel={t('more_options')}
-        />
-      </Dropdown.Toggle>
-      <Dropdown.Menu>
-        <MenuItem onClick={onEdit}>{t('edit')}</MenuItem>
-        <MenuItem onClick={onDelete}>{t('delete')}</MenuItem>
-      </Dropdown.Menu>
-    </ControlledDropdown>
+    <BootstrapVersionSwitcher
+      bs3={
+        <ControlledDropdown id={`review-panel-comment-options-${id}`} pullRight>
+          <BS3Dropdown.Toggle noCaret bsSize="small" bsStyle={null}>
+            <MaterialIcon
+              type="more_vert"
+              className="review-panel-entry-actions-icon"
+              accessibilityLabel={t('more_options')}
+            />
+          </BS3Dropdown.Toggle>
+          <BS3Dropdown.Menu>
+            <BS3MenuItem onClick={onEdit}>{t('edit')}</BS3MenuItem>
+            <BS3MenuItem onClick={onDelete}>{t('delete')}</BS3MenuItem>
+          </BS3Dropdown.Menu>
+        </ControlledDropdown>
+      }
+      bs5={
+        <Dropdown align="end">
+          <DropdownToggle
+            as={ReviewPanelCommentDropdownToggleButton}
+            id={`review-panel-comment-options-btn-${id}`}
+          >
+            <MaterialIcon
+              type="more_vert"
+              className="review-panel-entry-actions-icon"
+              accessibilityLabel={t('more_options')}
+            />
+          </DropdownToggle>
+          <DropdownMenu flip={false}>
+            <li role="none">
+              <DropdownItem as="button" onClick={onEdit}>
+                {t('edit')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem as="button" onClick={onDelete}>
+                {t('delete')}
+              </DropdownItem>
+            </li>
+          </DropdownMenu>
+        </Dropdown>
+      }
+    />
   )
 }
 

+ 19 - 14
services/web/frontend/js/features/review-panel-new/components/review-panel-delete-comment-modal.tsx

@@ -1,7 +1,12 @@
-import AccessibleModal from '@/shared/components/accessible-modal'
 import { FC, memo } from 'react'
-import { Button, Modal } from 'react-bootstrap'
+import OLButton from '@/features/ui/components/ol/ol-button'
 import { useTranslation } from 'react-i18next'
+import OLModal, {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/ol/ol-modal'
 
 const ReviewPanelDeleteCommentModal: FC<{
   onHide: () => void
@@ -12,20 +17,20 @@ const ReviewPanelDeleteCommentModal: FC<{
   const { t } = useTranslation()
 
   return (
-    <AccessibleModal show onHide={onHide}>
-      <Modal.Header>
-        <Modal.Title>{title}</Modal.Title>
-      </Modal.Header>
-      <Modal.Body>{message}</Modal.Body>
-      <Modal.Footer>
-        <Button bsStyle={null} className="btn-secondary" onClick={onHide}>
+    <OLModal show onHide={onHide}>
+      <OLModalHeader>
+        <OLModalTitle>{title}</OLModalTitle>
+      </OLModalHeader>
+      <OLModalBody>{message}</OLModalBody>
+      <OLModalFooter>
+        <OLButton variant="secondary" onClick={onHide}>
           {t('cancel')}
-        </Button>
-        <Button bsStyle="danger" onClick={onDelete}>
+        </OLButton>
+        <OLButton variant="danger" onClick={onDelete}>
           {t('delete')}
-        </Button>
-      </Modal.Footer>
-    </AccessibleModal>
+        </OLButton>
+      </OLModalFooter>
+    </OLModal>
   )
 }
 

+ 7 - 7
services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx

@@ -1,6 +1,6 @@
 import { FC, useCallback, useEffect, useRef, useState } from 'react'
 import classNames from 'classnames'
-import { Button } from 'react-bootstrap'
+import OLButton from '@/features/ui/components/ol/ol-button'
 import { useTranslation } from 'react-i18next'
 
 export const ExpandableContent: FC<{ className?: string }> = ({
@@ -49,22 +49,22 @@ export const ExpandableContent: FC<{ className?: string }> = ({
         {children}
       </div>
       {isExpanded ? (
-        <Button
-          bsStyle="link"
+        <OLButton
+          variant="link"
           className="btn-inline-link"
           onClick={handleShowLess}
         >
           {t('show_less')}
-        </Button>
+        </OLButton>
       ) : (
         isOverflowing && (
-          <Button
-            bsStyle="link"
+          <OLButton
+            variant="link"
             className="btn-inline-link"
             onClick={handleShowMore}
           >
             {t('show_more')}
-          </Button>
+          </OLButton>
         )
       )}
     </div>

+ 3 - 5
services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx

@@ -2,7 +2,6 @@ import { FC, memo, useState } from 'react'
 import { ReviewPanelResolvedThreadsButton } from './review-panel-resolved-threads-button'
 import { ReviewPanelTrackChangesMenu } from './review-panel-track-changes-menu'
 import ReviewPanelTrackChangesMenuButton from './review-panel-track-changes-menu-button'
-import { Button } from 'react-bootstrap'
 import MaterialIcon from '@/shared/components/material-icon'
 import { useLayoutContext } from '@/shared/context/layout-context'
 import SplitTestBadge from '@/shared/components/split-test-badge'
@@ -26,14 +25,13 @@ const ReviewPanelHeader: FC = () => {
             />
           </span>
         </div>
-        <Button
-          bsStyle={null}
-          className="review-panel-close-button"
+        <button
           type="button"
+          className="btn review-panel-close-button"
           onClick={() => setReviewPanelOpen(false)}
         >
           <MaterialIcon type="close" />
-        </Button>
+        </button>
       </div>
       <div className="review-panel-tools">
         <ReviewPanelResolvedThreadsButton />

+ 6 - 6
services/web/frontend/js/features/review-panel-new/components/review-panel-message.tsx

@@ -5,8 +5,7 @@ import {
 } from '../../../../../types/review-panel/review-panel'
 import { useTranslation } from 'react-i18next'
 import { formatTimeBasedOnYear } from '@/features/utils/format-date'
-import Tooltip from '@/shared/components/tooltip'
-import { Button } from 'react-bootstrap'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import MaterialIcon from '@/shared/components/material-icon'
 import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
 import { buildName } from '../utils/build-name'
@@ -90,26 +89,27 @@ export const ReviewPanelMessage: FC<{
 
         <div className="review-panel-entry-actions">
           {!isReply && !isThreadResolved && (
-            <Tooltip
+            <OLTooltip
               id="resolve-thread"
               overlayProps={{ placement: 'bottom' }}
               description={t('resolve_comment')}
               tooltipProps={{ className: 'review-panel-tooltip' }}
             >
-              <Button onClick={onResolve} bsStyle={null}>
+              <button type="button" className="btn" onClick={onResolve}>
                 <MaterialIcon
                   type="check"
                   className="review-panel-entry-actions-icon"
                   accessibilityLabel={t('resolve_comment')}
                 />
-              </Button>
-            </Tooltip>
+              </button>
+            </OLTooltip>
           )}
 
           {!isThreadResolved && (
             <ReviewPanelCommentOptions
               onEdit={handleEditOption}
               onDelete={showDeleteModal}
+              id={message.id}
             />
           )}
         </div>

+ 10 - 5
services/web/frontend/js/features/review-panel-new/components/review-panel-more-comments-button.tsx

@@ -1,8 +1,9 @@
 import { FC, memo } from 'react'
-import { Button } from 'react-bootstrap'
 import MaterialIcon from '@/shared/components/material-icon'
 import classNames from 'classnames'
 import { useTranslation } from 'react-i18next'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import { bsVersion } from '@/features/utils/bootstrap-5'
 
 const MoreCommentsButton: FC<{
   onClick: () => void
@@ -17,14 +18,18 @@ const MoreCommentsButton: FC<{
         upwards: direction === 'upward',
       })}
     >
-      <Button
-        bsSize="small"
-        className="btn-secondary review-panel-more-comments-button"
+      <OLButton
+        variant="secondary"
+        size="sm"
+        className={bsVersion({ bs3: 'review-panel-more-comments-button' })}
         onClick={onClick}
+        bs3Props={{
+          bsSize: 'xsmall',
+        }}
       >
         <MaterialIcon type={`arrow_${direction}_alt`} />
         {t('more_comments')}
-      </Button>
+      </OLButton>
     </div>
   )
 }

+ 4 - 5
services/web/frontend/js/features/review-panel-new/components/review-panel-overview-file.tsx

@@ -11,7 +11,6 @@ import {
 } from '../../../../../types/change'
 import { canAggregate } from '../utils/can-aggregate'
 
-import { Button } from 'react-bootstrap'
 import MaterialIcon from '@/shared/components/material-icon'
 import useOverviewFileCollapsed from '../hooks/use-overview-file-collapsed'
 import { useThreadsContext } from '../context/threads-context'
@@ -61,9 +60,9 @@ export const ReviewPanelOverviewFile: FC<{
   return (
     <>
       <div>
-        <Button
-          bsClass="review-panel-overview-file-header"
-          bsStyle={null}
+        <button
+          type="button"
+          className="review-panel-overview-file-header"
           onClick={toggleCollapsed}
         >
           <MaterialIcon
@@ -73,7 +72,7 @@ export const ReviewPanelOverviewFile: FC<{
           <div className="review-panel-overview-file-entry-count">
             {numEntries}
           </div>
-        </Button>
+        </button>
 
         {!collapsed && (
           <div className="review-panel-overview-file-entries">

+ 9 - 10
services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-thread.tsx

@@ -2,12 +2,11 @@ import React, { FC, useCallback, useState } from 'react'
 import { useTranslation, Trans } from 'react-i18next'
 import { ThreadId } from '../../../../../types/review-panel/review-panel'
 import { useThreadsActionsContext } from '../context/threads-context'
-import { Button } from 'react-bootstrap'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import MaterialIcon from '@/shared/components/material-icon'
 import { ExpandableContent } from './review-panel-expandable-content'
 import { ReviewPanelCommentContent } from './review-panel-comment-content'
 import { Change, CommentOperation } from '../../../../../types/change'
-import Tooltip from '@/shared/components/tooltip'
 import classNames from 'classnames'
 import { debugConsole } from '@/utils/debugging'
 import { useModalsContext } from '@/features/ide-react/context/modals-context'
@@ -73,25 +72,25 @@ export const ReviewPanelResolvedThread: FC<{
           />
         </div>
         <div className="review-panel-resolved-comment-buttons">
-          <Tooltip
+          <OLTooltip
             id="reopen-thread"
             overlayProps={{ placement: 'bottom' }}
             description={t('reopen')}
           >
-            <Button onClick={handleReopenThread}>
+            <button type="button" className="btn" onClick={handleReopenThread}>
               <MaterialIcon type="refresh" accessibilityLabel={t('reopen')} />
-            </Button>
-          </Tooltip>
+            </button>
+          </OLTooltip>
 
-          <Tooltip
+          <OLTooltip
             id="delete-thread"
             overlayProps={{ placement: 'bottom' }}
             description={t('delete')}
           >
-            <Button onClick={handleDeleteThread}>
+            <button type="button" className="btn" onClick={handleDeleteThread}>
               <MaterialIcon type="delete" accessibilityLabel={t('delete')} />
-            </Button>
-          </Tooltip>
+            </button>
+          </OLTooltip>
         </div>
       </div>
       <div className="review-panel-resolved-comment-quoted-text">

+ 17 - 11
services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx

@@ -1,9 +1,12 @@
 import React, { FC, useRef, useState } from 'react'
 import Icon from '@/shared/components/icon'
+import OLOverlay from '@/features/ui/components/ol/ol-overlay'
+import OLPopover from '@/features/ui/components/ol/ol-popover'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import { ReviewPanelResolvedThreadsMenu } from './review-panel-resolved-threads-menu'
-import { Overlay, Popover } from 'react-bootstrap'
-import Tooltip from '@/shared/components/tooltip'
 import { useTranslation } from 'react-i18next'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
 
 export const ReviewPanelResolvedThreadsButton: FC = () => {
   const [expanded, setExpanded] = useState(false)
@@ -12,7 +15,7 @@ export const ReviewPanelResolvedThreadsButton: FC = () => {
 
   return (
     <>
-      <Tooltip
+      <OLTooltip
         id="resolved-comments"
         overlayProps={{ placement: 'bottom' }}
         description={t('resolved_comments')}
@@ -22,27 +25,30 @@ export const ReviewPanelResolvedThreadsButton: FC = () => {
           ref={buttonRef}
           onClick={() => setExpanded(true)}
         >
-          <Icon type="inbox" fw />
+          <BootstrapVersionSwitcher
+            bs3={<Icon type="inbox" fw />}
+            bs5={<MaterialIcon type="inbox" />}
+          />
         </button>
-      </Tooltip>
+      </OLTooltip>
       {expanded && (
-        <Overlay
+        <OLOverlay
           show
           onHide={() => setExpanded(false)}
-          animation={false}
+          transition={false}
           container={this}
           containerPadding={0}
           placement="bottom"
           rootClose
-          target={buttonRef.current ?? undefined}
+          target={buttonRef.current}
         >
-          <Popover
+          <OLPopover
             id="popover-resolved-threads"
             className="review-panel-resolved-comments review-panel-new"
           >
             <ReviewPanelResolvedThreadsMenu />
-          </Popover>
-        </Overlay>
+          </OLPopover>
+        </OLOverlay>
       )}
     </>
   )

+ 2 - 6
services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-menu.tsx

@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next'
 import { ReviewPanelResolvedThread } from './review-panel-resolved-thread'
 import useProjectRanges from '../hooks/use-project-ranges'
 import { useFileTreeData } from '@/shared/context/file-tree-data-context'
-import Icon from '@/shared/components/icon'
 import { Change, CommentOperation } from '../../../../../types/change'
 import { ThreadId } from '../../../../../types/review-panel/review-panel'
+import LoadingSpinner from '@/shared/components/loading-spinner'
 
 export const ReviewPanelResolvedThreadsMenu: FC = () => {
   const { t } = useTranslation()
@@ -59,11 +59,7 @@ export const ReviewPanelResolvedThreadsMenu: FC = () => {
   }, [threads])
 
   if (loading) {
-    return (
-      <div className="review-panel-resolved-comments-loading">
-        <Icon type="spinner" spin />
-      </div>
-    )
+    return <LoadingSpinner className="ms-auto me-auto" />
   }
 
   if (!resolvedThreads.length) {

+ 8 - 1
services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu-button.tsx

@@ -2,6 +2,8 @@ import { FC, memo } from 'react'
 import { Trans } from 'react-i18next'
 import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
 import Icon from '@/shared/components/icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
 
 const ReviewPanelTrackChangesMenuButton: FC<{
   menuExpanded: boolean
@@ -26,7 +28,12 @@ const ReviewPanelTrackChangesMenuButton: FC<{
           components={{ strong: <strong /> }}
         />
       )}
-      <Icon type={menuExpanded ? 'angle-down' : 'angle-right'} />
+      <BootstrapVersionSwitcher
+        bs3={<Icon type={menuExpanded ? 'angle-down' : 'angle-right'} />}
+        bs5={
+          <MaterialIcon type={menuExpanded ? 'expand_more' : 'chevron_right'} />
+        }
+      />
     </button>
   )
 }

+ 1 - 1
services/web/frontend/js/features/review-panel-new/components/review-panel.tsx

@@ -24,7 +24,7 @@ const ReviewPanel: FC<{ mini?: boolean }> = ({ mini = false }) => {
 
   return (
     <div className={className} style={style}>
-      <div className="review-panel-inner">
+      <div id="review-panel-inner" className="review-panel-inner">
         {!mini && <ReviewPanelHeader />}
 
         {activeSubView === 'cur_file' && <ReviewPanelCurrentFile />}

+ 9 - 14
services/web/frontend/js/features/source-editor/components/review-panel/toolbar/track-changes-toggle.tsx

@@ -1,3 +1,5 @@
+import OLFormSwitch from '@/features/ui/components/ol/ol-form-switch'
+
 type TrackChangesToggleProps = {
   id: string
   description: string
@@ -14,20 +16,13 @@ function TrackChangesToggle({
   value,
 }: TrackChangesToggleProps) {
   return (
-    <div className="input-switch">
-      <input
-        id={`input-switch-${id}`}
-        disabled={disabled}
-        type="checkbox"
-        autoComplete="off"
-        className="input-switch-hidden-input"
-        onChange={handleToggle}
-        checked={value}
-      />
-      <label htmlFor={`input-switch-${id}`} className="input-switch-btn">
-        <span className="sr-only">{description}</span>
-      </label>
-    </div>
+    <OLFormSwitch
+      id={`input-switch-${id}`}
+      disabled={disabled}
+      onChange={handleToggle}
+      checked={value}
+      label={description}
+    />
   )
 }
 

+ 5 - 3
services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx

@@ -103,9 +103,11 @@ const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, {
 
 export { ForwardReferredDropdownItem as DropdownItem }
 
-export function DropdownToggle(props: DropdownToggleProps) {
-  return <BS5DropdownToggle {...props} />
-}
+export const DropdownToggle = forwardRef<
+  typeof BS5DropdownToggle,
+  DropdownToggleProps
+>((props, ref) => <BS5DropdownToggle {...props} ref={ref} />)
+DropdownToggle.displayName = 'DropdownToggle'
 
 export const DropdownMenu = forwardRef<
   typeof BS5DropdownMenu,

+ 1 - 1
services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx

@@ -4,7 +4,7 @@ import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/boots
 import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
 
 type OLFormCheckboxProps = React.ComponentProps<(typeof Form)['Check']> & {
-  inputRef?: React.MutableRefObject<HTMLInputElement | undefined>
+  inputRef?: React.MutableRefObject<HTMLInputElement | null>
   bs3Props?: Record<string, unknown>
 }
 

+ 54 - 0
services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx

@@ -0,0 +1,54 @@
+import { FormCheck, FormCheckProps, FormLabel } from 'react-bootstrap-5'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
+
+type OLFormSwitchProps = FormCheckProps & {
+  inputRef?: React.MutableRefObject<HTMLInputElement | null>
+  bs3Props?: React.InputHTMLAttributes<HTMLInputElement>
+}
+
+function OLFormSwitch(props: OLFormSwitchProps) {
+  const { bs3Props, inputRef, label, id, ...rest } = props
+
+  const bs3FormSwitchProps: React.InputHTMLAttributes<HTMLInputElement> = {
+    id,
+    checked: rest.checked,
+    required: rest.required,
+    readOnly: rest.readOnly,
+    disabled: rest.disabled,
+    autoComplete: rest.autoComplete,
+    defaultChecked: rest.defaultChecked,
+    onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
+    ...getAriaAndDataProps(rest),
+    ...bs3Props,
+  }
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={
+        <div className="input-switch">
+          <input
+            id={id}
+            type="checkbox"
+            className="input-switch-hidden-input"
+            ref={inputRef}
+            {...bs3FormSwitchProps}
+          />
+          <label htmlFor={id} className="input-switch-btn">
+            <span className="sr-only">{label}</span>
+          </label>
+        </div>
+      }
+      bs5={
+        <>
+          <FormCheck type="switch" ref={inputRef} id={id} {...rest} />
+          <FormLabel htmlFor={id} visuallyHidden>
+            {label}
+          </FormLabel>
+        </>
+      }
+    />
+  )
+}
+
+export default OLFormSwitch

+ 31 - 0
services/web/frontend/stories/input-switch.stories.tsx

@@ -0,0 +1,31 @@
+import OLFormSwitch from '@/features/ui/components/ol/ol-form-switch'
+import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
+import { disableControlsOf } from './utils/arg-types'
+
+export const Unchecked = () => {
+  return <OLFormSwitch onChange={() => {}} checked={false} />
+}
+
+export const UncheckedDisabled = () => {
+  return <OLFormSwitch onChange={() => {}} checked={false} disabled />
+}
+
+export const Checked = () => {
+  return <OLFormSwitch onChange={() => {}} checked />
+}
+
+export const CheckedDisabled = () => {
+  return <OLFormSwitch onChange={() => {}} checked disabled />
+}
+
+export default {
+  title: 'Shared / Components / Input Switch',
+  component: OLFormSwitch,
+  argTypes: {
+    ...bsVersionDecorator.argTypes,
+    ...disableControlsOf('inputRef', 'bs3Props'),
+  },
+  args: {
+    ...bsVersionDecorator.args,
+  },
+}

+ 0 - 14
services/web/frontend/stories/switch.stories.tsx

@@ -1,14 +0,0 @@
-import Switch from '../js/shared/components/switch'
-
-export const Unchecked = () => {
-  return <Switch onChange={() => {}} checked={false} />
-}
-
-export const Checked = () => {
-  return <Switch onChange={() => {}} checked />
-}
-
-export default {
-  title: 'Shared / Components / Switch',
-  component: Switch,
-}

+ 6 - 17
services/web/frontend/stylesheets/app/editor/review-panel-new.less

@@ -86,7 +86,7 @@
 
       .review-panel-entry-actions-icon {
         padding: @spacing-01;
-        font-size: 20px;
+        font-size: @font-size-05;
       }
     }
   }
@@ -111,7 +111,7 @@
   .review-panel-entry-icon {
     border-radius: @border-radius-base-new;
     padding: @spacing-02;
-    font-size: 16px;
+    font-size: @font-size-03;
   }
 
   .review-panel-entry-change-icon {
@@ -138,7 +138,6 @@
     top: var(--review-panel-top);
     width: var(--review-panel-width);
     height: @review-panel-header-height;
-    z-index: 2;
     display: flex;
     flex-direction: column;
     justify-content: center;
@@ -187,7 +186,7 @@
     display: flex;
     align-items: center;
     gap: 4px;
-    font-size: 14px;
+    font-size: @font-size-02;
 
     i {
       width: 8px;
@@ -202,7 +201,7 @@
 
     .review-panel-label {
       font-family: Lato, sans-serif;
-      font-size: 14px;
+      font-size: @font-size-02;
       font-weight: bold;
       margin: 0;
     }
@@ -245,10 +244,6 @@
     pointer-events: none;
   }
 
-  .review-panel-resolved-comments-loading {
-    text-align: center;
-  }
-
   .review-panel-resolved-comments-empty {
     text-align: center;
   }
@@ -472,7 +467,7 @@
     width: var(--review-panel-width);
     z-index: 2;
     background-color: white;
-    border-top: 1px solid #d9d9d9;
+    border-top: 1px solid @rp-border-grey;
     display: flex;
 
     .review-panel-tab {
@@ -507,11 +502,6 @@
     min-height: 44px;
   }
 
-  .review-panel-add-comment {
-    position: absolute;
-    z-index: 2;
-  }
-
   .review-panel-add-comment-buttons {
     display: flex;
     justify-content: flex-end;
@@ -545,12 +535,11 @@
     }
   }
 
+  // Would not be migrated
   .review-panel-more-comments-button {
     display: flex;
     justify-content: center;
     align-items: center;
-    padding: 2px 8px;
-    height: 24px;
   }
 
   &.review-panel-subview-overview {

+ 10 - 0
services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss

@@ -6,6 +6,7 @@ $prefix: bs-;
 // Quickly modify global styling by enabling or disabling optional features.
 
 $enable-validation-icons: false;
+$component-active-color: $white;
 
 // Fonts
 $font-family-sans-serif: 'Noto Sans', sans-serif;
@@ -115,6 +116,15 @@ $form-check-input-border-radius: $border-radius-base;
 $form-check-input-focus-border: var(--border-primary);
 $form-check-input-disabled-opacity: 1;
 
+// form-switch-variables
+$form-switch-width: 34px;
+$form-switch-padding-start: $spacing-03;
+$form-switch-color: $component-active-color;
+$form-switch-width: 34px;
+$form-switch-padding-start: $spacing-03;
+$form-switch-focus-color: $component-active-color;
+$form-switch-checked-color: $component-active-color;
+
 // Form validation
 
 // form-feedback-variables

+ 26 - 0
services/web/frontend/stylesheets/bootstrap-5/components/form.scss

@@ -73,6 +73,32 @@
   }
 }
 
+.form-switch {
+  .form-check-input {
+    --bs-form-check-bg: var(--bg-dark-tertiary);
+
+    height: 20px;
+    margin: 0;
+    border: 0;
+
+    &:hover {
+      cursor: pointer;
+    }
+
+    &:disabled {
+      opacity: 0.32;
+      background-color: var(--bg-dark-tertiary);
+
+      &:checked {
+        background-color: var(--bg-accent-01);
+        background-image: #{escape-svg(
+            url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$component-active-color}'/></svg>")
+          )};
+      }
+    }
+  }
+}
+
 .form-text {
   display: inline-flex;
   gap: $spacing-02;

+ 1 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/all.scss

@@ -22,6 +22,7 @@
 @import 'editor/publish-modal';
 @import 'editor/share';
 @import 'editor/tags-input';
+@import 'editor/review-panel-new';
 @import 'website-redesign';
 @import 'group-settings';
 @import 'templates-v2';

+ 638 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss

@@ -0,0 +1,638 @@
+.review-panel-new {
+  $review-panel-header-height: 69px;
+
+  &.review-panel-container {
+    height: 100%;
+    flex-shrink: 0;
+    position: relative;
+  }
+
+  .review-panel-inner {
+    z-index: 6;
+    flex-shrink: 0;
+    background-color: var(--neutral-10);
+    border-left: solid 0 var(--neutral-20);
+    color: var(--content-primary);
+    font-family: $font-family-base;
+    line-height: $line-height-base;
+    font-size: var(--font-size-01);
+    box-sizing: content-box;
+    width: var(--review-panel-width);
+    min-height: var(--review-panel-height);
+
+    .dropdown-menu {
+      z-index: 1;
+      min-width: var(--bs-dropdown-min-width);
+    }
+  }
+
+  .review-panel-entry {
+    background-color: var(--white);
+    border-radius: var(--border-radius-base);
+    border: 1px solid var(--neutral-20);
+    padding: var(--spacing-04);
+    width: calc(100% - var(--spacing-04));
+    margin-left: var(--spacing-02);
+    z-index: 1;
+  }
+
+  .review-panel-entry.review-panel-entry-disabled {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+
+  .review-panel-entry-indicator {
+    display: none;
+  }
+
+  .review-panel-entry-content {
+    display: flex;
+    flex-direction: column;
+    font-size: var(--font-size-01);
+    gap: var(--spacing-04);
+  }
+
+  .review-panel-entry-focused,
+  .review-panel-entry-highlighted {
+    margin-left: var(--spacing-01);
+    border: 1px solid var(--border-active);
+  }
+
+  .review-panel-entry-header {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: var(--spacing-01);
+
+    .review-panel-entry-user {
+      color: var(--bg-info-01);
+      font-size: 110%;
+    }
+
+    .review-panel-entry-time {
+      color: var(--content-secondary);
+    }
+
+    .review-panel-entry-actions {
+      display: flex;
+      align-items: center;
+      gap: var(--spacing-03);
+
+      .btn {
+        background-color: transparent;
+        border-width: 0;
+        padding: 0;
+        height: 24px;
+        width: 24px;
+
+        &:hover,
+        &:focus {
+          background-color: var(--neutral-20);
+          color: var(--content-primary);
+        }
+      }
+
+      .dropdown-toggle::after {
+        display: none;
+      }
+
+      .review-panel-entry-actions-icon {
+        padding: var(--spacing-01);
+        font-size: var(--font-size-05);
+      }
+    }
+  }
+
+  .review-panel-change-body {
+    display: flex;
+    align-items: flex-start;
+    color: var(--content-secondary);
+    gap: var(--spacing-02);
+    overflow-wrap: anywhere;
+  }
+
+  .review-panel-content-highlight {
+    color: var(--content-primary);
+    text-decoration: none;
+  }
+
+  del.review-panel-content-highlight {
+    text-decoration: line-through;
+  }
+
+  .review-panel-entry-icon {
+    border-radius: var(--border-radius-base);
+    padding: var(--spacing-02);
+    font-size: var(--font-size-03);
+  }
+
+  .review-panel-entry-change-icon {
+    margin-top: calc(-1 * var(--spacing-01));
+  }
+
+  .review-panel-entry-icon-accept {
+    background-color: var(--bg-accent-03);
+    color: var(--bg-accent-01);
+  }
+
+  .review-panel-entry-icon-reject {
+    background-color: var(--bg-danger-03);
+    color: var(--bg-danger-01);
+  }
+
+  .review-panel-entry-icon-changed {
+    background-color: var(--neutral-20);
+    color: var(--content-secondary);
+  }
+
+  .review-panel-header {
+    position: fixed;
+    top: var(--review-panel-top);
+    width: var(--review-panel-width);
+    height: $review-panel-header-height;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    border-bottom: 1px solid var(--rp-border-grey);
+    background-color: white;
+    text-align: center;
+    z-index: 4;
+  }
+
+  // TODO: Update this when we move the track changes menu to the new design
+  .rp-tc-state {
+    background-color: var(--white);
+    max-height: calc(
+      100vh - var(--review-panel-top) - $review-panel-header-height
+    );
+    overflow-y: auto;
+  }
+
+  .review-panel-tools {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-left: var(--spacing-02);
+    padding-right: var(--spacing-05);
+    flex-shrink: 0;
+    flex-basis: 32px;
+  }
+
+  .resolved-comments-toggle {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .track-changes-indicator-circle {
+    width: 8px;
+    height: 8px;
+    border-radius: 100%;
+    background-color: var(--bg-accent-01);
+  }
+
+  .track-changes-menu-button {
+    border: none;
+    background: none;
+    padding: 0;
+    display: flex;
+    align-items: center;
+    gap: var(--spacing-02);
+    font-size: var(--font-size-02);
+
+    i {
+      width: 8px;
+    }
+  }
+
+  .review-panel-heading {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: var(--spacing-03) var(--spacing-02);
+
+    .review-panel-label {
+      font-size: var(--font-size-02);
+      font-weight: bold;
+      margin: 0;
+    }
+
+    .review-panel-split-test-badge {
+      margin-left: var(--spacing-02);
+    }
+
+    .review-panel-close-button {
+      display: flex;
+      align-items: center;
+      border: none;
+      background-color: transparent;
+      color: var(--content-primary);
+      padding: var(--spacing-01);
+
+      &:hover,
+      &:focus {
+        background-color: var(--neutral-20);
+      }
+    }
+  }
+
+  &.review-panel-resolved-comments {
+    --bs-popover-border-width: 1px;
+    --bs-popover-bg: var(--bg-light-secondary);
+    --bs-popover-body-color: var(--content-secondary);
+
+    width: 280px;
+
+    .popover-body {
+      overflow-y: auto;
+      max-height: calc(100vh - 180px);
+      display: flex;
+      flex-direction: column;
+      gap: var(--spacing-02);
+      padding: var(--spacing-04) var(--spacing-03);
+    }
+  }
+
+  .review-panel-resolved-disabled {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+
+  .review-panel-resolved-comments-empty {
+    text-align: center;
+  }
+
+  .review-panel-resolved-comments-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: var(--spacing-02) 0;
+  }
+
+  .review-panel-resolved-comments-label {
+    font-weight: bold;
+    font-size: var(--font-size-02);
+  }
+
+  .review-panel-resolved-comments-count {
+    background-color: var(--neutral-20);
+    border-radius: var(--border-radius-base);
+    padding: var(--spacing-01);
+  }
+
+  .review-panel-resolved-comment {
+    background-color: var(--white);
+    border-radius: var(--border-radius-base);
+    padding: var(--spacing-04);
+    display: flex;
+    flex-direction: column;
+    gap: var(--spacing-04);
+  }
+
+  .review-panel-resolved-comment-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: var(--content-secondary);
+    font-size: var(--font-size-01);
+  }
+
+  .review-panel-resolved-comment-filename {
+    color: var(--content-primary);
+  }
+
+  .review-panel-resolved-comment-buttons {
+    display: flex;
+    align-items: center;
+    gap: var(--spacing-03);
+
+    .btn {
+      background-color: transparent;
+      border-width: 0;
+      color: var(--content-primary);
+      padding: var(--spacing-01);
+      height: 24px;
+      width: 24px;
+
+      &:hover,
+      &:focus {
+        background-color: var(--neutral-20);
+      }
+    }
+
+    .material-symbols {
+      font-size: var(--font-size-05);
+    }
+  }
+
+  .review-panel-resolved-comment-quoted-text {
+    background-color: var(--neutral-20);
+    border-radius: var(--border-radius-base);
+    padding: var(--spacing-02) var(--spacing-04);
+  }
+
+  .review-panel-resolved-comment-quoted-text-label {
+    color: var(--content-secondary);
+    font-size: var(--font-size-01);
+  }
+
+  .review-panel-resolved-comment-quoted-text-quote {
+    overflow-wrap: anywhere;
+    font-size: var(--font-size-02);
+  }
+
+  .review-panel-comment-wrapper {
+    display: flex;
+    gap: var(--spacing-04);
+  }
+
+  .review-panel-comment {
+    flex-grow: 1;
+  }
+
+  .review-panel-comment-reply-divider {
+    border-left: 2px solid var(--yellow-20);
+  }
+
+  .review-panel-comment-body {
+    font-size: var(--font-size-02);
+    color: var(--content-primary);
+    overflow-wrap: anywhere;
+  }
+
+  .review-panel-content-expandable {
+    display: -webkit-box;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 3;
+    line-clamp: 3;
+    overflow: hidden;
+  }
+
+  .review-panel-content-expanded {
+    display: block;
+  }
+
+  .review-panel-comment-input {
+    width: 100%;
+    font-size: var(--rp-base-font-size);
+    padding: 2px var(--spacing-03);
+    border-radius: var(--border-radius-base);
+    border: solid 1px var(--neutral-60);
+    resize: vertical;
+    color: var(--rp-type-darkgrey);
+    background-color: var(--white);
+    height: 25px;
+    min-height: 25px;
+    overflow-x: hidden;
+    max-height: 400px;
+  }
+
+  .review-panel-empty-state {
+    position: fixed;
+    width: var(--review-panel-width);
+    top: 0;
+    bottom: 0;
+    pointer-events: none;
+  }
+
+  .review-panel-empty-state-inner {
+    position: sticky;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 100%;
+    padding-left: var(--spacing-06);
+    padding-right: var(--spacing-06);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+
+    p {
+      margin-bottom: 0;
+      text-align: center;
+    }
+  }
+
+  .review-panel-empty-state-comment-icon {
+    width: 80px;
+    height: 80px;
+    background-color: white;
+    border-radius: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: var(--spacing-06);
+
+    .material-symbols {
+      font-size: 32px;
+    }
+  }
+
+  .review-panel-overview {
+    padding: var(--spacing-02);
+    position: absolute;
+    top: $review-panel-header-height;
+    bottom: 59px;
+    width: 100%;
+    overflow: auto;
+    overscroll-behavior-block: none;
+
+    .review-panel-entry {
+      margin-left: 0;
+      width: 100%;
+    }
+  }
+
+  .review-panel-overview-file-header {
+    all: unset;
+    padding: var(--spacing-03) var(--spacing-04);
+    font-size: var(--font-size-02);
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: var(--spacing-04);
+    box-sizing: border-box;
+    width: 100%;
+  }
+
+  .review-panel-overfile-divider {
+    border-bottom: 1px solid var(--border-divider);
+    margin: var(--spacing-01) 0;
+  }
+
+  .review-panel-overview-file-entries {
+    overflow: hidden;
+    padding-top: var(--spacing-02);
+    padding-bottom: var(--spacing-03);
+  }
+
+  .review-panel-overview-file-entry-count {
+    background-color: var(--neutral-20);
+    padding: var(--spacing-01) var(--spacing-02);
+    margin-left: auto;
+    border-radius: var(--border-radius-base);
+  }
+
+  .review-panel-footer {
+    position: fixed;
+    height: 60px;
+    bottom: 0;
+    width: var(--review-panel-width);
+    z-index: 2;
+    background-color: white;
+    border-top: 1px solid var(--rp-border-grey);
+    display: flex;
+
+    .review-panel-tab {
+      flex: 0 0 50%;
+      padding: var(--spacing-03) 0;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: var(--spacing-02);
+      border: 0;
+      border-top: solid 3px transparent;
+      background: none;
+      color: var(--content-secondary);
+      font-size: var(--font-size-02);
+
+      &:hover,
+      &:focus {
+        text-decoration: none;
+        color: var(--content-primary);
+      }
+
+      &-active {
+        color: var(--content-primary);
+        border-top: solid 3px var(--bg-accent-01);
+      }
+    }
+  }
+
+  .review-panel-add-comment-textarea {
+    padding: var(--spacing-01) var(--spacing-03);
+    resize: vertical;
+    min-height: 44px;
+  }
+
+  .review-panel-add-comment-buttons {
+    display: flex;
+    justify-content: flex-end;
+    gap: var(--spacing-04);
+  }
+
+  .review-panel-add-comment-cancel-button {
+    background-color: transparent;
+
+    &:hover,
+    &:focus {
+      background-color: var(--neutral-20);
+      color: var(--content-primary);
+    }
+  }
+
+  .review-panel-more-comments-button-container {
+    position: fixed;
+    width: var(--review-panel-width);
+    display: flex;
+    justify-content: center;
+    z-index: 3;
+
+    &.downwards {
+      // TODO: fix this to not use a magic number when we have updated the footer ui
+      top: calc(100% - 102px);
+    }
+
+    &.upwards {
+      top: calc(var(--review-panel-top) + $review-panel-header-height + 16px);
+    }
+  }
+
+  &.review-panel-subview-overview {
+    &.review-panel-container {
+      overflow-y: hidden;
+      position: sticky;
+      top: 0;
+    }
+
+    .review-panel-inner {
+      min-height: auto;
+      height: 100%;
+      overflow: hidden;
+    }
+  }
+
+  &.review-panel-mini {
+    overflow: visible !important;
+
+    .review-panel-inner {
+      width: 24px;
+    }
+
+    .review-panel-entry {
+      margin-left: 0;
+      background-color: transparent;
+      border: none;
+      width: 100%;
+    }
+
+    .review-panel-entry-indicator {
+      position: absolute;
+      left: 0;
+      top: 0;
+      display: flex;
+      color: var(--content-secondary);
+      cursor: pointer;
+    }
+
+    .review-panel-entry-content {
+      display: none;
+      background: var(--white);
+      border: 1px solid var(--rp-border-grey);
+      border-radius: var(--border-radius-base);
+      width: 200px;
+      padding: var(--spacing-02);
+    }
+
+    .review-panel-entry-hover {
+      .review-panel-entry-content {
+        display: initial;
+        position: absolute;
+        left: -200px;
+        top: 0;
+      }
+    }
+
+    .review-panel-more-comments-button-container {
+      display: none;
+    }
+
+    .review-panel-footer {
+      display: none;
+    }
+  }
+}
+
+.review-tooltip-menu {
+  box-shadow: 0 2px 4px 0 #1e253029;
+  border: none;
+  border-radius: var(--border-radius-base);
+  padding: var(--spacing-02);
+}
+
+.review-tooltip-menu-button {
+  background-color: inherit;
+  border: none;
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-01);
+  padding: var(--spacing-01);
+}
+
+.review-tooltip-add-comment-button {
+  padding: var(--spacing-01) var(--spacing-04);
+}
+
+.review-panel-tooltip {
+  pointer-events: none; // this is to prevent mouseLeave event from firing when hovering over the tooltip
+}

+ 62 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel.scss

@@ -1,5 +1,11 @@
+@use 'sass:color';
+
 :root {
   --review-icon: url('../../../../../public/img/ol-icons/review-icon-dark-theme.svg');
+  --rp-base-font-size: var(--font-size-01);
+  --rp-bg-dim-blue: #fafafa;
+  --rp-border-grey: #d9d9d9;
+  --rp-type-darkgrey: #3f3f3f;
 }
 
 @include theme('light') {
@@ -15,6 +21,43 @@
   }
 }
 
+$rp-type-blue: #6b7797;
+
+.rp-tc-state {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  overflow: hidden;
+  list-style: none;
+  padding: 0 var(--spacing-03);
+  margin: 0;
+  border-bottom: 1px solid var(--rp-border-grey);
+  background-color: var(--rp-bg-dim-blue);
+  text-align: left;
+}
+
+.rp-tc-state-item {
+  display: flex;
+  align-items: center;
+  padding: var(--spacing-02) 0;
+
+  &:last-of-type {
+    padding-bottom: var(--spacing-03);
+  }
+}
+
+.rp-tc-state-item-name {
+  @extend .text-truncate;
+
+  flex-grow: 1;
+  font-weight: 600;
+}
+
+.rp-tc-state-item-name-disabled {
+  opacity: 0.35;
+}
+
 .review-icon {
   display: inline-block;
   background-image: var(--review-icon);
@@ -27,3 +70,22 @@
     content: '\00a0'; // Non-breakable space. A non-breakable character here makes this icon work like font-awesome.
   }
 }
+
+.resolved-comments-toggle {
+  background-color: var(--bg-light-secondary);
+  font-size: var(--font-size-02);
+  color: color.adjust($rp-type-blue, $lightness: 25%);
+  border: solid 1px var(--rp-border-grey);
+  border-radius: var(--border-radius-base);
+  padding: 0 var(--spacing-02);
+  display: block;
+  height: 22px;
+  width: 22px;
+  line-height: 1.4;
+
+  &:hover,
+  &:focus {
+    text-decoration: none;
+    color: $rp-type-blue;
+  }
+}

+ 0 - 1
services/web/frontend/stylesheets/components/input-switch.less

@@ -1,6 +1,5 @@
 .input-switch {
   display: inline-block;
-  vertical-align: middle;
   padding-left: 5px;
 }
 .input-switch-hidden-input {