mirror of
https://github.com/acid-info/logos-press-engine.git
synced 2025-02-23 14:48:08 +00:00
feat: make theme color and font persistent fixes #40
This commit is contained in:
parent
07711572fe
commit
88c9d8eba5
@ -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'}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
29
src/containers/LSDThemeProvider/LSDThemeProvider.tsx
Normal file
29
src/containers/LSDThemeProvider/LSDThemeProvider.tsx
Normal 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>
|
||||
)
|
||||
}
|
1
src/containers/LSDThemeProvider/index.ts
Normal file
1
src/containers/LSDThemeProvider/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './LSDThemeProvider'
|
61
src/containers/LSDThemeProvider/themes.ts
Normal file
61
src/containers/LSDThemeProvider/themes.ts
Normal 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'),
|
||||
}
|
||||
}
|
@ -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}
|
||||
/>
|
||||
|
@ -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} />
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 />
|
||||
|
@ -1 +0,0 @@
|
||||
export { default as isDarkState } from './isDarkState';
|
@ -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;
|
1
src/states/themeState/index.ts
Normal file
1
src/states/themeState/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './theme.state'
|
35
src/states/themeState/theme.state.ts
Normal file
35
src/states/themeState/theme.state.ts
Normal 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
|
Loading…
x
Reference in New Issue
Block a user