feat: make theme color and font persistent fixes #40

This commit is contained in:
Hossein Mehrabi 2023-08-01 16:50:58 +03:30
parent 07711572fe
commit 88c9d8eba5
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
15 changed files with 199 additions and 88 deletions

View File

@ -1,16 +1,15 @@
import styled from '@emotion/styled'
import { CloseIcon, IconButton, SearchIcon } from '@acid-info/lsd-react'
import { LogosIcon } from '../Icons/LogosIcon'
import { SunIcon } from '../Icons/SunIcon'
import { MoonIcon } from '../Icons/MoonIcon'
import { uiConfigs } from '@/configs/ui.configs'
import Link from 'next/link'
import styles from '@/components/Searchbar/Search.module.css'
import React, { useEffect, useState } from 'react'
import { Searchbar } from '@/components/Searchbar'
import { useScrollDirection } from '@/utils/ui.utils'
import { useRouter } from 'next/router'
import { uiConfigs } from '@/configs/ui.configs'
import { useSearchBarContext } from '@/context/searchbar.context'
import { useScrollDirection } from '@/utils/ui.utils'
import { IconButton, SearchIcon } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { LogosIcon } from '../Icons/LogosIcon'
import { MoonIcon } from '../Icons/MoonIcon'
import { SunIcon } from '../Icons/SunIcon'
interface AppBarProps {
isDark: boolean
@ -58,11 +57,8 @@ export default function AppBar({
</LogosIconContainer>
<Icons>
<IconButton size="small" onClick={() => toggle()}>
{isDark ? (
<SunIcon color="primary" />
) : (
<MoonIcon color="primary" />
)}
<SunIcon color="primary" className="light-mode-hidden" />
<MoonIcon color="primary" className="dark-mode-hidden" />
</IconButton>
<IconButton
className={'searchIcon searchIconHome'}

View File

@ -1,8 +1,5 @@
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import React from 'react'
import { Collapse } from '@/components/Collapse'
import useIsDarkState from '@/states/isDarkState/isDarkState'
type Props = {
summary: string

View File

@ -1,25 +1,9 @@
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { defaultThemes } from '@acid-info/lsd-react'
import NextNProgress from 'nextjs-progressbar'
import { useCallback, useEffect, useState } from 'react'
export const ProgressBar = () => {
const isDark = useIsDarkState().get()
const getColor = useCallback(() => {
if (isDark) {
return `rgb(${defaultThemes.dark.palette.primary})`
} else {
return `rgb(${defaultThemes.dark.palette.secondary})`
}
}, [isDark])
const [color, setColor] = useState(getColor())
useEffect(() => setColor(getColor()), [isDark, getColor])
return (
<NextNProgress
color={color}
color="rgb(var(--lsd-surface-secondary))"
height={1}
options={{
showSpinner: false,

View File

@ -0,0 +1,29 @@
import { ThemeProvider, ThemeProviderProps } from '@acid-info/lsd-react'
import { Global } from '@emotion/react'
import React, { useEffect } from 'react'
import useThemeState from '../../states/themeState/theme.state'
import { useLSDTheme } from './themes'
export type LSDThemeProviderProps = Partial<ThemeProviderProps>
export const LSDThemeProvider: React.FC<LSDThemeProviderProps> = ({
children,
...props
}) => {
const theme = useLSDTheme()
const { mode, genericFontFamily } = useThemeState()
useEffect(() => {
const html = document.querySelector('html') as HTMLElement
html.setAttribute('data-theme', mode.value)
html.setAttribute('data-font-family', genericFontFamily.value)
}, [mode.value, genericFontFamily.value])
return (
<ThemeProvider theme={theme.current} injectCssVars={false}>
{children}
<Global styles={theme.darkCssVars} />
<Global styles={theme.lightCssVars} />
</ThemeProvider>
)
}

View File

@ -0,0 +1 @@
export * from './LSDThemeProvider'

View File

@ -0,0 +1,61 @@
import {
createTheme,
CreateThemeProps,
defaultThemes,
Theme,
} from '@acid-info/lsd-react'
import { css } from '@emotion/react'
import { useMemo } from 'react'
import { useThemeState } from '../../states/themeState/theme.state'
const baseThemes = defaultThemes
const useThemeCssVars = (theme: Theme, colorMode: string) =>
useMemo(
() => css`
[data-theme=${colorMode}] {
${theme.cssVars}
}
[data-font-family='sans-serif'] {
--lsd-typography-generic-font-family: sans-serif;
}
[data-font-family='serif'] {
--lsd-typography-generic-font-family: serif;
}
[data-font-family='monospace'] {
--lsd-typography-generic-font-family: monospace;
}
`,
[theme],
)
export const useLSDTheme = () => {
const { genericFontFamily, mode } = useThemeState().get()
const themes = useMemo(() => {
const options: CreateThemeProps = {
breakpoints: {},
palette: {},
typography: {},
typographyGlobal: {
genericFontFamily: genericFontFamily,
},
}
return {
light: createTheme(options, baseThemes.light),
dark: createTheme(options, baseThemes.dark),
}
}, [baseThemes, genericFontFamily])
return {
dark: themes.dark,
light: themes.light,
current: themes[mode],
lightCssVars: useThemeCssVars(themes.light, 'light'),
darkCssVars: useThemeCssVars(themes.dark, 'dark'),
}
}

View File

@ -1,24 +1,24 @@
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import styles from './Article.layout.module.css'
import { AppBar } from '@/components/AppBar'
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { useArticleContext } from '@/context/article.context'
import { AppBar } from '@/components/AppBar'
import { PropsWithChildren } from 'react'
import { useThemeState } from '../../states/themeState'
import styles from './Article.layout.module.css'
type Props = PropsWithChildren<{
// onSearch: (query: string, filters: string[]) => void
}>
export default function ArticleLayout({ children }: Props) {
const isDarkState = useIsDarkState()
const themeState = useThemeState()
const { onSearch, onReset } = useArticleContext()
return (
<>
<header className={styles.header}>
<AppBar
isDark={isDarkState.get()}
toggle={isDarkState.toggle}
isDark={themeState.mode.get() === 'dark'}
toggle={themeState.toggleMode}
onSearch={onSearch}
onReset={onReset}
/>

View File

@ -1,18 +1,15 @@
import { AppBar } from '../../components/AppBar'
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import { Hero } from '@/components/Hero'
import { NavbarFiller } from '@/components/AppBar/NavbarFiller'
import { Searchbar } from '@/components/Searchbar'
import { Footer } from '@/components/Footer'
import { Hero } from '@/components/Hero'
import { Main } from '@/components/Main'
import { Searchbar } from '@/components/Searchbar'
import { uiConfigs } from '@/configs/ui.configs'
import { useRouter } from 'next/router'
import { defaultThemes } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { PropsWithChildren } from 'react'
import { AppBar } from '../../components/AppBar'
import { useThemeState } from '../../states/themeState'
export default function DefaultLayout(props: PropsWithChildren<any>) {
const isDarkState = useIsDarkState()
const themeState = useThemeState()
return (
<>
@ -23,7 +20,10 @@ export default function DefaultLayout(props: PropsWithChildren<any>) {
}}
>
<div>
<AppBar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
<AppBar
isDark={themeState.mode.get() === 'dark'}
toggle={themeState.toggleMode}
/>
<Hero />
</div>
<Searchbar withFilterTags={false} beSticky={true} />

View File

@ -1,18 +1,22 @@
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import styles from './Search.layout.module.css'
import { AppBar } from '@/components/AppBar'
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { AppBar } from '@/components/AppBar'
import styled from '@emotion/styled'
import { uiConfigs } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import { PropsWithChildren } from 'react'
import { useThemeState } from '../../states/themeState'
import styles from './Search.layout.module.css'
export default function SearchLayout(props: PropsWithChildren<any>) {
const isDarkState = useIsDarkState()
const themeState = useThemeState()
return (
<>
<header className={styles.header}>
<AppBar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
<AppBar
isDark={themeState.mode.get() === 'dark'}
toggle={themeState.toggleMode}
/>
</header>
<MainContainer className={'search_page'}>{props.children}</MainContainer>
<Footer />

View File

@ -1,14 +1,13 @@
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { defaultThemes, ThemeProvider } from '@acid-info/lsd-react'
import { ProgressBar } from '@/components/ProgressBar/ProgressBar'
import { uiConfigs } from '@/configs/ui.configs'
import { SearchBarProvider } from '@/context/searchbar.context'
import { DefaultLayout } from '@/layouts/DefaultLayout'
import { css, Global } from '@emotion/react'
import { NextComponentType, NextPageContext } from 'next'
import type { AppProps } from 'next/app'
import Head from 'next/head'
import { uiConfigs } from '@/configs/ui.configs'
import { DefaultLayout } from '@/layouts/DefaultLayout'
import { ReactNode } from 'react'
import { NextComponentType, NextPageContext } from 'next'
import { SearchBarProvider } from '@/context/searchbar.context'
import { ProgressBar } from '@/components/ProgressBar/ProgressBar'
import { LSDThemeProvider } from '../containers/LSDThemeProvider'
type NextLayoutComponentType<P = {}> = NextComponentType<
NextPageContext,
@ -23,14 +22,12 @@ type AppLayoutProps<P = {}> = AppProps & {
}
export default function App({ Component, pageProps }: AppLayoutProps) {
const isDark = useIsDarkState().get()
const getLayout =
Component.getLayout ||
((page: ReactNode) => <DefaultLayout>{page}</DefaultLayout>)
return (
<ThemeProvider theme={isDark ? defaultThemes.dark : defaultThemes.light}>
<LSDThemeProvider>
<Head>
<title>Logos Press Engine</title>
<meta
@ -79,12 +76,24 @@ export default function App({ Component, pageProps }: AppLayoutProps) {
opacity: 0;
z-index: -1;
}
[data-theme='light'] {
.light-mode-hidden {
display: none !important;
}
}
[data-theme='dark'] {
.dark-mode-hidden {
display: none !important;
}
}
`}
/>
<ProgressBar />
<SearchBarProvider>
{getLayout(<Component {...pageProps} />)}
</SearchBarProvider>
</ThemeProvider>
</LSDThemeProvider>
)
}

View File

@ -1,10 +1,17 @@
import { uiConfigs } from '@/configs/ui.configs'
import { Html, Head, Main, NextScript } from 'next/document'
import { Head, Html, Main, NextScript } from 'next/document'
import { defaultThemeState } from '../states/themeState'
export default function Document() {
return (
<Html lang="en">
<Head />
<Html lang="en" data-theme="light">
<Head>
<script
dangerouslySetInnerHTML={{
__html: `var main=function(){try{var t=JSON.parse(localStorage.getItem("theme")||"{}"),e=(null==t?void 0:t.mode)||${defaultThemeState.mode},a=(null==t?void 0:t.genericFontFamily)||${defaultThemeState.genericFontFamily},i=document.querySelector("html");i.setAttribute("data-theme",e),i.setAttribute("data-font-family",a)}catch(n){}};main();`,
}}
/>
</Head>
<body>
<Main />
<NextScript />

View File

@ -1 +0,0 @@
export { default as isDarkState } from './isDarkState';

View File

@ -1,12 +0,0 @@
import { State, hookstate, useHookstate } from "@hookstate/core";
const isDarkState = hookstate(false);
const wrapIsDarkState = (state: State<boolean>) => ({
get: () => state.value,
toggle: () => state.set((value) => !value),
});
export const useIsDarkState = () => wrapIsDarkState(useHookstate(isDarkState))
export default useIsDarkState;

View File

@ -0,0 +1 @@
export * from './theme.state'

View File

@ -0,0 +1,35 @@
import { TypographyGenericFontFamily } from '@acid-info/lsd-react'
import { hookstate, State, useHookstate } from '@hookstate/core'
import { localstored } from '@hookstate/localstored'
export type ThemeState = {
mode: 'light' | 'dark'
genericFontFamily: TypographyGenericFontFamily
}
export const defaultThemeState: ThemeState = {
mode: 'light',
genericFontFamily: 'sans-serif',
}
const themeState =
typeof window === 'undefined'
? hookstate(defaultThemeState)
: hookstate<ThemeState>(defaultThemeState, localstored({ key: 'theme' }))
const wrapThemeState = (state: State<ThemeState>) => ({
mode: state.mode,
genericFontFamily: state.genericFontFamily,
get: () => state.value,
setMode: (value: ThemeState['mode']) => state.mode.set(value),
setGenericFontFamily: (value: ThemeState['genericFontFamily']) =>
state.genericFontFamily.set('sans-serif'),
toggleMode: () =>
state.mode.set(state.mode.get() === 'dark' ? 'light' : 'dark'),
})
export const useThemeState = () => wrapThemeState(useHookstate(themeState))
export const useIsDarkTheme = () => useThemeState().mode.get() === 'dark'
export default useThemeState