@kbach/react 0.2.2 → 0.2.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/dist/index.d.mts CHANGED
@@ -142,14 +142,29 @@ declare function useTheme(): ThemeContextValue;
142
142
 
143
143
  interface ThemeProviderProps {
144
144
  children: ReactNode;
145
- /** Default mode on first render. Falls back to persisted value, then 'system'. */
145
+ /** Initial mode. Falls back to persisted value, then 'system'. */
146
146
  defaultMode?: ThemeMode;
147
+ /**
148
+ * Explicit color scheme override for native with `defaultMode="system"`.
149
+ *
150
+ * On some Android devices the system scheme is not detected on startup
151
+ * (the `appearanceChanged` event only fires on *changes*, not on launch when
152
+ * the device was already dark). Pass `useColorScheme()` from `react-native`
153
+ * here to guarantee correct detection:
154
+ *
155
+ * ```tsx
156
+ * import { useColorScheme } from 'react-native';
157
+ * const scheme = useColorScheme();
158
+ * <ThemeProvider defaultMode="system" colorScheme={scheme}>…</ThemeProvider>
159
+ * ```
160
+ */
161
+ colorScheme?: 'light' | 'dark' | null;
147
162
  /** Override the config (useful for per-tree config). Defaults to global getConfig(). */
148
163
  config?: FrameworkConfig;
149
- /** Disable persistence to localStorage / AsyncStorage */
164
+ /** Disable persistence to localStorage */
150
165
  disablePersistence?: boolean;
151
166
  }
152
- declare function ThemeProvider({ children, defaultMode, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
167
+ declare function ThemeProvider({ children, defaultMode, colorScheme: colorSchemeProp, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
153
168
 
154
169
  type ToggleVariant = 'button' | 'switch' | 'icon-button';
155
170
  interface ThemeToggleProps {
package/dist/index.d.ts CHANGED
@@ -142,14 +142,29 @@ declare function useTheme(): ThemeContextValue;
142
142
 
143
143
  interface ThemeProviderProps {
144
144
  children: ReactNode;
145
- /** Default mode on first render. Falls back to persisted value, then 'system'. */
145
+ /** Initial mode. Falls back to persisted value, then 'system'. */
146
146
  defaultMode?: ThemeMode;
147
+ /**
148
+ * Explicit color scheme override for native with `defaultMode="system"`.
149
+ *
150
+ * On some Android devices the system scheme is not detected on startup
151
+ * (the `appearanceChanged` event only fires on *changes*, not on launch when
152
+ * the device was already dark). Pass `useColorScheme()` from `react-native`
153
+ * here to guarantee correct detection:
154
+ *
155
+ * ```tsx
156
+ * import { useColorScheme } from 'react-native';
157
+ * const scheme = useColorScheme();
158
+ * <ThemeProvider defaultMode="system" colorScheme={scheme}>…</ThemeProvider>
159
+ * ```
160
+ */
161
+ colorScheme?: 'light' | 'dark' | null;
147
162
  /** Override the config (useful for per-tree config). Defaults to global getConfig(). */
148
163
  config?: FrameworkConfig;
149
- /** Disable persistence to localStorage / AsyncStorage */
164
+ /** Disable persistence to localStorage */
150
165
  disablePersistence?: boolean;
151
166
  }
152
- declare function ThemeProvider({ children, defaultMode, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
167
+ declare function ThemeProvider({ children, defaultMode, colorScheme: colorSchemeProp, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
153
168
 
154
169
  type ToggleVariant = 'button' | 'switch' | 'icon-button';
155
170
  interface ThemeToggleProps {
package/dist/index.js CHANGED
@@ -2065,17 +2065,15 @@ function subscribeGlobalDarkMode(callback) {
2065
2065
 
2066
2066
  // src/ThemeProvider.tsx
2067
2067
  var import_jsx_runtime = require("react/jsx-runtime");
2068
- var Appearance;
2068
+ var _useColorScheme;
2069
2069
  try {
2070
- ({ Appearance } = require("react-native"));
2070
+ ({ useColorScheme: _useColorScheme } = require("react-native"));
2071
2071
  } catch {
2072
2072
  }
2073
2073
  var STORAGE_KEY = "kbach-theme";
2074
2074
  function loadPersistedMode() {
2075
2075
  try {
2076
- if (isWeb) {
2077
- return localStorage.getItem(STORAGE_KEY);
2078
- }
2076
+ if (isWeb) return localStorage.getItem(STORAGE_KEY);
2079
2077
  return null;
2080
2078
  } catch {
2081
2079
  return null;
@@ -2100,23 +2098,23 @@ function applyWebTheme(resolvedMode, strategy) {
2100
2098
  function ThemeProvider({
2101
2099
  children,
2102
2100
  defaultMode = "system",
2101
+ colorScheme: colorSchemeProp,
2103
2102
  config: configOverride,
2104
2103
  disablePersistence = false
2105
2104
  }) {
2106
2105
  const [resolvedConfig, setResolvedConfig] = (0, import_react2.useState)(
2107
2106
  () => configOverride ? buildConfig(configOverride) : getConfig()
2108
2107
  );
2109
- const getSystemScheme = (0, import_react2.useCallback)(() => {
2108
+ const [nativeSchemeFallback, setNativeSchemeFallback] = (0, import_react2.useState)(null);
2109
+ const hookScheme = _useColorScheme?.();
2110
+ const nativeScheme = colorSchemeProp !== void 0 ? colorSchemeProp ?? null : hookScheme ?? nativeSchemeFallback;
2111
+ const [webScheme, setWebScheme] = (0, import_react2.useState)(() => {
2110
2112
  if (isWeb && typeof window !== "undefined") {
2111
2113
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
2112
2114
  }
2113
- if (Appearance) {
2114
- const scheme = Appearance.getColorScheme();
2115
- return scheme === "dark" ? "dark" : "light";
2116
- }
2117
2115
  return "light";
2118
- }, []);
2119
- const [systemScheme, setSystemScheme] = (0, import_react2.useState)(getSystemScheme);
2116
+ });
2117
+ const systemScheme = isWeb ? webScheme : nativeScheme === "dark" ? "dark" : "light";
2120
2118
  const [mode, _setMode] = (0, import_react2.useState)(() => {
2121
2119
  if (!disablePersistence) {
2122
2120
  const persisted = loadPersistedMode();
@@ -2135,36 +2133,32 @@ function ThemeProvider({
2135
2133
  const isDark = resolvedMode === "dark";
2136
2134
  syncGlobalDarkMode(isDark);
2137
2135
  (0, import_react2.useEffect)(() => {
2138
- applyWebTheme(resolvedMode, resolvedConfig.darkMode);
2139
- setGlobalDarkMode(isDark);
2140
- }, [isDark, resolvedMode, resolvedConfig.darkMode]);
2141
- const setSystemSchemeRef = (0, import_react2.useRef)(setSystemScheme);
2142
- setSystemSchemeRef.current = setSystemScheme;
2143
- (0, import_react2.useEffect)(() => {
2144
- if (Appearance) {
2145
- const scheme = Appearance.getColorScheme();
2146
- if (scheme != null) {
2147
- setSystemSchemeRef.current(scheme === "dark" ? "dark" : "light");
2136
+ if (isWeb) return;
2137
+ try {
2138
+ const { TurboModuleRegistry } = require("react-native");
2139
+ const native = TurboModuleRegistry?.get?.("Appearance");
2140
+ const scheme = native?.getColorScheme?.();
2141
+ if (scheme === "dark" || scheme === "light") {
2142
+ setNativeSchemeFallback(scheme);
2148
2143
  }
2144
+ } catch {
2149
2145
  }
2150
2146
  }, []);
2151
2147
  (0, import_react2.useEffect)(() => {
2152
- if (isWeb && typeof window !== "undefined") {
2153
- const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
2154
- if (!mq) return;
2155
- const handler = (e) => {
2156
- setSystemSchemeRef.current(e.matches ? "dark" : "light");
2157
- };
2158
- mq.addEventListener("change", handler);
2159
- return () => mq.removeEventListener("change", handler);
2160
- }
2161
- if (Appearance) {
2162
- const sub = Appearance.addChangeListener(({ colorScheme }) => {
2163
- setSystemSchemeRef.current(colorScheme === "dark" ? "dark" : "light");
2164
- });
2165
- return () => sub.remove();
2166
- }
2167
- return void 0;
2148
+ applyWebTheme(resolvedMode, resolvedConfig.darkMode);
2149
+ setGlobalDarkMode(isDark);
2150
+ }, [isDark, resolvedMode, resolvedConfig.darkMode]);
2151
+ const setWebSchemeRef = (0, import_react2.useRef)(setWebScheme);
2152
+ setWebSchemeRef.current = setWebScheme;
2153
+ (0, import_react2.useEffect)(() => {
2154
+ if (!isWeb || typeof window === "undefined") return;
2155
+ const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
2156
+ if (!mq) return;
2157
+ const handler = (e) => {
2158
+ setWebSchemeRef.current(e.matches ? "dark" : "light");
2159
+ };
2160
+ mq.addEventListener("change", handler);
2161
+ return () => mq.removeEventListener("change", handler);
2168
2162
  }, []);
2169
2163
  (0, import_react2.useEffect)(() => {
2170
2164
  if (configOverride) return;
package/dist/index.mjs CHANGED
@@ -39,17 +39,15 @@ import {
39
39
  useState
40
40
  } from "react";
41
41
  import { jsx } from "react/jsx-runtime";
42
- var Appearance;
42
+ var _useColorScheme;
43
43
  try {
44
- ({ Appearance } = __require("react-native"));
44
+ ({ useColorScheme: _useColorScheme } = __require("react-native"));
45
45
  } catch {
46
46
  }
47
47
  var STORAGE_KEY = "kbach-theme";
48
48
  function loadPersistedMode() {
49
49
  try {
50
- if (isWeb) {
51
- return localStorage.getItem(STORAGE_KEY);
52
- }
50
+ if (isWeb) return localStorage.getItem(STORAGE_KEY);
53
51
  return null;
54
52
  } catch {
55
53
  return null;
@@ -74,23 +72,23 @@ function applyWebTheme(resolvedMode, strategy) {
74
72
  function ThemeProvider({
75
73
  children,
76
74
  defaultMode = "system",
75
+ colorScheme: colorSchemeProp,
77
76
  config: configOverride,
78
77
  disablePersistence = false
79
78
  }) {
80
79
  const [resolvedConfig, setResolvedConfig] = useState(
81
80
  () => configOverride ? buildConfig(configOverride) : getConfig()
82
81
  );
83
- const getSystemScheme = useCallback(() => {
82
+ const [nativeSchemeFallback, setNativeSchemeFallback] = useState(null);
83
+ const hookScheme = _useColorScheme?.();
84
+ const nativeScheme = colorSchemeProp !== void 0 ? colorSchemeProp ?? null : hookScheme ?? nativeSchemeFallback;
85
+ const [webScheme, setWebScheme] = useState(() => {
84
86
  if (isWeb && typeof window !== "undefined") {
85
87
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
86
88
  }
87
- if (Appearance) {
88
- const scheme = Appearance.getColorScheme();
89
- return scheme === "dark" ? "dark" : "light";
90
- }
91
89
  return "light";
92
- }, []);
93
- const [systemScheme, setSystemScheme] = useState(getSystemScheme);
90
+ });
91
+ const systemScheme = isWeb ? webScheme : nativeScheme === "dark" ? "dark" : "light";
94
92
  const [mode, _setMode] = useState(() => {
95
93
  if (!disablePersistence) {
96
94
  const persisted = loadPersistedMode();
@@ -109,36 +107,32 @@ function ThemeProvider({
109
107
  const isDark = resolvedMode === "dark";
110
108
  syncGlobalDarkMode(isDark);
111
109
  useEffect(() => {
112
- applyWebTheme(resolvedMode, resolvedConfig.darkMode);
113
- setGlobalDarkMode(isDark);
114
- }, [isDark, resolvedMode, resolvedConfig.darkMode]);
115
- const setSystemSchemeRef = useRef(setSystemScheme);
116
- setSystemSchemeRef.current = setSystemScheme;
117
- useEffect(() => {
118
- if (Appearance) {
119
- const scheme = Appearance.getColorScheme();
120
- if (scheme != null) {
121
- setSystemSchemeRef.current(scheme === "dark" ? "dark" : "light");
110
+ if (isWeb) return;
111
+ try {
112
+ const { TurboModuleRegistry } = __require("react-native");
113
+ const native = TurboModuleRegistry?.get?.("Appearance");
114
+ const scheme = native?.getColorScheme?.();
115
+ if (scheme === "dark" || scheme === "light") {
116
+ setNativeSchemeFallback(scheme);
122
117
  }
118
+ } catch {
123
119
  }
124
120
  }, []);
125
121
  useEffect(() => {
126
- if (isWeb && typeof window !== "undefined") {
127
- const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
128
- if (!mq) return;
129
- const handler = (e) => {
130
- setSystemSchemeRef.current(e.matches ? "dark" : "light");
131
- };
132
- mq.addEventListener("change", handler);
133
- return () => mq.removeEventListener("change", handler);
134
- }
135
- if (Appearance) {
136
- const sub = Appearance.addChangeListener(({ colorScheme }) => {
137
- setSystemSchemeRef.current(colorScheme === "dark" ? "dark" : "light");
138
- });
139
- return () => sub.remove();
140
- }
141
- return void 0;
122
+ applyWebTheme(resolvedMode, resolvedConfig.darkMode);
123
+ setGlobalDarkMode(isDark);
124
+ }, [isDark, resolvedMode, resolvedConfig.darkMode]);
125
+ const setWebSchemeRef = useRef(setWebScheme);
126
+ setWebSchemeRef.current = setWebScheme;
127
+ useEffect(() => {
128
+ if (!isWeb || typeof window === "undefined") return;
129
+ const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
130
+ if (!mq) return;
131
+ const handler = (e) => {
132
+ setWebSchemeRef.current(e.matches ? "dark" : "light");
133
+ };
134
+ mq.addEventListener("change", handler);
135
+ return () => mq.removeEventListener("change", handler);
142
136
  }, []);
143
137
  useEffect(() => {
144
138
  if (configOverride) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",