react-native-molecules 0.5.0-beta.3 → 0.5.0-beta.30

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 (227) hide show
  1. package/components/Accordion/Accordion.tsx +2 -6
  2. package/components/Accordion/AccordionItem.tsx +16 -12
  3. package/components/Accordion/AccordionItemContent.tsx +6 -1
  4. package/components/Accordion/AccordionItemHeader.tsx +1 -1
  5. package/components/Accordion/utils.ts +6 -0
  6. package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
  7. package/components/Appbar/AppbarBase.tsx +18 -13
  8. package/components/Button/Button.tsx +211 -264
  9. package/components/Button/index.tsx +9 -3
  10. package/components/Button/types.ts +16 -2
  11. package/components/Button/utils.ts +230 -208
  12. package/components/Card/Card.tsx +1 -1
  13. package/components/Checkbox/Checkbox.tsx +125 -88
  14. package/components/Checkbox/CheckboxBase.ios.tsx +14 -23
  15. package/components/Checkbox/CheckboxBase.tsx +21 -137
  16. package/components/Checkbox/context.tsx +14 -0
  17. package/components/Checkbox/index.tsx +11 -4
  18. package/components/Checkbox/types.ts +63 -29
  19. package/components/Checkbox/utils.ts +25 -108
  20. package/components/Chip/Chip.tsx +41 -52
  21. package/components/Chip/utils.ts +3 -7
  22. package/components/DateField/DateField.tsx +111 -0
  23. package/components/DateField/index.tsx +6 -0
  24. package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +19 -51
  25. package/components/DatePicker/DateCalendar.tsx +83 -0
  26. package/components/DatePicker/DatePickerActions.tsx +73 -0
  27. package/components/DatePicker/DatePickerModal.tsx +246 -0
  28. package/components/DatePicker/DatePickerPopover.tsx +79 -0
  29. package/components/DatePicker/DatePickerProvider.tsx +158 -0
  30. package/components/DatePicker/DatePickerTrigger.tsx +23 -0
  31. package/components/DatePicker/context.tsx +83 -0
  32. package/components/DatePicker/index.tsx +45 -0
  33. package/components/DatePicker/utils.ts +295 -0
  34. package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
  35. package/components/DatePickerInline/DatePickerInline.tsx +17 -16
  36. package/components/DatePickerInline/DatePickerInlineBase.tsx +11 -5
  37. package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
  38. package/components/DatePickerInline/Day.tsx +25 -1
  39. package/components/DatePickerInline/DayNames.tsx +13 -10
  40. package/components/DatePickerInline/DayRange.tsx +2 -4
  41. package/components/DatePickerInline/HeaderItem.tsx +44 -29
  42. package/components/DatePickerInline/Month.tsx +48 -67
  43. package/components/DatePickerInline/MonthPicker.tsx +80 -92
  44. package/components/DatePickerInline/Swiper.native.tsx +21 -4
  45. package/components/DatePickerInline/Swiper.tsx +169 -14
  46. package/components/DatePickerInline/SwiperUtils.ts +1 -1
  47. package/components/DatePickerInline/Week.tsx +6 -1
  48. package/components/DatePickerInline/YearPicker.tsx +220 -78
  49. package/components/DatePickerInline/dateUtils.tsx +18 -13
  50. package/components/DatePickerInline/store.tsx +27 -0
  51. package/components/DatePickerInline/types.ts +6 -2
  52. package/components/DatePickerInline/utils.ts +66 -29
  53. package/components/Divider/Divider.tsx +192 -0
  54. package/components/Divider/index.tsx +10 -0
  55. package/components/Drawer/Drawer.tsx +17 -6
  56. package/components/Drawer/DrawerItemGroup.tsx +3 -7
  57. package/components/ElementGroup/ElementGroup.tsx +1 -1
  58. package/components/FilePicker/FilePicker.tsx +48 -78
  59. package/components/FilePicker/index.tsx +2 -1
  60. package/components/FilePicker/utils.ts +9 -0
  61. package/components/HelperText/HelperText.tsx +0 -35
  62. package/components/Icon/iconFactory.tsx +5 -4
  63. package/components/Icon/index.tsx +1 -1
  64. package/components/Icon/types.ts +17 -6
  65. package/components/IconButton/IconButton.tsx +84 -84
  66. package/components/IconButton/index.tsx +1 -0
  67. package/components/IconButton/types.ts +10 -0
  68. package/components/IconButton/utils.ts +167 -33
  69. package/components/List/List.tsx +276 -0
  70. package/components/List/context.tsx +27 -0
  71. package/components/List/index.ts +8 -0
  72. package/components/List/types.ts +117 -0
  73. package/components/List/utils.ts +79 -0
  74. package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
  75. package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
  76. package/components/LoadingIndicator/index.tsx +13 -0
  77. package/components/LoadingIndicator/utils.ts +117 -0
  78. package/components/Menu/Menu.tsx +162 -39
  79. package/components/Menu/index.tsx +10 -7
  80. package/components/Menu/utils.ts +21 -70
  81. package/components/NavigationRail/NavigationRail.tsx +15 -9
  82. package/components/Popover/Popover.tsx +119 -145
  83. package/components/Popover/PopoverRoot.tsx +60 -0
  84. package/components/Popover/common.ts +54 -34
  85. package/components/Popover/index.ts +12 -1
  86. package/components/Popover/usePlatformMeasure.native.ts +90 -0
  87. package/components/Popover/usePlatformMeasure.ts +120 -0
  88. package/components/Popover/utils.ts +34 -0
  89. package/components/Portal/Portal.tsx +1 -2
  90. package/components/Radio/Radio.tsx +188 -0
  91. package/components/Radio/RadioBase.ios.tsx +69 -0
  92. package/components/Radio/RadioBase.tsx +136 -0
  93. package/components/Radio/context.tsx +23 -0
  94. package/components/Radio/index.tsx +20 -0
  95. package/components/Radio/types.ts +101 -0
  96. package/components/Radio/utils.ts +115 -0
  97. package/components/Rating/Rating.tsx +1 -1
  98. package/components/Select/Select.tsx +521 -785
  99. package/components/Select/context.tsx +81 -0
  100. package/components/Select/index.ts +26 -14
  101. package/components/Select/types.ts +65 -58
  102. package/components/Select/utils.ts +126 -0
  103. package/components/Slot/Slot.tsx +244 -0
  104. package/components/Slot/compose-refs.tsx +62 -0
  105. package/components/Slot/index.tsx +8 -0
  106. package/components/Surface/Surface.android.tsx +32 -7
  107. package/components/Surface/Surface.ios.tsx +34 -29
  108. package/components/Surface/Surface.tsx +31 -4
  109. package/components/Surface/utils.ts +44 -6
  110. package/components/Switch/Switch.ios.tsx +1 -1
  111. package/components/Switch/Switch.tsx +10 -3
  112. package/components/Tabs/TabItem.tsx +35 -58
  113. package/components/Tabs/TabLabel.tsx +5 -9
  114. package/components/Tabs/Tabs.tsx +156 -150
  115. package/components/Tabs/utils.ts +15 -2
  116. package/components/Text/textFactory.tsx +17 -5
  117. package/components/TextInput/TextInput.tsx +663 -579
  118. package/components/TextInput/index.tsx +19 -3
  119. package/components/TextInput/types.ts +77 -28
  120. package/components/TextInput/utils.ts +235 -145
  121. package/components/TimeField/TimeField.tsx +75 -0
  122. package/components/TimeField/index.tsx +6 -0
  123. package/components/TimeField/useTimeFieldState.ts +70 -0
  124. package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
  125. package/components/TimePicker/AnalogClock.tsx +1 -1
  126. package/components/TimePicker/TimeInput.tsx +87 -42
  127. package/components/TimePicker/TimeInputs.tsx +138 -50
  128. package/components/TimePicker/TimePicker.tsx +74 -11
  129. package/components/TimePicker/TimePickerModal.tsx +186 -0
  130. package/components/TimePicker/context.tsx +17 -0
  131. package/components/TimePicker/index.tsx +15 -3
  132. package/components/TimePicker/utils.ts +93 -4
  133. package/components/Tooltip/Tooltip.tsx +42 -67
  134. package/components/Tooltip/TooltipContent.tsx +32 -5
  135. package/components/Tooltip/TooltipTrigger.tsx +20 -20
  136. package/components/Tooltip/index.tsx +1 -1
  137. package/components/TouchableRipple/TouchableRipple.native.tsx +83 -16
  138. package/components/TouchableRipple/TouchableRipple.tsx +150 -102
  139. package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
  140. package/hocs/index.tsx +1 -1
  141. package/hocs/withKeyboardAccessibility.tsx +2 -3
  142. package/hocs/withPortal.tsx +1 -1
  143. package/hooks/index.tsx +2 -12
  144. package/hooks/useActionState.tsx +19 -8
  145. package/hooks/useContrastColor.ts +1 -2
  146. package/hooks/useFilePicker.tsx +7 -17
  147. package/hooks/useHandleNumberFormat.tsx +2 -2
  148. package/hooks/useMediaQuery.tsx +1 -2
  149. package/package.json +95 -111
  150. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +6 -3
  151. package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
  152. package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
  153. package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
  154. package/styles/shadow.ts +2 -1
  155. package/styles/themes/LightTheme.tsx +1 -1
  156. package/utils/DocumentPicker/documentPicker.ts +78 -27
  157. package/utils/DocumentPicker/types.ts +0 -1
  158. package/utils/extractSubcomponents.ts +89 -0
  159. package/utils/extractTextStyles.ts +1 -2
  160. package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
  161. package/utils/index.ts +0 -3
  162. package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
  163. package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
  164. package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
  165. package/components/DatePickerDocked/index.tsx +0 -17
  166. package/components/DatePickerDocked/types.ts +0 -11
  167. package/components/DatePickerDocked/utils.ts +0 -157
  168. package/components/DatePickerInline/DatePickerContext.tsx +0 -21
  169. package/components/DatePickerInput/DatePickerInput.tsx +0 -139
  170. package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
  171. package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
  172. package/components/DatePickerInput/DateRangeInput.tsx +0 -88
  173. package/components/DatePickerInput/index.tsx +0 -10
  174. package/components/DatePickerInput/types.ts +0 -28
  175. package/components/DatePickerInput/utils.ts +0 -15
  176. package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
  177. package/components/DatePickerModal/CalendarEdit.tsx +0 -139
  178. package/components/DatePickerModal/DatePickerModal.tsx +0 -85
  179. package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
  180. package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
  181. package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
  182. package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
  183. package/components/DatePickerModal/index.tsx +0 -16
  184. package/components/DatePickerModal/types.ts +0 -92
  185. package/components/DatePickerModal/utils.ts +0 -122
  186. package/components/DateTimePicker/DateTimePicker.tsx +0 -172
  187. package/components/DateTimePicker/index.tsx +0 -10
  188. package/components/DateTimePicker/utils.ts +0 -12
  189. package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
  190. package/components/HorizontalDivider/index.tsx +0 -9
  191. package/components/ListItem/ListItem.tsx +0 -136
  192. package/components/ListItem/ListItemDescription.tsx +0 -25
  193. package/components/ListItem/ListItemTitle.tsx +0 -25
  194. package/components/ListItem/index.tsx +0 -14
  195. package/components/ListItem/utils.ts +0 -115
  196. package/components/Menu/MenuDivider.tsx +0 -13
  197. package/components/Menu/MenuItem.tsx +0 -128
  198. package/components/Popover/Popover.native.tsx +0 -185
  199. package/components/RadioButton/RadioButton.tsx +0 -138
  200. package/components/RadioButton/RadioButtonAndroid.tsx +0 -188
  201. package/components/RadioButton/RadioButtonGroup.tsx +0 -98
  202. package/components/RadioButton/RadioButtonIOS.tsx +0 -106
  203. package/components/RadioButton/RadioButtonItem.tsx +0 -232
  204. package/components/RadioButton/index.ts +0 -22
  205. package/components/RadioButton/utils.ts +0 -165
  206. package/components/TimePickerField/TimePickerField.tsx +0 -152
  207. package/components/TimePickerField/index.tsx +0 -10
  208. package/components/TimePickerField/utils.ts +0 -94
  209. package/components/TimePickerModal/TimePickerModal.tsx +0 -115
  210. package/components/TimePickerModal/index.tsx +0 -10
  211. package/components/TimePickerModal/utils.ts +0 -47
  212. package/components/VerticalDivider/VerticalDivider.tsx +0 -100
  213. package/components/VerticalDivider/index.tsx +0 -9
  214. package/context-bridge/index.tsx +0 -87
  215. package/fast-context/index.tsx +0 -190
  216. package/hocs/typedMemo.tsx +0 -5
  217. package/hooks/useControlledValue.tsx +0 -68
  218. package/hooks/useLatest.tsx +0 -9
  219. package/hooks/useMergedRefs.ts +0 -14
  220. package/hooks/usePrevious.ts +0 -13
  221. package/hooks/useSearchable.tsx +0 -74
  222. package/hooks/useSubcomponents.tsx +0 -59
  223. package/hooks/useToggle.tsx +0 -24
  224. package/utils/color.ts +0 -22
  225. package/utils/compare/index.ts +0 -54
  226. package/utils/lodash.ts +0 -49
  227. package/utils/repository.ts +0 -53
@@ -1,14 +1,18 @@
1
- import { Children, forwardRef, memo, type ReactNode, useCallback, useMemo } from 'react';
1
+ import { noop } from '@react-native-molecules/utils/helpers/lodash';
2
+ import { forwardRef, memo, type ReactNode, useCallback, useRef } from 'react';
2
3
  import {
3
4
  type GestureResponderEvent,
5
+ Platform,
4
6
  Pressable,
5
7
  type PressableProps,
6
8
  type StyleProp,
7
- View,
8
9
  type ViewStyle,
9
10
  } from 'react-native';
10
11
  import { StyleSheet } from 'react-native-unistyles';
11
12
 
13
+ import { useTheme } from '../../hooks/useTheme';
14
+ import { Slot } from '../Slot';
15
+ import { rippleColorFromBackground } from './rippleFromForegroundColor';
12
16
  import { touchableRippleStyles } from './utils';
13
17
 
14
18
  export type Props = PressableProps & {
@@ -45,11 +49,38 @@ export type Props = PressableProps & {
45
49
  * Color of the underlay for the highlight effect (Android < 5.0 and iOS).
46
50
  */
47
51
  underlayColor?: string;
52
+ /**
53
+ * Alpha used for auto-derived ripple color when `rippleColor` is not provided.
54
+ * @default 0.24
55
+ */
56
+ rippleAlpha?: number;
48
57
  /**
49
58
  * Content of the `TouchableRipple`.
50
59
  */
51
60
  children: ReactNode;
52
61
  style?: StyleProp<ViewStyle>;
62
+ /**
63
+ * When `true`, the component will not render a wrapper element. Instead, it will
64
+ * merge its props (styles, event handlers, ref) onto its immediate child element.
65
+ * This follows the Radix UI "Slot" pattern for flexible component composition.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * // Without asChild - renders a Pressable wrapper
70
+ * <TouchableRipple onPress={handlePress}>
71
+ * <View><Text>Click me</Text></View>
72
+ * </TouchableRipple>
73
+ *
74
+ * // With asChild - merges props onto the child
75
+ * <TouchableRipple asChild onPress={handlePress}>
76
+ * <Link href="/page"><Text>Navigate</Text></Link>
77
+ * </TouchableRipple>
78
+ * ```
79
+ *
80
+ * @note When `asChild` is `true`, only a single child element is allowed.
81
+ * @default false
82
+ */
83
+ asChild?: boolean;
53
84
  };
54
85
 
55
86
  /**
@@ -91,73 +122,70 @@ const TouchableRipple = (
91
122
  disabled: disabledProp,
92
123
  rippleColor: rippleColorProp,
93
124
  underlayColor: _underlayColor,
94
- onPress,
125
+ rippleAlpha = 0.24,
126
+ onPress = noop,
95
127
  children,
96
128
  onPressIn: onPressInProp,
97
129
  onPressOut: onPressOutProp,
98
130
  centered,
131
+ asChild = false,
99
132
  ...rest
100
133
  }: Props,
101
134
  ref: any,
102
135
  ) => {
103
136
  // TODO - enable ripple onLongPress, need to check for mobile as well
104
- const disabled = disabledProp || !onPress;
137
+ const disabled = disabledProp;
138
+ const theme = useTheme();
105
139
 
106
140
  const componentStyles = touchableRippleStyles;
107
141
 
108
- const { rippleColor, containerStyle } = useMemo(() => {
109
- const { rippleColor: defaultRippleColor } = componentStyles.root;
110
-
111
- return {
112
- rippleColor: rippleColorProp || defaultRippleColor,
113
- containerStyle: [
114
- styles.touchable,
115
- { borderRadius: 'inherit' },
116
- borderless && styles.borderless,
117
- // ...(Platform.OS === 'web' && !disabled ? ({ cursor: 'pointer' } as any) : {}),
118
- componentStyles.root,
119
- style,
120
- ],
121
- };
122
- }, [borderless, componentStyles.root, rippleColorProp, style]);
123
-
124
- const handlePressIn = useCallback(
125
- (e: any) => {
126
- onPressInProp?.(e);
127
-
128
- if (disabled) return;
129
-
130
- const button = e.currentTarget;
131
- const computedStyle = window.getComputedStyle(button);
132
- const dimensions = button.getBoundingClientRect();
133
-
134
- let touchX;
135
- let touchY;
136
-
137
- const { changedTouches, touches } = e.nativeEvent;
138
- const touch = touches?.[0] ?? changedTouches?.[0];
139
-
140
- // If centered or it was pressed using keyboard - enter or space
141
- if (centered || !touch) {
142
- touchX = dimensions.width / 2;
143
- touchY = dimensions.height / 2;
144
- } else {
145
- touchX = touch.locationX ?? e.pageX;
146
- touchY = touch.locationY ?? e.pageY;
147
- }
142
+ const { rippleColor: themeRippleFallback } = componentStyles.root;
143
+
144
+ const tokenResolvedColor =
145
+ typeof rippleColorProp === 'string'
146
+ ? theme.colors[rippleColorProp as keyof typeof theme.colors]
147
+ : undefined;
148
+
149
+ const rippleColorResolvedProp =
150
+ typeof tokenResolvedColor === 'string' ? tokenResolvedColor : rippleColorProp;
151
+ const containerStyle = [
152
+ styles.touchable,
153
+ { borderRadius: 'inherit' },
154
+ borderless && styles.borderless,
155
+ // ...(Platform.OS === 'web' && !disabled ? ({ cursor: 'pointer' } as any) : {}),
156
+ componentStyles.root,
157
+ style,
158
+ ];
159
+
160
+ // The active ripple is tracked so onPressOut can fade it. Driving the lifecycle
161
+ // off Pressable's press events (instead of raw pointer events) means a nested
162
+ // element that captures the gesture won't trigger an orphan ripple — Pressable
163
+ // only fires onPressIn when its own press is being handled.
164
+ const activeRippleRef = useRef<HTMLElement | null>(null);
165
+
166
+ const startRipple = useCallback(
167
+ (host: HTMLElement, x: number, y: number) => {
168
+ const computedStyle = window.getComputedStyle(host);
169
+ const dimensions = host.getBoundingClientRect();
170
+
171
+ const resolvedRippleColor =
172
+ rippleColorResolvedProp ??
173
+ (Platform.OS === 'web'
174
+ ? rippleColorFromBackground(
175
+ computedStyle.backgroundColor,
176
+ String(themeRippleFallback),
177
+ rippleAlpha,
178
+ )
179
+ : String(themeRippleFallback));
148
180
 
149
- // Get the size of the button to determine how big the ripple should be
150
181
  const size = centered
151
- ? // If ripple is always centered, we don't need to make it too big
152
- Math.min(dimensions.width, dimensions.height) * 1.25
153
- : // Otherwise make it twice as big so clicking on one end spreads ripple to other
154
- Math.max(dimensions.width, dimensions.height) * 2;
182
+ ? Math.min(dimensions.width, dimensions.height) * 1.25
183
+ : Math.max(dimensions.width, dimensions.height) * 2;
155
184
 
156
- // Create a container for our ripple effect so we don't need to change the parent's style
157
- const container = document.createElement('span');
158
-
159
- container.setAttribute('data-paper-ripple', '');
185
+ const expandDuration = Math.min(size * 1.5, 350);
160
186
 
187
+ const container = document.createElement('span');
188
+ container.setAttribute('data-molecules-ripple', '');
161
189
  Object.assign(container.style, {
162
190
  position: 'absolute',
163
191
  pointerEvents: 'none',
@@ -172,39 +200,28 @@ const TouchableRipple = (
172
200
  overflow: centered ? 'visible' : 'hidden',
173
201
  });
174
202
 
175
- // Create span to show the ripple effect
176
203
  const ripple = document.createElement('span');
177
-
178
204
  Object.assign(ripple.style, {
179
205
  position: 'absolute',
180
206
  pointerEvents: 'none',
181
- backgroundColor: rippleColor,
207
+ backgroundColor: resolvedRippleColor,
182
208
  borderRadius: '50%',
183
-
184
- /* Transition configuration */
185
- transitionProperty: 'transform opacity',
186
- transitionDuration: `${Math.min(size * 1.5, 350)}ms`,
209
+ transitionProperty: 'transform, opacity',
210
+ transitionDuration: `${expandDuration}ms`,
187
211
  transitionTimingFunction: 'linear',
188
212
  transformOrigin: 'center',
189
-
190
- /* We'll animate these properties */
191
213
  transform: 'translate3d(-50%, -50%, 0) scale3d(0.1, 0.1, 0.1)',
192
214
  opacity: '0.5',
193
-
194
- // Position the ripple where cursor was
195
- left: `${touchX}px`,
196
- top: `${touchY}px`,
215
+ left: `${x}px`,
216
+ top: `${y}px`,
197
217
  width: `${size}px`,
198
218
  height: `${size}px`,
199
219
  });
200
220
 
201
- // Finally, append it to DOM
202
221
  container.appendChild(ripple);
203
- button.appendChild(container);
222
+ host.appendChild(container);
223
+ activeRippleRef.current = container;
204
224
 
205
- // rAF runs in the same frame as the event handler
206
- // Use double rAF to ensure the transition class is added in next frame
207
- // This will make sure that the transition animation is triggered
208
225
  requestAnimationFrame(() => {
209
226
  requestAnimationFrame(() => {
210
227
  Object.assign(ripple.style, {
@@ -214,49 +231,80 @@ const TouchableRipple = (
214
231
  });
215
232
  });
216
233
  },
217
- [onPressInProp, disabled, centered, rippleColor],
234
+ [centered, rippleColorResolvedProp, themeRippleFallback, rippleAlpha],
218
235
  );
219
236
 
220
- const handlePressOut = useCallback(
221
- (e: any) => {
222
- onPressOutProp?.(e);
223
-
224
- if (disabled) return;
237
+ const fadeRipple = useCallback((container: HTMLElement | null) => {
238
+ if (!container) return;
239
+ const ripple = container.firstChild as HTMLElement | null;
240
+ if (!ripple) {
241
+ container.parentNode?.removeChild(container);
242
+ return;
243
+ }
244
+
245
+ const onTransitionEnd = (ev: TransitionEvent) => {
246
+ if (ev.propertyName !== 'opacity') return;
247
+ ripple.removeEventListener('transitionend', onTransitionEnd);
248
+ container.parentNode?.removeChild(container);
249
+ };
250
+ ripple.addEventListener('transitionend', onTransitionEnd);
225
251
 
226
- const containers = e.currentTarget.querySelectorAll(
227
- '[data-paper-ripple]',
228
- ) as HTMLElement[];
252
+ Object.assign(ripple.style, {
253
+ transitionDuration: '250ms',
254
+ opacity: '0',
255
+ });
256
+ }, []);
229
257
 
230
- requestAnimationFrame(() => {
231
- requestAnimationFrame(() => {
232
- containers.forEach(container => {
233
- const ripple = container.firstChild as HTMLSpanElement;
258
+ const handlePressIn = useCallback(
259
+ (e: GestureResponderEvent) => {
260
+ onPressInProp?.(e);
261
+ if (disabled) return;
234
262
 
235
- Object.assign(ripple.style, {
236
- transitionDuration: '250ms',
237
- opacity: 0,
238
- });
263
+ const host = e.currentTarget as unknown as HTMLElement | null;
264
+ if (!host || typeof host.appendChild !== 'function') return;
265
+
266
+ const rect = host.getBoundingClientRect();
267
+ let x = rect.width / 2;
268
+ let y = rect.height / 2;
269
+
270
+ if (!centered) {
271
+ const ne: any = e.nativeEvent;
272
+ if (ne) {
273
+ if (typeof ne.locationX === 'number' && typeof ne.locationY === 'number') {
274
+ x = ne.locationX;
275
+ y = ne.locationY;
276
+ } else if (typeof ne.clientX === 'number' && typeof ne.clientY === 'number') {
277
+ x = ne.clientX - rect.left;
278
+ y = ne.clientY - rect.top;
279
+ }
280
+ }
281
+ }
239
282
 
240
- // Finally remove the span after the transition
241
- setTimeout(() => {
242
- const { parentNode } = container;
283
+ startRipple(host, x, y);
284
+ },
285
+ [onPressInProp, disabled, centered, startRipple],
286
+ );
243
287
 
244
- if (parentNode) {
245
- parentNode.removeChild(container);
246
- }
247
- }, 500);
248
- });
249
- });
250
- });
288
+ const handlePressOut = useCallback(
289
+ (e: GestureResponderEvent) => {
290
+ onPressOutProp?.(e);
291
+ const container = activeRippleRef.current;
292
+ activeRippleRef.current = null;
293
+ fadeRipple(container);
251
294
  },
252
- [onPressOutProp, disabled],
295
+ [onPressOutProp, fadeRipple],
253
296
  );
254
297
 
255
- const Component = onPress ? Pressable : View;
298
+ const Component = asChild ? Slot : Pressable;
299
+
300
+ const accessibilityRoleProp = (rest as { accessibilityRole?: unknown }).accessibilityRole;
301
+ const roleProp = (rest as { role?: unknown }).role;
302
+ const applyDefaultWebButtonRole =
303
+ !!onPress && accessibilityRoleProp === undefined && roleProp === undefined;
256
304
 
257
305
  return (
258
306
  <Component
259
- {...(onPress ? { role: 'button' } : {})}
307
+ {...(applyDefaultWebButtonRole ? { role: 'button' } : {})}
260
308
  {...rest}
261
309
  style={containerStyle}
262
310
  ref={ref}
@@ -264,7 +312,7 @@ const TouchableRipple = (
264
312
  onPressIn={handlePressIn}
265
313
  onPressOut={handlePressOut}
266
314
  disabled={disabled}>
267
- {Children.only(children)}
315
+ {children}
268
316
  </Component>
269
317
  );
270
318
  };
@@ -0,0 +1,21 @@
1
+ import setColor from 'color';
2
+
3
+ /** Ripple ink derived from background color for better contrast. */
4
+ export function rippleColorFromBackground(
5
+ backgroundColor: string | undefined,
6
+ fallback: string,
7
+ alpha: number = 0.24,
8
+ ): string {
9
+ if (!backgroundColor || backgroundColor === '') {
10
+ return fallback;
11
+ }
12
+ try {
13
+ const base = setColor(backgroundColor);
14
+ if (base.alpha() < 0.05) {
15
+ return fallback;
16
+ }
17
+ return base.isLight() ? `rgba(0, 0, 0, ${alpha})` : `rgba(255, 255, 255, ${alpha})`;
18
+ } catch {
19
+ return fallback;
20
+ }
21
+ }
package/hocs/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
- export { default as typedMemo } from './typedMemo';
2
1
  export {
3
2
  useCurrentIndexStore,
4
3
  default as withKeyboardAccessibility,
5
4
  } from './withKeyboardAccessibility';
5
+ export { typedMemo } from '@react-native-molecules/utils/hocs';
@@ -1,3 +1,5 @@
1
+ import { createFastContext } from '@react-native-molecules/utils/fast-context';
2
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
3
  import {
2
4
  type ComponentType,
3
5
  forwardRef,
@@ -15,9 +17,6 @@ import type { FlatList } from 'react-native';
15
17
  import type { SectionList } from 'react-native';
16
18
  import { Platform } from 'react-native';
17
19
 
18
- import { createFastContext } from '../fast-context';
19
- import useLatest from '../hooks/useLatest';
20
-
21
20
  export type Store = {
22
21
  currentIndex: number | null;
23
22
  };
@@ -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
@@ -4,7 +4,6 @@ export * from './useActionState';
4
4
  export * from './useActive';
5
5
  export { default as useColorMode } from './useColorMode';
6
6
  export { useContrastColor } from './useContrastColor';
7
- export { default as useControlledValue } from './useControlledValue';
8
7
  export * from './useFocus';
9
8
  export {
10
9
  type NumberMaskConfig,
@@ -13,16 +12,7 @@ export {
13
12
  } from './useHandleNumberFormat';
14
13
  export * from './useHover';
15
14
  export * from './useKeyboardDismissable';
16
- export { default as useLatest } from './useLatest';
17
- export { useMediaQuery } from './useMediaQuery';
18
- export { useMergedRefs } from './useMergedRefs';
19
- export { default as usePrevious } from './usePrevious';
15
+ export * from './useMediaQuery';
20
16
  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
17
  export * from './useTheme';
28
- export { default as useToggle } from './useToggle';
18
+ export * from '@react-native-molecules/utils/hooks';
@@ -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,8 +1,7 @@
1
+ import { resolveContrastColor } from '@react-native-molecules/utils/helpers/resolveContrastColor';
1
2
  import { useMemo } from 'react';
2
3
  import { useUnistyles } from 'react-native-unistyles';
3
4
 
4
- import { resolveContrastColor } from '../utils/color';
5
-
6
5
  export const useContrastColor = (bgColor: string, lightColor?: string, darkColor?: string) => {
7
6
  const isDarkMode = useUnistyles().theme.dark;
8
7
 
@@ -1,3 +1,4 @@
1
+ import { isNil, omitBy } from '@react-native-molecules/utils/helpers/lodash';
1
2
  import { useCallback } from 'react';
2
3
 
3
4
  import {
@@ -5,17 +6,12 @@ import {
5
6
  type DocumentPickerOptions,
6
7
  type DocumentResult,
7
8
  } from '../utils/DocumentPicker';
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 };
@@ -1,9 +1,9 @@
1
+ import { isNil } from '@react-native-molecules/utils/helpers/lodash';
2
+ import { usePrevious } from '@react-native-molecules/utils/hooks';
1
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
4
  import type { NativeSyntheticEvent, TextInputFocusEventData } from 'react-native';
3
5
 
4
6
  import { formatNumberWithMask, normalizeToNumberString } from '../utils';
5
- import { isNil } from '../utils/lodash';
6
- import usePrevious from './usePrevious';
7
7
 
8
8
  export type NumberMaskConfig = {
9
9
  prefix?: string;
@@ -1,8 +1,7 @@
1
+ import { isNil } from '@react-native-molecules/utils/helpers/lodash';
1
2
  import { useMemo } from 'react';
2
3
  import { useWindowDimensions } from 'react-native';
3
4
 
4
- import { isNil } from '../utils/lodash';
5
-
6
5
  type Query = {
7
6
  maxWidth?: number;
8
7
  minWidth?: number;