@solucx/react-native-solucx-widget 0.1.15 → 0.2.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.intern.md +513 -513
- package/README.md +285 -285
- package/package.json +50 -23
- package/src/SoluCXWidget.tsx +172 -119
- package/src/__mocks__/expo-modules-core-web.js +16 -0
- package/src/__mocks__/expo-modules-core.js +33 -0
- package/src/__tests__/ClientVersionCollector.test.ts +55 -0
- package/src/__tests__/CloseButton.test.tsx +47 -0
- package/src/__tests__/Constants.test.ts +17 -0
- package/src/__tests__/InlineWidget.rendering.test.tsx +81 -0
- package/src/__tests__/ModalWidget.rendering.test.tsx +157 -0
- package/src/__tests__/OverlayWidget.rendering.test.tsx +123 -0
- package/src/__tests__/SoluCXWidget.rendering.test.tsx +315 -0
- package/src/__tests__/e2e/widget-lifecycle.test.tsx +353 -0
- package/src/__tests__/integration/webview-communication-simple.test.tsx +147 -0
- package/src/__tests__/integration/webview-communication.test.tsx +417 -0
- package/src/__tests__/urlUtils.test.ts +56 -56
- package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
- package/src/__tests__/useWidgetState.test.ts +181 -189
- package/src/__tests__/widgetBootstrapService.test.ts +182 -0
- package/src/components/CloseButton.tsx +36 -36
- package/src/components/InlineWidget.tsx +36 -36
- package/src/components/ModalWidget.tsx +57 -59
- package/src/components/OverlayWidget.tsx +88 -88
- package/src/constants/Constants.ts +4 -0
- package/src/constants/webViewConstants.ts +15 -14
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useDeviceInfoCollector.ts +67 -0
- package/src/hooks/useHeightAnimation.ts +22 -22
- package/src/hooks/useWidgetHeight.ts +38 -38
- package/src/hooks/useWidgetState.ts +101 -101
- package/src/index.ts +12 -8
- package/src/interfaces/WidgetCallbacks.ts +15 -0
- package/src/interfaces/WidgetData.ts +19 -19
- package/src/interfaces/WidgetOptions.ts +7 -7
- package/src/interfaces/WidgetResponse.ts +15 -15
- package/src/interfaces/WidgetSamplerLog.ts +5 -5
- package/src/interfaces/index.ts +25 -24
- package/src/services/ClientVersionCollector.ts +15 -0
- package/src/services/storage.ts +21 -21
- package/src/services/widgetBootstrapService.ts +67 -0
- package/src/services/widgetEventService.ts +110 -111
- package/src/services/widgetValidationService.ts +102 -86
- package/src/setupTests.js +43 -0
- package/src/styles/widgetStyles.ts +58 -58
- package/src/utils/urlUtils.ts +13 -13
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import { styles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
4
|
-
import { CloseButton } from './CloseButton';
|
|
5
|
-
import { Animated } from 'react-native';
|
|
6
|
-
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
7
|
-
|
|
8
|
-
interface InlineWidgetProps {
|
|
9
|
-
visible: boolean;
|
|
10
|
-
height: number;
|
|
11
|
-
children?: React.ReactNode;
|
|
12
|
-
onClose?: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const InlineWidget: React.FC<InlineWidgetProps> = ({
|
|
16
|
-
visible,
|
|
17
|
-
height,
|
|
18
|
-
children,
|
|
19
|
-
onClose,
|
|
20
|
-
}) => {
|
|
21
|
-
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
updateHeight(height);
|
|
25
|
-
}, [height, updateHeight]);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<View style={[styles.inlineWrapper, getWidgetVisibility(visible)]}>
|
|
29
|
-
<Animated.View
|
|
30
|
-
style={[styles.inline, animatedHeightStyle, getWidgetVisibility(visible)]}>
|
|
31
|
-
{children}
|
|
32
|
-
<CloseButton visible={visible} onPress={onClose || (() => { })} />
|
|
33
|
-
</Animated.View>
|
|
34
|
-
</View>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { styles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
4
|
+
import { CloseButton } from './CloseButton';
|
|
5
|
+
import { Animated } from 'react-native';
|
|
6
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
7
|
+
|
|
8
|
+
interface InlineWidgetProps {
|
|
9
|
+
visible: boolean;
|
|
10
|
+
height: number;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const InlineWidget: React.FC<InlineWidgetProps> = ({
|
|
16
|
+
visible,
|
|
17
|
+
height,
|
|
18
|
+
children,
|
|
19
|
+
onClose,
|
|
20
|
+
}) => {
|
|
21
|
+
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
updateHeight(height);
|
|
25
|
+
}, [height, updateHeight]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={[styles.inlineWrapper, getWidgetVisibility(visible)]}>
|
|
29
|
+
<Animated.View
|
|
30
|
+
style={[styles.inline, animatedHeightStyle, getWidgetVisibility(visible)]}>
|
|
31
|
+
{children}
|
|
32
|
+
<CloseButton visible={visible} onPress={onClose || (() => { })} />
|
|
33
|
+
</Animated.View>
|
|
34
|
+
</View>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -1,59 +1,57 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Modal,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
};
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Modal, View, Animated } from 'react-native';
|
|
3
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
|
+
import { styles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
5
|
+
import { CloseButton } from './CloseButton';
|
|
6
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
7
|
+
|
|
8
|
+
interface ModalWidgetProps {
|
|
9
|
+
visible: boolean;
|
|
10
|
+
height: number;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ModalWidget: React.FC<ModalWidgetProps> = ({
|
|
16
|
+
visible,
|
|
17
|
+
height,
|
|
18
|
+
children,
|
|
19
|
+
onClose,
|
|
20
|
+
}) => {
|
|
21
|
+
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
updateHeight(height);
|
|
25
|
+
}, [height, updateHeight]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<SafeAreaView>
|
|
29
|
+
<Modal
|
|
30
|
+
transparent
|
|
31
|
+
visible={visible}
|
|
32
|
+
animationType="slide"
|
|
33
|
+
hardwareAccelerated
|
|
34
|
+
>
|
|
35
|
+
<View style={[styles.modalOverlay, getWidgetVisibility(visible)]}>
|
|
36
|
+
<Animated.View
|
|
37
|
+
style={[
|
|
38
|
+
styles.modalContent,
|
|
39
|
+
getWidgetVisibility(visible),
|
|
40
|
+
animatedHeightStyle,
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<CloseButton
|
|
45
|
+
visible={visible}
|
|
46
|
+
onPress={() => {
|
|
47
|
+
if (onClose) {
|
|
48
|
+
onClose();
|
|
49
|
+
}
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
</Animated.View>
|
|
53
|
+
</View>
|
|
54
|
+
</Modal>
|
|
55
|
+
</SafeAreaView>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { View, ViewStyle, Animated } from 'react-native';
|
|
3
|
-
import { initialWindowMetrics } from 'react-native-safe-area-context';
|
|
4
|
-
import { getWidgetStyles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
5
|
-
import { FIXED_Z_INDEX } from '../constants/webViewConstants';
|
|
6
|
-
import { CloseButton } from './CloseButton';
|
|
7
|
-
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
8
|
-
|
|
9
|
-
interface OverlayWidgetProps {
|
|
10
|
-
visible: boolean;
|
|
11
|
-
width: number;
|
|
12
|
-
height: number;
|
|
13
|
-
position: 'top' | 'bottom';
|
|
14
|
-
children?: React.ReactNode;
|
|
15
|
-
onClose?: () => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
|
|
19
|
-
visible,
|
|
20
|
-
width,
|
|
21
|
-
height,
|
|
22
|
-
position,
|
|
23
|
-
children,
|
|
24
|
-
onClose,
|
|
25
|
-
}) => {
|
|
26
|
-
const insets =
|
|
27
|
-
initialWindowMetrics?.insets ?? { top: 0, bottom: 0, left: 0, right: 0 };
|
|
28
|
-
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(true);
|
|
29
|
-
|
|
30
|
-
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
updateHeight(height);
|
|
34
|
-
}, [height, updateHeight]);
|
|
35
|
-
|
|
36
|
-
const containerStyle: ViewStyle = {
|
|
37
|
-
position: 'absolute',
|
|
38
|
-
top: 0,
|
|
39
|
-
left: 0,
|
|
40
|
-
right: 0,
|
|
41
|
-
bottom: 0,
|
|
42
|
-
width: '100%',
|
|
43
|
-
height: '100%',
|
|
44
|
-
zIndex: FIXED_Z_INDEX,
|
|
45
|
-
pointerEvents: 'box-none',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const contentStyle = [
|
|
49
|
-
getWidgetStyles(position).content,
|
|
50
|
-
{
|
|
51
|
-
width,
|
|
52
|
-
pointerEvents: 'auto' as const,
|
|
53
|
-
...(position === 'top' && {
|
|
54
|
-
top: insets.top,
|
|
55
|
-
}),
|
|
56
|
-
...(position === 'bottom' && {
|
|
57
|
-
bottom: insets.bottom,
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<>
|
|
64
|
-
{isWidgetVisible && (
|
|
65
|
-
<View style={[containerStyle, getWidgetVisibility(visible)]}>
|
|
66
|
-
<Animated.View
|
|
67
|
-
style={[
|
|
68
|
-
contentStyle,
|
|
69
|
-
animatedHeightStyle,
|
|
70
|
-
getWidgetVisibility(visible),
|
|
71
|
-
]}
|
|
72
|
-
>
|
|
73
|
-
{children}
|
|
74
|
-
<CloseButton
|
|
75
|
-
visible={visible}
|
|
76
|
-
onPress={() => {
|
|
77
|
-
setIsWidgetVisible(false);
|
|
78
|
-
if (onClose) {
|
|
79
|
-
onClose();
|
|
80
|
-
}
|
|
81
|
-
}}
|
|
82
|
-
/>
|
|
83
|
-
</Animated.View>
|
|
84
|
-
</View>
|
|
85
|
-
)}
|
|
86
|
-
</>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { View, type ViewStyle, Animated } from 'react-native';
|
|
3
|
+
import { initialWindowMetrics } from 'react-native-safe-area-context';
|
|
4
|
+
import { getWidgetStyles, getWidgetVisibility } from '../styles/widgetStyles';
|
|
5
|
+
import { FIXED_Z_INDEX } from '../constants/webViewConstants';
|
|
6
|
+
import { CloseButton } from './CloseButton';
|
|
7
|
+
import { useHeightAnimation } from '../hooks/useHeightAnimation';
|
|
8
|
+
|
|
9
|
+
interface OverlayWidgetProps {
|
|
10
|
+
visible: boolean;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
position: 'top' | 'bottom';
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const OverlayWidget: React.FC<OverlayWidgetProps> = ({
|
|
19
|
+
visible,
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
position,
|
|
23
|
+
children,
|
|
24
|
+
onClose,
|
|
25
|
+
}) => {
|
|
26
|
+
const insets =
|
|
27
|
+
initialWindowMetrics?.insets ?? { top: 0, bottom: 0, left: 0, right: 0 };
|
|
28
|
+
const [isWidgetVisible, setIsWidgetVisible] = useState<boolean>(true);
|
|
29
|
+
|
|
30
|
+
const { animatedHeightStyle, updateHeight } = useHeightAnimation(height);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
updateHeight(height);
|
|
34
|
+
}, [height, updateHeight]);
|
|
35
|
+
|
|
36
|
+
const containerStyle: ViewStyle = {
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
top: 0,
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
bottom: 0,
|
|
42
|
+
width: '100%',
|
|
43
|
+
height: '100%',
|
|
44
|
+
zIndex: FIXED_Z_INDEX,
|
|
45
|
+
pointerEvents: 'box-none',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const contentStyle = [
|
|
49
|
+
getWidgetStyles(position).content,
|
|
50
|
+
{
|
|
51
|
+
width,
|
|
52
|
+
pointerEvents: 'auto' as const,
|
|
53
|
+
...(position === 'top' && {
|
|
54
|
+
top: insets.top,
|
|
55
|
+
}),
|
|
56
|
+
...(position === 'bottom' && {
|
|
57
|
+
bottom: insets.bottom,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
{isWidgetVisible && (
|
|
65
|
+
<View style={[containerStyle, getWidgetVisibility(visible)]}>
|
|
66
|
+
<Animated.View
|
|
67
|
+
style={[
|
|
68
|
+
contentStyle,
|
|
69
|
+
animatedHeightStyle,
|
|
70
|
+
getWidgetVisibility(visible),
|
|
71
|
+
]}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
<CloseButton
|
|
75
|
+
visible={visible}
|
|
76
|
+
onPress={() => {
|
|
77
|
+
setIsWidgetVisible(false);
|
|
78
|
+
if (onClose) {
|
|
79
|
+
onClose();
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
</Animated.View>
|
|
84
|
+
</View>
|
|
85
|
+
)}
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
export const BASE_URL = 'https://survey-link.solucx.com.br/link';
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
export const BASE_URL = 'https://survey-link.solucx.com.br/link';
|
|
2
|
+
export const RATING_FORM_ENDPOINT = 'https://widget-api.solucx.com.br/widget/preflight';
|
|
3
|
+
export const STORAGE_KEY = '@solucxWidgetLog';
|
|
4
|
+
export const DEFAULT_CHANNEL_NUMBER = 1;
|
|
5
|
+
export const DEFAULT_CHANNEL = 'widget';
|
|
6
|
+
export const DEFAULT_WIDTH = 380;
|
|
7
|
+
export const MIN_HEIGHT = 200;
|
|
8
|
+
export const FIXED_Z_INDEX = 9999;
|
|
9
|
+
export const MODAL_Z_INDEX = 10000;
|
|
10
|
+
export const DESIGN_HEIGHT = 700;
|
|
11
|
+
export const WEB_VIEW_MESSAGE_LISTENER = `
|
|
12
|
+
window.addEventListener('message', function(event) {
|
|
13
|
+
window.ReactNativeWebView.postMessage(event.data);
|
|
14
|
+
});
|
|
15
|
+
`;
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { useWidgetState } from './useWidgetState';
|
|
2
|
-
export { useWidgetHeight } from './useWidgetHeight';
|
|
1
|
+
export { useWidgetState } from './useWidgetState';
|
|
2
|
+
export { useWidgetHeight } from './useWidgetHeight';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Platform, Dimensions } from "react-native";
|
|
2
|
+
|
|
3
|
+
export interface DeviceInfo {
|
|
4
|
+
platform: "ios" | "android" | "web" | "windows" | "macos";
|
|
5
|
+
osVersion: string;
|
|
6
|
+
screenWidth: number;
|
|
7
|
+
screenHeight: number;
|
|
8
|
+
windowWidth: number;
|
|
9
|
+
windowHeight: number;
|
|
10
|
+
scale: number;
|
|
11
|
+
fontScale: number;
|
|
12
|
+
deviceType?: "tablet" | "phone";
|
|
13
|
+
model: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const isTablet = (): boolean => {
|
|
17
|
+
const { width, height } = Dimensions.get("screen");
|
|
18
|
+
const aspectRatio = height / width;
|
|
19
|
+
|
|
20
|
+
return Math.min(width, height) >= 600 && aspectRatio < 1.6;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getDeviceModel = (): string => {
|
|
24
|
+
try {
|
|
25
|
+
const DeviceInfo = require('react-native-device-info');
|
|
26
|
+
if (DeviceInfo?.default?.getModel) {
|
|
27
|
+
return DeviceInfo.default.getModel();
|
|
28
|
+
}
|
|
29
|
+
if (DeviceInfo?.getModel) {
|
|
30
|
+
return DeviceInfo.getModel();
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// react-native-device-info não disponível
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const constants = Platform.constants as any;
|
|
38
|
+
if (constants?.Model) {
|
|
39
|
+
return constants.Model;
|
|
40
|
+
}
|
|
41
|
+
if (constants?.model) {
|
|
42
|
+
return constants.model;
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Nada disponível
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return 'unknown';
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getDeviceInfo = (): DeviceInfo => {
|
|
52
|
+
const screen = Dimensions.get("screen");
|
|
53
|
+
const window = Dimensions.get("window");
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
platform: Platform.OS,
|
|
57
|
+
osVersion: Platform.Version.toString(),
|
|
58
|
+
screenWidth: screen.width,
|
|
59
|
+
screenHeight: screen.height,
|
|
60
|
+
windowWidth: window.width,
|
|
61
|
+
windowHeight: window.height,
|
|
62
|
+
scale: screen.scale,
|
|
63
|
+
fontScale: screen.fontScale,
|
|
64
|
+
deviceType: isTablet() ? "tablet" : "phone",
|
|
65
|
+
model: getDeviceModel(),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// hooks/useHeightAnimation.ts
|
|
2
|
-
import { useRef, useCallback } from 'react';
|
|
3
|
-
import { Animated } from 'react-native';
|
|
4
|
-
|
|
5
|
-
export function useHeightAnimation(initialHeight = 0, duration = 300) {
|
|
6
|
-
const height = useRef(new Animated.Value(initialHeight)).current;
|
|
7
|
-
|
|
8
|
-
const updateHeight = useCallback(
|
|
9
|
-
(toValue: number) => {
|
|
10
|
-
Animated.timing(height, {
|
|
11
|
-
toValue,
|
|
12
|
-
duration,
|
|
13
|
-
useNativeDriver: false,
|
|
14
|
-
}).start();
|
|
15
|
-
},
|
|
16
|
-
[height, duration],
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const animatedHeightStyle = { height };
|
|
20
|
-
|
|
21
|
-
return { animatedHeightStyle, updateHeight, height };
|
|
22
|
-
}
|
|
1
|
+
// hooks/useHeightAnimation.ts
|
|
2
|
+
import { useRef, useCallback } from 'react';
|
|
3
|
+
import { Animated } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function useHeightAnimation(initialHeight = 0, duration = 300) {
|
|
6
|
+
const height = useRef(new Animated.Value(initialHeight)).current;
|
|
7
|
+
|
|
8
|
+
const updateHeight = useCallback(
|
|
9
|
+
(toValue: number) => {
|
|
10
|
+
Animated.timing(height, {
|
|
11
|
+
toValue,
|
|
12
|
+
duration,
|
|
13
|
+
useNativeDriver: false,
|
|
14
|
+
}).start();
|
|
15
|
+
},
|
|
16
|
+
[height, duration],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const animatedHeightStyle = { height };
|
|
20
|
+
|
|
21
|
+
return { animatedHeightStyle, updateHeight, height };
|
|
22
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
import type { WidgetOptions } from '../interfaces';
|
|
3
|
-
|
|
4
|
-
interface UseWidgetHeightProps {
|
|
5
|
-
options?: WidgetOptions;
|
|
6
|
-
initialHeight?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface UseWidgetHeightReturn {
|
|
10
|
-
height: number;
|
|
11
|
-
isFixedHeight: boolean;
|
|
12
|
-
handleResize: (newHeight: number) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const useWidgetHeight = ({
|
|
16
|
-
options,
|
|
17
|
-
initialHeight = 300
|
|
18
|
-
}: UseWidgetHeightProps): UseWidgetHeightReturn => {
|
|
19
|
-
const [dynamicHeight, setDynamicHeight] = useState<number>(initialHeight);
|
|
20
|
-
const hasFixedHeight = typeof options?.height === 'number';
|
|
21
|
-
|
|
22
|
-
const height = hasFixedHeight ? options!.height! : dynamicHeight;
|
|
23
|
-
|
|
24
|
-
const handleResize = useCallback(
|
|
25
|
-
(newHeight: number) => {
|
|
26
|
-
if (!hasFixedHeight && newHeight > 0) {
|
|
27
|
-
setDynamicHeight(newHeight);
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
[hasFixedHeight]
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
height,
|
|
35
|
-
isFixedHeight: hasFixedHeight,
|
|
36
|
-
handleResize
|
|
37
|
-
};
|
|
38
|
-
};
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import type { WidgetOptions } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
interface UseWidgetHeightProps {
|
|
5
|
+
options?: WidgetOptions;
|
|
6
|
+
initialHeight?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UseWidgetHeightReturn {
|
|
10
|
+
height: number;
|
|
11
|
+
isFixedHeight: boolean;
|
|
12
|
+
handleResize: (newHeight: number) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useWidgetHeight = ({
|
|
16
|
+
options,
|
|
17
|
+
initialHeight = 300
|
|
18
|
+
}: UseWidgetHeightProps): UseWidgetHeightReturn => {
|
|
19
|
+
const [dynamicHeight, setDynamicHeight] = useState<number>(initialHeight);
|
|
20
|
+
const hasFixedHeight = typeof options?.height === 'number';
|
|
21
|
+
|
|
22
|
+
const height = hasFixedHeight ? options!.height! : dynamicHeight;
|
|
23
|
+
|
|
24
|
+
const handleResize = useCallback(
|
|
25
|
+
(newHeight: number) => {
|
|
26
|
+
if (!hasFixedHeight && newHeight > 0) {
|
|
27
|
+
setDynamicHeight(newHeight);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
[hasFixedHeight]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
height,
|
|
35
|
+
isFixedHeight: hasFixedHeight,
|
|
36
|
+
handleResize
|
|
37
|
+
};
|
|
38
|
+
};
|