@mrmeg/expo-ui 0.1.0
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.
- package/README.md +96 -0
- package/dist/components/Accordion.d.ts +54 -0
- package/dist/components/Accordion.js +149 -0
- package/dist/components/Alert.d.ts +30 -0
- package/dist/components/Alert.js +25 -0
- package/dist/components/AnimatedView.d.ts +55 -0
- package/dist/components/AnimatedView.js +39 -0
- package/dist/components/Badge.d.ts +23 -0
- package/dist/components/Badge.js +74 -0
- package/dist/components/BottomSheet.d.ts +74 -0
- package/dist/components/BottomSheet.js +513 -0
- package/dist/components/Button.d.ts +129 -0
- package/dist/components/Button.js +216 -0
- package/dist/components/Card.d.ts +42 -0
- package/dist/components/Card.js +126 -0
- package/dist/components/Checkbox.d.ts +39 -0
- package/dist/components/Checkbox.js +96 -0
- package/dist/components/Collapsible.d.ts +67 -0
- package/dist/components/Collapsible.js +38 -0
- package/dist/components/Dialog.d.ts +140 -0
- package/dist/components/Dialog.js +167 -0
- package/dist/components/DismissKeyboard.d.ts +15 -0
- package/dist/components/DismissKeyboard.js +13 -0
- package/dist/components/Drawer.d.ts +74 -0
- package/dist/components/Drawer.js +423 -0
- package/dist/components/DropdownMenu.d.ts +120 -0
- package/dist/components/DropdownMenu.js +211 -0
- package/dist/components/EmptyState.d.ts +42 -0
- package/dist/components/EmptyState.js +58 -0
- package/dist/components/ErrorBoundary.d.ts +53 -0
- package/dist/components/ErrorBoundary.js +75 -0
- package/dist/components/Icon.d.ts +46 -0
- package/dist/components/Icon.js +40 -0
- package/dist/components/InputOTP.d.ts +72 -0
- package/dist/components/InputOTP.js +155 -0
- package/dist/components/Label.d.ts +61 -0
- package/dist/components/Label.js +72 -0
- package/dist/components/MaxWidthContainer.d.ts +58 -0
- package/dist/components/MaxWidthContainer.js +64 -0
- package/dist/components/Notification.d.ts +26 -0
- package/dist/components/Notification.js +230 -0
- package/dist/components/Popover.d.ts +79 -0
- package/dist/components/Popover.js +91 -0
- package/dist/components/Progress.d.ts +28 -0
- package/dist/components/Progress.js +107 -0
- package/dist/components/RadioGroup.d.ts +65 -0
- package/dist/components/RadioGroup.js +142 -0
- package/dist/components/Select.d.ts +88 -0
- package/dist/components/Select.js +172 -0
- package/dist/components/Separator.d.ts +83 -0
- package/dist/components/Separator.js +85 -0
- package/dist/components/Skeleton.d.ts +68 -0
- package/dist/components/Skeleton.js +99 -0
- package/dist/components/Slider.d.ts +24 -0
- package/dist/components/Slider.js +162 -0
- package/dist/components/StatusBar.d.ts +1 -0
- package/dist/components/StatusBar.js +19 -0
- package/dist/components/StyledText.d.ts +161 -0
- package/dist/components/StyledText.js +193 -0
- package/dist/components/Switch.d.ts +44 -0
- package/dist/components/Switch.js +129 -0
- package/dist/components/Tabs.d.ts +31 -0
- package/dist/components/Tabs.js +127 -0
- package/dist/components/TextInput.d.ts +120 -0
- package/dist/components/TextInput.js +263 -0
- package/dist/components/Toggle.d.ts +106 -0
- package/dist/components/Toggle.js +150 -0
- package/dist/components/ToggleGroup.d.ts +80 -0
- package/dist/components/ToggleGroup.js +189 -0
- package/dist/components/Tooltip.d.ts +121 -0
- package/dist/components/Tooltip.js +132 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +35 -0
- package/dist/constants/colors.d.ts +82 -0
- package/dist/constants/colors.js +116 -0
- package/dist/constants/fonts.d.ts +32 -0
- package/dist/constants/fonts.js +91 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.js +3 -0
- package/dist/constants/spacing.d.ts +40 -0
- package/dist/constants/spacing.js +48 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/useDimensions.d.ts +19 -0
- package/dist/hooks/useDimensions.js +55 -0
- package/dist/hooks/useReduceMotion.d.ts +5 -0
- package/dist/hooks/useReduceMotion.js +64 -0
- package/dist/hooks/useResources.d.ts +12 -0
- package/dist/hooks/useResources.js +56 -0
- package/dist/hooks/useScalePress.d.ts +57 -0
- package/dist/hooks/useScalePress.js +55 -0
- package/dist/hooks/useStaggeredEntrance.d.ts +67 -0
- package/dist/hooks/useStaggeredEntrance.js +74 -0
- package/dist/hooks/useTheme.d.ts +88 -0
- package/dist/hooks/useTheme.js +328 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lib/animations.d.ts +1 -0
- package/dist/lib/animations.js +3 -0
- package/dist/lib/haptics.d.ts +3 -0
- package/dist/lib/haptics.js +29 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/sentry.d.ts +16 -0
- package/dist/lib/sentry.js +55 -0
- package/dist/state/globalUIStore.d.ts +30 -0
- package/dist/state/globalUIStore.js +8 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.js +2 -0
- package/dist/state/themeStore.d.ts +6 -0
- package/dist/state/themeStore.js +38 -0
- package/package.json +92 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { createContext, useContext, useState, useReducer, useRef } from "react";
|
|
3
|
+
import { View, Pressable, Animated, StyleSheet, Platform, Dimensions, PanResponder, } from "react-native";
|
|
4
|
+
import { Portal } from "@rn-primitives/portal";
|
|
5
|
+
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
|
|
6
|
+
import { Pressable as SlotPressable } from "@rn-primitives/slot";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme";
|
|
8
|
+
import { shouldUseNativeDriver } from "../lib/animations";
|
|
9
|
+
import { spacing } from "../constants/spacing";
|
|
10
|
+
import { TextColorContext, TextClassContext } from "./StyledText";
|
|
11
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
|
+
/**
|
|
13
|
+
* Drawer Component with Sub-components
|
|
14
|
+
*
|
|
15
|
+
* A sliding drawer overlay that can appear from left or right side.
|
|
16
|
+
* Supports both controlled and uncontrolled modes.
|
|
17
|
+
* Supports swipe gestures on native platforms and backdrop press to close.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // Uncontrolled (internal state management)
|
|
22
|
+
* <Drawer side="left">
|
|
23
|
+
* <Drawer.Trigger asChild>
|
|
24
|
+
* <Button>Open Menu</Button>
|
|
25
|
+
* </Drawer.Trigger>
|
|
26
|
+
* <Drawer.Content>
|
|
27
|
+
* <Drawer.Header>
|
|
28
|
+
* <SansSerifBoldText>Menu</SansSerifBoldText>
|
|
29
|
+
* </Drawer.Header>
|
|
30
|
+
* <Drawer.Body>
|
|
31
|
+
* <SansSerifText>Content here</SansSerifText>
|
|
32
|
+
* </Drawer.Body>
|
|
33
|
+
* <Drawer.Footer>
|
|
34
|
+
* <DrawerCloseButton />
|
|
35
|
+
* </Drawer.Footer>
|
|
36
|
+
* </Drawer.Content>
|
|
37
|
+
* </Drawer>
|
|
38
|
+
*
|
|
39
|
+
* // Controlled (parent manages state)
|
|
40
|
+
* const [open, setOpen] = useState(false);
|
|
41
|
+
* <Drawer open={open} onOpenChange={setOpen} side="left">
|
|
42
|
+
* ...
|
|
43
|
+
* </Drawer>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
// Platform-specific overlay wrapper
|
|
47
|
+
const FullWindowOverlay = Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Context
|
|
50
|
+
// ============================================================================
|
|
51
|
+
const DrawerContext = createContext(null);
|
|
52
|
+
function useDrawerContext() {
|
|
53
|
+
const context = useContext(DrawerContext);
|
|
54
|
+
if (!context) {
|
|
55
|
+
throw new Error("Drawer components must be used within a Drawer");
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
}
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Utility Functions
|
|
61
|
+
// ============================================================================
|
|
62
|
+
function parseWidth(width) {
|
|
63
|
+
if (typeof width === "number") {
|
|
64
|
+
return width;
|
|
65
|
+
}
|
|
66
|
+
// Parse percentage string
|
|
67
|
+
const percentage = parseFloat(width) / 100;
|
|
68
|
+
const screenWidth = Dimensions.get("window").width;
|
|
69
|
+
return screenWidth * percentage;
|
|
70
|
+
}
|
|
71
|
+
function drawerReducer(state, action) {
|
|
72
|
+
switch (action.type) {
|
|
73
|
+
case 'OPEN': return true;
|
|
74
|
+
case 'CLOSE': return false;
|
|
75
|
+
case 'TOGGLE': return !state; // Always uses current state!
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Drawer Root Component
|
|
80
|
+
// ============================================================================
|
|
81
|
+
function DrawerRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen = false, side = "left", width = 300, closeOnBackdropPress = true, children, }) {
|
|
82
|
+
// Use reducer for stable state management - dispatch is stable and reducer always gets current state
|
|
83
|
+
const [internalOpen, dispatch] = useReducer(drawerReducer, defaultOpen);
|
|
84
|
+
const isControlled = controlledOpen !== undefined;
|
|
85
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
86
|
+
// Stable toggle function - dispatch is stable across renders
|
|
87
|
+
const toggle = () => {
|
|
88
|
+
if (isControlled) {
|
|
89
|
+
// For controlled mode, we need to call the callback with the toggled value
|
|
90
|
+
// We use a functional update pattern via dispatch to ensure we get current state
|
|
91
|
+
controlledOnOpenChange?.(!controlledOpen);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
dispatch({ type: 'TOGGLE' });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Handler for explicit open/close actions
|
|
98
|
+
const onOpenChange = (newOpen) => {
|
|
99
|
+
if (isControlled) {
|
|
100
|
+
controlledOnOpenChange?.(newOpen);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
dispatch({ type: newOpen ? 'OPEN' : 'CLOSE' });
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const parsedWidth = parseWidth(width);
|
|
107
|
+
const contextValue = {
|
|
108
|
+
open,
|
|
109
|
+
onOpenChange,
|
|
110
|
+
toggle,
|
|
111
|
+
side,
|
|
112
|
+
width: parsedWidth,
|
|
113
|
+
closeOnBackdropPress,
|
|
114
|
+
};
|
|
115
|
+
return (_jsx(DrawerContext.Provider, { value: contextValue, children: children }));
|
|
116
|
+
}
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Drawer Trigger Component
|
|
119
|
+
// ============================================================================
|
|
120
|
+
function DrawerTrigger({ asChild, children, style: styleOverride }) {
|
|
121
|
+
const { toggle } = useDrawerContext();
|
|
122
|
+
// Use toggle directly - it reads current state from a ref, avoiding stale closures
|
|
123
|
+
const handlePress = () => {
|
|
124
|
+
toggle();
|
|
125
|
+
};
|
|
126
|
+
if (asChild && React.isValidElement(children)) {
|
|
127
|
+
// Clone child and inject onPress directly instead of using SlotPressable
|
|
128
|
+
return React.cloneElement(children, {
|
|
129
|
+
onPress: handlePress,
|
|
130
|
+
accessibilityRole: "button",
|
|
131
|
+
style: [
|
|
132
|
+
children.props.style,
|
|
133
|
+
Platform.OS === "web" && { cursor: "pointer" },
|
|
134
|
+
styleOverride,
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return (_jsx(Pressable, { onPress: handlePress, accessibilityRole: "button", style: [
|
|
139
|
+
Platform.OS === "web" && { cursor: "pointer" },
|
|
140
|
+
styleOverride,
|
|
141
|
+
], children: children }));
|
|
142
|
+
}
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// Drawer Content Component
|
|
145
|
+
// ============================================================================
|
|
146
|
+
function DrawerContent({ swipeEnabled = true, swipeThreshold = 0.3, velocityThreshold = 500, style: styleOverride, children, ...props }) {
|
|
147
|
+
const drawerContext = useDrawerContext();
|
|
148
|
+
const { open, onOpenChange, side, width, closeOnBackdropPress } = drawerContext;
|
|
149
|
+
const { theme, getShadowStyle } = useTheme();
|
|
150
|
+
const insets = useSafeAreaInsets();
|
|
151
|
+
// Animation values - initialize based on initial open state
|
|
152
|
+
const closedPosition = side === "left" ? -width : width;
|
|
153
|
+
const translateX = useRef(new Animated.Value(open ? 0 : closedPosition)).current;
|
|
154
|
+
const backdropOpacity = useRef(new Animated.Value(open ? 1 : 0)).current;
|
|
155
|
+
// Track if drawer is actually visible (for unmounting after close animation)
|
|
156
|
+
const [isVisible, setIsVisible] = useState(open);
|
|
157
|
+
// Track what we last animated to - persists across renders
|
|
158
|
+
const lastOpenRef = useRef(null);
|
|
159
|
+
// Track running animation to properly cancel it
|
|
160
|
+
const runningAnimationRef = useRef(null);
|
|
161
|
+
// Use semantic foreground color for text on background
|
|
162
|
+
const textColor = theme.colors.foreground;
|
|
163
|
+
// Trigger animation during render if open changed
|
|
164
|
+
if (open !== lastOpenRef.current) {
|
|
165
|
+
const previousOpen = lastOpenRef.current;
|
|
166
|
+
lastOpenRef.current = open;
|
|
167
|
+
// Stop any running animations immediately
|
|
168
|
+
if (runningAnimationRef.current) {
|
|
169
|
+
runningAnimationRef.current.stop();
|
|
170
|
+
runningAnimationRef.current = null;
|
|
171
|
+
}
|
|
172
|
+
if (open) {
|
|
173
|
+
// Opening - set visible immediately
|
|
174
|
+
if (!isVisible) {
|
|
175
|
+
setIsVisible(true);
|
|
176
|
+
}
|
|
177
|
+
// If this is first render (previousOpen is null), set initial position
|
|
178
|
+
// Otherwise animate from current position (handles mid-animation toggle)
|
|
179
|
+
if (previousOpen === null) {
|
|
180
|
+
translateX.setValue(closedPosition);
|
|
181
|
+
backdropOpacity.setValue(0);
|
|
182
|
+
}
|
|
183
|
+
// Animate to open position from wherever we are
|
|
184
|
+
const animation = Animated.parallel([
|
|
185
|
+
Animated.timing(translateX, {
|
|
186
|
+
toValue: 0,
|
|
187
|
+
duration: 200,
|
|
188
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
189
|
+
}),
|
|
190
|
+
Animated.timing(backdropOpacity, {
|
|
191
|
+
toValue: 1,
|
|
192
|
+
duration: 200,
|
|
193
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
194
|
+
}),
|
|
195
|
+
]);
|
|
196
|
+
runningAnimationRef.current = animation;
|
|
197
|
+
animation.start(({ finished }) => {
|
|
198
|
+
if (finished) {
|
|
199
|
+
runningAnimationRef.current = null;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else if (previousOpen === true) {
|
|
204
|
+
// Closing - only animate if we were actually open (skip mount when drawer starts closed)
|
|
205
|
+
const animation = Animated.parallel([
|
|
206
|
+
Animated.timing(translateX, {
|
|
207
|
+
toValue: closedPosition,
|
|
208
|
+
duration: 200,
|
|
209
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
210
|
+
}),
|
|
211
|
+
Animated.timing(backdropOpacity, {
|
|
212
|
+
toValue: 0,
|
|
213
|
+
duration: 200,
|
|
214
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
215
|
+
}),
|
|
216
|
+
]);
|
|
217
|
+
runningAnimationRef.current = animation;
|
|
218
|
+
animation.start(({ finished }) => {
|
|
219
|
+
runningAnimationRef.current = null;
|
|
220
|
+
// Only hide if animation completed (wasn't interrupted)
|
|
221
|
+
if (finished) {
|
|
222
|
+
setIsVisible(false);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Create pan responder for swipe gestures (native only)
|
|
228
|
+
const panResponder = useRef(Platform.OS !== "web" && swipeEnabled
|
|
229
|
+
? PanResponder.create({
|
|
230
|
+
onStartShouldSetPanResponder: () => false,
|
|
231
|
+
onMoveShouldSetPanResponder: (_evt, gestureState) => {
|
|
232
|
+
// Only respond to horizontal swipes
|
|
233
|
+
const isHorizontal = Math.abs(gestureState.dx) > Math.abs(gestureState.dy);
|
|
234
|
+
const isSignificant = Math.abs(gestureState.dx) > 10;
|
|
235
|
+
// For left drawer, only respond to leftward swipes (negative dx)
|
|
236
|
+
// For right drawer, only respond to rightward swipes (positive dx)
|
|
237
|
+
const isCorrectDirection = (side === "left" && gestureState.dx < 0) ||
|
|
238
|
+
(side === "right" && gestureState.dx > 0);
|
|
239
|
+
return isHorizontal && isSignificant && isCorrectDirection;
|
|
240
|
+
},
|
|
241
|
+
onPanResponderMove: (_evt, gestureState) => {
|
|
242
|
+
// Clamp the translation
|
|
243
|
+
let translation;
|
|
244
|
+
if (side === "left") {
|
|
245
|
+
// Left drawer: allow negative translation (closing)
|
|
246
|
+
translation = Math.min(0, Math.max(-width, gestureState.dx));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Right drawer: allow positive translation (closing)
|
|
250
|
+
translation = Math.max(0, Math.min(width, gestureState.dx));
|
|
251
|
+
}
|
|
252
|
+
translateX.setValue(translation);
|
|
253
|
+
// Update backdrop opacity based on drawer position
|
|
254
|
+
const progress = 1 - Math.abs(translation) / width;
|
|
255
|
+
backdropOpacity.setValue(progress);
|
|
256
|
+
},
|
|
257
|
+
onPanResponderRelease: (_evt, gestureState) => {
|
|
258
|
+
const velocity = side === "left" ? -gestureState.vx : gestureState.vx;
|
|
259
|
+
const translation = Math.abs(gestureState.dx);
|
|
260
|
+
// Determine if we should close
|
|
261
|
+
const shouldClose = translation > width * swipeThreshold || velocity > velocityThreshold / 1000;
|
|
262
|
+
if (shouldClose) {
|
|
263
|
+
// Animate to closed
|
|
264
|
+
const targetX = side === "left" ? -width : width;
|
|
265
|
+
Animated.parallel([
|
|
266
|
+
Animated.spring(translateX, {
|
|
267
|
+
toValue: targetX,
|
|
268
|
+
tension: 65,
|
|
269
|
+
friction: 11,
|
|
270
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
271
|
+
}),
|
|
272
|
+
Animated.timing(backdropOpacity, {
|
|
273
|
+
toValue: 0,
|
|
274
|
+
duration: 200,
|
|
275
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
276
|
+
}),
|
|
277
|
+
]).start(() => {
|
|
278
|
+
onOpenChange(false);
|
|
279
|
+
setIsVisible(false);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Snap back to open
|
|
284
|
+
Animated.parallel([
|
|
285
|
+
Animated.spring(translateX, {
|
|
286
|
+
toValue: 0,
|
|
287
|
+
tension: 65,
|
|
288
|
+
friction: 11,
|
|
289
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
290
|
+
}),
|
|
291
|
+
Animated.timing(backdropOpacity, {
|
|
292
|
+
toValue: 1,
|
|
293
|
+
duration: 150,
|
|
294
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
295
|
+
}),
|
|
296
|
+
]).start();
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
: null).current;
|
|
301
|
+
// Handle backdrop press
|
|
302
|
+
const handleBackdropPress = () => {
|
|
303
|
+
if (closeOnBackdropPress) {
|
|
304
|
+
onOpenChange(false);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
// Don't render if not visible
|
|
308
|
+
if (!isVisible && !open) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const shadowStyle = StyleSheet.flatten(getShadowStyle("soft"));
|
|
312
|
+
const drawerStyle = {
|
|
313
|
+
position: "absolute",
|
|
314
|
+
top: 0,
|
|
315
|
+
bottom: 0,
|
|
316
|
+
width,
|
|
317
|
+
[side]: 0,
|
|
318
|
+
backgroundColor: theme.colors.background,
|
|
319
|
+
borderColor: theme.colors.border,
|
|
320
|
+
...(side === "left"
|
|
321
|
+
? { borderRightWidth: 1 }
|
|
322
|
+
: { borderLeftWidth: 1 }),
|
|
323
|
+
...shadowStyle,
|
|
324
|
+
paddingTop: insets.top,
|
|
325
|
+
paddingBottom: insets.bottom,
|
|
326
|
+
...(Platform.OS === "web" && { zIndex: 51 }),
|
|
327
|
+
};
|
|
328
|
+
const contentElement = (_jsx(Portal, { name: "drawer-portal", children: _jsx(FullWindowOverlay, { children: _jsx(DrawerContext.Provider, { value: drawerContext, children: _jsxs(View, { style: StyleSheet.absoluteFill, children: [_jsx(Animated.View, { style: [
|
|
329
|
+
StyleSheet.absoluteFill,
|
|
330
|
+
{
|
|
331
|
+
backgroundColor: theme.colors.overlay,
|
|
332
|
+
opacity: backdropOpacity,
|
|
333
|
+
},
|
|
334
|
+
Platform.OS === "web" && { zIndex: 50 },
|
|
335
|
+
], children: _jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: handleBackdropPress, accessibilityRole: "button", accessibilityLabel: "Close drawer" }) }), _jsx(Animated.View, { style: [
|
|
336
|
+
drawerStyle,
|
|
337
|
+
{
|
|
338
|
+
transform: [{ translateX }],
|
|
339
|
+
},
|
|
340
|
+
styleOverride && typeof styleOverride !== "function"
|
|
341
|
+
? StyleSheet.flatten(styleOverride)
|
|
342
|
+
: undefined,
|
|
343
|
+
], accessibilityViewIsModal: true, ...(Platform.OS === "web" && {
|
|
344
|
+
role: "dialog",
|
|
345
|
+
"aria-modal": true,
|
|
346
|
+
}), ...(panResponder ? panResponder.panHandlers : {}), ...props, children: _jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: children }) }) })] }) }) }) }));
|
|
347
|
+
return contentElement;
|
|
348
|
+
}
|
|
349
|
+
// ============================================================================
|
|
350
|
+
// Drawer Header Component
|
|
351
|
+
// ============================================================================
|
|
352
|
+
function DrawerHeader({ children, style, ...props }) {
|
|
353
|
+
const { theme } = useTheme();
|
|
354
|
+
return (_jsx(View, { style: [
|
|
355
|
+
{
|
|
356
|
+
paddingHorizontal: spacing.md,
|
|
357
|
+
paddingVertical: spacing.md,
|
|
358
|
+
borderBottomWidth: 1,
|
|
359
|
+
borderBottomColor: theme.colors.border,
|
|
360
|
+
},
|
|
361
|
+
style,
|
|
362
|
+
], ...props, children: children }));
|
|
363
|
+
}
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// Drawer Body Component
|
|
366
|
+
// ============================================================================
|
|
367
|
+
function DrawerBody({ children, style, ...props }) {
|
|
368
|
+
return (_jsx(View, { style: [
|
|
369
|
+
{
|
|
370
|
+
flex: 1,
|
|
371
|
+
paddingHorizontal: spacing.md,
|
|
372
|
+
paddingVertical: spacing.md,
|
|
373
|
+
},
|
|
374
|
+
style,
|
|
375
|
+
], ...props, children: children }));
|
|
376
|
+
}
|
|
377
|
+
// ============================================================================
|
|
378
|
+
// Drawer Footer Component
|
|
379
|
+
// ============================================================================
|
|
380
|
+
function DrawerFooter({ children, style, ...props }) {
|
|
381
|
+
const { theme } = useTheme();
|
|
382
|
+
return (_jsx(View, { style: [
|
|
383
|
+
{
|
|
384
|
+
paddingHorizontal: spacing.md,
|
|
385
|
+
paddingVertical: spacing.md,
|
|
386
|
+
borderTopWidth: 1,
|
|
387
|
+
borderTopColor: theme.colors.border,
|
|
388
|
+
},
|
|
389
|
+
style,
|
|
390
|
+
], ...props, children: children }));
|
|
391
|
+
}
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// Close Button Hook (for programmatic close from within drawer)
|
|
394
|
+
// ============================================================================
|
|
395
|
+
function useDrawerClose() {
|
|
396
|
+
const { onOpenChange } = useDrawerContext();
|
|
397
|
+
return () => onOpenChange(false);
|
|
398
|
+
}
|
|
399
|
+
function DrawerClose({ asChild, children, style: styleOverride }) {
|
|
400
|
+
const { onOpenChange } = useDrawerContext();
|
|
401
|
+
const handlePress = () => {
|
|
402
|
+
onOpenChange(false);
|
|
403
|
+
};
|
|
404
|
+
if (asChild) {
|
|
405
|
+
return (_jsx(SlotPressable, { onPress: handlePress, accessibilityRole: "button", accessibilityLabel: "Close", style: [
|
|
406
|
+
Platform.OS === "web" && { cursor: "pointer" },
|
|
407
|
+
styleOverride,
|
|
408
|
+
], children: children }));
|
|
409
|
+
}
|
|
410
|
+
return (_jsx(Pressable, { onPress: handlePress, accessibilityRole: "button", accessibilityLabel: "Close", style: [
|
|
411
|
+
Platform.OS === "web" && { cursor: "pointer" },
|
|
412
|
+
styleOverride,
|
|
413
|
+
], children: children }));
|
|
414
|
+
}
|
|
415
|
+
const Drawer = Object.assign(DrawerRoot, {
|
|
416
|
+
Trigger: DrawerTrigger,
|
|
417
|
+
Content: DrawerContent,
|
|
418
|
+
Header: DrawerHeader,
|
|
419
|
+
Body: DrawerBody,
|
|
420
|
+
Footer: DrawerFooter,
|
|
421
|
+
Close: DrawerClose,
|
|
422
|
+
});
|
|
423
|
+
export { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, DrawerClose, useDrawerClose, };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type TextStyle, View } from "react-native";
|
|
3
|
+
import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu";
|
|
4
|
+
declare const DropdownMenu: {
|
|
5
|
+
({ asChild, onOpenChange: onOpenChangeProp, ref, ...viewProps }: import("react-native").ViewProps & {
|
|
6
|
+
asChild?: boolean;
|
|
7
|
+
} & {
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
} & React.RefAttributes<View>): React.JSX.Element;
|
|
10
|
+
displayName: string;
|
|
11
|
+
};
|
|
12
|
+
declare const DropdownMenuTrigger: {
|
|
13
|
+
({ asChild, onPress: onPressProp, disabled, ref, ...props }: Omit<import("react-native").PressableProps & React.RefAttributes<View>, "ref"> & {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
} & {
|
|
16
|
+
onKeyDown?: (ev: React.KeyboardEvent) => void;
|
|
17
|
+
onKeyUp?: (ev: React.KeyboardEvent) => void;
|
|
18
|
+
} & React.RefAttributes<import("@rn-primitives/dropdown-menu").TriggerRef>): React.JSX.Element;
|
|
19
|
+
displayName: string;
|
|
20
|
+
};
|
|
21
|
+
declare const DropdownMenuGroup: {
|
|
22
|
+
({ asChild, ref, ...props }: import("react-native").ViewProps & {
|
|
23
|
+
asChild?: boolean;
|
|
24
|
+
} & React.RefAttributes<View>): React.JSX.Element;
|
|
25
|
+
displayName: string;
|
|
26
|
+
};
|
|
27
|
+
declare const DropdownMenuPortal: typeof DropdownMenuPrimitive.Portal;
|
|
28
|
+
declare const DropdownMenuSub: {
|
|
29
|
+
({ asChild, defaultOpen, open: openProp, onOpenChange: onOpenChangeProp, ref, ...props }: import("react-native").ViewProps & {
|
|
30
|
+
asChild?: boolean;
|
|
31
|
+
} & {
|
|
32
|
+
defaultOpen?: boolean;
|
|
33
|
+
open?: boolean;
|
|
34
|
+
onOpenChange?: (value: boolean) => void;
|
|
35
|
+
} & React.RefAttributes<View>): React.JSX.Element;
|
|
36
|
+
displayName: string;
|
|
37
|
+
};
|
|
38
|
+
declare const DropdownMenuRadioGroup: {
|
|
39
|
+
({ asChild, value, onValueChange, ref, ...props }: import("react-native").ViewProps & {
|
|
40
|
+
asChild?: boolean;
|
|
41
|
+
} & {
|
|
42
|
+
value: string | undefined;
|
|
43
|
+
onValueChange: (value: string) => void;
|
|
44
|
+
} & React.RefAttributes<View>): React.JSX.Element;
|
|
45
|
+
displayName: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* DropdownMenuSubTrigger Component
|
|
49
|
+
* Trigger for sub-menus with automatic chevron icon
|
|
50
|
+
* Shows ChevronRight on web, ChevronDown/ChevronUp on native based on open state
|
|
51
|
+
*/
|
|
52
|
+
type DropdownMenuSubTriggerProps = DropdownMenuPrimitive.SubTriggerProps & {
|
|
53
|
+
inset?: boolean;
|
|
54
|
+
};
|
|
55
|
+
declare function DropdownMenuSubTrigger({ inset, children, style: styleOverride, ...props }: DropdownMenuSubTriggerProps): import("react/jsx-runtime").JSX.Element;
|
|
56
|
+
/**
|
|
57
|
+
* DropdownMenuSubContent Component
|
|
58
|
+
* Content container for sub-menus
|
|
59
|
+
*/
|
|
60
|
+
type DropdownMenuSubContentProps = DropdownMenuPrimitive.SubContentProps;
|
|
61
|
+
declare function DropdownMenuSubContent({ style: styleOverride, ...props }: DropdownMenuSubContentProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
/**
|
|
63
|
+
* DropdownMenuContent Component
|
|
64
|
+
* Main dropdown content with portal, overlay, and animation
|
|
65
|
+
*
|
|
66
|
+
* Positioning props:
|
|
67
|
+
* - side: Which side of the trigger to position on ("top" | "bottom" | "left" | "right")
|
|
68
|
+
* - align: Alignment relative to the trigger ("start" | "center" | "end")
|
|
69
|
+
* - sideOffset: Distance from the trigger in pixels (default: 4)
|
|
70
|
+
*/
|
|
71
|
+
type DropdownMenuContentProps = DropdownMenuPrimitive.ContentProps & {
|
|
72
|
+
portalHost?: string;
|
|
73
|
+
};
|
|
74
|
+
declare function DropdownMenuContent({ side, align, sideOffset, portalHost, style: styleOverride, ...props }: DropdownMenuContentProps): import("react/jsx-runtime").JSX.Element;
|
|
75
|
+
/**
|
|
76
|
+
* DropdownMenuItem Component
|
|
77
|
+
* Standard menu item with optional destructive variant
|
|
78
|
+
*/
|
|
79
|
+
type DropdownMenuItemProps = DropdownMenuPrimitive.ItemProps & {
|
|
80
|
+
inset?: boolean;
|
|
81
|
+
variant?: "default" | "destructive";
|
|
82
|
+
};
|
|
83
|
+
declare function DropdownMenuItem({ inset, variant, style: styleOverride, ...props }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
84
|
+
/**
|
|
85
|
+
* DropdownMenuCheckboxItem Component
|
|
86
|
+
* Menu item with checkbox indicator
|
|
87
|
+
*/
|
|
88
|
+
type DropdownMenuCheckboxItemProps = DropdownMenuPrimitive.CheckboxItemProps;
|
|
89
|
+
declare function DropdownMenuCheckboxItem({ children, style: styleOverride, ...props }: DropdownMenuCheckboxItemProps): import("react/jsx-runtime").JSX.Element;
|
|
90
|
+
/**
|
|
91
|
+
* DropdownMenuRadioItem Component
|
|
92
|
+
* Menu item with radio button indicator
|
|
93
|
+
*/
|
|
94
|
+
type DropdownMenuRadioItemProps = DropdownMenuPrimitive.RadioItemProps;
|
|
95
|
+
declare function DropdownMenuRadioItem({ children, style: styleOverride, ...props }: DropdownMenuRadioItemProps): import("react/jsx-runtime").JSX.Element;
|
|
96
|
+
/**
|
|
97
|
+
* DropdownMenuLabel Component
|
|
98
|
+
* Label for menu sections
|
|
99
|
+
*/
|
|
100
|
+
type DropdownMenuLabelProps = DropdownMenuPrimitive.LabelProps & {
|
|
101
|
+
inset?: boolean;
|
|
102
|
+
};
|
|
103
|
+
declare function DropdownMenuLabel({ inset, style: styleOverride, ...props }: DropdownMenuLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
104
|
+
/**
|
|
105
|
+
* DropdownMenuSeparator Component
|
|
106
|
+
* Visual divider between menu sections
|
|
107
|
+
*/
|
|
108
|
+
type DropdownMenuSeparatorProps = DropdownMenuPrimitive.SeparatorProps;
|
|
109
|
+
declare function DropdownMenuSeparator({ style: styleOverride, ...props }: DropdownMenuSeparatorProps): import("react/jsx-runtime").JSX.Element;
|
|
110
|
+
/**
|
|
111
|
+
* DropdownMenuShortcut Component
|
|
112
|
+
* Text component for displaying keyboard shortcuts
|
|
113
|
+
*/
|
|
114
|
+
interface DropdownMenuShortcutProps {
|
|
115
|
+
children: React.ReactNode;
|
|
116
|
+
style?: TextStyle;
|
|
117
|
+
}
|
|
118
|
+
declare function DropdownMenuShortcut({ style: styleOverride, ...props }: DropdownMenuShortcutProps): import("react/jsx-runtime").JSX.Element;
|
|
119
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, };
|
|
120
|
+
export type { DropdownMenuSubTriggerProps, DropdownMenuSubContentProps, DropdownMenuContentProps, DropdownMenuItemProps, DropdownMenuCheckboxItemProps, DropdownMenuRadioItemProps, DropdownMenuLabelProps, DropdownMenuSeparatorProps, DropdownMenuShortcutProps, };
|