diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx
index 97f77f1be5..537f8aa2b2 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx
@@ -1,20 +1,20 @@
import { useTranslation } from 'react-i18next'
-import { Button } from 'react-bootstrap'
import MaterialIcon from '@/shared/components/material-icon'
+import OLButton from '@/features/ui/components/ol/ol-button'
function BackToEditorButton({ onClick }: { onClick: () => void }) {
const { t } = useTranslation()
return (
-
+ {t('back_to_editor')}
+
)
}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx
index 9fb9968d19..b6139820ca 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx
@@ -1,13 +1,15 @@
import { useTranslation } from 'react-i18next'
-import Tooltip from '../../../shared/components/tooltip'
import Icon from '../../../shared/components/icon'
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() {
const { t } = useTranslation()
return (
-
-
+ }
+ bs5={
+
+ }
/>
-
+
)
}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx
index 7835f2d73b..ee1926e1bf 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx
+++ b/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 { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
@@ -16,11 +19,25 @@ function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) {
return (
-
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx
index bd135c17e2..4c7632fe8c 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.jsx
@@ -1,14 +1,19 @@
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
return (
-
-
+
+ }
+ bs5={}
+ />
{t('history')}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx
index 60449e6528..e9e45763bb 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx
+++ b/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 Tooltip from '../../../shared/components/tooltip'
import Icon from '../../../shared/components/icon'
@@ -13,6 +24,8 @@ import {
import * as eventTracking from '../../../infrastructure/event-tracking'
import useEventListener from '../../../shared/hooks/use-event-listener'
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() {
return
@@ -42,7 +55,7 @@ function IconPdfOnly() {
return
}
-function IconCheckmark({
+const isActiveDropdownItem = ({
iconFor,
pdfLayout,
view,
@@ -52,23 +65,26 @@ function IconCheckmark({
pdfLayout: IdeLayout
view: IdeView | null
detachRole?: DetachRole
-}) {
+}) => {
if (detachRole === 'detacher' || view === 'history') {
- return
+ return false
}
if (
iconFor === 'editorOnly' &&
pdfLayout === 'flat' &&
(view === 'editor' || view === 'file')
) {
- return
+ return true
} else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
- return
+ return true
} else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
- return
+ return true
}
- // return empty icon for placeholder
- return
+ return false
+}
+
+function IconCheckmark(props: Parameters[0]) {
+ return isActiveDropdownItem(props) ? :
}
function LayoutMenuItem({
@@ -94,6 +110,35 @@ function LayoutMenuItem({
)
}
+function EnhancedDropdownItem({
+ active,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const LayoutDropdownToggleButton = forwardRef<
+ HTMLButtonElement,
+ {
+ onClick: (e: React.MouseEvent) => void
+ }
+>(({ onClick, ...props }, ref) => {
+ const handleClick = (e: React.MouseEvent) => {
+ eventTracking.sendMB('navigation-clicked-layout')
+ onClick(e)
+ }
+
+ return
+})
+LayoutDropdownToggleButton.displayName = 'LayoutDropdownToggleButton'
+
function DetachDisabled() {
const { t } = useTranslation()
@@ -164,97 +209,209 @@ function LayoutDropdownButton() {
{t('layout_processing')}
)}
- {
- eventTracking.sendMB('navigation-clicked-layout')
- }}
- className="toolbar-item layout-dropdown"
- pullRight
- >
-
- {processing ? : }
- {t('layout')}
-
-
- handleChangeLayout('sideBySide')}
- checkmark={
- {
+ eventTracking.sendMB('navigation-clicked-layout')
+ }}
+ className="toolbar-item layout-dropdown"
+ pullRight
+ >
+
+ {processing ? : }
+ {t('layout')}
+
+
+ handleChangeLayout('sideBySide')}
+ checkmark={
+
+ }
+ icon={}
+ text={t('editor_and_pdf')}
/>
- }
- icon={}
- text={t('editor_and_pdf')}
- />
- handleChangeLayout('flat', 'editor')}
- checkmark={
- handleChangeLayout('flat', 'editor')}
+ checkmark={
+
+ }
+ icon={}
+ text={
+ ,
+ ]}
+ />
+ }
/>
- }
- icon={}
- text={
- ,
- ]}
- />
- }
- />
- handleChangeLayout('flat', 'pdf')}
- checkmark={
- handleChangeLayout('flat', 'pdf')}
+ checkmark={
+
+ }
+ icon={}
+ text={
+ ,
+ ]}
+ />
+ }
/>
- }
- icon={}
- text={
- ,
- ]}
- />
- }
- />
- {'BroadcastChannel' in window ? (
- handleDetach()}
- checkmark={
- detachRole === 'detacher' ? (
- detachIsLinked ? (
-
- ) : (
-
- )
- ) : (
-
- )
- }
- icon={}
- text={t('pdf_in_separate_tab')}
- />
- ) : (
-
- )}
-
-
+ {'BroadcastChannel' in window ? (
+ handleDetach()}
+ checkmark={
+ detachRole === 'detacher' ? (
+ detachIsLinked ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )
+ }
+ icon={}
+ text={t('pdf_in_separate_tab')}
+ />
+ ) : (
+
+ )}
+
+
+ }
+ bs5={
+
+
+ {processing ? (
+
+ ) : (
+
+ )}
+ {t('layout')}
+
+
+ handleChangeLayout('sideBySide')}
+ active={isActiveDropdownItem({
+ iconFor: 'sideBySide',
+ pdfLayout,
+ view,
+ detachRole,
+ })}
+ leadingIcon="dock_to_right"
+ >
+ {t('editor_and_pdf')}
+
+
+ handleChangeLayout('flat', 'editor')}
+ active={isActiveDropdownItem({
+ iconFor: 'editorOnly',
+ pdfLayout,
+ view,
+ detachRole,
+ })}
+ leadingIcon="code"
+ >
+ ,
+ ]}
+ />
+
+
+ handleChangeLayout('flat', 'pdf')}
+ active={isActiveDropdownItem({
+ iconFor: 'pdfOnly',
+ pdfLayout,
+ view,
+ detachRole,
+ })}
+ leadingIcon="picture_as_pdf"
+ >
+ ,
+ ]}
+ />
+
+
+ {'BroadcastChannel' in window ? (
+ handleDetach()}
+ active={detachRole === 'detacher' && detachIsLinked}
+ trailingIcon={
+ detachRole === 'detacher' ? (
+ detachIsLinked ? (
+ 'check'
+ ) : (
+
+
+
+ {t('loading')}
+
+
+ )
+ ) : null
+ }
+ leadingIcon="select_window"
+ >
+ {t('pdf_in_separate_tab')}
+
+ ) : (
+
+ )}
+
+
+ }
+ />
>
)
}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx
index c7b860fcb6..a4d431dea5 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx
@@ -1,14 +1,24 @@
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
return (
-
-
+
+ }
+ bs5={
+
+ }
+ />
{t('menu')}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx
index 0b2beba5eb..e1c32bcc85 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx
@@ -1,11 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
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 { getHueForUserId } from '../../../shared/utils/colors'
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 }) {
const { t } = useTranslation()
@@ -14,31 +23,63 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) {
if (shouldDisplayDropdown) {
return (
-
-
-
-
- {onlineUsers.map((user, index) => (
-
- ))}
-
-
+
+
+
+
+ {onlineUsers.map((user, index) => (
+
+ ))}
+
+
+ }
+ bs5={
+
+
+
+
+ {t('connected_users')}
+
+ {onlineUsers.map((user, index) => (
+
+ goToUser(user)}
+ >
+
+
+
+ ))}
+
+
+ }
+ />
)
} else {
return (
{onlineUsers.map((user, index) => (
-
-
+
))}
)
@@ -69,7 +110,7 @@ function UserIcon({ user, showName, onClick }) {
const backgroundColor = `hsl(${getHueForUserId(user.user_id)}, 70%, 50%)`
function handleOnClick() {
- onClick(user)
+ onClick?.(user)
}
const [character] = [...user.name]
@@ -91,25 +132,30 @@ UserIcon.propTypes = {
name: PropTypes.string.isRequired,
}),
showName: PropTypes.bool,
- onClick: PropTypes.func.isRequired,
+ onClick: PropTypes.func,
}
const DropDownToggleButton = React.forwardRef((props, ref) => {
const { t } = useTranslation()
return (
-
- {props.onlineUserCount}
-
+ {props.onlineUserCount}
+ }
+ bs5={}
+ />
-
+
)
})
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.tsx
index 78e9a41d7f..3d15cfad8a 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.tsx
+++ b/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 { useTranslation } from 'react-i18next'
import classNames from 'classnames'
-import Tooltip from '../../../shared/components/tooltip'
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 = {
projectName: string
@@ -70,10 +73,9 @@ function ProjectNameEditableLabel({
)}
{isRenaming && (
-
)}
{canRename && (
-
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */}
-
+ }
+ bs5={}
+ />
-
+
)}
)
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx
index 6a6a26a182..2644d142b3 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx
@@ -1,14 +1,19 @@
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
return (
-
-
+
+ }
+ bs5={}
+ />
{t('share')}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx
index 75247388b9..071d51e37c 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx
+++ b/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 getMeta from '@/utils/meta'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
const [publishModalModules] = importOverleafModules('publishModal')
const PublishButton = publishModalModules?.import.default
@@ -55,7 +56,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
return (
@@ -72,7 +73,16 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
}
)}
- {getMeta('ol-showUpgradePrompt') && }
+ {getMeta('ol-showUpgradePrompt') && (
+ }
+ bs5={
+
+
+
+ }
+ />
+ )}
{historyIsOpen ? (
-
+ }
+ bs5={
+
+
+
+ }
+ />
) : (
<>
{trackChangesVisible && (
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx
index ce838c428c..ae917fd408 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx
@@ -15,7 +15,12 @@ function TrackChangesToggleButton({
return (
-
+
{t('review')}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx
index 7e08d3145f..a214b704f9 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx
@@ -1,5 +1,8 @@
import { useTranslation } from 'react-i18next'
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() {
const { t } = useTranslation()
@@ -10,14 +13,19 @@ function UpgradePrompt() {
}
return (
-
{t('upgrade')}
-
+
)
}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
index e759d8f7f9..584dae9485 100644
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
+++ b/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 function DropdownToggle({ ...props }: DropdownToggleProps) {
+export function DropdownToggle(props: DropdownToggleProps) {
return
}
diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
index c145f3bde1..1b9ea18ed8 100644
--- a/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
+++ b/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
@@ -35,6 +35,7 @@ const OLFormControl = forwardRef(
onChange: rest.onChange as BS3FormControlProps['onChange'],
onKeyDown: rest.onKeyDown as BS3FormControlProps['onKeyDown'],
onFocus: rest.onFocus as BS3FormControlProps['onFocus'],
+ onBlur: rest.onBlur as BS3FormControlProps['onBlur'],
onInvalid: rest.onInvalid as BS3FormControlProps['onInvalid'],
inputRef: (inputElement: HTMLInputElement) => {
if (typeof ref === 'function') {
diff --git a/services/web/frontend/stories/ui/dropdown-menu.stories.tsx b/services/web/frontend/stories/ui/dropdown-menu.stories.tsx
index fe7208f653..6766b6905d 100644
--- a/services/web/frontend/stories/ui/dropdown-menu.stories.tsx
+++ b/services/web/frontend/stories/ui/dropdown-menu.stories.tsx
@@ -60,8 +60,6 @@ export const Active = (args: Args) => {
}
export const MultipleSelection = (args: Args) => {
- console.log('DropdownItem.EmptyLeadingIcon', DropdownItem.EmptyLeadingIcon)
-
return (
Header
diff --git a/services/web/frontend/stylesheets/app/editor/online-users.less b/services/web/frontend/stylesheets/app/editor/online-users.less
index 91084765f4..c699913693 100644
--- a/services/web/frontend/stylesheets/app/editor/online-users.less
+++ b/services/web/frontend/stylesheets/app/editor/online-users.less
@@ -14,6 +14,8 @@
}
.online-user-multi {
+ .reset-button;
+
color: @toolbar-btn-color;
width: auto;
min-width: 24px;
diff --git a/services/web/frontend/stylesheets/bootstrap-5/base/base.scss b/services/web/frontend/stylesheets/bootstrap-5/base/base.scss
index 64ae509a40..a18f536bae 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/base/base.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/base/base.scss
@@ -2,3 +2,40 @@
visibility: hidden;
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;
+}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss b/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss
index 1cd660c686..1b37e6ed3a 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss
@@ -17,6 +17,10 @@
&.sm {
min-width: 160px;
}
+
+ .subdued {
+ color: var(--content-disabled);
+ }
}
.dropdown-item {
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
index 2d3ebd68df..8f60ef5237 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
@@ -2,7 +2,9 @@
@import 'project-list';
@import 'sidebar-v2-dash-pane';
@import 'editor/ide';
+@import 'editor/toolbar';
+@import 'editor/online-users';
+@import 'editor/review-panel';
@import 'editor/loading-screen';
@import 'editor/outline';
@import 'editor/file-tree';
-@import 'editor/toolbar';
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss
new file mode 100644
index 0000000000..c67e6da3b4
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel.scss
new file mode 100644
index 0000000000..d7ff2e93c2
--- /dev/null
+++ b/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.
+ }
+}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
index d4281055c1..3b52ed15b7 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
+++ b/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-height: 40px;
+
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;
+ }
+ }
}