Merge pull request #28503 from overleaf/mj-split-theme-list-dark-light-legacy

[web] Split dark and light options in theme selector

GitOrigin-RevId: 507c5a71269b360d23b246516487a0a072ee7d18
This commit is contained in:
Mathias Jakobsen
2025-12-08 10:53:55 +00:00
committed by Copybot
parent da72a45e86
commit 0b51972da1
10 changed files with 79 additions and 74 deletions

View File

@@ -381,6 +381,7 @@
"customizing_figures": "",
"customizing_tables": "",
"dark_mode_pdf_preview": "",
"dark_themes": "",
"date_and_owner": "",
"date_and_time": "",
"dealing_with_errors": "",
@@ -977,6 +978,7 @@
"leave_project": "",
"leave_projects": "",
"left": "",
"legacy_themes": "",
"length_unit": "",
"let_us_know": "",
"let_us_know_how_we_can_help": "",
@@ -985,6 +987,7 @@
"lets_get_you_set_up": "",
"library": "",
"licenses": "",
"light_themes": "",
"limited_document_history": "",
"limited_to_n_collaborators_per_project": "",
"limited_to_n_collaborators_per_project_plural": "",

View File

@@ -1,43 +1,19 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
import { useEditorThemesOptionGroups } from '../../hooks/use-editor-theme-option-groups'
export default function SettingsEditorTheme() {
const { t } = useTranslation()
const editorThemes = getMeta('ol-editorThemes')
const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
const options = useMemo(() => {
const editorThemeOptions: Array<Option> =
editorThemes?.map(theme => ({
value: theme.name,
label: theme.name.replace(/_/g, ' '),
})) ?? []
const dividerOption: Option = {
value: '-',
label: '—————————————————',
disabled: true,
}
const legacyEditorThemeOptions: Array<Option> =
legacyEditorThemes?.map(theme => ({
value: theme.name,
label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
})) ?? []
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
}, [editorThemes, legacyEditorThemes])
const optGroups = useEditorThemesOptionGroups()
return (
<SettingsMenuSelect
onChange={setEditorTheme}
value={editorTheme}
options={options}
optgroups={optGroups}
label={t('editor_theme')}
name="editorTheme"
translateOptions="no"

View File

@@ -22,8 +22,8 @@ export type Optgroup<T extends PossibleValue = string> = {
type SettingsMenuSelectProps<T extends PossibleValue = string> = {
label: string
name: string
options: Array<Option<T>>
optgroup?: Optgroup<T>
options?: Array<Option<T>>
optgroups?: Array<Optgroup<T>>
loading?: boolean
onChange: (val: T) => void
value?: T
@@ -35,7 +35,7 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
label,
name,
options,
optgroup,
optgroups,
loading,
onChange,
value,
@@ -92,7 +92,7 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
ref={selectRef}
translate={translateOptions}
>
{options.map(option => (
{options?.map(option => (
<option
key={`${name}-${option.value}`}
value={option.value.toString()}
@@ -102,8 +102,8 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
{option.label}
</option>
))}
{optgroup ? (
<optgroup label={optgroup.label}>
{optgroups?.map(optgroup => (
<optgroup label={optgroup.label} key={optgroup.label}>
{optgroup.options.map(option => (
<option
value={option.value.toString()}
@@ -113,7 +113,7 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
</option>
))}
</optgroup>
) : null}
))}
</OLFormSelect>
)}
</OLFormGroup>

View File

@@ -33,7 +33,7 @@ export default function SettingsSpellCheckLanguage() {
onChange={setSpellCheckLanguage}
value={supportsWebAssembly() ? spellCheckLanguage : ''}
options={[{ value: '', label: t('off') }]}
optgroup={optgroup}
optgroups={[optgroup]}
label={t('spell_check')}
name="spellCheckLanguage"
disabled={permissionsLevel === 'readOnly' || !supportsWebAssembly()}

View File

@@ -0,0 +1,51 @@
import getMeta from '@/utils/meta'
import { useMemo } from 'react'
import { Option } from '../components/settings/settings-menu-select'
import { useTranslation } from 'react-i18next'
const overrides = new Map([['overleaf', 'overleaf light']])
function getThemeName(theme: string): string {
return (overrides.get(theme) ?? theme).replace(/_/g, ' ')
}
export function useEditorThemesOptionGroups() {
const editorThemes = getMeta('ol-editorThemes')
const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
const { t } = useTranslation()
const optgroups = useMemo(() => {
const lightThemes: Array<Option> = []
const darkThemes: Array<Option> = []
editorThemes?.forEach(({ name: theme, dark }) => {
const target = dark ? darkThemes : lightThemes
target.push({
value: theme,
label: getThemeName(theme),
})
})
const legacyEditorThemeOptions: Array<Option> =
legacyEditorThemes?.map(({ name: theme }) => ({
value: theme,
label: getThemeName(theme) + ' (Legacy)',
})) ?? []
const groups = []
if (lightThemes.length > 0) {
groups.push({ label: t('light_themes'), options: lightThemes })
}
if (darkThemes.length > 0) {
groups.push({ label: t('dark_themes'), options: darkThemes })
}
if (legacyEditorThemeOptions.length > 0) {
groups.push({
label: t('legacy_themes'),
options: legacyEditorThemeOptions,
})
}
return groups
}, [editorThemes, legacyEditorThemes, t])
return optgroups
}

View File

@@ -1,44 +1,20 @@
import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
import DropdownSetting from '../dropdown-setting'
import getMeta from '@/utils/meta'
import { useMemo } from 'react'
import type { Option } from '../dropdown-setting'
import { useTranslation } from 'react-i18next'
import { useEditorThemesOptionGroups } from '@/features/editor-left-menu/hooks/use-editor-theme-option-groups'
export default function EditorThemeSetting() {
const editorThemes = getMeta('ol-editorThemes')
const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
const { t } = useTranslation()
const options = useMemo(() => {
const editorThemeOptions: Array<Option> =
editorThemes?.map(theme => ({
value: theme.name,
label: theme.name.replace(/_/g, ' '),
})) ?? []
const dividerOption: Option = {
value: '-',
label: '—————————————————',
disabled: true,
}
const legacyEditorThemeOptions: Array<Option> =
legacyEditorThemes?.map(theme => ({
value: theme.name,
label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
})) ?? []
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
}, [editorThemes, legacyEditorThemes])
const optGroups = useEditorThemesOptionGroups()
return (
<DropdownSetting
id="editorTheme"
label={t('editor_theme')}
description={t('the_code_editor_color_scheme')}
options={options}
optgroups={optGroups}
onChange={setEditorTheme}
value={editorTheme}
translateOptions="no"

View File

@@ -21,12 +21,10 @@ export type Optgroup<T extends PossibleValue = string> = {
type SettingsMenuSelectProps<T extends PossibleValue = string> = {
id: string
label: string
options: Array<Option<T>>
options?: Array<Option<T>>
onChange: (val: T) => void
description?: string
// TODO: We can remove optgroup when the spellcheck setting is
// split into 2 and no longer uses it.
optgroup?: Optgroup<T>
optgroups?: Array<Optgroup<T>>
value?: T
disabled?: boolean
width?: 'default' | 'wide'
@@ -40,7 +38,7 @@ export default function DropdownSetting<T extends PossibleValue = string>({
options,
onChange,
value,
optgroup,
optgroups,
description = undefined,
disabled = false,
width = 'default',
@@ -77,7 +75,7 @@ export default function DropdownSetting<T extends PossibleValue = string>({
disabled={disabled}
translate={translateOptions}
>
{options.map(option => (
{options?.map(option => (
<option
key={`${id}-${option.value}`}
value={option.value.toString()}
@@ -87,8 +85,8 @@ export default function DropdownSetting<T extends PossibleValue = string>({
{option.label}
</option>
))}
{optgroup ? (
<optgroup label={optgroup.label}>
{optgroups?.map(optgroup => (
<optgroup label={optgroup.label} key={optgroup.label}>
{optgroup.options.map(option => (
<option
value={option.value.toString()}
@@ -98,7 +96,7 @@ export default function DropdownSetting<T extends PossibleValue = string>({
</option>
))}
</optgroup>
) : null}
))}
</OLFormSelect>
)}
</Setting>

View File

@@ -31,7 +31,7 @@ export default function SpellCheckSetting() {
id="spellCheckLanguage"
label={t('spellcheck_language')}
options={[{ value: '', label: t('off') }]}
optgroup={optgroup}
optgroups={[optgroup]}
onChange={setSpellCheckLanguage}
value={supportsWebAssembly() ? spellCheckLanguage : ''}
width="wide"

View File

@@ -502,6 +502,7 @@
"da": "Danish",
"dark_mode": "Dark mode",
"dark_mode_pdf_preview": "Dark mode PDF preview",
"dark_themes": "Dark themes",
"date": "Date",
"date_and_owner": "Date and owner",
"date_and_time": "Date and time",
@@ -1263,6 +1264,7 @@
"leave_project": "Leave Project",
"leave_projects": "Leave Projects",
"left": "Left",
"legacy_themes": "Legacy themes",
"length_unit": "Length unit",
"let_us_know": "Let us know",
"let_us_know_how_we_can_help": "Let us know how we can help",
@@ -1273,6 +1275,7 @@
"library": "Library",
"license": "License",
"licenses": "Licenses",
"light_themes": "Light themes",
"limited_document_history": "Limited document history",
"limited_to_n_collaborators_per_project": "Limited to __count__ collaborator per project",
"limited_to_n_collaborators_per_project_plural": "Limited to __count__ collaborators per project",

View File

@@ -607,7 +607,6 @@ describe('<EditorLeftMenu />', function () {
'editortheme-1',
'editortheme-2',
'editortheme-3',
'-',
'legacytheme-1',
'legacytheme-2',
'legacytheme-3',
@@ -618,7 +617,6 @@ describe('<EditorLeftMenu />', function () {
'editortheme-1',
'editortheme-2',
'editortheme-3',
'—————————————————',
'legacytheme-1 (Legacy)',
'legacytheme-2 (Legacy)',
'legacytheme-3 (Legacy)',