Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for prefers-color-scheme #4380

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
10 changes: 8 additions & 2 deletions packages/app-core/src/ui/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Suspense, lazy } from 'react'
import { AppBar } from '@mui/material'
import React, { Suspense, lazy, useEffect } from 'react'
import { AppBar, useMediaQuery } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util'
Expand Down Expand Up @@ -87,6 +87,12 @@ const App = observer(function (props: Props) {
const d = drawerVisible ? `[drawer] ${drawerWidth}px` : undefined
const grid =
drawerPosition === 'right' ? ['[main] 1fr', d] : [d, '[main] 1fr']
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')

useEffect(() => {
// @ts-expect-error
session.setPrefersDarkMode(`${prefersDarkMode}`)
}, [prefersDarkMode, session])

return (
<div
Expand Down
16 changes: 10 additions & 6 deletions packages/core/ui/theme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ test('can create a default theme', () => {
})
test('allows overriding primary and secondary colors', () => {
const theme = createJBrowseTheme({
palette: {
primary: { main: '#888888' },
secondary: { main: 'rgb(137,137,137)' },
light: {
palette: {
primary: { main: '#888888' },
secondary: { main: 'rgb(137,137,137)' },
},
},
})
expect(theme.palette.primary.main).toEqual('#888888')
expect(theme.palette.secondary.main).toEqual('rgb(137,137,137)')
})
test('allows overriding tertiary and quaternary colors', () => {
const theme = createJBrowseTheme({
palette: {
tertiary: { 500: '#888' },
quaternary: { main: 'hsl(0,0,54)' },
light: {
palette: {
tertiary: { 500: '#888' },
quaternary: { main: 'hsl(0,0,54)' },
},
},
})
const { tertiary, quaternary } = theme.palette
Expand Down
200 changes: 153 additions & 47 deletions packages/core/ui/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { createTheme, ThemeOptions } from '@mui/material/styles'
import type {
PaletteAugmentColorOptions,
PaletteColor,
SimplePaletteColorOptions,
} from '@mui/material/styles/createPalette'
import deepmerge from 'deepmerge'
import { makeContrasting } from '../util/color'

declare module '@mui/material/styles/createPalette' {
interface Palette {
Expand Down Expand Up @@ -113,33 +115,41 @@ const frames = [
const stopCodon = '#e22'
const startCodon = '#3e3'

export interface JBrowseThemeOptions extends ThemeOptions {
light?: ThemeOptions
dark?: ThemeOptions
}

function stockTheme() {
return {
palette: {
mode: undefined,
primary: { main: midnight },
secondary: { main: grape },
tertiary: forest,
quaternary: mandarin,
highlight: mandarin,
stopCodon,
startCodon,
bases,
frames,
framesCDS,
},
components: {
MuiLink: {
styleOverrides: {
// the default link color uses theme.palette.primary.main which is
// very bad with dark mode+midnight primary
root: ({ theme }) => ({
color: theme.palette.tertiary.main,
}),
light: {
palette: {
mode: undefined,
primary: { main: midnight },
secondary: { main: grape },
tertiary: forest,
quaternary: mandarin,
highlight: mandarin,
stopCodon,
startCodon,
bases,
frames,
framesCDS,
},
components: {
MuiLink: {
styleOverrides: {
// the default link color uses theme.palette.primary.main which is
// very bad with dark mode+midnight primary
root: ({ theme }) => ({
color: theme.palette.tertiary.main,
}),
},
},
},
},
} satisfies ThemeOptions
dark: getDarkStockTheme(),
} satisfies JBrowseThemeOptions
}

function getDefaultTheme() {
Expand All @@ -149,10 +159,10 @@ function getDefaultTheme() {
}
}

function getLightStockTheme() {
function getStockTheme() {
return {
...stockTheme(),
name: 'Light (stock)',
name: 'Stock',
}
}

Expand Down Expand Up @@ -208,28 +218,29 @@ function getDarkMinimalTheme() {

function getMinimalTheme() {
return {
name: 'Light (minimal)',
palette: {
primary: { main: grey[900] },
secondary: { main: grey[800] },
tertiary: refTheme.palette.augmentColor({ color: { main: grey[900] } }),
quaternary: mandarin,
highlight: mandarin,
stopCodon,
startCodon,
bases,
frames,
framesCDS,
name: 'Minimal',
light: {
palette: {
primary: { main: grey[900] },
secondary: { main: grey[800] },
tertiary: refTheme.palette.augmentColor({ color: { main: grey[900] } }),
quaternary: mandarin,
highlight: mandarin,
stopCodon,
startCodon,
bases,
frames,
framesCDS,
},
},
} satisfies ThemeOptions & { name: string }
dark: getDarkMinimalTheme(),
} satisfies JBrowseThemeOptions & { name: string }
}

export const defaultThemes = {
default: getDefaultTheme(),
lightStock: getLightStockTheme(),
lightMinimal: getMinimalTheme(),
darkMinimal: getDarkMinimalTheme(),
darkStock: getDarkStockTheme(),
stock: getStockTheme(),
minimal: getMinimalTheme(),
} as ThemeMap

function overwriteArrayMerge(_: unknown, sourceArray: unknown[]) {
Expand Down Expand Up @@ -455,24 +466,119 @@ export function createJBrowseBaseTheme(theme?: ThemeOptions): ThemeOptions {
return deepmerge(themeP, theme || {}, { arrayMerge: overwriteArrayMerge })
}

type ThemeMap = Record<string, ThemeOptions>
type ThemeMap = Record<string, JBrowseThemeOptions>

export function createJBrowseTheme(
configTheme: ThemeOptions = {},
configTheme: JBrowseThemeOptions = defaultThemes.default,
themes = defaultThemes,
themeName = 'default',
mode = 'light',
) {
if (Object.keys(configTheme).length === 0) {
configTheme = defaultThemes.default
}
let theme =
mode === 'light'
? configTheme?.light
: mode === 'dark'
? configTheme?.dark
: undefined
if (!theme) {
theme = generateAltTheme(
configTheme.light ?? configTheme.dark ?? (configTheme as ThemeOptions),
mode,
)
}
return createTheme(
createJBrowseBaseTheme(
themeName === 'default'
? deepmerge(themes.default, augmentTheme(configTheme), {
arrayMerge: overwriteArrayMerge,
})
: augmentThemePlus(themes[themeName]) || themes.default,
? deepmerge(
themes.default.light ?? themes.default,
augmentTheme(theme),
{
arrayMerge: overwriteArrayMerge,
},
)
: (augmentThemePlus(theme) || themes.default.light) ?? themes.default,
),
)
}

function generateAltTheme(theme: ThemeOptions = {}, mode: string) {
if (theme?.palette?.mode === 'dark' && mode === 'dark') {
return theme
}
const background = mode === 'dark' ? 'black' : 'white'
const contrast = 4.5

if (theme?.palette) {
theme = deepmerge(theme, {
palette: {
mode: mode,
},
})
}

if (theme?.palette?.primary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.primary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
primary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.secondary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.secondary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
secondary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.tertiary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.tertiary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
tertiary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}
if (theme?.palette?.quaternary) {
const contrastColor = {
main: makeContrasting(
(theme.palette.quaternary as SimplePaletteColorOptions).main,
background,
contrast,
),
}
theme = deepmerge(theme, {
palette: {
quaternary: refTheme.palette.augmentColor({ color: contrastColor }),
},
})
}

return theme
}

function augmentTheme(theme: ThemeOptions = {}) {
if (theme?.palette?.tertiary) {
theme = deepmerge(theme, {
Expand Down
Loading
Loading