@applicaster/zapp-react-native-ui-components 15.1.6-alpha.2015591222 → 15.1.6

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 (34) hide show
  1. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +69 -33
  2. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +47 -25
  3. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +39 -122
  4. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +131 -299
  5. package/Components/ModalComponent/AudioPlayer/Components/index.ts +4 -8
  6. package/Components/ModalComponent/BottomSheetModalContent.tsx +55 -290
  7. package/Components/ModalComponent/Button/index.tsx +3 -5
  8. package/Components/ModalComponent/Header/utils.ts +1 -1
  9. package/Components/ModalComponent/index.tsx +0 -11
  10. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +13 -82
  11. package/Components/OfflineHandler/__tests__/index.test.tsx +100 -35
  12. package/Components/OfflineHandler/hooks.ts +123 -0
  13. package/Components/OfflineHandler/index.tsx +51 -73
  14. package/Components/OfflineHandler/utils/index.ts +1 -13
  15. package/package.json +5 -6
  16. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +0 -671
  17. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +0 -86
  18. package/Components/ModalComponent/SortableList.tsx +0 -142
  19. package/Components/ModalComponent/TextInputContent.tsx +0 -100
  20. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +0 -183
  21. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +0 -128
  22. package/Components/ModalComponent/__tests__/showTextInput.test.ts +0 -95
  23. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +0 -66
  24. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +0 -74
  25. package/Components/ModalComponent/presets/index.ts +0 -27
  26. package/Components/ModalComponent/presets/selectableCell.tsx +0 -27
  27. package/Components/ModalComponent/presets/standardCell.tsx +0 -41
  28. package/Components/ModalComponent/presets/types.ts +0 -35
  29. package/Components/ModalComponent/showTextInput.ts +0 -43
  30. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  31. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  32. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  33. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  34. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
@@ -1,95 +0,0 @@
1
- import {
2
- actionExecutor,
3
- ActionResult,
4
- } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
5
- import { TextInputContent } from "../TextInputContent";
6
- import { showTextInputAction } from "../showTextInput";
7
-
8
- jest.mock(
9
- "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor",
10
- () => ({
11
- actionExecutor: {
12
- handleAction: jest.fn().mockResolvedValue("success"),
13
- },
14
- ActionResult: { Success: "success", Error: "error" },
15
- })
16
- );
17
-
18
- jest.mock("../TextInputContent", () => ({
19
- TextInputContent: function MockTextInputContent() {
20
- return null;
21
- },
22
- }));
23
-
24
- describe("showTextInputAction", () => {
25
- beforeEach(() => {
26
- jest.clearAllMocks();
27
- });
28
-
29
- it("delegates to openBottomSheet with TextInputContent as body-only component", async () => {
30
- const action = {
31
- type: "showTextInput",
32
- options: {
33
- headerTitle: "Create Playlist",
34
- inputLabel: "Name your playlist",
35
- defaultValue: "",
36
- buttonLabel: "Create",
37
- actions: [
38
- {
39
- type: "sendCloudEvent",
40
- options: {
41
- url: "https://example.com/events",
42
- type: "com.applicaster.collection.create.v1",
43
- data: {},
44
- },
45
- },
46
- {
47
- type: "showToast",
48
- options: {
49
- message: "Playlist created.",
50
- },
51
- },
52
- ],
53
- },
54
- };
55
-
56
- const result = await showTextInputAction(action as any);
57
-
58
- expect(result).toBe(ActionResult.Success);
59
- expect(actionExecutor.handleAction).toHaveBeenCalledTimes(1);
60
-
61
- expect(actionExecutor.handleAction).toHaveBeenCalledWith(
62
- {
63
- type: "openBottomSheet",
64
- options: {
65
- header: {
66
- title: "Create Playlist",
67
- },
68
- content: {
69
- component: TextInputContent,
70
- props: {
71
- inputLabel: "Name your playlist",
72
- defaultValue: "",
73
- buttonLabel: "Create",
74
- actions: action.options.actions,
75
- },
76
- },
77
- },
78
- },
79
- undefined
80
- );
81
- });
82
-
83
- it("returns Error when required options are missing", async () => {
84
- const action = {
85
- type: "showTextInput",
86
- options: {
87
- headerTitle: "Create Playlist",
88
- },
89
- };
90
-
91
- const result = await showTextInputAction(action as any);
92
- expect(result).toBe(ActionResult.Error);
93
- expect(actionExecutor.handleAction).not.toHaveBeenCalled();
94
- });
95
- });
@@ -1,66 +0,0 @@
1
- import { getCell } from "../index";
2
- import { standardCell } from "../standardCell";
3
- import { selectableCell } from "../selectableCell";
4
- import { dynamicCollectionCell } from "../dynamicCollectionCell";
5
- import { mapMenuItemToPlaylistData } from "../../AudioPlayer/Components/Item";
6
-
7
- describe("getCell", () => {
8
- it("returns selectable cell for collection_selector", () => {
9
- expect(getCell("collection_selector")).toBe(selectableCell);
10
- });
11
-
12
- it("returns selectable cell for preference_editor", () => {
13
- expect(getCell("preference_editor")).toBe(selectableCell);
14
- });
15
-
16
- it("returns standard cell when role is undefined", () => {
17
- expect(getCell(undefined)).toBe(standardCell);
18
- });
19
-
20
- it("returns standard cell for unknown roles", () => {
21
- expect(getCell("unknown_role")).toBe(standardCell);
22
- });
23
-
24
- it("returns the dynamic collection cell for dynamic_collection", () => {
25
- expect(getCell("dynamic_collection")).toBe(dynamicCollectionCell);
26
- });
27
-
28
- it("does not use behavior to select the cell family", () => {
29
- expect(getCell(undefined, { selectMode: "multi" })).toBe(standardCell);
30
-
31
- expect(getCell("collection_selector", { selectMode: "single" })).toBe(
32
- selectableCell
33
- );
34
- });
35
- });
36
-
37
- describe("mapMenuItemToPlaylistData", () => {
38
- const item = {
39
- title: "Playlist 1",
40
- summary: "5 items",
41
- icon: "https://example.com/icon.png",
42
- };
43
-
44
- it("defaults to multiSelect trailing button when selectMode is multi or undefined", () => {
45
- const data1 = mapMenuItemToPlaylistData(item, { selectMode: "multi" });
46
- expect(data1.trailingButton).toBe("multiSelect");
47
-
48
- const data2 = mapMenuItemToPlaylistData(item);
49
- expect(data2.trailingButton).toBe("multiSelect");
50
- });
51
-
52
- it("omits trailing button when behavior selectMode is none", () => {
53
- const data = mapMenuItemToPlaylistData(item, { selectMode: "none" });
54
- expect(data.trailingButton).toBeUndefined();
55
- });
56
-
57
- it("omits trailing button when item trailingButton is explicitly none", () => {
58
- const itemWithNone = { ...item, trailingButton: "none" };
59
-
60
- const data = mapMenuItemToPlaylistData(itemWithNone, {
61
- selectMode: "multi",
62
- });
63
-
64
- expect(data.trailingButton).toBeUndefined();
65
- });
66
- });
@@ -1,74 +0,0 @@
1
- import React from "react";
2
- import {
3
- AudioPlayerTrackItem,
4
- VARIANT_QUEUED_ITEM,
5
- ItemData,
6
- } from "../AudioPlayer/Components/Item";
7
- import type { CellRenderer, RenderItemArgs } from "./types";
8
- import { MenuItem } from "@applicaster/zapp-react-native-utils/modalState";
9
-
10
- function mapMenuItemToDynamicData(
11
- item: MenuItem,
12
- operations: string[]
13
- ): ItemData {
14
- const hasRemove = operations.includes("remove");
15
- const hasReorder = operations.includes("reorder");
16
-
17
- const title =
18
- typeof item?.title === "string"
19
- ? item.title
20
- : item?.title != null
21
- ? String(item.title)
22
- : "";
23
-
24
- const summary =
25
- typeof item?.summary === "string"
26
- ? item.summary
27
- : item?.summary != null
28
- ? String(item.summary)
29
- : "";
30
-
31
- return {
32
- imageUri: item?.icon,
33
- textLabel1: title,
34
- textLabel2: summary,
35
- leadingButton: hasRemove ? "remove" : undefined,
36
- trailingButton: hasReorder ? "drag" : undefined,
37
- isSelected: item?.isSelected,
38
- };
39
- }
40
-
41
- export const dynamicCollectionCell: CellRenderer = {
42
- renderItem: ({
43
- item,
44
- index,
45
- onPress,
46
- context,
47
- renderHandle,
48
- }: RenderItemArgs) => {
49
- const { editableCollection } = context;
50
- const operations = editableCollection?.operations || [];
51
-
52
- const handleRemove = () => {
53
- if (editableCollection?.remove) {
54
- editableCollection.remove(index);
55
- }
56
- };
57
-
58
- const handleDrag = () => {
59
- // Drag is managed by SortableList drag handle context
60
- };
61
-
62
- return (
63
- <AudioPlayerTrackItem
64
- key={index}
65
- configuration={VARIANT_QUEUED_ITEM}
66
- data={mapMenuItemToDynamicData(item, operations)}
67
- onPress={() => onPress(item)}
68
- onLeadingButtonPress={handleRemove}
69
- onTrailingButtonPress={handleDrag}
70
- renderHandle={renderHandle}
71
- />
72
- );
73
- },
74
- };
@@ -1,27 +0,0 @@
1
- import type { SelectionBehavior } from "@applicaster/zapp-react-native-utils/modalState";
2
- import { selectableCell } from "./selectableCell";
3
- import { standardCell } from "./standardCell";
4
- import type { CellRenderer } from "./types";
5
-
6
- export type { CellRenderer, RenderItemArgs, SheetRenderContext } from "./types";
7
-
8
- export { standardCell } from "./standardCell";
9
-
10
- export { selectableCell } from "./selectableCell";
11
-
12
- import { dynamicCollectionCell } from "./dynamicCollectionCell";
13
-
14
- const SELECTION_ROLES = new Set(["collection_selector", "preference_editor"]);
15
-
16
- /**
17
- * Resolve the row cell from feed role (+ behavior for future config only).
18
- * Unknown / none -> standard Button cell.
19
- */
20
- export const getCell = (
21
- role?: string,
22
- _behavior?: SelectionBehavior
23
- ): CellRenderer => {
24
- if (role === "dynamic_collection") return dynamicCollectionCell;
25
-
26
- return role && SELECTION_ROLES.has(role) ? selectableCell : standardCell;
27
- };
@@ -1,27 +0,0 @@
1
- import React from "react";
2
- import {
3
- AudioPlayerTrackItem,
4
- mapMenuItemToPlaylistData,
5
- VARIANT_PLAYLIST_ITEM,
6
- } from "../AudioPlayer/Components/Item";
7
- import type { CellRenderer } from "./types";
8
-
9
- export const selectableCell: CellRenderer = {
10
- renderItem: ({ item, index, onPress, context }) => (
11
- <AudioPlayerTrackItem
12
- key={index}
13
- configuration={VARIANT_PLAYLIST_ITEM}
14
- data={mapMenuItemToPlaylistData(item, context?.behavior)}
15
- onPress={() => onPress(item)}
16
- onTrailingButtonPress={
17
- item.secondaryAction?.action
18
- ? () =>
19
- onPress({
20
- title: item.title,
21
- action: item.secondaryAction!.action,
22
- })
23
- : undefined
24
- }
25
- />
26
- ),
27
- };
@@ -1,41 +0,0 @@
1
- import React from "react";
2
- import type { CellRenderer } from "./types";
3
-
4
- export const standardCell: CellRenderer = {
5
- renderItem: ({ item, index, onPress, context }) => {
6
- if (!item) {
7
- return null;
8
- }
9
-
10
- const {
11
- theme,
12
- ButtonComponent,
13
- current_selection,
14
- iconBaseProps,
15
- itemBaseProps,
16
- labelBaseProps,
17
- getSelectedItemIcon,
18
- getDefaultItemIcon,
19
- iconPlacement,
20
- } = context;
21
-
22
- return (
23
- <ButtonComponent
24
- key={index}
25
- configuration={theme}
26
- selectedItem={current_selection}
27
- item={item}
28
- onPress={onPress}
29
- label={
30
- theme[(item as any)?.label] ?? ((item as any)?.label || item?.title)
31
- }
32
- iconBaseProps={iconBaseProps}
33
- itemBaseProps={itemBaseProps}
34
- labelBaseProps={labelBaseProps}
35
- selectedItemIcon={getSelectedItemIcon(theme)}
36
- defaultItemIcon={getDefaultItemIcon(theme)}
37
- iconPlacement={iconPlacement}
38
- />
39
- );
40
- },
41
- };
@@ -1,35 +0,0 @@
1
- import type React from "react";
2
- import type {
3
- MenuItem,
4
- SelectionBehavior,
5
- } from "@applicaster/zapp-react-native-utils/modalState";
6
- import type { EditableCollection } from "@applicaster/zapp-react-native-utils/modalState/EditableCollection";
7
-
8
- /**
9
- * Extra props row renderers need.
10
- */
11
- export type SheetRenderContext = {
12
- theme: any;
13
- ButtonComponent: React.ComponentType<any>;
14
- current_selection?: any;
15
- iconBaseProps: any;
16
- itemBaseProps: any;
17
- labelBaseProps: any;
18
- getSelectedItemIcon: (theme: any) => any;
19
- getDefaultItemIcon: (theme: any) => any;
20
- iconPlacement?: "left" | "right";
21
- editableCollection?: EditableCollection | null;
22
- behavior?: SelectionBehavior;
23
- };
24
-
25
- export type RenderItemArgs = {
26
- item: MenuItem;
27
- index: number;
28
- onPress: (item: MenuItem) => void;
29
- context: SheetRenderContext;
30
- renderHandle?: (children: React.ReactElement) => React.ReactElement;
31
- };
32
-
33
- export type CellRenderer = {
34
- renderItem: (args: RenderItemArgs) => React.ReactNode;
35
- };
@@ -1,43 +0,0 @@
1
- import {
2
- actionExecutor,
3
- ActionResult,
4
- } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
5
- import { TextInputContent } from "./TextInputContent";
6
-
7
- /**
8
- * Opens a text-input bottom sheet driven by action.options.
9
- * Uses openBottomSheet with content.component as body-only UI;
10
- * standard ModalHeader (dismiss x) is provided by ModalComponent.
11
- */
12
- export async function showTextInputAction(
13
- action: ActionType,
14
- context?: Record<string, any>
15
- ): Promise<ActionResult> {
16
- const { headerTitle, inputLabel, defaultValue, buttonLabel, actions } =
17
- action.options || {};
18
-
19
- if (!headerTitle || !buttonLabel || !actions || !actions.length) {
20
- return ActionResult.Error;
21
- }
22
-
23
- return actionExecutor.handleAction(
24
- {
25
- type: "openBottomSheet",
26
- options: {
27
- header: {
28
- title: headerTitle,
29
- },
30
- content: {
31
- component: TextInputContent,
32
- props: {
33
- inputLabel,
34
- defaultValue: defaultValue ?? "",
35
- buttonLabel,
36
- actions,
37
- },
38
- },
39
- },
40
- },
41
- context
42
- );
43
- }
@@ -1,112 +0,0 @@
1
- import * as React from "react";
2
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
3
- import { useNetworkStatusLocalizations } from "./utils";
4
-
5
- type Props = {
6
- children?: React.ReactNode;
7
- hidden?: boolean;
8
- online?: boolean;
9
- previousOnline?: boolean;
10
- dismiss: () => void;
11
- };
12
-
13
- const NOTIF_DURATION = 4500;
14
-
15
- const styles: Record<any, React.CSSProperties> = {
16
- body: {
17
- position: "absolute",
18
- bottom: 0,
19
- width: 1920,
20
- height: 200,
21
- background:
22
- "linear-gradient(180deg, rgba(17,17,17,0.9) 0%, rgba(17,17,17,1) 49%, rgba(0,0,0,0.85) 100%)",
23
- display: "flex",
24
- alignItems: "center",
25
- justifyContent: "center",
26
- flexDirection: "column",
27
- color: "#fff",
28
- paddingBottom: 15,
29
- },
30
- title: {
31
- fontSize: 28,
32
- margin: 0,
33
- padding: 0,
34
- },
35
- subtitle: {
36
- fontSize: 20,
37
- margin: 0,
38
- padding: 0,
39
- },
40
- };
41
-
42
- export const NotificationView = (props: Props) => {
43
- const { children, hidden, dismiss, previousOnline, online } = props;
44
-
45
- const theme = useTheme<BaseThemePropertiesTV>();
46
- const storedLocalizations = useNetworkStatusLocalizations();
47
-
48
- const [open, setOpen] = React.useState<boolean | null>(null);
49
-
50
- const timeout = React.useRef<NodeJS.Timeout>();
51
- const wentOnline = !previousOnline && props.online;
52
-
53
- const onClose = () => {
54
- setOpen(false);
55
- dismiss();
56
- };
57
-
58
- const closeNotification = () => {
59
- clearTimeout(timeout.current as NodeJS.Timeout);
60
-
61
- timeout.current = setTimeout(() => {
62
- setOpen(false);
63
- }, NOTIF_DURATION);
64
- };
65
-
66
- const openNotification = () => {
67
- if (!open && !hidden) {
68
- setOpen(true);
69
- }
70
- };
71
-
72
- const handleNotificationChange = () => {
73
- if (open !== null) {
74
- openNotification();
75
- }
76
-
77
- closeNotification();
78
- };
79
-
80
- React.useEffect(() => {
81
- handleNotificationChange();
82
- }, [hidden, online]);
83
-
84
- const showOnlineMsg = wentOnline || online;
85
-
86
- const MSG = showOnlineMsg
87
- ? (theme?.online_notification_title ??
88
- storedLocalizations?.online_notification_title)
89
- : (theme?.offline_notification_title ??
90
- storedLocalizations?.offline_notification_title);
91
-
92
- const EXTRA_MSG = showOnlineMsg
93
- ? (theme?.online_notification_subtitle ??
94
- storedLocalizations?.online_notification_subtitle)
95
- : (theme?.offline_notification_subtitle ??
96
- storedLocalizations?.offline_notification_subtitle);
97
-
98
- return (
99
- <div onClick={onClose}>
100
- {children}
101
- {open ? (
102
- <div style={styles.body}>
103
- <div style={styles.title}>
104
- <h2>{MSG}</h2>
105
- </div>
106
-
107
- <h4 style={styles.subtitle}>{EXTRA_MSG}</h4>
108
- </div>
109
- ) : null}
110
- </div>
111
- );
112
- };
@@ -1,107 +0,0 @@
1
- import * as React from "react";
2
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
3
- import { useNetworkStatusLocalizations } from "./utils";
4
-
5
- type Props = {
6
- children?: React.ReactNode;
7
- online?: boolean;
8
- previousOnline?: boolean;
9
- dismiss: () => void;
10
- };
11
-
12
- const DURATION_TO_HIDE_AFTER_BACK_TO_ONLINE = 4500; // ms
13
-
14
- const styles: Record<any, React.CSSProperties> = {
15
- body: {
16
- position: "absolute",
17
- top: 0,
18
- width: 1920,
19
- height: 200,
20
- background:
21
- "linear-gradient(180deg, rgba(17,17,17,0.9) 0%, rgba(17,17,17,1) 49%, rgba(0,0,0,0.85) 100%)",
22
- display: "flex",
23
- alignItems: "center",
24
- justifyContent: "center",
25
- flexDirection: "column",
26
- color: "#fff",
27
- paddingTop: 15,
28
- },
29
- title: {
30
- fontSize: 28,
31
- margin: 0,
32
- padding: 0,
33
- },
34
- subtitle: {
35
- fontSize: 20,
36
- margin: 0,
37
- padding: 0,
38
- },
39
- };
40
-
41
- let timer: NodeJS.Timeout;
42
-
43
- export const NotificationView = (props: Props) => {
44
- const { children, dismiss, previousOnline = true, online } = props;
45
-
46
- const theme = useTheme<BaseThemePropertiesTV>();
47
- const storedLocalizations = useNetworkStatusLocalizations();
48
-
49
- const [shown, setShown] = React.useState<boolean>(false);
50
-
51
- const onClose = () => {
52
- setShown(false);
53
- dismiss();
54
- };
55
-
56
- React.useEffect(() => {
57
- if (previousOnline && online) {
58
- return;
59
- }
60
-
61
- if (!previousOnline && !online) {
62
- return;
63
- }
64
-
65
- if (previousOnline && !online) {
66
- // went to offline
67
- clearTimeout(timer);
68
- setShown(true);
69
- }
70
-
71
- if (!previousOnline && online) {
72
- // back to online
73
- setShown(true);
74
-
75
- timer = setTimeout(() => {
76
- setShown(false);
77
- }, DURATION_TO_HIDE_AFTER_BACK_TO_ONLINE);
78
- }
79
- }, [online, previousOnline]);
80
-
81
- const title = online
82
- ? (theme?.online_notification_title ??
83
- storedLocalizations?.online_notification_title)
84
- : (theme?.offline_notification_title ??
85
- storedLocalizations?.offline_notification_title);
86
-
87
- const subtitle = online
88
- ? (theme?.online_notification_subtitle ??
89
- storedLocalizations?.online_notification_subtitle)
90
- : (theme?.offline_notification_subtitle ??
91
- storedLocalizations?.offline_notification_subtitle);
92
-
93
- return (
94
- <div onClick={onClose}>
95
- {children}
96
- {shown ? (
97
- <div style={styles.body}>
98
- <div style={styles.title}>
99
- <h2>{title}</h2>
100
- </div>
101
-
102
- <h4 style={styles.subtitle}>{subtitle}</h4>
103
- </div>
104
- ) : null}
105
- </div>
106
- );
107
- };