@applicaster/zapp-react-native-ui-components 12.0.0-rc.3 → 12.0.0-rc.31
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/ErrorScreen/index.tsx +1 -1
- package/Components/FocusableList/FocusableChild.ts +24 -14
- package/Components/FocusableList/FocusableItem.tsx +1 -1
- package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
- package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
- package/Components/FocusableList/index.tsx +26 -25
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +11 -8
- package/Components/HandlePlayable/HandlePlayable.tsx +1 -1
- package/Components/HookRenderer/index.tsx +1 -4
- package/Components/Image/Image.android.tsx +11 -2
- package/Components/Image/Image.tsx +18 -2
- package/Components/Image/utils/index.ts +29 -0
- package/Components/Layout/TV/__tests__/index.test.tsx +0 -21
- package/Components/Layout/TV/index.tsx +20 -36
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +0 -1
- package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +21 -8
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
- package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +3 -1
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +8 -10
- package/Components/MasterCell/utils/index.ts +2 -2
- 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/PlayerContainer/ProgramInfo/index.tsx +3 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +25 -0
- package/Components/River/River.tsx +8 -5
- package/Components/River/TV/River.tsx +2 -2
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
- package/Components/River/useScreenConfiguration.ts +5 -6
- package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
- package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
- package/Components/RouteManager/index.tsx +5 -3
- package/Components/Screen/__tests__/Screen.test.tsx +43 -35
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +10 -4
- package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
- package/Components/Screen/index.tsx +92 -46
- package/Components/Screen/navigationHandler.ts +0 -82
- package/Components/Screen/orientationHandler.ts +1 -1
- package/Components/ScreenResolver/index.tsx +1 -1
- package/Components/TrackedView/index.tsx +6 -3
- package/Components/VideoLive/LiveImageManager.ts +42 -13
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +81 -72
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +25 -3
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +22 -27
- package/Components/VideoModal/PlayerDetails.tsx +3 -0
- package/Components/VideoModal/PlayerWrapper.tsx +66 -63
- package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +0 -2
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +4 -4
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +10 -18
- package/Components/VideoModal/hooks/useModalSize.ts +13 -4
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
- package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
- package/Components/ZappUIComponent/index.tsx +22 -12
- package/Components/index.js +2 -0
- package/Contexts/ScreenContext/index.tsx +1 -4
- package/Decorators/RiverResolver/index.tsx +0 -4
- package/Decorators/index.tsx +0 -4
- package/package.json +5 -6
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
4
|
-
import {
|
|
5
|
-
useIsNavBarVisible,
|
|
6
|
-
useNavigation,
|
|
7
|
-
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
4
|
+
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
8
5
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
9
6
|
|
|
10
7
|
import {
|
|
@@ -43,7 +40,7 @@ type Props = {
|
|
|
43
40
|
const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
44
41
|
const navigator = useNavigation();
|
|
45
42
|
const theme = useTheme();
|
|
46
|
-
const { title } = useNavbarState();
|
|
43
|
+
const { title, visible } = useNavbarState();
|
|
47
44
|
const { activeRiver, currentRoute } = navigator;
|
|
48
45
|
|
|
49
46
|
const {
|
|
@@ -80,10 +77,22 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
|
80
77
|
[]
|
|
81
78
|
);
|
|
82
79
|
|
|
83
|
-
const navBarVisible = useIsNavBarVisible();
|
|
84
|
-
|
|
85
80
|
const [navBarReady, setNavBarReady] = React.useState(false);
|
|
86
81
|
|
|
82
|
+
const navBarContainer = (
|
|
83
|
+
<NavBarContainer isVisible={visible} onReady={setNavBarReady}>
|
|
84
|
+
<NavBar
|
|
85
|
+
ref={ComponentsExtraProps?.NavBar?.ref}
|
|
86
|
+
isVisible={visible && !routeIsPlayerScreen(currentRoute)}
|
|
87
|
+
menuPlugin={menuPlugin}
|
|
88
|
+
navigationProps={navigationProps}
|
|
89
|
+
{...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
|
|
90
|
+
/>
|
|
91
|
+
</NavBarContainer>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const isTVOS = isApplePlatform();
|
|
95
|
+
|
|
87
96
|
if (!appReady) {
|
|
88
97
|
return null;
|
|
89
98
|
}
|
|
@@ -96,18 +105,7 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
|
96
105
|
backgroundImageUrl={backgroundImageUrl}
|
|
97
106
|
{...R.omit(["ref"])(ComponentsExtraProps?.Background)}
|
|
98
107
|
>
|
|
99
|
-
{!
|
|
100
|
-
<NavBarContainer isVisible={navBarVisible} onReady={setNavBarReady}>
|
|
101
|
-
<NavBar
|
|
102
|
-
ref={ComponentsExtraProps?.NavBar?.ref}
|
|
103
|
-
isVisible={navBarVisible && !routeIsPlayerScreen(currentRoute)}
|
|
104
|
-
menuPlugin={menuPlugin}
|
|
105
|
-
navigationProps={navigationProps}
|
|
106
|
-
{...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
|
|
107
|
-
/>
|
|
108
|
-
</NavBarContainer>
|
|
109
|
-
) : null}
|
|
110
|
-
|
|
108
|
+
{!isTVOS ? navBarContainer : null}
|
|
111
109
|
{navBarReady ? (
|
|
112
110
|
/**
|
|
113
111
|
Need to re-mount this component on each navigation event
|
|
@@ -118,23 +116,9 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
|
|
|
118
116
|
<ScreenContainer>{children}</ScreenContainer>
|
|
119
117
|
</PathnameContext.Provider>
|
|
120
118
|
) : null}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Render NavBarContainer over ScreenContainer to let tvOS native
|
|
125
|
-
focus manager works correctly to focus nav bar items first
|
|
126
|
-
**/
|
|
127
|
-
<NavBarContainer isVisible={navBarVisible} onReady={setNavBarReady}>
|
|
128
|
-
<NavBar
|
|
129
|
-
ref={ComponentsExtraProps?.NavBar?.ref}
|
|
130
|
-
isVisible={navBarVisible && !routeIsPlayerScreen(currentRoute)}
|
|
131
|
-
menuPlugin={menuPlugin}
|
|
132
|
-
navigationProps={navigationProps}
|
|
133
|
-
{...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
|
|
134
|
-
/>
|
|
135
|
-
</NavBarContainer>
|
|
136
|
-
) : null}
|
|
137
|
-
|
|
119
|
+
{/** Render NavBarContainer over ScreenContainer to let tvOS native
|
|
120
|
+
focus manager works correctly to focus nav bar items first **/}
|
|
121
|
+
{isTVOS ? navBarContainer : null}
|
|
138
122
|
<TransitionOverlay />
|
|
139
123
|
</Components.Background>
|
|
140
124
|
</ScreenLayoutContextProvider>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compact,
|
|
3
3
|
ifElseFocusedSelected,
|
|
4
|
+
ifElseSelected,
|
|
4
5
|
} from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
5
6
|
|
|
6
7
|
import { getPositionStyles } from "./utils";
|
|
@@ -12,9 +13,15 @@ type Props = {
|
|
|
12
13
|
value: Function;
|
|
13
14
|
state: string;
|
|
14
15
|
visible?: Boolean;
|
|
16
|
+
isMobile?: Boolean;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
export const CellBadge = ({
|
|
19
|
+
export const CellBadge = ({
|
|
20
|
+
value,
|
|
21
|
+
state,
|
|
22
|
+
visible = true,
|
|
23
|
+
isMobile = false,
|
|
24
|
+
}: Props) => {
|
|
18
25
|
if (!value("cell_badge_switch") || !visible) {
|
|
19
26
|
return null;
|
|
20
27
|
}
|
|
@@ -45,13 +52,19 @@ export const CellBadge = ({ value, state, visible = true }: Props) => {
|
|
|
45
52
|
data: [
|
|
46
53
|
{
|
|
47
54
|
func: () => {
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
return isMobile
|
|
56
|
+
? ifElseSelected(
|
|
57
|
+
state,
|
|
58
|
+
value("cell_badge_selected"),
|
|
59
|
+
value("cell_badge_default")
|
|
60
|
+
)
|
|
61
|
+
: ifElseFocusedSelected(
|
|
62
|
+
state,
|
|
63
|
+
value("cell_badge_focused"),
|
|
64
|
+
value("cell_badge_selected"),
|
|
65
|
+
value("cell_badge_focused_selected"),
|
|
66
|
+
value("cell_badge_default")
|
|
67
|
+
);
|
|
55
68
|
},
|
|
56
69
|
args: [],
|
|
57
70
|
propName: "uri",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { ImageStyle, View } from "react-native";
|
|
3
3
|
import * as R from "ramda";
|
|
4
|
-
import
|
|
4
|
+
import { QBImage } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
5
5
|
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
6
|
-
import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
7
6
|
|
|
8
7
|
import { useImageSourceWithDefault, useCheckFailedUri } from "./hooks";
|
|
8
|
+
import useImageSize from "./hooks/useImageSize";
|
|
9
9
|
|
|
10
10
|
type Source = {
|
|
11
11
|
uri: string;
|
|
@@ -19,32 +19,13 @@ type Props = Record<string, any> & {
|
|
|
19
19
|
withDimensions: (source: Source) => Source;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
const MemoizedImage = React.memo(
|
|
23
|
-
|
|
24
|
-
const normalize = Math.ceil;
|
|
22
|
+
const MemoizedImage = React.memo(QBImage, R.equals);
|
|
25
23
|
|
|
26
24
|
const isValidExternalSource = (source): boolean =>
|
|
27
25
|
source?.uri?.startsWith("http") ||
|
|
28
26
|
source?.uri?.startsWith("data:image") ||
|
|
29
27
|
source?.uri?.startsWith("file://");
|
|
30
28
|
|
|
31
|
-
const isValidSize = (size: unknown): boolean =>
|
|
32
|
-
toFiniteNumberWithDefault(0, size) > 0;
|
|
33
|
-
|
|
34
|
-
const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
|
|
35
|
-
const { width, height } = style;
|
|
36
|
-
|
|
37
|
-
if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
|
|
38
|
-
return source;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
...source,
|
|
43
|
-
maxWidth: normalize(width),
|
|
44
|
-
maxHeight: normalize(height),
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
|
|
48
29
|
export default function Image({
|
|
49
30
|
style,
|
|
50
31
|
uri,
|
|
@@ -82,10 +63,12 @@ export default function Image({
|
|
|
82
63
|
|
|
83
64
|
const applyMaxSize = toBooleanWithDefaultFalse(otherProps?.applyMaxSize);
|
|
84
65
|
|
|
85
|
-
const sourceWithMaxSizes =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
66
|
+
const sourceWithMaxSizes = useImageSize({
|
|
67
|
+
style,
|
|
68
|
+
applyMaxSize,
|
|
69
|
+
source,
|
|
70
|
+
withDimensions,
|
|
71
|
+
});
|
|
89
72
|
|
|
90
73
|
if (isFailedToLoadUri(uri) || !isValidExternalSource(source)) {
|
|
91
74
|
return <View style={style} />;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ImageStyle } from "react-native";
|
|
3
|
+
import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
4
|
+
|
|
5
|
+
type Source = {
|
|
6
|
+
uri: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Props = Record<string, any> & {
|
|
10
|
+
style: ImageStyle;
|
|
11
|
+
uri?: string;
|
|
12
|
+
placeholderImage?: string;
|
|
13
|
+
entry?: ZappEntry | ZappFeed;
|
|
14
|
+
withDimensions: (source: Source) => Source;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const normalize = Math.ceil;
|
|
18
|
+
|
|
19
|
+
const isValidSize = (size: unknown): boolean =>
|
|
20
|
+
toFiniteNumberWithDefault(0, size) > 0;
|
|
21
|
+
|
|
22
|
+
type WidthDimensions = { aspectRatio?: number; width: number; height?: number };
|
|
23
|
+
type HeightDimensions = {
|
|
24
|
+
aspectRatio?: number;
|
|
25
|
+
width?: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
type FullDimensions = { aspectRatio?: number; width: number; height: number };
|
|
29
|
+
type Dimensions = WidthDimensions | HeightDimensions | FullDimensions;
|
|
30
|
+
|
|
31
|
+
const calculateDimensions = ({ aspectRatio, width, height }: Dimensions) => {
|
|
32
|
+
if (aspectRatio) {
|
|
33
|
+
return {
|
|
34
|
+
width: width || height * aspectRatio,
|
|
35
|
+
height: height || width / aspectRatio,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { width, height };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
|
|
43
|
+
const { width, height } = calculateDimensions(style);
|
|
44
|
+
|
|
45
|
+
if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
|
|
46
|
+
return source;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...source,
|
|
51
|
+
maxWidth: normalize(width),
|
|
52
|
+
maxHeight: normalize(height),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default function useImageSize(props: Props) {
|
|
57
|
+
const { style, applyMaxSize, source, withDimensions } = props;
|
|
58
|
+
|
|
59
|
+
const sourceWithMaxSizes = React.useMemo(
|
|
60
|
+
() => withDimensions(getSourceWithMaxSizes(applyMaxSize, style, source)),
|
|
61
|
+
[applyMaxSize, style?.height, style?.width, source?.uri, withDimensions]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return sourceWithMaxSizes;
|
|
65
|
+
}
|
|
@@ -18,6 +18,7 @@ type LiveImageProps = {
|
|
|
18
18
|
imageKey?: string;
|
|
19
19
|
state: "focused" | "unfocused" | "selected";
|
|
20
20
|
audio_muted_by_default?: boolean;
|
|
21
|
+
uri: string; // cover image url
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const DEFAULT_PLAYER_IDENTIFIER = "QuickBrickPlayerPlugin";
|
|
@@ -209,10 +210,11 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
209
210
|
|
|
210
211
|
return (
|
|
211
212
|
<>
|
|
212
|
-
<Image {...props} />
|
|
213
|
+
<Image {...props} applyMaxSize />
|
|
213
214
|
{isShouldRender && (
|
|
214
215
|
<View style={[style, positionAbsolute]}>
|
|
215
216
|
<PlayerLiveImage
|
|
217
|
+
uri={props.uri}
|
|
216
218
|
playerId={cellUUID}
|
|
217
219
|
playerPluginId={playerPluginId}
|
|
218
220
|
screenConfig={screenConfig}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { isNil } from "ramda";
|
|
3
|
-
import {
|
|
3
|
+
import { ImageStyle, View } from "react-native";
|
|
4
4
|
import {
|
|
5
5
|
FIT_POSITION,
|
|
6
6
|
IMAGE_SIZING_FIT,
|
|
7
7
|
IMAGE_SIZING_FILL,
|
|
8
8
|
} from "@applicaster/zapp-react-native-utils/manifestUtils/secondaryImage";
|
|
9
|
+
import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
9
10
|
|
|
10
11
|
import { useGetImageDimensions } from "./hooks";
|
|
11
12
|
import {
|
|
@@ -59,7 +60,7 @@ const SecondaryImageComponent = (props: Props) => {
|
|
|
59
60
|
|
|
60
61
|
return (
|
|
61
62
|
<View style={style} onLayout={onAsyncRender}>
|
|
62
|
-
<
|
|
63
|
+
<Image
|
|
63
64
|
{...props}
|
|
64
65
|
source={{ uri }}
|
|
65
66
|
style={{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { path
|
|
1
|
+
import { path } from "ramda";
|
|
2
2
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
3
|
import {
|
|
4
|
+
getColorFromData,
|
|
4
5
|
getKeyForLabel,
|
|
5
6
|
mapSelfAlignment,
|
|
6
7
|
} from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
@@ -48,15 +49,12 @@ export const CollapsibleTextContainer = ({
|
|
|
48
49
|
]),
|
|
49
50
|
{
|
|
50
51
|
func: (data) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
// return default value
|
|
58
|
-
return additionalStyles.backgroundColor;
|
|
59
|
-
}
|
|
52
|
+
return getColorFromData({
|
|
53
|
+
data,
|
|
54
|
+
getValue,
|
|
55
|
+
key: "background_color",
|
|
56
|
+
defaultColor: additionalStyles.backgroundColor,
|
|
57
|
+
});
|
|
60
58
|
},
|
|
61
59
|
propName: "backgroundColor",
|
|
62
60
|
},
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, { ReactChild } from "react";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
|
|
4
|
+
export type ItemProps = {
|
|
5
|
+
backgroundColor: string;
|
|
6
|
+
focusedBackgroundColor: string;
|
|
7
|
+
selectedBackgroundColor?: string;
|
|
8
|
+
focusectedBackgroundColor?: string;
|
|
9
|
+
borderRadius: number;
|
|
10
|
+
marginBottom: number;
|
|
11
|
+
marginLeft: number;
|
|
12
|
+
marginRight: number;
|
|
13
|
+
marginTop: number;
|
|
14
|
+
paddingTop: number;
|
|
15
|
+
paddingBottom: number;
|
|
16
|
+
paddingRight: number;
|
|
17
|
+
paddingLeft: number;
|
|
18
|
+
focused?: boolean;
|
|
19
|
+
selected?: boolean;
|
|
20
|
+
children?: ReactChild[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const styles = StyleSheet.create({
|
|
24
|
+
container: {
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
justifyContent: "space-between",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function getBackgroundColor({
|
|
31
|
+
selected,
|
|
32
|
+
focused,
|
|
33
|
+
backgroundColor,
|
|
34
|
+
focusedBackgroundColor,
|
|
35
|
+
selectedBackgroundColor,
|
|
36
|
+
focusectedBackgroundColor,
|
|
37
|
+
}) {
|
|
38
|
+
switch (true) {
|
|
39
|
+
case selected && focused:
|
|
40
|
+
return focusectedBackgroundColor;
|
|
41
|
+
case selected && !focused:
|
|
42
|
+
return selectedBackgroundColor;
|
|
43
|
+
case !selected && focused:
|
|
44
|
+
return focusedBackgroundColor;
|
|
45
|
+
default:
|
|
46
|
+
return backgroundColor;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Item(props: ItemProps) {
|
|
51
|
+
const {
|
|
52
|
+
backgroundColor,
|
|
53
|
+
focusedBackgroundColor,
|
|
54
|
+
focusectedBackgroundColor,
|
|
55
|
+
selectedBackgroundColor,
|
|
56
|
+
children,
|
|
57
|
+
focused,
|
|
58
|
+
selected,
|
|
59
|
+
...itemStyles
|
|
60
|
+
} = props;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View
|
|
64
|
+
style={[
|
|
65
|
+
styles.container,
|
|
66
|
+
itemStyles,
|
|
67
|
+
{
|
|
68
|
+
backgroundColor: getBackgroundColor({
|
|
69
|
+
selected,
|
|
70
|
+
focused,
|
|
71
|
+
focusectedBackgroundColor,
|
|
72
|
+
focusedBackgroundColor,
|
|
73
|
+
selectedBackgroundColor,
|
|
74
|
+
backgroundColor,
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
]}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</View>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { QBImage } from "@applicaster/zapp-react-native-ui-components/Components";
|
|
4
|
+
|
|
5
|
+
export type ItemIconProps = {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
asset?: string | string[] | CellActionAssetComponent;
|
|
9
|
+
flavour?: "flavour_1" | "flavour_2";
|
|
10
|
+
marginTop: number;
|
|
11
|
+
marginBottom: number;
|
|
12
|
+
marginLeft: number;
|
|
13
|
+
marginRight: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function isImageAsset(asset) {
|
|
17
|
+
return typeof asset === "string" || Array.isArray(asset);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getImageAsset(asset, flavour) {
|
|
21
|
+
if (typeof asset === "string") return asset;
|
|
22
|
+
|
|
23
|
+
if (flavour) {
|
|
24
|
+
const flavourIndex = Number(flavour.replace("flavour_", ""));
|
|
25
|
+
if (flavourIndex > -1) return asset?.[flavourIndex - 1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return asset?.[0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ItemIcon(props: ItemIconProps) {
|
|
32
|
+
const { width, height, asset, flavour, ...styles } = props;
|
|
33
|
+
|
|
34
|
+
if (isImageAsset(asset)) {
|
|
35
|
+
const imageAsset = getImageAsset(asset, flavour);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View style={styles}>
|
|
39
|
+
<QBImage
|
|
40
|
+
fadeDuration={0}
|
|
41
|
+
source={{ uri: imageAsset }}
|
|
42
|
+
style={{ width, height }}
|
|
43
|
+
/>
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const AssetComponent = asset as CellActionAssetComponent;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<View style={styles}>
|
|
52
|
+
<AssetComponent
|
|
53
|
+
width={width}
|
|
54
|
+
height={height}
|
|
55
|
+
style={styles}
|
|
56
|
+
flavour={flavour}
|
|
57
|
+
/>
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
}
|