Merge pull request #35037 from overleaf/mj-themed-modals-again

[web] Add support for dark mode modals

GitOrigin-RevId: 34770a6f91d6a331cfaccf86c7dc1a5d151ad25b
This commit is contained in:
Mathias Jakobsen
2026-09-17 11:15:24 +00:00
committed by Copybot
parent ef8fce67f1
commit 3c3dd0fb37
5 changed files with 78 additions and 14 deletions
@@ -497,6 +497,7 @@ const _ProjectController = {
'ai-disabled-collaborators',
'group-link-sharing',
'compile-with-checkpoint',
'themed-modals',
].filter(Boolean)
const getUserValues = async userId =>
@@ -12,6 +12,7 @@ import {
} from '../context/settings-modal-context'
import useFocusOnSetting from '../hooks/use-focus-on-setting'
import useOpenSettingsViaQueryParam from '../hooks/use-open-settings-via-query-param'
import { useFeatureFlag } from '@/shared/context/split-test-context'
const SettingsModalWrapper = () => {
return (
@@ -25,6 +26,7 @@ const SettingsModal = () => {
const { t } = useTranslation()
const { show, setShow, settingsTabs, activeTab, setActiveTab } =
useSettingsModalContext()
const themed = useFeatureFlag('themed-modals')
useFocusOnSetting()
useOpenSettingsViaQueryParam()
@@ -39,6 +41,7 @@ const SettingsModal = () => {
? 'ide-settings-modal-transparent-backdrop'
: undefined
}
themed={themed}
>
<OLModalHeader>
<OLModalTitle>{t('settings')}</OLModalTitle>
@@ -9,11 +9,14 @@ import {
import { ModalBodyProps } from 'react-bootstrap/ModalBody'
import type { Options as FocusTrapOptions } from 'focus-trap'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
type OLModalProps = ModalProps & {
size?: 'sm' | 'lg'
onHide: () => void
show?: boolean
themed?: boolean
className?: string
} & Pick<
FocusTrapOptions,
| 'escapeDeactivates'
@@ -34,10 +37,22 @@ export function OLModal({
escapeDeactivates = false, // Let React-Bootstrap Modal handle Escape key to avoid double Escape key handling
clickOutsideDeactivates = true, // Allow focus trap to deactivate on outside click and let React-Bootstrap Modal handle it
initialFocus,
themed = false,
className,
backdropClassName,
...props
}: OLModalProps) {
return (
<Modal show={show} onHide={onHide} {...props}>
<Modal
show={show}
onHide={onHide}
className={classNames({ 'modal-themed': themed }, className)}
backdropClassName={classNames(
{ 'modal-backdrop-themed': themed },
backdropClassName
)}
{...props}
>
<FocusTrap
active={show}
focusTrapOptions={{
@@ -3,7 +3,7 @@
}
@include theme('default') {
.modal {
.modal:not(.modal-themed) {
// Until we add a dark modal version, all core components in modals should use light theme
@include light-dropdown-menu;
@include light-notification;
@@ -18,6 +18,27 @@
@include light-buttons;
}
}
.modal-themed {
--bs-modal-color: var(--content-primary-dark);
--bs-modal-bg: var(--bg-dark-primary);
--bs-modal-border-color: var(--border-divider-dark);
--bs-modal-box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 40%);
--bs-modal-header-border-color: var(--border-divider-dark);
--bs-modal-footer-bg: var(--bg-dark-secondary);
--bs-modal-footer-border-color: var(--border-divider-dark);
--bs-heading-color: var(--content-primary-dark);
color-scheme: dark;
.modal-header .btn-close {
--bs-btn-close-bg: url('data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23fff%27%3e%3cpath d=%27M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414%27/%3e%3c/svg%3e');
}
}
.modal-backdrop-themed {
background-color: var(--neutral-70);
}
}
@include media-breakpoint-up(sm) {
@@ -1,3 +1,27 @@
:root {
--ide-settings-text-color: var(--content-primary);
--ide-settings-section-title-color: var(--content-secondary);
--ide-settings-description-color: var(--content-secondary);
--ide-settings-section-title-border-color: var(--border-divider);
--ide-settings-link-hover-color: var(--link-ui-hover);
--ide-settings-link-hover-bg-color: var(--bg-light-secondary);
--ide-settings-link-active-color: var(--green-70);
--ide-settings-link-active-bg-color: var(--bg-accent-03);
}
@include theme('default') {
.modal-themed {
--ide-settings-text-color: var(--content-primary-dark);
--ide-settings-section-title-color: var(--content-secondary-dark);
--ide-settings-description-color: var(--content-secondary-dark);
--ide-settings-section-title-border-color: var(--border-divider-dark);
--ide-settings-link-hover-color: var(--link-ui-hover-dark);
--ide-settings-link-hover-bg-color: var(--bg-dark-secondary);
--ide-settings-link-active-color: var(--green-10);
--ide-settings-link-active-bg-color: var(--green-70);
}
}
.ide-settings-tab-nav.nav {
width: 300px;
border-right: var(--bs-modal-header-border-width) solid
@@ -25,7 +49,7 @@
align-items: flex-start;
flex-direction: row;
gap: var(--spacing-02);
color: var(--content-primary);
color: var(--ide-settings-text-color);
padding: var(--spacing-02);
border-radius: var(--border-radius-base);
font-size: var(--font-size-02);
@@ -33,24 +57,24 @@
text-decoration: none;
&:visited {
color: var(--content-primary);
color: var(--ide-settings-text-color);
}
&.ide-settings-tab-link-button:hover:not(.active),
&.ide-settings-tab-link-button:focus:not(.active) {
background-color: var(--bg-light-secondary);
color: var(--content-primary);
background-color: var(--ide-settings-link-hover-bg-color);
color: var(--ide-settings-text-color);
}
&.ide-settings-tab-link-anchor:hover,
&.ide-settings-tab-link-anchor:focus {
color: var(--link-ui-hover);
background-color: var(--bg-light-secondary);
color: var(--ide-settings-link-hover-color);
background-color: var(--ide-settings-link-hover-bg-color);
}
&.active {
color: var(--green-70);
background-color: var(--bg-accent-03);
color: var(--ide-settings-link-active-color);
background-color: var(--ide-settings-link-active-bg-color);
font-weight: bold;
}
}
@@ -72,11 +96,11 @@
}
.ide-settings-section-title {
color: var(--content-secondary);
color: var(--ide-settings-section-title-color);
font-size: var(--font-size-01);
line-height: var(--line-height-01);
padding-bottom: var(--spacing-06);
border-bottom: 1px solid var(--border-divider);
border-bottom: 1px solid var(--ide-settings-section-title-border-color);
margin: 0 var(--spacing-04);
}
@@ -99,12 +123,12 @@
}
.ide-setting-title {
color: var(--content-primary);
color: var(--ide-settings-text-color);
font-size: var(--font-size-02);
}
.ide-setting-description {
color: var(--content-secondary);
color: var(--ide-settings-description-color);
font-size: var(--font-size-02);
}