@applicaster/zapp-react-native-ui-components 12.0.0-alpha.5786305091 → 12.0.0-alpha.9151207838
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/Components/AnimatedInOut/index.tsx +18 -10
- package/Components/AudioPlayer/AudioPlayer.tsx +9 -1
- package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
- package/Components/AudioPlayer/helpers.tsx +1 -0
- package/Components/Image/Image.android.tsx +34 -2
- package/Components/MasterCell/utils/index.ts +2 -2
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +78 -249
- package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
- package/Components/ModalComponent/Button/Item.tsx +82 -0
- package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
- package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
- package/Components/ModalComponent/Button/assets.ts +4 -0
- package/Components/ModalComponent/Button/index.tsx +111 -0
- package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
- package/Components/ModalComponent/Header/Title.tsx +70 -0
- package/Components/ModalComponent/Header/assets.ts +4 -0
- package/Components/ModalComponent/Header/index.tsx +71 -0
- package/Components/ModalComponent/Header/utils.ts +79 -0
- package/Components/ModalComponent/index.tsx +38 -0
- package/Components/ModalComponent/utils.ts +126 -0
- package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +4 -7
- package/Components/VideoModal/PlayerDetails.tsx +3 -0
- package/Components/VideoModal/PlayerWrapper.tsx +63 -62
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +3 -2
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +10 -18
- package/Components/index.js +2 -0
- package/package.json +5 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Animated, EasingFunction, StyleProp, ViewStyle } from "react-native";
|
|
3
|
-
|
|
3
|
+
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
4
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
4
5
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
5
6
|
|
|
6
7
|
type AnimatedInterpolatedStyle =
|
|
@@ -35,11 +36,16 @@ function getAnimation(config: AnimatedInConfig, phase: "in" | "out") {
|
|
|
35
36
|
return config[phase] || config;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
export function AnimatedInOut(
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
export function AnimatedInOut({
|
|
40
|
+
visible,
|
|
41
|
+
animationConfig,
|
|
42
|
+
staticStyles = {},
|
|
43
|
+
children,
|
|
44
|
+
}: Props) {
|
|
41
45
|
const [animatedValue] = React.useState(new Animated.Value(visible ? 1 : 0));
|
|
42
|
-
const [animating, setAnimating] = React.useState(
|
|
46
|
+
const [animating, setAnimating] = React.useState(undefined);
|
|
47
|
+
|
|
48
|
+
const previousVisible = usePrevious(toBooleanWithDefaultFalse(visible));
|
|
43
49
|
|
|
44
50
|
function startAnimation(toValue, config) {
|
|
45
51
|
if (animating) {
|
|
@@ -55,7 +61,7 @@ export function AnimatedInOut(props: Props) {
|
|
|
55
61
|
delay,
|
|
56
62
|
useNativeDriver: true,
|
|
57
63
|
}).start(() => {
|
|
58
|
-
setAnimating(
|
|
64
|
+
setAnimating(undefined);
|
|
59
65
|
onAnimationEnd();
|
|
60
66
|
});
|
|
61
67
|
|
|
@@ -63,12 +69,14 @@ export function AnimatedInOut(props: Props) {
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
React.useEffect(() => {
|
|
66
|
-
if (visible) {
|
|
72
|
+
if (!previousVisible && visible) {
|
|
67
73
|
startAnimation(1.0, getAnimation(animationConfig, "in"));
|
|
68
|
-
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (previousVisible && !visible) {
|
|
69
77
|
startAnimation(0.0, getAnimation(animationConfig, "out"));
|
|
70
78
|
}
|
|
71
|
-
}, [visible,
|
|
79
|
+
}, [visible, previousVisible]);
|
|
72
80
|
|
|
73
81
|
const styles = visible
|
|
74
82
|
? getAnimation(animationConfig, "in").styles
|
|
@@ -77,7 +85,7 @@ export function AnimatedInOut(props: Props) {
|
|
|
77
85
|
return (
|
|
78
86
|
<Animated.View
|
|
79
87
|
renderToHardwareTextureAndroid={animating}
|
|
80
|
-
style={[styles(animatedValue),
|
|
88
|
+
style={[styles(animatedValue), staticStyles]}
|
|
81
89
|
>
|
|
82
90
|
{children}
|
|
83
91
|
</Animated.View>
|
|
@@ -23,6 +23,7 @@ type Props = {
|
|
|
23
23
|
audio_player_summary_color?: string;
|
|
24
24
|
audio_player_rtl?: boolean;
|
|
25
25
|
magic_background?: boolean;
|
|
26
|
+
audio_player_background_image_query?: string;
|
|
26
27
|
start_time?: string;
|
|
27
28
|
end_time?: string;
|
|
28
29
|
};
|
|
@@ -33,6 +34,7 @@ type Props = {
|
|
|
33
34
|
audio_player_summary_color?: string;
|
|
34
35
|
audio_player_rtl?: string;
|
|
35
36
|
magic_background?: string;
|
|
37
|
+
audio_player_background_image_query?: string;
|
|
36
38
|
audio_player_background_image?: string;
|
|
37
39
|
audio_player_artwork_aspect_ratio?: string;
|
|
38
40
|
lg_tv_audio_player_title_font_family?: string;
|
|
@@ -73,6 +75,11 @@ export function AudioPlayer(props: Props) {
|
|
|
73
75
|
const channelIcon = getProp("audio_player_channel_icon");
|
|
74
76
|
const rtlFlag = getProp("audio_player_rtl");
|
|
75
77
|
const magicBackground = getProp("magic_background");
|
|
78
|
+
|
|
79
|
+
const audioPlayerBackgroundImageQuery = getProp(
|
|
80
|
+
"audio_player_background_image_query"
|
|
81
|
+
);
|
|
82
|
+
|
|
76
83
|
const isRTL = rtlFlag === "1" || rtlFlag === "true" || rtlFlag === true;
|
|
77
84
|
|
|
78
85
|
const titleFontFamily = getProp(
|
|
@@ -149,7 +156,8 @@ export function AudioPlayer(props: Props) {
|
|
|
149
156
|
runTimeFontSize,
|
|
150
157
|
artworkAspectRatio,
|
|
151
158
|
channelIcon,
|
|
152
|
-
magicBackground
|
|
159
|
+
magicBackground,
|
|
160
|
+
audioPlayerBackgroundImageQuery,
|
|
153
161
|
};
|
|
154
162
|
}, [getProp]);
|
|
155
163
|
|
|
@@ -5,6 +5,7 @@ exports[`<AudioPlayer /> renders correctly 1`] = `
|
|
|
5
5
|
config={
|
|
6
6
|
Object {
|
|
7
7
|
"artworkAspectRatio": "1:1",
|
|
8
|
+
"audioPlayerBackgroundImageQuery": "",
|
|
8
9
|
"backgroundColor": "black",
|
|
9
10
|
"backgroundImage": "https://example.com",
|
|
10
11
|
"channelIcon": "https://example.com",
|
|
@@ -26,6 +27,7 @@ exports[`<AudioPlayer /> renders correctly 1`] = `
|
|
|
26
27
|
config={
|
|
27
28
|
Object {
|
|
28
29
|
"artworkAspectRatio": "1:1",
|
|
30
|
+
"audioPlayerBackgroundImageQuery": "",
|
|
29
31
|
"backgroundColor": "black",
|
|
30
32
|
"backgroundImage": "https://example.com",
|
|
31
33
|
"channelIcon": "https://example.com",
|
|
@@ -47,6 +49,7 @@ exports[`<AudioPlayer /> renders correctly 1`] = `
|
|
|
47
49
|
config={
|
|
48
50
|
Object {
|
|
49
51
|
"artworkAspectRatio": "1:1",
|
|
52
|
+
"audioPlayerBackgroundImageQuery": "",
|
|
50
53
|
"backgroundColor": "black",
|
|
51
54
|
"backgroundImage": "https://example.com",
|
|
52
55
|
"channelIcon": "https://example.com",
|
|
@@ -68,6 +71,7 @@ exports[`<AudioPlayer /> renders correctly 1`] = `
|
|
|
68
71
|
config={
|
|
69
72
|
Object {
|
|
70
73
|
"artworkAspectRatio": "1:1",
|
|
74
|
+
"audioPlayerBackgroundImageQuery": "",
|
|
71
75
|
"backgroundColor": "black",
|
|
72
76
|
"backgroundImage": "https://example.com",
|
|
73
77
|
"channelIcon": "https://example.com",
|
|
@@ -96,6 +100,7 @@ exports[`<AudioPlayer /> renders correctly 1`] = `
|
|
|
96
100
|
config={
|
|
97
101
|
Object {
|
|
98
102
|
"artworkAspectRatio": "1:1",
|
|
103
|
+
"audioPlayerBackgroundImageQuery": "",
|
|
99
104
|
"backgroundColor": "black",
|
|
100
105
|
"backgroundImage": "https://example.com",
|
|
101
106
|
"channelIcon": "https://example.com",
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import FastImage, {
|
|
4
|
+
FastImageProps,
|
|
5
|
+
} from "@applicaster/react-native-fast-image";
|
|
6
|
+
|
|
7
|
+
export const QBImage = (props: FastImageProps) => {
|
|
8
|
+
const source = React.useMemo(() => {
|
|
9
|
+
if (typeof props.source === "number") {
|
|
10
|
+
return props.source;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
uri: props.source.uri,
|
|
15
|
+
headers: props.source.headers,
|
|
16
|
+
priority: props.source.priority,
|
|
17
|
+
cache: props.source.cache,
|
|
18
|
+
maxWidth: props.source.maxWidth,
|
|
19
|
+
maxHeight: props.source.maxHeight,
|
|
20
|
+
};
|
|
21
|
+
}, [
|
|
22
|
+
...(typeof props.source === "object"
|
|
23
|
+
? [
|
|
24
|
+
props.source?.uri,
|
|
25
|
+
props.source?.priority,
|
|
26
|
+
props.source?.cache,
|
|
27
|
+
props.source?.headers,
|
|
28
|
+
props.source?.maxWidth,
|
|
29
|
+
props.source?.maxHeight,
|
|
30
|
+
]
|
|
31
|
+
: []),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
return <FastImage {...props} source={source} />;
|
|
35
|
+
};
|
|
@@ -1,43 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import { View, StyleSheet, LayoutChangeEvent } from "react-native";
|
|
3
|
-
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
1
|
+
/* eslint react/prop-types: off */
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type LayoutDimensions = {
|
|
11
|
-
width: number;
|
|
12
|
-
height: number;
|
|
13
|
-
};
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { View, StyleSheet } from "react-native";
|
|
5
|
+
import * as R from "ramda";
|
|
6
|
+
import { v4 } from "uuid";
|
|
14
7
|
|
|
15
|
-
type
|
|
16
|
-
width: number;
|
|
17
|
-
height: number;
|
|
18
|
-
index?: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type MeasurementsPortalProps = {
|
|
8
|
+
type Props = {
|
|
22
9
|
Component: React.ElementType;
|
|
23
|
-
onLayout: (
|
|
24
|
-
componentProps?: Record<
|
|
25
|
-
onLoadFinished?: () => void;
|
|
10
|
+
onLayout: (LayoutChangeEvent) => void;
|
|
11
|
+
componentProps?: Record<any, any>;
|
|
26
12
|
};
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
measureComponent: (
|
|
30
|
-
comp: React.ElementType,
|
|
31
|
-
props: Record<string, any>
|
|
32
|
-
) => Promise<MeasurementResult>;
|
|
33
|
-
measuringInProgress: boolean;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type MeasurementsPortalContextProviderProps = {
|
|
37
|
-
children: ReactNode;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const isError = (possibleError: any): boolean => possibleError instanceof Error;
|
|
14
|
+
const isError = (possibleError): boolean => possibleError instanceof Error;
|
|
41
15
|
|
|
42
16
|
const styles = StyleSheet.create({
|
|
43
17
|
container: {
|
|
@@ -51,245 +25,100 @@ const styles = StyleSheet.create({
|
|
|
51
25
|
wrapper: {},
|
|
52
26
|
});
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
component: React.ElementType;
|
|
56
|
-
componentProps: Record<string, any> | null;
|
|
57
|
-
measureComponentCallback: ((result: MeasurementResult) => void) | null;
|
|
58
|
-
onLoadFinishedIsCalled: boolean;
|
|
59
|
-
measuringInProgress: boolean;
|
|
60
|
-
layout: LayoutDimensions;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
type Action =
|
|
64
|
-
| { type: "SET_COMPONENT"; payload: React.ElementType }
|
|
65
|
-
| { type: "SET_COMPONENT_PROPS"; payload: Record<string, any> | null }
|
|
66
|
-
| {
|
|
67
|
-
type: "SET_MEASURE_CALLBACK";
|
|
68
|
-
payload: ((result: MeasurementResult) => void) | null;
|
|
69
|
-
}
|
|
70
|
-
| { type: "SET_ON_LOAD_FINISHED"; payload: boolean }
|
|
71
|
-
| { type: "SET_MEASURING_PROGRESS"; payload: boolean }
|
|
72
|
-
| { type: "SET_LAYOUT"; payload: LayoutDimensions };
|
|
73
|
-
|
|
74
|
-
const MEASUREMENT_ACTIONS = {
|
|
75
|
-
setComponent: "SET_COMPONENT",
|
|
76
|
-
setComponentProps: "SET_COMPONENT_PROPS",
|
|
77
|
-
setMeasureCallback: "SET_MEASURE_CALLBACK",
|
|
78
|
-
setOnLoadFinished: "SET_ON_LOAD_FINISHED",
|
|
79
|
-
setMeasuringProgress: "SET_MEASURING_PROGRESS",
|
|
80
|
-
setLayout: "SET_LAYOUT",
|
|
81
|
-
} as const;
|
|
82
|
-
|
|
83
|
-
const reducer = (state: State, action: Action): State => {
|
|
84
|
-
switch (action.type) {
|
|
85
|
-
case MEASUREMENT_ACTIONS.setComponent:
|
|
86
|
-
return { ...state, component: action.payload };
|
|
87
|
-
case MEASUREMENT_ACTIONS.setComponentProps:
|
|
88
|
-
return { ...state, componentProps: action.payload };
|
|
89
|
-
case MEASUREMENT_ACTIONS.setMeasureCallback:
|
|
90
|
-
return { ...state, measureComponentCallback: action.payload };
|
|
91
|
-
case MEASUREMENT_ACTIONS.setOnLoadFinished:
|
|
92
|
-
return { ...state, onLoadFinishedIsCalled: action.payload };
|
|
93
|
-
case MEASUREMENT_ACTIONS.setMeasuringProgress:
|
|
94
|
-
return { ...state, measuringInProgress: action.payload };
|
|
95
|
-
case MEASUREMENT_ACTIONS.setLayout:
|
|
96
|
-
return { ...state, layout: action.payload };
|
|
97
|
-
default:
|
|
98
|
-
return state;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const initialState: State = {
|
|
103
|
-
component: null,
|
|
104
|
-
componentProps: null,
|
|
105
|
-
measureComponentCallback: null,
|
|
106
|
-
onLoadFinishedIsCalled: false,
|
|
107
|
-
measuringInProgress: false,
|
|
108
|
-
layout: { width: 0, height: 0 },
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Handles layout changes for the MeasurementsPortal component
|
|
113
|
-
*/
|
|
114
|
-
const onLayout =
|
|
115
|
-
(dispatch) =>
|
|
116
|
-
({ nativeEvent: { layout: newLayout } }: LayoutChangeEvent) => {
|
|
117
|
-
log_debug("Rendered measurement layout", { newLayout });
|
|
118
|
-
dispatch({ type: MEASUREMENT_ACTIONS.setLayout, payload: newLayout });
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* MeasurementsPortal is a component that is used to measure the size of a component
|
|
123
|
-
* It is not rendered to the main tree, but it actually uses a component and its props to measure the size of the component.
|
|
124
|
-
* The <Component> comes from the measureComponent method of the MeasurementPortalContext.
|
|
125
|
-
*/
|
|
126
|
-
const MeasurementsPortal = ({
|
|
127
|
-
Component,
|
|
128
|
-
onLayout,
|
|
129
|
-
componentProps,
|
|
130
|
-
}: MeasurementsPortalProps) => {
|
|
131
|
-
if (!Component) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
28
|
+
const MeasurementsPortal = ({ Component, onLayout, componentProps }: Props) => {
|
|
135
29
|
return (
|
|
136
30
|
<View style={styles.container} testID="MeasurementsPortal">
|
|
137
|
-
<View style={styles.wrapper} onLayout={onLayout}>
|
|
31
|
+
<View style={styles.wrapper} onLayout={onLayout} key={v4()}>
|
|
138
32
|
<Component {...componentProps} />
|
|
139
33
|
</View>
|
|
140
34
|
</View>
|
|
141
35
|
);
|
|
142
36
|
};
|
|
143
37
|
|
|
144
|
-
const MeasurementPortalContext =
|
|
145
|
-
React.createContext<MeasurementPortalContextValue | null>(null);
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* The MeasurementsPortalContextProvider provides a measureComponent method, and a measuringInProgress flag
|
|
149
|
-
* With these we can pre-render a component and measure its size before rendering it to the main tree to avoid layout shifts
|
|
150
|
-
*/
|
|
151
|
-
const MeasurementsPortalContextProvider = ({
|
|
152
|
-
children,
|
|
153
|
-
}: MeasurementsPortalContextProviderProps) => {
|
|
154
|
-
const [state, dispatch] = useReducer(reducer, initialState);
|
|
155
|
-
|
|
156
|
-
const onLoadFinishedRef =
|
|
157
|
-
React.useRef<MeasurementsPortalProps["onLoadFinished"]>(null);
|
|
38
|
+
const MeasurementPortalContext = React.createContext(null);
|
|
158
39
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
const finalize = useCallback(() => {
|
|
164
|
-
if (state.measureComponentCallback) {
|
|
165
|
-
log_debug("Finalize measurement process", {
|
|
166
|
-
layout: state.layout,
|
|
167
|
-
componentProps: state.componentProps,
|
|
168
|
-
});
|
|
40
|
+
const MeasurementsPortalContextProvider = ({ children }) => {
|
|
41
|
+
const Component = React.useRef(View);
|
|
42
|
+
const [measuringInProgress, setMeasuringInProgress] = React.useState(false);
|
|
169
43
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
index: state.componentProps?.index,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
44
|
+
const measureComponentCallback = React.useRef(null);
|
|
45
|
+
const componentProps = React.useRef(null);
|
|
46
|
+
const onLoadFinishedIsCalled = React.useRef(null);
|
|
176
47
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
type: MEASUREMENT_ACTIONS.setMeasuringProgress,
|
|
181
|
-
payload: false,
|
|
182
|
-
});
|
|
48
|
+
const setComponent = (comp) => {
|
|
49
|
+
Component.current = comp;
|
|
50
|
+
};
|
|
183
51
|
|
|
184
|
-
|
|
185
|
-
|
|
52
|
+
const setMeasureComponentCallback = (cb) => {
|
|
53
|
+
measureComponentCallback.current = cb;
|
|
54
|
+
};
|
|
186
55
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
56
|
+
const finalize = ({ width, height }) => {
|
|
57
|
+
measureComponentCallback?.current?.({
|
|
58
|
+
width,
|
|
59
|
+
height,
|
|
60
|
+
index: componentProps.current.index,
|
|
192
61
|
});
|
|
193
62
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
onLoadFinishedRef.current
|
|
199
|
-
) {
|
|
200
|
-
finalize();
|
|
201
|
-
}
|
|
202
|
-
}, [state.measureComponentCallback, state.layout, finalize]);
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Utility function that takes, a component and its props, measures the rendered size of the component
|
|
206
|
-
* and creates a promise that does not get resolved with the measurement result (width/height/index)
|
|
207
|
-
* until the onLoadFinished callback is called
|
|
208
|
-
*/
|
|
209
|
-
const measureComponent = useCallback(
|
|
210
|
-
(
|
|
211
|
-
comp: React.ElementType,
|
|
212
|
-
props: { onLoadFinished: () => void; [key: string]: any }
|
|
213
|
-
): Promise<MeasurementResult> => {
|
|
214
|
-
return new Promise((resolve) => {
|
|
215
|
-
log_debug("Measuring process started", { comp, props });
|
|
216
|
-
|
|
217
|
-
dispatch({
|
|
218
|
-
type: MEASUREMENT_ACTIONS.setMeasureCallback,
|
|
219
|
-
payload: resolve,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
dispatch({
|
|
223
|
-
type: MEASUREMENT_ACTIONS.setMeasuringProgress,
|
|
224
|
-
payload: true,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
dispatch({
|
|
228
|
-
type: MEASUREMENT_ACTIONS.setOnLoadFinished,
|
|
229
|
-
payload: false,
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
dispatch({
|
|
233
|
-
type: MEASUREMENT_ACTIONS.setLayout,
|
|
234
|
-
payload: { width: 0, height: 0 },
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
onLoadFinishedRef.current =
|
|
238
|
-
// eslint-disable-next-line react/prop-types
|
|
239
|
-
props.onLoadFinished as MeasurementsPortalProps["onLoadFinished"];
|
|
240
|
-
|
|
241
|
-
const handleOnLoadFinish = (...args: any[]) => {
|
|
242
|
-
if (args?.[0]?.error && isError(args[0].error)) {
|
|
243
|
-
log_debug(
|
|
244
|
-
"Component called onLoadFinish with an error, completing measurement process",
|
|
245
|
-
{ args }
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
finalize();
|
|
249
|
-
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
log_debug(
|
|
254
|
-
"Component called onLoadFinish, completing measurement process",
|
|
255
|
-
{ args }
|
|
256
|
-
);
|
|
63
|
+
setMeasureComponentCallback(null);
|
|
64
|
+
setMeasuringInProgress(false);
|
|
65
|
+
onLoadFinishedIsCalled.current = false;
|
|
66
|
+
};
|
|
257
67
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
68
|
+
const setComponentProps = (props) => {
|
|
69
|
+
const handleOnLoadFinish = (...args) => {
|
|
70
|
+
const error = R.path([0, "error"], args);
|
|
71
|
+
|
|
72
|
+
if (isError(error)) {
|
|
73
|
+
finalize({ width: 0, height: 0 });
|
|
74
|
+
} else {
|
|
75
|
+
onLoadFinishedIsCalled.current = true;
|
|
76
|
+
props.onLoadFinished(...args);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
componentProps.current = {
|
|
81
|
+
...props,
|
|
82
|
+
onLoadFinished: handleOnLoadFinish,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
262
85
|
|
|
263
|
-
|
|
264
|
-
|
|
86
|
+
const measureComponent = React.useCallback(async (comp, props) => {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
setMeasureComponentCallback(resolve);
|
|
89
|
+
setMeasuringInProgress(true);
|
|
265
90
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
91
|
+
setComponentProps(props);
|
|
92
|
+
setComponent(comp);
|
|
93
|
+
});
|
|
94
|
+
}, []);
|
|
270
95
|
|
|
271
|
-
|
|
272
|
-
|
|
96
|
+
const onLayout = ({
|
|
97
|
+
nativeEvent: {
|
|
98
|
+
layout: { width, height },
|
|
273
99
|
},
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
[measureComponent, state.measuringInProgress]
|
|
283
|
-
);
|
|
100
|
+
}) => {
|
|
101
|
+
if (measureComponentCallback.current && onLoadFinishedIsCalled.current) {
|
|
102
|
+
finalize({
|
|
103
|
+
width,
|
|
104
|
+
height,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
284
108
|
|
|
285
109
|
return (
|
|
286
110
|
<>
|
|
287
111
|
<MeasurementsPortal
|
|
288
|
-
Component={
|
|
289
|
-
componentProps={
|
|
290
|
-
onLayout={onLayout
|
|
112
|
+
Component={Component.current}
|
|
113
|
+
componentProps={componentProps.current}
|
|
114
|
+
onLayout={onLayout}
|
|
291
115
|
/>
|
|
292
|
-
<MeasurementPortalContext.Provider
|
|
116
|
+
<MeasurementPortalContext.Provider
|
|
117
|
+
value={{
|
|
118
|
+
measureComponent,
|
|
119
|
+
measuringInProgress,
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
293
122
|
{children}
|
|
294
123
|
</MeasurementPortalContext.Provider>
|
|
295
124
|
</>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { View, LayoutChangeEvent, ScrollView } from "react-native";
|
|
9
|
+
import { Button } from "./Button";
|
|
10
|
+
import { ItemIconProps } from "./Button/ItemIcon";
|
|
11
|
+
import { ItemProps } from "./Button/Item";
|
|
12
|
+
import { ItemLabelProps } from "./Button/ItemLabel";
|
|
13
|
+
import {
|
|
14
|
+
defaultItemIconProps,
|
|
15
|
+
defaultItemLabelProps,
|
|
16
|
+
defaultItemProps,
|
|
17
|
+
} from "./utils";
|
|
18
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
19
|
+
|
|
20
|
+
import { ModalHeader } from "./Header";
|
|
21
|
+
|
|
22
|
+
type ModalComponentProps = {
|
|
23
|
+
items: any[];
|
|
24
|
+
current_selection?: any;
|
|
25
|
+
onPress: (item: any) => void;
|
|
26
|
+
width: number;
|
|
27
|
+
currentRoute: boolean;
|
|
28
|
+
summary?: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
maxHeight?: number;
|
|
31
|
+
dismiss: () => void;
|
|
32
|
+
buttonComponent?: React.ComponentType;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
36
|
+
const {
|
|
37
|
+
items,
|
|
38
|
+
currentRoute,
|
|
39
|
+
maxHeight,
|
|
40
|
+
current_selection = null,
|
|
41
|
+
onPress,
|
|
42
|
+
dismiss,
|
|
43
|
+
summary,
|
|
44
|
+
title,
|
|
45
|
+
buttonComponent: ButtonComponent = Button,
|
|
46
|
+
} = props;
|
|
47
|
+
|
|
48
|
+
const [headerHeight, setHeaderHeight] = useState(0);
|
|
49
|
+
const route = useRef(currentRoute);
|
|
50
|
+
const theme = useTheme<BaseThemePropertiesMobile>();
|
|
51
|
+
|
|
52
|
+
const maxContentHeight = maxHeight
|
|
53
|
+
? maxHeight - headerHeight - Number(theme.modal_bottom_sheet_padding_top)
|
|
54
|
+
: undefined;
|
|
55
|
+
|
|
56
|
+
const onHeaderLayout = useCallback((event: LayoutChangeEvent) => {
|
|
57
|
+
setHeaderHeight(event.nativeEvent.layout.height);
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (currentRoute !== route.current) {
|
|
62
|
+
props.dismiss();
|
|
63
|
+
}
|
|
64
|
+
}, [currentRoute]);
|
|
65
|
+
|
|
66
|
+
const iconBaseProps = useMemo<ItemIconProps>(() => {
|
|
67
|
+
return defaultItemIconProps(theme);
|
|
68
|
+
}, [theme]);
|
|
69
|
+
|
|
70
|
+
const itemBaseProps = useMemo<ItemProps>(() => {
|
|
71
|
+
return defaultItemProps(theme, props.width);
|
|
72
|
+
}, [theme, props.width]);
|
|
73
|
+
|
|
74
|
+
const labelBaseProps = useMemo<ItemLabelProps>(() => {
|
|
75
|
+
return defaultItemLabelProps(theme, props.width);
|
|
76
|
+
}, [theme, props.width]);
|
|
77
|
+
|
|
78
|
+
const handlePress = (item: any) => {
|
|
79
|
+
onPress(item);
|
|
80
|
+
dismiss();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View
|
|
85
|
+
style={{
|
|
86
|
+
paddingTop: Number(theme.modal_bottom_sheet_padding_top),
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<ModalHeader
|
|
90
|
+
width={props.width}
|
|
91
|
+
dismiss={dismiss}
|
|
92
|
+
configuration={theme}
|
|
93
|
+
onLayout={onHeaderLayout}
|
|
94
|
+
summary={summary}
|
|
95
|
+
title={title}
|
|
96
|
+
/>
|
|
97
|
+
<ScrollView
|
|
98
|
+
bounces={false}
|
|
99
|
+
style={{ maxHeight: maxContentHeight }}
|
|
100
|
+
contentContainerStyle={{
|
|
101
|
+
paddingBottom: Number(theme.modal_bottom_sheet_padding_bottom),
|
|
102
|
+
paddingTop: Number(theme.modal_bottom_sheet_padding_top),
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{items.map((item, index) => (
|
|
106
|
+
<ButtonComponent
|
|
107
|
+
key={index}
|
|
108
|
+
width={props.width}
|
|
109
|
+
configuration={theme}
|
|
110
|
+
selectedItem={current_selection}
|
|
111
|
+
item={item}
|
|
112
|
+
onPress={handlePress}
|
|
113
|
+
iconBaseProps={iconBaseProps}
|
|
114
|
+
itemBaseProps={itemBaseProps}
|
|
115
|
+
labelBaseProps={labelBaseProps}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
</ScrollView>
|
|
119
|
+
</View>
|
|
120
|
+
);
|
|
121
|
+
}
|