native-variants
Version:
A library for handling variants in React Native components with theme support.
328 lines • 9.75 kB
JSX
;
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