@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.
Files changed (27) hide show
  1. package/Components/AnimatedInOut/index.tsx +18 -10
  2. package/Components/AudioPlayer/AudioPlayer.tsx +9 -1
  3. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
  4. package/Components/AudioPlayer/helpers.tsx +1 -0
  5. package/Components/Image/Image.android.tsx +34 -2
  6. package/Components/MasterCell/utils/index.ts +2 -2
  7. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +78 -249
  8. package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
  9. package/Components/ModalComponent/Button/Item.tsx +82 -0
  10. package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
  11. package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
  12. package/Components/ModalComponent/Button/assets.ts +4 -0
  13. package/Components/ModalComponent/Button/index.tsx +111 -0
  14. package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
  15. package/Components/ModalComponent/Header/Title.tsx +70 -0
  16. package/Components/ModalComponent/Header/assets.ts +4 -0
  17. package/Components/ModalComponent/Header/index.tsx +71 -0
  18. package/Components/ModalComponent/Header/utils.ts +79 -0
  19. package/Components/ModalComponent/index.tsx +38 -0
  20. package/Components/ModalComponent/utils.ts +126 -0
  21. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +4 -7
  22. package/Components/VideoModal/PlayerDetails.tsx +3 -0
  23. package/Components/VideoModal/PlayerWrapper.tsx +63 -62
  24. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +3 -2
  25. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +10 -18
  26. package/Components/index.js +2 -0
  27. 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(props: Props) {
39
- const { visible, animationConfig, children } = props;
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(null);
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(null);
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
- } else {
74
+ }
75
+
76
+ if (previousVisible && !visible) {
69
77
  startAnimation(0.0, getAnimation(animationConfig, "out"));
70
78
  }
71
- }, [visible, animating]);
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), props?.staticStyles || {}]}
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",
@@ -5,6 +5,7 @@ const defaults = {
5
5
  audio_player_artwork_aspect_ratio: "1:1",
6
6
  audio_player_rtl: false,
7
7
  magic_background: false,
8
+ audio_player_background_image_query: "",
8
9
  };
9
10
 
10
11
  export function getPropertyFromEntryOrConfig({ entry, plugin_configuration }) {
@@ -1,3 +1,35 @@
1
- import FastImage from "@applicaster/react-native-fast-image";
1
+ import React from "react";
2
2
 
3
- export const QBImage = FastImage;
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
+ };
@@ -285,8 +285,8 @@ export const useBehaviorUpdate = (behavior: Behavior) => {
285
285
  }
286
286
  }
287
287
  }, [behavior]);
288
-
289
- useEffect(() => {
288
+
289
+ useEffect(() => {
290
290
  if (!behavior) {
291
291
  return;
292
292
  }
@@ -1,43 +1,17 @@
1
- import React, { useEffect, useCallback, ReactNode, useReducer } from "react";
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
- const { log_debug } = createLogger({
6
- subsystem: "MeasurementsPortal",
7
- category: "UIComponents",
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 MeasurementResult = {
16
- width: number;
17
- height: number;
18
- index?: number;
19
- };
20
-
21
- type MeasurementsPortalProps = {
8
+ type Props = {
22
9
  Component: React.ElementType;
23
- onLayout: (event: LayoutChangeEvent) => void;
24
- componentProps?: Record<string, any>;
25
- onLoadFinished?: () => void;
10
+ onLayout: (LayoutChangeEvent) => void;
11
+ componentProps?: Record<any, any>;
26
12
  };
27
13
 
28
- type MeasurementPortalContextValue = {
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
- type State = {
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
- * Finalize resolves the promise created by measureComponent and finalizes the measurement process
161
- * It resolves with the measurement result (width/height/index) to the measureComponentCallback
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
- state.measureComponentCallback({
171
- width: state.layout.width,
172
- height: state.layout.height,
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
- dispatch({ type: MEASUREMENT_ACTIONS.setMeasureCallback, payload: null });
178
-
179
- dispatch({
180
- type: MEASUREMENT_ACTIONS.setMeasuringProgress,
181
- payload: false,
182
- });
48
+ const setComponent = (comp) => {
49
+ Component.current = comp;
50
+ };
183
51
 
184
- dispatch({ type: MEASUREMENT_ACTIONS.setOnLoadFinished, payload: false });
185
- }, [state.layout, state.componentProps, state.measureComponentCallback]);
52
+ const setMeasureComponentCallback = (cb) => {
53
+ measureComponentCallback.current = cb;
54
+ };
186
55
 
187
- useEffect(() => {
188
- log_debug("Measurement portal updated", {
189
- measureComponentCallback: state.measureComponentCallback,
190
- onLoadFinishedIsCalled: state.onLoadFinishedIsCalled,
191
- layout: state.layout,
56
+ const finalize = ({ width, height }) => {
57
+ measureComponentCallback?.current?.({
58
+ width,
59
+ height,
60
+ index: componentProps.current.index,
192
61
  });
193
62
 
194
- if (
195
- state.measureComponentCallback &&
196
- state.layout.width > 0 &&
197
- state.layout.height > 0 &&
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
- dispatch({
259
- type: MEASUREMENT_ACTIONS.setOnLoadFinished,
260
- payload: true,
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
- onLoadFinishedRef.current?.apply(null, args);
264
- };
86
+ const measureComponent = React.useCallback(async (comp, props) => {
87
+ return new Promise((resolve) => {
88
+ setMeasureComponentCallback(resolve);
89
+ setMeasuringInProgress(true);
265
90
 
266
- dispatch({
267
- type: MEASUREMENT_ACTIONS.setComponentProps,
268
- payload: { ...props, onLoadFinished: handleOnLoadFinish },
269
- });
91
+ setComponentProps(props);
92
+ setComponent(comp);
93
+ });
94
+ }, []);
270
95
 
271
- dispatch({ type: MEASUREMENT_ACTIONS.setComponent, payload: comp });
272
- });
96
+ const onLayout = ({
97
+ nativeEvent: {
98
+ layout: { width, height },
273
99
  },
274
- []
275
- );
276
-
277
- const contextValue = React.useMemo(
278
- () => ({
279
- measureComponent,
280
- measuringInProgress: state.measuringInProgress,
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={state.component}
289
- componentProps={state.componentProps}
290
- onLayout={onLayout(dispatch)}
112
+ Component={Component.current}
113
+ componentProps={componentProps.current}
114
+ onLayout={onLayout}
291
115
  />
292
- <MeasurementPortalContext.Provider value={contextValue}>
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
+ }