@oxyhq/bloom 0.7.6 → 0.7.7

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 (47) hide show
  1. package/lib/commonjs/theme/apply-dark-class.js +28 -10
  2. package/lib/commonjs/theme/apply-dark-class.js.map +1 -1
  3. package/lib/commonjs/theme/color-presets.js +10 -4
  4. package/lib/commonjs/theme/color-presets.js.map +1 -1
  5. package/lib/commonjs/theme/color-scope/index.web.js +19 -2
  6. package/lib/commonjs/theme/color-scope/index.web.js.map +1 -1
  7. package/lib/commonjs/theme/index.js +6 -0
  8. package/lib/commonjs/theme/index.js.map +1 -1
  9. package/lib/commonjs/theme/preset-vars.js +54 -1
  10. package/lib/commonjs/theme/preset-vars.js.map +1 -1
  11. package/lib/module/theme/apply-dark-class.js +24 -11
  12. package/lib/module/theme/apply-dark-class.js.map +1 -1
  13. package/lib/module/theme/color-presets.js +10 -4
  14. package/lib/module/theme/color-presets.js.map +1 -1
  15. package/lib/module/theme/color-scope/index.web.js +22 -3
  16. package/lib/module/theme/color-scope/index.web.js.map +1 -1
  17. package/lib/module/theme/index.js +1 -1
  18. package/lib/module/theme/index.js.map +1 -1
  19. package/lib/module/theme/preset-vars.js +53 -1
  20. package/lib/module/theme/preset-vars.js.map +1 -1
  21. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +18 -9
  22. package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/theme/color-presets.d.ts +10 -4
  24. package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/theme/color-scope/index.web.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/theme/index.d.ts +1 -1
  27. package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/theme/preset-vars.d.ts +30 -0
  29. package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
  30. package/lib/typescript/module/theme/apply-dark-class.d.ts +18 -9
  31. package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
  32. package/lib/typescript/module/theme/color-presets.d.ts +10 -4
  33. package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
  34. package/lib/typescript/module/theme/color-scope/index.web.d.ts.map +1 -1
  35. package/lib/typescript/module/theme/index.d.ts +1 -1
  36. package/lib/typescript/module/theme/index.d.ts.map +1 -1
  37. package/lib/typescript/module/theme/preset-vars.d.ts +30 -0
  38. package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/__tests__/BloomColorScope.test.tsx +14 -0
  41. package/src/__tests__/apply-dark-class.test.ts +65 -0
  42. package/src/__tests__/preset-vars.test.ts +47 -1
  43. package/src/theme/apply-dark-class.ts +24 -11
  44. package/src/theme/color-presets.ts +10 -4
  45. package/src/theme/color-scope/index.web.tsx +23 -2
  46. package/src/theme/index.ts +1 -0
  47. package/src/theme/preset-vars.ts +53 -1
@@ -1,6 +1,11 @@
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
+ import { getPresetVars, toWebColorValue } from './preset-vars';
4
+
5
+ // Re-exported so the web var-contract helper is reachable from this file (the
6
+ // home of the web write path). Defined in `preset-vars.ts` next to the related
7
+ // HSL parsing so native (which never imports this module) doesn't pull it in.
8
+ export { toWebColorValue } from './preset-vars';
4
9
 
5
10
  export function applyDarkClass(resolved: 'light' | 'dark') {
6
11
  if (Platform.OS === 'web' && typeof document !== 'undefined') {
@@ -10,16 +15,24 @@ export function applyDarkClass(resolved: 'light' | 'dark') {
10
15
 
11
16
  /**
12
17
  * Apply a color preset's CSS custom properties to the document root.
13
- * No-op on native — only affects web.
18
+ * No-op on native — only affects web (early-returns on `Platform.OS !== 'web'`).
19
+ *
20
+ * Web var contract (the form Tailwind v4 `@theme inline` compiles to)
21
+ * -------------------------------------------------------------------
22
+ * The shadcn/Tailwind-v4 web apps compile their color utilities to reference the
23
+ * BASE token directly (`.bg-background { background-color: var(--background) }`),
24
+ * so on web the base `--x` tokens MUST be FULL CSS colors. We therefore write
25
+ * them as `hsl(...)` (e.g. `--primary: hsl(185 100% 20%)`) — `var(--primary)`
26
+ * then resolves to a valid color. The resolved `--color-*` vars are written
27
+ * verbatim as `rgb(...)` (already full colors, used by native `color-mix` alpha
28
+ * utilities; harmless on web). Non-color tokens (`--radius`, etc.) pass through
29
+ * unchanged. See `toWebColorValue`.
14
30
  *
15
- * Writes both the raw HSL triples (e.g. `--primary: 185 100% 20%`) and the
16
- * resolved `--color-*` vars (`--color-primary: rgb(0 94 102)`) so both
17
- * shadcn-style `hsl(var(--primary))` plumbing and Tailwind v4 `@theme`
18
- * utilities resolve consistently. The resolved vars are emitted as `rgb(...)`
19
- * (not `hsl(...)`) so the alpha utilities (`bg-primary/10`) resolve on native —
20
- * see `getPresetVars` / `hslTripletToRgb`. Includes extended tokens (card,
21
- * chart-*, content-area, sidebar-*) so consumer apps don't need to synthesize
22
- * them.
31
+ * NATIVE writes the SAME tokens as RAW HSL triples (no `hsl()` wrapper) via
32
+ * `rootVariables` (`native-root-vars.native.ts`), consumed through bloom's
33
+ * native `global.css` `hsl(var(--x))` indirection that path is untouched here.
34
+ * Includes extended tokens (card, chart-*, content-area, sidebar-*) so consumer
35
+ * apps don't need to synthesize them.
23
36
  */
24
37
  export function applyColorPresetVars(preset: AppColorName, resolved: 'light' | 'dark') {
25
38
  if (Platform.OS !== 'web' || typeof document === 'undefined') return;
@@ -29,6 +42,6 @@ export function applyColorPresetVars(preset: AppColorName, resolved: 'light' | '
29
42
  const root = document.documentElement.style;
30
43
 
31
44
  for (const [key, value] of Object.entries(vars)) {
32
- root.setProperty(key, value);
45
+ root.setProperty(key, toWebColorValue(key, value));
33
46
  }
34
47
  }
@@ -7,10 +7,16 @@ export type AppColorName = 'teal' | 'blue' | 'green' | 'amber' | 'yellow' | 'red
7
7
  * names) — keep in mind the values are platform-agnostic **raw HSL triples**
8
8
  * (e.g. `'185 100% 20%'` or `'185 100% 20% / 0.5'`), not CSS-resolved colors.
9
9
  * The same map drives both:
10
- * - the web layer (written verbatim into `document.documentElement.style`
11
- * so Tailwind's `hsl(var(--primary))` plumbing picks them up), and
12
- * - the native layer (`buildTheme` resolves them into `hsl(...)` strings
13
- * consumable by React Native styles).
10
+ * - the web layer: the base `--x` tokens are wrapped to full `hsl(...)` colors
11
+ * (via `toWebColorValue`) before being written to `document.documentElement`
12
+ * / a `BloomColorScope` element, so `var(--x)` — the form Tailwind v4
13
+ * `@theme inline` compiles its color utilities to — resolves to a valid
14
+ * color directly; the resolved `--color-*` companions are written as
15
+ * `rgb(...)`, and
16
+ * - the native layer (`buildTheme` resolves the raw triples into `hsl(...)`
17
+ * strings consumable by React Native styles; `native-root-vars.native.ts`
18
+ * writes the raw triples through bloom's `hsl(var(--x))` `global.css`
19
+ * indirection).
14
20
  *
15
21
  * The `--` prefix is an implementation detail we will drop in a future major.
16
22
  */
@@ -3,8 +3,29 @@ import React, { Children, cloneElement, isValidElement, useContext, useMemo } fr
3
3
  import { BloomThemeContext, type BloomThemeContextValue } from '../BloomThemeProvider';
4
4
  import { buildTheme } from '../build-theme';
5
5
  import type { AppColorName } from '../color-presets';
6
+ import { toWebColorValue } from '../preset-vars';
6
7
  import { buildScopeVars } from './style-builder';
7
8
 
9
+ /**
10
+ * `buildScopeVars` returns the preset's tokens as platform-agnostic raw HSL
11
+ * triples (shared with the native write path). On web they are scoped onto an
12
+ * element's inline `style`, where Tailwind v4's compiled utilities read them as
13
+ * `var(--x)` directly — so the base tokens MUST be full CSS colors. Wrap each
14
+ * raw triple in `hsl(...)` via `toWebColorValue`; `--color-*` rgb vars and
15
+ * non-color tokens pass through. See `toWebColorValue` in `../preset-vars`.
16
+ */
17
+ function buildWebScopeVars(
18
+ colorPreset: AppColorName,
19
+ mode: 'light' | 'dark',
20
+ ): React.CSSProperties {
21
+ const raw = buildScopeVars(colorPreset, mode);
22
+ const out: Record<string, string> = {};
23
+ for (const [key, value] of Object.entries(raw)) {
24
+ out[key] = toWebColorValue(key, value);
25
+ }
26
+ return out as React.CSSProperties;
27
+ }
28
+
8
29
  export interface BloomColorScopeProps {
9
30
  /**
10
31
  * Preset to apply within this subtree. When `undefined`, the scope is a
@@ -63,7 +84,7 @@ export function BloomColorScope({
63
84
  }, [colorPreset, resolvedMode, parent]);
64
85
 
65
86
  const varsStyle = useMemo(
66
- () => buildScopeVars(colorPreset, resolvedMode) as React.CSSProperties,
87
+ () => buildWebScopeVars(colorPreset, resolvedMode),
67
88
  [colorPreset, resolvedMode],
68
89
  );
69
90
 
@@ -104,7 +125,7 @@ export function useColorScopeStyle(colorPreset: AppColorName): React.CSSProperti
104
125
  }
105
126
  const resolvedMode = parent.theme.mode;
106
127
  return useMemo(
107
- () => buildScopeVars(colorPreset, resolvedMode) as React.CSSProperties,
128
+ () => buildWebScopeVars(colorPreset, resolvedMode),
108
129
  [colorPreset, resolvedMode],
109
130
  );
110
131
  }
@@ -19,6 +19,7 @@ export {
19
19
  export {
20
20
  getPresetVars,
21
21
  applyPresetVarsToDocument,
22
+ toWebColorValue,
22
23
  } from './preset-vars';
23
24
  export type { PresetVarsOptions } from './preset-vars';
24
25
  export { applyDarkClass } from './apply-dark-class';
@@ -83,6 +83,53 @@ export function hslTripletToRgb(triplet: string): string {
83
83
  return `rgb(${red} ${green} ${blue})`;
84
84
  }
85
85
 
86
+ /**
87
+ * Prefix that marks a Tailwind v4 resolved color var (`--color-primary`). These
88
+ * are already emitted as full `rgb(...)` colors by `getPresetVars` and must be
89
+ * written to web verbatim — wrapping them would corrupt the value.
90
+ */
91
+ const RESOLVED_COLOR_VAR_PREFIX = '--color-';
92
+
93
+ /**
94
+ * Matches a shadcn-style raw HSL triple — `H S% L%`, optionally with an alpha
95
+ * tail `H S% L% / A`. The value starts with a digit (or a leading `-`) and
96
+ * carries at least one `%` (the saturation/lightness units), which together
97
+ * distinguish a bare triple from an already-resolved CSS color (`rgb(...)`,
98
+ * `hsl(...)`, `#fff`, named colors) or a unitless/length token (`--radius`).
99
+ * Mirrors the triple shape parsed by `hslTripletToRgb`.
100
+ */
101
+ const RAW_HSL_TRIPLE = /^-?\d[\d.]*\s+[\d.]+%/;
102
+
103
+ /**
104
+ * Transform a single preset var into the value to write to a WEB element
105
+ * (`document.documentElement` or a `BloomColorScope` subtree element).
106
+ *
107
+ * The web apps (Vite + Tailwind v4 + shadcn) compile their `@theme inline`
108
+ * block so the color utilities reference the BASE token directly — e.g.
109
+ * `.bg-background { background-color: var(--background) }`. So on web the base
110
+ * `--x` tokens MUST resolve to a COMPLETE CSS color; a bare HSL triple
111
+ * (`185 50% 5%`) is invalid as a `background-color` value and renders
112
+ * transparent (the production incident this fixes). We therefore wrap raw
113
+ * triples in `hsl(...)`, preserving the exact preset color math (and any `/ A`
114
+ * alpha tail, valid in modern `hsl(H S% L% / A)` syntax) with zero rounding
115
+ * drift.
116
+ *
117
+ * Values that are already full colors pass through unchanged:
118
+ * - `--color-*` resolved vars (already `rgb(...)` from `getPresetVars`),
119
+ * - any value not shaped like a raw HSL triple (`--radius: 0.5rem`,
120
+ * pre-resolved `rgb(...)`/`hsl(...)`, etc.).
121
+ *
122
+ * NATIVE never calls this — `getPresetVars` returns raw triples verbatim and
123
+ * `native-root-vars.native.ts` writes them through bloom's `hsl(var(--x))`
124
+ * native `global.css` indirection. Pure function — no side effects — so it is
125
+ * unit-testable in isolation.
126
+ */
127
+ export function toWebColorValue(key: string, value: string): string {
128
+ if (key.startsWith(RESOLVED_COLOR_VAR_PREFIX)) return value;
129
+ if (!RAW_HSL_TRIPLE.test(value)) return value;
130
+ return `hsl(${value})`;
131
+ }
132
+
86
133
  const RESOLVED_COLOR_MAP: Record<string, string> = {
87
134
  '--background': '--color-background',
88
135
  '--foreground': '--color-foreground',
@@ -183,6 +230,11 @@ export function getPresetVars(
183
230
  * on native. `BloomThemeProvider` already writes the base preset vars on web;
184
231
  * call this only when an app needs the extended (card/chart/sidebar) tokens on
185
232
  * the document root.
233
+ *
234
+ * The base raw-HSL-triple tokens are wrapped to full `hsl(...)` colors via
235
+ * `toWebColorValue` so `var(--x)` (the form Tailwind v4 `@theme inline`
236
+ * compiles to) resolves directly on web; `--color-*` rgb vars pass through. See
237
+ * `toWebColorValue` for the web var contract.
186
238
  */
187
239
  export function applyPresetVarsToDocument(
188
240
  colorName: AppColorName,
@@ -194,6 +246,6 @@ export function applyPresetVarsToDocument(
194
246
  const vars = getPresetVars(colorName, mode, options);
195
247
  const root = document.documentElement.style;
196
248
  for (const [key, value] of Object.entries(vars)) {
197
- root.setProperty(key, value);
249
+ root.setProperty(key, toWebColorValue(key, value));
198
250
  }
199
251
  }