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

Merge pull request #20205 from overleaf/ii-bs5-ide-header

[web] BS5 ide page header

GitOrigin-RevId: b7aad221d458d8403d60ff9950129394c74af856
ilkin-overleaf пре 2 година
родитељ
комит
86689a6269
22 измењених фајлова са 770 додато и 170 уклоњено
  1. 6 6
      services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx
  2. 20 7
      services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx
  3. 22 5
      services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx
  4. 7 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx
  5. 252 95
      services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx
  6. 12 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx
  7. 76 30
      services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx
  8. 11 6
      services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.tsx
  9. 7 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx
  10. 20 3
      services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx
  11. 6 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx
  12. 11 3
      services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx
  13. 1 1
      services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
  14. 1 0
      services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
  15. 0 2
      services/web/frontend/stories/ui/dropdown-menu.stories.tsx
  16. 2 0
      services/web/frontend/stylesheets/app/editor/online-users.less
  17. 37 0
      services/web/frontend/stylesheets/bootstrap-5/base/base.scss
  18. 4 0
      services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss
  19. 3 1
      services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
  20. 33 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss
  21. 16 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel.scss
  22. 223 4
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss

+ 6 - 6
services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx

@@ -1,20 +1,20 @@
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
-import { Button } from 'react-bootstrap'
 import MaterialIcon from '@/shared/components/material-icon'
 import MaterialIcon from '@/shared/components/material-icon'
+import OLButton from '@/features/ui/components/ol/ol-button'
 
 
 function BackToEditorButton({ onClick }: { onClick: () => void }) {
 function BackToEditorButton({ onClick }: { onClick: () => void }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
-    <Button
-      bsSize="sm"
-      bsStyle={null}
+    <OLButton
+      variant="secondary"
+      size="small"
       onClick={onClick}
       onClick={onClick}
       className="back-to-editor-btn"
       className="back-to-editor-btn"
     >
     >
       <MaterialIcon type="arrow_back" className="toolbar-btn-secondary-icon" />
       <MaterialIcon type="arrow_back" className="toolbar-btn-secondary-icon" />
-      <p className="toolbar-label">{t('back_to_editor')}</p>
-    </Button>
+      <span className="toolbar-label">{t('back_to_editor')}</span>
+    </OLButton>
   )
   )
 }
 }
 
 

+ 20 - 7
services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx

@@ -1,13 +1,15 @@
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
-import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import * as eventTracking from '../../../infrastructure/event-tracking'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
 
 
 function BackToProjectsButton() {
 function BackToProjectsButton() {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
-    <Tooltip
+    <OLTooltip
       id="back-to-projects"
       id="back-to-projects"
       description={t('back_to_your_projects')}
       description={t('back_to_your_projects')}
       overlayProps={{ placement: 'right' }}
       overlayProps={{ placement: 'right' }}
@@ -21,14 +23,25 @@ function BackToProjectsButton() {
             eventTracking.sendMB('navigation-clicked-home')
             eventTracking.sendMB('navigation-clicked-home')
           }}
           }}
         >
         >
-          <Icon
-            type="home"
-            fw
-            accessibilityLabel={t('back_to_your_projects')}
+          <BootstrapVersionSwitcher
+            bs3={
+              <Icon
+                type="home"
+                fw
+                accessibilityLabel={t('back_to_your_projects')}
+              />
+            }
+            bs5={
+              <MaterialIcon
+                type="home"
+                className="align-text-bottom"
+                accessibilityLabel={t('back_to_your_projects')}
+              />
+            }
           />
           />
         </a>
         </a>
       </div>
       </div>
-    </Tooltip>
+    </OLTooltip>
   )
   )
 }
 }
 
 

+ 22 - 5
services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx

@@ -2,6 +2,9 @@ import PropTypes from 'prop-types'
 import classNames from 'classnames'
 import classNames from 'classnames'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
+import OLBadge from '@/features/ui/components/ol/ol-badge'
 
 
 function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) {
 function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -16,11 +19,25 @@ function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) {
 
 
   return (
   return (
     <div className="toolbar-item">
     <div className="toolbar-item">
-      <button className={classes} onClick={onClick}>
-        <Icon type="comment" fw className={hasUnreadMessages ? 'bounce' : ''} />
-        {hasUnreadMessages && (
-          <span className="label label-info">{unreadMessageCount}</span>
-        )}
+      <button type="button" className={classes} onClick={onClick}>
+        <BootstrapVersionSwitcher
+          bs3={
+            <Icon
+              type="comment"
+              fw
+              className={classNames({ bounce: hasUnreadMessages })}
+            />
+          }
+          bs5={
+            <MaterialIcon
+              type="chat"
+              className={classNames('align-middle', {
+                bounce: hasUnreadMessages,
+              })}
+            />
+          }
+        />
+        {hasUnreadMessages && <OLBadge bg="info">{unreadMessageCount}</OLBadge>}
         <p className="toolbar-label">{t('chat')}</p>
         <p className="toolbar-label">{t('chat')}</p>
       </button>
       </button>
     </div>
     </div>

+ 7 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx

@@ -1,14 +1,19 @@
 import PropTypes from 'prop-types'
 import PropTypes from 'prop-types'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
+import MaterialIcon from '@/shared/components/material-icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 
 function HistoryToggleButton({ onClick }) {
 function HistoryToggleButton({ onClick }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
     <div className="toolbar-item">
     <div className="toolbar-item">
-      <button className="btn btn-full-height" onClick={onClick}>
-        <Icon type="history" fw />
+      <button type="button" className="btn btn-full-height" onClick={onClick}>
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="history" fw />}
+          bs5={<MaterialIcon type="history" className="align-middle" />}
+        />
         <p className="toolbar-label">{t('history')}</p>
         <p className="toolbar-label">{t('history')}</p>
       </button>
       </button>
     </div>
     </div>

+ 252 - 95
services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx

@@ -1,5 +1,16 @@
-import { memo, ReactNode, useCallback } from 'react'
-import { Dropdown, MenuItem, MenuItemProps } from 'react-bootstrap'
+import { memo, ReactNode, useCallback, forwardRef } from 'react'
+import {
+  Dropdown as BS3Dropdown,
+  MenuItem,
+  MenuItemProps,
+} from 'react-bootstrap'
+import { Spinner } from 'react-bootstrap-5'
+import {
+  Dropdown,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
 import { Trans, useTranslation } from 'react-i18next'
 import { Trans, useTranslation } from 'react-i18next'
 import Tooltip from '../../../shared/components/tooltip'
 import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
@@ -13,6 +24,8 @@ import {
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import useEventListener from '../../../shared/hooks/use-event-listener'
 import useEventListener from '../../../shared/hooks/use-event-listener'
 import { DetachRole } from '@/shared/context/detach-context'
 import { DetachRole } from '@/shared/context/detach-context'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
 
 
 function IconPlaceholder() {
 function IconPlaceholder() {
   return <Icon type="" fw />
   return <Icon type="" fw />
@@ -42,7 +55,7 @@ function IconPdfOnly() {
   return <Icon type="file-pdf-o" fw />
   return <Icon type="file-pdf-o" fw />
 }
 }
 
 
-function IconCheckmark({
+const isActiveDropdownItem = ({
   iconFor,
   iconFor,
   pdfLayout,
   pdfLayout,
   view,
   view,
@@ -52,23 +65,26 @@ function IconCheckmark({
   pdfLayout: IdeLayout
   pdfLayout: IdeLayout
   view: IdeView | null
   view: IdeView | null
   detachRole?: DetachRole
   detachRole?: DetachRole
-}) {
+}) => {
   if (detachRole === 'detacher' || view === 'history') {
   if (detachRole === 'detacher' || view === 'history') {
-    return <IconPlaceholder />
+    return false
   }
   }
   if (
   if (
     iconFor === 'editorOnly' &&
     iconFor === 'editorOnly' &&
     pdfLayout === 'flat' &&
     pdfLayout === 'flat' &&
     (view === 'editor' || view === 'file')
     (view === 'editor' || view === 'file')
   ) {
   ) {
-    return <IconChecked />
+    return true
   } else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
   } else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
-    return <IconChecked />
+    return true
   } else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
   } else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
-    return <IconChecked />
+    return true
   }
   }
-  // return empty icon for placeholder
-  return <IconPlaceholder />
+  return false
+}
+
+function IconCheckmark(props: Parameters<typeof isActiveDropdownItem>[0]) {
+  return isActiveDropdownItem(props) ? <IconChecked /> : <IconPlaceholder />
 }
 }
 
 
 function LayoutMenuItem({
 function LayoutMenuItem({
@@ -94,6 +110,35 @@ function LayoutMenuItem({
   )
   )
 }
 }
 
 
+function EnhancedDropdownItem({
+  active,
+  ...props
+}: React.ComponentProps<typeof DropdownItem>) {
+  return (
+    <DropdownItem
+      active={active}
+      aria-current={active}
+      trailingIcon={active ? 'check' : null}
+      {...props}
+    />
+  )
+}
+
+const LayoutDropdownToggleButton = forwardRef<
+  HTMLButtonElement,
+  {
+    onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
+  }
+>(({ onClick, ...props }, ref) => {
+  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
+    eventTracking.sendMB('navigation-clicked-layout')
+    onClick(e)
+  }
+
+  return <button {...props} ref={ref} onClick={handleClick} />
+})
+LayoutDropdownToggleButton.displayName = 'LayoutDropdownToggleButton'
+
 function DetachDisabled() {
 function DetachDisabled() {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
@@ -164,97 +209,209 @@ function LayoutDropdownButton() {
           {t('layout_processing')}
           {t('layout_processing')}
         </div>
         </div>
       )}
       )}
-      <ControlledDropdown
-        id="layout-dropdown"
-        onMainButtonClick={() => {
-          eventTracking.sendMB('navigation-clicked-layout')
-        }}
-        className="toolbar-item layout-dropdown"
-        pullRight
-      >
-        <Dropdown.Toggle className="btn-full-height" bsStyle="link">
-          {processing ? <IconRefresh /> : <IconLayout />}
-          <span className="toolbar-label">{t('layout')}</span>
-        </Dropdown.Toggle>
-        <Dropdown.Menu className="layout-dropdown-list">
-          <LayoutMenuItem
-            onSelect={() => handleChangeLayout('sideBySide')}
-            checkmark={
-              <IconCheckmark
-                iconFor="sideBySide"
-                pdfLayout={pdfLayout}
-                view={view}
-                detachRole={detachRole}
+      <BootstrapVersionSwitcher
+        bs3={
+          <ControlledDropdown
+            id="layout-dropdown"
+            onMainButtonClick={() => {
+              eventTracking.sendMB('navigation-clicked-layout')
+            }}
+            className="toolbar-item layout-dropdown"
+            pullRight
+          >
+            <BS3Dropdown.Toggle className="btn-full-height" bsStyle="link">
+              {processing ? <IconRefresh /> : <IconLayout />}
+              <span className="toolbar-label">{t('layout')}</span>
+            </BS3Dropdown.Toggle>
+            <BS3Dropdown.Menu className="layout-dropdown-list">
+              <LayoutMenuItem
+                onSelect={() => handleChangeLayout('sideBySide')}
+                checkmark={
+                  <IconCheckmark
+                    iconFor="sideBySide"
+                    pdfLayout={pdfLayout}
+                    view={view}
+                    detachRole={detachRole}
+                  />
+                }
+                icon={<IconSplit />}
+                text={t('editor_and_pdf')}
               />
               />
-            }
-            icon={<IconSplit />}
-            text={t('editor_and_pdf')}
-          />
 
 
-          <LayoutMenuItem
-            onSelect={() => handleChangeLayout('flat', 'editor')}
-            checkmark={
-              <IconCheckmark
-                iconFor="editorOnly"
-                pdfLayout={pdfLayout}
-                view={view}
-                detachRole={detachRole}
+              <LayoutMenuItem
+                onSelect={() => handleChangeLayout('flat', 'editor')}
+                checkmark={
+                  <IconCheckmark
+                    iconFor="editorOnly"
+                    pdfLayout={pdfLayout}
+                    view={view}
+                    detachRole={detachRole}
+                  />
+                }
+                icon={<IconEditorOnly />}
+                text={
+                  <Trans
+                    i18nKey="editor_only_hide_pdf"
+                    components={[
+                      <span key="editor_only_hide_pdf" className="subdued" />,
+                    ]}
+                  />
+                }
               />
               />
-            }
-            icon={<IconEditorOnly />}
-            text={
-              <Trans
-                i18nKey="editor_only_hide_pdf"
-                components={[
-                  <span key="editor_only_hide_pdf" className="subdued" />,
-                ]}
-              />
-            }
-          />
 
 
-          <LayoutMenuItem
-            onSelect={() => handleChangeLayout('flat', 'pdf')}
-            checkmark={
-              <IconCheckmark
-                iconFor="pdfOnly"
-                pdfLayout={pdfLayout}
-                view={view}
-                detachRole={detachRole}
-              />
-            }
-            icon={<IconPdfOnly />}
-            text={
-              <Trans
-                i18nKey="pdf_only_hide_editor"
-                components={[
-                  <span key="pdf_only_hide_editor" className="subdued" />,
-                ]}
+              <LayoutMenuItem
+                onSelect={() => handleChangeLayout('flat', 'pdf')}
+                checkmark={
+                  <IconCheckmark
+                    iconFor="pdfOnly"
+                    pdfLayout={pdfLayout}
+                    view={view}
+                    detachRole={detachRole}
+                  />
+                }
+                icon={<IconPdfOnly />}
+                text={
+                  <Trans
+                    i18nKey="pdf_only_hide_editor"
+                    components={[
+                      <span key="pdf_only_hide_editor" className="subdued" />,
+                    ]}
+                  />
+                }
               />
               />
-            }
-          />
 
 
-          {'BroadcastChannel' in window ? (
-            <LayoutMenuItem
-              onSelect={() => handleDetach()}
-              checkmark={
-                detachRole === 'detacher' ? (
-                  detachIsLinked ? (
-                    <IconChecked />
-                  ) : (
-                    <IconRefresh />
-                  )
-                ) : (
-                  <IconPlaceholder />
-                )
-              }
-              icon={<IconDetach />}
-              text={t('pdf_in_separate_tab')}
-            />
-          ) : (
-            <DetachDisabled />
-          )}
-        </Dropdown.Menu>
-      </ControlledDropdown>
+              {'BroadcastChannel' in window ? (
+                <LayoutMenuItem
+                  onSelect={() => handleDetach()}
+                  checkmark={
+                    detachRole === 'detacher' ? (
+                      detachIsLinked ? (
+                        <IconChecked />
+                      ) : (
+                        <IconRefresh />
+                      )
+                    ) : (
+                      <IconPlaceholder />
+                    )
+                  }
+                  icon={<IconDetach />}
+                  text={t('pdf_in_separate_tab')}
+                />
+              ) : (
+                <DetachDisabled />
+              )}
+            </BS3Dropdown.Menu>
+          </ControlledDropdown>
+        }
+        bs5={
+          <Dropdown className="toolbar-item layout-dropdown" align="end">
+            <DropdownToggle
+              id="layout-dropdown-btn"
+              className="btn-full-height"
+              as={LayoutDropdownToggleButton}
+            >
+              {processing ? (
+                <Spinner
+                  animation="border"
+                  aria-hidden="true"
+                  size="sm"
+                  role="status"
+                />
+              ) : (
+                <MaterialIcon type="dock_to_right" className="align-middle" />
+              )}
+              <span className="toolbar-label">{t('layout')}</span>
+            </DropdownToggle>
+            <DropdownMenu className="layout-dropdown-list">
+              <EnhancedDropdownItem
+                onClick={() => handleChangeLayout('sideBySide')}
+                active={isActiveDropdownItem({
+                  iconFor: 'sideBySide',
+                  pdfLayout,
+                  view,
+                  detachRole,
+                })}
+                leadingIcon="dock_to_right"
+              >
+                {t('editor_and_pdf')}
+              </EnhancedDropdownItem>
+
+              <EnhancedDropdownItem
+                onClick={() => handleChangeLayout('flat', 'editor')}
+                active={isActiveDropdownItem({
+                  iconFor: 'editorOnly',
+                  pdfLayout,
+                  view,
+                  detachRole,
+                })}
+                leadingIcon="code"
+              >
+                <Trans
+                  i18nKey="editor_only_hide_pdf"
+                  components={[
+                    <span
+                      key="editor_only_hide_pdf"
+                      className="ms-1 subdued"
+                    />,
+                  ]}
+                />
+              </EnhancedDropdownItem>
+
+              <EnhancedDropdownItem
+                onClick={() => handleChangeLayout('flat', 'pdf')}
+                active={isActiveDropdownItem({
+                  iconFor: 'pdfOnly',
+                  pdfLayout,
+                  view,
+                  detachRole,
+                })}
+                leadingIcon="picture_as_pdf"
+              >
+                <Trans
+                  i18nKey="pdf_only_hide_editor"
+                  components={[
+                    <span
+                      key="pdf_only_hide_editor"
+                      className="ms-1 subdued"
+                    />,
+                  ]}
+                />
+              </EnhancedDropdownItem>
+
+              {'BroadcastChannel' in window ? (
+                <EnhancedDropdownItem
+                  onClick={() => handleDetach()}
+                  active={detachRole === 'detacher' && detachIsLinked}
+                  trailingIcon={
+                    detachRole === 'detacher' ? (
+                      detachIsLinked ? (
+                        'check'
+                      ) : (
+                        <span className="spinner-container">
+                          <Spinner
+                            animation="border"
+                            aria-hidden="true"
+                            size="sm"
+                            role="status"
+                          />
+                          <span className="visually-hidden">
+                            {t('loading')}
+                          </span>
+                        </span>
+                      )
+                    ) : null
+                  }
+                  leadingIcon="select_window"
+                >
+                  {t('pdf_in_separate_tab')}
+                </EnhancedDropdownItem>
+              ) : (
+                <DetachDisabled />
+              )}
+            </DropdownMenu>
+          </Dropdown>
+        }
+      />
     </>
     </>
   )
   )
 }
 }

+ 12 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx

@@ -1,14 +1,24 @@
 import PropTypes from 'prop-types'
 import PropTypes from 'prop-types'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
+import MaterialIcon from '@/shared/components/material-icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 
 function MenuButton({ onClick }) {
 function MenuButton({ onClick }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
     <div className="toolbar-item">
     <div className="toolbar-item">
-      <button className="btn btn-full-height" onClick={onClick}>
-        <Icon type="bars" fw className="editor-menu-icon" />
+      <button type="button" className="btn btn-full-height" onClick={onClick}>
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="bars" fw className="editor-menu-icon" />}
+          bs5={
+            <MaterialIcon
+              type="menu"
+              className="editor-menu-icon align-middle"
+            />
+          }
+        />
         <p className="toolbar-label">{t('menu')}</p>
         <p className="toolbar-label">{t('menu')}</p>
       </button>
       </button>
     </div>
     </div>

+ 76 - 30
services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx

@@ -1,11 +1,20 @@
 import React from 'react'
 import React from 'react'
 import PropTypes from 'prop-types'
 import PropTypes from 'prop-types'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
-import { Dropdown, MenuItem } from 'react-bootstrap'
-import Tooltip from '../../../shared/components/tooltip'
+import { Dropdown as BS3Dropdown, MenuItem } from 'react-bootstrap'
+import {
+  Dropdown,
+  DropdownHeader,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
 import { getHueForUserId } from '../../../shared/utils/colors'
 import { getHueForUserId } from '../../../shared/utils/colors'
 import ControlledDropdown from '../../../shared/components/controlled-dropdown'
 import ControlledDropdown from '../../../shared/components/controlled-dropdown'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
 
 
 function OnlineUsersWidget({ onlineUsers, goToUser }) {
 function OnlineUsersWidget({ onlineUsers, goToUser }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -14,31 +23,63 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) {
 
 
   if (shouldDisplayDropdown) {
   if (shouldDisplayDropdown) {
     return (
     return (
-      <ControlledDropdown id="online-users" className="online-users" pullRight>
-        <DropDownToggleButton
-          bsRole="toggle"
-          onlineUserCount={onlineUsers.length}
-        />
-        <Dropdown.Menu>
-          <MenuItem header>{t('connected_users')}</MenuItem>
-          {onlineUsers.map((user, index) => (
-            <MenuItem
-              as="button"
-              key={`${user.user_id}_${index}`}
-              eventKey={user}
-              onSelect={goToUser}
-            >
-              <UserIcon user={user} onClick={goToUser} showName />
-            </MenuItem>
-          ))}
-        </Dropdown.Menu>
-      </ControlledDropdown>
+      <BootstrapVersionSwitcher
+        bs3={
+          <ControlledDropdown
+            id="online-users"
+            className="online-users"
+            pullRight
+          >
+            <DropDownToggleButton
+              bsRole="toggle"
+              onlineUserCount={onlineUsers.length}
+            />
+            <BS3Dropdown.Menu>
+              <MenuItem header>{t('connected_users')}</MenuItem>
+              {onlineUsers.map((user, index) => (
+                <MenuItem
+                  as="button"
+                  key={`${user.user_id}_${index}`}
+                  eventKey={user}
+                  onSelect={goToUser}
+                >
+                  <UserIcon user={user} showName />
+                </MenuItem>
+              ))}
+            </BS3Dropdown.Menu>
+          </ControlledDropdown>
+        }
+        bs5={
+          <Dropdown id="online-users" className="online-users" align="end">
+            <DropdownToggle
+              as={DropDownToggleButton}
+              onlineUserCount={onlineUsers.length}
+            />
+            <DropdownMenu>
+              <DropdownHeader aria-hidden="true">
+                {t('connected_users')}
+              </DropdownHeader>
+              {onlineUsers.map((user, index) => (
+                <li role="none" key={`${user.user_id}_${index}`}>
+                  <DropdownItem
+                    as="button"
+                    tabIndex={-1}
+                    onClick={() => goToUser(user)}
+                  >
+                    <UserIcon user={user} showName />
+                  </DropdownItem>
+                </li>
+              ))}
+            </DropdownMenu>
+          </Dropdown>
+        }
+      />
     )
     )
   } else {
   } else {
     return (
     return (
       <div className="online-users">
       <div className="online-users">
         {onlineUsers.map((user, index) => (
         {onlineUsers.map((user, index) => (
-          <Tooltip
+          <OLTooltip
             key={`${user.user_id}_${index}`}
             key={`${user.user_id}_${index}`}
             id="online-user"
             id="online-user"
             description={user.name}
             description={user.name}
@@ -48,7 +89,7 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) {
               {/* OverlayTrigger won't fire unless UserIcon is wrapped in a span */}
               {/* OverlayTrigger won't fire unless UserIcon is wrapped in a span */}
               <UserIcon user={user} onClick={goToUser} />
               <UserIcon user={user} onClick={goToUser} />
             </span>
             </span>
-          </Tooltip>
+          </OLTooltip>
         ))}
         ))}
       </div>
       </div>
     )
     )
@@ -69,7 +110,7 @@ function UserIcon({ user, showName, onClick }) {
   const backgroundColor = `hsl(${getHueForUserId(user.user_id)}, 70%, 50%)`
   const backgroundColor = `hsl(${getHueForUserId(user.user_id)}, 70%, 50%)`
 
 
   function handleOnClick() {
   function handleOnClick() {
-    onClick(user)
+    onClick?.(user)
   }
   }
 
 
   const [character] = [...user.name]
   const [character] = [...user.name]
@@ -91,25 +132,30 @@ UserIcon.propTypes = {
     name: PropTypes.string.isRequired,
     name: PropTypes.string.isRequired,
   }),
   }),
   showName: PropTypes.bool,
   showName: PropTypes.bool,
-  onClick: PropTypes.func.isRequired,
+  onClick: PropTypes.func,
 }
 }
 
 
 const DropDownToggleButton = React.forwardRef((props, ref) => {
 const DropDownToggleButton = React.forwardRef((props, ref) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   return (
   return (
-    <Tooltip
+    <OLTooltip
       id="connected-users"
       id="connected-users"
       description={t('connected_users')}
       description={t('connected_users')}
       overlayProps={{ placement: 'left' }}
       overlayProps={{ placement: 'left' }}
     >
     >
       <button
       <button
-        className="btn online-user online-user-multi"
+        type="button"
+        className="online-user online-user-multi"
         onClick={props.onClick} // required by Bootstrap Dropdown to trigger an opening
         onClick={props.onClick} // required by Bootstrap Dropdown to trigger an opening
+        ref={ref}
       >
       >
-        <strong>{props.onlineUserCount}</strong>
-        <Icon type="users" fw />
+        <strong>{props.onlineUserCount}</strong>&nbsp;
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="users" fw />}
+          bs5={<MaterialIcon type="groups" />}
+        />
       </button>
       </button>
-    </Tooltip>
+    </OLTooltip>
   )
   )
 })
 })
 
 

+ 11 - 6
services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.tsx

@@ -1,8 +1,11 @@
 import { useEffect, useState, useRef } from 'react'
 import { useEffect, useState, useRef } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import classNames from 'classnames'
 import classNames from 'classnames'
-import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
+import OLFormControl from '@/features/ui/components/ol/ol-form-control'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import MaterialIcon from '@/shared/components/material-icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 
 type ProjectNameEditableLabelProps = {
 type ProjectNameEditableLabelProps = {
   projectName: string
   projectName: string
@@ -70,10 +73,9 @@ function ProjectNameEditableLabel({
         </span>
         </span>
       )}
       )}
       {isRenaming && (
       {isRenaming && (
-        <input
+        <OLFormControl
           ref={inputRef}
           ref={inputRef}
           type="text"
           type="text"
-          className="form-control"
           onKeyDown={handleKeyDown}
           onKeyDown={handleKeyDown}
           onChange={handleOnChange}
           onChange={handleOnChange}
           onBlur={handleBlur}
           onBlur={handleBlur}
@@ -81,16 +83,19 @@ function ProjectNameEditableLabel({
         />
         />
       )}
       )}
       {canRename && (
       {canRename && (
-        <Tooltip
+        <OLTooltip
           id="online-user"
           id="online-user"
           description={t('rename')}
           description={t('rename')}
           overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
           overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
         >
         >
           {/* eslint-disable-next-line jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */}
           {/* eslint-disable-next-line jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */}
           <a className="rename" role="button" onClick={startRenaming}>
           <a className="rename" role="button" onClick={startRenaming}>
-            <Icon type="pencil" fw />
+            <BootstrapVersionSwitcher
+              bs3={<Icon type="pencil" fw />}
+              bs5={<MaterialIcon type="edit" className="align-text-bottom" />}
+            />
           </a>
           </a>
-        </Tooltip>
+        </OLTooltip>
       )}
       )}
     </div>
     </div>
   )
   )

+ 7 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx

@@ -1,14 +1,19 @@
 import PropTypes from 'prop-types'
 import PropTypes from 'prop-types'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import Icon from '../../../shared/components/icon'
 import Icon from '../../../shared/components/icon'
+import MaterialIcon from '@/shared/components/material-icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 
 function ShareProjectButton({ onClick }) {
 function ShareProjectButton({ onClick }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   return (
   return (
     <div className="toolbar-item">
     <div className="toolbar-item">
-      <button className="btn btn-full-height" onClick={onClick}>
-        <Icon type="group" fw />
+      <button type="button" className="btn btn-full-height" onClick={onClick}>
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="group" fw />}
+          bs5={<MaterialIcon type="groups" className="align-middle" />}
+        />
         <p className="toolbar-label">{t('share')}</p>
         <p className="toolbar-label">{t('share')}</p>
       </button>
       </button>
     </div>
     </div>

+ 20 - 3
services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx

@@ -16,6 +16,7 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac
 import BackToEditorButton from './back-to-editor-button'
 import BackToEditorButton from './back-to-editor-button'
 import getMeta from '@/utils/meta'
 import getMeta from '@/utils/meta'
 import { isSplitTestEnabled } from '@/utils/splitTestUtils'
 import { isSplitTestEnabled } from '@/utils/splitTestUtils'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 
 const [publishModalModules] = importOverleafModules('publishModal')
 const [publishModalModules] = importOverleafModules('publishModal')
 const PublishButton = publishModalModules?.import.default
 const PublishButton = publishModalModules?.import.default
@@ -55,7 +56,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
 
 
   return (
   return (
     <header
     <header
-      className="toolbar toolbar-header toolbar-with-labels"
+      className="toolbar toolbar-header"
       role="navigation"
       role="navigation"
       aria-label={t('project_layout_sharing_submission')}
       aria-label={t('project_layout_sharing_submission')}
     >
     >
@@ -72,7 +73,16 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
             }
             }
           )}
           )}
       </div>
       </div>
-      {getMeta('ol-showUpgradePrompt') && <UpgradePrompt />}
+      {getMeta('ol-showUpgradePrompt') && (
+        <BootstrapVersionSwitcher
+          bs3={<UpgradePrompt />}
+          bs5={
+            <div className="d-flex align-items-center">
+              <UpgradePrompt />
+            </div>
+          }
+        />
+      )}
       <ProjectNameEditableLabel
       <ProjectNameEditableLabel
         className="toolbar-center"
         className="toolbar-center"
         projectName={projectName}
         projectName={projectName}
@@ -84,7 +94,14 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
         <OnlineUsersWidget onlineUsers={onlineUsers} goToUser={goToUser} />
         <OnlineUsersWidget onlineUsers={onlineUsers} goToUser={goToUser} />
 
 
         {historyIsOpen ? (
         {historyIsOpen ? (
-          <BackToEditorButton onClick={toggleHistoryOpen} />
+          <BootstrapVersionSwitcher
+            bs3={<BackToEditorButton onClick={toggleHistoryOpen} />}
+            bs5={
+              <div className="d-flex align-items-center">
+                <BackToEditorButton onClick={toggleHistoryOpen} />
+              </div>
+            }
+          />
         ) : (
         ) : (
           <>
           <>
             {trackChangesVisible && (
             {trackChangesVisible && (

+ 6 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx

@@ -15,7 +15,12 @@ function TrackChangesToggleButton({
 
 
   return (
   return (
     <div className="toolbar-item">
     <div className="toolbar-item">
-      <button disabled={disabled} className={classes} onMouseDown={onMouseDown}>
+      <button
+        type="button"
+        disabled={disabled}
+        className={classes}
+        onMouseDown={onMouseDown}
+      >
         <i className="review-icon" />
         <i className="review-icon" />
         <p className="toolbar-label">{t('review')}</p>
         <p className="toolbar-label">{t('review')}</p>
       </button>
       </button>

+ 11 - 3
services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx

@@ -1,5 +1,8 @@
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import * as eventTracking from '../../../infrastructure/event-tracking'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import { bsVersion } from '@/features/utils/bootstrap-5'
+import classnames from 'classnames'
 
 
 function UpgradePrompt() {
 function UpgradePrompt() {
   const { t } = useTranslation()
   const { t } = useTranslation()
@@ -10,14 +13,19 @@ function UpgradePrompt() {
   }
   }
 
 
   return (
   return (
-    <a
-      className="toolbar-header-upgrade-prompt btn btn-primary btn-xs"
+    <OLButton
+      variant="primary"
+      size="small"
+      className={classnames(
+        'toolbar-header-upgrade-prompt',
+        bsVersion({ bs3: 'btn-xs' })
+      )}
       href="/user/subscription/plans?itm_referrer=editor-header-upgrade-prompt"
       href="/user/subscription/plans?itm_referrer=editor-header-upgrade-prompt"
       target="_blank"
       target="_blank"
       onClick={handleClick}
       onClick={handleClick}
     >
     >
       {t('upgrade')}
       {t('upgrade')}
-    </a>
+    </OLButton>
   )
   )
 }
 }
 
 

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

@@ -99,7 +99,7 @@ const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, {
 
 
 export { ForwardReferredDropdownItem as DropdownItem }
 export { ForwardReferredDropdownItem as DropdownItem }
 
 
-export function DropdownToggle({ ...props }: DropdownToggleProps) {
+export function DropdownToggle(props: DropdownToggleProps) {
   return <BS5DropdownToggle {...props} />
   return <BS5DropdownToggle {...props} />
 }
 }
 
 

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

@@ -35,6 +35,7 @@ const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
       onChange: rest.onChange as BS3FormControlProps['onChange'],
       onChange: rest.onChange as BS3FormControlProps['onChange'],
       onKeyDown: rest.onKeyDown as BS3FormControlProps['onKeyDown'],
       onKeyDown: rest.onKeyDown as BS3FormControlProps['onKeyDown'],
       onFocus: rest.onFocus as BS3FormControlProps['onFocus'],
       onFocus: rest.onFocus as BS3FormControlProps['onFocus'],
+      onBlur: rest.onBlur as BS3FormControlProps['onBlur'],
       onInvalid: rest.onInvalid as BS3FormControlProps['onInvalid'],
       onInvalid: rest.onInvalid as BS3FormControlProps['onInvalid'],
       inputRef: (inputElement: HTMLInputElement) => {
       inputRef: (inputElement: HTMLInputElement) => {
         if (typeof ref === 'function') {
         if (typeof ref === 'function') {

+ 0 - 2
services/web/frontend/stories/ui/dropdown-menu.stories.tsx

@@ -60,8 +60,6 @@ export const Active = (args: Args) => {
 }
 }
 
 
 export const MultipleSelection = (args: Args) => {
 export const MultipleSelection = (args: Args) => {
-  console.log('DropdownItem.EmptyLeadingIcon', DropdownItem.EmptyLeadingIcon)
-
   return (
   return (
     <DropdownMenu show>
     <DropdownMenu show>
       <DropdownHeader>Header</DropdownHeader>
       <DropdownHeader>Header</DropdownHeader>

+ 2 - 0
services/web/frontend/stylesheets/app/editor/online-users.less

@@ -14,6 +14,8 @@
   }
   }
 
 
   .online-user-multi {
   .online-user-multi {
+    .reset-button;
+
     color: @toolbar-btn-color;
     color: @toolbar-btn-color;
     width: auto;
     width: auto;
     min-width: 24px;
     min-width: 24px;

+ 37 - 0
services/web/frontend/stylesheets/bootstrap-5/base/base.scss

@@ -2,3 +2,40 @@
   visibility: hidden;
   visibility: hidden;
   height: 0 !important; // Prevent layout shift
   height: 0 !important; // Prevent layout shift
 }
 }
+
+@keyframes bounce {
+  0%,
+  10%,
+  26%,
+  40% {
+    transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+    transform: translate3d(0, 0, 0);
+  }
+
+  20%,
+  21% {
+    transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+    transform: translate3d(0, -10px, 0);
+  }
+
+  35% {
+    transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+    transform: translate3d(0, -5px, 0);
+  }
+
+  45% {
+    transform: translate3d(0, -2px, 0);
+  }
+
+  50% {
+    transform: translate3d(0, 0, 0);
+  }
+}
+
+.bounce {
+  animation-duration: 2s;
+  animation-fill-mode: both;
+  animation-iteration-count: infinite;
+  animation-name: bounce;
+  transform-origin: center bottom;
+}

+ 4 - 0
services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss

@@ -17,6 +17,10 @@
   &.sm {
   &.sm {
     min-width: 160px;
     min-width: 160px;
   }
   }
+
+  .subdued {
+    color: var(--content-disabled);
+  }
 }
 }
 
 
 .dropdown-item {
 .dropdown-item {

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

@@ -2,7 +2,9 @@
 @import 'project-list';
 @import 'project-list';
 @import 'sidebar-v2-dash-pane';
 @import 'sidebar-v2-dash-pane';
 @import 'editor/ide';
 @import 'editor/ide';
+@import 'editor/toolbar';
+@import 'editor/online-users';
+@import 'editor/review-panel';
 @import 'editor/loading-screen';
 @import 'editor/loading-screen';
 @import 'editor/outline';
 @import 'editor/outline';
 @import 'editor/file-tree';
 @import 'editor/file-tree';
-@import 'editor/toolbar';

+ 33 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss

@@ -0,0 +1,33 @@
+:root {
+  --toolbar-btn-color: var(--white);
+}
+
+.online-users {
+  display: flex;
+  align-items: center;
+
+  .online-user {
+    display: inline-block;
+    width: 24px;
+    height: 24px;
+    line-height: 24px;
+    margin-right: var(--spacing-04);
+    text-align: center;
+    color: white;
+    text-transform: uppercase;
+    border-radius: var(--border-radius-base);
+    cursor: pointer;
+  }
+
+  .online-user-multi {
+    @include reset-button;
+
+    color: var(--toolbar-btn-color);
+    width: auto;
+    min-width: 24px;
+    padding-left: var(--spacing-04);
+    padding-right: var(--spacing-03);
+    display: flex;
+    align-items: center;
+  }
+}

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

@@ -0,0 +1,16 @@
+:root {
+  --review-icon: url('../../../../../public/img/ol-icons/review-icon-dark-theme.svg');
+}
+
+.review-icon {
+  display: inline-block;
+  background-image: var(--review-icon);
+  background-repeat: no-repeat;
+  background-position: top;
+  background-size: 30px;
+  width: 30px;
+
+  &::before {
+    content: '\00a0'; // Non-breakable space. A non-breakable character here makes this icon work like font-awesome.
+  }
+}

+ 223 - 4
services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss

@@ -1,8 +1,227 @@
-$toolbar-height: 40px;
+:root {
+  --toolbar-border-color: var(--neutral-80);
+  --toolbar-header-bg-color: var(--neutral-90);
+  --toolbar-header-btn-border-color: var(--neutral-80);
+  --toolbar-btn-color: var(--white);
+  --toolbar-btn-hover-bg-color: var(--neutral-80);
+  --project-name-color: var(--neutral-40);
+  --project-rename-link-color: var(--neutral-40);
+  --project-rename-link-color-hover: var(--neutral-20);
+  --editor-header-logo-background: url(../../../../../public/img/ol-brand/overleaf-o-white.svg)
+    center / contain no-repeat;
+}
 
 
 .toolbar {
 .toolbar {
+  --toolbar-height: 40px;
+
   display: flex;
   display: flex;
-  align-items: center;
-  height: $toolbar-height;
-  border-bottom: 1px solid var(--border-divider-dark);
+  align-items: stretch;
+  height: var(--toolbar-height);
+  min-height: var(--toolbar-height);
+  border-bottom: 1px solid var(--toolbar-border-color);
+
+  button {
+    position: relative;
+
+    .badge {
+      position: absolute;
+      top: 0;
+      right: 0;
+      pointer-events: none; // Labels were capturing button/anchor clicks.
+
+      .badge-content {
+        padding: 0;
+      }
+    }
+  }
+
+  .toolbar-right,
+  .toolbar-left {
+    button {
+      background: transparent;
+      box-shadow: none;
+    }
+  }
+
+  .toolbar-right .back-to-editor-btn {
+    margin-right: var(--spacing-09);
+  }
+
+  a.btn-full-height,
+  button.btn-full-height {
+    border: none;
+    border-radius: 0;
+    border-right: 1px solid var(--toolbar-header-btn-border-color) !important;
+    color: var(--toolbar-btn-color);
+    padding: var(--spacing-02) var(--spacing-05) var(--spacing-03);
+    font-size: var(--font-size-05);
+    max-height: 39px;
+
+    &:hover {
+      text-shadow: none;
+      background-color: var(--toolbar-btn-hover-bg-color);
+      color: var(--toolbar-btn-color);
+    }
+
+    &.active,
+    &:active {
+      color: var(--white);
+      background-color: $bg-accent-01;
+      box-shadow: none;
+    }
+
+    .badge {
+      top: var(--spacing-01);
+      right: var(--spacing-02);
+    }
+
+    &.header-cobranding-logo-container {
+      height: calc(var(--toolbar-height) - 1px);
+      padding: var(--spacing-04) var(--spacing-05);
+    }
+
+    .spinner-border {
+      vertical-align: middle;
+      font-size: var(--font-size-02);
+    }
+
+    &.dropdown-toggle {
+      line-height: 1;
+
+      &::after {
+        vertical-align: middle;
+        font-size: var(--font-size-02);
+      }
+    }
+  }
+
+  .btn-full-height-no-border {
+    border-right: 0;
+    border-left: 0;
+  }
+
+  .toolbar-left {
+    display: flex;
+    align-items: stretch;
+    float: left;
+    text-align: center;
+  }
+
+  .toolbar-right {
+    display: flex;
+    align-items: stretch;
+    flex-grow: 1;
+    justify-content: flex-end;
+
+    .btn-full-height {
+      border-right: 0;
+      border-left: 1px solid var(--toolbar-header-btn-border-color);
+    }
+  }
+
+  .toolbar-center {
+    text-align: center;
+    text-overflow: ellipsis;
+    overflow: hidden;
+
+    // At small screen sizes, center relative to the left menu and right buttons
+    width: 100%;
+    display: flex;
+    justify-content: center;
+  }
+
+  &.toolbar-header {
+    background-color: var(--toolbar-header-bg-color);
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 1;
+  }
+
+  .toolbar-item {
+    display: flex;
+  }
+}
+
+.header-cobranding-logo {
+  display: block;
+  width: auto;
+  max-height: 100%;
+}
+
+.toolbar-label {
+  display: none;
+  margin: 0 var(--spacing-02);
+  font-size: var(--font-size-02);
+  font-weight: 600;
+  vertical-align: middle;
+  text-align: left;
+
+  @include media-breakpoint-up(lg) {
+    display: inline-block;
+  }
+
+  &.toolbar-label-multiline {
+    line-height: 1.1;
+  }
+}
+
+.toolbar-header-upgrade-prompt {
+  margin-left: var(--spacing-05);
+
+  @include media-breakpoint-down(lg) {
+    display: none !important;
+  }
+}
+
+.editor-menu-icon {
+  &.material-symbols {
+    width: 1em;
+    text-indent: -9999px;
+    background: var(--editor-header-logo-background);
+  }
+}
+
+.project-name {
+  .name {
+    display: inline-block;
+
+    @include text-truncate;
+
+    padding: var(--spacing-03);
+    vertical-align: top;
+    color: var(--project-name-color);
+    font-weight: 700;
+  }
+
+  input {
+    height: 30px;
+    margin-top: var(--spacing-02);
+    padding: var(--spacing-03);
+    max-width: 500px;
+    text-align: center;
+    font-weight: 700;
+  }
+
+  a.rename {
+    visibility: hidden;
+    display: inline-block;
+    color: var(--project-rename-link-color);
+    padding: var(--spacing-03);
+    border-radius: var(--border-radius-base);
+    cursor: pointer;
+
+    &:hover {
+      text-shadow: 0 1px 0 rgb(0 0 0 / 25%);
+      color: var(--project-rename-link-color-hover);
+      text-decoration: none;
+    }
+  }
+
+  &:hover {
+    a.rename {
+      visibility: visible;
+    }
+  }
 }
 }