react-native-molecules 0.5.0-beta.1 → 0.5.0-beta.10

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 (152) hide show
  1. package/README.md +87 -0
  2. package/components/Accordion/index.tsx +1 -6
  3. package/components/Accordion/utils.ts +17 -14
  4. package/components/ActivityIndicator/ActivityIndicator.tsx +12 -20
  5. package/components/ActivityIndicator/index.tsx +1 -5
  6. package/components/Appbar/index.tsx +1 -4
  7. package/components/Appbar/utils.ts +33 -21
  8. package/components/Avatar/index.tsx +1 -5
  9. package/components/Avatar/utils.ts +2 -6
  10. package/components/Backdrop/Backdrop.tsx +2 -2
  11. package/components/Backdrop/index.tsx +1 -5
  12. package/components/Backdrop/utils.ts +5 -6
  13. package/components/Badge/index.tsx +1 -5
  14. package/components/Badge/utils.ts +2 -6
  15. package/components/Button/Button.tsx +211 -264
  16. package/components/Button/index.tsx +9 -7
  17. package/components/Button/types.ts +16 -2
  18. package/components/Button/utils.ts +231 -210
  19. package/components/Card/Card.tsx +8 -4
  20. package/components/Card/CardContent.tsx +5 -4
  21. package/components/Card/CardHeader.tsx +5 -3
  22. package/components/Card/CardMedia.tsx +5 -3
  23. package/components/Card/CardTypography.tsx +5 -3
  24. package/components/Card/index.tsx +1 -5
  25. package/components/Card/utils.ts +5 -6
  26. package/components/Checkbox/Checkbox.tsx +1 -0
  27. package/components/Checkbox/CheckboxBase.ios.tsx +1 -0
  28. package/components/Checkbox/CheckboxBase.tsx +1 -0
  29. package/components/Checkbox/index.tsx +1 -5
  30. package/components/Checkbox/utils.ts +6 -6
  31. package/components/Chip/Chip.tsx +40 -52
  32. package/components/Chip/index.tsx +1 -5
  33. package/components/Chip/utils.ts +5 -13
  34. package/components/DatePickerDocked/index.tsx +1 -5
  35. package/components/DatePickerDocked/utils.ts +21 -19
  36. package/components/DatePickerInline/index.tsx +1 -5
  37. package/components/DatePickerInline/utils.ts +41 -28
  38. package/components/DatePickerInput/index.tsx +1 -5
  39. package/components/DatePickerInput/utils.ts +5 -6
  40. package/components/DatePickerModal/DatePickerModalHeader.tsx +1 -1
  41. package/components/DatePickerModal/index.tsx +1 -5
  42. package/components/DatePickerModal/utils.ts +17 -16
  43. package/components/DateTimePicker/index.tsx +1 -5
  44. package/components/DateTimePicker/utils.ts +5 -6
  45. package/components/Dialog/index.tsx +1 -5
  46. package/components/Dialog/utils.ts +22 -16
  47. package/components/Drawer/Collapsible/utils.ts +13 -13
  48. package/components/Drawer/Drawer.tsx +2 -3
  49. package/components/Drawer/DrawerContent.tsx +5 -3
  50. package/components/Drawer/DrawerFooter.tsx +5 -4
  51. package/components/Drawer/DrawerHeader.tsx +5 -4
  52. package/components/Drawer/DrawerItem.tsx +5 -3
  53. package/components/Drawer/DrawerItemGroup.tsx +5 -4
  54. package/components/Drawer/index.tsx +1 -5
  55. package/components/Drawer/utils.ts +7 -7
  56. package/components/ElementGroup/ElementGroup.tsx +16 -14
  57. package/components/ElementGroup/index.tsx +1 -5
  58. package/components/ElementGroup/utils.ts +5 -6
  59. package/components/FAB/index.tsx +1 -5
  60. package/components/FAB/utils.ts +2 -6
  61. package/components/FilePicker/index.tsx +1 -5
  62. package/components/FilePicker/utils.ts +5 -6
  63. package/components/HelperText/index.tsx +1 -5
  64. package/components/HelperText/utils.ts +5 -7
  65. package/components/HorizontalDivider/HorizontalDivider.tsx +5 -3
  66. package/components/HorizontalDivider/index.tsx +1 -5
  67. package/components/Icon/CrossFadeIcon.tsx +3 -5
  68. package/components/Icon/Icon.tsx +2 -4
  69. package/components/Icon/iconFactory.tsx +3 -3
  70. package/components/Icon/index.tsx +2 -6
  71. package/components/Icon/types.ts +17 -6
  72. package/components/IconButton/IconButton.tsx +45 -58
  73. package/components/IconButton/index.tsx +1 -5
  74. package/components/IconButton/utils.ts +15 -26
  75. package/components/If/index.tsx +1 -5
  76. package/components/InputAddon/index.tsx +1 -5
  77. package/components/InputAddon/utils.ts +5 -6
  78. package/components/Link/index.tsx +1 -5
  79. package/components/Link/utils.ts +2 -6
  80. package/components/ListItem/index.tsx +1 -5
  81. package/components/ListItem/utils.ts +13 -11
  82. package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
  83. package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
  84. package/components/LoadingIndicator/index.tsx +13 -0
  85. package/components/LoadingIndicator/utils.ts +117 -0
  86. package/components/Menu/index.tsx +1 -5
  87. package/components/Menu/utils.ts +6 -8
  88. package/components/Modal/index.tsx +1 -5
  89. package/components/Modal/utils.ts +2 -6
  90. package/components/NavigationRail/NavigationRailHeader.tsx +1 -1
  91. package/components/NavigationRail/index.tsx +1 -5
  92. package/components/NavigationRail/utils.ts +21 -17
  93. package/components/NavigationStack/index.tsx +1 -5
  94. package/components/NavigationStack/utils.tsx +7 -1
  95. package/components/Portal/index.tsx +1 -5
  96. package/components/RadioButton/index.ts +1 -5
  97. package/components/RadioButton/utils.ts +9 -8
  98. package/components/Rating/index.tsx +1 -5
  99. package/components/Rating/utils.ts +6 -8
  100. package/components/Select/Select.tsx +360 -501
  101. package/components/Select/index.ts +7 -14
  102. package/components/Select/types.ts +2 -4
  103. package/components/Select/utils.ts +215 -0
  104. package/components/Slot/Slot.tsx +244 -0
  105. package/components/Slot/compose-refs.tsx +60 -0
  106. package/components/Slot/index.tsx +8 -0
  107. package/components/StateLayer/index.tsx +1 -5
  108. package/components/StateLayer/utils.ts +5 -6
  109. package/components/Surface/Surface.android.tsx +34 -8
  110. package/components/Surface/Surface.ios.tsx +36 -29
  111. package/components/Surface/Surface.tsx +31 -4
  112. package/components/Surface/index.tsx +1 -5
  113. package/components/Surface/utils.ts +49 -36
  114. package/components/Switch/Switch.tsx +8 -2
  115. package/components/Switch/index.tsx +1 -5
  116. package/components/Switch/utils.ts +2 -6
  117. package/components/Tabs/index.tsx +1 -5
  118. package/components/Tabs/utils.ts +10 -10
  119. package/components/Text/Text.tsx +2 -8
  120. package/components/TextInput/TextInput.tsx +5 -4
  121. package/components/TextInput/index.tsx +1 -5
  122. package/components/TextInput/utils.ts +8 -15
  123. package/components/TextInputWithMask/index.tsx +1 -5
  124. package/components/TimePicker/AmPmSwitcher.tsx +1 -1
  125. package/components/TimePicker/index.tsx +1 -5
  126. package/components/TimePicker/utils.ts +29 -21
  127. package/components/TimePickerField/index.tsx +1 -5
  128. package/components/TimePickerField/utils.ts +5 -6
  129. package/components/TimePickerModal/TimePickerModal.tsx +6 -2
  130. package/components/TimePickerModal/index.tsx +1 -5
  131. package/components/TimePickerModal/utils.ts +5 -6
  132. package/components/Tooltip/TooltipTrigger.tsx +25 -16
  133. package/components/Tooltip/index.tsx +1 -5
  134. package/components/Tooltip/utils.ts +5 -6
  135. package/components/TouchableRipple/TouchableRipple.native.tsx +49 -13
  136. package/components/TouchableRipple/TouchableRipple.tsx +136 -46
  137. package/components/TouchableRipple/index.tsx +1 -5
  138. package/components/TouchableRipple/utils.ts +5 -6
  139. package/components/VerticalDivider/VerticalDivider.tsx +9 -8
  140. package/components/VerticalDivider/index.tsx +1 -5
  141. package/core/componentsRegistry.ts +31 -19
  142. package/hocs/withPortal.tsx +1 -1
  143. package/hooks/useControlledValue.tsx +20 -4
  144. package/hooks/useSubcomponents.tsx +56 -22
  145. package/hooks/useWhatHasUpdated.tsx +48 -0
  146. package/package.json +10 -13
  147. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +5 -2
  148. package/styles/shadow.ts +2 -1
  149. package/styles/themes/LightTheme.tsx +1 -1
  150. package/utils/extractPropertiesFromStyles.ts +25 -0
  151. package/utils/lodash.ts +77 -6
  152. package/utils/repository.ts +2 -52
@@ -103,8 +103,12 @@ export function TimePickerModal({
103
103
  accessibilityLabel="toggle keyboard"
104
104
  />
105
105
  <View style={styles.fill} />
106
- <Button onPress={onClose}>{cancelLabel}</Button>
107
- <Button onPress={onConfirm}>{confirmLabel}</Button>
106
+ <Button onPress={onClose}>
107
+ <Button.Text>{cancelLabel}</Button.Text>
108
+ </Button>
109
+ <Button onPress={onConfirm}>
110
+ <Button.Text>{confirmLabel}</Button.Text>
111
+ </Button>
108
112
  </View>
109
113
  </KeyboardAvoidingView>
110
114
  </Modal>
@@ -1,10 +1,6 @@
1
- import { getRegisteredComponentWithFallback, registerMoleculesComponents } from '../../core';
1
+ import { getRegisteredComponentWithFallback } from '../../core';
2
2
  import TimePickerModalDefault from './TimePickerModal';
3
3
 
4
- registerMoleculesComponents({
5
- TimePickerModal: TimePickerModalDefault,
6
- });
7
-
8
4
  export const TimePickerModal = getRegisteredComponentWithFallback(
9
5
  'TimePickerModal',
10
6
  TimePickerModalDefault,
@@ -1,6 +1,6 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
2
 
3
- import { getRegisteredMoleculesComponentStyles, registerComponentsStyles } from '../../core';
3
+ import { getRegisteredComponentStylesWithFallback } from '../../core';
4
4
 
5
5
  const timepickerModalStylesDefault = StyleSheet.create(theme => ({
6
6
  keyboardView: {
@@ -41,8 +41,7 @@ const timepickerModalStylesDefault = StyleSheet.create(theme => ({
41
41
  fill: { flex: 1 },
42
42
  }));
43
43
 
44
- registerComponentsStyles({
45
- TimePickerModal: timepickerModalStylesDefault,
46
- });
47
-
48
- export const styles = getRegisteredMoleculesComponentStyles('TimePickerModal');
44
+ export const styles = getRegisteredComponentStylesWithFallback(
45
+ 'TimePickerModal',
46
+ timepickerModalStylesDefault,
47
+ );
@@ -30,27 +30,36 @@ const TooltipTrigger = memo(({ children }: { children: ReactElement }) => {
30
30
  () => triggerRef?.current,
31
31
  );
32
32
 
33
- const onPress = useCallback(() => {
34
- // @ts-ignore
35
- children?.props?.onPress?.();
36
- }, [children?.props]);
33
+ const onPress = useCallback(
34
+ (e: unknown) => {
35
+ // @ts-ignore
36
+ children?.props?.onPress?.(e);
37
+ },
38
+ [children?.props],
39
+ );
37
40
 
38
- const onLongPress = useCallback(() => {
39
- // @ts-ignore
40
- children?.props?.onLongPress?.();
41
+ const onLongPress = useCallback(
42
+ (e: unknown) => {
43
+ // @ts-ignore
44
+ children?.props?.onLongPress?.(e);
41
45
 
42
- if (isWeb) return;
43
- onOpen();
44
- }, [children?.props, isWeb, onOpen]);
46
+ if (isWeb) return;
47
+ onOpen();
48
+ },
49
+ [children?.props, isWeb, onOpen],
50
+ );
45
51
 
46
- const onPressOut = useCallback(() => {
47
- // @ts-ignore
52
+ const onPressOut = useCallback(
53
+ (e: unknown) => {
54
+ // @ts-ignore
48
55
 
49
- children?.props?.onPressOut?.();
56
+ children?.props?.onPressOut?.(e);
50
57
 
51
- if (isWeb) return;
52
- onClose();
53
- }, [children?.props, isWeb, onClose]);
58
+ if (isWeb) return;
59
+ onClose();
60
+ },
61
+ [children?.props, isWeb, onClose],
62
+ );
54
63
 
55
64
  const onHoverIn = useCallback(() => {
56
65
  // @ts-ignore
@@ -1,4 +1,4 @@
1
- import { getRegisteredComponentWithFallback, registerMoleculesComponents } from '../../core';
1
+ import { getRegisteredComponentWithFallback } from '../../core';
2
2
  import TooltipComponent from './Tooltip';
3
3
  import TooltipContent from './TooltipContent';
4
4
  import TooltipTrigger from './TooltipTrigger';
@@ -8,10 +8,6 @@ export const TooltipDefault = Object.assign(TooltipComponent, {
8
8
  Content: TooltipContent,
9
9
  });
10
10
 
11
- registerMoleculesComponents({
12
- Tooltip: TooltipDefault,
13
- });
14
-
15
11
  export const Tooltip = getRegisteredComponentWithFallback('Tooltip', TooltipDefault);
16
12
 
17
13
  export type { Props as TooltipProps } from './Tooltip';
@@ -1,6 +1,6 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
2
 
3
- import { getRegisteredMoleculesComponentStyles, registerComponentsStyles } from '../../core';
3
+ import { getRegisteredComponentStylesWithFallback } from '../../core';
4
4
 
5
5
  const tooltipStylesDefault = StyleSheet.create(theme => ({
6
6
  content: {
@@ -14,8 +14,7 @@ const tooltipStylesDefault = StyleSheet.create(theme => ({
14
14
  },
15
15
  }));
16
16
 
17
- registerComponentsStyles({
18
- Tooltip: tooltipStylesDefault,
19
- });
20
-
21
- export const tooltipStyles = getRegisteredMoleculesComponentStyles('Tooltip');
17
+ export const tooltipStyles = getRegisteredComponentStylesWithFallback(
18
+ 'Tooltip',
19
+ tooltipStylesDefault,
20
+ );
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, memo, useMemo } from 'react';
1
+ import { type ComponentProps, forwardRef, memo, type ReactNode, useMemo } from 'react';
2
2
  import {
3
3
  type BackgroundPropType,
4
4
  Platform,
@@ -7,25 +7,44 @@ import {
7
7
  StyleSheet,
8
8
  TouchableNativeFeedback,
9
9
  TouchableWithoutFeedback,
10
- View,
11
10
  type ViewStyle,
12
11
  } from 'react-native';
13
- import { withUnistyles } from 'react-native-unistyles';
14
12
 
13
+ import { extractPropertiesFromStyles } from '../../utils/extractPropertiesFromStyles';
14
+ import { Slot } from '../Slot';
15
15
  import { touchableRippleStyles } from './utils';
16
16
 
17
17
  const ANDROID_VERSION_LOLLIPOP = 21;
18
18
  const ANDROID_VERSION_PIE = 28;
19
19
 
20
- type Props = React.ComponentProps<typeof TouchableWithoutFeedback> & {
20
+ type Props = ComponentProps<typeof TouchableWithoutFeedback> & {
21
21
  borderless?: boolean;
22
22
  background?: BackgroundPropType;
23
23
  disabled?: boolean;
24
24
  onPress?: () => void | null;
25
25
  rippleColor?: string;
26
26
  underlayColor?: string;
27
- children: React.ReactNode;
27
+ children: ReactNode;
28
28
  style?: StyleProp<ViewStyle>;
29
+ /**
30
+ * When `true`, the component will not render a wrapper element. Instead, it will
31
+ * merge its props (styles, event handlers, ref) onto its immediate child element.
32
+ * This follows the Radix UI "Slot" pattern for flexible component composition.
33
+ *
34
+ * @note On Android, the native ripple effect will NOT work when `asChild` is `true`
35
+ * because `TouchableNativeFeedback` requires a View wrapper. Only press events will work.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * <TouchableRipple asChild onPress={handlePress}>
40
+ * <View><Text>Custom pressable</Text></View>
41
+ * </TouchableRipple>
42
+ * ```
43
+ *
44
+ * @note When `asChild` is `true`, only a single child element is allowed.
45
+ * @default false
46
+ */
47
+ asChild?: boolean;
29
48
  };
30
49
 
31
50
  const TouchableRipple = (
@@ -37,6 +56,7 @@ const TouchableRipple = (
37
56
  rippleColor: rippleColorProp,
38
57
  underlayColor: underlayColorProp,
39
58
  children,
59
+ asChild = false,
40
60
  ...rest
41
61
  }: Props,
42
62
  ref: any,
@@ -46,8 +66,12 @@ const TouchableRipple = (
46
66
  const componentStyles = touchableRippleStyles;
47
67
 
48
68
  const { rippleColor, underlayColor, containerStyle } = useMemo(() => {
69
+ const { rippleColor: _rippleColor } = extractPropertiesFromStyles(
70
+ [componentStyles.root, style],
71
+ ['rippleColor'],
72
+ );
49
73
  return {
50
- rippleColor: rippleColorProp,
74
+ rippleColor: rippleColorProp || _rippleColor,
51
75
  underlayColor: underlayColorProp || rippleColorProp,
52
76
  containerStyle: [borderless && styles.borderless, componentStyles.root, style],
53
77
  };
@@ -58,6 +82,21 @@ const TouchableRipple = (
58
82
  const useForeground =
59
83
  Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_PIE && borderless;
60
84
 
85
+ if (asChild) {
86
+ // When asChild is true, use Slot to merge props with the child
87
+ // Note: TouchableNativeFeedback ripple won't work with asChild since it requires a View wrapper
88
+ return (
89
+ <Slot
90
+ {...rest}
91
+ style={containerStyle}
92
+ ref={ref}
93
+ onPress={rest.onPress}
94
+ disabled={disabled}>
95
+ {children}
96
+ </Slot>
97
+ );
98
+ }
99
+
61
100
  if (TouchableRipple.supported) {
62
101
  return (
63
102
  <TouchableNativeFeedback
@@ -65,12 +104,13 @@ const TouchableRipple = (
65
104
  ref={ref}
66
105
  disabled={disabled}
67
106
  useForeground={useForeground}
107
+ style={containerStyle}
68
108
  background={
69
109
  background != null
70
110
  ? background
71
111
  : TouchableNativeFeedback.Ripple(rippleColor!, borderless)
72
112
  }>
73
- <View style={containerStyle}>{React.Children.only(children)}</View>
113
+ <>{children}</>
74
114
  </TouchableNativeFeedback>
75
115
  );
76
116
  }
@@ -84,7 +124,7 @@ const TouchableRipple = (
84
124
  containerStyle,
85
125
  pressed && { backgroundColor: underlayColor },
86
126
  ]}>
87
- {React.Children.only(children)}
127
+ {children}
88
128
  </Pressable>
89
129
  );
90
130
  };
@@ -98,8 +138,4 @@ const styles = StyleSheet.create({
98
138
  TouchableRipple.supported =
99
139
  Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
100
140
 
101
- export default memo(
102
- withUnistyles(forwardRef(TouchableRipple), theme => ({
103
- rippleColor: theme.colors.onSurfaceRipple,
104
- })),
105
- );
141
+ export default memo(forwardRef(TouchableRipple));
@@ -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,6 +119,7 @@ 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,
@@ -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
  };
@@ -1,10 +1,6 @@
1
- import { getRegisteredComponentWithFallback, registerMoleculesComponents } from '../../core';
1
+ import { getRegisteredComponentWithFallback } from '../../core';
2
2
  import TouchableRippleDefault from './TouchableRipple';
3
3
 
4
- registerMoleculesComponents({
5
- TouchableRipple: TouchableRippleDefault,
6
- });
7
-
8
4
  export const TouchableRipple = getRegisteredComponentWithFallback(
9
5
  'TouchableRipple',
10
6
  TouchableRippleDefault,
@@ -1,6 +1,6 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
2
 
3
- import { getRegisteredMoleculesComponentStyles, registerComponentsStyles } from '../../core';
3
+ import { getRegisteredComponentStylesWithFallback } from '../../core';
4
4
 
5
5
  const touchableRippleStylesDefault = StyleSheet.create(theme => ({
6
6
  root: {
@@ -8,8 +8,7 @@ const touchableRippleStylesDefault = StyleSheet.create(theme => ({
8
8
  } as any,
9
9
  }));
10
10
 
11
- registerComponentsStyles({
12
- TouchableRipple: touchableRippleStylesDefault,
13
- });
14
-
15
- export const touchableRippleStyles = getRegisteredMoleculesComponentStyles('TouchableRipple');
11
+ export const touchableRippleStyles = getRegisteredComponentStylesWithFallback(
12
+ 'TouchableRipple',
13
+ touchableRippleStylesDefault,
14
+ );
@@ -1,8 +1,8 @@
1
1
  import { memo } from 'react';
2
- import { type StyleProp, View, type ViewProps, type ViewStyle } from 'react-native';
2
+ import { View, type ViewProps } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
 
5
- import { getRegisteredMoleculesComponentStyles, registerComponentStyles } from '../../core';
5
+ import { getRegisteredComponentStylesWithFallback } from '../../core';
6
6
 
7
7
  export type Props = Omit<ViewProps, 'children'> & {
8
8
  /**
@@ -21,7 +21,6 @@ export type Props = Omit<ViewProps, 'children'> & {
21
21
  * Horizontal spacing of the Divider
22
22
  */
23
23
  spacing?: number;
24
- style?: StyleProp<ViewStyle>;
25
24
  };
26
25
 
27
26
  /**
@@ -70,9 +69,9 @@ const VerticalDivider = ({
70
69
  style={[
71
70
  verticalDividerStyles.root,
72
71
  style,
73
- topInset && { marginTop: topInset },
74
- bottomInset && { marginBottom: bottomInset },
75
- spacing && { marginHorizontal: spacing },
72
+ topInset ? { marginTop: topInset } : undefined,
73
+ bottomInset ? { marginBottom: bottomInset } : undefined,
74
+ spacing ? { marginHorizontal: spacing } : undefined,
76
75
  ]}
77
76
  />
78
77
  );
@@ -93,7 +92,9 @@ export const verticalDividerStylesDefault = StyleSheet.create(theme => ({
93
92
  },
94
93
  }));
95
94
 
96
- registerComponentStyles('VerticalDivider', verticalDividerStylesDefault);
97
- export const verticalDividerStyles = getRegisteredMoleculesComponentStyles('HorizontalDivider');
95
+ export const verticalDividerStyles = getRegisteredComponentStylesWithFallback(
96
+ 'VerticalDivider',
97
+ verticalDividerStylesDefault,
98
+ );
98
99
 
99
100
  export default memo(VerticalDivider);
@@ -1,10 +1,6 @@
1
- import { getRegisteredComponentWithFallback, registerMoleculesComponents } from '../../core';
1
+ import { getRegisteredComponentWithFallback } from '../../core';
2
2
  import VerticalDividerDefault from './VerticalDivider';
3
3
 
4
- registerMoleculesComponents({
5
- VerticalDivider: VerticalDividerDefault,
6
- });
7
-
8
4
  export const VerticalDivider = getRegisteredComponentWithFallback(
9
5
  'VerticalDivider',
10
6
  VerticalDividerDefault,