فهرست منبع

Merge pull request #20708 from overleaf/ii-bs5-pdf-toolbar

[web] BS5 pdf toolbar

GitOrigin-RevId: a04091c9e936e52f47c3977b3149ffe613d43bb9
ilkin-overleaf 1 سال پیش
والد
کامیت
92eade7502
54فایلهای تغییر یافته به همراه1386 افزوده شده و 597 حذف شده
  1. 1 1
      services/web/app/views/project/ide-react-detached.pug
  2. 1 1
      services/web/app/views/project/ide-react.pug
  3. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-editor-button.tsx
  4. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx
  5. 38 15
      services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx
  6. 0 179
      services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.jsx
  7. 366 0
      services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.tsx
  8. 16 7
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.jsx
  9. 26 15
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.tsx
  10. 19 13
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx
  11. 5 10
      services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.jsx
  12. 3 3
      services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx
  13. 18 5
      services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx
  14. 64 21
      services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx
  15. 8 9
      services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx
  16. 22 17
      services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx
  17. 3 3
      services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx
  18. 218 86
      services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx
  19. 1 1
      services/web/frontend/js/features/pdf-preview/components/switch-to-editor-button.tsx
  20. 7 7
      services/web/frontend/js/features/project-list/components/table/project-tools/project-tools.tsx
  21. 1 1
      services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx
  22. 1 1
      services/web/frontend/js/features/settings/components/emails/actions/remove.tsx
  23. 1 1
      services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx
  24. 1 1
      services/web/frontend/js/features/settings/components/emails/row.tsx
  25. 1 1
      services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/access-levels-changed.tsx
  26. 1 1
      services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx
  27. 2 2
      services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx
  28. 1 1
      services/web/frontend/js/features/source-editor/components/switch-to-pdf-button.jsx
  29. 1 1
      services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx
  30. 3 3
      services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx
  31. 21 23
      services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx
  32. 0 51
      services/web/frontend/js/features/ui/components/bootstrap-5/split-button.tsx
  33. 2 2
      services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx
  34. 48 30
      services/web/frontend/js/features/ui/components/ol/ol-button.tsx
  35. 4 1
      services/web/frontend/js/features/ui/components/ol/ol-tooltip.tsx
  36. 7 3
      services/web/frontend/js/features/ui/components/types/button-props.ts
  37. 7 2
      services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts
  38. 0 29
      services/web/frontend/js/features/ui/components/types/split-button-props.ts
  39. 1 1
      services/web/frontend/js/shared/components/copy-to-clipboard.tsx
  40. 1 1
      services/web/frontend/js/shared/hooks/use-dropdown.ts
  41. 1 1
      services/web/frontend/stories/start-free-trial-button.stories.jsx
  42. 0 5
      services/web/frontend/stories/ui/badge-bs5.stories.tsx
  43. 47 20
      services/web/frontend/stories/ui/split-button.stories.tsx
  44. 0 4
      services/web/frontend/stylesheets/app/editor/pdf.less
  45. 19 2
      services/web/frontend/stylesheets/bootstrap-5/abstracts/mixins.scss
  46. 0 1
      services/web/frontend/stylesheets/bootstrap-5/components/all.scss
  47. 2 2
      services/web/frontend/stylesheets/bootstrap-5/components/button-group.scss
  48. 1 0
      services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss
  49. 0 9
      services/web/frontend/stylesheets/bootstrap-5/components/split-button.scss
  50. 2 0
      services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
  51. 31 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/compile-button.scss
  52. 357 0
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss
  53. 3 1
      services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
  54. 1 1
      services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx

+ 1 - 1
services/web/app/views/project/ide-react-detached.pug

@@ -1,4 +1,4 @@
-extends ../layout-marketing
+extends ../layout-react
 
 block entrypointVar
 	- entrypoint = 'ide-detached'

+ 1 - 1
services/web/app/views/project/ide-react.pug

@@ -1,4 +1,4 @@
-extends ../layout-marketing
+extends ../layout-react
 
 block vars
 	- var suppressNavbar = true

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

@@ -8,7 +8,7 @@ function BackToEditorButton({ onClick }: { onClick: () => void }) {
   return (
     <OLButton
       variant="secondary"
-      size="small"
+      size="sm"
       onClick={onClick}
       className="back-to-editor-btn"
     >

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

@@ -15,7 +15,7 @@ function UpgradePrompt() {
   return (
     <OLButton
       variant="primary"
-      size="small"
+      size="sm"
       className={classnames(
         'toolbar-header-upgrade-prompt',
         bsVersion({ bs3: 'btn-xs' })

+ 38 - 15
services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx

@@ -1,10 +1,12 @@
 import { useTranslation } from 'react-i18next'
 import { memo } from 'react'
-import { Button } from 'react-bootstrap'
-import classNames from 'classnames'
+import classnames from 'classnames'
 import Icon from '../../../shared/components/icon'
 import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
-import Tooltip from '../../../shared/components/tooltip'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import { bsVersion } from '@/features/utils/bootstrap-5'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
 
@@ -21,28 +23,49 @@ function DetachCompileButton() {
   )
 
   return (
-    <div className="detach-compile-button-container">
-      <Tooltip
+    <div
+      className={classnames(
+        'detach-compile-button-container',
+        bsVersion({ bs5: 'ms-1' })
+      )}
+    >
+      <OLTooltip
         id="detach-compile"
         description={tooltipElement}
         tooltipProps={{ className: 'keyboard-tooltip' }}
-        overlayProps={{ delayShow: 500 }}
+        overlayProps={{ delay: { show: 500, hide: 0 } }}
       >
-        <Button
-          bsStyle="primary"
+        <OLButton
+          variant="primary"
           onClick={() => startCompile()}
           disabled={compiling}
-          className={classNames('detach-compile-button', {
+          className={classnames('detach-compile-button', {
             'btn-striped-animated': hasChanges,
             'detach-compile-button-disabled': compiling,
           })}
+          size="sm"
+          isLoading={compiling}
+          bs3Props={{
+            loading: (
+              <>
+                <Icon type="refresh" spin={compiling} />
+                <span className="detach-compile-button-label">
+                  {compileButtonLabel}
+                </span>
+              </>
+            ),
+          }}
         >
-          <Icon type="refresh" spin={compiling} />
-          <span className="detach-compile-button-label">
-            {compileButtonLabel}
-          </span>
-        </Button>
-      </Tooltip>
+          <BootstrapVersionSwitcher
+            bs3={
+              <span className="detach-compile-button-label">
+                {compileButtonLabel}
+              </span>
+            }
+            bs5={compileButtonLabel}
+          />
+        </OLButton>
+      </OLTooltip>
     </div>
   )
 }

+ 0 - 179
services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.jsx

@@ -1,179 +0,0 @@
-import { useTranslation } from 'react-i18next'
-import { memo } from 'react'
-import classNames from 'classnames'
-import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
-import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
-import SplitMenu from '../../../shared/components/split-menu'
-import Icon from '../../../shared/components/icon'
-import * as eventTracking from '../../../infrastructure/event-tracking'
-
-const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
-
-function sendEventAndSet(value, setter, settingName) {
-  eventTracking.sendMB('recompile-setting-changed', {
-    setting: settingName,
-    settingVal: value,
-  })
-  setter(value)
-}
-
-function PdfCompileButton() {
-  const {
-    animateCompileDropdownArrow,
-    autoCompile,
-    compiling,
-    draft,
-    hasChanges,
-    setAnimateCompileDropdownArrow,
-    setAutoCompile,
-    setDraft,
-    setStopOnValidationError,
-    stopOnFirstError,
-    stopOnValidationError,
-    startCompile,
-    stopCompile,
-    recompileFromScratch,
-  } = useCompileContext()
-  const { enableStopOnFirstError, disableStopOnFirstError } =
-    useStopOnFirstError({ eventSource: 'dropdown' })
-
-  const { t } = useTranslation()
-
-  const fromScratchWithEvent = () => {
-    eventTracking.sendMB('recompile-setting-changed', {
-      setting: 'from-scratch',
-    })
-    recompileFromScratch()
-  }
-
-  const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
-  const tooltipElement = (
-    <>
-      {t('recompile_pdf')}{' '}
-      <span className="keyboard-shortcut">({modifierKey} + Enter)</span>
-    </>
-  )
-
-  const dropdownToggleClassName = classNames({
-    'detach-compile-button-animate': animateCompileDropdownArrow,
-    'btn-striped-animated': hasChanges,
-    'no-left-border': true,
-  })
-
-  const buttonClassName = classNames({
-    'btn-striped-animated': hasChanges,
-    'no-left-radius': true,
-  })
-
-  return (
-    <SplitMenu
-      bsStyle="primary"
-      bsSize="xs"
-      disabled={compiling}
-      button={{
-        tooltip: {
-          description: tooltipElement,
-          id: 'compile',
-          tooltipProps: { className: 'keyboard-tooltip' },
-          overlayProps: { delayShow: 500 },
-        },
-        icon: { type: 'refresh', spin: compiling },
-        onClick: () => startCompile(),
-        text: compileButtonLabel,
-        className: buttonClassName,
-      }}
-      dropdownToggle={{
-        'aria-label': t('toggle_compile_options_menu'),
-        handleAnimationEnd: () => setAnimateCompileDropdownArrow(false),
-        className: dropdownToggleClassName,
-      }}
-      dropdown={{
-        id: 'pdf-recompile-dropdown',
-      }}
-    >
-      <SplitMenu.Item header>{t('auto_compile')}</SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() => sendEventAndSet(true, setAutoCompile, 'auto-compile')}
-      >
-        <Icon type={autoCompile ? 'check' : ''} fw />
-        {t('on')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() => sendEventAndSet(false, setAutoCompile, 'auto-compile')}
-      >
-        <Icon type={!autoCompile ? 'check' : ''} fw />
-        {t('off')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item header>{t('compile_mode')}</SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() => sendEventAndSet(false, setDraft, 'compile-mode')}
-      >
-        <Icon type={!draft ? 'check' : ''} fw />
-        {t('normal')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() => sendEventAndSet(true, setDraft, 'compile-mode')}
-      >
-        <Icon type={draft ? 'check' : ''} fw />
-        {t('fast')} <span className="subdued">[draft]</span>
-      </SplitMenu.Item>
-
-      <SplitMenu.Item header>Syntax Checks</SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() =>
-          sendEventAndSet(true, setStopOnValidationError, 'syntax-check')
-        }
-      >
-        <Icon type={stopOnValidationError ? 'check' : ''} fw />
-        {t('stop_on_validation_error')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={() =>
-          sendEventAndSet(false, setStopOnValidationError, 'syntax-check')
-        }
-      >
-        <Icon type={!stopOnValidationError ? 'check' : ''} fw />
-        {t('ignore_validation_errors')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item header>{t('compile_error_handling')}</SplitMenu.Item>
-
-      <SplitMenu.Item onSelect={enableStopOnFirstError}>
-        <Icon type={stopOnFirstError ? 'check' : ''} fw />
-        {t('stop_on_first_error')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item onSelect={disableStopOnFirstError}>
-        <Icon type={!stopOnFirstError ? 'check' : ''} fw />
-        {t('try_to_compile_despite_errors')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item divider />
-
-      <SplitMenu.Item
-        onSelect={() => stopCompile()}
-        disabled={!compiling}
-        aria-disabled={!compiling}
-      >
-        {t('stop_compile')}
-      </SplitMenu.Item>
-
-      <SplitMenu.Item
-        onSelect={fromScratchWithEvent}
-        disabled={compiling}
-        aria-disabled={compiling}
-      >
-        {t('recompile_from_scratch')}
-      </SplitMenu.Item>
-    </SplitMenu>
-  )
-}
-
-export default memo(PdfCompileButton)

+ 366 - 0
services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.tsx

@@ -0,0 +1,366 @@
+import { useTranslation } from 'react-i18next'
+import { memo } from 'react'
+import classNames from 'classnames'
+import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
+import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
+import SplitMenu from '../../../shared/components/split-menu'
+import Icon from '../../../shared/components/icon'
+import * as eventTracking from '../../../infrastructure/event-tracking'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import { Spinner } from 'react-bootstrap-5'
+import {
+  Dropdown,
+  DropdownDivider,
+  DropdownHeader,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
+import { bsVersion } from '@/features/utils/bootstrap-5'
+
+const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
+
+function sendEventAndSet<T extends boolean>(
+  value: T,
+  setter: (value: T) => void,
+  settingName: string
+) {
+  eventTracking.sendMB('recompile-setting-changed', {
+    setting: settingName,
+    settingVal: value,
+  })
+  setter(value)
+}
+
+function PdfCompileButton() {
+  const {
+    animateCompileDropdownArrow,
+    autoCompile,
+    compiling,
+    draft,
+    hasChanges,
+    setAnimateCompileDropdownArrow,
+    setAutoCompile,
+    setDraft,
+    setStopOnValidationError,
+    stopOnFirstError,
+    stopOnValidationError,
+    startCompile,
+    stopCompile,
+    recompileFromScratch,
+  } = useCompileContext()
+  const { enableStopOnFirstError, disableStopOnFirstError } =
+    useStopOnFirstError({ eventSource: 'dropdown' })
+
+  const { t } = useTranslation()
+
+  const fromScratchWithEvent = () => {
+    eventTracking.sendMB('recompile-setting-changed', {
+      setting: 'from-scratch',
+    })
+    recompileFromScratch()
+  }
+
+  const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
+  const tooltipElement = (
+    <>
+      {t('recompile_pdf')}{' '}
+      <span className="keyboard-shortcut">({modifierKey} + Enter)</span>
+    </>
+  )
+
+  const dropdownToggleClassName = classNames(
+    {
+      'detach-compile-button-animate': animateCompileDropdownArrow,
+      'btn-striped-animated': hasChanges,
+      'no-left-border': true,
+    },
+    bsVersion({ bs5: 'dropdown-button-toggle' })
+  )
+
+  const buttonClassName = classNames({
+    'btn-striped-animated': hasChanges,
+    'no-left-radius': true,
+  })
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={
+        <SplitMenu
+          bsStyle="primary"
+          bsSize="xs"
+          disabled={compiling}
+          button={{
+            tooltip: {
+              description: tooltipElement,
+              id: 'compile',
+              tooltipProps: { className: 'keyboard-tooltip' },
+              overlayProps: { delayShow: 500 },
+            },
+            icon: { type: 'refresh', spin: compiling },
+            onClick: () => startCompile(),
+            text: compileButtonLabel,
+            className: buttonClassName,
+          }}
+          dropdownToggle={{
+            'aria-label': t('toggle_compile_options_menu'),
+            handleAnimationEnd: () => setAnimateCompileDropdownArrow(false),
+            className: dropdownToggleClassName,
+          }}
+          dropdown={{
+            id: 'pdf-recompile-dropdown',
+          }}
+        >
+          <SplitMenu.Item header>{t('auto_compile')}</SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() =>
+              sendEventAndSet(true, setAutoCompile, 'auto-compile')
+            }
+          >
+            <Icon type={autoCompile ? 'check' : ''} fw />
+            {t('on')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() =>
+              sendEventAndSet(false, setAutoCompile, 'auto-compile')
+            }
+          >
+            <Icon type={!autoCompile ? 'check' : ''} fw />
+            {t('off')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item header>{t('compile_mode')}</SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() => sendEventAndSet(false, setDraft, 'compile-mode')}
+          >
+            <Icon type={!draft ? 'check' : ''} fw />
+            {t('normal')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() => sendEventAndSet(true, setDraft, 'compile-mode')}
+          >
+            <Icon type={draft ? 'check' : ''} fw />
+            {t('fast')} <span className="subdued">[draft]</span>
+          </SplitMenu.Item>
+
+          <SplitMenu.Item header>Syntax Checks</SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() =>
+              sendEventAndSet(true, setStopOnValidationError, 'syntax-check')
+            }
+          >
+            <Icon type={stopOnValidationError ? 'check' : ''} fw />
+            {t('stop_on_validation_error')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={() =>
+              sendEventAndSet(false, setStopOnValidationError, 'syntax-check')
+            }
+          >
+            <Icon type={!stopOnValidationError ? 'check' : ''} fw />
+            {t('ignore_validation_errors')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item header>{t('compile_error_handling')}</SplitMenu.Item>
+
+          <SplitMenu.Item onSelect={enableStopOnFirstError}>
+            <Icon type={stopOnFirstError ? 'check' : ''} fw />
+            {t('stop_on_first_error')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item onSelect={disableStopOnFirstError}>
+            <Icon type={!stopOnFirstError ? 'check' : ''} fw />
+            {t('try_to_compile_despite_errors')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item divider />
+
+          <SplitMenu.Item
+            onSelect={() => stopCompile()}
+            disabled={!compiling}
+            aria-disabled={!compiling}
+          >
+            {t('stop_compile')}
+          </SplitMenu.Item>
+
+          <SplitMenu.Item
+            onSelect={fromScratchWithEvent}
+            disabled={compiling}
+            aria-disabled={compiling}
+          >
+            {t('recompile_from_scratch')}
+          </SplitMenu.Item>
+        </SplitMenu>
+      }
+      bs5={
+        <Dropdown as={OLButtonGroup} autoClose="outside">
+          <OLTooltip
+            description={tooltipElement}
+            id="compile"
+            tooltipProps={{ className: 'keyboard-tooltip' }}
+            overlayProps={{ delay: { show: 500, hide: 0 } }}
+          >
+            <OLButton
+              variant="primary"
+              size="sm"
+              disabled={compiling}
+              onClick={() => startCompile()}
+              leadingIcon={
+                compiling ? (
+                  <Spinner
+                    animation="border"
+                    aria-hidden="true"
+                    size="sm"
+                    role="status"
+                  />
+                ) : null
+              }
+              className={buttonClassName}
+            >
+              {compileButtonLabel}
+            </OLButton>
+          </OLTooltip>
+
+          <DropdownToggle
+            split
+            variant="primary"
+            id="pdf-recompile-dropdown"
+            size="sm"
+            aria-label={t('toggle_compile_options_menu')}
+            className={dropdownToggleClassName}
+          />
+
+          <DropdownMenu>
+            <DropdownHeader>{t('auto_compile')}</DropdownHeader>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() =>
+                  sendEventAndSet(true, setAutoCompile, 'auto-compile')
+                }
+                trailingIcon={autoCompile ? 'check' : null}
+              >
+                {t('on')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() =>
+                  sendEventAndSet(false, setAutoCompile, 'auto-compile')
+                }
+                trailingIcon={!autoCompile ? 'check' : null}
+              >
+                {t('off')}
+              </DropdownItem>
+            </li>
+            <DropdownDivider />
+            <DropdownHeader>{t('compile_mode')}</DropdownHeader>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() => sendEventAndSet(false, setDraft, 'compile-mode')}
+                trailingIcon={!draft ? 'check' : null}
+              >
+                {t('normal')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() => sendEventAndSet(true, setDraft, 'compile-mode')}
+                trailingIcon={draft ? 'check' : null}
+              >
+                {t('fast')}&nbsp;<span className="subdued">[draft]</span>
+              </DropdownItem>
+            </li>
+            <DropdownDivider />
+            <DropdownHeader>Syntax Checks</DropdownHeader>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() =>
+                  sendEventAndSet(
+                    true,
+                    setStopOnValidationError,
+                    'syntax-check'
+                  )
+                }
+                trailingIcon={stopOnValidationError ? 'check' : null}
+              >
+                {t('stop_on_validation_error')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() =>
+                  sendEventAndSet(
+                    false,
+                    setStopOnValidationError,
+                    'syntax-check'
+                  )
+                }
+                trailingIcon={!stopOnValidationError ? 'check' : null}
+              >
+                {t('ignore_validation_errors')}
+              </DropdownItem>
+            </li>
+            <DropdownDivider />
+            <DropdownHeader>{t('compile_error_handling')}</DropdownHeader>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={enableStopOnFirstError}
+                trailingIcon={stopOnFirstError ? 'check' : null}
+              >
+                {t('stop_on_first_error')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={disableStopOnFirstError}
+                trailingIcon={!stopOnFirstError ? 'check' : null}
+              >
+                {t('try_to_compile_despite_errors')}
+              </DropdownItem>
+            </li>
+            <DropdownDivider />
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={() => stopCompile()}
+                disabled={!compiling}
+                aria-disabled={!compiling}
+              >
+                {t('stop_compile')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                onClick={fromScratchWithEvent}
+                disabled={compiling}
+                aria-disabled={compiling}
+              >
+                {t('recompile_from_scratch')}
+              </DropdownItem>
+            </li>
+          </DropdownMenu>
+        </Dropdown>
+      }
+    />
+  )
+}
+
+export default memo(PdfCompileButton)

+ 16 - 7
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-code-check-button.jsx

@@ -1,8 +1,11 @@
 import { memo, useCallback } from 'react'
-import { Button } from 'react-bootstrap'
 import Icon from '../../../shared/components/icon'
 import { useTranslation } from 'react-i18next'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
+import { bsVersion } from '@/features/utils/bootstrap-5'
 
 function PdfHybridCodeCheckButton() {
   const { codeCheckFailed, error, toggleLogs } = useCompileContext()
@@ -18,18 +21,24 @@ function PdfHybridCodeCheckButton() {
   }
 
   return (
-    <Button
-      bsSize="xsmall"
-      bsStyle="danger"
+    <OLButton
+      variant="danger"
+      size="sm"
       disabled={Boolean(error)}
       className="btn-toggle-logs toolbar-item"
       onClick={handleClick}
+      bs3Props={{
+        bsSize: 'xsmall',
+      }}
     >
-      <Icon type="exclamation-triangle" />
-      <span className="toolbar-text toolbar-hide-small">
+      <BootstrapVersionSwitcher
+        bs3={<Icon type="exclamation-triangle" />}
+        bs5={<MaterialIcon type="warning" />}
+      />
+      <span className={bsVersion({ bs3: 'toolbar-text' })}>
         {t('code_check_failed')}
       </span>
-    </Button>
+    </OLButton>
   )
 }
 

+ 26 - 15
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.tsx

@@ -1,11 +1,12 @@
 import { useTranslation } from 'react-i18next'
-import { Button } from 'react-bootstrap'
-import Tooltip from '../../../shared/components/tooltip'
-import Icon from '../../../shared/components/icon'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
-import { useProjectContext } from '../../../shared/context/project-context'
-import * as eventTracking from '../../../infrastructure/event-tracking'
-import { isSmallDevice } from '../../../infrastructure/event-tracking'
+import { useProjectContext } from '@/shared/context/project-context'
+import { sendMB, isSmallDevice } from '@/infrastructure/event-tracking'
+import Icon from '@/shared/components/icon'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import MaterialIcon from '@/shared/components/material-icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 function PdfHybridDownloadButton() {
   const { pdfDownloadUrl } = useCompileContext()
@@ -17,8 +18,14 @@ function PdfHybridDownloadButton() {
     ? t('download_pdf')
     : t('please_compile_pdf_before_download')
 
-  function handleOnClick() {
-    eventTracking.sendMB('download-pdf-button-click', {
+  function handleOnClick(e: React.MouseEvent) {
+    const event = e as React.MouseEvent<HTMLAnchorElement>
+    if (event.currentTarget.dataset.disabled === 'true') {
+      event.preventDefault()
+      return
+    }
+
+    sendMB('download-pdf-button-click', {
       projectId,
       location: 'pdf-preview',
       isSmallDevice,
@@ -26,16 +33,17 @@ function PdfHybridDownloadButton() {
   }
 
   return (
-    <Tooltip
+    <OLTooltip
       id="download-pdf"
       description={description}
       overlayProps={{ placement: 'bottom' }}
     >
-      <Button
+      <OLButton
         onClick={handleOnClick}
-        bsStyle="link"
+        variant="link"
         className="pdf-toolbar-btn"
-        draggable="false"
+        draggable={false}
+        data-disabled={!pdfDownloadUrl}
         disabled={!pdfDownloadUrl}
         download
         href={pdfDownloadUrl || '#'}
@@ -43,9 +51,12 @@ function PdfHybridDownloadButton() {
         style={{ pointerEvents: 'auto' }}
         aria-label={t('download_pdf')}
       >
-        <Icon type="download" fw />
-      </Button>
-    </Tooltip>
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="download" fw />}
+          bs5={<MaterialIcon type="download" />}
+        />
+      </OLButton>
+    </OLTooltip>
   )
 }
 

+ 19 - 13
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx

@@ -1,10 +1,13 @@
 import { memo, useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
-import { Button, Label } from 'react-bootstrap'
-import Tooltip from '../../../shared/components/tooltip'
-import Icon from '../../../shared/components/icon'
-import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
-import * as eventTracking from '../../../infrastructure/event-tracking'
+import Icon from '@/shared/components/icon'
+import MaterialIcon from '@/shared/components/material-icon'
+import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
+import * as eventTracking from '@/infrastructure/event-tracking'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import OLBadge from '@/features/ui/components/ol/ol-badge'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
 
 function PdfHybridLogsButton() {
   const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
@@ -25,13 +28,13 @@ function PdfHybridLogsButton() {
   const totalCount = errorCount + warningCount
 
   return (
-    <Tooltip
+    <OLTooltip
       id="logs-toggle"
       description={t('logs_and_output_files')}
       overlayProps={{ placement: 'bottom' }}
     >
-      <Button
-        bsStyle="link"
+      <OLButton
+        variant="link"
         disabled={Boolean(error || stoppedOnFirstError)}
         active={showLogs}
         className="pdf-toolbar-btn toolbar-item log-btn"
@@ -39,15 +42,18 @@ function PdfHybridLogsButton() {
         style={{ position: 'relative' }}
         aria-label={showLogs ? t('view_pdf') : t('view_logs')}
       >
-        <Icon type="file-text-o" fw />
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="file-text-o" fw />}
+          bs5={<MaterialIcon type="description" />}
+        />
 
         {!showLogs && totalCount > 0 && (
-          <Label bsStyle={errorCount === 0 ? 'warning' : 'danger'}>
+          <OLBadge bg={errorCount === 0 ? 'warning' : 'danger'}>
             {totalCount}
-          </Label>
+          </OLBadge>
         )}
-      </Button>
-    </Tooltip>
+      </OLButton>
+    </OLTooltip>
   )
 }
 

+ 5 - 10
services/web/frontend/js/features/pdf-preview/components/pdf-orphan-refresh-button.jsx

@@ -1,8 +1,8 @@
-import { Button } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import { memo, useCallback } from 'react'
-import { buildUrlWithDetachRole } from '../../../shared/utils/url-helper'
-import { useLocation } from '../../../shared/hooks/use-location'
+import { buildUrlWithDetachRole } from '@/shared/utils/url-helper'
+import { useLocation } from '@/shared/hooks/use-location'
+import OLButton from '@/features/ui/components/ol/ol-button'
 
 function PdfOrphanRefreshButton() {
   const { t } = useTranslation()
@@ -13,14 +13,9 @@ function PdfOrphanRefreshButton() {
   }, [location])
 
   return (
-    <Button
-      onClick={redirect}
-      className="btn-orphan"
-      bsStyle="primary"
-      bsSize="small"
-    >
+    <OLButton variant="primary" size="sm" onClick={redirect}>
       {t('redirect_to_editor')}
-    </Button>
+    </OLButton>
   )
 }
 

+ 3 - 3
services/web/frontend/js/features/pdf-preview/components/pdf-page-number-control.tsx

@@ -1,7 +1,7 @@
-import { ButtonGroup } from 'react-bootstrap'
 import PDFToolbarButton from './pdf-toolbar-button'
 import { useTranslation } from 'react-i18next'
 import { useState, useEffect } from 'react'
+import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
 
 type PdfPageNumberControlProps = {
   setPage: (page: number) => void
@@ -38,7 +38,7 @@ function PdfPageNumberControl({
 
   return (
     <>
-      <ButtonGroup className="pdfjs-toolbar-buttons ">
+      <OLButtonGroup className="pdfjs-toolbar-buttons">
         <PDFToolbarButton
           tooltipId="pdf-controls-previous-page-tooltip"
           icon="keyboard_arrow_up"
@@ -53,7 +53,7 @@ function PdfPageNumberControl({
           disabled={page === totalPages}
           onClick={() => setPage(page + 1)}
         />
-      </ButtonGroup>
+      </OLButtonGroup>
       <div className="pdfjs-page-number-input">
         <form onSubmit={handleSubmit}>
           <input

+ 18 - 5
services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.jsx

@@ -1,7 +1,7 @@
 import { memo, useState, useEffect, useRef } from 'react'
-import { ButtonToolbar } from 'react-bootstrap'
+import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
 import { useTranslation } from 'react-i18next'
-import { useLayoutContext } from '../../../shared/context/layout-context'
+import { useLayoutContext } from '@/shared/context/layout-context'
 import PdfCompileButton from './pdf-compile-button'
 import SwitchToEditorButton from './switch-to-editor-button'
 import PdfHybridLogsButton from './pdf-hybrid-logs-button'
@@ -10,6 +10,8 @@ import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button'
 import PdfOrphanRefreshButton from './pdf-orphan-refresh-button'
 import { DetachedSynctexControl } from './detach-synctex-control'
 import Icon from '../../../shared/components/icon'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import { Spinner } from 'react-bootstrap-5'
 
 const ORPHAN_UI_TIMEOUT_MS = 5000
 
@@ -47,9 +49,9 @@ function PdfPreviewHybridToolbar() {
   }
 
   return (
-    <ButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
+    <OlButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
       {ToolbarInner}
-    </ButtonToolbar>
+    </OlButtonToolbar>
   )
 }
 
@@ -88,7 +90,18 @@ function PdfPreviewHybridToolbarConnectingInner() {
   return (
     <>
       <div className="toolbar-pdf-orphan">
-        <Icon type="refresh" fw spin />
+        <BootstrapVersionSwitcher
+          bs3={<Icon type="refresh" fw spin />}
+          bs5={
+            <Spinner
+              animation="border"
+              aria-hidden="true"
+              size="sm"
+              role="status"
+            />
+          }
+        />
+        &nbsp;
         {t('tab_connecting')}…
       </div>
     </>

+ 64 - 21
services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx

@@ -7,8 +7,6 @@ import { getJSON } from '../../../infrastructure/fetch-json'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
 import { useLayoutContext } from '../../../shared/context/layout-context'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
-import { Button } from 'react-bootstrap'
-import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
 import { useTranslation } from 'react-i18next'
 import useIsMounted from '../../../shared/hooks/use-is-mounted'
@@ -21,6 +19,12 @@ import useScopeEventListener from '../../../shared/hooks/use-scope-event-listene
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import { debugConsole } from '@/utils/debugging'
 import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import MaterialIcon from '@/shared/components/material-icon'
+import { Spinner } from 'react-bootstrap-5'
+import { bsVersion } from '@/features/utils/bootstrap-5'
 
 function GoToCodeButton({
   position,
@@ -30,15 +34,32 @@ function GoToCodeButton({
 }) {
   const { t } = useTranslation()
   const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
-  const buttonClasses = classNames('synctex-control', 'btn-secondary', {
+  const buttonClasses = classNames('synctex-control', {
     'detach-synctex-control': !!isDetachLayout,
   })
 
   let buttonIcon = null
   if (syncToCodeInFlight) {
-    buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
+    buttonIcon = (
+      <BootstrapVersionSwitcher
+        bs3={<Icon type="refresh" spin className="synctex-spin-icon" />}
+        bs5={
+          <Spinner
+            animation="border"
+            aria-hidden="true"
+            size="sm"
+            role="status"
+          />
+        }
+      />
+    )
   } else if (!isDetachLayout) {
-    buttonIcon = <Icon type="arrow-left" className="synctex-control-icon" />
+    buttonIcon = (
+      <BootstrapVersionSwitcher
+        bs3={<Icon type="arrow-left" className="synctex-control-icon" />}
+        bs5={<MaterialIcon type="arrow_left_alt" />}
+      />
+    )
   }
 
   const syncToCodeWithButton = () => {
@@ -50,23 +71,26 @@ function GoToCodeButton({
   }
 
   return (
-    <Tooltip
+    <OLTooltip
       id="sync-to-code"
       description={t('go_to_pdf_location_in_code')}
       overlayProps={{ placement: tooltipPlacement }}
     >
-      <Button
-        bsStyle={null}
-        bsSize="xs"
+      <OLButton
+        variant="secondary"
+        size="sm"
         onClick={syncToCodeWithButton}
         disabled={syncToCodeInFlight}
         className={buttonClasses}
         aria-label={t('go_to_pdf_location_in_code')}
+        bs3Props={{
+          bsSize: 'xs',
+        }}
       >
         {buttonIcon}
         {isDetachLayout ? <span>&nbsp;{t('show_in_code')}</span> : ''}
-      </Button>
-    </Tooltip>
+      </OLButton>
+    </OLTooltip>
   )
 }
 
@@ -81,8 +105,7 @@ function GoToPdfButton({
   const tooltipPlacement = isDetachLayout ? 'bottom' : 'right'
   const buttonClasses = classNames(
     'synctex-control',
-    'btn-secondary',
-    'toolbar-btn-secondary',
+    bsVersion({ bs3: 'toolbar-btn-secondary' }),
     {
       'detach-synctex-control': !!isDetachLayout,
     }
@@ -90,29 +113,49 @@ function GoToPdfButton({
 
   let buttonIcon = null
   if (syncToPdfInFlight) {
-    buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
+    buttonIcon = (
+      <BootstrapVersionSwitcher
+        bs3={<Icon type="refresh" spin className="synctex-spin-icon" />}
+        bs5={
+          <Spinner
+            animation="border"
+            aria-hidden="true"
+            size="sm"
+            role="status"
+          />
+        }
+      />
+    )
   } else if (!isDetachLayout) {
-    buttonIcon = <Icon type="arrow-right" className="synctex-control-icon" />
+    buttonIcon = (
+      <BootstrapVersionSwitcher
+        bs3={<Icon type="arrow-right" className="synctex-control-icon" />}
+        bs5={<MaterialIcon type="arrow_right_alt" />}
+      />
+    )
   }
 
   return (
-    <Tooltip
+    <OLTooltip
       id="sync-to-pdf"
       description={t('go_to_code_location_in_pdf')}
       overlayProps={{ placement: tooltipPlacement }}
     >
-      <Button
-        bsStyle={null}
-        bsSize="xs"
+      <OLButton
+        variant="secondary"
+        size="sm"
         onClick={() => syncToPdf(cursorPosition)}
         disabled={syncToPdfInFlight || !cursorPosition || !hasSingleSelectedDoc}
         className={buttonClasses}
         aria-label={t('go_to_code_location_in_pdf')}
+        bs3Props={{
+          bsSize: 'xs',
+        }}
       >
         {buttonIcon}
         {isDetachLayout ? <span>&nbsp;{t('show_in_pdf')}</span> : ''}
-      </Button>
-    </Tooltip>
+      </OLButton>
+    </OLTooltip>
   )
 }
 

+ 8 - 9
services/web/frontend/js/features/pdf-preview/components/pdf-toolbar-button.tsx

@@ -1,6 +1,6 @@
-import Button from 'react-bootstrap/lib/Button'
 import MaterialIcon from '@/shared/components/material-icon'
-import Tooltip from '@/shared/components/tooltip'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
 
 type PDFToolbarButtonProps = {
   tooltipId: string
@@ -20,7 +20,7 @@ export default function PDFToolbarButton({
   shortcut,
 }: PDFToolbarButtonProps) {
   return (
-    <Tooltip
+    <OLTooltip
       id={tooltipId}
       description={
         <>
@@ -30,16 +30,15 @@ export default function PDFToolbarButton({
       }
       overlayProps={{ placement: 'bottom' }}
     >
-      <Button
-        aria-label={label}
-        bsSize="large"
-        bsStyle={null}
+      <OLButton
+        variant="ghost"
         className="pdf-toolbar-btn pdfjs-toolbar-button"
         disabled={disabled}
         onClick={onClick}
+        aria-label={label}
       >
         <MaterialIcon type={icon} />
-      </Button>
-    </Tooltip>
+      </OLButton>
+    </OLTooltip>
   )
 }

+ 22 - 17
services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-menu-button.tsx

@@ -2,11 +2,13 @@ import { useRef } from 'react'
 
 import PdfPageNumberControl from './pdf-page-number-control'
 import PdfZoomButtons from './pdf-zoom-buttons'
-import { Button, Overlay, Popover } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import MaterialIcon from '@/shared/components/material-icon'
-import Tooltip from '@/shared/components/tooltip'
 import useDropdown from '@/shared/hooks/use-dropdown'
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import OLButton from '@/features/ui/components/ol/ol-button'
+import OLOverlay from '@/features/ui/components/ol/ol-overlay'
+import OLPopover from '@/features/ui/components/ol/ol-popover'
 
 type PdfViewerControlsMenuButtonProps = {
   setZoom: (zoom: string) => void
@@ -29,34 +31,37 @@ export default function PdfViewerControlsMenuButton({
     ref: popoverRef,
   } = useDropdown()
 
-  const targetRef = useRef<any>(null)
+  const targetRef = useRef<HTMLButtonElement | null>(null)
 
   return (
     <>
-      <Tooltip
+      <OLTooltip
         id="pdf-controls-menu-tooltip"
         description={t('view_options')}
         overlayProps={{ placement: 'bottom' }}
       >
-        <Button
-          className="pdf-toolbar-btn pdfjs-toolbar-popover-button"
-          onClick={togglePopover}
-          ref={targetRef}
-        >
-          <MaterialIcon type="more_horiz" />
-        </Button>
-      </Tooltip>
+        <span>
+          <OLButton
+            variant="ghost"
+            className="pdf-toolbar-btn pdfjs-toolbar-popover-button"
+            onClick={togglePopover}
+            ref={targetRef}
+          >
+            <MaterialIcon type="more_horiz" />
+          </OLButton>
+        </span>
+      </OLTooltip>
 
-      <Overlay
+      <OLOverlay
         show={popoverOpen}
         target={targetRef.current}
         placement="bottom"
         containerPadding={0}
-        animation
+        transition
         rootClose
         onHide={() => togglePopover(false)}
       >
-        <Popover
+        <OLPopover
           className="pdfjs-toolbar-popover"
           id="pdf-toolbar-popover-menu"
           ref={popoverRef}
@@ -69,8 +74,8 @@ export default function PdfViewerControlsMenuButton({
           <div className="pdfjs-zoom-controls">
             <PdfZoomButtons setZoom={setZoom} />
           </div>
-        </Popover>
-      </Overlay>
+        </OLPopover>
+      </OLOverlay>
     </>
   )
 }

+ 3 - 3
services/web/frontend/js/features/pdf-preview/components/pdf-zoom-buttons.tsx

@@ -1,6 +1,6 @@
-import { ButtonGroup } from 'react-bootstrap'
 import PDFToolbarButton from './pdf-toolbar-button'
 import { useTranslation } from 'react-i18next'
+import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
 
 const isMac = /Mac/.test(window.navigator?.platform)
 
@@ -15,7 +15,7 @@ function PdfZoomButtons({ setZoom }: PdfZoomButtonsProps) {
   const zoomOutShortcut = isMac ? '⌘-' : 'Ctrl+-'
 
   return (
-    <ButtonGroup className="pdfjs-toolbar-buttons">
+    <OLButtonGroup className="pdfjs-toolbar-buttons">
       <PDFToolbarButton
         tooltipId="pdf-controls-zoom-out-tooltip"
         label={t('zoom_out')}
@@ -30,7 +30,7 @@ function PdfZoomButtons({ setZoom }: PdfZoomButtonsProps) {
         onClick={() => setZoom('zoom-in')}
         shortcut={zoomInShortcut}
       />
-    </ButtonGroup>
+    </OLButtonGroup>
   )
 }
 

+ 218 - 86
services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx

@@ -1,9 +1,19 @@
-import { Dropdown, MenuItem } from 'react-bootstrap'
+import { Dropdown as BS3Dropdown, MenuItem } from 'react-bootstrap'
 import { useEffect, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import ControlledDropdown from '@/shared/components/controlled-dropdown'
 import classNames from 'classnames'
 import { useFeatureFlag } from '@/shared/context/split-test-context'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import {
+  Dropdown,
+  DropdownDivider,
+  DropdownHeader,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
 
 const isMac = /Mac/.test(window.navigator?.platform)
 
@@ -53,93 +63,215 @@ function PdfZoomDropdown({
   const showPresentOption = enablePresentationMode && document.fullscreenEnabled
 
   return (
-    <ControlledDropdown
-      id="pdf-zoom-dropdown"
-      onSelect={eventKey => {
-        if (eventKey === 'custom-zoom') {
-          return
-        }
-
-        if (eventKey === 'present') {
-          requestPresentationMode()
-          return
-        }
-
-        setZoom(eventKey)
-      }}
-      pullRight
-    >
-      <Dropdown.Toggle
-        bsStyle={null}
-        className="btn pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
-        value={rawScale}
-        title={rawScaleToPercentage(rawScale)}
-      />
-      <Dropdown.Menu className="pdfjs-zoom-dropdown-menu">
-        <MenuItem
-          draggable={false}
-          disabled
-          className="pdfjs-custom-zoom-menu-item"
-          key="custom-zoom"
-          eventKey="custom-zoom"
+    <BootstrapVersionSwitcher
+      bs3={
+        <ControlledDropdown
+          id="pdf-zoom-dropdown"
+          onSelect={eventKey => {
+            if (eventKey === 'custom-zoom') {
+              return
+            }
+
+            if (eventKey === 'present') {
+              requestPresentationMode()
+              return
+            }
+
+            setZoom(eventKey)
+          }}
+          pullRight
         >
-          <input
-            type="text"
-            onFocus={event => event.target.select()}
-            value={customZoomValue}
-            onKeyDown={event => {
-              if (event.key === 'Enter') {
-                const zoom = Number(customZoomValue.replace('%', '')) / 100
-
-                // Only allow zoom values between 10% and 999%
-                if (zoom < 0.1) {
-                  setZoom('0.1')
-                } else if (zoom > 9.99) {
-                  setZoom('9.99')
-                } else {
-                  setZoom(`${zoom}`)
-                }
-              }
-            }}
-            onChange={event => {
-              const rawValue = event.target.value
-              const parsedValue = rawValue.replace(/[^0-9%]/g, '')
-              setCustomZoomValue(parsedValue)
-            }}
+          <BS3Dropdown.Toggle
+            bsStyle={null}
+            className="btn pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
+            value={rawScale}
+            title={rawScaleToPercentage(rawScale)}
           />
-        </MenuItem>
-        <MenuItem divider />
-        <MenuItem draggable={false} key="zoom-in" eventKey="zoom-in">
-          <span>{t('zoom_in')}</span>
-          <Shortcut keys={shortcuts['zoom-in']} />
-        </MenuItem>
-        <MenuItem draggable={false} key="zoom-out" eventKey="zoom-out">
-          <span>{t('zoom_out')}</span>
-          <Shortcut keys={shortcuts['zoom-out']} />
-        </MenuItem>
-        <MenuItem draggable={false} key="page-width" eventKey="page-width">
-          {t('fit_to_width')}
-          <Shortcut keys={shortcuts['fit-to-width']} />
-        </MenuItem>
-        <MenuItem draggable={false} key="page-height" eventKey="page-height">
-          {t('fit_to_height')}
-          <Shortcut keys={shortcuts['fit-to-height']} />
-        </MenuItem>
-        {showPresentOption && <MenuItem divider />}
-        {showPresentOption && (
-          <MenuItem draggable={false} key="present" eventKey="present">
-            {t('presentation_mode')}
-          </MenuItem>
-        )}
-        <MenuItem divider />
-        <MenuItem header>{t('zoom_to')}</MenuItem>
-        {zoomValues.map(value => (
-          <MenuItem draggable={false} key={value} eventKey={value}>
-            {rawScaleToPercentage(Number(value))}
-          </MenuItem>
-        ))}
-      </Dropdown.Menu>
-    </ControlledDropdown>
+          <BS3Dropdown.Menu className="pdfjs-zoom-dropdown-menu">
+            <MenuItem
+              draggable={false}
+              disabled
+              className="pdfjs-custom-zoom-menu-item"
+              key="custom-zoom"
+              eventKey="custom-zoom"
+            >
+              <input
+                type="text"
+                onFocus={event => event.target.select()}
+                value={customZoomValue}
+                onKeyDown={event => {
+                  if (event.key === 'Enter') {
+                    const zoom = Number(customZoomValue.replace('%', '')) / 100
+
+                    // Only allow zoom values between 10% and 999%
+                    if (zoom < 0.1) {
+                      setZoom('0.1')
+                    } else if (zoom > 9.99) {
+                      setZoom('9.99')
+                    } else {
+                      setZoom(`${zoom}`)
+                    }
+                  }
+                }}
+                onChange={event => {
+                  const rawValue = event.target.value
+                  const parsedValue = rawValue.replace(/[^0-9%]/g, '')
+                  setCustomZoomValue(parsedValue)
+                }}
+              />
+            </MenuItem>
+            <MenuItem divider />
+            <MenuItem draggable={false} key="zoom-in" eventKey="zoom-in">
+              <span>{t('zoom_in')}</span>
+              <Shortcut keys={shortcuts['zoom-in']} />
+            </MenuItem>
+            <MenuItem draggable={false} key="zoom-out" eventKey="zoom-out">
+              <span>{t('zoom_out')}</span>
+              <Shortcut keys={shortcuts['zoom-out']} />
+            </MenuItem>
+            <MenuItem draggable={false} key="page-width" eventKey="page-width">
+              {t('fit_to_width')}
+              <Shortcut keys={shortcuts['fit-to-width']} />
+            </MenuItem>
+            <MenuItem
+              draggable={false}
+              key="page-height"
+              eventKey="page-height"
+            >
+              {t('fit_to_height')}
+              <Shortcut keys={shortcuts['fit-to-height']} />
+            </MenuItem>
+            {showPresentOption && <MenuItem divider />}
+            {showPresentOption && (
+              <MenuItem draggable={false} key="present" eventKey="present">
+                {t('presentation_mode')}
+              </MenuItem>
+            )}
+            <MenuItem divider />
+            <MenuItem header>{t('zoom_to')}</MenuItem>
+            {zoomValues.map(value => (
+              <MenuItem draggable={false} key={value} eventKey={value}>
+                {rawScaleToPercentage(Number(value))}
+              </MenuItem>
+            ))}
+          </BS3Dropdown.Menu>
+        </ControlledDropdown>
+      }
+      bs5={
+        <Dropdown
+          onSelect={eventKey => {
+            if (eventKey === 'custom-zoom') {
+              return
+            }
+
+            if (eventKey === 'present') {
+              requestPresentationMode()
+              return
+            }
+
+            setZoom(eventKey)
+          }}
+          align="end"
+        >
+          <DropdownToggle
+            id="pdf-zoom-dropdown"
+            variant="link"
+            className="pdf-toolbar-btn pdfjs-zoom-dropdown-button"
+          >
+            <small>{rawScaleToPercentage(rawScale)}</small>
+          </DropdownToggle>
+          <DropdownMenu className="pdfjs-zoom-dropdown-menu">
+            <li role="none">
+              <DropdownItem
+                disabled
+                as="div"
+                className="pdfjs-custom-zoom-menu-item"
+                eventKey="custom-zoom"
+              >
+                <FormControl
+                  onFocus={event => event.target.select()}
+                  value={customZoomValue}
+                  onKeyDown={event => {
+                    if (event.key === 'Enter') {
+                      const zoom =
+                        Number(customZoomValue.replace('%', '')) / 100
+
+                      // Only allow zoom values between 10% and 999%
+                      if (zoom < 0.1) {
+                        setZoom('0.1')
+                      } else if (zoom > 9.99) {
+                        setZoom('9.99')
+                      } else {
+                        setZoom(`${zoom}`)
+                      }
+                    }
+                  }}
+                  onChange={event => {
+                    const rawValue = event.target.value
+                    const parsedValue = rawValue.replace(/[^0-9%]/g, '')
+                    setCustomZoomValue(parsedValue)
+                  }}
+                />
+              </DropdownItem>
+            </li>
+            <DropdownDivider />
+            <li role="none">
+              <DropdownItem
+                as="button"
+                eventKey="zoom-in"
+                trailingIcon={<Shortcut keys={shortcuts['zoom-in']} />}
+              >
+                {t('zoom_in')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                eventKey="zoom-out"
+                trailingIcon={<Shortcut keys={shortcuts['zoom-out']} />}
+              >
+                {t('zoom_out')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                eventKey="page-width"
+                trailingIcon={<Shortcut keys={shortcuts['fit-to-width']} />}
+              >
+                {t('fit_to_width')}
+              </DropdownItem>
+            </li>
+            <li role="none">
+              <DropdownItem
+                as="button"
+                eventKey="page-height"
+                trailingIcon={<Shortcut keys={shortcuts['fit-to-height']} />}
+              >
+                {t('fit_to_height')}
+              </DropdownItem>
+            </li>
+            {showPresentOption && <DropdownDivider />}
+            {showPresentOption && (
+              <li role="none">
+                <DropdownItem as="button" eventKey="present">
+                  {t('presentation_mode')}
+                </DropdownItem>
+              </li>
+            )}
+            <DropdownDivider />
+            <DropdownHeader aria-hidden="true">{t('zoom_to')}</DropdownHeader>
+            {zoomValues.map(value => (
+              <li role="none" key={value}>
+                <DropdownItem as="button" eventKey={value}>
+                  {rawScaleToPercentage(Number(value))}
+                </DropdownItem>
+              </li>
+            ))}
+          </DropdownMenu>
+        </Dropdown>
+      }
+    />
   )
 }
 

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/switch-to-editor-button.tsx

@@ -25,7 +25,7 @@ function SwitchToEditorButton() {
   return (
     <OLButton
       variant="secondary"
-      size="small"
+      size="sm"
       onClick={handleClick}
       bs3Props={{
         bsSize: 'xsmall',

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

@@ -12,7 +12,7 @@ import DeleteLeaveProjectsButton from './buttons/delete-leave-projects-button'
 import LeaveProjectsButton from './buttons/leave-projects-button'
 import DeleteProjectsButton from './buttons/delete-projects-button'
 import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
-import OlButtonGroup from '@/features/ui/components/ol/ol-button-group'
+import OLButtonGroup from '@/features/ui/components/ol/ol-button-group'
 
 function ProjectTools() {
   const { t } = useTranslation()
@@ -20,27 +20,27 @@ function ProjectTools() {
 
   return (
     <OLButtonToolbar aria-label={t('toolbar_selected_projects')}>
-      <OlButtonGroup
+      <OLButtonGroup
         aria-label={t('toolbar_selected_projects_management_actions')}
       >
         <DownloadProjectsButton />
         {filter !== 'archived' && <ArchiveProjectsButton />}
         {filter !== 'trashed' && <TrashProjectsButton />}
-      </OlButtonGroup>
+      </OLButtonGroup>
 
       {(filter === 'trashed' || filter === 'archived') && (
-        <OlButtonGroup aria-label={t('toolbar_selected_projects_restore')}>
+        <OLButtonGroup aria-label={t('toolbar_selected_projects_restore')}>
           {filter === 'trashed' && <UntrashProjectsButton />}
           {filter === 'archived' && <UnarchiveProjectsButton />}
-        </OlButtonGroup>
+        </OLButtonGroup>
       )}
 
       {filter === 'trashed' && (
-        <OlButtonGroup aria-label={t('toolbar_selected_projects_remove')}>
+        <OLButtonGroup aria-label={t('toolbar_selected_projects_remove')}>
           <LeaveProjectsButton />
           <DeleteProjectsButton />
           <DeleteLeaveProjectsButton />
-        </OlButtonGroup>
+        </OLButtonGroup>
       )}
 
       {!['archived', 'trashed'].includes(filter) && <TagsDropdown />}

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx

@@ -8,7 +8,7 @@ function PrimaryButton({
 }: OLButtonProps) {
   return (
     <OLButton
-      size="small"
+      size="sm"
       disabled={disabled && !isLoading}
       isLoading={isLoading}
       onClick={onClick}

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/actions/remove.tsx

@@ -23,7 +23,7 @@ function DeleteButton({ disabled, isLoading, onClick }: DeleteButtonProps) {
       variant="danger"
       disabled={disabled}
       isLoading={isLoading}
-      size="small"
+      size="sm"
       onClick={onClick}
       accessibilityLabel={t('remove') || ''}
       icon={

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx

@@ -56,7 +56,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
       <OLButton
         variant="primary"
         className="btn-link-accounts"
-        size="small"
+        size="sm"
         disabled={linkAccountsButtonDisabled}
         onClick={handleLinkAccountsButtonClick}
       >

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/row.tsx

@@ -152,7 +152,7 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
                 className="btn-link-accounts"
                 disabled={linkAccountsButtonDisabled}
                 onClick={handleLinkAccountsButtonClick}
-                size="small"
+                size="sm"
               >
                 {t('link_accounts')}
               </OLButton>

+ 1 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/access-levels-changed.tsx

@@ -43,7 +43,7 @@ export default function AccessLevelsChanged({
           <div className="upgrade-actions">
             {user.allowedFreeTrial ? (
               <StartFreeTrialButton
-                buttonProps={{ variant: 'secondary', size: 'small' }}
+                buttonProps={{ variant: 'secondary', size: 'sm' }}
                 source="project-sharing"
                 variant="exceeds"
               >

+ 1 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx

@@ -33,7 +33,7 @@ export default function AddCollaboratorsUpgrade() {
           <div className="upgrade-actions">
             {user.allowedFreeTrial ? (
               <StartFreeTrialButton
-                buttonProps={{ variant: 'secondary', size: 'small' }}
+                buttonProps={{ variant: 'secondary', size: 'sm' }}
                 source="project-sharing"
                 variant="exceeds"
               >

+ 2 - 2
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx

@@ -37,7 +37,7 @@ export default function CollaboratorsLimitUpgrade() {
           action={
             user.allowedFreeTrial ? (
               <StartFreeTrialButton
-                buttonProps={{ variant: 'premium', size: 'default' }}
+                buttonProps={{ variant: 'premium' }}
                 source="project-sharing"
                 variant="limit"
               >
@@ -71,7 +71,7 @@ export default function CollaboratorsLimitUpgrade() {
           action={
             user.allowedFreeTrial ? (
               <StartFreeTrialButton
-                buttonProps={{ variant: 'secondary', size: 'small' }}
+                buttonProps={{ variant: 'secondary', size: 'sm' }}
                 source="project-sharing"
                 variant="limit"
               >

+ 1 - 1
services/web/frontend/js/features/source-editor/components/switch-to-pdf-button.jsx

@@ -25,7 +25,7 @@ function SwitchToPDFButton() {
   return (
     <OLButton
       variant="secondary"
-      size="small"
+      size="sm"
       onClick={handleClick}
       bs3Props={{
         bsSize: 'xsmall',

+ 1 - 1
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx

@@ -345,7 +345,7 @@ export function ChangeToGroupModal() {
             )}
             <OLButton
               variant="primary"
-              size="large"
+              size="lg"
               disabled={
                 queryingGroupPlanToChangeToPrice ||
                 !groupPlanToChangeToPrice ||

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

@@ -1,14 +1,14 @@
-import { Badge as BSBadge } from 'react-bootstrap-5'
+import { Badge as BSBadge, BadgeProps as BSBadgeProps } from 'react-bootstrap-5'
 import { MergeAndOverride } from '../../../../../../types/utils'
 
 type BadgeProps = MergeAndOverride<
-  React.ComponentProps<typeof BSBadge>,
+  BSBadgeProps,
   {
     prepend?: React.ReactNode
   }
 >
 
-function Badge({ prepend, children, closeBtnProps, ...rest }: BadgeProps) {
+function Badge({ prepend, children, ...rest }: BadgeProps) {
   return (
     <BSBadge {...rest}>
       {prepend && <span className="badge-prepend">{prepend}</span>}

+ 21 - 23
services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx

@@ -5,12 +5,6 @@ import classNames from 'classnames'
 import { useTranslation } from 'react-i18next'
 import MaterialIcon from '@/shared/components/material-icon'
 
-const sizeClasses = new Map<ButtonProps['size'], string>([
-  ['small', 'btn-sm'],
-  ['default', ''],
-  ['large', 'btn-lg'],
-])
-
 const Button = forwardRef<HTMLButtonElement, ButtonProps>(
   (
     {
@@ -19,7 +13,6 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
       leadingIcon,
       isLoading = false,
       loadingLabel,
-      size = 'default',
       trailingIcon,
       variant = 'primary',
       ...props
@@ -28,13 +21,28 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
   ) => {
     const { t } = useTranslation()
 
-    const sizeClass = sizeClasses.get(size)
-    const buttonClassName = classNames('d-inline-grid', sizeClass, className, {
+    const buttonClassName = classNames('d-inline-grid', className, {
       'button-loading': isLoading,
     })
+
     const loadingSpinnerClassName =
-      size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
-    const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
+      props.size === 'lg' ? 'loading-spinner-large' : 'loading-spinner-small'
+    const materialIconClassName =
+      props.size === 'lg' ? 'icon-large' : 'icon-small'
+
+    const leadingIconComponent =
+      leadingIcon && typeof leadingIcon === 'string' ? (
+        <MaterialIcon type={leadingIcon} className={materialIconClassName} />
+      ) : (
+        leadingIcon
+      )
+
+    const trailingIconComponent =
+      trailingIcon && typeof trailingIcon === 'string' ? (
+        <MaterialIcon type={trailingIcon} className={materialIconClassName} />
+      ) : (
+        trailingIcon
+      )
 
     return (
       <BS5Button
@@ -58,19 +66,9 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
           </span>
         )}
         <span className="button-content" aria-hidden={isLoading}>
-          {leadingIcon && (
-            <MaterialIcon
-              type={leadingIcon}
-              className={materialIconClassName}
-            />
-          )}
+          {leadingIconComponent}
           {children}
-          {trailingIcon && (
-            <MaterialIcon
-              type={trailingIcon}
-              className={materialIconClassName}
-            />
-          )}
+          {trailingIconComponent}
         </span>
       </BS5Button>
     )

+ 0 - 51
services/web/frontend/js/features/ui/components/bootstrap-5/split-button.tsx

@@ -1,51 +0,0 @@
-import classNames from 'classnames'
-import Button from './button'
-import {
-  Dropdown,
-  DropdownItem,
-  DropdownMenu,
-  DropdownToggle,
-} from './dropdown-menu'
-import MaterialIcon from '@/shared/components/material-icon'
-import type { SplitButtonProps } from '@/features/ui/components/types/split-button-props'
-
-export function SplitButton({
-  accessibilityLabel,
-  align,
-  id,
-  items,
-  text,
-  variant,
-  ...props
-}: SplitButtonProps) {
-  const buttonClassName = classNames('split-button')
-
-  return (
-    <div>
-      <Dropdown align={align}>
-        <Button className={buttonClassName} variant={variant} {...props}>
-          <span className="split-button-content">{text}</span>
-        </Button>
-        <DropdownToggle
-          bsPrefix="dropdown-button-toggle"
-          id={id}
-          variant={variant}
-          {...props}
-        >
-          <MaterialIcon
-            className="split-button-caret"
-            type="expand_more"
-            accessibilityLabel={accessibilityLabel}
-          />
-        </DropdownToggle>
-        <DropdownMenu>
-          {items.map((item, index) => (
-            <li key={index}>
-              <DropdownItem eventKey={item.eventKey}>{item.label}</DropdownItem>
-            </li>
-          ))}
-        </DropdownMenu>
-      </Dropdown>
-    </div>
-  )
-}

+ 2 - 2
services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx

@@ -10,7 +10,7 @@ type OLButtonGroupProps = ButtonGroupProps & {
   bs3Props?: Record<string, unknown>
 }
 
-function OlButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
+function OLButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
   const bs3ButtonGroupProps: BS3ButtonGroupProps = {
     children: rest.children,
     className: rest.className,
@@ -27,4 +27,4 @@ function OlButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
   )
 }
 
-export default OlButtonGroup
+export default OLButtonGroup

+ 48 - 30
services/web/frontend/js/features/ui/components/ol/ol-button.tsx

@@ -1,3 +1,4 @@
+import { forwardRef } from 'react'
 import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
 import { Button as BS3Button } from 'react-bootstrap'
 import type { ButtonProps } from '@/features/ui/components/types/button-props'
@@ -5,7 +6,7 @@ import type { ButtonProps as BS3ButtonPropsBase } from 'react-bootstrap'
 import Button from '../bootstrap-5/button'
 import classnames from 'classnames'
 import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
-
+import { callFnsInSequence } from '@/utils/functions'
 export type BS3ButtonSize = 'xsmall' | 'sm' | 'medium' | 'lg'
 
 export type OLButtonProps = ButtonProps & {
@@ -14,6 +15,10 @@ export type OLButtonProps = ButtonProps & {
     bsSize?: BS3ButtonSize
     block?: boolean
     className?: string
+    onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
+    onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
+    onFocus?: React.FocusEventHandler<HTMLButtonElement>
+    onBlur?: React.FocusEventHandler<HTMLButtonElement>
   }
 }
 
@@ -25,7 +30,7 @@ export type BS3ButtonProps = Omit<BS3ButtonPropsBase, 'onClick'> & {
 export function bs3ButtonProps(props: ButtonProps) {
   const bs3ButtonProps: BS3ButtonProps = {
     bsStyle: null,
-    bsSize: mapBsButtonSizes(props.size),
+    bsSize: props.size,
     className: classnames(`btn-${props.variant || 'primary'}`, props.className),
     disabled: props.isLoading || props.disabled,
     form: props.form,
@@ -34,38 +39,51 @@ export function bs3ButtonProps(props: ButtonProps) {
     rel: props.rel,
     onClick: props.onClick,
     type: props.type,
+    draggable: props.draggable,
+    download: props.download,
+    style: props.style,
+    active: props.active,
   }
   return bs3ButtonProps
 }
 
-// maps Bootstrap 5 sizes to Bootstrap 3 sizes
-export const mapBsButtonSizes = (
-  size: ButtonProps['size']
-): 'sm' | 'lg' | undefined =>
-  size === 'small' ? 'sm' : size === 'large' ? 'lg' : undefined
+const OLButton = forwardRef<HTMLButtonElement, OLButtonProps>(
+  ({ bs3Props = {}, ...rest }, ref) => {
+    const { className: _, ...restBs3Props } = bs3Props
 
-export default function OLButton({ bs3Props = {}, ...rest }: OLButtonProps) {
-  const { className: _, ...restBs3Props } = bs3Props
+    // BS3 OverlayTrigger automatically provides 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' event handlers
+    const bs3FinalProps = {
+      ...restBs3Props,
+      onMouseOver: callFnsInSequence(bs3Props?.onMouseOver, rest.onMouseOver),
+      onMouseOut: callFnsInSequence(bs3Props?.onMouseOut, rest.onMouseOut),
+      onFocus: callFnsInSequence(bs3Props?.onFocus, rest.onFocus),
+      onBlur: callFnsInSequence(bs3Props?.onBlur, rest.onBlur),
+    }
 
-  // Get all `aria-*` and `data-*` attributes
-  const extraProps = getAriaAndDataProps(rest)
+    // Get all `aria-*` and `data-*` attributes
+    const extraProps = getAriaAndDataProps(rest)
 
-  return (
-    <BootstrapVersionSwitcher
-      bs3={
-        <BS3Button
-          {...bs3ButtonProps({
-            ...rest,
-            // Override the `className` with bs3 specific className (if provided)
-            className: bs3Props?.className ?? rest.className,
-          })}
-          {...restBs3Props}
-          {...extraProps}
-        >
-          {bs3Props?.loading || rest.children}
-        </BS3Button>
-      }
-      bs5={<Button {...rest} />}
-    />
-  )
-}
+    return (
+      <BootstrapVersionSwitcher
+        bs3={
+          <BS3Button
+            {...bs3ButtonProps({
+              ...rest,
+              // Override the `className` with bs3 specific className (if provided)
+              className: bs3Props?.className ?? rest.className,
+            })}
+            {...extraProps}
+            {...bs3FinalProps}
+            ref={ref as React.LegacyRef<any> | undefined}
+          >
+            {bs3Props?.loading || rest.children}
+          </BS3Button>
+        }
+        bs5={<Button {...rest} ref={ref} />}
+      />
+    )
+  }
+)
+OLButton.displayName = 'OLButton'
+
+export default OLButton

+ 4 - 1
services/web/frontend/js/features/ui/components/ol/ol-tooltip.tsx

@@ -9,10 +9,13 @@ type OLTooltipProps = React.ComponentProps<typeof Tooltip> & {
 function OLTooltip(props: OLTooltipProps) {
   const { bs3Props, ...bs5Props } = props
 
-  const bs3TooltipProps: React.ComponentProps<typeof BS3Tooltip> = {
+  type BS3TooltipProps = React.ComponentProps<typeof BS3Tooltip>
+
+  const bs3TooltipProps: BS3TooltipProps = {
     children: bs5Props.children,
     id: bs5Props.id,
     description: bs5Props.description,
+    tooltipProps: bs5Props.tooltipProps as BS3TooltipProps,
     overlayProps: {
       placement: bs5Props.overlayProps?.placement,
     },

+ 7 - 3
services/web/frontend/js/features/ui/components/types/button-props.ts

@@ -4,8 +4,10 @@ export type ButtonProps = {
   children?: ReactNode
   className?: string
   disabled?: boolean
+  download?: boolean
+  draggable?: boolean
   form?: string
-  leadingIcon?: string
+  leadingIcon?: string | React.ReactNode
   href?: string
   target?: string
   rel?: string
@@ -16,8 +18,10 @@ export type ButtonProps = {
   onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
   onFocus?: React.FocusEventHandler<HTMLButtonElement>
   onBlur?: React.FocusEventHandler<HTMLButtonElement>
-  size?: 'small' | 'default' | 'large'
-  trailingIcon?: string
+  size?: 'sm' | 'lg' | undefined
+  style?: Record<PropertyKey, string>
+  active?: boolean
+  trailingIcon?: string | React.ReactNode
   type?: 'button' | 'reset' | 'submit'
   variant?:
     | 'primary'

+ 7 - 2
services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts

@@ -1,5 +1,10 @@
 import type { ElementType, ReactNode, PropsWithChildren } from 'react'
-import type { SplitButtonVariants } from './split-button-props'
+import type { ButtonProps } from '@/features/ui/components/types/button-props'
+
+type SplitButtonVariants = Extract<
+  ButtonProps['variant'],
+  'primary' | 'secondary' | 'danger' | 'link'
+>
 
 export type DropdownProps = {
   align?:
@@ -47,7 +52,7 @@ export type DropdownToggleProps = PropsWithChildren<{
   id?: string // necessary for assistive technologies
   variant?: SplitButtonVariants
   as?: ElementType
-  size?: 'sm' | 'lg'
+  size?: 'sm' | 'lg' | undefined
 }>
 
 export type DropdownMenuProps = PropsWithChildren<{

+ 0 - 29
services/web/frontend/js/features/ui/components/types/split-button-props.ts

@@ -1,29 +0,0 @@
-import { PropsWithChildren } from 'react'
-import type {
-  DropdownItemProps,
-  DropdownProps,
-  DropdownToggleProps,
-} from './dropdown-menu-props'
-import type { ButtonProps } from './button-props'
-
-type SplitButtonItemProps = Pick<
-  DropdownItemProps,
-  'eventKey' | 'leadingIcon'
-> & {
-  label: React.ReactNode
-}
-
-export type SplitButtonVariants = Extract<
-  ButtonProps['variant'],
-  'primary' | 'secondary' | 'danger' | 'link'
->
-
-export type SplitButtonProps = PropsWithChildren<{
-  accessibilityLabel?: string
-  align?: DropdownProps['align']
-  disabled?: boolean
-  id: DropdownToggleProps['id']
-  items: SplitButtonItemProps[]
-  text: string
-  variant: SplitButtonVariants
-}>

+ 1 - 1
services/web/frontend/js/shared/components/copy-to-clipboard.tsx

@@ -53,7 +53,7 @@ const TextButton: FC<{
   return (
     <OLButton
       onClick={handleClick}
-      size="small"
+      size="sm"
       variant="secondary"
       className="copy-button"
       bs3Props={{ bsSize: 'xsmall' }}

+ 1 - 1
services/web/frontend/js/shared/hooks/use-dropdown.ts

@@ -25,7 +25,7 @@ export default function useDropdown(defaultOpen = false) {
 
   // handle dropdown toggle
   const handleToggle = useCallback(value => {
-    setOpen(value)
+    setOpen(Boolean(value))
   }, [])
 
   // close the dropdown on click outside the dropdown

+ 1 - 1
services/web/frontend/stories/start-free-trial-button.stories.jsx

@@ -17,7 +17,7 @@ export const ButtonStyle = args => {
       {...args}
       buttonProps={{
         variant: 'danger',
-        size: 'large',
+        size: 'lg',
       }}
     />
   )

+ 0 - 5
services/web/frontend/stories/ui/badge-bs5.stories.tsx

@@ -27,11 +27,6 @@ const meta: Meta<typeof Badge> = {
         disable: true,
       },
     },
-    closeBtnProps: {
-      table: {
-        disable: true,
-      },
-    },
   },
 }
 export default meta

+ 47 - 20
services/web/frontend/stories/ui/split-button.stories.tsx

@@ -1,33 +1,60 @@
-import { SplitButton } from '@/features/ui/components/bootstrap-5/split-button'
+import { Fragment } from 'react'
 import type { Meta } from '@storybook/react'
 import { useTranslation } from 'react-i18next'
+import {
+  Dropdown,
+  DropdownHeader,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import Button from '@/features/ui/components/bootstrap-5/button'
+import { ButtonGroup } from 'react-bootstrap-5'
 
-type Args = React.ComponentProps<typeof SplitButton>
+type Args = React.ComponentProps<typeof Dropdown>
 
-export const Dropdown = (args: Args) => {
+export const Sizes = (args: Args) => {
   const { t } = useTranslation()
+  const sizes = {
+    Large: 'lg',
+    Regular: undefined,
+    Small: 'sm',
+  } as const
+  const variants = ['primary', 'secondary', 'danger'] as const
 
-  return <SplitButton accessibilityLabel={t('expand')} {...args} />
+  return Object.entries(sizes).map(([label, size]) => (
+    <Fragment key={`${label}-${size}`}>
+      <h4>{label}</h4>
+      <div style={{ display: 'inline-flex', gap: '10px' }}>
+        {variants.map(variant => (
+          <Dropdown key={variant} as={ButtonGroup}>
+            <Button variant={variant} size={size}>
+              Split Button
+            </Button>
+            <DropdownToggle
+              split
+              variant={variant}
+              id={`split-btn-${variant}-${size}`}
+              size={size}
+              aria-label={t('expand')}
+            />
+            <DropdownMenu>
+              <DropdownHeader>Header</DropdownHeader>
+              <DropdownItem as="button">Action 1</DropdownItem>
+              <DropdownItem as="button">Action 2</DropdownItem>
+              <DropdownItem as="button">Action 3</DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+        ))}
+      </div>
+    </Fragment>
+  ))
 }
-const meta: Meta<typeof SplitButton> = {
+const meta: Meta<typeof Dropdown> = {
   title: 'Shared/Components/Bootstrap 5/SplitButton',
-  component: SplitButton,
+  component: Dropdown,
   args: {
     align: { sm: 'start' },
-    id: 'split-button',
-    items: [
-      { eventKey: '1', label: 'Action 1' },
-      { eventKey: '2', label: 'Action 2' },
-      { eventKey: '3', label: 'Action 3' },
-    ],
-    text: 'Split Button',
-  },
-  argTypes: {
-    id: {
-      table: {
-        disable: true,
-      },
-    },
   },
   parameters: {
     bootstrap5: true,

+ 0 - 4
services/web/frontend/stylesheets/app/editor/pdf.less

@@ -53,9 +53,6 @@
     outline: none;
   }
 }
-.btn-toggle-logs-label {
-  padding-left: @line-height-computed / 4;
-}
 
 .pdf-toolbar-btn {
   display: inline-block;
@@ -64,7 +61,6 @@
   padding: 4px 2px;
   line-height: 1;
   height: 24px;
-  border-radius: 2px;
   border-radius: @border-radius-base;
 
   &:hover,

+ 19 - 2
services/web/frontend/stylesheets/bootstrap-5/abstracts/mixins.scss

@@ -67,11 +67,11 @@
 
 // Toolbar
 @mixin toolbar-sm-height {
-  height: 32px;
+  height: var(--toolbar-small-height);
 }
 
 @mixin toolbar-alt-bg() {
-  background-color: var(--bg-dark-secondary);
+  background-color: var(--toolbar-alt-bg-color);
 }
 
 @mixin theme($name) {
@@ -87,3 +87,20 @@
 @mixin box-shadow-button-input {
   box-shadow: 0 0 0 2px var(--blue-30);
 }
+
+@mixin animation($animation) {
+  animation: $animation;
+}
+
+@mixin striped($color: rgba(255, 255, 255, 0.15), $angle: 45deg) {
+  background-image: linear-gradient(
+    $angle,
+    $color 25%,
+    transparent 25%,
+    transparent 50%,
+    $color 50%,
+    $color 75%,
+    transparent 75%,
+    transparent
+  );
+}

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

@@ -2,7 +2,6 @@
 @import 'button-group';
 @import 'dropdown-menu';
 @import 'image';
-@import 'split-button';
 @import 'notifications';
 @import 'system-messages';
 @import 'tooltip';

+ 2 - 2
services/web/frontend/stylesheets/bootstrap-5/components/button-group.scss

@@ -1,10 +1,10 @@
 .btn-group {
   > .btn {
-    &:first-child {
+    &:first-of-type {
       padding-left: var(--spacing-05);
     }
 
-    &:last-child {
+    &:last-of-type {
       padding-right: var(--spacing-05);
     }
   }

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

@@ -138,6 +138,7 @@
   border-top-left-radius: 0;
   padding-right: var(--spacing-05);
   padding-left: var(--spacing-05);
+  margin-left: 0;
 
   &.btn-primary,
   &.btn-danger {

+ 0 - 9
services/web/frontend/stylesheets/bootstrap-5/components/split-button.scss

@@ -1,9 +0,0 @@
-.split-button {
-  border-bottom-right-radius: 0;
-  border-top-right-radius: 0;
-  border-right: none;
-}
-
-.split-button-caret {
-  vertical-align: middle;
-}

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

@@ -12,5 +12,7 @@
 @import 'editor/file-tree';
 @import 'editor/figure-modal';
 @import 'subscription';
+@import 'editor/pdf';
+@import 'editor/compile-button';
 @import 'website-redesign';
 @import 'group-settings';

+ 31 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/compile-button.scss

@@ -0,0 +1,31 @@
+$stripe-width: 20px;
+
+@keyframes pdf-toolbar-stripes {
+  from {
+    background-position: 0 0;
+  }
+
+  to {
+    background-position: $stripe-width 0;
+  }
+}
+
+.btn-striped-animated {
+  @include striped;
+
+  background-size: $stripe-width $stripe-width;
+  background-origin: content-box;
+
+  @include animation(pdf-toolbar-stripes 2s linear infinite);
+}
+
+.detach-compile-button {
+  &[disabled],
+  &[disabled]:active {
+    background-color: var(--bs-btn-bg);
+    color: var(--bs-btn-color);
+    opacity: 1;
+    pointer-events: auto;
+    cursor: not-allowed;
+  }
+}

+ 357 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss

@@ -0,0 +1,357 @@
+:root {
+  --pdf-bg: var(--neutral-10);
+  --pdf-toolbar-btn-hover-color: rgb(125 125 125 / 20%);
+}
+
+@include theme('light') {
+  --pdf-toolbar-btn-hover-color: var(--neutral-10);
+}
+
+.pdf .toolbar.toolbar-pdf {
+  @include toolbar-sm-height;
+  @include toolbar-alt-bg;
+
+  padding-right: var(--spacing-03);
+  margin-left: 0;
+
+  .btn.disabled,
+  .btn[disabled] {
+    pointer-events: auto;
+    cursor: not-allowed;
+    opacity: 1;
+  }
+}
+
+.toolbar-pdf-left {
+  gap: var(--spacing-02);
+
+  .dropdown > .btn {
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+
+    &[disabled],
+    &[disabled]:active {
+      background-color: var(--bs-btn-bg);
+      color: var(--bs-btn-color);
+      opacity: 1;
+      pointer-events: auto;
+      cursor: not-allowed;
+    }
+  }
+}
+
+.toolbar-pdf-orphan,
+.toolbar-pdf-left,
+.toolbar-pdf-right,
+.toolbar-pdf-controls {
+  display: flex;
+  align-items: center;
+  align-self: stretch;
+}
+
+.toolbar-pdf-orphan,
+.toolbar-pdf-controls {
+  flex: 1 1 100%;
+}
+
+.toolbar-pdf-controls {
+  margin-right: var(--spacing-02);
+  justify-content: flex-end;
+}
+
+.toolbar-pdf-right {
+  flex: 1;
+  justify-content: flex-end;
+}
+
+.toolbar-pdf-orphan {
+  justify-content: center;
+  color: var(--toolbar-btn-color);
+
+  .btn {
+    margin-left: var(--spacing-03);
+  }
+}
+
+.btn.pdf-toolbar-btn {
+  display: inline-block;
+  color: var(--toolbar-btn-color);
+  background-color: transparent;
+  padding: 0 var(--spacing-01);
+  line-height: 1;
+  height: 24px;
+  border-radius: var(--border-radius-base);
+  text-decoration: none;
+
+  &:hover,
+  &:active,
+  &:focus {
+    color: var(--toolbar-btn-color);
+  }
+
+  &:hover {
+    &:not(:disabled) {
+      background-color: var(--pdf-toolbar-btn-hover-color);
+    }
+  }
+
+  &:active {
+    background-color: transparent;
+  }
+
+  .button-content {
+    align-self: center;
+  }
+
+  .badge {
+    font-size: 60%;
+  }
+
+  &.log-btn {
+    border: none;
+
+    &.active {
+      color: var(--white);
+      background-color: var(--link-color);
+      box-shadow: none;
+      opacity: 0.65;
+
+      &:hover {
+        &:not(:disabled) {
+          background-color: transparent;
+          color: var(--toolbar-btn-color);
+        }
+      }
+    }
+
+    &:focus {
+      outline: none;
+    }
+  }
+}
+
+.pdf {
+  background-color: var(--pdf-bg);
+}
+
+.pdf-viewer,
+.pdf-logs,
+.pdf-errors,
+.pdf-uncompiled {
+  @extend .full-size;
+
+  top: var(--toolbar-small-height);
+}
+
+.pdf-viewer {
+  iframe {
+    width: 100%;
+    height: 100%;
+    border: none;
+  }
+
+  .pdfjs-viewer {
+    @extend .full-size;
+
+    background-color: transparent;
+    overflow: scroll;
+
+    /* stylelint-disable-next-line selector-class-pattern */
+    .canvasWrapper > canvas,
+    div.pdf-canvas {
+      background: white;
+      box-shadow: 0 0 10px rgb(0 0 0 / 50%);
+    }
+
+    div.pdf-canvas.pdfng-empty {
+      background-color: var(--white);
+    }
+
+    div.pdf-canvas.pdfng-loading {
+      background-color: var(--white);
+    }
+
+    .page-container {
+      margin: var(--spacing-05) auto;
+      padding: 0 var(--spacing-05);
+      box-sizing: content-box;
+      user-select: none;
+    }
+
+    .page {
+      box-sizing: content-box;
+      margin: var(--spacing-05) auto;
+      box-shadow: 0 0 8px #bbb;
+      border: none;
+    }
+
+    .pdfjs-viewer-inner {
+      position: absolute;
+      overflow-y: scroll;
+      width: 100%;
+      height: 100%;
+      -webkit-font-smoothing: initial;
+      -moz-osx-font-smoothing: initial;
+
+      /* fix review-panel overflow issue, see: https://github.com/overleaf/internal/issues/6781#issuecomment-1112708638 */
+      /* stylelint-disable-next-line selector-class-pattern */
+      .pdfViewer {
+        min-height: 100%;
+      }
+    }
+
+    &:focus-within {
+      outline: none;
+    }
+
+    /* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
+    /* stylelint-disable-next-line selector-class-pattern */
+    .textLayer br::selection {
+      background: transparent;
+    }
+  }
+
+  .progress-thin {
+    position: absolute;
+    top: -2px;
+    height: 3px;
+    left: 0;
+    right: 0;
+
+    .progress-bar {
+      height: 100%;
+      background-color: var(--link-color);
+    }
+  }
+}
+
+.pdfjs-viewer-controls {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  width: 100%;
+}
+
+.pdfjs-zoom-controls {
+  display: inline-flex;
+  border-left: 1px solid rgb(125 125 125 / 30%);
+}
+
+.pdfjs-toolbar-buttons {
+  display: flex;
+  gap: var(--spacing-04);
+  margin-left: var(--spacing-04);
+  margin-right: var(--spacing-04);
+}
+
+.pdfjs-toolbar-button {
+  padding: var(--spacing-01) !important;
+  display: flex;
+  align-items: center;
+}
+
+.pdfjs-zoom-dropdown-button {
+  width: 60px;
+  text-align: right;
+  font-weight: normal;
+}
+
+.pdfjs-zoom-dropdown-mac-shortcut-char {
+  display: inline-block;
+  width: 1em;
+  text-align: center;
+}
+
+.pdfjs-custom-zoom-menu-item {
+  display: block;
+  pointer-events: initial !important;
+
+  &:hover {
+    background-color: initial !important;
+    color: initial !important;
+    cursor: initial !important;
+  }
+}
+
+.pdfjs-page-number-input {
+  color: var(--toolbar-btn-color);
+  font-size: var(--font-size-02);
+  padding-right: var(--spacing-04);
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-02);
+
+  input {
+    color: initial;
+    border: 1px solid var(--neutral-60);
+    width: 32px;
+    height: 24px;
+    border-radius: var(--border-radius-base);
+    text-align: center;
+  }
+}
+
+.pdfjs-viewer-controls-small {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-04);
+}
+
+.pdfjs-toolbar-popover-button {
+  padding: var(--spacing-01) !important;
+}
+
+.pdfjs-toolbar-popover {
+  background-color: var(--editor-toolbar-bg);
+  border-radius: var(--border-radius-base);
+
+  .popover-arrow {
+    display: none;
+  }
+
+  button {
+    background-color: transparent;
+    color: var(--toolbar-btn-color);
+  }
+
+  .popover-body {
+    display: flex;
+    align-items: center;
+    padding: var(--spacing-04) 0;
+  }
+}
+
+// The new viewer UI has overflow on the inner element,
+// so disable the overflow on the outer element
+.pdf-viewer .pdfjs-viewer.pdfjs-viewer-outer {
+  overflow: hidden;
+}
+
+:fullscreen {
+  .pdfjs-viewer-inner {
+    overflow-y: hidden !important;
+  }
+}
+
+.synctex-control {
+  > .synctex-control-icon {
+    display: inline-block;
+    font:
+      normal normal normal 14px/1 FontAwesome,
+      sans-serif;
+    font-size: inherit;
+    text-rendering: auto;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+
+  > .synctex-spin-icon {
+    margin-top: var(--spacing-01);
+  }
+}
+
+.keyboard-tooltip {
+  .tooltip-inner {
+    max-width: none;
+  }
+}

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

@@ -7,6 +7,8 @@
   --toolbar-btn-hover-color: var(--white);
   --toolbar-btn-active-color: var(--white);
   --toolbar-btn-active-bg-color: var(--green-50);
+  --toolbar-small-height: 32px;
+  --toolbar-alt-bg-color: var(--neutral-80);
   --formatting-btn-color: var(--white);
   --formatting-btn-bg: var(--neutral-80);
   --formatting-btn-border: var(--neutral-70);
@@ -27,6 +29,7 @@
   --toolbar-btn-hover-bg-color: var(--neutral-10);
   --toolbar-btn-hover-color: var(--neutral-70);
   --toolbar-btn-active-bg-color: var(--green-50);
+  --toolbar-alt-bg-color: var(--white);
   --formatting-btn-color: var(--neutral-70);
   --formatting-btn-bg: transparent;
   --formatting-btn-border: var(--neutral-20);
@@ -43,7 +46,6 @@
   display: flex;
   align-items: center;
   height: var(--toolbar-height);
-  min-height: var(--toolbar-height);
   border-bottom: 1px solid var(--toolbar-border-color);
 
   button {

+ 1 - 1
services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx

@@ -44,7 +44,7 @@ describe('start free trial button', function () {
         source="cypress-test"
         buttonProps={{
           variant: 'danger',
-          size: 'large',
+          size: 'lg',
         }}
       />
     )