@oxyhq/bloom 0.50.1 → 0.50.2

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 (83) hide show
  1. package/lib/commonjs/bottom-sheet/index.web.js +3 -2
  2. package/lib/commonjs/bottom-sheet/index.web.js.map +1 -1
  3. package/lib/commonjs/context-menu/index.web.js +3 -7
  4. package/lib/commonjs/context-menu/index.web.js.map +1 -1
  5. package/lib/commonjs/dialog/Dialog.js +12 -7
  6. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  7. package/lib/commonjs/dialog/Dialog.web.js +14 -19
  8. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  9. package/lib/commonjs/dialog/DialogHeader.js +61 -12
  10. package/lib/commonjs/dialog/DialogHeader.js.map +1 -1
  11. package/lib/commonjs/menu/index.web.js +3 -7
  12. package/lib/commonjs/menu/index.web.js.map +1 -1
  13. package/lib/commonjs/popover/index.web.js +5 -7
  14. package/lib/commonjs/popover/index.web.js.map +1 -1
  15. package/lib/commonjs/select/index.web.js +3 -7
  16. package/lib/commonjs/select/index.web.js.map +1 -1
  17. package/lib/commonjs/slider/Slider.js +4 -9
  18. package/lib/commonjs/slider/Slider.js.map +1 -1
  19. package/lib/commonjs/switch/Switch.js +4 -8
  20. package/lib/commonjs/switch/Switch.js.map +1 -1
  21. package/lib/commonjs/tabs/Tabs.js +3 -8
  22. package/lib/commonjs/tabs/Tabs.js.map +1 -1
  23. package/lib/commonjs/toast/Toast.js +3 -8
  24. package/lib/commonjs/toast/Toast.js.map +1 -1
  25. package/lib/module/bottom-sheet/index.web.js +3 -2
  26. package/lib/module/bottom-sheet/index.web.js.map +1 -1
  27. package/lib/module/context-menu/index.web.js +3 -7
  28. package/lib/module/context-menu/index.web.js.map +1 -1
  29. package/lib/module/dialog/Dialog.js +12 -7
  30. package/lib/module/dialog/Dialog.js.map +1 -1
  31. package/lib/module/dialog/Dialog.web.js +14 -19
  32. package/lib/module/dialog/Dialog.web.js.map +1 -1
  33. package/lib/module/dialog/DialogHeader.js +61 -12
  34. package/lib/module/dialog/DialogHeader.js.map +1 -1
  35. package/lib/module/menu/index.web.js +3 -7
  36. package/lib/module/menu/index.web.js.map +1 -1
  37. package/lib/module/popover/index.web.js +5 -7
  38. package/lib/module/popover/index.web.js.map +1 -1
  39. package/lib/module/select/index.web.js +3 -7
  40. package/lib/module/select/index.web.js.map +1 -1
  41. package/lib/module/slider/Slider.js +4 -9
  42. package/lib/module/slider/Slider.js.map +1 -1
  43. package/lib/module/switch/Switch.js +4 -8
  44. package/lib/module/switch/Switch.js.map +1 -1
  45. package/lib/module/tabs/Tabs.js +3 -8
  46. package/lib/module/tabs/Tabs.js.map +1 -1
  47. package/lib/module/toast/Toast.js +3 -8
  48. package/lib/module/toast/Toast.js.map +1 -1
  49. package/lib/typescript/commonjs/context-menu/index.web.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/menu/index.web.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/popover/index.web.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/select/index.web.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/slider/Slider.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/switch/Switch.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/toast/Toast.d.ts.map +1 -1
  60. package/lib/typescript/module/context-menu/index.web.d.ts.map +1 -1
  61. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  62. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  63. package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -1
  64. package/lib/typescript/module/menu/index.web.d.ts.map +1 -1
  65. package/lib/typescript/module/popover/index.web.d.ts.map +1 -1
  66. package/lib/typescript/module/select/index.web.d.ts.map +1 -1
  67. package/lib/typescript/module/slider/Slider.d.ts.map +1 -1
  68. package/lib/typescript/module/switch/Switch.d.ts.map +1 -1
  69. package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -1
  70. package/lib/typescript/module/toast/Toast.d.ts.map +1 -1
  71. package/package.json +1 -1
  72. package/src/bottom-sheet/index.web.tsx +1 -1
  73. package/src/context-menu/index.web.tsx +3 -4
  74. package/src/dialog/Dialog.tsx +7 -4
  75. package/src/dialog/Dialog.web.tsx +10 -11
  76. package/src/dialog/DialogHeader.tsx +72 -17
  77. package/src/menu/index.web.tsx +3 -4
  78. package/src/popover/index.web.tsx +5 -4
  79. package/src/select/index.web.tsx +3 -4
  80. package/src/slider/Slider.tsx +3 -5
  81. package/src/switch/Switch.tsx +4 -5
  82. package/src/tabs/Tabs.tsx +3 -5
  83. package/src/toast/Toast.tsx +3 -5
@@ -106,6 +106,62 @@ interface HeaderStore {
106
106
  subscribe: (listener: () => void) => () => void;
107
107
  }
108
108
 
109
+ type PrimaryAction = NonNullable<DialogHeaderConfig['primaryAction']>;
110
+ type HeaderAction = NonNullable<DialogHeaderConfig['actions']>[number];
111
+ type HeaderSearch = NonNullable<DialogHeaderConfig['search']>;
112
+ type HeaderSegments = NonNullable<DialogHeaderConfig['segments']>;
113
+ type HeaderProgress = NonNullable<DialogHeaderConfig['progress']>;
114
+
115
+ function primaryActionEqual(a: PrimaryAction | undefined, b: PrimaryAction | undefined): boolean {
116
+ if (a === b) return true;
117
+ if (!a || !b) return false;
118
+ // `onPress` is a fresh closure each render — its identity never changes what the
119
+ // button renders, so compare only the render-affecting scalars.
120
+ return a.label === b.label && !!a.disabled === !!b.disabled && !!a.loading === !!b.loading;
121
+ }
122
+
123
+ function actionsEqual(a: HeaderAction[] | undefined, b: HeaderAction[] | undefined): boolean {
124
+ if (a === b) return true;
125
+ if (!a || !b || a.length !== b.length) return false;
126
+ for (let i = 0; i < a.length; i += 1) {
127
+ const x = a[i];
128
+ const y = b[i];
129
+ if (!x || !y) return false;
130
+ // `icon` is a memoized node (compared by identity); `onPress` is ignored.
131
+ if (
132
+ x.accessibilityLabel !== y.accessibilityLabel ||
133
+ !!x.disabled !== !!y.disabled ||
134
+ x.icon !== y.icon
135
+ ) {
136
+ return false;
137
+ }
138
+ }
139
+ return true;
140
+ }
141
+
142
+ function searchEqual(a: HeaderSearch | undefined, b: HeaderSearch | undefined): boolean {
143
+ if (a === b) return true;
144
+ if (!a || !b) return false;
145
+ return a.value === b.value && a.placeholder === b.placeholder;
146
+ }
147
+
148
+ function segmentsEqual(a: HeaderSegments | undefined, b: HeaderSegments | undefined): boolean {
149
+ if (a === b) return true;
150
+ if (!a || !b || a.value !== b.value || a.items.length !== b.items.length) return false;
151
+ for (let i = 0; i < a.items.length; i += 1) {
152
+ const ai = a.items[i];
153
+ const bi = b.items[i];
154
+ if (!ai || !bi || ai.key !== bi.key || ai.label !== bi.label) return false;
155
+ }
156
+ return true;
157
+ }
158
+
159
+ function progressEqual(a: HeaderProgress | undefined, b: HeaderProgress | undefined): boolean {
160
+ if (a === b) return true;
161
+ if (!a || !b) return false;
162
+ return a.step === b.step && a.total === b.total;
163
+ }
164
+
109
165
  function configsEqual(a: DialogHeaderConfig | null, b: DialogHeaderConfig | null): boolean {
110
166
  if (a === b) return true;
111
167
  if (!a || !b) return false;
@@ -116,16 +172,20 @@ function configsEqual(a: DialogHeaderConfig | null, b: DialogHeaderConfig | null
116
172
  a.largeTitle === b.largeTitle &&
117
173
  a.left === b.left &&
118
174
  a.right === b.right &&
119
- a.onBack === b.onBack &&
175
+ // `onBack` legitimately gets a fresh closure each render; its identity never
176
+ // changes what the header renders, so compare by PRESENCE, never identity —
177
+ // treating a new ref as "changed" is exactly what drives the update loop.
178
+ !!a.onBack === !!b.onBack &&
120
179
  a.showClose === b.showClose &&
121
- // Rich fields — object fields compared by identity (callers memoize them);
122
- // `tone` is a plain value.
123
- a.primaryAction === b.primaryAction &&
124
- a.actions === b.actions &&
125
- a.search === b.search &&
126
- a.segments === b.segments &&
180
+ // Rich fields — compared by VALUE (render-affecting scalars only). A caller
181
+ // passing an inline `primaryAction`/`actions`/… object (a fresh reference
182
+ // every render) must NOT force a store update: that is React error #185.
127
183
  a.tone === b.tone &&
128
- a.progress === b.progress
184
+ primaryActionEqual(a.primaryAction, b.primaryAction) &&
185
+ actionsEqual(a.actions, b.actions) &&
186
+ searchEqual(a.search, b.search) &&
187
+ segmentsEqual(a.segments, b.segments) &&
188
+ progressEqual(a.progress, b.progress)
129
189
  );
130
190
  }
131
191
 
@@ -474,28 +534,23 @@ export const DialogNavHeader = memo(function DialogNavHeader({
474
534
  ) : null);
475
535
 
476
536
  return (
477
- <View
478
- pointerEvents="box-none"
479
- style={[styles.overlay, style]}
480
- >
537
+ <View style={[styles.overlay, style, { pointerEvents: 'box-none' }]}>
481
538
  {/* Opaque (surface bg) at the top → transparent at the bottom, so scrolled
482
539
  content fades out under the bar. `onImage` swaps to a dark scrim so the
483
540
  chrome reads over media. Pure NativeWind — no SVG dependency. */}
484
541
  <View
485
- pointerEvents="none"
486
542
  className={
487
543
  onImage
488
544
  ? 'bg-gradient-to-b from-black/60 to-transparent'
489
545
  : 'bg-gradient-to-b from-bg to-transparent'
490
546
  }
491
- style={[StyleSheet.absoluteFill, { height: DIALOG_HEADER_OVERLAY_HEIGHT }]}
547
+ style={[StyleSheet.absoluteFill, { height: DIALOG_HEADER_OVERLAY_HEIGHT, pointerEvents: 'none' }]}
492
548
  />
493
- <View pointerEvents="box-none" style={styles.navRow}>
549
+ <View style={[styles.navRow, { pointerEvents: 'box-none' }]}>
494
550
  <View style={styles.side}>{left}</View>
495
551
  <Animated.View
496
- pointerEvents="none"
497
552
  testID="dialog-nav-title"
498
- style={[styles.centerTitle, titleStyle]}
553
+ style={[styles.centerTitle, titleStyle, { pointerEvents: 'none' }]}
499
554
  >
500
555
  {config.titleContent ?? null}
501
556
  {!config.titleContent && config.title ? (
@@ -17,6 +17,7 @@ import { Text } from '../typography';
17
17
  import type { DialogControlProps } from '../dialog/types';
18
18
  import { Portal } from '../portal/index.web';
19
19
  import { createDropdownZIndex } from '../styles/z-index';
20
+ import { bloomShadowStyle } from '../design-tokens/shadows';
20
21
  import {
21
22
  MenuContext,
22
23
  ItemContext,
@@ -255,7 +256,7 @@ export function MenuContent({
255
256
  ? theme.colors.backgroundSecondary
256
257
  : theme.colors.background,
257
258
  borderColor: theme.colors.borderLight,
258
- shadowColor: theme.colors.shadow,
259
+ ...bloomShadowStyle('m'),
259
260
  },
260
261
  style,
261
262
  styles.portaledDropdown,
@@ -393,9 +394,7 @@ const styles = StyleSheet.create({
393
394
  borderWidth: 1,
394
395
  overflow: 'hidden',
395
396
  zIndex: menuZIndex.surface,
396
- shadowOpacity: 0.15,
397
- shadowRadius: 12,
398
- shadowOffset: { width: 0, height: 4 },
397
+ // Overlay elevation applied at the usage site via `bloomShadowStyle('m')`.
399
398
  minWidth: 180,
400
399
  },
401
400
  portaledDropdown: {
@@ -12,6 +12,7 @@ import { Pressable, StyleSheet, View } from 'react-native';
12
12
  import { useTheme } from '../theme/use-theme';
13
13
  import { Portal } from '../portal/index.web';
14
14
  import { createOverlayZIndex } from '../styles/z-index';
15
+ import { bloomShadowStyle } from '../design-tokens/shadows';
15
16
  import { PopoverContext, usePopoverContext } from './context';
16
17
  import type {
17
18
  PopoverContentProps,
@@ -288,7 +289,9 @@ export function PopoverContent({
288
289
  ? theme.colors.backgroundSecondary
289
290
  : theme.colors.background,
290
291
  borderColor: theme.colors.borderLight,
291
- shadowColor: theme.colors.shadow,
292
+ // Design-system overlay elevation (`shadow-m`) as a `boxShadow` —
293
+ // RN-Web deprecated the `shadow*` style props.
294
+ ...bloomShadowStyle('m'),
292
295
  // Hide until measured to avoid a one-frame jump.
293
296
  opacity: panelSize ? 1 : 0,
294
297
  },
@@ -320,9 +323,7 @@ const styles = StyleSheet.create({
320
323
  borderWidth: 1,
321
324
  paddingVertical: 4,
322
325
  overflow: 'hidden',
323
- shadowOpacity: 0.15,
324
- shadowRadius: 12,
325
- shadowOffset: { width: 0, height: 4 },
326
+ // Elevation applied at the usage site via `bloomShadowStyle('m')` (boxShadow).
326
327
  pointerEvents: 'auto',
327
328
  },
328
329
  });
@@ -5,6 +5,7 @@ import { useTheme } from '../theme/use-theme';
5
5
  import { Text } from '../typography';
6
6
  import { Portal } from '../portal/index.web';
7
7
  import { createOverlayZIndex } from '../styles/z-index';
8
+ import { bloomShadowStyle } from '../design-tokens/shadows';
8
9
  import { RadioIndicator } from '../radio-indicator';
9
10
  import { useInteractionState } from '../hooks/useInteractionState';
10
11
  import {
@@ -201,7 +202,7 @@ export function SelectContent<T>({
201
202
  ? theme.colors.backgroundSecondary
202
203
  : theme.colors.background,
203
204
  borderColor: theme.colors.borderLight,
204
- shadowColor: theme.colors.shadow,
205
+ ...bloomShadowStyle('m'),
205
206
  },
206
207
  ]}
207
208
  >
@@ -348,9 +349,7 @@ const styles = StyleSheet.create({
348
349
  overflow: 'hidden',
349
350
  padding: 4,
350
351
  minWidth: 180,
351
- shadowOpacity: 0.15,
352
- shadowRadius: 12,
353
- shadowOffset: { width: 0, height: 4 },
352
+ // Overlay elevation applied at the usage site via `bloomShadowStyle('m')`.
354
353
  // Opt back in from the Portal root's `pointer-events: none`.
355
354
  pointerEvents: 'auto',
356
355
  },
@@ -12,6 +12,7 @@ import {
12
12
 
13
13
  import { useTheme } from '../theme/use-theme';
14
14
  import { animation } from '../styles/tokens';
15
+ import { bloomShadowStyle } from '../design-tokens/shadows';
15
16
  import type { SliderProps } from './types';
16
17
 
17
18
  function clamp(v: number, min: number, max: number): number {
@@ -236,7 +237,6 @@ const SliderComponent = function Slider({
236
237
  backgroundColor: thumbColor,
237
238
  left: thumbLeft,
238
239
  transform: [{ scale: thumbScale }],
239
- shadowColor: theme.colors.shadow,
240
240
  },
241
241
  ]}
242
242
  />
@@ -261,10 +261,8 @@ const styles = StyleSheet.create({
261
261
  thumb: {
262
262
  position: 'absolute',
263
263
  top: '50%',
264
- shadowOffset: { width: 0, height: 1 },
265
- shadowOpacity: 0.2,
266
- shadowRadius: 2,
267
- elevation: 2,
264
+ // Subtle raise (`shadow-s`) — `boxShadow` on web, RN shadow/elevation on native.
265
+ ...bloomShadowStyle('s'),
268
266
  },
269
267
  disabled: {
270
268
  opacity: 0.5,
@@ -3,6 +3,7 @@ import { Pressable, Animated, StyleSheet } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../theme/use-theme';
5
5
  import { animation } from '../styles/tokens';
6
+ import { bloomShadowStyle } from '../design-tokens/shadows';
6
7
  import type { SwitchProps } from './types';
7
8
 
8
9
  const TRACK = { default: { w: 44, h: 26 }, sm: { w: 36, h: 22 } } as const;
@@ -128,10 +129,8 @@ const styles = StyleSheet.create({
128
129
  },
129
130
  thumb: {
130
131
  backgroundColor: '#fff',
131
- shadowColor: '#000',
132
- shadowOffset: { width: 0, height: 2 },
133
- shadowOpacity: 0.15,
134
- shadowRadius: 3,
135
- elevation: 3,
132
+ // Subtle raise (`shadow-s`) — `boxShadow` on web (RN-Web deprecated `shadow*`),
133
+ // RN elevation/shadow on native.
134
+ ...bloomShadowStyle('s'),
136
135
  },
137
136
  });
package/src/tabs/Tabs.tsx CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  import { useTheme } from '../theme/use-theme';
23
23
  import { usePressAnimation } from '../hooks/usePressAnimation';
24
24
  import { borderRadius, space } from '../styles/tokens';
25
+ import { bloomShadowStyle } from '../design-tokens/shadows';
25
26
  import type { TabsProps, TabsTriggerProps, TabsContentProps, TabsVariant } from './types';
26
27
 
27
28
  type TriggerLayout = Pick<LayoutRectangle, 'x' | 'width'>;
@@ -226,11 +227,8 @@ const TabComponent: React.FC<TabsTriggerProps> = ({
226
227
  if (isSelected) {
227
228
  base.backgroundColor = theme.colors.card;
228
229
  base.borderRadius = borderRadius.xs + 2;
229
- base.shadowColor = theme.colors.shadow;
230
- base.shadowOffset = { width: 0, height: 1 };
231
- base.shadowOpacity = 0.1;
232
- base.shadowRadius = 2;
233
- base.elevation = 1;
230
+ // Subtle raise (`shadow-s`) — `boxShadow` on web, RN shadow/elevation on native.
231
+ Object.assign(base, bloomShadowStyle('s'));
234
232
  }
235
233
  break;
236
234
  case 'outlined':
@@ -9,6 +9,7 @@ import {
9
9
 
10
10
  import { useTheme } from '../theme/use-theme';
11
11
  import { useInteractionState } from '../hooks/useInteractionState';
12
+ import { bloomShadowStyle } from '../design-tokens/shadows';
12
13
  import type { ThemeColors } from '../theme/types';
13
14
  import { dismiss } from './sonner';
14
15
  import type { ToastType } from './types';
@@ -242,11 +243,8 @@ const styles = StyleSheet.create({
242
243
  flexDirection: 'row',
243
244
  gap: 8,
244
245
  paddingVertical: 14,
245
- shadowColor: '#000',
246
- shadowOffset: { width: 0, height: 1 },
247
- shadowOpacity: 0.05,
248
- shadowRadius: 2,
249
- elevation: 2,
246
+ // Subtle raise (`shadow-s`) — `boxShadow` on web, RN shadow/elevation on native.
247
+ ...bloomShadowStyle('s'),
250
248
  },
251
249
  textContainer: {
252
250
  flex: 1,