@oxyhq/bloom 0.6.17 → 0.6.19

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 (49) hide show
  1. package/lib/commonjs/theme/BloomThemeProvider.js +13 -4
  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 +21 -14
  6. package/lib/commonjs/theme/color-scope/index.js.map +1 -1
  7. package/lib/commonjs/theme/color-scope/index.web.js +29 -8
  8. package/lib/commonjs/theme/color-scope/index.web.js.map +1 -1
  9. package/lib/commonjs/theme/color-scope/style-builder.js +18 -0
  10. package/lib/commonjs/theme/color-scope/style-builder.js.map +1 -1
  11. package/lib/module/theme/BloomThemeProvider.js +14 -5
  12. package/lib/module/theme/BloomThemeProvider.js.map +1 -1
  13. package/lib/module/theme/apply-dark-class.js +10 -7
  14. package/lib/module/theme/apply-dark-class.js.map +1 -1
  15. package/lib/module/theme/color-scope/index.js +23 -16
  16. package/lib/module/theme/color-scope/index.js.map +1 -1
  17. package/lib/module/theme/color-scope/index.web.js +30 -9
  18. package/lib/module/theme/color-scope/index.web.js.map +1 -1
  19. package/lib/module/theme/color-scope/style-builder.js +17 -0
  20. package/lib/module/theme/color-scope/style-builder.js.map +1 -1
  21. package/lib/typescript/commonjs/icons/common.d.ts +8 -8
  22. package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts +8 -1
  23. package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +5 -4
  25. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/theme/color-scope/index.d.ts +4 -4
  27. package/lib/typescript/commonjs/theme/color-scope/index.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts +6 -7
  29. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts +8 -0
  31. package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts.map +1 -1
  32. package/lib/typescript/module/icons/common.d.ts +8 -8
  33. package/lib/typescript/module/theme/BloomThemeProvider.d.ts +8 -1
  34. package/lib/typescript/module/theme/BloomThemeProvider.d.ts.map +1 -1
  35. package/lib/typescript/module/theme/apply-dark-class.d.ts +5 -4
  36. package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
  37. package/lib/typescript/module/theme/color-scope/index.d.ts +4 -4
  38. package/lib/typescript/module/theme/color-scope/index.d.ts.map +1 -1
  39. package/lib/typescript/module/theme/color-scope/index.web.d.ts +6 -7
  40. package/lib/typescript/module/theme/color-scope/index.web.d.ts.map +1 -1
  41. package/lib/typescript/module/theme/color-scope/style-builder.d.ts +8 -0
  42. package/lib/typescript/module/theme/color-scope/style-builder.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/__tests__/BloomColorScope.test.tsx +27 -3
  45. package/src/theme/BloomThemeProvider.tsx +32 -4
  46. package/src/theme/apply-dark-class.ts +8 -8
  47. package/src/theme/color-scope/index.tsx +28 -26
  48. package/src/theme/color-scope/index.web.tsx +35 -16
  49. package/src/theme/color-scope/style-builder.ts +25 -0
@@ -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)) {
@@ -1,37 +1,28 @@
1
- import React, { useContext, useMemo } from 'react';
1
+ import React, { Children, cloneElement, isValidElement, useContext, useMemo } from 'react';
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native';
3
3
 
4
4
  import { BloomThemeContext, type BloomThemeContextValue } from '../BloomThemeProvider';
5
5
  import { buildTheme } from '../build-theme';
6
6
  import type { AppColorName } from '../color-presets';
7
- import { lazyRequire } from '../../utils/lazy-require';
8
- import { buildScopeVars } from './style-builder';
9
-
10
- interface NativeWindVarsModule {
11
- vars: (record: Record<string, string>) => StyleProp<ViewStyle>;
12
- }
13
-
14
- const getNativeWindVars = lazyRequire<NativeWindVarsModule>('nativewind');
15
-
16
- function presetStyle(colorPreset: AppColorName, mode: 'light' | 'dark'): StyleProp<ViewStyle> {
17
- const module = getNativeWindVars();
18
- if (!module || typeof module.vars !== 'function') return undefined;
19
- return module.vars(buildScopeVars(colorPreset, mode));
20
- }
7
+ import { buildNativePresetStyle } from './style-builder';
21
8
 
22
9
  export interface BloomColorScopeProps {
23
10
  /** Preset to apply within this subtree. */
24
11
  colorPreset: AppColorName;
25
12
  /**
26
- * When `true`, do not render a wrapping `<View>`. The caller owns the
27
- * element that receives the CSS vars (via `useColorScopeStyle`).
13
+ * When `true`, do not render a wrapping `<View>`. The single child is cloned
14
+ * with the scope's CSS vars merged into its `style` prop (Radix-style).
28
15
  */
29
16
  asChild?: boolean;
30
- /** Additional style applied to the wrapping `<View>`. Ignored with `asChild`. */
17
+ /** Additional style applied to the wrapping `<View>` (or merged into the cloned child with `asChild`). */
31
18
  style?: StyleProp<ViewStyle>;
32
19
  children: React.ReactNode;
33
20
  }
34
21
 
22
+ interface StyleableProps {
23
+ style?: StyleProp<ViewStyle>;
24
+ }
25
+
35
26
  export function BloomColorScope({
36
27
  colorPreset,
37
28
  asChild = false,
@@ -51,21 +42,32 @@ export function BloomColorScope({
51
42
  }, [colorPreset, resolvedMode, parent]);
52
43
 
53
44
  const varsStyle = useMemo(
54
- () => presetStyle(colorPreset, resolvedMode),
45
+ () => buildNativePresetStyle(colorPreset, resolvedMode),
55
46
  [colorPreset, resolvedMode],
56
47
  );
57
48
 
58
- return (
59
- <BloomThemeContext.Provider value={contextValue}>
60
- {asChild ? children : <View style={[{ flex: 1 }, varsStyle, style]}>{children}</View>}
61
- </BloomThemeContext.Provider>
62
- );
49
+ let content: React.ReactNode;
50
+ if (asChild) {
51
+ const child = Children.only(children);
52
+ if (!isValidElement<StyleableProps>(child)) {
53
+ throw new Error(
54
+ 'BloomColorScope with `asChild` requires a single React element child that accepts a `style` prop.',
55
+ );
56
+ }
57
+ const childStyle = child.props.style;
58
+ const mergedStyle: StyleProp<ViewStyle> = [varsStyle, style, childStyle];
59
+ content = cloneElement(child, { style: mergedStyle });
60
+ } else {
61
+ content = <View style={[{ flex: 1 }, varsStyle, style]}>{children}</View>;
62
+ }
63
+
64
+ return <BloomThemeContext.Provider value={contextValue}>{content}</BloomThemeContext.Provider>;
63
65
  }
64
66
 
65
67
  /**
66
68
  * Escape hatch for advanced cases where the wrapping element is owned by the
67
69
  * caller. Returns a stable native style object carrying the preset's CSS vars.
68
- * Returns `undefined` when nativewind is not installed.
70
+ * Returns `undefined` on web or when `nativewind` is not installed.
69
71
  */
70
72
  export function useColorScopeStyle(colorPreset: AppColorName): StyleProp<ViewStyle> {
71
73
  const parent = useContext(BloomThemeContext);
@@ -74,7 +76,7 @@ export function useColorScopeStyle(colorPreset: AppColorName): StyleProp<ViewSty
74
76
  }
75
77
  const resolvedMode = parent.theme.mode;
76
78
  return useMemo(
77
- () => presetStyle(colorPreset, resolvedMode),
79
+ () => buildNativePresetStyle(colorPreset, resolvedMode),
78
80
  [colorPreset, resolvedMode],
79
81
  );
80
82
  }
@@ -1,24 +1,33 @@
1
- import React, { useContext, useMemo } from 'react';
1
+ import React, { Children, cloneElement, isValidElement, useContext, useMemo } from 'react';
2
2
 
3
- import { BloomThemeContext } from '../BloomThemeProvider';
3
+ import { BloomThemeContext, type BloomThemeContextValue } from '../BloomThemeProvider';
4
4
  import { buildTheme } from '../build-theme';
5
5
  import type { AppColorName } from '../color-presets';
6
- import { BloomThemeContextValue } from '../BloomThemeProvider';
7
6
  import { buildScopeVars } from './style-builder';
8
7
 
9
8
  export interface BloomColorScopeProps {
10
9
  /** Preset to apply within this subtree. */
11
10
  colorPreset: AppColorName;
12
11
  /**
13
- * When `true`, do not render a wrapping element. The caller is responsible
14
- * for placing the returned context provider over a DOM node that owns the
15
- * CSS vars (via `useColorScopeStyle`).
12
+ * When `true`, do not render a wrapping `<div>`. The single child is cloned
13
+ * with the scope's CSS vars merged into its `style` prop (Radix-style).
16
14
  */
17
15
  asChild?: boolean;
16
+ /** Additional style applied to the wrapping `<div>` (or merged into the cloned child with `asChild`). */
17
+ style?: React.CSSProperties;
18
18
  children: React.ReactNode;
19
19
  }
20
20
 
21
- export function BloomColorScope({ colorPreset, asChild = false, children }: BloomColorScopeProps) {
21
+ interface StyleableProps {
22
+ style?: React.CSSProperties;
23
+ }
24
+
25
+ export function BloomColorScope({
26
+ colorPreset,
27
+ asChild = false,
28
+ style,
29
+ children,
30
+ }: BloomColorScopeProps) {
22
31
  const parent = useContext(BloomThemeContext);
23
32
  if (!parent) {
24
33
  throw new Error('BloomColorScope must be used within a <BloomThemeProvider>');
@@ -31,23 +40,33 @@ export function BloomColorScope({ colorPreset, asChild = false, children }: Bloo
31
40
  return { ...parent, theme, colorPreset };
32
41
  }, [colorPreset, resolvedMode, parent]);
33
42
 
34
- const style = useMemo(
43
+ const varsStyle = useMemo(
35
44
  () => buildScopeVars(colorPreset, resolvedMode) as React.CSSProperties,
36
45
  [colorPreset, resolvedMode],
37
46
  );
38
47
 
39
- return (
40
- <BloomThemeContext.Provider value={contextValue}>
41
- {asChild ? children : <div style={style}>{children}</div>}
42
- </BloomThemeContext.Provider>
43
- );
48
+ let content: React.ReactNode;
49
+ if (asChild) {
50
+ const child = Children.only(children);
51
+ if (!isValidElement<StyleableProps>(child)) {
52
+ throw new Error(
53
+ 'BloomColorScope with `asChild` requires a single React element child that accepts a `style` prop.',
54
+ );
55
+ }
56
+ const childStyle = child.props.style;
57
+ const mergedStyle: React.CSSProperties = { ...varsStyle, ...style, ...childStyle };
58
+ content = cloneElement(child, { style: mergedStyle });
59
+ } else {
60
+ const mergedStyle: React.CSSProperties = { ...varsStyle, ...style };
61
+ content = <div style={mergedStyle}>{children}</div>;
62
+ }
63
+
64
+ return <BloomThemeContext.Provider value={contextValue}>{content}</BloomThemeContext.Provider>;
44
65
  }
45
66
 
46
67
  /**
47
68
  * Escape hatch for advanced cases where the wrapping element is owned by the
48
- * caller (e.g. a Pressable, a NativeWind-styled View that already has a style
49
- * prop). Returns a stable React `style` object carrying every CSS custom
50
- * property of the preset.
69
+ * caller. Returns a stable React style object carrying the preset's CSS vars.
51
70
  */
52
71
  export function useColorScopeStyle(colorPreset: AppColorName): React.CSSProperties {
53
72
  const parent = useContext(BloomThemeContext);
@@ -1,5 +1,14 @@
1
+ import { Platform, type StyleProp, type ViewStyle } from 'react-native';
2
+
1
3
  import { getPresetVars } from '../preset-vars';
2
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');
3
12
 
4
13
  /**
5
14
  * Build the CSS custom-property map for a preset, ready to be applied to a
@@ -12,3 +21,19 @@ export function buildScopeVars(
12
21
  ): Record<string, string> {
13
22
  return getPresetVars(colorPreset, mode, { includeResolvedColorVars: true });
14
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
+ }