focalboard/webapp/src/theme.ts

311 lines
11 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {CSSObject} from '@emotion/serialize'
import isEqual from 'lodash/isEqual'
import color from 'color'
import {Utils} from './utils'
let activeThemeName: string
import {UserSettings} from './userSettings'
export type Theme = {
mainBg: string,
mainFg: string,
buttonBg: string,
buttonFg: string,
sidebarBg: string,
sidebarFg: string,
sidebarTextActiveBorder: string,
sidebarWhiteLogo: string,
link: string,
linkVisited: string,
propDefault: string,
propGray: string,
propBrown: string,
propOrange: string,
propYellow: string,
propGreen: string,
propBlue: string,
propPurple: string,
propPink: string,
propRed: string,
}
export const systemThemeName = 'system-theme'
export const defaultThemeName = 'default-theme'
export const defaultTheme = {
mainBg: '255, 255, 255',
mainFg: '63, 67, 80',
buttonBg: '28, 88, 217',
buttonFg: '255, 255, 255',
sidebarBg: '30, 50, 92',
sidebarFg: '255, 255, 255',
sidebarTextActiveBorder: '93, 137, 243',
sidebarWhiteLogo: 'true',
link: '#0000ee',
linkVisited: '#551a8b',
propDefault: '#fff',
propGray: '#EDEDED',
propBrown: '#F7DDC3',
propOrange: '#ffd3c1',
propYellow: '#f7f0b6',
propGreen: '#c7eac3',
propBlue: '#B1D1F6',
propPurple: '#e6d0ff',
propPink: '#ffd6e9',
propRed: '#ffa9a9',
}
export const darkThemeName = 'dark-theme'
export const darkTheme = {
...defaultTheme,
mainBg: '55, 53, 47',
mainFg: '220, 220, 220',
buttonBg: '80, 170, 221',
buttonFg: '255, 255, 255',
sidebarBg: '75, 73, 67',
sidebarFg: '255, 255, 255',
sidebarTextActiveBorder: '102, 185, 167',
sidebarWhiteLogo: 'true',
link: '#0090ff',
linkVisited: 'hsla(270, 68%, 70%, 1.0)',
propDefault: 'hsla(0, 100%, 100%, 0.08)',
propGray: 'hsla(0, 0%, 70%, 0.4)',
propBrown: 'hsla(25, 60%, 40%, 0.4)',
propOrange: 'hsla(35, 100%, 50%, 0.4)',
propYellow: 'hsla(48, 100%, 70%, 0.4)',
propGreen: 'hsla(120, 100%, 70%, 0.4)',
propBlue: 'hsla(240, 100%, 70%, 0.4)',
propPurple: 'hsla(270, 100%, 64%, 0.4)',
propPink: 'hsla(310, 100%, 80%, 0.4)',
propRed: 'hsla(4, 100%, 70%, 0.4)',
}
export const lightThemeName = 'light-theme'
export const lightTheme = {
...defaultTheme,
mainBg: '255, 255, 255',
mainFg: '55, 53, 47',
buttonBg: '80, 170, 221',
buttonFg: '255, 255, 255',
sidebarBg: '247, 246, 243',
sidebarFg: '55, 53, 47',
sidebarTextActiveBorder: '87, 158, 255',
sidebarWhiteLogo: 'false',
}
export function setTheme(theme: Theme | null): Theme {
let consolidatedTheme = defaultTheme
if (theme) {
consolidatedTheme = {...defaultTheme, ...theme}
UserSettings.theme = JSON.stringify(consolidatedTheme)
} else {
UserSettings.theme = ''
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
if (darkThemeMq.matches) {
consolidatedTheme = {...defaultTheme, ...darkTheme}
}
}
setActiveThemeName(consolidatedTheme, theme)
if (!Utils.isFocalboardPlugin()) {
document.documentElement.style.setProperty('--center-channel-bg-rgb', consolidatedTheme.mainBg)
document.documentElement.style.setProperty('--center-channel-color-rgb', consolidatedTheme.mainFg)
document.documentElement.style.setProperty('--button-bg-rgb', consolidatedTheme.buttonBg)
document.documentElement.style.setProperty('--button-color-rgb', consolidatedTheme.buttonFg)
document.documentElement.style.setProperty('--sidebar-bg-rgb', consolidatedTheme.sidebarBg)
document.documentElement.style.setProperty('--sidebar-text-rgb', consolidatedTheme.sidebarFg)
document.documentElement.style.setProperty('--link-color-rgb', consolidatedTheme.link)
document.documentElement.style.setProperty('--sidebar-text-active-border-rgb', consolidatedTheme.sidebarTextActiveBorder)
}
document.documentElement.style.setProperty('--sidebar-white-logo', consolidatedTheme.sidebarWhiteLogo)
document.documentElement.style.setProperty('--link-visited-color-rgb', consolidatedTheme.linkVisited)
const mainBgColor = color(`rgb(${getComputedStyle(document.documentElement).getPropertyValue('--center-channel-bg-rgb')})`)
if (Utils.isFocalboardPlugin()) {
let fixedTheme = lightTheme
if (mainBgColor.isDark()) {
fixedTheme = darkTheme
}
consolidatedTheme.propDefault = fixedTheme.propDefault
consolidatedTheme.propGray = fixedTheme.propGray
consolidatedTheme.propBrown = fixedTheme.propBrown
consolidatedTheme.propOrange = fixedTheme.propOrange
consolidatedTheme.propYellow = fixedTheme.propYellow
consolidatedTheme.propGreen = fixedTheme.propGreen
consolidatedTheme.propBlue = fixedTheme.propBlue
consolidatedTheme.propPurple = fixedTheme.propPurple
consolidatedTheme.propPink = fixedTheme.propPink
consolidatedTheme.propRed = fixedTheme.propRed
}
document.documentElement.style.setProperty('--prop-default', consolidatedTheme.propDefault)
document.documentElement.style.setProperty('--prop-gray', consolidatedTheme.propGray)
document.documentElement.style.setProperty('--prop-brown', consolidatedTheme.propBrown)
document.documentElement.style.setProperty('--prop-orange', consolidatedTheme.propOrange)
document.documentElement.style.setProperty('--prop-yellow', consolidatedTheme.propYellow)
document.documentElement.style.setProperty('--prop-green', consolidatedTheme.propGreen)
document.documentElement.style.setProperty('--prop-blue', consolidatedTheme.propBlue)
document.documentElement.style.setProperty('--prop-purple', consolidatedTheme.propPurple)
document.documentElement.style.setProperty('--prop-pink', consolidatedTheme.propPink)
document.documentElement.style.setProperty('--prop-red', consolidatedTheme.propRed)
return consolidatedTheme
}
export function setMattermostTheme(theme: any): Theme {
if (!theme) {
return setTheme(defaultTheme)
}
document.documentElement.style.setProperty('--center-channel-bg-rgb', color(theme.centerChannelBg).rgb().array().join(', '))
document.documentElement.style.setProperty('--center-channel-color-rgb', color(theme.centerChannelColor).rgb().array().join(', '))
document.documentElement.style.setProperty('--button-bg-rgb', color(theme.buttonBg).rgb().array().join(', '))
document.documentElement.style.setProperty('--button-color-rgb', color(theme.buttonColor).rgb().array().join(', '))
document.documentElement.style.setProperty('--sidebar-bg-rgb', color(theme.sidebarBg).rgb().array().join(', '))
document.documentElement.style.setProperty('--sidebar-text-rgb', color(theme.sidebarText).rgb().array().join(', '))
document.documentElement.style.setProperty('--link-color-rgb', theme.linkColor)
document.documentElement.style.setProperty('--sidebar-text-active-border-rgb', color(theme.sidebarTextActiveBorder).rgb().array().join(', '))
return setTheme({
...defaultTheme,
mainBg: color(theme.centerChannelBg).rgb().array().join(', '),
mainFg: color(theme.centerChannelColor).rgb().array().join(', '),
buttonBg: color(theme.buttonBg).rgb().array().join(', '),
buttonFg: color(theme.buttonColor).rgb().array().join(', '),
sidebarBg: color(theme.sidebarBg).rgb().array().join(', '),
sidebarFg: color(theme.sidebarColor || '#ffffff').rgb().array().join(', '),
sidebarTextActiveBorder: color(theme.sidebarTextActiveBorder).rgb().array().join(', '),
link: theme.linkColor,
})
}
function setActiveThemeName(consolidatedTheme: Theme, theme: Theme | null) {
if (theme === null) {
activeThemeName = systemThemeName
} else if (isEqual(consolidatedTheme, darkTheme)) {
activeThemeName = darkThemeName
} else if (isEqual(consolidatedTheme, lightTheme)) {
activeThemeName = lightThemeName
} else {
activeThemeName = defaultThemeName
}
}
export function loadTheme(): Theme {
const themeStr = UserSettings.theme
if (themeStr) {
try {
const theme = JSON.parse(themeStr)
const consolidatedTheme = setTheme(theme)
setActiveThemeName(consolidatedTheme, theme)
return consolidatedTheme
} catch (e) {
return setTheme(null)
}
} else {
return setTheme(null)
}
}
export function initThemes(): void {
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
const changeHandler = () => {
const themeStr = UserSettings.theme
if (!themeStr) {
setTheme(null)
}
}
if (darkThemeMq.addEventListener) {
darkThemeMq.addEventListener('change', changeHandler)
} else if (darkThemeMq.addListener) {
// Safari and Mac app support
darkThemeMq.addListener(changeHandler)
}
loadTheme()
}
export function getSelectBaseStyle() {
return {
dropdownIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none !important',
}),
indicatorSeparator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
loadingIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
clearIndicator: (provided: CSSObject): CSSObject => ({
...provided,
display: 'none',
}),
menu: (provided: CSSObject): CSSObject => ({
...provided,
width: 'unset',
background: 'rgb(var(--center-channel-bg-rgb))',
}),
option: (provided: CSSObject, state: { isFocused: boolean }): CSSObject => ({
...provided,
background: state.isFocused ? 'rgba(var(--center-channel-color-rgb), 0.1)' : 'rgb(var(--center-channel-bg-rgb))',
color: state.isFocused ? 'rgb(var(--center-channel-color-rgb))' : 'rgb(var(--center-channel-color-rgb))',
padding: '2px 8px',
}),
control: (): CSSObject => ({
border: 0,
width: '100%',
margin: '4px 0 0 0',
// display: 'flex',
// marginTop: 0,
}),
valueContainer: (provided: CSSObject): CSSObject => ({
...provided,
padding: '0 5px',
overflow: 'unset',
}),
singleValue: (provided: CSSObject): CSSObject => ({
...provided,
color: 'rgb(var(--center-channel-color-rgb))',
overflow: 'unset',
maxWidth: 'calc(100% - 20px)',
}),
input: (provided: CSSObject): CSSObject => ({
...provided,
paddingBottom: 0,
paddingTop: 0,
marginBottom: 0,
marginTop: 0,
}),
menuList: (provided: CSSObject): CSSObject => ({
...provided,
overflowY: 'auto',
overflowX: 'hidden',
}),
}
}
export function getActiveThemeName(): string {
return activeThemeName || defaultThemeName
}