UNPKG

native-variants

Version:

A library for handling variants in React Native components with theme support.

328 lines 9.75 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ThemeProvider = ThemeProvider; exports.useTheme = useTheme; exports.useThemeColors = useThemeColors; exports.useIsDark = useIsDark; exports.useColorScheme = useColorScheme; exports.createThemedStyles = createThemedStyles; const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); // Create context with any type to allow generic usage const ThemeContext = (0, react_1.createContext)(undefined); /** * Resolves the actual color scheme from mode and system preference. */ function resolveColorScheme(mode, systemScheme) { if (mode === "system") { return systemScheme === "dark" ? "dark" : "light"; } return mode; } /** * ThemeProvider component. * Provides theme context with dark/light mode support. * * **Important:** This provider requires colors to be passed explicitly. * Get colors from createNVA's colorScheme output. * * @template T - The colors type * * @example * ```tsx * // 1. Create your theme with createNVA * const { theme, colorScheme, styled } = createNVA({ * theme: { * colors: { * default: { * background: "#ffffff", * foreground: "#000000", * primary: "#3b82f6", * }, * dark: { * background: "#0a0a0a", * foreground: "#ffffff", * primary: "#60a5fa", * }, * }, * }, * }); * * // 2. Wrap your app with ThemeProvider * function App() { * return ( * <ThemeProvider colors={colorScheme}> * <MyApp /> * </ThemeProvider> * ); * } * * // 3. Use colors in components * function MyComponent() { * const { colors, isDark, toggle } = useTheme(); * * return ( * <View style={{ backgroundColor: colors.background }}> * <Text style={{ color: colors.foreground }}> * {isDark ? "Dark Mode" : "Light Mode"} * </Text> * <Button onPress={toggle} title="Toggle" /> * </View> * ); * } * * // With AsyncStorage persistence * import AsyncStorage from "@react-native-async-storage/async-storage"; * * <ThemeProvider * colors={colorScheme} * defaultMode="system" * onGetStorage={async () => { * const mode = await AsyncStorage.getItem("theme-mode"); * return mode as ThemeMode | null; * }} * onSetStorage={async (mode) => { * await AsyncStorage.setItem("theme-mode", mode); * }} * > * <App /> * </ThemeProvider> * ``` */ function ThemeProvider({ children, colors, defaultMode = "system", onGetStorage, onSetStorage, }) { const [mode, setModeState] = (0, react_1.useState)(defaultMode); const [systemScheme, setSystemScheme] = (0, react_1.useState)(react_native_1.Appearance.getColorScheme()); const [isHydrated, setIsHydrated] = (0, react_1.useState)(!onGetStorage); // Load persisted mode on mount (0, react_1.useEffect)(() => { if (onGetStorage) { onGetStorage().then((storedMode) => { if (storedMode) { setModeState(storedMode); } setIsHydrated(true); }); } }, [onGetStorage]); // Listen to system color scheme changes (0, react_1.useEffect)(() => { const subscription = react_native_1.Appearance.addChangeListener(({ colorScheme }) => { setSystemScheme(colorScheme); }); return () => subscription.remove(); }, []); // Set mode with optional persistence const setMode = (0, react_1.useCallback)((newMode) => { setModeState(newMode); if (onSetStorage) { onSetStorage(newMode); } }, [onSetStorage]); // Toggle between light and dark const toggle = (0, react_1.useCallback)(() => { const currentScheme = resolveColorScheme(mode, systemScheme); const newMode = currentScheme === "light" ? "dark" : "light"; setMode(newMode); }, [mode, systemScheme, setMode]); // Compute context value const value = (0, react_1.useMemo)(() => { const colorScheme = resolveColorScheme(mode, systemScheme); return { colorScheme, mode, isDark: colorScheme === "dark", colors: (colorScheme === "dark" ? colors.dark : colors.default), setMode, toggle, }; }, [mode, systemScheme, colors, setMode, toggle]); // Don't render until hydrated to prevent flash if (!isHydrated) { return null; } return (<ThemeContext.Provider value={value}> {children} </ThemeContext.Provider>); } /** * Hook to access theme context. * Must be used within a ThemeProvider. * * @template T - The colors type * @returns Theme context value with colors and controls * @throws Error if used outside ThemeProvider * * @example * ```tsx * function MyComponent() { * const { colors, isDark, toggle, setMode } = useTheme<MyColors>(); * * return ( * <View style={{ backgroundColor: colors.background }}> * <Text style={{ color: colors.foreground }}> * Current mode: {isDark ? "Dark" : "Light"} * </Text> * <Button onPress={toggle} title="Toggle Theme" /> * <Button onPress={() => setMode("system")} title="Use System" /> * </View> * ); * } * ``` */ function useTheme() { const context = (0, react_1.useContext)(ThemeContext); if (!context) { throw new Error("useTheme must be used within a ThemeProvider. " + "Make sure to wrap your app with <ThemeProvider colors={colorScheme}>."); } return context; } /** * Hook to access only theme colors. * Convenience wrapper around useTheme that returns just the colors. * * @template T - The colors type * @returns Current theme colors * * @example * ```tsx * function MyComponent() { * const colors = useThemeColors<MyColors>(); * * return ( * <View style={{ backgroundColor: colors.background }}> * <Text style={{ color: colors.primary }}>Hello</Text> * </View> * ); * } * ``` */ function useThemeColors() { const { colors } = useTheme(); return colors; } /** * Hook to check if dark mode is active. * Convenience wrapper for quick dark mode checks. * * @returns Boolean indicating if dark mode is active * * @example * ```tsx * function MyComponent() { * const isDark = useIsDark(); * * return ( * <Image source={isDark ? darkLogo : lightLogo} /> * ); * } * ``` */ function useIsDark() { const { isDark } = useTheme(); return isDark; } /** * Hook to get the current color scheme. * Returns "light" or "dark" based on current theme. * * @returns Current color scheme * * @example * ```tsx * function MyComponent() { * const colorScheme = useColorScheme(); * * return ( * <StatusBar barStyle={colorScheme === "dark" ? "light-content" : "dark-content"} /> * ); * } * ``` */ function useColorScheme() { const { colorScheme } = useTheme(); return colorScheme; } /** * Creates a themed style helper that automatically uses current theme colors. * Useful for creating styled components with theme access. * * @template T - The colors type * @template S - The styles record type * @param styleFactory - Function that receives colors and returns styles * @returns Hook that returns computed styles * * @example * ```tsx * type MyColors = { card: string; border: string; cardForeground: string }; * * const useCardStyles = createThemedStyles<MyColors, { * container: ViewStyle; * title: TextStyle; * }>((colors) => ({ * container: { * backgroundColor: colors.card, * borderColor: colors.border, * borderWidth: 1, * borderRadius: 8, * padding: 16, * }, * title: { * color: colors.cardForeground, * fontSize: 18, * fontWeight: "600", * }, * })); * * function Card({ title, children }) { * const styles = useCardStyles(); * * return ( * <View style={styles.container}> * <Text style={styles.title}>{title}</Text> * {children} * </View> * ); * } * ``` */ function createThemedStyles(styleFactory) { return function useThemedStyles() { const { colors } = useTheme(); return (0, react_1.useMemo)(() => styleFactory(colors), [colors]); }; } //# sourceMappingURL=theme-provider.jsx.map