@oxyhq/bloom 0.17.0 → 0.18.1

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 (245) hide show
  1. package/lib/commonjs/admonition/index.js +24 -23
  2. package/lib/commonjs/admonition/index.js.map +1 -1
  3. package/lib/commonjs/combobox/Combobox.js +7 -7
  4. package/lib/commonjs/combobox/Combobox.js.map +1 -1
  5. package/lib/commonjs/context-menu/context.js +2 -2
  6. package/lib/commonjs/context-menu/context.js.map +1 -1
  7. package/lib/commonjs/context-menu/index.js +25 -25
  8. package/lib/commonjs/context-menu/index.js.map +1 -1
  9. package/lib/commonjs/context-menu/index.web.js +27 -27
  10. package/lib/commonjs/context-menu/index.web.js.map +1 -1
  11. package/lib/commonjs/dialog/SheetShell.js +1 -1
  12. package/lib/commonjs/index.js +134 -38
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/index.web.js +134 -38
  15. package/lib/commonjs/index.web.js.map +1 -1
  16. package/lib/commonjs/input-group/InputGroup.js +1 -1
  17. package/lib/commonjs/menu/context.js +2 -2
  18. package/lib/commonjs/menu/context.js.map +1 -1
  19. package/lib/commonjs/menu/index.js +17 -17
  20. package/lib/commonjs/menu/index.js.map +1 -1
  21. package/lib/commonjs/menu/index.web.js +16 -16
  22. package/lib/commonjs/menu/index.web.js.map +1 -1
  23. package/lib/commonjs/popover/context.js +1 -1
  24. package/lib/commonjs/popover/context.js.map +1 -1
  25. package/lib/commonjs/popover/index.js +6 -6
  26. package/lib/commonjs/popover/index.js.map +1 -1
  27. package/lib/commonjs/popover/index.web.js +6 -6
  28. package/lib/commonjs/popover/index.web.js.map +1 -1
  29. package/lib/commonjs/search-input/index.js +4 -5
  30. package/lib/commonjs/search-input/index.js.map +1 -1
  31. package/lib/commonjs/segmented-control/index.js +18 -18
  32. package/lib/commonjs/segmented-control/index.js.map +1 -1
  33. package/lib/commonjs/select/common.js +2 -2
  34. package/lib/commonjs/select/common.js.map +1 -1
  35. package/lib/commonjs/select/index.js +34 -38
  36. package/lib/commonjs/select/index.js.map +1 -1
  37. package/lib/commonjs/select/index.web.js +31 -35
  38. package/lib/commonjs/select/index.web.js.map +1 -1
  39. package/lib/commonjs/tabs/Tabs.js +7 -7
  40. package/lib/commonjs/tabs/Tabs.js.map +1 -1
  41. package/lib/commonjs/tabs/index.js +6 -6
  42. package/lib/commonjs/text-field/index.js +14 -14
  43. package/lib/commonjs/text-field/index.js.map +1 -1
  44. package/lib/commonjs/theme/color-scope/style-builder.js +6 -6
  45. package/lib/commonjs/tooltip/index.js +11 -11
  46. package/lib/commonjs/tooltip/index.js.map +1 -1
  47. package/lib/commonjs/tooltip/index.web.js +11 -11
  48. package/lib/commonjs/tooltip/index.web.js.map +1 -1
  49. package/lib/module/admonition/index.js +18 -17
  50. package/lib/module/admonition/index.js.map +1 -1
  51. package/lib/module/combobox/Combobox.js +7 -7
  52. package/lib/module/combobox/Combobox.js.map +1 -1
  53. package/lib/module/context-menu/context.js +2 -2
  54. package/lib/module/context-menu/context.js.map +1 -1
  55. package/lib/module/context-menu/index.js +17 -17
  56. package/lib/module/context-menu/index.js.map +1 -1
  57. package/lib/module/context-menu/index.web.js +19 -19
  58. package/lib/module/context-menu/index.web.js.map +1 -1
  59. package/lib/module/dialog/SheetShell.js +1 -1
  60. package/lib/module/index.js +11 -11
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/index.web.js +11 -11
  63. package/lib/module/index.web.js.map +1 -1
  64. package/lib/module/input-group/InputGroup.js +1 -1
  65. package/lib/module/menu/context.js +2 -2
  66. package/lib/module/menu/context.js.map +1 -1
  67. package/lib/module/menu/index.js +9 -9
  68. package/lib/module/menu/index.js.map +1 -1
  69. package/lib/module/menu/index.web.js +8 -8
  70. package/lib/module/menu/index.web.js.map +1 -1
  71. package/lib/module/popover/context.js +1 -1
  72. package/lib/module/popover/context.js.map +1 -1
  73. package/lib/module/popover/index.js +3 -3
  74. package/lib/module/popover/index.js.map +1 -1
  75. package/lib/module/popover/index.web.js +3 -3
  76. package/lib/module/popover/index.web.js.map +1 -1
  77. package/lib/module/search-input/index.js +4 -4
  78. package/lib/module/search-input/index.js.map +1 -1
  79. package/lib/module/segmented-control/index.js +15 -15
  80. package/lib/module/segmented-control/index.js.map +1 -1
  81. package/lib/module/select/common.js +1 -1
  82. package/lib/module/select/common.js.map +1 -1
  83. package/lib/module/select/index.js +25 -29
  84. package/lib/module/select/index.js.map +1 -1
  85. package/lib/module/select/index.web.js +22 -26
  86. package/lib/module/select/index.web.js.map +1 -1
  87. package/lib/module/tabs/Tabs.js +6 -6
  88. package/lib/module/tabs/Tabs.js.map +1 -1
  89. package/lib/module/tabs/index.js +1 -1
  90. package/lib/module/tabs/index.js.map +1 -1
  91. package/lib/module/text-field/index.js +8 -8
  92. package/lib/module/text-field/index.js.map +1 -1
  93. package/lib/module/theme/color-scope/style-builder.js +6 -6
  94. package/lib/module/tooltip/index.js +6 -6
  95. package/lib/module/tooltip/index.js.map +1 -1
  96. package/lib/module/tooltip/index.web.js +6 -6
  97. package/lib/module/tooltip/index.web.js.map +1 -1
  98. package/lib/typescript/commonjs/admonition/index.d.ts +14 -13
  99. package/lib/typescript/commonjs/admonition/index.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/combobox/Combobox.d.ts +7 -7
  101. package/lib/typescript/commonjs/combobox/Combobox.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/context-menu/index.d.ts +9 -9
  103. package/lib/typescript/commonjs/context-menu/index.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/context-menu/index.web.d.ts +9 -9
  105. package/lib/typescript/commonjs/context-menu/index.web.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/context-menu/types.d.ts +6 -6
  107. package/lib/typescript/commonjs/context-menu/types.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/dialog/SheetShell.d.ts +1 -1
  109. package/lib/typescript/commonjs/index.d.ts +11 -11
  110. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.web.d.ts +11 -11
  112. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/input-group/types.d.ts +1 -1
  114. package/lib/typescript/commonjs/menu/index.d.ts +9 -9
  115. package/lib/typescript/commonjs/menu/index.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/menu/index.web.d.ts +9 -9
  117. package/lib/typescript/commonjs/menu/index.web.d.ts.map +1 -1
  118. package/lib/typescript/commonjs/menu/types.d.ts +5 -5
  119. package/lib/typescript/commonjs/menu/types.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/popover/index.d.ts +5 -5
  121. package/lib/typescript/commonjs/popover/index.d.ts.map +1 -1
  122. package/lib/typescript/commonjs/popover/index.web.d.ts +5 -5
  123. package/lib/typescript/commonjs/popover/index.web.d.ts.map +1 -1
  124. package/lib/typescript/commonjs/popover/types.d.ts +1 -1
  125. package/lib/typescript/commonjs/popover/types.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/search-input/index.d.ts +3 -3
  127. package/lib/typescript/commonjs/search-input/index.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/segmented-control/index.d.ts +13 -13
  129. package/lib/typescript/commonjs/segmented-control/index.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/select/common.d.ts +1 -1
  131. package/lib/typescript/commonjs/select/common.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/select/index.d.ts +12 -12
  133. package/lib/typescript/commonjs/select/index.d.ts.map +1 -1
  134. package/lib/typescript/commonjs/select/index.web.d.ts +12 -12
  135. package/lib/typescript/commonjs/select/index.web.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/select/types.d.ts +10 -10
  137. package/lib/typescript/commonjs/select/types.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/tabs/Tabs.d.ts +4 -4
  139. package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/tabs/index.d.ts +2 -2
  141. package/lib/typescript/commonjs/tabs/index.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/tabs/types.d.ts +3 -3
  143. package/lib/typescript/commonjs/tabs/types.d.ts.map +1 -1
  144. package/lib/typescript/commonjs/text-field/index.d.ts +8 -8
  145. package/lib/typescript/commonjs/text-field/index.d.ts.map +1 -1
  146. package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts +6 -6
  147. package/lib/typescript/commonjs/tooltip/index.d.ts +6 -6
  148. package/lib/typescript/commonjs/tooltip/index.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/tooltip/index.web.d.ts +6 -6
  150. package/lib/typescript/commonjs/tooltip/index.web.d.ts.map +1 -1
  151. package/lib/typescript/module/admonition/index.d.ts +14 -13
  152. package/lib/typescript/module/admonition/index.d.ts.map +1 -1
  153. package/lib/typescript/module/combobox/Combobox.d.ts +7 -7
  154. package/lib/typescript/module/combobox/Combobox.d.ts.map +1 -1
  155. package/lib/typescript/module/context-menu/index.d.ts +9 -9
  156. package/lib/typescript/module/context-menu/index.d.ts.map +1 -1
  157. package/lib/typescript/module/context-menu/index.web.d.ts +9 -9
  158. package/lib/typescript/module/context-menu/index.web.d.ts.map +1 -1
  159. package/lib/typescript/module/context-menu/types.d.ts +6 -6
  160. package/lib/typescript/module/context-menu/types.d.ts.map +1 -1
  161. package/lib/typescript/module/dialog/SheetShell.d.ts +1 -1
  162. package/lib/typescript/module/index.d.ts +11 -11
  163. package/lib/typescript/module/index.d.ts.map +1 -1
  164. package/lib/typescript/module/index.web.d.ts +11 -11
  165. package/lib/typescript/module/index.web.d.ts.map +1 -1
  166. package/lib/typescript/module/input-group/types.d.ts +1 -1
  167. package/lib/typescript/module/menu/index.d.ts +9 -9
  168. package/lib/typescript/module/menu/index.d.ts.map +1 -1
  169. package/lib/typescript/module/menu/index.web.d.ts +9 -9
  170. package/lib/typescript/module/menu/index.web.d.ts.map +1 -1
  171. package/lib/typescript/module/menu/types.d.ts +5 -5
  172. package/lib/typescript/module/menu/types.d.ts.map +1 -1
  173. package/lib/typescript/module/popover/index.d.ts +5 -5
  174. package/lib/typescript/module/popover/index.d.ts.map +1 -1
  175. package/lib/typescript/module/popover/index.web.d.ts +5 -5
  176. package/lib/typescript/module/popover/index.web.d.ts.map +1 -1
  177. package/lib/typescript/module/popover/types.d.ts +1 -1
  178. package/lib/typescript/module/popover/types.d.ts.map +1 -1
  179. package/lib/typescript/module/search-input/index.d.ts +3 -3
  180. package/lib/typescript/module/search-input/index.d.ts.map +1 -1
  181. package/lib/typescript/module/segmented-control/index.d.ts +13 -13
  182. package/lib/typescript/module/segmented-control/index.d.ts.map +1 -1
  183. package/lib/typescript/module/select/common.d.ts +1 -1
  184. package/lib/typescript/module/select/common.d.ts.map +1 -1
  185. package/lib/typescript/module/select/index.d.ts +12 -12
  186. package/lib/typescript/module/select/index.d.ts.map +1 -1
  187. package/lib/typescript/module/select/index.web.d.ts +12 -12
  188. package/lib/typescript/module/select/index.web.d.ts.map +1 -1
  189. package/lib/typescript/module/select/types.d.ts +10 -10
  190. package/lib/typescript/module/select/types.d.ts.map +1 -1
  191. package/lib/typescript/module/tabs/Tabs.d.ts +4 -4
  192. package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -1
  193. package/lib/typescript/module/tabs/index.d.ts +2 -2
  194. package/lib/typescript/module/tabs/index.d.ts.map +1 -1
  195. package/lib/typescript/module/tabs/types.d.ts +3 -3
  196. package/lib/typescript/module/tabs/types.d.ts.map +1 -1
  197. package/lib/typescript/module/text-field/index.d.ts +8 -8
  198. package/lib/typescript/module/text-field/index.d.ts.map +1 -1
  199. package/lib/typescript/module/theme/color-scope/style-builder.d.ts +6 -6
  200. package/lib/typescript/module/tooltip/index.d.ts +6 -6
  201. package/lib/typescript/module/tooltip/index.d.ts.map +1 -1
  202. package/lib/typescript/module/tooltip/index.web.d.ts +6 -6
  203. package/lib/typescript/module/tooltip/index.web.d.ts.map +1 -1
  204. package/package.json +1 -7
  205. package/src/__tests__/public-api-contract.test.ts +19 -7
  206. package/src/__tests__/useNavigationTheme.test.tsx +227 -0
  207. package/src/admonition/index.tsx +22 -21
  208. package/src/combobox/Combobox.tsx +14 -14
  209. package/src/context-menu/ContextMenu.stories.tsx +26 -18
  210. package/src/context-menu/context.ts +2 -2
  211. package/src/context-menu/index.tsx +24 -24
  212. package/src/context-menu/index.web.tsx +26 -26
  213. package/src/context-menu/types.ts +6 -6
  214. package/src/dialog/SheetShell.tsx +1 -1
  215. package/src/field/Field.stories.tsx +3 -3
  216. package/src/index.ts +11 -11
  217. package/src/index.web.ts +11 -11
  218. package/src/input-group/InputGroup.stories.tsx +4 -4
  219. package/src/input-group/InputGroup.tsx +1 -1
  220. package/src/input-group/types.ts +1 -1
  221. package/src/menu/Menu.stories.tsx +35 -35
  222. package/src/menu/context.ts +2 -2
  223. package/src/menu/index.tsx +15 -15
  224. package/src/menu/index.web.tsx +14 -14
  225. package/src/menu/types.ts +5 -5
  226. package/src/popover/Popover.stories.tsx +13 -13
  227. package/src/popover/context.ts +1 -1
  228. package/src/popover/index.tsx +5 -5
  229. package/src/popover/index.web.tsx +5 -5
  230. package/src/popover/types.ts +1 -1
  231. package/src/search-input/index.tsx +7 -7
  232. package/src/segmented-control/index.tsx +15 -15
  233. package/src/select/Select.stories.tsx +32 -23
  234. package/src/select/common.ts +1 -1
  235. package/src/select/index.tsx +39 -43
  236. package/src/select/index.web.tsx +33 -37
  237. package/src/select/types.ts +10 -10
  238. package/src/tabs/Tabs.tsx +9 -9
  239. package/src/tabs/index.ts +2 -2
  240. package/src/tabs/types.ts +3 -3
  241. package/src/text-field/TextField.stories.tsx +5 -5
  242. package/src/text-field/index.tsx +12 -12
  243. package/src/theme/color-scope/style-builder.ts +6 -6
  244. package/src/tooltip/index.tsx +6 -6
  245. package/src/tooltip/index.web.tsx +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -880,12 +880,6 @@
880
880
  "react-dom": {
881
881
  "optional": true
882
882
  },
883
- "react-native-reanimated": {
884
- "optional": true
885
- },
886
- "react-native-gesture-handler": {
887
- "optional": true
888
- },
889
883
  "react-native-svg": {
890
884
  "optional": true
891
885
  },
@@ -3,12 +3,10 @@ import * as Bloom from '../index';
3
3
  type AnyExports = Record<string, unknown>;
4
4
  const api = Bloom as AnyExports;
5
5
 
6
- // The ONLY families allowed to remain namespace objects (collections /
7
- // generic-colliding part names). Everything else is flat-with-prefix.
6
+ // Collections / generic-colliding part names that intentionally STAY namespaced.
8
7
  const ALLOWED_NAMESPACES = ['Icons', 'Typography', 'Skeleton', 'Grid', 'Code', 'Fonts'] as const;
9
8
 
10
- // Compound families converted to flat: their top-level name becomes the
11
- // root component (a function), no longer a namespace object.
9
+ // Compound families converted to flat: the top-level name is the root component.
12
10
  const FLATTENED_ROOTS = [
13
11
  'Tabs', 'Accordion', 'Select', 'Menu', 'ContextMenu', 'Popover',
14
12
  'Tooltip', 'SegmentedControl', 'TextField', 'Admonition', 'PromptInput',
@@ -21,22 +19,36 @@ const REPRESENTATIVE_PARTS = [
21
19
  'AdmonitionIcon', 'PromptInputTextarea',
22
20
  ] as const;
23
21
 
22
+ /**
23
+ * A flat React component export is either a plain function component or a wrapped
24
+ * component object (React.memo / forwardRef), which carries a `$$typeof` symbol.
25
+ * A `export * as X` namespace is a plain object WITHOUT `$$typeof`.
26
+ */
27
+ function isReactComponent(value: unknown): boolean {
28
+ if (typeof value === 'function') return true;
29
+ if (typeof value === 'object' && value !== null) {
30
+ return typeof (value as { $$typeof?: unknown }).$$typeof === 'symbol';
31
+ }
32
+ return false;
33
+ }
34
+
24
35
  describe('public API contract', () => {
25
- it('keeps the six collection families as namespace objects', () => {
36
+ it('keeps the six collection families as namespace objects (not components)', () => {
26
37
  for (const ns of ALLOWED_NAMESPACES) {
27
38
  expect(typeof api[ns]).toBe('object');
39
+ expect(isReactComponent(api[ns])).toBe(false);
28
40
  }
29
41
  });
30
42
 
31
43
  it('exposes each compound family root as a flat component (not a namespace)', () => {
32
44
  for (const name of FLATTENED_ROOTS) {
33
- expect(typeof api[name]).toBe('function');
45
+ expect(isReactComponent(api[name])).toBe(true);
34
46
  }
35
47
  });
36
48
 
37
49
  it('exposes compound parts as flat prefixed components', () => {
38
50
  for (const name of REPRESENTATIVE_PARTS) {
39
- expect(typeof api[name]).toBe('function');
51
+ expect(isReactComponent(api[name])).toBe(true);
40
52
  }
41
53
  });
42
54
  });
@@ -0,0 +1,227 @@
1
+ import React, { useRef } from 'react';
2
+ import { Text, View } from 'react-native';
3
+ import { render } from '@testing-library/react-native';
4
+
5
+ let mockColorScheme: 'light' | 'dark' | null = 'light';
6
+
7
+ jest.mock('react-native', () => {
8
+ const actual = jest.requireActual('react-native');
9
+ return {
10
+ ...actual,
11
+ useColorScheme: () => mockColorScheme,
12
+ Appearance: {
13
+ ...actual.Appearance,
14
+ setColorScheme: jest.fn(),
15
+ getColorScheme: () => mockColorScheme,
16
+ },
17
+ };
18
+ });
19
+
20
+ import { BloomThemeProvider } from '../theme/BloomThemeProvider';
21
+ import { useTheme } from '../theme/use-theme';
22
+ import { useNavigationTheme } from '../theme/use-navigation-theme';
23
+ import type { NavigationTheme } from '../theme/use-navigation-theme';
24
+
25
+ // Probe that captures both the navigation theme and the raw bloom theme
26
+ // into external refs so tests can inspect them after render.
27
+ function Probe({
28
+ navRef,
29
+ bloomRef,
30
+ }: {
31
+ navRef: React.MutableRefObject<NavigationTheme | null>;
32
+ bloomRef: React.MutableRefObject<ReturnType<typeof useTheme> | null>;
33
+ }) {
34
+ const nav = useNavigationTheme();
35
+ const bloom = useTheme();
36
+ navRef.current = nav;
37
+ bloomRef.current = bloom;
38
+ return (
39
+ <View>
40
+ <Text testID="dark">{String(nav.dark)}</Text>
41
+ <Text testID="primary">{nav.colors.primary}</Text>
42
+ <Text testID="notification">{nav.colors.notification}</Text>
43
+ </View>
44
+ );
45
+ }
46
+
47
+ // Probe that captures the navigation theme reference twice across renders,
48
+ // storing it in an array so the test can assert referential stability.
49
+ function MemoProbe({
50
+ capturesRef,
51
+ triggerProp,
52
+ }: {
53
+ capturesRef: React.MutableRefObject<NavigationTheme[]>;
54
+ triggerProp: number;
55
+ }) {
56
+ void triggerProp; // read the prop so React re-renders when it changes
57
+ const nav = useNavigationTheme();
58
+ capturesRef.current.push(nav);
59
+ return null;
60
+ }
61
+
62
+ beforeEach(() => {
63
+ mockColorScheme = 'light';
64
+ });
65
+
66
+ describe('useNavigationTheme', () => {
67
+ describe('dark flag', () => {
68
+ it('returns dark=false under explicit light mode', () => {
69
+ const navRef = { current: null as NavigationTheme | null };
70
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
71
+ render(
72
+ <BloomThemeProvider mode="light">
73
+ <Probe navRef={navRef} bloomRef={bloomRef} />
74
+ </BloomThemeProvider>,
75
+ );
76
+ expect(navRef.current?.dark).toBe(false);
77
+ });
78
+
79
+ it('returns dark=true under explicit dark mode', () => {
80
+ const navRef = { current: null as NavigationTheme | null };
81
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
82
+ render(
83
+ <BloomThemeProvider mode="dark">
84
+ <Probe navRef={navRef} bloomRef={bloomRef} />
85
+ </BloomThemeProvider>,
86
+ );
87
+ expect(navRef.current?.dark).toBe(true);
88
+ });
89
+
90
+ it('returns dark=false when system mode resolves to light', () => {
91
+ mockColorScheme = 'light';
92
+ const navRef = { current: null as NavigationTheme | null };
93
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
94
+ render(
95
+ <BloomThemeProvider mode="system">
96
+ <Probe navRef={navRef} bloomRef={bloomRef} />
97
+ </BloomThemeProvider>,
98
+ );
99
+ expect(navRef.current?.dark).toBe(false);
100
+ });
101
+
102
+ it('returns dark=true when system mode resolves to dark', () => {
103
+ mockColorScheme = 'dark';
104
+ const navRef = { current: null as NavigationTheme | null };
105
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
106
+ render(
107
+ <BloomThemeProvider mode="system">
108
+ <Probe navRef={navRef} bloomRef={bloomRef} />
109
+ </BloomThemeProvider>,
110
+ );
111
+ expect(navRef.current?.dark).toBe(true);
112
+ });
113
+ });
114
+
115
+ describe('color mapping', () => {
116
+ it('maps primary/background/card/text/border 1:1 from useTheme().colors', () => {
117
+ const navRef = { current: null as NavigationTheme | null };
118
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
119
+ render(
120
+ <BloomThemeProvider mode="light" colorPreset="blue">
121
+ <Probe navRef={navRef} bloomRef={bloomRef} />
122
+ </BloomThemeProvider>,
123
+ );
124
+ const nav = navRef.current;
125
+ const bloom = bloomRef.current;
126
+ expect(nav).not.toBeNull();
127
+ expect(bloom).not.toBeNull();
128
+ expect(nav!.colors.primary).toBe(bloom!.colors.primary);
129
+ expect(nav!.colors.background).toBe(bloom!.colors.background);
130
+ expect(nav!.colors.card).toBe(bloom!.colors.card);
131
+ expect(nav!.colors.text).toBe(bloom!.colors.text);
132
+ expect(nav!.colors.border).toBe(bloom!.colors.border);
133
+ });
134
+
135
+ it('maps notification to colors.error (not a direct key)', () => {
136
+ const navRef = { current: null as NavigationTheme | null };
137
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
138
+ render(
139
+ <BloomThemeProvider mode="light" colorPreset="blue">
140
+ <Probe navRef={navRef} bloomRef={bloomRef} />
141
+ </BloomThemeProvider>,
142
+ );
143
+ expect(navRef.current!.colors.notification).toBe(
144
+ bloomRef.current!.colors.error,
145
+ );
146
+ });
147
+
148
+ it('colors differ between light and dark presets (sanity check)', () => {
149
+ const lightNavRef = { current: null as NavigationTheme | null };
150
+ const lightBloomRef = { current: null as ReturnType<typeof useTheme> | null };
151
+ const { unmount } = render(
152
+ <BloomThemeProvider mode="light" colorPreset="teal">
153
+ <Probe navRef={lightNavRef} bloomRef={lightBloomRef} />
154
+ </BloomThemeProvider>,
155
+ );
156
+ const lightBg = lightNavRef.current!.colors.background;
157
+ unmount();
158
+
159
+ const darkNavRef = { current: null as NavigationTheme | null };
160
+ const darkBloomRef = { current: null as ReturnType<typeof useTheme> | null };
161
+ render(
162
+ <BloomThemeProvider mode="dark" colorPreset="teal">
163
+ <Probe navRef={darkNavRef} bloomRef={darkBloomRef} />
164
+ </BloomThemeProvider>,
165
+ );
166
+ const darkBg = darkNavRef.current!.colors.background;
167
+
168
+ expect(lightBg).not.toBe(darkBg);
169
+ });
170
+ });
171
+
172
+ describe('fonts shape', () => {
173
+ it('has all four font variants with System fontFamily', () => {
174
+ const navRef = { current: null as NavigationTheme | null };
175
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
176
+ render(
177
+ <BloomThemeProvider mode="light">
178
+ <Probe navRef={navRef} bloomRef={bloomRef} />
179
+ </BloomThemeProvider>,
180
+ );
181
+ const fonts = navRef.current!.fonts;
182
+ for (const key of ['regular', 'medium', 'bold', 'heavy'] as const) {
183
+ expect(fonts[key].fontFamily).toBe('System');
184
+ }
185
+ });
186
+
187
+ it('has correct fontWeight values for each variant', () => {
188
+ const navRef = { current: null as NavigationTheme | null };
189
+ const bloomRef = { current: null as ReturnType<typeof useTheme> | null };
190
+ render(
191
+ <BloomThemeProvider mode="light">
192
+ <Probe navRef={navRef} bloomRef={bloomRef} />
193
+ </BloomThemeProvider>,
194
+ );
195
+ const fonts = navRef.current!.fonts;
196
+ expect(fonts.regular.fontWeight).toBe('400');
197
+ expect(fonts.medium.fontWeight).toBe('500');
198
+ expect(fonts.bold.fontWeight).toBe('700');
199
+ expect(fonts.heavy.fontWeight).toBe('900');
200
+ });
201
+ });
202
+
203
+ describe('memo stability', () => {
204
+ it('returns a referentially-equal object on re-render when theme is unchanged', () => {
205
+ const capturesRef: React.MutableRefObject<NavigationTheme[]> = {
206
+ current: [],
207
+ };
208
+ const { rerender } = render(
209
+ <BloomThemeProvider mode="light">
210
+ <MemoProbe capturesRef={capturesRef} triggerProp={0} />
211
+ </BloomThemeProvider>,
212
+ );
213
+
214
+ // Re-render without changing mode or colorPreset — memo should hold.
215
+ rerender(
216
+ <BloomThemeProvider mode="light">
217
+ <MemoProbe capturesRef={capturesRef} triggerProp={1} />
218
+ </BloomThemeProvider>,
219
+ );
220
+
221
+ expect(capturesRef.current.length).toBeGreaterThanOrEqual(2);
222
+ const first = capturesRef.current[0];
223
+ const second = capturesRef.current[1];
224
+ expect(Object.is(first, second)).toBe(true);
225
+ });
226
+ });
227
+ });
@@ -28,7 +28,7 @@ const ICON_MAP = {
28
28
  apology: EmojiSadIcon,
29
29
  } as const;
30
30
 
31
- export function Icon() {
31
+ export function AdmonitionIcon() {
32
32
  const theme = useTheme();
33
33
  const { type } = useContext(AdmonitionContext);
34
34
 
@@ -44,9 +44,9 @@ export function Icon() {
44
44
 
45
45
  return <IconComponent fill={fillMap[type]} size="md" />;
46
46
  }
47
- Icon.displayName = 'Admonition.Icon';
47
+ AdmonitionIcon.displayName = 'AdmonitionIcon';
48
48
 
49
- export function Content({
49
+ export function AdmonitionContent({
50
50
  children,
51
51
  style,
52
52
  }: {
@@ -59,9 +59,9 @@ export function Content({
59
59
  </View>
60
60
  );
61
61
  }
62
- Content.displayName = 'Admonition.Content';
62
+ AdmonitionContent.displayName = 'AdmonitionContent';
63
63
 
64
- export function Text({
64
+ export function AdmonitionText({
65
65
  children,
66
66
  style,
67
67
  }: {
@@ -74,9 +74,9 @@ export function Text({
74
74
  </BaseText>
75
75
  );
76
76
  }
77
- Text.displayName = 'Admonition.Text';
77
+ AdmonitionText.displayName = 'AdmonitionText';
78
78
 
79
- export function Button({
79
+ export function AdmonitionButton({
80
80
  children,
81
81
  ...props
82
82
  }: Omit<ButtonProps, 'size' | 'variant'>) {
@@ -86,9 +86,9 @@ export function Button({
86
86
  </BaseButton>
87
87
  );
88
88
  }
89
- Button.displayName = 'Admonition.Button';
89
+ AdmonitionButton.displayName = 'AdmonitionButton';
90
90
 
91
- export function Row({
91
+ export function AdmonitionRow({
92
92
  children,
93
93
  style,
94
94
  }: {
@@ -101,9 +101,9 @@ export function Row({
101
101
  </View>
102
102
  );
103
103
  }
104
- Row.displayName = 'Admonition.Row';
104
+ AdmonitionRow.displayName = 'AdmonitionRow';
105
105
 
106
- export function Outer({
106
+ export function AdmonitionRoot({
107
107
  children,
108
108
  type = 'info',
109
109
  style,
@@ -139,11 +139,12 @@ export function Outer({
139
139
  </AdmonitionContext.Provider>
140
140
  );
141
141
  }
142
- Outer.displayName = 'Admonition.Outer';
142
+ AdmonitionRoot.displayName = 'AdmonitionRoot';
143
143
 
144
144
  /**
145
145
  * Simple all-in-one Admonition component for common use cases.
146
- * For more control, compose with Outer, Row, Icon, Content, Text, and Button.
146
+ * For more control, compose with AdmonitionRoot, AdmonitionRow, AdmonitionIcon,
147
+ * AdmonitionContent, AdmonitionText, and AdmonitionButton.
147
148
  */
148
149
  export function Admonition({
149
150
  children,
@@ -155,14 +156,14 @@ export function Admonition({
155
156
  style?: StyleProp<ViewStyle>;
156
157
  }) {
157
158
  return (
158
- <Outer type={type} style={style}>
159
- <Row>
160
- <Icon />
161
- <Content>
162
- <Text>{children}</Text>
163
- </Content>
164
- </Row>
165
- </Outer>
159
+ <AdmonitionRoot type={type} style={style}>
160
+ <AdmonitionRow>
161
+ <AdmonitionIcon />
162
+ <AdmonitionContent>
163
+ <AdmonitionText>{children}</AdmonitionText>
164
+ </AdmonitionContent>
165
+ </AdmonitionRow>
166
+ </AdmonitionRoot>
166
167
  );
167
168
  }
168
169
  Admonition.displayName = 'Admonition';
@@ -20,7 +20,7 @@ import {
20
20
  import { Check_Stroke2_Corner0_Rounded as CheckIcon } from '../icons/Check';
21
21
  import type {
22
22
  PopoverContentProps,
23
- PopoverRootProps,
23
+ PopoverProps,
24
24
  PopoverTriggerProps,
25
25
  } from '../popover/types';
26
26
  import type { usePopoverContext as usePopoverContextType } from '../popover/context';
@@ -29,13 +29,13 @@ import type { ComboboxOption, ComboboxProps } from './types';
29
29
  /**
30
30
  * Structural shape of the `Popover` module, satisfied by both the native and
31
31
  * web platform forks. Defined explicitly (rather than `typeof import(...)`) so
32
- * the slightly different render-return types between forks (web `Content` may
33
- * return `null`) don't make the modules mutually unassignable.
32
+ * the slightly different render-return types between forks (web `PopoverContent`
33
+ * may return `null`) don't make the modules mutually unassignable.
34
34
  */
35
35
  interface PopoverModule {
36
- Root: (props: PopoverRootProps) => React.ReactElement;
37
- Trigger: (props: PopoverTriggerProps) => React.ReactElement;
38
- Content: (props: PopoverContentProps) => React.ReactElement | null;
36
+ Popover: (props: PopoverProps) => React.ReactElement;
37
+ PopoverTrigger: (props: PopoverTriggerProps) => React.ReactElement;
38
+ PopoverContent: (props: PopoverContentProps) => React.ReactElement | null;
39
39
  usePopoverContext: typeof usePopoverContextType;
40
40
  }
41
41
 
@@ -59,7 +59,7 @@ function defaultFilter<T>(option: ComboboxOption<T>, query: string): boolean {
59
59
  * read the overlay control from `Popover` context (via an inner component) so
60
60
  * selecting an option reliably closes the overlay on every platform.
61
61
  */
62
- export function createCombobox(Popover: PopoverModule) {
62
+ export function createCombobox(PopoverImpl: PopoverModule) {
63
63
  function ComboboxInner<T>({
64
64
  options,
65
65
  value,
@@ -80,7 +80,7 @@ export function createCombobox(Popover: PopoverModule) {
80
80
  > &
81
81
  Pick<ComboboxProps<T>, 'label' | 'query' | 'onQueryChange' | 'disabled'>) {
82
82
  const theme = useTheme();
83
- const { control } = Popover.usePopoverContext();
83
+ const { control } = PopoverImpl.usePopoverContext();
84
84
  const searchRef = useRef<TextInput>(null);
85
85
 
86
86
  const [query, setQuery] = useControllableState<string>({
@@ -113,7 +113,7 @@ export function createCombobox(Popover: PopoverModule) {
113
113
 
114
114
  return (
115
115
  <>
116
- <Popover.Trigger label={accessibleLabel}>
116
+ <PopoverImpl.PopoverTrigger label={accessibleLabel}>
117
117
  {({ props }) => (
118
118
  <Pressable
119
119
  {...props}
@@ -149,9 +149,9 @@ export function createCombobox(Popover: PopoverModule) {
149
149
  <ChevronUpDownIcon size="xs" fill={theme.colors.textSecondary} />
150
150
  </Pressable>
151
151
  )}
152
- </Popover.Trigger>
152
+ </PopoverImpl.PopoverTrigger>
153
153
 
154
- <Popover.Content label={accessibleLabel} placement="bottom-start" minWidth={240}>
154
+ <PopoverImpl.PopoverContent label={accessibleLabel} placement="bottom-start" minWidth={240}>
155
155
  <View style={styles.searchWrap}>
156
156
  <SearchInput
157
157
  ref={searchRef}
@@ -196,7 +196,7 @@ export function createCombobox(Popover: PopoverModule) {
196
196
  })}
197
197
  </ScrollView>
198
198
  )}
199
- </Popover.Content>
199
+ </PopoverImpl.PopoverContent>
200
200
  </>
201
201
  );
202
202
  }
@@ -217,7 +217,7 @@ export function createCombobox(Popover: PopoverModule) {
217
217
  testID,
218
218
  }: ComboboxProps<T>) {
219
219
  return (
220
- <Popover.Root>
220
+ <PopoverImpl.Popover>
221
221
  <View style={style} testID={testID}>
222
222
  <ComboboxInner<T>
223
223
  options={options}
@@ -233,7 +233,7 @@ export function createCombobox(Popover: PopoverModule) {
233
233
  maxListHeight={maxListHeight}
234
234
  />
235
235
  </View>
236
- </Popover.Root>
236
+ </PopoverImpl.Popover>
237
237
  );
238
238
  }
239
239
 
@@ -3,7 +3,15 @@ import { Pressable, Text, View } from 'react-native';
3
3
  import type { Meta, StoryObj } from '@storybook/react-vite';
4
4
 
5
5
  import { useTheme } from '../theme/use-theme';
6
- import * as ContextMenu from './index';
6
+ import {
7
+ ContextMenu,
8
+ ContextMenuContent,
9
+ ContextMenuDivider,
10
+ ContextMenuGroup,
11
+ ContextMenuItem,
12
+ ContextMenuItemText,
13
+ ContextMenuTrigger,
14
+ } from './index';
7
15
 
8
16
  const meta: Meta = {
9
17
  title: 'Components/ContextMenu',
@@ -16,8 +24,8 @@ type Story = StoryObj;
16
24
  function TriggerSurface() {
17
25
  const theme = useTheme();
18
26
  return (
19
- <ContextMenu.Root>
20
- <ContextMenu.Trigger label="Long-press for actions">
27
+ <ContextMenu>
28
+ <ContextMenuTrigger label="Long-press for actions">
21
29
  {({ props }) => (
22
30
  <Pressable
23
31
  onPress={() => props.onPress?.()}
@@ -39,21 +47,21 @@ function TriggerSurface() {
39
47
  </Text>
40
48
  </Pressable>
41
49
  )}
42
- </ContextMenu.Trigger>
43
- <ContextMenu.Outer>
44
- <ContextMenu.Group>
45
- <ContextMenu.Item label="Open" onPress={() => {}}>
46
- <ContextMenu.ItemText>Open</ContextMenu.ItemText>
47
- </ContextMenu.Item>
48
- <ContextMenu.Item label="Rename" onPress={() => {}}>
49
- <ContextMenu.ItemText>Rename</ContextMenu.ItemText>
50
- </ContextMenu.Item>
51
- <ContextMenu.Item label="Delete" onPress={() => {}}>
52
- <ContextMenu.ItemText>Delete</ContextMenu.ItemText>
53
- </ContextMenu.Item>
54
- </ContextMenu.Group>
55
- </ContextMenu.Outer>
56
- </ContextMenu.Root>
50
+ </ContextMenuTrigger>
51
+ <ContextMenuContent>
52
+ <ContextMenuGroup>
53
+ <ContextMenuItem label="Open" onPress={() => {}}>
54
+ <ContextMenuItemText>Open</ContextMenuItemText>
55
+ </ContextMenuItem>
56
+ <ContextMenuItem label="Rename" onPress={() => {}}>
57
+ <ContextMenuItemText>Rename</ContextMenuItemText>
58
+ </ContextMenuItem>
59
+ <ContextMenuItem label="Delete" onPress={() => {}}>
60
+ <ContextMenuItemText>Delete</ContextMenuItemText>
61
+ </ContextMenuItem>
62
+ </ContextMenuGroup>
63
+ </ContextMenuContent>
64
+ </ContextMenu>
57
65
  );
58
66
  }
59
67
 
@@ -21,7 +21,7 @@ export function useContextMenuContext(): ContextMenuContextValue {
21
21
  const ctx = useContext(ContextMenuContext);
22
22
  if (!ctx) {
23
23
  throw new Error(
24
- 'ContextMenu components must be used within a ContextMenu.Root',
24
+ 'ContextMenu components must be used within a <ContextMenu>',
25
25
  );
26
26
  }
27
27
  return ctx;
@@ -38,7 +38,7 @@ export function useItemContext(): ItemContextValue {
38
38
  const ctx = useContext(ItemCtx);
39
39
  if (!ctx) {
40
40
  throw new Error(
41
- 'ContextMenu.ItemText/ItemIcon must be used within a ContextMenu.Item',
41
+ 'ContextMenuItemText/ContextMenuItemIcon must be used within a <ContextMenuItem>',
42
42
  );
43
43
  }
44
44
  return ctx;