react-native-molecules 0.5.0-beta.2 → 0.5.0-beta.20

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 (157) hide show
  1. package/README.md +1 -1
  2. package/components/Accordion/Accordion.tsx +2 -6
  3. package/components/Accordion/AccordionItem.tsx +16 -12
  4. package/components/Accordion/AccordionItemContent.tsx +6 -1
  5. package/components/Accordion/AccordionItemHeader.tsx +1 -1
  6. package/components/Accordion/utils.ts +6 -0
  7. package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
  8. package/components/Appbar/AppbarBase.tsx +18 -13
  9. package/components/Button/Button.tsx +209 -264
  10. package/components/Button/index.tsx +9 -3
  11. package/components/Button/types.ts +16 -2
  12. package/components/Button/utils.ts +230 -208
  13. package/components/Checkbox/CheckboxBase.tsx +23 -128
  14. package/components/Checkbox/utils.ts +0 -25
  15. package/components/Chip/Chip.tsx +40 -52
  16. package/components/Chip/utils.ts +3 -7
  17. package/components/DateField/DateField.tsx +110 -0
  18. package/components/DateField/index.tsx +6 -0
  19. package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +17 -49
  20. package/components/DatePicker/DateCalendar.tsx +83 -0
  21. package/components/DatePicker/DatePickerActions.tsx +73 -0
  22. package/components/DatePicker/DatePickerModal.tsx +234 -0
  23. package/components/DatePicker/DatePickerPopover.tsx +79 -0
  24. package/components/DatePicker/DatePickerProvider.tsx +152 -0
  25. package/components/DatePicker/DatePickerTrigger.tsx +23 -0
  26. package/components/DatePicker/context.tsx +82 -0
  27. package/components/DatePicker/index.tsx +44 -0
  28. package/components/DatePicker/utils.ts +293 -0
  29. package/components/DatePickerInline/DatePickerContext.tsx +1 -0
  30. package/components/DatePickerInline/DatePickerDockedHeader.tsx +113 -0
  31. package/components/DatePickerInline/DatePickerInline.tsx +16 -15
  32. package/components/DatePickerInline/DatePickerInlineBase.tsx +7 -1
  33. package/components/DatePickerInline/Day.tsx +25 -1
  34. package/components/DatePickerInline/DayRange.tsx +2 -4
  35. package/components/DatePickerInline/HeaderItem.tsx +42 -27
  36. package/components/DatePickerInline/Month.tsx +45 -65
  37. package/components/DatePickerInline/MonthPicker.tsx +25 -41
  38. package/components/DatePickerInline/Swiper.native.tsx +21 -4
  39. package/components/DatePickerInline/Swiper.tsx +168 -13
  40. package/components/DatePickerInline/Week.tsx +6 -1
  41. package/components/DatePickerInline/YearPicker.tsx +206 -53
  42. package/components/DatePickerInline/dateUtils.tsx +17 -12
  43. package/components/DatePickerInline/types.ts +3 -0
  44. package/components/DatePickerInline/utils.ts +66 -29
  45. package/components/Drawer/Drawer.tsx +17 -6
  46. package/components/ElementGroup/ElementGroup.tsx +16 -14
  47. package/components/FilePicker/FilePicker.tsx +48 -78
  48. package/components/FilePicker/index.tsx +2 -1
  49. package/components/FilePicker/utils.ts +9 -0
  50. package/components/HelperText/HelperText.tsx +0 -35
  51. package/components/Icon/iconFactory.tsx +3 -3
  52. package/components/Icon/index.tsx +1 -1
  53. package/components/Icon/types.ts +17 -6
  54. package/components/IconButton/IconButton.tsx +42 -57
  55. package/components/IconButton/utils.ts +142 -33
  56. package/components/ListItem/ListItem.tsx +3 -1
  57. package/components/ListItem/utils.ts +1 -1
  58. package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
  59. package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
  60. package/components/LoadingIndicator/index.tsx +13 -0
  61. package/components/LoadingIndicator/utils.ts +117 -0
  62. package/components/Menu/Menu.tsx +3 -18
  63. package/components/NavigationRail/NavigationRail.tsx +15 -9
  64. package/components/Popover/Popover.tsx +122 -145
  65. package/components/Popover/PopoverRoot.tsx +74 -0
  66. package/components/Popover/common.ts +50 -34
  67. package/components/Popover/index.ts +18 -1
  68. package/components/Popover/usePlatformMeasure.native.ts +90 -0
  69. package/components/Popover/usePlatformMeasure.ts +118 -0
  70. package/components/Popover/utils.ts +34 -0
  71. package/components/Select/Select.tsx +368 -507
  72. package/components/Select/context.tsx +72 -0
  73. package/components/Select/index.ts +8 -14
  74. package/components/Select/types.ts +2 -4
  75. package/components/Select/utils.ts +144 -0
  76. package/components/Slot/Slot.tsx +244 -0
  77. package/components/Slot/compose-refs.tsx +62 -0
  78. package/components/Slot/index.tsx +8 -0
  79. package/components/Surface/Surface.android.tsx +34 -8
  80. package/components/Surface/Surface.ios.tsx +36 -29
  81. package/components/Surface/Surface.tsx +31 -4
  82. package/components/Surface/utils.ts +44 -30
  83. package/components/Switch/Switch.tsx +8 -2
  84. package/components/Tabs/TabItem.tsx +35 -58
  85. package/components/Tabs/TabLabel.tsx +5 -9
  86. package/components/Tabs/Tabs.tsx +154 -148
  87. package/components/Tabs/utils.ts +15 -2
  88. package/components/TextInput/TextInput.tsx +658 -575
  89. package/components/TextInput/index.tsx +19 -3
  90. package/components/TextInput/types.ts +76 -27
  91. package/components/TextInput/utils.ts +225 -145
  92. package/components/TimeField/TimeField.tsx +75 -0
  93. package/components/TimeField/index.tsx +6 -0
  94. package/components/TimeField/useTimeFieldState.ts +70 -0
  95. package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
  96. package/components/TimePicker/TimeInput.tsx +87 -37
  97. package/components/TimePicker/TimeInputs.tsx +137 -49
  98. package/components/TimePicker/TimePicker.tsx +73 -10
  99. package/components/TimePicker/TimePickerModal.tsx +186 -0
  100. package/components/TimePicker/context.tsx +17 -0
  101. package/components/TimePicker/index.tsx +15 -3
  102. package/components/TimePicker/utils.ts +93 -0
  103. package/components/Tooltip/Tooltip.tsx +42 -67
  104. package/components/Tooltip/TooltipContent.tsx +32 -5
  105. package/components/Tooltip/TooltipTrigger.tsx +20 -20
  106. package/components/Tooltip/index.tsx +1 -1
  107. package/components/TouchableRipple/TouchableRipple.native.tsx +50 -14
  108. package/components/TouchableRipple/TouchableRipple.tsx +137 -47
  109. package/hocs/withPortal.tsx +1 -1
  110. package/hooks/index.tsx +0 -6
  111. package/hooks/useActionState.tsx +19 -8
  112. package/hooks/useControlledValue.tsx +20 -4
  113. package/hooks/useFilePicker.tsx +6 -16
  114. package/hooks/useWhatHasUpdated.tsx +48 -0
  115. package/package.json +17 -13
  116. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +5 -2
  117. package/styles/shadow.ts +2 -1
  118. package/styles/themes/LightTheme.tsx +1 -1
  119. package/utils/DocumentPicker/documentPicker.ts +78 -27
  120. package/utils/DocumentPicker/types.ts +0 -1
  121. package/utils/extractPropertiesFromStyles.ts +25 -0
  122. package/utils/extractSubcomponents.ts +89 -0
  123. package/utils/lodash.ts +77 -5
  124. package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
  125. package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
  126. package/components/DatePickerDocked/index.tsx +0 -17
  127. package/components/DatePickerDocked/types.ts +0 -11
  128. package/components/DatePickerDocked/utils.ts +0 -157
  129. package/components/DatePickerInput/DatePickerInput.tsx +0 -139
  130. package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
  131. package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
  132. package/components/DatePickerInput/DateRangeInput.tsx +0 -88
  133. package/components/DatePickerInput/index.tsx +0 -10
  134. package/components/DatePickerInput/types.ts +0 -28
  135. package/components/DatePickerInput/utils.ts +0 -15
  136. package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
  137. package/components/DatePickerModal/CalendarEdit.tsx +0 -139
  138. package/components/DatePickerModal/DatePickerModal.tsx +0 -85
  139. package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
  140. package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
  141. package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
  142. package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
  143. package/components/DatePickerModal/index.tsx +0 -16
  144. package/components/DatePickerModal/types.ts +0 -92
  145. package/components/DatePickerModal/utils.ts +0 -122
  146. package/components/DateTimePicker/DateTimePicker.tsx +0 -172
  147. package/components/DateTimePicker/index.tsx +0 -10
  148. package/components/DateTimePicker/utils.ts +0 -12
  149. package/components/Popover/Popover.native.tsx +0 -185
  150. package/components/TimePickerField/TimePickerField.tsx +0 -152
  151. package/components/TimePickerField/index.tsx +0 -10
  152. package/components/TimePickerField/utils.ts +0 -94
  153. package/components/TimePickerModal/TimePickerModal.tsx +0 -115
  154. package/components/TimePickerModal/index.tsx +0 -10
  155. package/components/TimePickerModal/utils.ts +0 -47
  156. package/hooks/useSearchable.tsx +0 -74
  157. package/hooks/useSubcomponents.tsx +0 -59
@@ -1,4 +1,4 @@
1
- import { Children, forwardRef, memo, type ReactNode, useCallback, useMemo } from 'react';
1
+ import { forwardRef, memo, type ReactNode, useCallback, useMemo, useRef } from 'react';
2
2
  import {
3
3
  type GestureResponderEvent,
4
4
  Pressable,
@@ -9,6 +9,7 @@ import {
9
9
  } from 'react-native';
10
10
  import { StyleSheet } from 'react-native-unistyles';
11
11
 
12
+ import { Slot } from '../Slot';
12
13
  import { touchableRippleStyles } from './utils';
13
14
 
14
15
  export type Props = PressableProps & {
@@ -50,6 +51,28 @@ export type Props = PressableProps & {
50
51
  */
51
52
  children: ReactNode;
52
53
  style?: StyleProp<ViewStyle>;
54
+ /**
55
+ * When `true`, the component will not render a wrapper element. Instead, it will
56
+ * merge its props (styles, event handlers, ref) onto its immediate child element.
57
+ * This follows the Radix UI "Slot" pattern for flexible component composition.
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * // Without asChild - renders a Pressable wrapper
62
+ * <TouchableRipple onPress={handlePress}>
63
+ * <View><Text>Click me</Text></View>
64
+ * </TouchableRipple>
65
+ *
66
+ * // With asChild - merges props onto the child
67
+ * <TouchableRipple asChild onPress={handlePress}>
68
+ * <Link href="/page"><Text>Navigate</Text></Link>
69
+ * </TouchableRipple>
70
+ * ```
71
+ *
72
+ * @note When `asChild` is `true`, only a single child element is allowed.
73
+ * @default false
74
+ */
75
+ asChild?: boolean;
53
76
  };
54
77
 
55
78
  /**
@@ -96,12 +119,13 @@ const TouchableRipple = (
96
119
  onPressIn: onPressInProp,
97
120
  onPressOut: onPressOutProp,
98
121
  centered,
122
+ asChild = false,
99
123
  ...rest
100
124
  }: Props,
101
125
  ref: any,
102
126
  ) => {
103
127
  // TODO - enable ripple onLongPress, need to check for mobile as well
104
- const disabled = disabledProp || !onPress;
128
+ const disabled = disabledProp;
105
129
 
106
130
  const componentStyles = touchableRippleStyles;
107
131
 
@@ -121,29 +145,52 @@ const TouchableRipple = (
121
145
  };
122
146
  }, [borderless, componentStyles.root, rippleColorProp, style]);
123
147
 
124
- const handlePressIn = useCallback(
148
+ // Track whether pointer is currently down for handling pointer leave
149
+ const isPointerDownRef = useRef(false);
150
+ // Store current target element to clean up ripples on pointer up/leave
151
+ const currentTargetRef = useRef<HTMLElement | null>(null);
152
+
153
+ // Using 'any' for event types to support both React DOM PointerEvent and React Native events
154
+ // This is a web-only file, so we primarily handle DOM pointer events
155
+ const handlePointerDown = useCallback(
125
156
  (e: any) => {
126
- onPressInProp?.(e);
157
+ onPressInProp?.(e as GestureResponderEvent);
127
158
 
128
159
  if (disabled) return;
129
160
 
130
- const button = e.currentTarget;
161
+ isPointerDownRef.current = true;
162
+
163
+ const button = e.currentTarget as HTMLElement;
164
+ currentTargetRef.current = button;
131
165
  const computedStyle = window.getComputedStyle(button);
132
166
  const dimensions = button.getBoundingClientRect();
133
167
 
134
- let touchX;
135
- let touchY;
136
-
137
- const { changedTouches, touches } = e.nativeEvent;
138
- const touch = touches?.[0] ?? changedTouches?.[0];
168
+ let touchX: number;
169
+ let touchY: number;
139
170
 
140
- // If centered or it was pressed using keyboard - enter or space
141
- if (centered || !touch) {
171
+ if (centered) {
172
+ // If centered, always position ripple at center
142
173
  touchX = dimensions.width / 2;
143
174
  touchY = dimensions.height / 2;
175
+ } else if ('clientX' in e && 'clientY' in e) {
176
+ // Web pointer event - calculate position relative to element
177
+ touchX = e.clientX - dimensions.left;
178
+ touchY = e.clientY - dimensions.top;
179
+ } else if (e.nativeEvent) {
180
+ // React Native gesture event
181
+ const { changedTouches, touches } = e.nativeEvent;
182
+ const touch = touches?.[0] ?? changedTouches?.[0];
183
+ if (touch) {
184
+ touchX = touch.locationX ?? dimensions.width / 2;
185
+ touchY = touch.locationY ?? dimensions.height / 2;
186
+ } else {
187
+ touchX = dimensions.width / 2;
188
+ touchY = dimensions.height / 2;
189
+ }
144
190
  } else {
145
- touchX = touch.locationX ?? e.pageX;
146
- touchY = touch.locationY ?? e.pageY;
191
+ // Fallback to center (keyboard activation)
192
+ touchX = dimensions.width / 2;
193
+ touchY = dimensions.height / 2;
147
194
  }
148
195
 
149
196
  // Get the size of the button to determine how big the ripple should be
@@ -156,7 +203,7 @@ const TouchableRipple = (
156
203
  // Create a container for our ripple effect so we don't need to change the parent's style
157
204
  const container = document.createElement('span');
158
205
 
159
- container.setAttribute('data-paper-ripple', '');
206
+ container.setAttribute('data-molecules-ripple', '');
160
207
 
161
208
  Object.assign(container.style, {
162
209
  position: 'absolute',
@@ -217,42 +264,86 @@ const TouchableRipple = (
217
264
  [onPressInProp, disabled, centered, rippleColor],
218
265
  );
219
266
 
220
- const handlePressOut = useCallback(
221
- (e: any) => {
222
- onPressOutProp?.(e);
223
-
224
- if (disabled) return;
225
-
226
- const containers = e.currentTarget.querySelectorAll(
227
- '[data-paper-ripple]',
228
- ) as HTMLElement[];
267
+ const fadeOutRipples = useCallback((target: HTMLElement) => {
268
+ const containers = target.querySelectorAll(
269
+ '[data-molecules-ripple]',
270
+ ) as NodeListOf<HTMLElement>;
229
271
 
272
+ requestAnimationFrame(() => {
230
273
  requestAnimationFrame(() => {
231
- requestAnimationFrame(() => {
232
- containers.forEach(container => {
233
- const ripple = container.firstChild as HTMLSpanElement;
234
-
235
- Object.assign(ripple.style, {
236
- transitionDuration: '250ms',
237
- opacity: 0,
238
- });
239
-
240
- // Finally remove the span after the transition
241
- setTimeout(() => {
242
- const { parentNode } = container;
243
-
244
- if (parentNode) {
245
- parentNode.removeChild(container);
246
- }
247
- }, 500);
274
+ containers.forEach(container => {
275
+ const ripple = container.firstChild as HTMLSpanElement;
276
+
277
+ Object.assign(ripple.style, {
278
+ transitionDuration: '250ms',
279
+ opacity: 0,
248
280
  });
281
+
282
+ // Finally remove the span after the transition
283
+ setTimeout(() => {
284
+ const { parentNode } = container;
285
+
286
+ if (parentNode) {
287
+ parentNode.removeChild(container);
288
+ }
289
+ }, 500);
249
290
  });
250
291
  });
292
+ });
293
+ }, []);
294
+
295
+ const handlePointerUp = useCallback(
296
+ (e: any) => {
297
+ onPressOutProp?.(e as GestureResponderEvent);
298
+
299
+ if (disabled || !isPointerDownRef.current) return;
300
+
301
+ isPointerDownRef.current = false;
302
+ currentTargetRef.current = null;
303
+
304
+ const target = e.currentTarget as HTMLElement;
305
+ fadeOutRipples(target);
251
306
  },
252
- [onPressOutProp, disabled],
307
+ [onPressOutProp, disabled, fadeOutRipples],
253
308
  );
254
309
 
255
- const Component = onPress ? Pressable : View;
310
+ const handlePointerLeave = useCallback(
311
+ (e: any) => {
312
+ // Only fade out if pointer was down (dragging out of element)
313
+ if (disabled || !isPointerDownRef.current) return;
314
+
315
+ isPointerDownRef.current = false;
316
+ currentTargetRef.current = null;
317
+
318
+ const target = e.currentTarget as HTMLElement;
319
+ fadeOutRipples(target);
320
+ },
321
+ [disabled, fadeOutRipples],
322
+ );
323
+
324
+ const handlePointerCancel = useCallback(
325
+ (e: any) => {
326
+ if (disabled || !isPointerDownRef.current) return;
327
+
328
+ isPointerDownRef.current = false;
329
+ currentTargetRef.current = null;
330
+
331
+ const target = e.currentTarget as HTMLElement;
332
+ fadeOutRipples(target);
333
+ },
334
+ [disabled, fadeOutRipples],
335
+ );
336
+
337
+ const Component = asChild ? Slot : onPress ? Pressable : View;
338
+
339
+ // Use pointer events for universal compatibility (works on any HTML element)
340
+ // These events work with mouse, touch, and stylus inputs
341
+ const pointerEventProps = {
342
+ onPointerDown: handlePointerDown,
343
+ onPointerUp: handlePointerUp,
344
+ onPointerLeave: handlePointerLeave,
345
+ onPointerCancel: handlePointerCancel,
346
+ };
256
347
 
257
348
  return (
258
349
  <Component
@@ -261,10 +352,9 @@ const TouchableRipple = (
261
352
  style={containerStyle}
262
353
  ref={ref}
263
354
  onPress={onPress}
264
- onPressIn={handlePressIn}
265
- onPressOut={handlePressOut}
266
- disabled={disabled}>
267
- {Children.only(children)}
355
+ disabled={disabled}
356
+ {...pointerEventProps}>
357
+ {children}
268
358
  </Component>
269
359
  );
270
360
  };
@@ -6,7 +6,7 @@ const withPortal =
6
6
  <T,>(Component: ComponentType<T>) =>
7
7
  (props: T) => {
8
8
  return (
9
- <Portal name={'withPortal' + (Component.displayName ?? '')}>
9
+ <Portal>
10
10
  {/* @ts-ignore */}
11
11
  <Component {...(props as T)} />
12
12
  </Portal>
package/hooks/index.tsx CHANGED
@@ -18,11 +18,5 @@ export { useMediaQuery } from './useMediaQuery';
18
18
  export { useMergedRefs } from './useMergedRefs';
19
19
  export { default as usePrevious } from './usePrevious';
20
20
  export * from './useQueryFilter';
21
- export {
22
- default as useSearchable,
23
- type UseSearchableProps,
24
- useSearchInputProps,
25
- } from './useSearchable';
26
- export { default as useSubcomponents, type UseSubcomponentsProps } from './useSubcomponents';
27
21
  export * from './useTheme';
28
22
  export { default as useToggle } from './useToggle';
@@ -1,4 +1,4 @@
1
- import { type RefObject, useRef } from 'react';
1
+ import { type RefObject, useCallback, useRef } from 'react';
2
2
 
3
3
  import { useActive } from './useActive';
4
4
  import { useFocus } from './useFocus';
@@ -14,16 +14,27 @@ export type UseActionStateProps = {
14
14
  export const useActionState = (
15
15
  props: UseActionStateProps & { ref?: RefObject<any> | React.ForwardedRef<any> } = {},
16
16
  ) => {
17
- const ref = useRef(null);
18
- const actionsRef = (
19
- (props.ref as any)?.current === undefined ? ref : props.ref
20
- ) as RefObject<any>;
17
+ const internalRef = useRef(null);
18
+ const externalRef = props.ref;
19
+
20
+ const actionsRef = useCallback(
21
+ (node: any) => {
22
+ internalRef.current = node;
23
+ if (typeof externalRef === 'function') {
24
+ externalRef(node);
25
+ } else if (externalRef) {
26
+ (externalRef as RefObject<any>).current = node;
27
+ }
28
+ },
29
+ [externalRef],
30
+ ) as unknown as RefObject<any>;
31
+
21
32
  const hovered =
22
- useHover(actionsRef, props.actionsToListen?.includes('hover')) || !!props.hovered;
33
+ useHover(internalRef, props.actionsToListen?.includes('hover')) || !!props.hovered;
23
34
  const pressed =
24
- useActive(actionsRef, props.actionsToListen?.includes('press')) || !!props.pressed;
35
+ useActive(internalRef, props.actionsToListen?.includes('press')) || !!props.pressed;
25
36
  const focused =
26
- useFocus(actionsRef, props.actionsToListen?.includes('focus')) || !!props.focused;
37
+ useFocus(internalRef, props.actionsToListen?.includes('focus')) || !!props.focused;
27
38
 
28
39
  return {
29
40
  actionsRef,
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
+ import useLatest from './useLatest';
4
+
3
5
  type ReturnType<T> = [T, (value: T, ...args: any[]) => void];
4
6
 
5
7
  type Args<T> = {
@@ -31,21 +33,35 @@ const useControlledValue = <T,>({
31
33
 
32
34
  const isUncontrolled = useRef(valueProp).current === undefined;
33
35
  const [uncontrolledValue, setValue] = useState(value);
36
+ const valuePropRef = useLatest(valueProp);
37
+ const onChangeRef = useLatest(onChange);
38
+ const manipulateValueRef = useLatest(manipulateValue);
39
+ const uncontrolledValueRef = useLatest(uncontrolledValue);
34
40
 
35
41
  const updateValue = useCallback(
36
42
  (val: T, ...rest: any[]) => {
37
43
  if (disabled) return;
38
44
 
39
45
  if (isUncontrolled) {
40
- setValue(manipulateValue(val, uncontrolledValue));
46
+ setValue(manipulateValueRef.current(val, uncontrolledValueRef.current));
41
47
  }
42
48
 
43
- onChange?.(
44
- manipulateValue(val, isUncontrolled ? uncontrolledValue : valueProp),
49
+ onChangeRef.current?.(
50
+ manipulateValueRef.current(
51
+ val,
52
+ isUncontrolled ? uncontrolledValueRef.current : valuePropRef.current,
53
+ ),
45
54
  ...rest,
46
55
  );
47
56
  },
48
- [disabled, isUncontrolled, manipulateValue, onChange, uncontrolledValue, valueProp],
57
+ [
58
+ disabled,
59
+ isUncontrolled,
60
+ manipulateValueRef,
61
+ onChangeRef,
62
+ uncontrolledValueRef,
63
+ valuePropRef,
64
+ ],
49
65
  );
50
66
 
51
67
  useEffect(() => {
@@ -7,15 +7,11 @@ import {
7
7
  } from '../utils/DocumentPicker';
8
8
  import { isNil, omitBy } from '../utils/lodash';
9
9
 
10
- export const useFilePicker = ({
11
- multiple,
12
- onCancel,
13
- onError,
14
- ...options
15
- }: DocumentPickerOptions) => {
10
+ export const useFilePicker = (options: DocumentPickerOptions) => {
16
11
  const openFilePicker = useCallback(
17
- async (callback: (response: DocumentResult | DocumentResult[]) => void): Promise<void> => {
18
- const omittedOptions = omitBy(options, isNil);
12
+ async (callback: (response: DocumentResult[]) => void) => {
13
+ const { multiple, ...rest } = options;
14
+ const omittedOptions = omitBy(rest, isNil);
19
15
 
20
16
  try {
21
17
  let response;
@@ -30,16 +26,10 @@ export const useFilePicker = ({
30
26
  } catch (e: any) {
31
27
  // eslint-disable-next-line no-console
32
28
  console.log('FilePicker Error', e, e?.code);
33
-
34
- if (e?.code === 'DOCUMENT_PICKER_CANCELED.') {
35
- onCancel?.();
36
- return;
37
- }
38
- // It might result in an error.
39
- onError?.(e);
29
+ // Error and cancel callbacks are handled by DocumentPicker itself
40
30
  }
41
31
  },
42
- [multiple, onCancel, onError, options],
32
+ [options],
43
33
  );
44
34
 
45
35
  return { openFilePicker };
@@ -0,0 +1,48 @@
1
+ import { useEffect, useMemo } from 'react';
2
+
3
+ import useLatest from './useLatest';
4
+
5
+ const map = new Map();
6
+
7
+ export const whatHasUpdatedFactory = <T extends Record<string, unknown>>(
8
+ name: string,
9
+ prevObject: T,
10
+ { debug = false, useCached = false } = {},
11
+ ) => {
12
+ const getPrev = () => {
13
+ if (!map.has(name)) map.set(name, prevObject);
14
+ return useCached ? map.get(name) : prevObject;
15
+ };
16
+
17
+ const setPrev = (value: T) => {
18
+ if (useCached) map.set(name, value);
19
+ else prevObject = value;
20
+ };
21
+
22
+ return (nextObject: T) => {
23
+ const old = getPrev();
24
+ const changes = Object.keys({ ...nextObject, ...old }).reduce((all, key) => {
25
+ const newValue = nextObject?.[key];
26
+ const oldValue = old[key];
27
+ if (oldValue === newValue) return all;
28
+ return { ...all, [key]: { newValue, oldValue } };
29
+ }, {});
30
+
31
+ setPrev(nextObject);
32
+
33
+ if (!debug && !Object.keys(changes).length) return;
34
+ // eslint-disable-next-line no-console
35
+ console.log('🚨🕵️ UPDATED', name, changes);
36
+ };
37
+ };
38
+
39
+ export const useWhatHasUpdated = (name: string, props: Record<string, any>) => {
40
+ const argRef = useLatest({ name, props });
41
+ const checkFunc = useMemo(
42
+ () => whatHasUpdatedFactory(argRef.current.name, argRef.current.props),
43
+ [argRef],
44
+ );
45
+ useEffect(() => {
46
+ checkFunc(props);
47
+ });
48
+ };
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "react-native-molecules",
3
- "version": "0.5.0-beta.2",
3
+ "version": "0.5.0-beta.20",
4
4
  "author": "Thet Aung <thetaung.dev@gmail.com>",
5
5
  "license": "MIT",
6
6
  "main": "index.ts",
7
+ "sideEffects": [
8
+ "components/DatePicker/context.tsx",
9
+ "components/Select/context.tsx",
10
+ "components/TimePicker/context.tsx"
11
+ ],
7
12
  "files": [
8
13
  "components",
9
14
  "context-bridge",
@@ -42,39 +47,38 @@
42
47
  },
43
48
  "dependencies": {
44
49
  "@gorhom/portal": "^1.0.14",
50
+ "@radix-ui/react-compose-refs": "^1.1.2",
45
51
  "@react-native-vector-icons/feather": "^12.4.0",
46
52
  "@react-native-vector-icons/material-design-icons": "^12.4.0",
47
53
  "color": "^4.2.3",
48
54
  "date-fns": "^4.1.0",
49
55
  "eventemitter2": "^6.4.9",
50
- "lodash.get": "^4.4.2",
51
- "lodash.keyby": "^4.6.0",
52
- "lodash.memoize": "^4.1.2",
53
- "lodash.omitby": "^4.6.0",
56
+ "flubber": "^0.4.2",
54
57
  "use-sync-external-store": "^1.2.0"
55
58
  },
56
59
  "peerDependencies": {
57
60
  "@react-native-documents/picker": "^10.1.2",
58
61
  "react": "19.1.0",
59
62
  "react-native": "0.81.4",
60
- "react-native-unistyles": "^3.0.15",
61
- "react-native-web": "~0.21.1"
63
+ "react-native-svg": ">=12.1.0",
64
+ "react-native-unistyles": "^3.0.22",
65
+ "react-native-web": "~0.21.1",
66
+ "react-native-reanimated": ">=4.0.0",
67
+ "react-native-redash": ">=18.0.0"
62
68
  },
63
69
  "devDependencies": {
70
+ "@types/flubber": "^0.4.0",
71
+ "react-native-svg": "^15.10.1",
64
72
  "@react-native-documents/picker": "^10.1.2",
65
73
  "@types/color": "^3.0.3",
66
74
  "@types/jest": "^29.1.2",
67
- "@types/lodash": "^4.17.13",
68
- "@types/lodash.get": "^4.4.7",
69
- "@types/lodash.keyby": "^4.6.7",
70
- "@types/lodash.memoize": "^4.1.7",
71
- "@types/lodash.omitby": "^4.6.7",
72
75
  "@types/react": "~19.1.10",
73
76
  "@types/use-sync-external-store": "^1.5.0",
74
77
  "react": "19.1.0",
75
78
  "react-native": "0.81.4",
76
79
  "react-native-builder-bob": "^0.17.1",
77
- "react-native-unistyles": "^3.0.15",
80
+ "react-native-reanimated": "~4.1.1",
81
+ "react-native-unistyles": "^3.0.22",
78
82
  "react-native-web": "~0.21.1"
79
83
  },
80
84
  "eslintIgnore": [
@@ -10,9 +10,12 @@ import {
10
10
  type ShortcutsManagerProps,
11
11
  } from './utils';
12
12
 
13
+ const defaultScopes: ShortcutsManagerProps['scopes'] = [];
14
+ const defaultShortcuts: Shortcut[] = [];
15
+
13
16
  const _ShortcutsManager = ({ shortcuts, scopes, children }: ShortcutsManagerProps) => {
14
- const shortcutsRef = useRef<Shortcut[]>(shortcuts || []);
15
- const scopesRef = useRef(scopes);
17
+ const shortcutsRef = useRef<Shortcut[]>(shortcuts || defaultShortcuts);
18
+ const scopesRef = useRef(scopes || defaultScopes);
16
19
  const parentContextRef = useContext(ShortcutsManagerContext);
17
20
 
18
21
  const contextValue = useMemo(() => {
package/styles/shadow.ts CHANGED
@@ -8,7 +8,8 @@ export const inputRange: MD3Elevation[] = [0, 1, 2, 3, 4, 5];
8
8
  export const shadowHeight = [0, 1, 2, 4, 6, 8];
9
9
  export const shadowRadius = [0, 3, 6, 8, 10, 12];
10
10
 
11
- export default function shadow(elevation: number) {
11
+ export default function shadow(_elevation: number) {
12
+ const elevation = typeof _elevation === 'number' ? (_elevation > 5 ? 5 : _elevation) : 0;
12
13
  return {
13
14
  shadowColor: MD3_SHADOW_COLOR,
14
15
  shadowOpacity: elevation ? MD3_SHADOW_OPACITY : 0,
@@ -25,7 +25,7 @@ export const generateLightThemeColors = (
25
25
  error: palette.error40,
26
26
  errorContainer: palette.error90,
27
27
  onPrimary: palette.primary100,
28
- onPrimaryContainer: palette.primary10,
28
+ onPrimaryContainer: palette.primary30,
29
29
  onSecondary: palette.secondary100,
30
30
  onSecondaryContainer: palette.secondary10,
31
31
  onTertiary: palette.tertiary100,