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

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 +224 -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 +21 -24
  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,17 +1,35 @@
1
- import { Fragment, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
2
- import { Pressable, View } from 'react-native';
3
- import { ScopedTheme, StyleSheet, UnistylesRuntime } from 'react-native-unistyles';
4
-
1
+ import {
2
+ cloneElement,
3
+ Fragment,
4
+ memo,
5
+ type ReactElement,
6
+ type ReactNode,
7
+ type Ref,
8
+ useContext,
9
+ useMemo,
10
+ useRef,
11
+ } from 'react';
12
+ import { Pressable, type PressableProps, type StyleProp, View, type ViewStyle } from 'react-native';
13
+ import { ScopedTheme, UnistylesRuntime } from 'react-native-unistyles';
14
+
15
+ import { mergeRefs } from '../../utils';
5
16
  import { Portal } from '../Portal';
6
17
  import {
7
18
  DEFAULT_ARROW_SIZE,
8
- popoverDefaultStyles,
19
+ PopoverContext,
20
+ PopoverPanelContext,
21
+ type PopoverPanelContextValue,
9
22
  type PopoverProps,
10
23
  useArrowStyles,
11
24
  usePopover,
12
25
  } from './common';
26
+ import { createPopoverRoot } from './PopoverRoot';
27
+ import { usePlatformMeasure } from './usePlatformMeasure';
28
+ import { popoverStyles } from './utils';
29
+
30
+ type PopoverPanelProps = PopoverProps & { backdrop?: ReactNode };
13
31
 
14
- const Popover = ({
32
+ const PopoverPanel = ({
15
33
  triggerRef,
16
34
  children,
17
35
  isOpen,
@@ -19,17 +37,15 @@ const Popover = ({
19
37
  position = 'bottom',
20
38
  align = 'center',
21
39
  style,
22
- showArrow = false,
23
- arrowSize = DEFAULT_ARROW_SIZE,
24
40
  inverted = false,
25
41
  // @ts-ignore
26
42
  dataSet,
27
- withBackdropDismiss = false,
28
43
  offset = 8,
29
- backdropStyles,
44
+ horizontalOffset = 0,
30
45
  triggerDimensions,
46
+ backdrop,
31
47
  ...rest
32
- }: PopoverProps) => {
48
+ }: PopoverPanelProps) => {
33
49
  const {
34
50
  popoverLayoutRef,
35
51
  targetLayoutRef,
@@ -37,111 +53,33 @@ const Popover = ({
37
53
  calculatedPosition,
38
54
  calculateAndSetPosition,
39
55
  handlePopoverLayout,
40
- } = usePopover({
41
- isOpen,
42
- position,
43
- align,
44
- showArrow,
45
- arrowSize,
46
- offset,
47
- });
56
+ } = usePopover({ isOpen, position, align, offset, horizontalOffset });
48
57
 
49
58
  const popoverRef = useRef<View>(null);
59
+ const hasBackdrop = !!backdrop;
50
60
 
51
- const measureTarget = useCallback(() => {
52
- if (triggerRef.current) {
53
- triggerRef.current.measureInWindow(
54
- (x: number, y: number, width: number, height: number) => {
55
- if (width !== 0 || height !== 0) {
56
- const newLayout = { x, y, width, height };
57
- const changed =
58
- !targetLayoutRef.current ||
59
- targetLayoutRef.current.x !== newLayout.x ||
60
- targetLayoutRef.current.y !== newLayout.y ||
61
- targetLayoutRef.current.width !== newLayout.width ||
62
- targetLayoutRef.current.height !== newLayout.height;
63
-
64
- if (changed) {
65
- targetLayoutRef.current = newLayout;
66
- calculateAndSetPosition();
67
- }
68
- } else {
69
- targetLayoutRef.current = null;
70
- calculateAndSetPosition();
71
- }
72
- },
73
- );
74
- } else {
75
- targetLayoutRef.current = null;
76
- calculateAndSetPosition();
77
- }
78
- }, [triggerRef, calculateAndSetPosition, targetLayoutRef]);
79
-
80
- useLayoutEffect(() => {
81
- if (isOpen) {
82
- const timeoutId = setTimeout(measureTarget, 0);
83
- return () => clearTimeout(timeoutId);
84
- }
85
- return;
86
- }, [isOpen, measureTarget, triggerDimensions]);
87
-
88
- useLayoutEffect(() => {
89
- if (!isOpen) return;
90
- const handleResize = () => {
91
- if (triggerRef.current && isOpen) {
92
- window.requestAnimationFrame(measureTarget);
93
- }
94
- };
95
- window.addEventListener('resize', handleResize);
96
- window.addEventListener('scroll', handleResize, true);
97
- return () => {
98
- window.removeEventListener('resize', handleResize);
99
- window.removeEventListener('scroll', handleResize, true);
100
- };
101
- }, [isOpen, measureTarget, triggerRef]);
102
-
103
- useEffect(() => {
104
- if (!isOpen || !onClose || withBackdropDismiss) return;
105
- const handleClickOutside = (event: MouseEvent) => {
106
- const popoverElement = popoverRef.current as any as HTMLElement;
107
- const targetElement = triggerRef.current as any as HTMLElement;
108
- if (
109
- popoverElement &&
110
- !popoverElement.contains(event.target as Node) &&
111
- targetElement &&
112
- !targetElement.contains(event.target as Node)
113
- ) {
114
- onClose();
115
- }
116
- };
117
- document.addEventListener('mousedown', handleClickOutside, { capture: true });
118
- return () => {
119
- document.removeEventListener('mousedown', handleClickOutside, { capture: true });
120
- };
121
- }, [isOpen, onClose, popoverRef, triggerRef, withBackdropDismiss]);
122
-
123
- const arrowStyles = useArrowStyles({
124
- showArrow,
125
- arrowSize,
126
- style,
61
+ const { popoverStyle } = usePlatformMeasure({
62
+ triggerRef,
63
+ isOpen,
64
+ onClose,
65
+ dismissOnClickOutside: !hasBackdrop,
127
66
  calculatedPosition,
67
+ calculateAndSetPosition,
128
68
  targetLayoutRef,
129
- popoverLayoutRef,
130
- actualPositionRef,
69
+ popoverRef,
70
+ triggerDimensions,
131
71
  });
132
72
 
133
- const popoverStyle = useMemo(() => {
134
- if (!calculatedPosition) return popoverDefaultStyles;
135
-
136
- const scrollX = window.scrollX ?? window.pageXOffset ?? 0;
137
- const scrollY = window.scrollY ?? window.pageYOffset ?? 0;
138
-
139
- return {
140
- ...calculatedPosition,
141
- left: (calculatedPosition.left as number) + scrollX,
142
- top: (calculatedPosition.top as number) + scrollY,
143
- };
144
- }, [calculatedPosition]);
73
+ const panelContextValue = useMemo<PopoverPanelContextValue>(
74
+ () => ({
75
+ calculatedPosition,
76
+ targetLayoutRef,
77
+ popoverLayoutRef,
78
+ actualPositionRef,
79
+ containerStyle: style,
80
+ }),
81
+ [calculatedPosition, targetLayoutRef, popoverLayoutRef, actualPositionRef, style],
82
+ );
145
83
 
146
84
  const Wrapper = inverted ? ScopedTheme : Fragment;
147
85
  const WrapperProps = inverted
@@ -154,45 +92,81 @@ const Popover = ({
154
92
 
155
93
  return (
156
94
  <Portal>
157
- <Wrapper {...(WrapperProps as any)}>
158
- {withBackdropDismiss && (
159
- <Pressable style={[styles.backdrop, backdropStyles]} onPress={onClose} />
160
- )}
161
- <View
162
- onLayout={handlePopoverLayout}
163
- style={[styles.popoverContainer, style, popoverStyle]}
164
- {...{ dataSet }}
165
- {...rest}
166
- ref={popoverRef}>
167
- {children}
168
- {showArrow && popoverStyle.opacity === 1 && <View style={arrowStyles} />}
169
- </View>
170
- </Wrapper>
95
+ <PopoverPanelContext value={panelContextValue}>
96
+ {backdrop}
97
+ <Wrapper {...(WrapperProps as any)}>
98
+ <View
99
+ onLayout={handlePopoverLayout}
100
+ style={[popoverStyles.popoverContainer, style, popoverStyle]}
101
+ {...{ dataSet }}
102
+ {...rest}
103
+ ref={popoverRef}>
104
+ {children}
105
+ </View>
106
+ </Wrapper>
107
+ </PopoverPanelContext>
171
108
  </Portal>
172
109
  );
173
110
  };
174
111
 
175
- const styles = StyleSheet.create(theme => ({
176
- popoverContainer: {
177
- ...popoverDefaultStyles,
178
- backgroundColor: theme.colors.surface,
179
- borderRadius: 4,
180
- shadowColor: 'rgba(0, 0, 0, 1)',
181
- shadowOffset: { width: 0, height: 2 },
182
- shadowOpacity: theme.dark ? 0.7 : 0.3,
183
- shadowRadius: 10,
184
- zIndex: 100,
185
- },
186
- backdrop: {
187
- position: 'absolute',
188
- top: 0,
189
- left: 0,
190
- right: 0,
191
- bottom: 0,
192
- _web: {
193
- cursor: 'default',
194
- },
112
+ type PopoverTriggerProps = {
113
+ children: ReactElement;
114
+ ref?: Ref<any>;
115
+ triggerRef?: Ref<any>;
116
+ };
117
+
118
+ export const PopoverTrigger = memo(
119
+ ({ children, ref: refProp, triggerRef: triggerRefProp }: PopoverTriggerProps) => {
120
+ const { triggerRef } = useContext(PopoverContext);
121
+ const mergedRef = useMemo(
122
+ () => mergeRefs([triggerRef, refProp, triggerRefProp]),
123
+ [triggerRef, refProp, triggerRefProp],
124
+ );
125
+ return cloneElement(children as ReactElement<{ ref?: unknown }>, { ref: mergedRef });
195
126
  },
196
- }));
127
+ );
128
+ PopoverTrigger.displayName = 'Popover_Trigger';
129
+
130
+ export const PopoverBackdrop = memo(({ style, onPress, ...rest }: PressableProps) => {
131
+ const { isOpen, onClose } = useContext(PopoverContext);
132
+ if (!isOpen) return null;
133
+ return (
134
+ <Pressable
135
+ {...rest}
136
+ onPress={onPress ?? onClose}
137
+ style={[popoverStyles.overlay, style as StyleProp<ViewStyle>]}
138
+ />
139
+ );
140
+ });
141
+ PopoverBackdrop.displayName = 'Popover_Backdrop';
142
+
143
+ type PopoverArrowProps = {
144
+ size?: number;
145
+ style?: StyleProp<ViewStyle>;
146
+ };
147
+
148
+ export const PopoverArrow = memo(({ size = DEFAULT_ARROW_SIZE, style }: PopoverArrowProps) => {
149
+ const {
150
+ calculatedPosition,
151
+ targetLayoutRef,
152
+ popoverLayoutRef,
153
+ actualPositionRef,
154
+ containerStyle,
155
+ } = useContext(PopoverPanelContext);
156
+
157
+ const arrowStyles = useArrowStyles({
158
+ arrowSize: size,
159
+ containerStyle,
160
+ calculatedPosition,
161
+ targetLayoutRef,
162
+ popoverLayoutRef,
163
+ actualPositionRef,
164
+ });
165
+
166
+ if (!arrowStyles || Object.keys(arrowStyles).length === 0) return null;
167
+ return <View style={[arrowStyles, style]} />;
168
+ });
169
+
170
+ PopoverArrow.displayName = 'Popover_Arrow';
197
171
 
198
- export default memo(Popover);
172
+ export default memo(createPopoverRoot(PopoverPanel));
@@ -0,0 +1,60 @@
1
+ import { type ComponentType, memo, type ReactNode, useMemo, useRef } from 'react';
2
+ import { type View } from 'react-native';
3
+
4
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
5
+ import { PopoverContext, type PopoverProps } from './common';
6
+
7
+ type PopoverPanelInternalProps = PopoverProps & { backdrop?: ReactNode };
8
+
9
+ export const createPopoverRoot = (PopoverPanel: ComponentType<PopoverPanelInternalProps>) => {
10
+ const PopoverRoot = ({
11
+ triggerRef: triggerRefProp,
12
+ isOpen,
13
+ onClose,
14
+ children,
15
+ ...rest
16
+ }: PopoverProps) => {
17
+ const internalTriggerRef = useRef<View>(null);
18
+
19
+ const {
20
+ Popover_Trigger,
21
+ Popover_Backdrop,
22
+ rest: restChildren,
23
+ } = extractSubcomponents({
24
+ children,
25
+ allowedChildren: [
26
+ { name: 'Popover_Trigger', allowMultiple: false },
27
+ { name: 'Popover_Backdrop', allowMultiple: false },
28
+ ] as const,
29
+ includeRest: true,
30
+ });
31
+
32
+ const hasTrigger = Popover_Trigger.length > 0;
33
+ const resolvedTriggerRef = triggerRefProp ?? (hasTrigger ? internalTriggerRef : undefined);
34
+
35
+ const contextValue = useMemo(
36
+ () => ({
37
+ triggerRef: resolvedTriggerRef ?? internalTriggerRef,
38
+ isOpen,
39
+ onClose,
40
+ }),
41
+ [resolvedTriggerRef, isOpen, onClose],
42
+ );
43
+
44
+ return (
45
+ <PopoverContext value={contextValue}>
46
+ {hasTrigger && Popover_Trigger[0]}
47
+ <PopoverPanel
48
+ triggerRef={resolvedTriggerRef}
49
+ isOpen={isOpen}
50
+ onClose={onClose}
51
+ backdrop={Popover_Backdrop[0]}
52
+ {...rest}>
53
+ {restChildren}
54
+ </PopoverPanel>
55
+ </PopoverContext>
56
+ );
57
+ };
58
+
59
+ return memo(PopoverRoot);
60
+ };
@@ -1,8 +1,11 @@
1
1
  import type { ReactNode, RefObject } from 'react';
2
+ import { createContext } from 'react';
2
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
4
  import type { LayoutRectangle, StyleProp, View, ViewStyle } from 'react-native';
4
5
  import { Dimensions, StyleSheet } from 'react-native';
5
6
 
7
+ import { registerPortalContext } from '../Portal';
8
+
6
9
  export type Position = 'top' | 'left' | 'right' | 'bottom';
7
10
  export type Align = 'start' | 'center' | 'end';
8
11
 
@@ -15,10 +18,38 @@ export const popoverDefaultStyles = {
15
18
  opacity: 0,
16
19
  };
17
20
 
21
+ export type PopoverContextValue = {
22
+ triggerRef: RefObject<View | any>;
23
+ isOpen: boolean;
24
+ onClose?: () => void;
25
+ };
26
+
27
+ export const PopoverContext = createContext<PopoverContextValue>({
28
+ isOpen: false,
29
+ triggerRef: { current: null },
30
+ });
31
+
32
+ registerPortalContext(PopoverContext);
33
+
34
+ export type PopoverPanelContextValue = {
35
+ calculatedPosition: ViewStyle | null;
36
+ targetLayoutRef: RefObject<LayoutRectangle | null>;
37
+ popoverLayoutRef: RefObject<LayoutRectangle | null>;
38
+ actualPositionRef: RefObject<Position | undefined>;
39
+ containerStyle?: StyleProp<ViewStyle>;
40
+ };
41
+
42
+ export const PopoverPanelContext = createContext<PopoverPanelContextValue>({
43
+ calculatedPosition: null,
44
+ targetLayoutRef: { current: null },
45
+ popoverLayoutRef: { current: null },
46
+ actualPositionRef: { current: undefined },
47
+ });
48
+
18
49
  export type PopoverProps = {
19
50
  inverted?: boolean;
20
51
  /** Reference to the element the popover should anchor to */
21
- triggerRef: RefObject<View | any>; // Allow different ref types
52
+ triggerRef?: RefObject<View | any>;
22
53
  /** Content to display inside the popover */
23
54
  children: ReactNode;
24
55
  /** Whether the popover is visible */
@@ -31,13 +62,10 @@ export type PopoverProps = {
31
62
  align?: Align;
32
63
  /** Optional style for the popover container */
33
64
  style?: StyleProp<ViewStyle>;
34
- /** Show arrow pointing to the target */
35
- showArrow?: boolean;
36
- /** Size of the arrow */
37
- arrowSize?: number;
38
- withBackdropDismiss?: boolean;
65
+ /** Gap between the popover and the trigger along the main axis */
39
66
  offset?: number;
40
- backdropStyles?: StyleProp<ViewStyle>;
67
+ /** Additional horizontal shift applied to the popover (positive = right) */
68
+ horizontalOffset?: number;
41
69
  /** Optional trigger dimensions to trigger re-measurement when changed */
42
70
  triggerDimensions?: { width: number; height: number } | null;
43
71
  };
@@ -165,13 +193,12 @@ export const adjustPositionForBoundaries = (
165
193
  // --- Arrow Style Hook ---
166
194
 
167
195
  interface UseArrowStylesProps {
168
- showArrow?: boolean;
169
196
  arrowSize: number;
170
- style?: StyleProp<ViewStyle>;
197
+ containerStyle?: StyleProp<ViewStyle>;
171
198
  calculatedPosition: ViewStyle | null;
172
199
  targetLayoutRef: RefObject<LayoutRectangle | null>;
173
200
  popoverLayoutRef: RefObject<LayoutRectangle | null>;
174
- actualPositionRef: RefObject<Position | undefined>; // Use Position type
201
+ actualPositionRef: RefObject<Position | undefined>;
175
202
  }
176
203
 
177
204
  // Define a base style for the popover container to extract default background
@@ -183,9 +210,8 @@ const basePopoverStyle = StyleSheet.create({
183
210
  });
184
211
 
185
212
  export const useArrowStyles = ({
186
- showArrow,
187
213
  arrowSize,
188
- style,
214
+ containerStyle,
189
215
  calculatedPosition,
190
216
  targetLayoutRef,
191
217
  popoverLayoutRef,
@@ -193,7 +219,6 @@ export const useArrowStyles = ({
193
219
  }: UseArrowStylesProps): ViewStyle => {
194
220
  return useMemo(() => {
195
221
  if (
196
- !showArrow ||
197
222
  !targetLayoutRef.current ||
198
223
  !popoverLayoutRef.current ||
199
224
  !calculatedPosition ||
@@ -213,7 +238,7 @@ export const useArrowStyles = ({
213
238
  } = targetLayoutRef.current;
214
239
 
215
240
  const arrowHalfSize = arrowSize / 2;
216
- const popoverStyleFlat = StyleSheet.flatten(style || {});
241
+ const popoverStyleFlat = StyleSheet.flatten(containerStyle || {});
217
242
  const containerStyleFlat = StyleSheet.flatten(basePopoverStyle.container);
218
243
 
219
244
  const backgroundColor =
@@ -320,14 +345,11 @@ export const useArrowStyles = ({
320
345
  default:
321
346
  return {};
322
347
  }
323
- // Use refs directly in dependency array for useMemo
324
- // React checks ref.current internally when deciding memoization
325
348
  }, [
326
- showArrow,
327
349
  arrowSize,
328
- style, // Include style for potential background/zIndex changes
350
+ containerStyle,
329
351
  calculatedPosition,
330
- targetLayoutRef, // Dependency on the ref objects
352
+ targetLayoutRef,
331
353
  popoverLayoutRef,
332
354
  actualPositionRef,
333
355
  ]);
@@ -339,18 +361,16 @@ interface UsePopoverProps {
339
361
  isOpen: boolean;
340
362
  position: Position | undefined;
341
363
  align: Align | undefined;
342
- showArrow: boolean | undefined;
343
- arrowSize: number;
344
364
  offset?: number;
365
+ horizontalOffset?: number;
345
366
  }
346
367
 
347
368
  export const usePopover = ({
348
369
  isOpen,
349
370
  position = 'bottom',
350
371
  align = 'center',
351
- showArrow = true,
352
- arrowSize = DEFAULT_ARROW_SIZE,
353
372
  offset = 0,
373
+ horizontalOffset = 0,
354
374
  }: UsePopoverProps) => {
355
375
  const popoverLayoutRef = useRef<LayoutRectangle | null>(null);
356
376
  const targetLayoutRef = useRef<LayoutRectangle | null>(null);
@@ -359,22 +379,20 @@ export const usePopover = ({
359
379
 
360
380
  const calculateAndSetPosition = useCallback(() => {
361
381
  if (!targetLayoutRef.current || !popoverLayoutRef.current) {
362
- setCalculatedPosition(popoverDefaultStyles); // Hide if layouts are not ready
382
+ setCalculatedPosition(popoverDefaultStyles);
363
383
  return;
364
384
  }
365
385
 
366
386
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
367
- const effectiveArrowSize = showArrow ? arrowSize : 0;
368
387
 
369
388
  let { top: initialTop, left: initialLeft } = getInitialPosition(
370
389
  position,
371
390
  align,
372
391
  targetLayoutRef.current,
373
392
  popoverLayoutRef.current,
374
- effectiveArrowSize,
393
+ 0,
375
394
  );
376
395
 
377
- // Apply offset based on the initial intended position
378
396
  switch (position) {
379
397
  case 'top':
380
398
  initialTop -= offset;
@@ -390,18 +408,20 @@ export const usePopover = ({
390
408
  break;
391
409
  }
392
410
 
411
+ initialLeft += horizontalOffset;
412
+
393
413
  const { finalTop, finalLeft, finalPosition } = adjustPositionForBoundaries(
394
- position, // Pass initial intended position for boundary check logic
395
- initialTop, // Pass offset-adjusted top
396
- initialLeft, // Pass offset-adjusted left
414
+ position,
415
+ initialTop,
416
+ initialLeft,
397
417
  targetLayoutRef.current,
398
418
  popoverLayoutRef.current,
399
419
  screenHeight,
400
420
  screenWidth,
401
- effectiveArrowSize,
421
+ 0,
402
422
  );
403
423
 
404
- actualPositionRef.current = finalPosition; // Store the actual position after adjustments
424
+ actualPositionRef.current = finalPosition;
405
425
 
406
426
  setCalculatedPosition({
407
427
  position: 'absolute',
@@ -409,7 +429,7 @@ export const usePopover = ({
409
429
  left: finalLeft,
410
430
  opacity: 1,
411
431
  });
412
- }, [position, align, showArrow, arrowSize, offset]); // Add offset to dependency array
432
+ }, [position, align, offset, horizontalOffset]);
413
433
 
414
434
  const handlePopoverLayout = useCallback(
415
435
  (event: { nativeEvent: { layout: LayoutRectangle } }) => {
@@ -435,7 +455,7 @@ export const usePopover = ({
435
455
  calculateAndSetPosition();
436
456
  }
437
457
  // This effect specifically handles prop changes
438
- }, [isOpen, position, align, arrowSize, showArrow, calculateAndSetPosition]);
458
+ }, [isOpen, position, align, calculateAndSetPosition]);
439
459
 
440
460
  // Effect to reset layout refs when popover is closed
441
461
  useEffect(() => {
@@ -1,2 +1,13 @@
1
+ import { getRegisteredComponentWithFallback } from '../../core';
2
+ import PopoverDefault, { PopoverArrow, PopoverBackdrop, PopoverTrigger } from './Popover';
3
+
4
+ const PopoverBase = getRegisteredComponentWithFallback('Popover', PopoverDefault);
5
+
6
+ export const Popover = Object.assign(PopoverBase, {
7
+ Trigger: PopoverTrigger,
8
+ Arrow: PopoverArrow,
9
+ Backdrop: PopoverBackdrop,
10
+ });
11
+
1
12
  export type { Align, PopoverProps, Position } from './common';
2
- export { default as Popover } from './Popover';
13
+ export { PopoverContext, PopoverPanelContext } from './common';