Skip to content

Commit

Permalink
[C-4243] Fix theming on first render (#8046)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanjeffers authored Apr 8, 2024
1 parent 1e0b795 commit 05d6096
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 104 deletions.
8 changes: 7 additions & 1 deletion packages/harmony/src/foundations/theme/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'
import { useEffect, type ReactNode } from 'react'

import { ThemeProvider as EmotionThemeProvider } from '@emotion/react'

Expand All @@ -13,6 +13,12 @@ type ThemeProviderProps = {
export const ThemeProvider = (props: ThemeProviderProps) => {
const { children, theme } = props

useEffect(() => {
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-theme', theme)
}
}, [theme])

return (
<EmotionThemeProvider theme={themes[theme]}>
{children}
Expand Down
13 changes: 12 additions & 1 deletion packages/web/src/app/AppProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HeaderContextProvider } from 'components/header/mobile/HeaderContextPro
import { NavProvider } from 'components/nav/store/context'
import { ScrollProvider } from 'components/scroll-provider/ScrollProvider'
import { ToastContextProvider } from 'components/toast/ToastContext'
import { getSystemAppearance, getTheme } from 'utils/theme/theme'

import { MainContentContextProvider } from '../pages/MainContentContext'

Expand All @@ -24,8 +25,18 @@ type AppContextProps = {

export const AppProviders = ({ children }: AppContextProps) => {
const { history } = useHistoryContext()

const initialStoreState = {
ui: {
theme: {
theme: getTheme(),
systemPreference: getSystemAppearance()
}
}
}

return (
<ReduxProvider>
<ReduxProvider initialStoreState={initialStoreState}>
<ConnectedRouter history={history}>
<LastLocationProvider>
<TrpcProvider>
Expand Down
5 changes: 3 additions & 2 deletions packages/web/src/app/ReduxProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactNode, useRef } from 'react'
import { Provider } from 'react-redux'
import { Persistor, persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
import { PartialDeep } from 'type-fest'

import { useIsMobile } from 'hooks/useIsMobile'
import { configureStore } from 'store/configureStore'
Expand All @@ -18,8 +19,8 @@ export const ReduxProvider = ({
initialStoreState
}: {
children: ReactNode
// Optional (for testing purposes) - sets up an initial store state
initialStoreState?: Partial<AppState>
// Sets up an initial store state
initialStoreState?: PartialDeep<AppState>
}) => {
const { pageProps, isServerSide } = useSsrContext()
const { history } = useHistoryContext()
Expand Down
44 changes: 3 additions & 41 deletions packages/web/src/app/web-player/WebPlayer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ import {
Name,
Client,
SmartCollectionVariant,
Status,
Theme,
SystemAppearance
Status
} from '@audius/common/models'
import { StringKeys, FeatureFlags } from '@audius/common/services'
import {
accountSelectors,
ExploreCollectionsVariant,
themeActions,
themeSelectors,
UploadType
} from '@audius/common/store'
import cn from 'classnames'
Expand Down Expand Up @@ -82,7 +78,7 @@ import { PremiumTracksPage } from 'pages/premium-tracks-page/PremiumTracksPage'
import ProfilePage from 'pages/profile-page/ProfilePage'
import RemixesPage from 'pages/remixes-page/RemixesPage'
import RepostsPage from 'pages/reposts-page/RepostsPage'
import RequiresUpdate from 'pages/requires-update/RequiresUpdate'
import { RequiresUpdate } from 'pages/requires-update/RequiresUpdate'
import SavedPage from 'pages/saved-page/SavedPage'
import SearchPage from 'pages/search-page/SearchPage'
import SettingsPage from 'pages/settings-page/SettingsPage'
Expand Down Expand Up @@ -182,16 +178,9 @@ import {
PURCHASES_PAGE,
SALES_PAGE
} from 'utils/route'
import {
doesPreferDarkMode,
getTheme as getSystemTheme
} from 'utils/theme/theme'

import styles from './WebPlayer.module.css'

const { setTheme, setSystemAppearance } = themeActions
const { getTheme, getSystemAppearance } = themeSelectors

const { getHasAccount, getAccountStatus, getUserId, getUserHandle } =
accountSelectors

Expand Down Expand Up @@ -334,8 +323,6 @@ class WebPlayer extends Component {
}
}
}

this.handleTheme()
}

componentDidUpdate(prevProps) {
Expand All @@ -359,10 +346,6 @@ class WebPlayer extends Component {
this.pushWithToken(TRENDING_PAGE)
}
}

if (prevProps.theme !== this.props.theme) {
this.handleTheme()
}
}

componentWillUnmount() {
Expand All @@ -376,20 +359,6 @@ class WebPlayer extends Component {
}
}

handleTheme() {
const { theme, systemAppearance, setTheme, setSystemAppearance } =
this.props
// Set local theme
if (theme === null) {
setTheme(getSystemTheme() || Theme.DEFAULT)
}
if (systemAppearance === null) {
setSystemAppearance(
doesPreferDarkMode() ? SystemAppearance.DARK : SystemAppearance.LIGHT
)
}
}

pushWithToken = (route) => {
const search = this.props.location.search
// Twitter and instagram search params
Expand Down Expand Up @@ -429,7 +398,7 @@ class WebPlayer extends Component {
}

render() {
const { theme, incrementScroll, decrementScroll, userHandle } = this.props
const { incrementScroll, decrementScroll, userHandle } = this.props

const {
showWebUpdateBanner,
Expand All @@ -444,7 +413,6 @@ class WebPlayer extends Component {
if (showRequiresUpdate)
return (
<RequiresUpdate
theme={theme}
isUpdating={isUpdating}
onUpdate={this.acceptUpdateApp}
/>
Expand All @@ -453,7 +421,6 @@ class WebPlayer extends Component {
if (showRequiresWebUpdate)
return (
<RequiresUpdate
theme={theme}
isUpdating={isUpdating}
onUpdate={this.acceptWebUpdate}
/>
Expand Down Expand Up @@ -1022,16 +989,11 @@ const mapStateToProps = (state) => ({
userHandle: getUserHandle(state),
accountStatus: getAccountStatus(state),
signOnStatus: getSignOnStatus(state),
theme: getTheme(state),
systemAppearance: getSystemAppearance(state),
showCookieBanner: getShowCookieBanner(state),
isChatEnabled: getFeatureEnabled(FeatureFlags.CHAT_ENABLED)
})

const mapDispatchToProps = (dispatch) => ({
setTheme: (theme) => dispatch(setTheme({ theme })),
setSystemAppearance: (systemAppearance) =>
dispatch(setSystemAppearance({ systemAppearance })),
updateRouteOnSignUpCompletion: (route) =>
dispatch(updateRouteOnSignUpCompletion(route)),
openSignOn: (signIn = true, page = null, fields = {}) =>
Expand Down
51 changes: 24 additions & 27 deletions packages/web/src/pages/requires-update/RequiresUpdate.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Theme } from '@audius/common/models'
import { Button } from '@audius/harmony'
import { Box, Button } from '@audius/harmony'

import tileBackground from 'assets/img/notFoundTiledBackround.png'
import { isMatrix, shouldShowDark } from 'utils/theme/theme'

import styles from './RequiresUpdate.module.css'

Expand All @@ -13,33 +11,32 @@ const messages = {
buttonIsUpdating: 'UPDATING'
}

type SomethingWrongProps = {
type RequiresUpdateProps = {
isUpdating: boolean
theme: Theme
onUpdate: () => void
}

const SomethingWrong = ({
isUpdating,
onUpdate,
theme
}: SomethingWrongProps) => (
<div className={styles.requiresUpdate}>
<div
className={styles.content}
style={{
backgroundImage: `url(${tileBackground})`,
backgroundBlendMode:
shouldShowDark(theme) || isMatrix() ? 'color-burn' : 'none'
}}
>
<div className={styles.title}>{messages.title}</div>
<div className={styles.subtitle}>{messages.subtitle}</div>
<Button variant='primary' isLoading={isUpdating} onClick={onUpdate}>
{isUpdating ? messages.buttonIsUpdating : messages.buttonUpdate}
</Button>
export const RequiresUpdate = (props: RequiresUpdateProps) => {
const { isUpdating, onUpdate } = props
return (
<div className={styles.requiresUpdate}>
<div
className={styles.content}
css={(theme) => ({
backgroundImage: `url(${tileBackground})`,
backgroundBlendMode: theme.type === 'day' ? 'none' : 'color-burn'
})}
>
<div className={styles.title}>{messages.title}</div>
<div className={styles.subtitle}>{messages.subtitle}</div>
<Box>
<Button variant='primary' isLoading={isUpdating} onClick={onUpdate}>
{isUpdating ? messages.buttonIsUpdating : messages.buttonUpdate}
</Button>
</Box>
</div>
</div>
</div>
)
)
}

export default SomethingWrong
export default RequiresUpdate
8 changes: 3 additions & 5 deletions packages/web/src/store/application/ui/theme/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SystemAppearance, Theme } from '@audius/common/models'
import { Theme } from '@audius/common/models'
import { themeActions } from '@audius/common/store'
import { actionChannelDispatcher } from '@audius/common/utils'
import { PayloadAction } from '@reduxjs/toolkit'
Expand All @@ -8,7 +8,7 @@ import { spawn, takeEvery } from 'redux-saga/effects'
import {
setTheme,
PREFERS_DARK_MEDIA_QUERY,
doesPreferDarkMode
getSystemAppearance
} from 'utils/theme/theme'
const { setTheme: setThemeAction, setSystemAppearance } = themeActions

Expand All @@ -31,9 +31,7 @@ function* setThemeAsync(action: PayloadAction<{ theme: Theme }>) {
mqlListener = () => {
emitter(
setSystemAppearance({
systemAppearance: doesPreferDarkMode()
? SystemAppearance.DARK
: SystemAppearance.LIGHT
systemAppearance: getSystemAppearance()
})
)
emitter(setThemeAction({ theme: Theme.AUTO }))
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/store/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createStore, applyMiddleware, Action, Store } from 'redux'
import createSagaMiddleware from 'redux-saga'
import createSentryMiddleware from 'redux-sentry-middleware'
import thunk from 'redux-thunk'
import { PartialDeep } from 'type-fest'

import { track as amplitudeTrack } from 'services/analytics/amplitude'
import { audiusSdk } from 'services/audius-sdk'
Expand Down Expand Up @@ -97,7 +98,7 @@ export const configureStore = (
isMobile: boolean,
ssrPageProps?: SsrPageProps,
isServerSide?: boolean,
initialStoreState?: Partial<AppState>
initialStoreState?: PartialDeep<AppState>
) => {
const onSagaError = (
error: Error,
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/test/test-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ReactElement, ReactNode } from 'react'

import { DeepPartial } from '@audius/common/utils'
import { ThemeProvider } from '@audius/harmony'
import { render, RenderOptions } from '@testing-library/react'
import { PartialDeep } from 'type-fest'

import { ReduxProvider } from 'app/ReduxProvider'
import { RouterContextProvider } from 'components/animated-switch/RouterContextProvider'
import { ToastContextProvider } from 'components/toast/ToastContext'
import { AppState } from 'store/types'

type TestOptions = {
reduxState?: DeepPartial<AppState>
reduxState?: PartialDeep<AppState>
}

type TestProvidersProps = {
Expand All @@ -23,7 +23,7 @@ const TestProviders =
const { reduxState } = options ?? {}
return (
<ThemeProvider theme='day'>
<ReduxProvider initialStoreState={reduxState as unknown as AppState}>
<ReduxProvider initialStoreState={reduxState}>
<RouterContextProvider>
<ToastContextProvider>{children} </ToastContextProvider>
</RouterContextProvider>
Expand Down
30 changes: 7 additions & 23 deletions packages/web/src/utils/theme/theme.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
import { Theme } from '@audius/common/models'
import { SystemAppearance, Theme } from '@audius/common/models'

const THEME_KEY = 'theme'
export const PREFERS_DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)'

const getThemeNameKey = (theme: Theme) => {
switch (theme) {
case Theme.AUTO:
if (doesPreferDarkMode()) {
return Theme.DARK
}
return Theme.DEFAULT
case Theme.DEFAULT:
case Theme.DARK:
case Theme.MATRIX:
default:
return theme
}
}

const applyTheme = (theme: Theme) => {
// Set data-theme to enable theme scoped css rules
document.documentElement.setAttribute('data-theme', getThemeNameKey(theme))
}

export const doesPreferDarkMode = () => {
return (
window.matchMedia && window.matchMedia(PREFERS_DARK_MEDIA_QUERY).matches
typeof window !== 'undefined' &&
window.matchMedia &&
window.matchMedia(PREFERS_DARK_MEDIA_QUERY).matches
)
}

Expand All @@ -37,7 +19,6 @@ export const shouldShowDark = (theme?: Theme | null) => {
}

export const setTheme = (theme: Theme) => {
applyTheme(theme)
if (typeof window !== 'undefined') {
window.localStorage.setItem(THEME_KEY, theme)
}
Expand All @@ -54,6 +35,9 @@ export const getTheme = (): Theme | null => {
return null
}

export const getSystemAppearance = () =>
doesPreferDarkMode() ? SystemAppearance.DARK : SystemAppearance.LIGHT

export const isDarkMode = () => shouldShowDark(getTheme())
export const isMatrix = () => getTheme() === Theme.MATRIX

Expand Down

0 comments on commit 05d6096

Please sign in to comment.