164 lines
3.9 KiB
TypeScript
164 lines
3.9 KiB
TypeScript
import React from 'react'
|
|
import {useMediaQuery} from 'react-responsive'
|
|
|
|
import {
|
|
computeFontScaleMultiplier,
|
|
getFontFamily,
|
|
getFontScale,
|
|
setFontFamily as persistFontFamily,
|
|
setFontScale as persistFontScale,
|
|
} from '#/alf/fonts'
|
|
import {createThemes, defaultTheme} from '#/alf/themes'
|
|
import {Theme, ThemeName} from '#/alf/types'
|
|
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
|
|
import {Device} from '#/storage'
|
|
|
|
export {atoms} from '#/alf/atoms'
|
|
export * from '#/alf/fonts'
|
|
export * as tokens from '#/alf/tokens'
|
|
export * from '#/alf/types'
|
|
export * from '#/alf/util/flatten'
|
|
export * from '#/alf/util/platform'
|
|
export * from '#/alf/util/themeSelector'
|
|
|
|
export type Alf = {
|
|
themeName: ThemeName
|
|
theme: Theme
|
|
themes: ReturnType<typeof createThemes>
|
|
fonts: {
|
|
scale: Exclude<Device['fontScale'], undefined>
|
|
scaleMultiplier: number
|
|
family: Device['fontFamily']
|
|
setFontScale: (fontScale: Exclude<Device['fontScale'], undefined>) => void
|
|
setFontFamily: (fontFamily: Device['fontFamily']) => void
|
|
}
|
|
/**
|
|
* Feature flags or other gated options
|
|
*/
|
|
flags: {}
|
|
}
|
|
|
|
/*
|
|
* Context
|
|
*/
|
|
export const Context = React.createContext<Alf>({
|
|
themeName: 'light',
|
|
theme: defaultTheme,
|
|
themes: createThemes({
|
|
hues: {
|
|
primary: BLUE_HUE,
|
|
negative: RED_HUE,
|
|
positive: GREEN_HUE,
|
|
},
|
|
}),
|
|
fonts: {
|
|
scale: getFontScale(),
|
|
scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
|
|
family: getFontFamily(),
|
|
setFontScale: () => {},
|
|
setFontFamily: () => {},
|
|
},
|
|
flags: {},
|
|
})
|
|
|
|
export function ThemeProvider({
|
|
children,
|
|
themeName,
|
|
theme,
|
|
}: React.PropsWithChildren<{
|
|
themeName: ThemeName
|
|
theme?: ReturnType<typeof createThemes>
|
|
}>) {
|
|
const [fontScale, setFontScale] = React.useState<Alf['fonts']['scale']>(() =>
|
|
getFontScale(),
|
|
)
|
|
const [fontScaleMultiplier, setFontScaleMultiplier] = React.useState(() =>
|
|
computeFontScaleMultiplier(fontScale),
|
|
)
|
|
const setFontScaleAndPersist = React.useCallback<
|
|
Alf['fonts']['setFontScale']
|
|
>(
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
fontScale => {
|
|
setFontScale(fontScale)
|
|
persistFontScale(fontScale)
|
|
setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
|
|
},
|
|
[setFontScale],
|
|
)
|
|
const [fontFamily, setFontFamily] = React.useState<Alf['fonts']['family']>(
|
|
() => getFontFamily(),
|
|
)
|
|
const setFontFamilyAndPersist = React.useCallback<
|
|
Alf['fonts']['setFontFamily']
|
|
>(
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
fontFamily => {
|
|
setFontFamily(fontFamily)
|
|
persistFontFamily(fontFamily)
|
|
},
|
|
[setFontFamily],
|
|
)
|
|
const themes = React.useMemo(() => {
|
|
if (theme) {
|
|
return theme
|
|
}
|
|
return createThemes({
|
|
hues: {
|
|
primary: BLUE_HUE,
|
|
negative: RED_HUE,
|
|
positive: GREEN_HUE,
|
|
},
|
|
})
|
|
}, [theme])
|
|
|
|
const value = React.useMemo<Alf>(
|
|
() => ({
|
|
themes,
|
|
themeName: themeName,
|
|
theme: themes[themeName],
|
|
fonts: {
|
|
scale: fontScale,
|
|
scaleMultiplier: fontScaleMultiplier,
|
|
family: fontFamily,
|
|
setFontScale: setFontScaleAndPersist,
|
|
setFontFamily: setFontFamilyAndPersist,
|
|
},
|
|
flags: {},
|
|
}),
|
|
[
|
|
themeName,
|
|
themes,
|
|
fontScale,
|
|
setFontScaleAndPersist,
|
|
fontFamily,
|
|
setFontFamilyAndPersist,
|
|
fontScaleMultiplier,
|
|
],
|
|
)
|
|
|
|
return <Context.Provider value={value}>{children}</Context.Provider>
|
|
}
|
|
|
|
export function useAlf() {
|
|
return React.useContext(Context)
|
|
}
|
|
|
|
export function useTheme(theme?: ThemeName) {
|
|
const alf = useAlf()
|
|
return React.useMemo(() => {
|
|
return theme ? alf.themes[theme] : alf.theme
|
|
}, [theme, alf])
|
|
}
|
|
|
|
export function useBreakpoints() {
|
|
const gtPhone = useMediaQuery({minWidth: 500})
|
|
const gtMobile = useMediaQuery({minWidth: 800})
|
|
const gtTablet = useMediaQuery({minWidth: 1300})
|
|
return {
|
|
gtPhone,
|
|
gtMobile,
|
|
gtTablet,
|
|
}
|
|
}
|