@react-native-reusables/cli 0.0.8 → 0.0.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 (136) hide show
  1. package/dist/generated/components/primitives/accordion/accordion.tsx +216 -0
  2. package/dist/generated/components/primitives/accordion/accordion.web.tsx +295 -0
  3. package/dist/generated/components/primitives/accordion/index.ts +1 -0
  4. package/dist/generated/components/primitives/accordion/types.ts +45 -0
  5. package/dist/generated/components/primitives/alert-dialog/alert-dialog.tsx +237 -0
  6. package/dist/generated/components/primitives/alert-dialog/alert-dialog.web.tsx +256 -0
  7. package/dist/generated/components/primitives/alert-dialog/index.ts +1 -0
  8. package/dist/generated/components/primitives/alert-dialog/types.ts +48 -0
  9. package/dist/generated/components/primitives/aspect-ratio.tsx +23 -0
  10. package/dist/generated/components/primitives/avatar/ types.ts +10 -0
  11. package/dist/generated/components/primitives/avatar/index.tsx +95 -0
  12. package/dist/generated/components/primitives/checkbox/checkbox.tsx +101 -0
  13. package/dist/generated/components/primitives/checkbox/checkbox.web.tsx +114 -0
  14. package/dist/generated/components/primitives/checkbox/index.ts +1 -0
  15. package/dist/generated/components/primitives/checkbox/types.ts +11 -0
  16. package/dist/generated/components/primitives/collapsible/collapsible.tsx +119 -0
  17. package/dist/generated/components/primitives/collapsible/collapsible.web.tsx +157 -0
  18. package/dist/generated/components/primitives/collapsible/index.ts +1 -0
  19. package/dist/generated/components/primitives/collapsible/types.ts +18 -0
  20. package/dist/generated/components/primitives/context-menu/context-menu.tsx +626 -0
  21. package/dist/generated/components/primitives/context-menu/context-menu.web.tsx +504 -0
  22. package/dist/generated/components/primitives/context-menu/index.ts +1 -0
  23. package/dist/generated/components/primitives/context-menu/types.ts +82 -0
  24. package/dist/generated/components/primitives/dialog/dialog.tsx +211 -0
  25. package/dist/generated/components/primitives/dialog/dialog.web.tsx +197 -0
  26. package/dist/generated/components/primitives/dialog/index.ts +1 -0
  27. package/dist/generated/components/primitives/dialog/types.ts +60 -0
  28. package/dist/generated/components/primitives/dropdown-menu/dropdown-menu.tsx +584 -0
  29. package/dist/generated/components/primitives/dropdown-menu/dropdown-menu.web.tsx +521 -0
  30. package/dist/generated/components/primitives/dropdown-menu/index.ts +1 -0
  31. package/dist/generated/components/primitives/dropdown-menu/types.ts +71 -0
  32. package/dist/generated/components/primitives/hooks/index.ts +3 -0
  33. package/dist/generated/components/primitives/hooks/useAugmentedRef.tsx +29 -0
  34. package/dist/generated/components/primitives/hooks/useControllableState.tsx +75 -0
  35. package/dist/generated/components/primitives/hooks/useRelativePosition.tsx +227 -0
  36. package/dist/generated/components/primitives/hover-card/hover-card.tsx +271 -0
  37. package/dist/generated/components/primitives/hover-card/hover-card.web.tsx +145 -0
  38. package/dist/generated/components/primitives/hover-card/index.ts +1 -0
  39. package/dist/generated/components/primitives/hover-card/types.ts +42 -0
  40. package/dist/generated/components/primitives/label/index.ts +1 -0
  41. package/dist/generated/components/primitives/label/label.tsx +31 -0
  42. package/dist/generated/components/primitives/label/label.web.tsx +36 -0
  43. package/dist/generated/components/primitives/label/types.ts +15 -0
  44. package/dist/generated/components/primitives/menubar/index.ts +1 -0
  45. package/dist/generated/components/primitives/menubar/menubar.tsx +624 -0
  46. package/dist/generated/components/primitives/menubar/menubar.web.tsx +543 -0
  47. package/dist/generated/components/primitives/menubar/types.ts +76 -0
  48. package/dist/generated/components/primitives/navigation-menu/index.ts +1 -0
  49. package/dist/generated/components/primitives/navigation-menu/navigation-menu.tsx +315 -0
  50. package/dist/generated/components/primitives/navigation-menu/navigation-menu.web.tsx +264 -0
  51. package/dist/generated/components/primitives/navigation-menu/types.ts +49 -0
  52. package/dist/generated/components/primitives/popover/index.ts +1 -0
  53. package/dist/generated/components/primitives/popover/popover.tsx +286 -0
  54. package/dist/generated/components/primitives/popover/popover.web.tsx +179 -0
  55. package/dist/generated/components/primitives/popover/types.ts +30 -0
  56. package/dist/generated/components/primitives/portal.tsx +67 -0
  57. package/dist/generated/components/primitives/progress/index.ts +1 -0
  58. package/dist/generated/components/primitives/progress/progress.tsx +59 -0
  59. package/dist/generated/components/primitives/progress/progress.web.tsx +36 -0
  60. package/dist/generated/components/primitives/progress/types.ts +7 -0
  61. package/dist/generated/components/primitives/radio-group/index.ts +1 -0
  62. package/dist/generated/components/primitives/radio-group/radio-group.tsx +116 -0
  63. package/dist/generated/components/primitives/radio-group/radio-group.web.tsx +78 -0
  64. package/dist/generated/components/primitives/radio-group/types.ts +15 -0
  65. package/dist/generated/components/primitives/select/index.ts +1 -0
  66. package/dist/generated/components/primitives/select/select.tsx +455 -0
  67. package/dist/generated/components/primitives/select/select.web.tsx +319 -0
  68. package/dist/generated/components/primitives/select/types.ts +87 -0
  69. package/dist/generated/components/primitives/separator/ types.ts +6 -0
  70. package/dist/generated/components/primitives/separator/index.tsx +23 -0
  71. package/dist/generated/components/primitives/slider/index.ts +1 -0
  72. package/dist/generated/components/primitives/slider/slider.tsx +89 -0
  73. package/dist/generated/components/primitives/slider/slider.web.tsx +67 -0
  74. package/dist/generated/components/primitives/slider/types.ts +24 -0
  75. package/dist/generated/components/primitives/slot.tsx +187 -0
  76. package/dist/generated/components/primitives/switch/index.ts +1 -0
  77. package/dist/generated/components/primitives/switch/switch.tsx +65 -0
  78. package/dist/generated/components/primitives/switch/switch.web.tsx +67 -0
  79. package/dist/generated/components/primitives/switch/types.ts +11 -0
  80. package/dist/generated/components/primitives/table.tsx +55 -0
  81. package/dist/generated/components/primitives/tabs/index.ts +1 -0
  82. package/dist/generated/components/primitives/tabs/tabs.tsx +133 -0
  83. package/dist/generated/components/primitives/tabs/tabs.web.tsx +97 -0
  84. package/dist/generated/components/primitives/tabs/types.ts +24 -0
  85. package/dist/generated/components/primitives/toast/ types.ts +7 -0
  86. package/dist/generated/components/primitives/toast/index.tsx +128 -0
  87. package/dist/generated/components/primitives/toggle/index.ts +1 -0
  88. package/dist/generated/components/primitives/toggle/toggle.tsx +37 -0
  89. package/dist/generated/components/primitives/toggle/toggle.web.tsx +26 -0
  90. package/dist/generated/components/primitives/toggle/types.ts +7 -0
  91. package/dist/generated/components/primitives/toggle-group/index.ts +1 -0
  92. package/dist/generated/components/primitives/toggle-group/toggle-group.tsx +125 -0
  93. package/dist/generated/components/primitives/toggle-group/toggle-group.web.tsx +124 -0
  94. package/dist/generated/components/primitives/toggle-group/types.ts +37 -0
  95. package/dist/generated/components/primitives/toolbar/index.ts +1 -0
  96. package/dist/generated/components/primitives/toolbar/toolbar.tsx +125 -0
  97. package/dist/generated/components/primitives/toolbar/toolbar.web.tsx +129 -0
  98. package/dist/generated/components/primitives/toolbar/types.ts +39 -0
  99. package/dist/generated/components/primitives/tooltip/index.ts +1 -0
  100. package/dist/generated/components/primitives/tooltip/tooltip.tsx +271 -0
  101. package/dist/generated/components/primitives/tooltip/tooltip.web.tsx +167 -0
  102. package/dist/generated/components/primitives/tooltip/types.ts +44 -0
  103. package/dist/generated/components/primitives/types.ts +105 -0
  104. package/dist/generated/components/primitives/utils.ts +61 -0
  105. package/dist/generated/components/ui/accordion.tsx +127 -0
  106. package/dist/generated/components/ui/alert-dialog.tsx +167 -0
  107. package/dist/generated/components/ui/aspect-ratio.tsx +5 -0
  108. package/dist/generated/components/ui/avatar.tsx +44 -0
  109. package/dist/generated/components/ui/badge.tsx +51 -0
  110. package/dist/generated/components/ui/button.tsx +88 -0
  111. package/dist/generated/components/ui/card.tsx +67 -0
  112. package/dist/generated/components/ui/checkbox.tsx +34 -0
  113. package/dist/generated/components/ui/collapsible.tsx +9 -0
  114. package/dist/generated/components/ui/context-menu.tsx +244 -0
  115. package/dist/generated/components/ui/dialog.tsx +150 -0
  116. package/dist/generated/components/ui/dropdown-menu.tsx +244 -0
  117. package/dist/generated/components/ui/hover-card.tsx +45 -0
  118. package/dist/generated/components/ui/input.tsx +26 -0
  119. package/dist/generated/components/ui/label.tsx +28 -0
  120. package/dist/generated/components/ui/menubar.tsx +260 -0
  121. package/dist/generated/components/ui/navigation-menu.tsx +177 -0
  122. package/dist/generated/components/ui/popover.tsx +39 -0
  123. package/dist/generated/components/ui/radio-group.tsx +38 -0
  124. package/dist/generated/components/ui/select.tsx +181 -0
  125. package/dist/generated/components/ui/separator.tsx +23 -0
  126. package/dist/generated/components/ui/skeleton.tsx +39 -0
  127. package/dist/generated/components/ui/switch.tsx +97 -0
  128. package/dist/generated/components/ui/table.tsx +99 -0
  129. package/dist/generated/components/ui/tabs.tsx +65 -0
  130. package/dist/generated/components/ui/text.tsx +24 -0
  131. package/dist/generated/components/ui/textarea.tsx +28 -0
  132. package/dist/generated/components/ui/toggle-group.tsx +86 -0
  133. package/dist/generated/components/ui/toggle.tsx +85 -0
  134. package/dist/generated/components/ui/tooltip.tsx +36 -0
  135. package/dist/generated/components/ui/typography.tsx +204 -0
  136. package/package.json +8 -8
@@ -0,0 +1,227 @@
1
+ import * as React from 'react';
2
+ import {
3
+ useWindowDimensions,
4
+ type LayoutRectangle,
5
+ type ScaledSize,
6
+ type ViewStyle,
7
+ } from 'react-native';
8
+ import type { Insets } from '@rnr/types';
9
+
10
+ const POSITION_ABSOLUTE: ViewStyle = {
11
+ position: 'absolute',
12
+ };
13
+
14
+ const HIDDEN_CONTENT: ViewStyle = {
15
+ position: 'absolute',
16
+ opacity: 0,
17
+ zIndex: -9999999,
18
+ };
19
+
20
+ type UseRelativePositionArgs = Omit<
21
+ GetContentStyleArgs,
22
+ 'triggerPosition' | 'contentLayout' | 'dimensions'
23
+ > & {
24
+ triggerPosition: LayoutPosition | null;
25
+ contentLayout: LayoutRectangle | null;
26
+ disablePositioningStyle?: boolean;
27
+ };
28
+
29
+ export function useRelativePosition({
30
+ align,
31
+ avoidCollisions,
32
+ triggerPosition,
33
+ contentLayout,
34
+ alignOffset,
35
+ insets,
36
+ sideOffset,
37
+ side,
38
+ disablePositioningStyle,
39
+ }: UseRelativePositionArgs) {
40
+ const dimensions = useWindowDimensions();
41
+ return React.useMemo(() => {
42
+ if (disablePositioningStyle) {
43
+ return {};
44
+ }
45
+ if (!triggerPosition || !contentLayout) {
46
+ return HIDDEN_CONTENT;
47
+ }
48
+ return getContentStyle({
49
+ align,
50
+ avoidCollisions,
51
+ contentLayout,
52
+ side,
53
+ triggerPosition,
54
+ alignOffset,
55
+ insets,
56
+ sideOffset,
57
+ dimensions,
58
+ });
59
+ }, [triggerPosition, contentLayout, dimensions.width, dimensions.height]);
60
+ }
61
+
62
+ export interface LayoutPosition {
63
+ pageY: number;
64
+ pageX: number;
65
+ width: number;
66
+ height: number;
67
+ }
68
+
69
+ interface GetPositionArgs {
70
+ dimensions: ScaledSize;
71
+ avoidCollisions: boolean;
72
+ triggerPosition: LayoutPosition;
73
+ contentLayout: LayoutRectangle;
74
+ insets?: Insets;
75
+ }
76
+
77
+ interface GetSidePositionArgs extends GetPositionArgs {
78
+ side: 'top' | 'bottom';
79
+ sideOffset: number;
80
+ }
81
+
82
+ function getSidePosition({
83
+ side,
84
+ triggerPosition,
85
+ contentLayout,
86
+ sideOffset,
87
+ insets,
88
+ avoidCollisions,
89
+ dimensions,
90
+ }: GetSidePositionArgs) {
91
+ const insetTop = insets?.top ?? 0;
92
+ const insetBottom = insets?.bottom ?? 0;
93
+ const positionTop = triggerPosition?.pageY - sideOffset - contentLayout.height;
94
+ const positionBottom = triggerPosition.pageY + triggerPosition.height + sideOffset;
95
+
96
+ if (!avoidCollisions) {
97
+ return {
98
+ top: side === 'top' ? positionTop : positionBottom,
99
+ };
100
+ }
101
+
102
+ if (side === 'top') {
103
+ return {
104
+ top: Math.max(insetTop, positionTop),
105
+ };
106
+ }
107
+
108
+ return {
109
+ top: Math.min(dimensions.height - insetBottom - contentLayout.height, positionBottom),
110
+ };
111
+ }
112
+
113
+ interface GetAlignPositionArgs extends GetPositionArgs {
114
+ align: 'start' | 'center' | 'end';
115
+ alignOffset: number;
116
+ }
117
+
118
+ function getAlignPosition({
119
+ align,
120
+ avoidCollisions,
121
+ contentLayout,
122
+ triggerPosition,
123
+ alignOffset,
124
+ insets,
125
+ dimensions,
126
+ }: GetAlignPositionArgs) {
127
+ const insetLeft = insets?.left ?? 0;
128
+ const insetRight = insets?.right ?? 0;
129
+ const maxContentWidth = dimensions.width - insetLeft - insetRight;
130
+
131
+ const contentWidth = Math.min(contentLayout.width, maxContentWidth);
132
+
133
+ let left = getLeftPosition(
134
+ align,
135
+ triggerPosition.pageX,
136
+ triggerPosition.width,
137
+ contentWidth,
138
+ alignOffset,
139
+ insetLeft,
140
+ insetRight,
141
+ dimensions
142
+ );
143
+
144
+ if (avoidCollisions) {
145
+ const doesCollide = left < insetLeft || left + contentWidth > dimensions.width - insetRight;
146
+ if (doesCollide) {
147
+ const spaceLeft = left - insetLeft;
148
+ const spaceRight = dimensions.width - insetRight - (left + contentWidth);
149
+
150
+ if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {
151
+ left = insetLeft;
152
+ } else if (spaceRight >= contentWidth) {
153
+ left = dimensions.width - insetRight - contentWidth;
154
+ } else {
155
+ const centeredPosition = Math.max(
156
+ insetLeft,
157
+ (dimensions.width - contentWidth - insetRight) / 2
158
+ );
159
+ left = centeredPosition;
160
+ }
161
+ }
162
+ }
163
+
164
+ return { left, maxWidth: maxContentWidth };
165
+ }
166
+
167
+ function getLeftPosition(
168
+ align: 'start' | 'center' | 'end',
169
+ triggerPageX: number,
170
+ triggerWidth: number,
171
+ contentWidth: number,
172
+ alignOffset: number,
173
+ insetLeft: number,
174
+ insetRight: number,
175
+ dimensions: ScaledSize
176
+ ) {
177
+ let left = 0;
178
+ if (align === 'start') {
179
+ left = triggerPageX;
180
+ }
181
+ if (align === 'center') {
182
+ left = triggerPageX + triggerWidth / 2 - contentWidth / 2;
183
+ }
184
+ if (align === 'end') {
185
+ left = triggerPageX + triggerWidth - contentWidth;
186
+ }
187
+ return Math.max(
188
+ insetLeft,
189
+ Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight)
190
+ );
191
+ }
192
+
193
+ type GetContentStyleArgs = GetPositionArgs & GetSidePositionArgs & GetAlignPositionArgs;
194
+
195
+ function getContentStyle({
196
+ align,
197
+ avoidCollisions,
198
+ contentLayout,
199
+ side,
200
+ triggerPosition,
201
+ alignOffset,
202
+ insets,
203
+ sideOffset,
204
+ dimensions,
205
+ }: GetContentStyleArgs) {
206
+ return Object.assign(
207
+ POSITION_ABSOLUTE,
208
+ getSidePosition({
209
+ side,
210
+ triggerPosition,
211
+ contentLayout,
212
+ sideOffset,
213
+ insets,
214
+ avoidCollisions,
215
+ dimensions,
216
+ }),
217
+ getAlignPosition({
218
+ align,
219
+ avoidCollisions,
220
+ triggerPosition,
221
+ contentLayout,
222
+ alignOffset,
223
+ insets,
224
+ dimensions,
225
+ })
226
+ );
227
+ }
@@ -0,0 +1,271 @@
1
+ import * as React from 'react';
2
+ import {
3
+ BackHandler,
4
+ Pressable,
5
+ View,
6
+ type GestureResponderEvent,
7
+ type LayoutChangeEvent,
8
+ type LayoutRectangle,
9
+ } from 'react-native';
10
+ import { useRelativePosition, type LayoutPosition, useControllableState } from '@rnr/hooks';
11
+ import { Portal as RNPPortal } from '@rnr/portal';
12
+ import * as Slot from '@rnr/slot';
13
+ import type {
14
+ PositionedContentProps,
15
+ PressableRef,
16
+ SlottablePressableProps,
17
+ SlottableViewProps,
18
+ ViewRef,
19
+ } from '@rnr/types';
20
+ import type {
21
+ HoverCardOverlayProps,
22
+ HoverCardPortalProps,
23
+ HoverCardRootProps,
24
+ RootContext,
25
+ } from './types';
26
+
27
+ interface IRootContext extends RootContext {
28
+ triggerPosition: LayoutPosition | null;
29
+ setTriggerPosition: (triggerPosition: LayoutPosition | null) => void;
30
+ contentLayout: LayoutRectangle | null;
31
+ setContentLayout: (contentLayout: LayoutRectangle | null) => void;
32
+ nativeID: string;
33
+ }
34
+
35
+ const RootContext = React.createContext<IRootContext | null>(null);
36
+
37
+ const Root = React.forwardRef<ViewRef, SlottableViewProps & HoverCardRootProps>(
38
+ (
39
+ {
40
+ asChild,
41
+ open: openProp,
42
+ defaultOpen,
43
+ onOpenChange: onOpenChangeProp,
44
+ openDelay: _openDelay,
45
+ closeDelay: _closeDelay,
46
+ ...viewProps
47
+ },
48
+ ref
49
+ ) => {
50
+ const nativeID = React.useId();
51
+ const [open = false, onOpenChange] = useControllableState({
52
+ prop: openProp,
53
+ defaultProp: defaultOpen,
54
+ onChange: onOpenChangeProp,
55
+ });
56
+ const [triggerPosition, setTriggerPosition] = React.useState<LayoutPosition | null>(null);
57
+ const [contentLayout, setContentLayout] = React.useState<LayoutRectangle | null>(null);
58
+
59
+ const Component = asChild ? Slot.View : View;
60
+ return (
61
+ <RootContext.Provider
62
+ value={{
63
+ open,
64
+ onOpenChange,
65
+ contentLayout,
66
+ nativeID,
67
+ setContentLayout,
68
+ setTriggerPosition,
69
+ triggerPosition,
70
+ }}
71
+ >
72
+ <Component ref={ref} {...viewProps} />
73
+ </RootContext.Provider>
74
+ );
75
+ }
76
+ );
77
+
78
+ Root.displayName = 'RootNativeHoverCard';
79
+
80
+ function useRootContext() {
81
+ const context = React.useContext(RootContext);
82
+ if (!context) {
83
+ throw new Error(
84
+ 'HoverCard compound components cannot be rendered outside the HoverCard component'
85
+ );
86
+ }
87
+ return context;
88
+ }
89
+
90
+ const Trigger = React.forwardRef<PressableRef, SlottablePressableProps>(
91
+ ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => {
92
+ const triggerRef = React.useRef<View>(null);
93
+ const { open, onOpenChange, setTriggerPosition } = useRootContext();
94
+
95
+ React.useImperativeHandle(
96
+ ref,
97
+ () => {
98
+ if (!triggerRef.current) {
99
+ return new View({});
100
+ }
101
+ return triggerRef.current;
102
+ },
103
+ [triggerRef.current]
104
+ );
105
+
106
+ function onPress(ev: GestureResponderEvent) {
107
+ if (disabled) return;
108
+ triggerRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
109
+ setTriggerPosition({ width, pageX, pageY: pageY, height });
110
+ });
111
+ const newValue = !open;
112
+ onOpenChange(newValue);
113
+ onPressProp?.(ev);
114
+ }
115
+
116
+ const Component = asChild ? Slot.Pressable : Pressable;
117
+ return (
118
+ <Component
119
+ ref={triggerRef}
120
+ aria-disabled={disabled ?? undefined}
121
+ role='button'
122
+ onPress={onPress}
123
+ disabled={disabled ?? undefined}
124
+ {...props}
125
+ />
126
+ );
127
+ }
128
+ );
129
+
130
+ Trigger.displayName = 'TriggerNativeHoverCard';
131
+
132
+ /**
133
+ * @warning when using a custom `<PortalHost />`, you might have to adjust the Content's sideOffset to account for nav elements like headers.
134
+ */
135
+ function Portal({ forceMount, hostName, children }: HoverCardPortalProps) {
136
+ const value = useRootContext();
137
+
138
+ if (!value.triggerPosition) {
139
+ return null;
140
+ }
141
+
142
+ if (!forceMount) {
143
+ if (!value.open) {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ return (
149
+ <RNPPortal hostName={hostName} name={`${value.nativeID}_portal`}>
150
+ <RootContext.Provider value={value}>{children}</RootContext.Provider>
151
+ </RNPPortal>
152
+ );
153
+ }
154
+
155
+ const Overlay = React.forwardRef<PressableRef, SlottablePressableProps & HoverCardOverlayProps>(
156
+ ({ asChild, forceMount, onPress: OnPressProp, closeOnPress = true, ...props }, ref) => {
157
+ const { open, onOpenChange, setTriggerPosition, setContentLayout } = useRootContext();
158
+
159
+ function onPress(ev: GestureResponderEvent) {
160
+ if (closeOnPress) {
161
+ setTriggerPosition(null);
162
+ setContentLayout(null);
163
+ onOpenChange(false);
164
+ }
165
+ OnPressProp?.(ev);
166
+ }
167
+
168
+ if (!forceMount) {
169
+ if (!open) {
170
+ return null;
171
+ }
172
+ }
173
+
174
+ const Component = asChild ? Slot.Pressable : Pressable;
175
+ return <Component ref={ref} onPress={onPress} {...props} />;
176
+ }
177
+ );
178
+
179
+ Overlay.displayName = 'OverlayNativeHoverCard';
180
+
181
+ /**
182
+ * @info `position`, `top`, `left`, and `maxWidth` style properties are controlled internally. Opt out of this behavior by setting `disablePositioningStyle` to `true`.
183
+ */
184
+ const Content = React.forwardRef<ViewRef, SlottableViewProps & PositionedContentProps>(
185
+ (
186
+ {
187
+ asChild = false,
188
+ forceMount,
189
+ align = 'start',
190
+ side = 'bottom',
191
+ sideOffset = 0,
192
+ alignOffset = 0,
193
+ avoidCollisions = true,
194
+ onLayout: onLayoutProp,
195
+ insets,
196
+ style,
197
+ disablePositioningStyle,
198
+ ...props
199
+ },
200
+ ref
201
+ ) => {
202
+ const {
203
+ open,
204
+ onOpenChange,
205
+ contentLayout,
206
+ nativeID,
207
+ setContentLayout,
208
+ setTriggerPosition,
209
+ triggerPosition,
210
+ } = useRootContext();
211
+
212
+ React.useEffect(() => {
213
+ const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
214
+ setTriggerPosition(null);
215
+ setContentLayout(null);
216
+ onOpenChange(false);
217
+ return true;
218
+ });
219
+
220
+ return () => {
221
+ setContentLayout(null);
222
+ backHandler.remove();
223
+ };
224
+ }, []);
225
+
226
+ const positionStyle = useRelativePosition({
227
+ align,
228
+ avoidCollisions,
229
+ triggerPosition,
230
+ contentLayout,
231
+ alignOffset,
232
+ insets,
233
+ sideOffset,
234
+ side,
235
+ disablePositioningStyle,
236
+ });
237
+
238
+ function onLayout(event: LayoutChangeEvent) {
239
+ setContentLayout(event.nativeEvent.layout);
240
+ onLayoutProp?.(event);
241
+ }
242
+
243
+ if (!forceMount) {
244
+ if (!open) {
245
+ return null;
246
+ }
247
+ }
248
+
249
+ const Component = asChild ? Slot.View : View;
250
+ return (
251
+ <Component
252
+ ref={ref}
253
+ role='dialog'
254
+ nativeID={nativeID}
255
+ aria-modal={true}
256
+ style={[positionStyle, style]}
257
+ onLayout={onLayout}
258
+ onStartShouldSetResponder={onStartShouldSetResponder}
259
+ {...props}
260
+ />
261
+ );
262
+ }
263
+ );
264
+
265
+ Content.displayName = 'ContentNativeHoverCard';
266
+
267
+ export { Content, Overlay, Portal, Root, Trigger, useRootContext };
268
+
269
+ function onStartShouldSetResponder() {
270
+ return true;
271
+ }
@@ -0,0 +1,145 @@
1
+ import * as HoverCard from '@radix-ui/react-hover-card';
2
+ import * as React from 'react';
3
+ import { Pressable, View } from 'react-native';
4
+ import * as Slot from '@rnr/slot';
5
+ import type {
6
+ PositionedContentProps,
7
+ PressableRef,
8
+ SlottablePressableProps,
9
+ SlottableViewProps,
10
+ ViewRef,
11
+ } from '@rnr/types';
12
+ import type {
13
+ HoverCardOverlayProps,
14
+ HoverCardPortalProps,
15
+ HoverCardRootProps,
16
+ RootContext,
17
+ } from './types';
18
+ import { useControllableState } from '@rnr/hooks';
19
+
20
+ const HoverCardContext = React.createContext<RootContext | null>(null);
21
+
22
+ const Root = React.forwardRef<ViewRef, SlottableViewProps & HoverCardRootProps>(
23
+ (
24
+ {
25
+ asChild,
26
+ open: openProp,
27
+ defaultOpen,
28
+ onOpenChange: onOpenChangeProp,
29
+ openDelay,
30
+ closeDelay,
31
+ ...viewProps
32
+ },
33
+ ref
34
+ ) => {
35
+ const [open = false, onOpenChange] = useControllableState({
36
+ prop: openProp,
37
+ defaultProp: defaultOpen,
38
+ onChange: onOpenChangeProp,
39
+ });
40
+ const Component = asChild ? Slot.View : View;
41
+ return (
42
+ <HoverCardContext.Provider value={{ open, onOpenChange }}>
43
+ <HoverCard.Root
44
+ open={open}
45
+ defaultOpen={defaultOpen}
46
+ onOpenChange={onOpenChange}
47
+ openDelay={openDelay}
48
+ closeDelay={closeDelay}
49
+ >
50
+ <Component ref={ref} {...viewProps} />
51
+ </HoverCard.Root>
52
+ </HoverCardContext.Provider>
53
+ );
54
+ }
55
+ );
56
+
57
+ Root.displayName = 'RootWebHoverCard';
58
+
59
+ function useRootContext() {
60
+ const context = React.useContext(HoverCardContext);
61
+ if (!context) {
62
+ throw new Error(
63
+ 'HoverCard compound components cannot be rendered outside the HoverCard component'
64
+ );
65
+ }
66
+ return context;
67
+ }
68
+
69
+ const Trigger = React.forwardRef<PressableRef, SlottablePressableProps>(
70
+ ({ asChild, ...props }, ref) => {
71
+ const Component = asChild ? Slot.Pressable : Pressable;
72
+ return (
73
+ <HoverCard.Trigger asChild>
74
+ <Component ref={ref} {...props} />
75
+ </HoverCard.Trigger>
76
+ );
77
+ }
78
+ );
79
+
80
+ Trigger.displayName = 'TriggerWebHoverCard';
81
+
82
+ function Portal({ forceMount, container, children }: HoverCardPortalProps) {
83
+ return <HoverCard.Portal forceMount={forceMount} container={container} children={children} />;
84
+ }
85
+
86
+ const Overlay = React.forwardRef<PressableRef, SlottablePressableProps & HoverCardOverlayProps>(
87
+ ({ asChild, ...props }, ref) => {
88
+ const Component = asChild ? Slot.Pressable : Pressable;
89
+ return <Component ref={ref} {...props} />;
90
+ }
91
+ );
92
+
93
+ Overlay.displayName = 'OverlayWebHoverCard';
94
+
95
+ const Content = React.forwardRef<PressableRef, SlottablePressableProps & PositionedContentProps>(
96
+ (
97
+ {
98
+ asChild = false,
99
+ forceMount,
100
+ align,
101
+ side,
102
+ sideOffset,
103
+ alignOffset = 0,
104
+ avoidCollisions = true,
105
+ insets,
106
+ loop: _loop,
107
+ onCloseAutoFocus: _onCloseAutoFocus,
108
+ onEscapeKeyDown,
109
+ onPointerDownOutside,
110
+ onFocusOutside,
111
+ onInteractOutside,
112
+ collisionBoundary,
113
+ sticky,
114
+ hideWhenDetached,
115
+ ...props
116
+ },
117
+ ref
118
+ ) => {
119
+ const Component = asChild ? Slot.Pressable : Pressable;
120
+ return (
121
+ <HoverCard.Content
122
+ forceMount={forceMount}
123
+ alignOffset={alignOffset}
124
+ avoidCollisions={avoidCollisions}
125
+ collisionPadding={insets}
126
+ onEscapeKeyDown={onEscapeKeyDown}
127
+ onPointerDownOutside={onPointerDownOutside}
128
+ onFocusOutside={onFocusOutside}
129
+ onInteractOutside={onInteractOutside}
130
+ collisionBoundary={collisionBoundary}
131
+ sticky={sticky}
132
+ hideWhenDetached={hideWhenDetached}
133
+ align={align}
134
+ side={side}
135
+ sideOffset={sideOffset}
136
+ >
137
+ <Component ref={ref} {...props} />
138
+ </HoverCard.Content>
139
+ );
140
+ }
141
+ );
142
+
143
+ Content.displayName = 'ContentWebHoverCard';
144
+
145
+ export { Content, Overlay, Portal, Root, Trigger, useRootContext };
@@ -0,0 +1 @@
1
+ export * from './hover-card';
@@ -0,0 +1,42 @@
1
+ import type { ForceMountable } from '@rnr/types';
2
+
3
+ interface RootContext {
4
+ open: boolean;
5
+ onOpenChange: (value: boolean) => void;
6
+ openDelay?: number;
7
+ closeDelay?: number;
8
+ }
9
+
10
+ interface HoverCardRootProps {
11
+ open?: boolean;
12
+ defaultOpen?: boolean;
13
+ onOpenChange?: (value: boolean) => void;
14
+ /**
15
+ * Platform: WEB ONLY
16
+ * @default 700
17
+ */
18
+ openDelay?: number;
19
+ /**
20
+ * Platform: WEB ONLY
21
+ * @default 300
22
+ */
23
+ closeDelay?: number;
24
+ }
25
+
26
+ interface HoverCardPortalProps extends ForceMountable {
27
+ children: React.ReactNode;
28
+ /**
29
+ * Platform: NATIVE ONLY
30
+ */
31
+ hostName?: string;
32
+ /**
33
+ * Platform: WEB ONLY
34
+ */
35
+ container?: HTMLElement | null | undefined;
36
+ }
37
+
38
+ interface HoverCardOverlayProps extends ForceMountable {
39
+ closeOnPress?: boolean;
40
+ }
41
+
42
+ export type { HoverCardRootProps, HoverCardOverlayProps, HoverCardPortalProps, RootContext };
@@ -0,0 +1 @@
1
+ export * from './label';
@@ -0,0 +1,31 @@
1
+ import * as Slot from '@rnr/slot';
2
+ import type {
3
+ PressableRef,
4
+ SlottablePressableProps,
5
+ SlottableTextProps,
6
+ TextRef,
7
+ } from '@rnr/types';
8
+ import * as React from 'react';
9
+ import { Pressable, Text as RNText } from 'react-native';
10
+ import type { LabelRootProps, LabelTextProps } from './types';
11
+
12
+ const Root = React.forwardRef<
13
+ PressableRef,
14
+ Omit<SlottablePressableProps, 'children' | 'hitSlop' | 'style'> & LabelRootProps
15
+ >(({ asChild, ...props }, ref) => {
16
+ const Component = asChild ? Slot.Pressable : Pressable;
17
+ return <Component ref={ref} {...props} />;
18
+ });
19
+
20
+ Root.displayName = 'RootNativeLabel';
21
+
22
+ const Text = React.forwardRef<TextRef, SlottableTextProps & LabelTextProps>(
23
+ ({ asChild, ...props }, ref) => {
24
+ const Component = asChild ? Slot.Text : RNText;
25
+ return <Component ref={ref} {...props} />;
26
+ }
27
+ );
28
+
29
+ Text.displayName = 'TextNativeLabel';
30
+
31
+ export { Root, Text };