Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/common/src/messages/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const settingsMessages = {
lightMode: 'Light',
autoMode: 'Auto',
matrixMode: 'Matrix',
defaultPalette: 'Default',
classicPalette: 'Classic',
signOut: 'Sign Out',

appearanceTitle: 'Appearance',
Expand All @@ -29,7 +31,7 @@ export const settingsMessages = {
desktopAppCardTitle: 'Download the Desktop App',

appearanceDescription:
'Enable dark mode or use the default setting to match your system preferences.',
'Choose a theme palette and enable dark mode or use the default setting to match your system preferences.',
inboxSettingsCardDescription:
'Configure who is able to send messages to your inbox.',
commentSettingsCardDescription:
Expand Down
19 changes: 19 additions & 0 deletions packages/common/src/models/Theme.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/** Color mode - auto follows system, light/dark are explicit. Used in segmented control. */
export enum ThemeMode {
AUTO = 'auto',
LIGHT = 'light',
DARK = 'dark'
}

/** Theme palette - selected in dropdown. Default and classic have light/dark variants. */
export enum ThemePalette {
DEFAULT = 'default',
CLASSIC = 'classic',
MATRIX = 'matrix'
}

/**
* @deprecated Use ThemePalette + ThemeMode. Legacy theme enum.
* - LIGHT/DARK/AUTO = mode (map to ThemeMode)
* - MATRIX = palette (map to ThemePalette.MATRIX)
*/
export enum Theme {
DARK = 'dark',
AUTO = 'auto',
Expand Down
6 changes: 4 additions & 2 deletions packages/common/src/services/remote-config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export enum FeatureFlags {
FAST_REFERRAL = 'fast_referral',
REACT_QUERY_SYNC = 'react_query_sync',
COLLAPSED_EXPLORE_HEADER = 'collapsed_explore_header',
LAUNCHPAD_VERIFICATION = 'launchpad_verification'
LAUNCHPAD_VERIFICATION = 'launchpad_verification',
NEW_THEME_MODEL = 'new_theme_model'
}

type FlagDefaults = Record<FeatureFlags, boolean>
Expand Down Expand Up @@ -43,5 +44,6 @@ export const flagDefaults: FlagDefaults = {
[FeatureFlags.FAST_REFERRAL]: false,
[FeatureFlags.REACT_QUERY_SYNC]: false,
[FeatureFlags.COLLAPSED_EXPLORE_HEADER]: false,
[FeatureFlags.LAUNCHPAD_VERIFICATION]: true
[FeatureFlags.LAUNCHPAD_VERIFICATION]: true,
[FeatureFlags.NEW_THEME_MODEL]: false
}
7 changes: 6 additions & 1 deletion packages/common/src/store/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ export {
} from './coinflow-modal/slice'

export { default as themeReducer, actions as themeActions } from './theme/slice'
export type { SetThemeAction, SetSystemAppearanceAction } from './theme/slice'
export type {
SetThemeAction,
SetThemePaletteAction,
SetThemeModeAction,
SetSystemAppearanceAction
} from './theme/slice'
export * as themeSelectors from './theme/selectors'

export { default as toastReducer, actions as toastActions } from './toast/slice'
Expand Down
11 changes: 11 additions & 0 deletions packages/common/src/store/ui/theme/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ThemePalette } from '~/models/Theme'
import { CommonState } from '~/store/commonStore'

const getBaseState = (state: CommonState) => state.ui.theme
Expand All @@ -6,6 +7,16 @@ export const getTheme = (state: CommonState) => {
return getBaseState(state).theme
}

/** Palette from dropdown (default, classic, matrix). Falls back to classic for legacy. */
export const getThemePalette = (state: CommonState): ThemePalette | null => {
return getBaseState(state).themePalette
}

/** Mode from segmented control (auto, light, dark). */
export const getThemeMode = (state: CommonState) => {
return getBaseState(state).themeMode
}

export const getSystemAppearance = (state: CommonState) => {
return getBaseState(state).systemAppearance
}
55 changes: 53 additions & 2 deletions packages/common/src/store/ui/theme/slice.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { SystemAppearance, Theme } from '../../../models/Theme'
import {
SystemAppearance,
Theme,
ThemeMode,
ThemePalette
} from '../../../models/Theme'
import { Nullable } from '../../../utils'

export type ThemeState = {
/** @deprecated Use themePalette + themeMode. Legacy - when MATRIX, palette is matrix. */
theme: Nullable<Theme>
/** Palette selected in dropdown: default, classic, matrix */
themePalette: Nullable<ThemePalette>
/** Mode selected in segmented control: auto, light, dark. Ignored when palette is matrix. */
themeMode: Nullable<ThemeMode>
systemAppearance: Nullable<SystemAppearance>
}

export type SetThemeAction = PayloadAction<{
theme: Theme
}>

export type SetThemePaletteAction = PayloadAction<{
themePalette: ThemePalette
}>

export type SetThemeModeAction = PayloadAction<{
themeMode: ThemeMode
}>

export type SetSystemAppearanceAction = PayloadAction<{
systemAppearance: SystemAppearance
}>

const initialState: ThemeState = {
theme: null,
themePalette: null,
themeMode: null,
systemAppearance: null
}

Expand All @@ -27,13 +47,44 @@ const themeSlice = createSlice({
reducers: {
setTheme: (state, action: SetThemeAction) => {
state.theme = action.payload.theme
// Sync legacy theme to new format for backward compat
if (action.payload.theme === Theme.MATRIX) {
state.themePalette = ThemePalette.MATRIX
} else {
state.themeMode =
action.payload.theme === Theme.LIGHT
? ThemeMode.LIGHT
: action.payload.theme === Theme.DARK
? ThemeMode.DARK
: ThemeMode.AUTO
// Preserve palette when updating mode; default to classic for legacy
if (state.themePalette === null) {
state.themePalette = ThemePalette.CLASSIC
}
}
},
setThemePalette: (state, action: SetThemePaletteAction) => {
state.themePalette = action.payload.themePalette
if (action.payload.themePalette === ThemePalette.MATRIX) {
state.theme = Theme.MATRIX
}
},
setThemeMode: (state, action: SetThemeModeAction) => {
state.themeMode = action.payload.themeMode
state.theme =
action.payload.themeMode === ThemeMode.LIGHT
? Theme.LIGHT
: action.payload.themeMode === ThemeMode.DARK
? Theme.DARK
: Theme.AUTO
},
setSystemAppearance: (state, action: SetSystemAppearanceAction) => {
state.systemAppearance = action.payload.systemAppearance
}
}
})

export const { setTheme, setSystemAppearance } = themeSlice.actions
export const { setTheme, setThemePalette, setThemeMode, setSystemAppearance } =
themeSlice.actions
export default themeSlice.reducer
export const actions = themeSlice.actions
6 changes: 4 additions & 2 deletions packages/harmony/src/components/button/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CSSProperties, forwardRef } from 'react'

import { CSSObject, useTheme } from '@emotion/react'

import { isDarkTheme } from '../../../foundations/theme/theme'
import { BaseButton } from '../BaseButton/BaseButton'

import { ButtonProps } from './types'
Expand Down Expand Up @@ -125,8 +126,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
boxShadow: `0 0 0 1px inset ${themeColors.border.strong}`
}
const tertiaryStyles: CSSObject = {
background:
type === 'dark' ? 'rgba(50, 51, 77, 0.6)' : 'rgb(255, 255, 255, 0.85)',
background: isDarkTheme(type)
? 'rgba(50, 51, 77, 0.6)'
: 'rgb(255, 255, 255, 0.85)',
color: themeColors.text.default,
backdropFilter: 'blur(6px)',
boxShadow: `0 0 0 1px inset ${themeColors.border.default}, ${shadows.near}`,
Expand Down
26 changes: 22 additions & 4 deletions packages/harmony/src/foundations/color/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@ import { primitiveTheme } from './primitive'
import { semanticTheme } from './semantic'

export const colorTheme = {
defaultLight: {
...primitiveTheme.defaultLight,
...semanticTheme.defaultLight
},
defaultDark: {
...primitiveTheme.defaultDark,
...semanticTheme.defaultDark
},
classicLight: {
...primitiveTheme.classicLight,
...semanticTheme.classicLight
},
classicDark: {
...primitiveTheme.classicDark,
...semanticTheme.classicDark
},
matrix: {
...primitiveTheme.matrix,
...semanticTheme.matrix
},
/** @deprecated Use classicLight */
day: {
...primitiveTheme.day,
...semanticTheme.day
},
/** @deprecated Use classicDark */
dark: {
...primitiveTheme.dark,
...semanticTheme.dark
},
matrix: {
...primitiveTheme.matrix,
...semanticTheme.matrix
}
}

Expand Down
Loading