@oxyhq/bloom 0.6.16 → 0.6.18

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.
Files changed (58) hide show
  1. package/lib/commonjs/theme/BloomThemeProvider.js +13 -32
  2. package/lib/commonjs/theme/BloomThemeProvider.js.map +1 -1
  3. package/lib/commonjs/theme/apply-dark-class.js +10 -7
  4. package/lib/commonjs/theme/apply-dark-class.js.map +1 -1
  5. package/lib/commonjs/theme/color-scope/index.js +59 -0
  6. package/lib/commonjs/theme/color-scope/index.js.map +1 -0
  7. package/lib/commonjs/theme/color-scope/index.web.js +58 -0
  8. package/lib/commonjs/theme/color-scope/index.web.js.map +1 -0
  9. package/lib/commonjs/theme/color-scope/style-builder.js +36 -0
  10. package/lib/commonjs/theme/color-scope/style-builder.js.map +1 -0
  11. package/lib/commonjs/theme/index.js +8 -1
  12. package/lib/commonjs/theme/index.js.map +1 -1
  13. package/lib/module/theme/BloomThemeProvider.js +15 -33
  14. package/lib/module/theme/BloomThemeProvider.js.map +1 -1
  15. package/lib/module/theme/apply-dark-class.js +10 -7
  16. package/lib/module/theme/apply-dark-class.js.map +1 -1
  17. package/lib/module/theme/color-scope/index.js +53 -0
  18. package/lib/module/theme/color-scope/index.js.map +1 -0
  19. package/lib/module/theme/color-scope/index.web.js +52 -0
  20. package/lib/module/theme/color-scope/index.web.js.map +1 -0
  21. package/lib/module/theme/color-scope/style-builder.js +31 -0
  22. package/lib/module/theme/color-scope/style-builder.js.map +1 -0
  23. package/lib/module/theme/index.js +2 -1
  24. package/lib/module/theme/index.js.map +1 -1
  25. package/lib/typescript/commonjs/icons/common.d.ts +4 -4
  26. package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts +8 -10
  27. package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +5 -4
  29. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/theme/color-scope/index.d.ts +23 -0
  31. package/lib/typescript/commonjs/theme/color-scope/index.d.ts.map +1 -0
  32. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts +21 -0
  33. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts.map +1 -0
  34. package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts +16 -0
  35. package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/theme/index.d.ts +4 -2
  37. package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
  38. package/lib/typescript/module/icons/common.d.ts +4 -4
  39. package/lib/typescript/module/theme/BloomThemeProvider.d.ts +8 -10
  40. package/lib/typescript/module/theme/BloomThemeProvider.d.ts.map +1 -1
  41. package/lib/typescript/module/theme/apply-dark-class.d.ts +5 -4
  42. package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
  43. package/lib/typescript/module/theme/color-scope/index.d.ts +23 -0
  44. package/lib/typescript/module/theme/color-scope/index.d.ts.map +1 -0
  45. package/lib/typescript/module/theme/color-scope/index.web.d.ts +21 -0
  46. package/lib/typescript/module/theme/color-scope/index.web.d.ts.map +1 -0
  47. package/lib/typescript/module/theme/color-scope/style-builder.d.ts +16 -0
  48. package/lib/typescript/module/theme/color-scope/style-builder.d.ts.map +1 -0
  49. package/lib/typescript/module/theme/index.d.ts +4 -2
  50. package/lib/typescript/module/theme/index.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/src/__tests__/BloomColorScope.test.tsx +53 -0
  53. package/src/theme/BloomThemeProvider.tsx +32 -27
  54. package/src/theme/apply-dark-class.ts +8 -8
  55. package/src/theme/color-scope/index.tsx +67 -0
  56. package/src/theme/color-scope/index.web.tsx +65 -0
  57. package/src/theme/color-scope/style-builder.ts +39 -0
  58. package/src/theme/index.ts +3 -2
@@ -13,7 +13,13 @@ import React, {
13
13
  useRef,
14
14
  useState,
15
15
  } from 'react';
16
- import { useColorScheme as useRNColorScheme } from 'react-native';
16
+ import {
17
+ Platform,
18
+ View,
19
+ useColorScheme as useRNColorScheme,
20
+ type StyleProp,
21
+ type ViewStyle,
22
+ } from 'react-native';
17
23
 
18
24
  import { useControllableState } from '../hooks/useControllableState';
19
25
  import { FontLoader } from '../fonts/FontLoader';
@@ -21,6 +27,7 @@ import { FontLoader } from '../fonts/FontLoader';
21
27
  import { applyDarkClass, applyColorPresetVars } from './apply-dark-class';
22
28
  import { buildTheme } from './build-theme';
23
29
  import { type AppColorName } from './color-presets';
30
+ import { buildNativePresetStyle } from './color-scope/style-builder';
24
31
  import {
25
32
  readPersistedTheme,
26
33
  readPersistedThemeSync,
@@ -91,6 +98,13 @@ export interface BloomThemeProviderProps {
91
98
  /** Rendered while native fonts load. Ignored on web. */
92
99
  onFontsLoading?: React.ReactNode;
93
100
 
101
+ /**
102
+ * Style applied to the native `<View>` wrapper that carries the preset's
103
+ * CSS vars. Defaults to `{ flex: 1 }`. Ignored on web (the provider writes
104
+ * vars to `document.documentElement` instead of using a wrapper).
105
+ */
106
+ nativeWrapperStyle?: StyleProp<ViewStyle>;
107
+
94
108
  children: React.ReactNode;
95
109
  }
96
110
 
@@ -251,6 +265,7 @@ export function BloomThemeProvider({
251
265
  onHydrating,
252
266
  fonts = true,
253
267
  onFontsLoading,
268
+ nativeWrapperStyle,
254
269
  children,
255
270
  }: BloomThemeProviderProps) {
256
271
  const rnScheme = useRNColorScheme();
@@ -295,36 +310,26 @@ export function BloomThemeProvider({
295
310
  const shouldAwait = awaitHydration ?? Boolean(persistKey && storage);
296
311
  const isGated = shouldAwait && !hydrated;
297
312
 
313
+ const nativeVarsStyle = useMemo(
314
+ () => (Platform.OS === 'web' ? undefined : buildNativePresetStyle(colorPreset, resolved)),
315
+ [colorPreset, resolved],
316
+ );
317
+
318
+ const content = (
319
+ <FontLoader enabled={fonts} fallback={onFontsLoading}>
320
+ {isGated ? onHydrating ?? null : children}
321
+ </FontLoader>
322
+ );
323
+
298
324
  return (
299
325
  <BloomThemeContext.Provider value={contextValue}>
300
- <FontLoader enabled={fonts} fallback={onFontsLoading}>
301
- {isGated ? onHydrating ?? null : children}
302
- </FontLoader>
326
+ {Platform.OS === 'web' ? (
327
+ content
328
+ ) : (
329
+ <View style={[{ flex: 1 }, nativeVarsStyle, nativeWrapperStyle]}>{content}</View>
330
+ )}
303
331
  </BloomThemeContext.Provider>
304
332
  );
305
333
  }
306
334
 
307
- /**
308
- * Scoped color override for a subtree. Inherits the resolved mode from the
309
- * parent `BloomThemeProvider` but renders descendants with a different preset.
310
- */
311
- export interface BloomColorScopeProps {
312
- colorPreset: AppColorName;
313
- children: React.ReactNode;
314
- }
315
-
316
- export function BloomColorScope({ colorPreset, children }: BloomColorScopeProps) {
317
- const parent = useContext(BloomThemeContext);
318
- if (!parent) {
319
- throw new Error('BloomColorScope must be used within a <BloomThemeProvider>');
320
- }
321
335
 
322
- const contextValue = useMemo<BloomThemeContextValue>(() => {
323
- const theme = buildTheme(colorPreset, parent.theme.mode);
324
- return { ...parent, theme, colorPreset };
325
- }, [colorPreset, parent]);
326
-
327
- return (
328
- <BloomThemeContext.Provider value={contextValue}>{children}</BloomThemeContext.Provider>
329
- );
330
- }
@@ -1,5 +1,6 @@
1
1
  import { Platform } from 'react-native';
2
2
  import { APP_COLOR_PRESETS, type AppColorName } from './color-presets';
3
+ import { getPresetVars } from './preset-vars';
3
4
 
4
5
  export function applyDarkClass(resolved: 'light' | 'dark') {
5
6
  if (Platform.OS === 'web' && typeof document !== 'undefined') {
@@ -11,18 +12,17 @@ export function applyDarkClass(resolved: 'light' | 'dark') {
11
12
  * Apply a color preset's CSS custom properties to the document root.
12
13
  * No-op on native — only affects web.
13
14
  *
14
- * Values are written as raw HSL triples (e.g. `185 100% 20%`), matching the
15
- * shadcn/Tailwind convention where stylesheets wrap them themselves with
16
- * `hsl(var(--primary))`. Writing pre-resolved `hsl(...)` values here would
17
- * produce invalid `hsl(hsl(...))` in consuming stylesheets and break theming.
15
+ * Writes both the raw HSL triples (e.g. `--primary: 185 100% 20%`) and the
16
+ * resolved `--color-*` vars (`--color-primary: hsl(185 100% 20%)`) so both
17
+ * shadcn-style `hsl(var(--primary))` plumbing and Tailwind v4 `@theme`
18
+ * utilities resolve consistently. Includes extended tokens (card, chart-*,
19
+ * content-area, sidebar-*) so consumer apps don't need to synthesize them.
18
20
  */
19
21
  export function applyColorPresetVars(preset: AppColorName, resolved: 'light' | 'dark') {
20
22
  if (Platform.OS !== 'web' || typeof document === 'undefined') return;
23
+ if (!APP_COLOR_PRESETS[preset]) return;
21
24
 
22
- const config = APP_COLOR_PRESETS[preset];
23
- if (!config) return;
24
-
25
- const vars = resolved === 'dark' ? config.dark : config.light;
25
+ const vars = getPresetVars(preset, resolved, { includeResolvedColorVars: true });
26
26
  const root = document.documentElement.style;
27
27
 
28
28
  for (const [key, value] of Object.entries(vars)) {
@@ -0,0 +1,67 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { View, type StyleProp, type ViewStyle } from 'react-native';
3
+
4
+ import { BloomThemeContext, type BloomThemeContextValue } from '../BloomThemeProvider';
5
+ import { buildTheme } from '../build-theme';
6
+ import type { AppColorName } from '../color-presets';
7
+ import { buildNativePresetStyle } from './style-builder';
8
+
9
+ export interface BloomColorScopeProps {
10
+ /** Preset to apply within this subtree. */
11
+ colorPreset: AppColorName;
12
+ /**
13
+ * When `true`, do not render a wrapping `<View>`. The caller owns the
14
+ * element that receives the CSS vars (via `useColorScopeStyle`).
15
+ */
16
+ asChild?: boolean;
17
+ /** Additional style applied to the wrapping `<View>`. Ignored with `asChild`. */
18
+ style?: StyleProp<ViewStyle>;
19
+ children: React.ReactNode;
20
+ }
21
+
22
+ export function BloomColorScope({
23
+ colorPreset,
24
+ asChild = false,
25
+ style,
26
+ children,
27
+ }: BloomColorScopeProps) {
28
+ const parent = useContext(BloomThemeContext);
29
+ if (!parent) {
30
+ throw new Error('BloomColorScope must be used within a <BloomThemeProvider>');
31
+ }
32
+
33
+ const resolvedMode = parent.theme.mode;
34
+
35
+ const contextValue = useMemo<BloomThemeContextValue>(() => {
36
+ const theme = buildTheme(colorPreset, resolvedMode);
37
+ return { ...parent, theme, colorPreset };
38
+ }, [colorPreset, resolvedMode, parent]);
39
+
40
+ const varsStyle = useMemo(
41
+ () => buildNativePresetStyle(colorPreset, resolvedMode),
42
+ [colorPreset, resolvedMode],
43
+ );
44
+
45
+ return (
46
+ <BloomThemeContext.Provider value={contextValue}>
47
+ {asChild ? children : <View style={[{ flex: 1 }, varsStyle, style]}>{children}</View>}
48
+ </BloomThemeContext.Provider>
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Escape hatch for advanced cases where the wrapping element is owned by the
54
+ * caller. Returns a stable native style object carrying the preset's CSS vars.
55
+ * Returns `undefined` on web or when `nativewind` is not installed.
56
+ */
57
+ export function useColorScopeStyle(colorPreset: AppColorName): StyleProp<ViewStyle> {
58
+ const parent = useContext(BloomThemeContext);
59
+ if (!parent) {
60
+ throw new Error('useColorScopeStyle must be used within a <BloomThemeProvider>');
61
+ }
62
+ const resolvedMode = parent.theme.mode;
63
+ return useMemo(
64
+ () => buildNativePresetStyle(colorPreset, resolvedMode),
65
+ [colorPreset, resolvedMode],
66
+ );
67
+ }
@@ -0,0 +1,65 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+
3
+ import { BloomThemeContext, type BloomThemeContextValue } from '../BloomThemeProvider';
4
+ import { buildTheme } from '../build-theme';
5
+ import type { AppColorName } from '../color-presets';
6
+ import { buildScopeVars } from './style-builder';
7
+
8
+ export interface BloomColorScopeProps {
9
+ /** Preset to apply within this subtree. */
10
+ colorPreset: AppColorName;
11
+ /**
12
+ * When `true`, do not render a wrapping element. The caller owns the
13
+ * DOM node that receives the CSS vars (via `useColorScopeStyle`).
14
+ */
15
+ asChild?: boolean;
16
+ /** Additional style applied to the wrapping `<div>`. Ignored with `asChild`. */
17
+ style?: React.CSSProperties;
18
+ children: React.ReactNode;
19
+ }
20
+
21
+ export function BloomColorScope({
22
+ colorPreset,
23
+ asChild = false,
24
+ style,
25
+ children,
26
+ }: BloomColorScopeProps) {
27
+ const parent = useContext(BloomThemeContext);
28
+ if (!parent) {
29
+ throw new Error('BloomColorScope must be used within a <BloomThemeProvider>');
30
+ }
31
+
32
+ const resolvedMode = parent.theme.mode;
33
+
34
+ const contextValue = useMemo<BloomThemeContextValue>(() => {
35
+ const theme = buildTheme(colorPreset, resolvedMode);
36
+ return { ...parent, theme, colorPreset };
37
+ }, [colorPreset, resolvedMode, parent]);
38
+
39
+ const varsStyle = useMemo(
40
+ () => ({ ...(buildScopeVars(colorPreset, resolvedMode) as React.CSSProperties), ...style }),
41
+ [colorPreset, resolvedMode, style],
42
+ );
43
+
44
+ return (
45
+ <BloomThemeContext.Provider value={contextValue}>
46
+ {asChild ? children : <div style={varsStyle}>{children}</div>}
47
+ </BloomThemeContext.Provider>
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Escape hatch for advanced cases where the wrapping element is owned by the
53
+ * caller. Returns a stable React style object carrying the preset's CSS vars.
54
+ */
55
+ export function useColorScopeStyle(colorPreset: AppColorName): React.CSSProperties {
56
+ const parent = useContext(BloomThemeContext);
57
+ if (!parent) {
58
+ throw new Error('useColorScopeStyle must be used within a <BloomThemeProvider>');
59
+ }
60
+ const resolvedMode = parent.theme.mode;
61
+ return useMemo(
62
+ () => buildScopeVars(colorPreset, resolvedMode) as React.CSSProperties,
63
+ [colorPreset, resolvedMode],
64
+ );
65
+ }
@@ -0,0 +1,39 @@
1
+ import { Platform, type StyleProp, type ViewStyle } from 'react-native';
2
+
3
+ import { getPresetVars } from '../preset-vars';
4
+ import type { AppColorName } from '../color-presets';
5
+ import { lazyRequire } from '../../utils/lazy-require';
6
+
7
+ interface NativeWindVarsModule {
8
+ vars: (record: Record<string, string>) => StyleProp<ViewStyle>;
9
+ }
10
+
11
+ const getNativeWindVars = lazyRequire<NativeWindVarsModule>('nativewind');
12
+
13
+ /**
14
+ * Build the CSS custom-property map for a preset, ready to be applied to a
15
+ * subtree. Always includes the resolved `--color-*` vars so Tailwind v4
16
+ * `@theme` utilities (e.g. `bg-background`) honour the scope.
17
+ */
18
+ export function buildScopeVars(
19
+ colorPreset: AppColorName,
20
+ mode: 'light' | 'dark',
21
+ ): Record<string, string> {
22
+ return getPresetVars(colorPreset, mode, { includeResolvedColorVars: true });
23
+ }
24
+
25
+ /**
26
+ * Build a native style object carrying every CSS var of the preset, using
27
+ * NativeWind's `vars()` when available. Returns `undefined` on web (where the
28
+ * provider writes vars to `documentElement` instead) or when `nativewind` is
29
+ * not installed.
30
+ */
31
+ export function buildNativePresetStyle(
32
+ colorPreset: AppColorName,
33
+ mode: 'light' | 'dark',
34
+ ): StyleProp<ViewStyle> {
35
+ if (Platform.OS === 'web') return undefined;
36
+ const module = getNativeWindVars();
37
+ if (!module || typeof module.vars !== 'function') return undefined;
38
+ return module.vars(buildScopeVars(colorPreset, mode));
39
+ }
@@ -1,9 +1,10 @@
1
- export { BloomThemeProvider, BloomColorScope } from './BloomThemeProvider';
1
+ export { BloomThemeProvider } from './BloomThemeProvider';
2
2
  export type {
3
3
  BloomThemeProviderProps,
4
4
  BloomThemeContextValue,
5
- BloomColorScopeProps,
6
5
  } from './BloomThemeProvider';
6
+ export { BloomColorScope, useColorScopeStyle } from './color-scope';
7
+ export type { BloomColorScopeProps } from './color-scope';
7
8
  export { buildTheme, STATUS_COLORS } from './build-theme';
8
9
  export { useTheme, useThemeColor, useBloomTheme } from './use-theme';
9
10
  export type { Theme, ThemeColors, ThemeMode } from './types';