146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
import {
|
|
createContext,
|
|
FC,
|
|
PropsWithChildren,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
} from "react"
|
|
import { StyleProp, useColorScheme } from "react-native"
|
|
import {
|
|
DarkTheme as NavDarkTheme,
|
|
DefaultTheme as NavDefaultTheme,
|
|
Theme as NavTheme,
|
|
} from "@react-navigation/native"
|
|
import { useMMKVString } from "react-native-mmkv"
|
|
|
|
import { storage } from "@/utils/storage"
|
|
|
|
import { setImperativeTheming } from "./context.utils"
|
|
import { darkTheme, lightTheme } from "./theme"
|
|
import type {
|
|
AllowedStylesT,
|
|
ImmutableThemeContextModeT,
|
|
Theme,
|
|
ThemeContextModeT,
|
|
ThemedFnT,
|
|
ThemedStyle,
|
|
} from "./types"
|
|
|
|
export type ThemeContextType = {
|
|
navigationTheme: NavTheme
|
|
setThemeContextOverride: (newTheme: ThemeContextModeT) => void
|
|
theme: Theme
|
|
themeContext: ImmutableThemeContextModeT
|
|
themed: ThemedFnT
|
|
}
|
|
|
|
export const ThemeContext = createContext<ThemeContextType | null>(null)
|
|
|
|
export interface ThemeProviderProps {
|
|
initialContext?: ThemeContextModeT
|
|
}
|
|
|
|
/**
|
|
* The ThemeProvider is the heart and soul of the design token system. It provides a context wrapper
|
|
* for your entire app to consume the design tokens as well as global functionality like the app's theme.
|
|
*
|
|
* To get started, you want to wrap your entire app's JSX hierarchy in `ThemeProvider`
|
|
* and then use the `useAppTheme()` hook to access the theme context.
|
|
*
|
|
* Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/Theming/
|
|
*/
|
|
export const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({
|
|
children,
|
|
initialContext,
|
|
}) => {
|
|
// The operating system theme:
|
|
const systemColorScheme = useColorScheme()
|
|
// Our saved theme context: can be "light", "dark", or undefined (system theme)
|
|
const [themeScheme, setThemeScheme] = useMMKVString("ignite.themeScheme", storage)
|
|
|
|
/**
|
|
* This function is used to set the theme context and is exported from the useAppTheme() hook.
|
|
* - setThemeContextOverride("dark") sets the app theme to dark no matter what the system theme is.
|
|
* - setThemeContextOverride("light") sets the app theme to light no matter what the system theme is.
|
|
* - setThemeContextOverride(undefined) the app will follow the operating system theme.
|
|
*/
|
|
const setThemeContextOverride = useCallback(
|
|
(newTheme: ThemeContextModeT) => {
|
|
setThemeScheme(newTheme)
|
|
},
|
|
[setThemeScheme],
|
|
)
|
|
|
|
/**
|
|
* initialContext is the theme context passed in from the app.tsx file and always takes precedence.
|
|
* themeScheme is the value from MMKV. If undefined, we fall back to the system theme
|
|
* systemColorScheme is the value from the device. If undefined, we fall back to "light"
|
|
*/
|
|
const themeContext: ImmutableThemeContextModeT = useMemo(() => {
|
|
const t = initialContext || themeScheme || (!!systemColorScheme ? systemColorScheme : "light")
|
|
return t === "dark" ? "dark" : "light"
|
|
}, [initialContext, themeScheme, systemColorScheme])
|
|
|
|
const navigationTheme: NavTheme = useMemo(() => {
|
|
switch (themeContext) {
|
|
case "dark":
|
|
return NavDarkTheme
|
|
default:
|
|
return NavDefaultTheme
|
|
}
|
|
}, [themeContext])
|
|
|
|
const theme: Theme = useMemo(() => {
|
|
switch (themeContext) {
|
|
case "dark":
|
|
return darkTheme
|
|
default:
|
|
return lightTheme
|
|
}
|
|
}, [themeContext])
|
|
|
|
useEffect(() => {
|
|
setImperativeTheming(theme)
|
|
}, [theme])
|
|
|
|
const themed = useCallback(
|
|
<T,>(styleOrStyleFn: AllowedStylesT<T>) => {
|
|
const flatStyles = [styleOrStyleFn].flat(3) as (ThemedStyle<T> | StyleProp<T>)[]
|
|
const stylesArray = flatStyles.map((f) => {
|
|
if (typeof f === "function") {
|
|
return (f as ThemedStyle<T>)(theme)
|
|
} else {
|
|
return f
|
|
}
|
|
})
|
|
// Flatten the array of styles into a single object
|
|
return Object.assign({}, ...stylesArray) as T
|
|
},
|
|
[theme],
|
|
)
|
|
|
|
const value = {
|
|
navigationTheme,
|
|
theme,
|
|
themeContext,
|
|
setThemeContextOverride,
|
|
themed,
|
|
}
|
|
|
|
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
|
}
|
|
|
|
/**
|
|
* This is the primary hook that you will use to access the theme context in your components.
|
|
* Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/useAppTheme.tsx/
|
|
*/
|
|
export const useAppTheme = () => {
|
|
const context = useContext(ThemeContext)
|
|
if (!context) {
|
|
throw new Error("useAppTheme must be used within an ThemeProvider")
|
|
}
|
|
return context
|
|
}
|