@whitemordred/react-native-bootstrap5
Version:
A complete React Native library that replicates Bootstrap 5.3 with 100% feature parity, full theming support, CSS variables, and dark/light mode
480 lines (479 loc) • 15.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ThemeManager = exports.ThemeAnimationUtils = exports.ColorUtils = exports.SystemTheme = exports.ThemeStorage = void 0;
const react_native_1 = require("react-native");
const defaultTheme_1 = require("./defaultTheme");
// Global reference checks for web environments
const isWeb = react_native_1.Platform.OS === 'web';
const hasWindow = isWeb && typeof globalThis !== 'undefined' && 'window' in globalThis;
const hasLocalStorage = hasWindow && typeof localStorage !== 'undefined';
const hasDocument = isWeb && typeof globalThis !== 'undefined' && 'document' in globalThis;
// Optional AsyncStorage import (only for React Native)
let AsyncStorage = null;
try {
if (!isWeb) {
AsyncStorage = require('@react-native-async-storage/async-storage').default;
}
}
catch (error) {
// AsyncStorage not available
}
// Storage keys
const THEME_STORAGE_KEY = '@bootstrap5_theme_preferences';
const THEME_MODE_KEY = '@bootstrap5_theme_mode';
/**
* Theme storage utilities for persisting user preferences
*/
class ThemeStorage {
/**
* Save theme preferences to storage
*/
static async savePreferences(preferences) {
try {
if (isWeb && hasLocalStorage) {
localStorage.setItem(THEME_STORAGE_KEY, JSON.stringify(preferences));
}
else if (AsyncStorage) {
await AsyncStorage.setItem(THEME_STORAGE_KEY, JSON.stringify(preferences));
}
}
catch (error) {
console.warn('Failed to save theme preferences:', error);
}
}
/**
* Load theme preferences from storage
*/
static async loadPreferences() {
try {
let stored = null;
if (isWeb && hasLocalStorage) {
stored = localStorage.getItem(THEME_STORAGE_KEY);
}
else if (AsyncStorage) {
stored = await AsyncStorage.getItem(THEME_STORAGE_KEY);
}
if (stored) {
const parsed = JSON.parse(stored);
return Object.assign(Object.assign({}, defaultTheme_1.defaultThemePreferences), parsed);
}
}
catch (error) {
console.warn('Failed to load theme preferences:', error);
}
return defaultTheme_1.defaultThemePreferences;
}
/**
* Save current theme mode
*/
static async saveThemeMode(mode) {
try {
if (isWeb && hasLocalStorage) {
localStorage.setItem(THEME_MODE_KEY, mode);
}
else if (AsyncStorage) {
await AsyncStorage.setItem(THEME_MODE_KEY, mode);
}
}
catch (error) {
console.warn('Failed to save theme mode:', error);
}
}
/**
* Load saved theme mode
*/
static async loadThemeMode() {
try {
if (isWeb && hasLocalStorage) {
return localStorage.getItem(THEME_MODE_KEY);
}
else if (AsyncStorage) {
return await AsyncStorage.getItem(THEME_MODE_KEY);
}
}
catch (error) {
console.warn('Failed to load theme mode:', error);
}
return null;
}
/**
* Clear all theme data
*/
static async clearThemeData() {
try {
if (isWeb && hasLocalStorage) {
localStorage.removeItem(THEME_STORAGE_KEY);
localStorage.removeItem(THEME_MODE_KEY);
}
else if (AsyncStorage) {
await AsyncStorage.multiRemove([THEME_STORAGE_KEY, THEME_MODE_KEY]);
}
}
catch (error) {
console.warn('Failed to clear theme data:', error);
}
}
}
exports.ThemeStorage = ThemeStorage;
/**
* System theme detection utilities
*/
class SystemTheme {
/**
* Get system preferred color scheme
*/
static getSystemTheme() {
if (hasWindow) {
try {
const win = globalThis;
if (win.window && win.window.matchMedia) {
const mediaQuery = win.window.matchMedia('(prefers-color-scheme: dark)');
return mediaQuery.matches ? 'dark' : 'light';
}
}
catch (error) {
// Fallback
}
}
return 'light';
}
/**
* Listen to system theme changes on web
*/
static addSystemThemeListener(callback) {
if (hasWindow) {
try {
const win = globalThis;
if (win.window && win.window.matchMedia) {
const mediaQuery = win.window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e) => {
callback(e.matches ? 'dark' : 'light');
};
if (mediaQuery.addListener) {
mediaQuery.addListener(handler);
return () => { var _a; return (_a = mediaQuery.removeListener) === null || _a === void 0 ? void 0 : _a.call(mediaQuery, handler); };
}
else if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handler);
return () => { var _a; return (_a = mediaQuery.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(mediaQuery, 'change', handler); };
}
}
}
catch (error) {
// Fallback
}
}
return null;
}
/**
* Check if user prefers reduced motion
*/
static prefersReducedMotion() {
if (hasWindow) {
try {
const win = globalThis;
if (win.window && win.window.matchMedia) {
const mediaQuery = win.window.matchMedia('(prefers-reduced-motion: reduce)');
return mediaQuery.matches;
}
}
catch (error) {
// Fallback
}
}
return false;
}
/**
* Check if user prefers high contrast
*/
static prefersHighContrast() {
if (hasWindow) {
try {
const win = globalThis;
if (win.window && win.window.matchMedia) {
const mediaQuery = win.window.matchMedia('(prefers-contrast: high)');
return mediaQuery.matches;
}
}
catch (error) {
// Fallback
}
}
return false;
}
}
exports.SystemTheme = SystemTheme;
/**
* Color utilities for theme manipulation
*/
class ColorUtils {
/**
* Convert hex color to rgba
*/
static hexToRgba(hex, alpha = 1) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
/**
* Lighten a hex color by a percentage
*/
static lighten(hex, percent) {
const num = parseInt(hex.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255))
.toString(16)
.slice(1);
}
/**
* Darken a hex color by a percentage
*/
static darken(hex, percent) {
const num = parseInt(hex.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) - amt;
const G = (num >> 8 & 0x00FF) - amt;
const B = (num & 0x0000FF) - amt;
return '#' + (0x1000000 + (R > 255 ? 255 : R < 0 ? 0 : R) * 0x10000 +
(G > 255 ? 255 : G < 0 ? 0 : G) * 0x100 +
(B > 255 ? 255 : B < 0 ? 0 : B))
.toString(16)
.slice(1);
}
/**
* Get contrast color (black or white) for a given background
*/
static getContrastColor(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// Calculate luminance
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? '#000000' : '#ffffff';
}
/**
* Check if a color is dark
*/
static isDark(hex) {
return ColorUtils.getContrastColor(hex) === '#ffffff';
}
/**
* Generate color variations (lighter and darker shades)
*/
static generateColorVariations(hex) {
return {
50: ColorUtils.lighten(hex, 45),
100: ColorUtils.lighten(hex, 40),
200: ColorUtils.lighten(hex, 30),
300: ColorUtils.lighten(hex, 20),
400: ColorUtils.lighten(hex, 10),
500: hex,
600: ColorUtils.darken(hex, 10),
700: ColorUtils.darken(hex, 20),
800: ColorUtils.darken(hex, 30),
900: ColorUtils.darken(hex, 40),
950: ColorUtils.darken(hex, 50),
};
}
}
exports.ColorUtils = ColorUtils;
/**
* Animation utilities for theme transitions
*/
class ThemeAnimationUtils {
/**
* Create smooth theme transition
*/
static createThemeTransition(duration = 300) {
if (hasDocument) {
try {
const doc = globalThis;
if (doc.document) {
const style = doc.document.createElement('style');
style.textContent = `
*, *::before, *::after {
transition: background-color ${duration}ms ease-in-out,
border-color ${duration}ms ease-in-out,
color ${duration}ms ease-in-out,
opacity ${duration}ms ease-in-out,
box-shadow ${duration}ms ease-in-out !important;
}
`;
doc.document.head.appendChild(style);
// Remove transition after animation completes
setTimeout(() => {
try {
doc.document.head.removeChild(style);
}
catch (error) {
// Style already removed
}
}, duration + 100);
}
}
catch (error) {
// Fallback
}
}
}
/**
* Disable animations if user prefers reduced motion
*/
static respectReducedMotion() {
if (hasDocument && SystemTheme.prefersReducedMotion()) {
try {
const doc = globalThis;
if (doc.document) {
const style = doc.document.createElement('style');
style.textContent = `
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
`;
doc.document.head.appendChild(style);
}
}
catch (error) {
// Fallback
}
}
}
}
exports.ThemeAnimationUtils = ThemeAnimationUtils;
/**
* Theme manager for advanced theme handling
*/
class ThemeManager {
/**
* Initialize theme manager
*/
static async initialize(followSystemTheme = true) {
this.followSystem = followSystemTheme;
// Load saved theme mode first
const savedMode = await ThemeStorage.loadThemeMode();
if (savedMode) {
this.currentMode = savedMode;
}
else if (followSystemTheme) {
this.currentMode = SystemTheme.getSystemTheme();
}
// Set up system theme listener if following system
if (followSystemTheme) {
this.setupSystemListener();
}
// Apply reduced motion preferences
ThemeAnimationUtils.respectReducedMotion();
return this.currentMode;
}
/**
* Set up system theme listener
*/
static setupSystemListener() {
if (this.systemListener) {
this.systemListener();
this.systemListener = null;
}
this.systemListener = SystemTheme.addSystemThemeListener((theme) => {
if (this.followSystem) {
this.setMode(theme, false); // Don't save when following system
}
});
}
/**
* Set theme mode
*/
static async setMode(mode, saveToStorage = true) {
if (this.currentMode === mode)
return;
// Create smooth transition
ThemeAnimationUtils.createThemeTransition();
this.currentMode = mode;
// Save to storage if requested
if (saveToStorage) {
await ThemeStorage.saveThemeMode(mode);
// Stop following system when user manually sets mode
this.followSystem = false;
if (this.systemListener) {
this.systemListener();
this.systemListener = null;
}
}
// Notify all listeners
this.listeners.forEach(listener => listener(mode));
}
/**
* Toggle between light and dark mode
*/
static async toggleMode() {
const newMode = this.currentMode === 'light' ? 'dark' : 'light';
await this.setMode(newMode, true);
}
/**
* Get current theme mode
*/
static getCurrentMode() {
return this.currentMode;
}
/**
* Check if following system theme
*/
static isFollowingSystem() {
return this.followSystem;
}
/**
* Start following system theme
*/
static async followSystemTheme() {
this.followSystem = true;
this.setupSystemListener();
// Immediately switch to system theme
const systemTheme = SystemTheme.getSystemTheme();
await this.setMode(systemTheme, false);
}
/**
* Stop following system theme
*/
static stopFollowingSystem() {
this.followSystem = false;
if (this.systemListener) {
this.systemListener();
this.systemListener = null;
}
}
/**
* Add theme change listener
*/
static addListener(listener) {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
/**
* Clean up all listeners
*/
static cleanup() {
this.listeners = [];
if (this.systemListener) {
this.systemListener();
this.systemListener = null;
}
}
}
exports.ThemeManager = ThemeManager;
ThemeManager.listeners = [];
ThemeManager.systemListener = null;
ThemeManager.currentMode = 'light';
ThemeManager.followSystem = false;