@impulse-ui-native/flyout 1.0.10 → 2.0.1

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 ADDED
@@ -0,0 +1,36 @@
1
+ # @impulse-ui-native/flyout
2
+
3
+ A token-driven, gesture-enabled sheet that can enter from the top or bottom of the screen.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ pnpm add @impulse-ui-native/flyout
9
+ ```
10
+
11
+ This package uses React Native Gesture Handler, Reanimated, Safe Area Context, and Worklets. Complete their native setup and render the flyout inside both `GestureHandlerRootView` and `SafeAreaProvider`.
12
+
13
+ ## Main exports
14
+
15
+ - `Flyout` renders the sheet, overlay, drag handle, title, safe-area padding, and lifecycle animations.
16
+ - `FlyoutProps` adds `open`, `placement`, `topOffset`, and `bottomOffset` to the common layer contract.
17
+
18
+ ## Usage
19
+
20
+ ```tsx
21
+ import { Flyout } from "@impulse-ui-native/flyout";
22
+
23
+ <Flyout
24
+ id="filters"
25
+ title="Filters"
26
+ open={filtersOpen}
27
+ placement="bottom"
28
+ onClose={() => setFiltersOpen(false)}
29
+ >
30
+ <Filters />
31
+ </Flyout>;
32
+ ```
33
+
34
+ The overlay closes the sheet when pressed. A drag past half the measured sheet height, or a sufficiently fast swipe toward the edge, closes it as well.
35
+
36
+ For app-wide imperative sheets, register flyouts through `@impulse-ui-native/layers` and mount `LayerCenter` once.
package/dist/index.d.mts CHANGED
@@ -1,10 +1,12 @@
1
1
  import * as react from 'react';
2
2
  import { PropsWithChildren } from 'react';
3
- import { LayerComponentBaseProps } from '@impulse-ui-native/layer-manager-types';
3
+ import { LayerComponentBaseProps } from '@impulse-ui-native/types';
4
4
 
5
5
  interface FlyoutProps extends LayerComponentBaseProps {
6
6
  open?: boolean;
7
7
  placement?: "top" | "bottom";
8
+ topOffset?: number;
9
+ bottomOffset?: number;
8
10
  }
9
11
 
10
12
  declare const Flyout: react.NamedExoticComponent<PropsWithChildren<FlyoutProps>>;
package/dist/index.js ADDED
@@ -0,0 +1,343 @@
1
+ // src/components/flyout.tsx
2
+ import {
3
+ Fragment,
4
+ memo as memo3,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo as useMemo2,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import {
12
+ Pressable,
13
+ StyleSheet as StyleSheet3,
14
+ useWindowDimensions
15
+ } from "react-native";
16
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
17
+ import Animated2, {
18
+ Easing,
19
+ useAnimatedStyle,
20
+ useSharedValue,
21
+ withSpring,
22
+ withTiming
23
+ } from "react-native-reanimated";
24
+ import {
25
+ SafeAreaView,
26
+ useSafeAreaInsets
27
+ } from "react-native-safe-area-context";
28
+ import { scheduleOnRN } from "react-native-worklets";
29
+ import { View as View3 } from "@impulse-ui-native/primitives";
30
+ import { useTheme } from "@impulse-ui-native/theme";
31
+
32
+ // src/constants/flyout-handle.constants.ts
33
+ var FlyoutHandleHeight = 32;
34
+
35
+ // src/components/flyout-handle.tsx
36
+ import { memo } from "react";
37
+ import { StyleSheet } from "react-native";
38
+ import Animated from "react-native-reanimated";
39
+ import { View } from "@impulse-ui-native/primitives";
40
+ import { useThemedStyles } from "@impulse-ui-native/theme";
41
+ import { jsx } from "react/jsx-runtime";
42
+ var FlyoutHandle = memo(function FlyoutHandle2(props) {
43
+ const styles = useThemedStyles(themedStyles, props, [props.placement]);
44
+ return /* @__PURE__ */ jsx(Animated.View, { style: styles.flyoutHandleContainer, children: /* @__PURE__ */ jsx(View, { style: styles.flyoutHandle }) });
45
+ });
46
+ function themedStyles(theme, { placement }) {
47
+ const handleTokens = theme.components.flyout.handle;
48
+ return StyleSheet.create({
49
+ flyoutHandle: {
50
+ alignSelf: "center",
51
+ backgroundColor: handleTokens.backgroundColor,
52
+ width: handleTokens.width,
53
+ height: handleTokens.height,
54
+ borderRadius: handleTokens.borderRadius
55
+ },
56
+ flyoutHandleContainer: {
57
+ position: "absolute",
58
+ top: placement === "bottom" ? -handleTokens.containerHeight : void 0,
59
+ bottom: placement === "top" ? -handleTokens.containerHeight : void 0,
60
+ width: "100%",
61
+ justifyContent: "center",
62
+ alignItems: "center",
63
+ height: handleTokens.containerHeight
64
+ }
65
+ });
66
+ }
67
+
68
+ // src/components/flyout-title.tsx
69
+ import { memo as memo2, useMemo } from "react";
70
+ import { StyleSheet as StyleSheet2 } from "react-native";
71
+ import { Typography, View as View2 } from "@impulse-ui-native/primitives";
72
+ import { useThemedStyles as useThemedStyles2 } from "@impulse-ui-native/theme";
73
+ import { jsx as jsx2 } from "react/jsx-runtime";
74
+ var FlyoutTitle = memo2(function FlyoutTitle2(props) {
75
+ const styles = useThemedStyles2(themedStyles2);
76
+ const title = useMemo(() => {
77
+ if (typeof props.title === "string") {
78
+ return /* @__PURE__ */ jsx2(Typography.Title3, { children: props.title });
79
+ }
80
+ return props.title;
81
+ }, [props.title]);
82
+ if (!props.title) {
83
+ return null;
84
+ }
85
+ return /* @__PURE__ */ jsx2(View2, { style: styles.container, children: title });
86
+ });
87
+ function themedStyles2(theme) {
88
+ const titleTokens = theme.components.flyout.title;
89
+ return StyleSheet2.create({
90
+ container: {
91
+ paddingVertical: titleTokens.paddingVertical,
92
+ paddingHorizontal: titleTokens.paddingHorizontal,
93
+ alignItems: "center"
94
+ }
95
+ });
96
+ }
97
+
98
+ // src/components/flyout.tsx
99
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
100
+ var EnterAnimationConfig = {
101
+ damping: 50,
102
+ stiffness: 300,
103
+ mass: 1,
104
+ overshootClamping: true
105
+ };
106
+ var ExitAnimationConfig = {
107
+ duration: 150,
108
+ easing: Easing.in(Easing.quad)
109
+ };
110
+ var Flyout = memo3(function Flyout2(props) {
111
+ const {
112
+ id,
113
+ children,
114
+ placement = "bottom",
115
+ layer = 0,
116
+ title,
117
+ open,
118
+ topOffset,
119
+ bottomOffset,
120
+ onCloseFinished,
121
+ onClose,
122
+ onOpen,
123
+ onOpenFinished
124
+ } = props;
125
+ const theme = useTheme();
126
+ const insets = useSafeAreaInsets();
127
+ const windowDimensions = useWindowDimensions();
128
+ const flyoutTokens = theme.components.flyout;
129
+ const placementOffset = placement === "top" ? topOffset : bottomOffset;
130
+ const screenHeight = windowDimensions.height;
131
+ const maxHeight = screenHeight * flyoutTokens.maxHeightRatio;
132
+ const zIndex = flyoutTokens.zIndexBase + layer;
133
+ const [mounted, setMounted] = useState(open);
134
+ const [measuredHeight, setMeasuredHeight] = useState(null);
135
+ const [hasMeasured, setHasMeasured] = useState(false);
136
+ const [isTouchable, setIsTouchable] = useState(false);
137
+ const didOpen = useRef(false);
138
+ const isClosing = useRef(false);
139
+ const translateY = useSharedValue(
140
+ placement === "top" ? -screenHeight : screenHeight
141
+ );
142
+ const opacity = useSharedValue(0);
143
+ const onLayout = useCallback(
144
+ (event) => {
145
+ if (measuredHeight !== null) {
146
+ return;
147
+ }
148
+ const rawHeight = event.nativeEvent.layout.height;
149
+ const clampedHeight = rawHeight + insets[placement] + FlyoutHandleHeight;
150
+ translateY.value = placement === "top" ? -clampedHeight : clampedHeight;
151
+ setMeasuredHeight(clampedHeight);
152
+ setHasMeasured(true);
153
+ },
154
+ [insets, measuredHeight, placement, translateY]
155
+ );
156
+ const animateOut = useCallback(() => {
157
+ if (measuredHeight === null || isClosing.current) {
158
+ return;
159
+ }
160
+ isClosing.current = true;
161
+ setIsTouchable(false);
162
+ onClose?.(id);
163
+ const target = placement === "top" ? -measuredHeight : measuredHeight;
164
+ translateY.value = withTiming(target, ExitAnimationConfig, (finished) => {
165
+ if (finished) {
166
+ if (onCloseFinished) {
167
+ scheduleOnRN(onCloseFinished, id);
168
+ }
169
+ scheduleOnRN(setMounted, false);
170
+ scheduleOnRN(setMeasuredHeight, null);
171
+ scheduleOnRN(setHasMeasured, false);
172
+ }
173
+ });
174
+ opacity.value = withTiming(0, ExitAnimationConfig);
175
+ didOpen.current = false;
176
+ }, [
177
+ id,
178
+ measuredHeight,
179
+ onClose,
180
+ onCloseFinished,
181
+ opacity,
182
+ placement,
183
+ translateY
184
+ ]);
185
+ const dragGesture = useMemo2(
186
+ () => Gesture.Pan().enabled(hasMeasured).onUpdate((event) => {
187
+ if (placement === "bottom") {
188
+ translateY.value = Math.max(0, event.translationY);
189
+ return;
190
+ }
191
+ translateY.value = Math.min(0, event.translationY);
192
+ }).onEnd((event) => {
193
+ if (measuredHeight === null) {
194
+ return;
195
+ }
196
+ let shouldClose = false;
197
+ if (placement === "bottom") {
198
+ shouldClose = event.translationY > measuredHeight * 0.5 || event.velocityY > 1500;
199
+ }
200
+ if (placement === "top") {
201
+ shouldClose = event.translationY < -measuredHeight * 0.5 || event.velocityY < -1500;
202
+ }
203
+ if (shouldClose) {
204
+ scheduleOnRN(animateOut);
205
+ return;
206
+ }
207
+ translateY.value = withSpring(0, EnterAnimationConfig);
208
+ opacity.value = withSpring(
209
+ flyoutTokens.overlayVisibleOpacity,
210
+ EnterAnimationConfig
211
+ );
212
+ }),
213
+ [
214
+ animateOut,
215
+ flyoutTokens.overlayVisibleOpacity,
216
+ hasMeasured,
217
+ measuredHeight,
218
+ opacity,
219
+ placement,
220
+ translateY
221
+ ]
222
+ );
223
+ const animatedStyle = useAnimatedStyle(() => {
224
+ const offset = placement === "bottom" ? -(placementOffset ?? 0) : placementOffset ?? 0;
225
+ return {
226
+ transform: [{ translateY: translateY.value + offset }],
227
+ zIndex
228
+ };
229
+ });
230
+ const overlayStyle = useAnimatedStyle(() => ({
231
+ opacity: opacity.value,
232
+ zIndex,
233
+ backgroundColor: flyoutTokens.overlayColor
234
+ }));
235
+ const edges = useMemo2(() => {
236
+ return [placement, "left", "right"];
237
+ }, [placement]);
238
+ const styles = useMemo2(() => {
239
+ return themedStyles3(theme, placement, maxHeight);
240
+ }, [theme, placement, maxHeight]);
241
+ useEffect(() => {
242
+ if (!open) {
243
+ return;
244
+ }
245
+ setMounted(true);
246
+ setIsTouchable(false);
247
+ setMeasuredHeight(null);
248
+ setHasMeasured(false);
249
+ isClosing.current = false;
250
+ didOpen.current = false;
251
+ translateY.value = placement === "top" ? -screenHeight : screenHeight;
252
+ opacity.value = 0;
253
+ }, [open, opacity, placement, screenHeight, translateY]);
254
+ useEffect(() => {
255
+ if (!open || measuredHeight === null || !hasMeasured || didOpen.current) {
256
+ return;
257
+ }
258
+ setIsTouchable(true);
259
+ onOpen?.(id);
260
+ translateY.value = withSpring(0, EnterAnimationConfig, (finished) => {
261
+ if (finished && onOpenFinished) {
262
+ scheduleOnRN(onOpenFinished, id);
263
+ }
264
+ });
265
+ opacity.value = withSpring(
266
+ flyoutTokens.overlayVisibleOpacity,
267
+ EnterAnimationConfig
268
+ );
269
+ didOpen.current = true;
270
+ }, [
271
+ flyoutTokens.overlayVisibleOpacity,
272
+ hasMeasured,
273
+ id,
274
+ measuredHeight,
275
+ onOpen,
276
+ onOpenFinished,
277
+ opacity,
278
+ open,
279
+ translateY
280
+ ]);
281
+ useEffect(() => {
282
+ if (!open && didOpen.current) {
283
+ animateOut();
284
+ }
285
+ }, [animateOut, open]);
286
+ if (!mounted) {
287
+ return null;
288
+ }
289
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
290
+ /* @__PURE__ */ jsx3(
291
+ Animated2.View,
292
+ {
293
+ pointerEvents: isTouchable ? "auto" : "none",
294
+ style: [StyleSheet3.absoluteFill, overlayStyle],
295
+ children: /* @__PURE__ */ jsx3(Pressable, { onPress: animateOut, style: StyleSheet3.absoluteFill })
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsx3(GestureDetector, { gesture: dragGesture, children: /* @__PURE__ */ jsx3(
299
+ Animated2.View,
300
+ {
301
+ onLayout,
302
+ style: [
303
+ styles.container,
304
+ !hasMeasured && styles.hidden,
305
+ animatedStyle
306
+ ],
307
+ children: /* @__PURE__ */ jsxs(SafeAreaView, { edges, children: [
308
+ placement === "bottom" ? /* @__PURE__ */ jsx3(FlyoutHandle, { placement }) : null,
309
+ title ? /* @__PURE__ */ jsx3(FlyoutTitle, { title }) : null,
310
+ /* @__PURE__ */ jsx3(View3, { style: styles.content, children }),
311
+ placement === "top" ? /* @__PURE__ */ jsx3(FlyoutHandle, { placement }) : null
312
+ ] })
313
+ }
314
+ ) })
315
+ ] });
316
+ });
317
+ function themedStyles3(theme, placement, maxHeight) {
318
+ const flyoutTokens = theme.components.flyout;
319
+ return StyleSheet3.create({
320
+ container: {
321
+ position: "absolute",
322
+ [placement]: 0,
323
+ left: 0,
324
+ right: 0,
325
+ backgroundColor: flyoutTokens.backgroundColor,
326
+ borderTopLeftRadius: placement === "bottom" ? flyoutTokens.borderRadius : 0,
327
+ borderTopRightRadius: placement === "bottom" ? flyoutTokens.borderRadius : 0,
328
+ borderBottomLeftRadius: placement === "top" ? flyoutTokens.borderRadius : 0,
329
+ borderBottomRightRadius: placement === "top" ? flyoutTokens.borderRadius : 0
330
+ },
331
+ content: {
332
+ paddingHorizontal: flyoutTokens.contentPaddingHorizontal,
333
+ maxHeight
334
+ },
335
+ hidden: {
336
+ opacity: flyoutTokens.hiddenOpacity
337
+ }
338
+ });
339
+ }
340
+ export {
341
+ Flyout
342
+ };
343
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/flyout.tsx","../src/constants/flyout-handle.constants.ts","../src/components/flyout-handle.tsx","../src/components/flyout-title.tsx"],"sourcesContent":["import {\r\n Fragment,\r\n memo,\r\n PropsWithChildren,\r\n useCallback,\r\n useEffect,\r\n useMemo,\r\n useRef,\r\n useState,\r\n} from \"react\";\r\nimport {\r\n LayoutChangeEvent,\r\n Pressable,\r\n StyleSheet,\r\n useWindowDimensions,\r\n} from \"react-native\";\r\nimport { Gesture, GestureDetector } from \"react-native-gesture-handler\";\r\nimport Animated, {\r\n Easing,\r\n useAnimatedStyle,\r\n useSharedValue,\r\n withSpring,\r\n withTiming,\r\n} from \"react-native-reanimated\";\r\nimport {\r\n Edge,\r\n SafeAreaView,\r\n useSafeAreaInsets,\r\n} from \"react-native-safe-area-context\";\r\nimport { scheduleOnRN } from \"react-native-worklets\";\r\n\r\nimport { View } from \"@impulse-ui-native/primitives\";\r\nimport { AppTheme, useTheme } from \"@impulse-ui-native/theme\";\r\n\r\nimport { FlyoutHandleHeight } from \"../constants\";\r\nimport { FlyoutProps } from \"../types\";\r\nimport { FlyoutHandle } from \"./flyout-handle\";\r\nimport { FlyoutTitle } from \"./flyout-title\";\r\n\r\nconst EnterAnimationConfig = {\r\n damping: 50,\r\n stiffness: 300,\r\n mass: 1,\r\n overshootClamping: true,\r\n};\r\n\r\nconst ExitAnimationConfig = {\r\n duration: 150,\r\n easing: Easing.in(Easing.quad),\r\n};\r\n\r\nexport const Flyout = memo(function Flyout(\r\n props: PropsWithChildren<FlyoutProps>,\r\n) {\r\n const {\r\n id,\r\n children,\r\n placement = \"bottom\",\r\n layer = 0,\r\n title,\r\n open,\r\n topOffset,\r\n bottomOffset,\r\n onCloseFinished,\r\n onClose,\r\n onOpen,\r\n onOpenFinished,\r\n } = props;\r\n\r\n const theme = useTheme();\r\n const insets = useSafeAreaInsets();\r\n\r\n const windowDimensions = useWindowDimensions();\r\n\r\n const flyoutTokens = theme.components.flyout;\r\n\r\n const placementOffset = placement === \"top\" ? topOffset : bottomOffset;\r\n const screenHeight = windowDimensions.height;\r\n const maxHeight = screenHeight * flyoutTokens.maxHeightRatio;\r\n const zIndex = flyoutTokens.zIndexBase + layer;\r\n\r\n const [mounted, setMounted] = useState(open);\r\n const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);\r\n const [hasMeasured, setHasMeasured] = useState(false);\r\n const [isTouchable, setIsTouchable] = useState(false);\r\n\r\n const didOpen = useRef(false);\r\n const isClosing = useRef(false);\r\n\r\n const translateY = useSharedValue(\r\n placement === \"top\" ? -screenHeight : screenHeight,\r\n );\r\n const opacity = useSharedValue(0);\r\n\r\n const onLayout = useCallback(\r\n (event: LayoutChangeEvent) => {\r\n if (measuredHeight !== null) {\r\n return;\r\n }\r\n\r\n const rawHeight = event.nativeEvent.layout.height;\r\n const clampedHeight = rawHeight + insets[placement] + FlyoutHandleHeight;\r\n\r\n translateY.value = placement === \"top\" ? -clampedHeight : clampedHeight;\r\n\r\n setMeasuredHeight(clampedHeight);\r\n setHasMeasured(true);\r\n },\r\n [insets, measuredHeight, placement, translateY],\r\n );\r\n\r\n const animateOut = useCallback(() => {\r\n if (measuredHeight === null || isClosing.current) {\r\n return;\r\n }\r\n\r\n isClosing.current = true;\r\n setIsTouchable(false);\r\n onClose?.(id);\r\n\r\n const target = placement === \"top\" ? -measuredHeight : measuredHeight;\r\n\r\n translateY.value = withTiming(target, ExitAnimationConfig, (finished) => {\r\n if (finished) {\r\n if (onCloseFinished) {\r\n scheduleOnRN(onCloseFinished, id);\r\n }\r\n\r\n scheduleOnRN(setMounted, false);\r\n scheduleOnRN(setMeasuredHeight, null);\r\n scheduleOnRN(setHasMeasured, false);\r\n }\r\n });\r\n\r\n opacity.value = withTiming(0, ExitAnimationConfig);\r\n didOpen.current = false;\r\n }, [\r\n id,\r\n measuredHeight,\r\n onClose,\r\n onCloseFinished,\r\n opacity,\r\n placement,\r\n translateY,\r\n ]);\r\n\r\n const dragGesture = useMemo(\r\n () =>\r\n Gesture.Pan()\r\n .enabled(hasMeasured)\r\n .onUpdate((event) => {\r\n if (placement === \"bottom\") {\r\n translateY.value = Math.max(0, event.translationY);\r\n return;\r\n }\r\n\r\n translateY.value = Math.min(0, event.translationY);\r\n })\r\n .onEnd((event) => {\r\n if (measuredHeight === null) {\r\n return;\r\n }\r\n\r\n let shouldClose = false;\r\n\r\n if (placement === \"bottom\") {\r\n shouldClose =\r\n event.translationY > measuredHeight * 0.5 ||\r\n event.velocityY > 1500;\r\n }\r\n\r\n if (placement === \"top\") {\r\n shouldClose =\r\n event.translationY < -measuredHeight * 0.5 ||\r\n event.velocityY < -1500;\r\n }\r\n\r\n if (shouldClose) {\r\n scheduleOnRN(animateOut);\r\n return;\r\n }\r\n\r\n translateY.value = withSpring(0, EnterAnimationConfig);\r\n opacity.value = withSpring(\r\n flyoutTokens.overlayVisibleOpacity,\r\n EnterAnimationConfig,\r\n );\r\n }),\r\n [\r\n animateOut,\r\n flyoutTokens.overlayVisibleOpacity,\r\n hasMeasured,\r\n measuredHeight,\r\n opacity,\r\n placement,\r\n translateY,\r\n ],\r\n );\r\n\r\n const animatedStyle = useAnimatedStyle(() => {\r\n const offset =\r\n placement === \"bottom\" ? -(placementOffset ?? 0) : (placementOffset ?? 0);\r\n\r\n return {\r\n transform: [{ translateY: translateY.value + offset }],\r\n zIndex,\r\n };\r\n });\r\n\r\n const overlayStyle = useAnimatedStyle(() => ({\r\n opacity: opacity.value,\r\n zIndex,\r\n backgroundColor: flyoutTokens.overlayColor,\r\n }));\r\n\r\n const edges = useMemo<Edge[]>(() => {\r\n return [placement, \"left\", \"right\"];\r\n }, [placement]);\r\n\r\n const styles = useMemo(() => {\r\n return themedStyles(theme, placement, maxHeight);\r\n }, [theme, placement, maxHeight]);\r\n\r\n useEffect(() => {\r\n if (!open) {\r\n return;\r\n }\r\n\r\n setMounted(true);\r\n setIsTouchable(false);\r\n setMeasuredHeight(null);\r\n setHasMeasured(false);\r\n\r\n isClosing.current = false;\r\n didOpen.current = false;\r\n\r\n translateY.value = placement === \"top\" ? -screenHeight : screenHeight;\r\n opacity.value = 0;\r\n }, [open, opacity, placement, screenHeight, translateY]);\r\n\r\n useEffect(() => {\r\n if (!open || measuredHeight === null || !hasMeasured || didOpen.current) {\r\n return;\r\n }\r\n\r\n setIsTouchable(true);\r\n onOpen?.(id);\r\n\r\n translateY.value = withSpring(0, EnterAnimationConfig, (finished) => {\r\n if (finished && onOpenFinished) {\r\n scheduleOnRN(onOpenFinished, id);\r\n }\r\n });\r\n\r\n opacity.value = withSpring(\r\n flyoutTokens.overlayVisibleOpacity,\r\n EnterAnimationConfig,\r\n );\r\n\r\n didOpen.current = true;\r\n }, [\r\n flyoutTokens.overlayVisibleOpacity,\r\n hasMeasured,\r\n id,\r\n measuredHeight,\r\n onOpen,\r\n onOpenFinished,\r\n opacity,\r\n open,\r\n translateY,\r\n ]);\r\n\r\n useEffect(() => {\r\n if (!open && didOpen.current) {\r\n animateOut();\r\n }\r\n }, [animateOut, open]);\r\n\r\n if (!mounted) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Fragment>\r\n <Animated.View\r\n pointerEvents={isTouchable ? \"auto\" : \"none\"}\r\n style={[StyleSheet.absoluteFill, overlayStyle]}\r\n >\r\n <Pressable onPress={animateOut} style={StyleSheet.absoluteFill} />\r\n </Animated.View>\r\n\r\n <GestureDetector gesture={dragGesture}>\r\n <Animated.View\r\n onLayout={onLayout}\r\n style={[\r\n styles.container,\r\n !hasMeasured && styles.hidden,\r\n animatedStyle,\r\n ]}\r\n >\r\n <SafeAreaView edges={edges}>\r\n {placement === \"bottom\" ? (\r\n <FlyoutHandle placement={placement} />\r\n ) : null}\r\n\r\n {title ? <FlyoutTitle title={title} /> : null}\r\n\r\n <View style={styles.content}>{children}</View>\r\n\r\n {placement === \"top\" ? (\r\n <FlyoutHandle placement={placement} />\r\n ) : null}\r\n </SafeAreaView>\r\n </Animated.View>\r\n </GestureDetector>\r\n </Fragment>\r\n );\r\n});\r\n\r\nfunction themedStyles(\r\n theme: AppTheme,\r\n placement: \"top\" | \"bottom\",\r\n maxHeight: number,\r\n) {\r\n const flyoutTokens = theme.components.flyout;\r\n\r\n return StyleSheet.create({\r\n container: {\r\n position: \"absolute\",\r\n [placement]: 0,\r\n left: 0,\r\n right: 0,\r\n backgroundColor: flyoutTokens.backgroundColor,\r\n borderTopLeftRadius:\r\n placement === \"bottom\" ? flyoutTokens.borderRadius : 0,\r\n borderTopRightRadius:\r\n placement === \"bottom\" ? flyoutTokens.borderRadius : 0,\r\n borderBottomLeftRadius:\r\n placement === \"top\" ? flyoutTokens.borderRadius : 0,\r\n borderBottomRightRadius:\r\n placement === \"top\" ? flyoutTokens.borderRadius : 0,\r\n },\r\n content: {\r\n paddingHorizontal: flyoutTokens.contentPaddingHorizontal,\r\n maxHeight,\r\n },\r\n hidden: {\r\n opacity: flyoutTokens.hiddenOpacity,\r\n },\r\n });\r\n}\r\n","export const FlyoutHandleHeight = 32;\r\n","import { memo } from \"react\";\r\nimport { StyleSheet } from \"react-native\";\r\nimport Animated from \"react-native-reanimated\";\r\n\r\nimport { View } from \"@impulse-ui-native/primitives\";\r\nimport { AppTheme, useThemedStyles } from \"@impulse-ui-native/theme\";\r\n\r\ninterface FlyoutHandleProps {\r\n placement: \"top\" | \"bottom\";\r\n}\r\n\r\nexport const FlyoutHandle = memo(function FlyoutHandle(\r\n props: FlyoutHandleProps,\r\n) {\r\n const styles = useThemedStyles(themedStyles, props, [props.placement]);\r\n\r\n return (\r\n <Animated.View style={styles.flyoutHandleContainer}>\r\n <View style={styles.flyoutHandle} />\r\n </Animated.View>\r\n );\r\n});\r\n\r\nfunction themedStyles(\r\n theme: AppTheme,\r\n { placement }: { placement: \"top\" | \"bottom\" },\r\n) {\r\n const handleTokens = theme.components.flyout.handle;\r\n\r\n return StyleSheet.create({\r\n flyoutHandle: {\r\n alignSelf: \"center\",\r\n backgroundColor: handleTokens.backgroundColor,\r\n width: handleTokens.width,\r\n height: handleTokens.height,\r\n borderRadius: handleTokens.borderRadius,\r\n },\r\n flyoutHandleContainer: {\r\n position: \"absolute\",\r\n top: placement === \"bottom\" ? -handleTokens.containerHeight : undefined,\r\n bottom: placement === \"top\" ? -handleTokens.containerHeight : undefined,\r\n width: \"100%\",\r\n justifyContent: \"center\",\r\n alignItems: \"center\",\r\n height: handleTokens.containerHeight,\r\n },\r\n });\r\n}\r\n","import { memo, ReactNode, useMemo } from \"react\";\r\nimport { StyleSheet } from \"react-native\";\r\n\r\nimport { Typography, View } from \"@impulse-ui-native/primitives\";\r\nimport { AppTheme, useThemedStyles } from \"@impulse-ui-native/theme\";\r\n\r\ninterface FlyoutTitleProps {\r\n title?: ReactNode | string;\r\n}\r\n\r\nexport const FlyoutTitle = memo(function FlyoutTitle(props: FlyoutTitleProps) {\r\n const styles = useThemedStyles(themedStyles);\r\n\r\n const title = useMemo(() => {\r\n if (typeof props.title === \"string\") {\r\n return <Typography.Title3>{props.title}</Typography.Title3>;\r\n }\r\n\r\n return props.title;\r\n }, [props.title]);\r\n\r\n if (!props.title) {\r\n return null;\r\n }\r\n\r\n return <View style={styles.container}>{title}</View>;\r\n});\r\n\r\nfunction themedStyles(theme: AppTheme) {\r\n const titleTokens = theme.components.flyout.title;\r\n\r\n return StyleSheet.create({\r\n container: {\r\n paddingVertical: titleTokens.paddingVertical,\r\n paddingHorizontal: titleTokens.paddingHorizontal,\r\n alignItems: \"center\",\r\n },\r\n });\r\n}\r\n"],"mappings":"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","names":["memo","useMemo","StyleSheet","Animated","View","FlyoutHandle","memo","StyleSheet","View","useThemedStyles","jsx","FlyoutTitle","themedStyles","jsx","memo","Flyout","useMemo","themedStyles","Animated","StyleSheet","View"]}
package/package.json CHANGED
@@ -1,49 +1,59 @@
1
1
  {
2
2
  "name": "@impulse-ui-native/flyout",
3
- "version": "1.0.10",
3
+ "version": "2.0.1",
4
4
  "source": "src/index.ts",
5
- "main": "dist/index.cjs.js",
6
- "module": "dist/index.esm.js",
7
- "types": "dist/index.d.ts",
5
+ "module": "dist/index.js",
6
+ "types": "dist/index.d.mts",
8
7
  "react-native": "src/index.ts",
9
8
  "exports": {
10
9
  ".": {
11
- "types": "./dist/index.d.ts",
10
+ "types": "./dist/index.d.mts",
12
11
  "react-native": "./src/index.ts",
13
- "import": "./dist/index.esm.js",
14
- "require": "./dist/index.cjs.js"
12
+ "import": "./dist/index.js"
15
13
  }
16
14
  },
17
15
  "files": [
18
16
  "dist",
19
- "src",
20
17
  "README.md",
21
18
  "LICENSE"
22
19
  ],
23
20
  "dependencies": {
24
- "@impulse-ui-native/theme": "1.0.10",
25
- "@impulse-ui-native/view": "1.0.10",
26
- "@impulse-ui-native/typography": "1.0.10",
27
- "@impulse-ui-native/safe-area-view": "1.0.10",
28
- "@impulse-ui-native/core": "1.0.10",
29
- "@impulse-ui-native/layer-manager-types": "1.0.10"
21
+ "@impulse-ui-native/core": "2.0.1",
22
+ "@impulse-ui-native/types": "2.0.1",
23
+ "@impulse-ui-native/theme": "2.0.1",
24
+ "@impulse-ui-native/primitives": "2.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "~19.2.14",
28
+ "eslint": "^9.38.0",
29
+ "prettier": "^3.6.2",
30
+ "react": "19.2.0",
31
+ "react-native": "0.83.6",
32
+ "react-native-gesture-handler": "~2.30.1",
33
+ "react-native-reanimated": "4.2.1",
34
+ "react-native-safe-area-context": "~5.6.0",
35
+ "react-native-worklets": "0.7.4",
36
+ "typescript": "~5.9.2",
37
+ "@impulse-ui-native/prettier-config": "1.0.0",
38
+ "@impulse-ui-native/eslint-config": "1.0.0",
39
+ "@impulse-ui-native/tsconfig": "1.0.0"
30
40
  },
31
41
  "peerDependencies": {
32
42
  "react": ">=18",
33
- "react-native": ">=0.74",
34
- "expo": ">=51 || >=52",
35
- "react-native-worklets": ">=0.5.1",
36
- "expo-modules-core": ">=1.0.0",
37
- "react-native-safe-area-context": "5.6.1",
38
- "react-native-gesture-handler": ">=2",
39
- "react-native-reanimated": ">=3"
43
+ "react-native": ">=0.73",
44
+ "react-native-reanimated": ">=3.6.0",
45
+ "react-native-gesture-handler": ">=2.0.0",
46
+ "react-native-worklets": ">=0.5",
47
+ "react-native-safe-area-context": ">=5.0.0"
40
48
  },
41
49
  "publishConfig": {
42
50
  "access": "public"
43
51
  },
52
+ "prettier": "@impulse-ui-native/prettier-config",
44
53
  "scripts": {
45
54
  "build": "tsup --config tsup.config.ts",
46
55
  "typecheck": "tsc -p tsconfig.json --noEmit",
47
- "lint": "pnpm exec eslint . --fix"
56
+ "lint": "pnpm exec eslint . --fix",
57
+ "format": "prettier --write \"./**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path ../../.prettierignore"
48
58
  }
49
59
  }