@sanvika/ui 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanvika/ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Sanvika Production — shared UI component library for 50+ projects (pure JS, CSS Modules, props-driven) + cloud-driven Navbar/Theme presets via ui.sanvikaproduction.com",
@@ -24,7 +24,8 @@
24
24
  ".": "./src/index.js",
25
25
  "./icons": "./src/components/icons/index.js",
26
26
  "./styles": "./src/styles/index.css",
27
- "./server": "./src/server/index.js"
27
+ "./server": "./src/server/index.js",
28
+ "./react-native": "./src/react-native/index.js"
28
29
  },
29
30
  "files": [
30
31
  "dist",
@@ -32,7 +33,16 @@
32
33
  ],
33
34
  "peerDependencies": {
34
35
  "react": ">=18.0.0",
35
- "react-dom": ">=18.0.0"
36
+ "react-dom": ">=18.0.0",
37
+ "react-native": "*",
38
+ "@react-native-async-storage/async-storage": ">=1.0.0",
39
+ "@sanvika/logger": ">=0.2.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "react-dom": { "optional": true },
43
+ "react-native": { "optional": true },
44
+ "@react-native-async-storage/async-storage": { "optional": true },
45
+ "@sanvika/logger": { "optional": true }
36
46
  },
37
47
  "scripts": {
38
48
  "build": "rollup -c rollup.config.js",
@@ -0,0 +1,101 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import AsyncStorage from '@react-native-async-storage/async-storage';
3
+ import { Appearance } from 'react-native';
4
+ import { createLogger } from '@sanvika/logger';
5
+ import { FAPK_THEME_COLORS } from './themeColors.js';
6
+
7
+ const logger = createLogger({ namespace: 'ThemeProvider' });
8
+ const STORAGE_KEY = 'theme';
9
+
10
+ const ThemeContext = createContext(null);
11
+
12
+ export function ThemeProvider({ children, themeColors = FAPK_THEME_COLORS }) {
13
+ const [theme, setTheme] = useState('dark');
14
+ const [isReady, setIsReady] = useState(false);
15
+
16
+ useEffect(() => {
17
+ let mounted = true;
18
+
19
+ const initializeTheme = async () => {
20
+ try {
21
+ const savedTheme = await AsyncStorage.getItem(STORAGE_KEY);
22
+ if (savedTheme === 'light' || savedTheme === 'dark') {
23
+ if (mounted) setTheme(savedTheme);
24
+ } else {
25
+ const systemTheme = Appearance.getColorScheme() || 'dark';
26
+ if (mounted) setTheme(systemTheme);
27
+ }
28
+ } catch (error) {
29
+ logger.error('Theme initialization error', { message: error?.message });
30
+ if (mounted) setTheme('dark');
31
+ } finally {
32
+ if (mounted) setIsReady(true);
33
+ }
34
+ };
35
+
36
+ initializeTheme();
37
+
38
+ const subscription = Appearance.addChangeListener(({ colorScheme }) => {
39
+ AsyncStorage.getItem(STORAGE_KEY)
40
+ .then((savedTheme) => {
41
+ if (!savedTheme && colorScheme && mounted) {
42
+ setTheme(colorScheme);
43
+ }
44
+ })
45
+ .catch((error) => {
46
+ logger.error('Error checking saved theme', { message: error?.message });
47
+ });
48
+ });
49
+
50
+ return () => {
51
+ mounted = false;
52
+ subscription?.remove();
53
+ };
54
+ }, []);
55
+
56
+ const toggleTheme = async () => {
57
+ try {
58
+ const newTheme = theme === 'light' ? 'dark' : 'light';
59
+ setTheme(newTheme);
60
+ await AsyncStorage.setItem(STORAGE_KEY, newTheme);
61
+ } catch (error) {
62
+ logger.error('Error toggling theme', { message: error?.message });
63
+ }
64
+ };
65
+
66
+ const setThemeExplicitly = async (newTheme) => {
67
+ let resolved = newTheme;
68
+ if (resolved !== 'light' && resolved !== 'dark') {
69
+ logger.warn('Invalid theme, defaulting to dark', { newTheme });
70
+ resolved = 'dark';
71
+ }
72
+ try {
73
+ setTheme(resolved);
74
+ await AsyncStorage.setItem(STORAGE_KEY, resolved);
75
+ } catch (error) {
76
+ logger.error('Error setting theme explicitly', { message: error?.message });
77
+ }
78
+ };
79
+
80
+ const value = {
81
+ theme,
82
+ isDarkMode: theme === 'dark',
83
+ isThemeReady: isReady,
84
+ colors: themeColors[theme] || themeColors.dark,
85
+ toggleTheme,
86
+ setTheme: setThemeExplicitly,
87
+ themeColors,
88
+ };
89
+
90
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
91
+ }
92
+
93
+ export function useTheme() {
94
+ const context = useContext(ThemeContext);
95
+ if (!context) {
96
+ throw new Error('useTheme must be used within a ThemeProvider');
97
+ }
98
+ return context;
99
+ }
100
+
101
+ export { ThemeContext, FAPK_THEME_COLORS };
@@ -0,0 +1,2 @@
1
+ // @sanvika/ui/react-native — ThemeProvider + FAPK palette (no DOM/CSS)
2
+ export { ThemeProvider, useTheme, ThemeContext, FAPK_THEME_COLORS } from './ThemeProvider.jsx';
@@ -0,0 +1,53 @@
1
+ /** FAPK mobile palette — mirrors web CSS tokens (--bg-color, --primary-color, etc.). */
2
+ export const FAPK_THEME_COLORS = {
3
+ light: {
4
+ background: '#fafafa',
5
+ surface: '#f5f5f5',
6
+ surfacePrimary: '#ffffff',
7
+ surfaceSecondary: '#fafafa',
8
+ text: '#000000',
9
+ textPrimary: '#000000',
10
+ textSecondary: '#121111',
11
+ textTertiary: '#666666',
12
+ textOnAccent: '#ffffff',
13
+ primary: '#0077cc',
14
+ accent: '#0077cc',
15
+ primaryLight: '#007bff',
16
+ primaryDark: '#004c99',
17
+ success: '#28a745',
18
+ warning: '#ffc107',
19
+ error: '#dc3545',
20
+ border: '#fa7d08',
21
+ borderLight: '#f0f0f0',
22
+ divider: '#cccccc',
23
+ shadow: 'rgba(0, 0, 0, 0.1)',
24
+ shadowDark: 'rgba(0, 0, 0, 0.2)',
25
+ overlay: 'rgba(0, 0, 0, 0.5)',
26
+ highlight: '#E3F2FD',
27
+ },
28
+ dark: {
29
+ background: '#111111',
30
+ surface: '#1a1a1a',
31
+ surfacePrimary: '#222222',
32
+ surfaceSecondary: '#111111',
33
+ text: '#ffffff',
34
+ textPrimary: '#ffffff',
35
+ textSecondary: '#cccccc',
36
+ textTertiary: '#999999',
37
+ textOnAccent: '#ffffff',
38
+ primary: '#1a75d2',
39
+ accent: '#1a75d2',
40
+ primaryLight: '#88c5ff',
41
+ primaryDark: '#b3d9ff',
42
+ success: '#25a03a',
43
+ warning: '#e0aa12',
44
+ error: '#b20c0c',
45
+ border: '#fbad05',
46
+ borderLight: '#444444',
47
+ divider: '#555555',
48
+ shadow: 'rgba(0, 0, 0, 0.3)',
49
+ shadowDark: 'rgba(0, 0, 0, 0.5)',
50
+ overlay: 'rgba(0, 0, 0, 0.7)',
51
+ highlight: '#1E3A5F',
52
+ },
53
+ };
@@ -23,12 +23,12 @@ export class SanvikaUI {
23
23
  #secret;
24
24
 
25
25
  constructor({ url, clientId, serviceKey, secret } = {}) {
26
- const finalUrl = url || _readEnv("UI_URL");
26
+ const finalUrl = url || _readEnv("UI_URL") || "https://ui.sanvikaproduction.com";
27
27
  const finalClientId = clientId || _readEnv("CLIENT_ID") || _readEnv("UI_CLIENT_ID");
28
28
  const finalServiceKey = serviceKey || _readEnv("SANVIKA_SERVICE_KEY") || "";
29
29
  const finalSecret = secret || _readEnv("UI_CLIENT_SECRET") || ""; // Tier 2 fallback (optional)
30
- if (!finalUrl || !finalClientId) {
31
- throw new Error("@sanvika/ui/server: UI_URL and CLIENT_ID are required");
30
+ if (!finalClientId) {
31
+ throw new Error("@sanvika/ui/server: CLIENT_ID is required");
32
32
  }
33
33
  this.#url = finalUrl.replace(/\/$/, "");
34
34
  this.#clientId = finalClientId;