@impulse-ui-native/flyout 2.1.1 → 2.1.3
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.
|
@@ -74,6 +74,24 @@ const Flyout = memo(function Flyout2(props) {
|
|
|
74
74
|
placement === "top" ? -screenHeight : screenHeight
|
|
75
75
|
);
|
|
76
76
|
const opacity = useSharedValue(0);
|
|
77
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
78
|
+
const offset = placement === "bottom" ? -(placementOffset ?? 0) : placementOffset ?? 0;
|
|
79
|
+
return {
|
|
80
|
+
transform: [{ translateY: translateY.value + offset }],
|
|
81
|
+
zIndex
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
const overlayStyle = useAnimatedStyle(() => ({
|
|
85
|
+
opacity: opacity.value,
|
|
86
|
+
zIndex,
|
|
87
|
+
backgroundColor: flyoutTokens.overlayColor
|
|
88
|
+
}));
|
|
89
|
+
const edges = useMemo(() => {
|
|
90
|
+
return [placement, "left", "right"];
|
|
91
|
+
}, [placement]);
|
|
92
|
+
const styles = useMemo(() => {
|
|
93
|
+
return themedStyles(theme, placement, maxHeight);
|
|
94
|
+
}, [theme, placement, maxHeight]);
|
|
77
95
|
const onLayout = useCallback(
|
|
78
96
|
(event) => {
|
|
79
97
|
if (measuredHeight !== null) {
|
|
@@ -154,24 +172,6 @@ const Flyout = memo(function Flyout2(props) {
|
|
|
154
172
|
translateY
|
|
155
173
|
]
|
|
156
174
|
);
|
|
157
|
-
const animatedStyle = useAnimatedStyle(() => {
|
|
158
|
-
const offset = placement === "bottom" ? -(placementOffset ?? 0) : placementOffset ?? 0;
|
|
159
|
-
return {
|
|
160
|
-
transform: [{ translateY: translateY.value + offset }],
|
|
161
|
-
zIndex
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
const overlayStyle = useAnimatedStyle(() => ({
|
|
165
|
-
opacity: opacity.value,
|
|
166
|
-
zIndex,
|
|
167
|
-
backgroundColor: flyoutTokens.overlayColor
|
|
168
|
-
}));
|
|
169
|
-
const edges = useMemo(() => {
|
|
170
|
-
return [placement, "left", "right"];
|
|
171
|
-
}, [placement]);
|
|
172
|
-
const styles = useMemo(() => {
|
|
173
|
-
return themedStyles(theme, placement, maxHeight);
|
|
174
|
-
}, [theme, placement, maxHeight]);
|
|
175
175
|
useEffect(() => {
|
|
176
176
|
if (!open) {
|
|
177
177
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/flyout.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"],"mappings":"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","names":["Flyout"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/flyout.tsx"],"sourcesContent":["import {\n Fragment,\n memo,\n PropsWithChildren,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n LayoutChangeEvent,\n Pressable,\n StyleSheet,\n useWindowDimensions,\n} from \"react-native\";\nimport { Gesture, GestureDetector } from \"react-native-gesture-handler\";\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useSharedValue,\n withSpring,\n withTiming,\n} from \"react-native-reanimated\";\nimport {\n Edge,\n SafeAreaView,\n useSafeAreaInsets,\n} from \"react-native-safe-area-context\";\nimport { scheduleOnRN } from \"react-native-worklets\";\n\nimport { View } from \"@impulse-ui-native/primitives\";\nimport { AppTheme, useTheme } from \"@impulse-ui-native/theme\";\n\nimport { FlyoutHandleHeight } from \"../constants\";\nimport { FlyoutProps } from \"../types\";\nimport { FlyoutHandle } from \"./flyout-handle\";\nimport { FlyoutTitle } from \"./flyout-title\";\n\nconst EnterAnimationConfig = {\n damping: 50,\n stiffness: 300,\n mass: 1,\n overshootClamping: true,\n};\n\nconst ExitAnimationConfig = {\n duration: 150,\n easing: Easing.in(Easing.quad),\n};\n\nexport const Flyout = memo(function Flyout(\n props: PropsWithChildren<FlyoutProps>,\n) {\n const {\n id,\n children,\n placement = \"bottom\",\n layer = 0,\n title,\n open,\n topOffset,\n bottomOffset,\n onCloseFinished,\n onClose,\n onOpen,\n onOpenFinished,\n } = props;\n\n const theme = useTheme();\n const insets = useSafeAreaInsets();\n\n const windowDimensions = useWindowDimensions();\n\n const flyoutTokens = theme.components.flyout;\n\n const placementOffset = placement === \"top\" ? topOffset : bottomOffset;\n const screenHeight = windowDimensions.height;\n const maxHeight = screenHeight * flyoutTokens.maxHeightRatio;\n const zIndex = flyoutTokens.zIndexBase + layer;\n\n const [mounted, setMounted] = useState(open);\n const [measuredHeight, setMeasuredHeight] = useState<number | null>(null);\n const [hasMeasured, setHasMeasured] = useState(false);\n const [isTouchable, setIsTouchable] = useState(false);\n\n const didOpen = useRef(false);\n const isClosing = useRef(false);\n\n const translateY = useSharedValue(\n placement === \"top\" ? -screenHeight : screenHeight,\n );\n const opacity = useSharedValue(0);\n\n const animatedStyle = useAnimatedStyle(() => {\n const offset =\n placement === \"bottom\" ? -(placementOffset ?? 0) : (placementOffset ?? 0);\n\n return {\n transform: [{ translateY: translateY.value + offset }],\n zIndex,\n };\n });\n\n const overlayStyle = useAnimatedStyle(() => ({\n opacity: opacity.value,\n zIndex,\n backgroundColor: flyoutTokens.overlayColor,\n }));\n\n const edges = useMemo<Edge[]>(() => {\n return [placement, \"left\", \"right\"];\n }, [placement]);\n\n const styles = useMemo(() => {\n return themedStyles(theme, placement, maxHeight);\n }, [theme, placement, maxHeight]);\n\n const onLayout = useCallback(\n (event: LayoutChangeEvent) => {\n if (measuredHeight !== null) {\n return;\n }\n\n const rawHeight = event.nativeEvent.layout.height;\n const clampedHeight = rawHeight + insets[placement] + FlyoutHandleHeight;\n\n translateY.value = placement === \"top\" ? -clampedHeight : clampedHeight;\n\n setMeasuredHeight(clampedHeight);\n setHasMeasured(true);\n },\n [insets, measuredHeight, placement, translateY],\n );\n\n const animateOut = useCallback(() => {\n if (measuredHeight === null || isClosing.current) {\n return;\n }\n\n isClosing.current = true;\n setIsTouchable(false);\n onClose?.(id);\n\n const target = placement === \"top\" ? -measuredHeight : measuredHeight;\n\n translateY.value = withTiming(target, ExitAnimationConfig, (finished) => {\n if (finished) {\n if (onCloseFinished) {\n scheduleOnRN(onCloseFinished, id);\n }\n\n scheduleOnRN(setMounted, false);\n scheduleOnRN(setMeasuredHeight, null);\n scheduleOnRN(setHasMeasured, false);\n }\n });\n\n opacity.value = withTiming(0, ExitAnimationConfig);\n didOpen.current = false;\n }, [\n id,\n measuredHeight,\n onClose,\n onCloseFinished,\n opacity,\n placement,\n translateY,\n ]);\n\n const dragGesture = useMemo(\n () =>\n Gesture.Pan()\n .enabled(hasMeasured)\n .onUpdate((event) => {\n if (placement === \"bottom\") {\n translateY.value = Math.max(0, event.translationY);\n return;\n }\n\n translateY.value = Math.min(0, event.translationY);\n })\n .onEnd((event) => {\n if (measuredHeight === null) {\n return;\n }\n\n let shouldClose = false;\n\n if (placement === \"bottom\") {\n shouldClose =\n event.translationY > measuredHeight * 0.5 ||\n event.velocityY > 1500;\n }\n\n if (placement === \"top\") {\n shouldClose =\n event.translationY < -measuredHeight * 0.5 ||\n event.velocityY < -1500;\n }\n\n if (shouldClose) {\n scheduleOnRN(animateOut);\n return;\n }\n\n translateY.value = withSpring(0, EnterAnimationConfig);\n opacity.value = withSpring(\n flyoutTokens.overlayVisibleOpacity,\n EnterAnimationConfig,\n );\n }),\n [\n animateOut,\n flyoutTokens.overlayVisibleOpacity,\n hasMeasured,\n measuredHeight,\n opacity,\n placement,\n translateY,\n ],\n );\n\n useEffect(() => {\n if (!open) {\n return;\n }\n\n setMounted(true);\n setIsTouchable(false);\n setMeasuredHeight(null);\n setHasMeasured(false);\n\n isClosing.current = false;\n didOpen.current = false;\n\n translateY.value = placement === \"top\" ? -screenHeight : screenHeight;\n opacity.value = 0;\n }, [open, opacity, placement, screenHeight, translateY]);\n\n useEffect(() => {\n if (!open || measuredHeight === null || !hasMeasured || didOpen.current) {\n return;\n }\n\n setIsTouchable(true);\n onOpen?.(id);\n\n translateY.value = withSpring(0, EnterAnimationConfig, (finished) => {\n if (finished && onOpenFinished) {\n scheduleOnRN(onOpenFinished, id);\n }\n });\n\n opacity.value = withSpring(\n flyoutTokens.overlayVisibleOpacity,\n EnterAnimationConfig,\n );\n\n didOpen.current = true;\n }, [\n flyoutTokens.overlayVisibleOpacity,\n hasMeasured,\n id,\n measuredHeight,\n onOpen,\n onOpenFinished,\n opacity,\n open,\n translateY,\n ]);\n\n useEffect(() => {\n if (!open && didOpen.current) {\n animateOut();\n }\n }, [animateOut, open]);\n\n if (!mounted) {\n return null;\n }\n\n return (\n <Fragment>\n <Animated.View\n pointerEvents={isTouchable ? \"auto\" : \"none\"}\n style={[StyleSheet.absoluteFill, overlayStyle]}\n >\n <Pressable onPress={animateOut} style={StyleSheet.absoluteFill} />\n </Animated.View>\n\n <GestureDetector gesture={dragGesture}>\n <Animated.View\n onLayout={onLayout}\n style={[\n styles.container,\n !hasMeasured && styles.hidden,\n animatedStyle,\n ]}\n >\n <SafeAreaView edges={edges}>\n {placement === \"bottom\" ? (\n <FlyoutHandle placement={placement} />\n ) : null}\n\n {title ? <FlyoutTitle title={title} /> : null}\n\n <View style={styles.content}>{children}</View>\n\n {placement === \"top\" ? (\n <FlyoutHandle placement={placement} />\n ) : null}\n </SafeAreaView>\n </Animated.View>\n </GestureDetector>\n </Fragment>\n );\n});\n\nfunction themedStyles(\n theme: AppTheme,\n placement: \"top\" | \"bottom\",\n maxHeight: number,\n) {\n const flyoutTokens = theme.components.flyout;\n\n return StyleSheet.create({\n container: {\n position: \"absolute\",\n [placement]: 0,\n left: 0,\n right: 0,\n backgroundColor: flyoutTokens.backgroundColor,\n borderTopLeftRadius:\n placement === \"bottom\" ? flyoutTokens.borderRadius : 0,\n borderTopRightRadius:\n placement === \"bottom\" ? flyoutTokens.borderRadius : 0,\n borderBottomLeftRadius:\n placement === \"top\" ? flyoutTokens.borderRadius : 0,\n borderBottomRightRadius:\n placement === \"top\" ? flyoutTokens.borderRadius : 0,\n },\n content: {\n paddingHorizontal: flyoutTokens.contentPaddingHorizontal,\n maxHeight,\n },\n hidden: {\n opacity: flyoutTokens.hiddenOpacity,\n },\n });\n}\n"],"mappings":"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","names":["Flyout"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impulse-ui-native/flyout",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.mts",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
],
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@impulse-ui-native/theme": "2.1.
|
|
23
|
-
"@impulse-ui-native/core": "2.1.
|
|
24
|
-
"@impulse-ui-native/
|
|
25
|
-
"@impulse-ui-native/
|
|
22
|
+
"@impulse-ui-native/theme": "2.1.3",
|
|
23
|
+
"@impulse-ui-native/core": "2.1.3",
|
|
24
|
+
"@impulse-ui-native/primitives": "2.1.3",
|
|
25
|
+
"@impulse-ui-native/types": "2.1.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/react": "~19.2.14",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"react-native-safe-area-context": "~5.7.0",
|
|
36
36
|
"react-native-worklets": "0.10.1",
|
|
37
37
|
"typescript": "~6.0.3",
|
|
38
|
+
"@impulse-ui-native/prettier-config": "1.0.0",
|
|
38
39
|
"@impulse-ui-native/eslint-config": "1.0.0",
|
|
39
|
-
"@impulse-ui-native/tsconfig": "1.0.0"
|
|
40
|
-
"@impulse-ui-native/prettier-config": "1.0.0"
|
|
40
|
+
"@impulse-ui-native/tsconfig": "1.0.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": ">=18",
|