@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1263110541 → 16.0.0-alpha.1334507601
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/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +15 -0
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -2
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +4 -2
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +5 -2
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +4 -1
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +70 -73
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +1 -36
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +1 -26
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +180 -47
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +13 -20
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +113 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +0 -2
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +248 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +194 -19
- package/Components/ModalComponent/BottomSheetModalContent.tsx +94 -66
- package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
- package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
- package/Components/ModalComponent/SortableList.tsx +38 -23
- package/Components/ModalComponent/SortableList.web.tsx +1 -1
- package/Components/ModalComponent/TextInputContent.tsx +15 -3
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +37 -5
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +36 -0
- package/Components/ModalComponent/index.tsx +7 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +24 -0
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +1 -1
- package/Components/ModalComponent/presets/selectableCell.tsx +1 -1
- package/Components/ModalComponent/utils.ts +6 -1
- package/Components/OfflineHandler/hooks.ts +0 -12
- package/Components/River/ComponentsMap/ComponentsMap.tsx +85 -59
- package/Components/River/River.tsx +4 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +138 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/River/__tests__/resultCallback.test.tsx +47 -0
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +5 -0
- package/Components/Screen/navigationHandler.ts +8 -8
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/package.json +5 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -314
|
@@ -11,6 +11,20 @@ type HookComponentProps = HookPluginProps & {
|
|
|
11
11
|
function GeneralContentScreenHookComponent(props: HookComponentProps) {
|
|
12
12
|
const { hookPlugin, focused, parentFocus } = props;
|
|
13
13
|
|
|
14
|
+
const componentsMapExtraPropsMemo = React.useMemo(
|
|
15
|
+
() => ({
|
|
16
|
+
safeArea: {
|
|
17
|
+
edges: {
|
|
18
|
+
top: "additive",
|
|
19
|
+
bottom: "additive",
|
|
20
|
+
left: "off",
|
|
21
|
+
right: "off",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
[]
|
|
26
|
+
);
|
|
27
|
+
|
|
14
28
|
if (!hookPlugin?.screen_id) {
|
|
15
29
|
log_error(
|
|
16
30
|
"GeneralContentScreenHookAdapter: This component should only be used as a hook, but no hookPlugin was provided."
|
|
@@ -26,6 +40,7 @@ function GeneralContentScreenHookComponent(props: HookComponentProps) {
|
|
|
26
40
|
isScreenWrappedInContainer={false}
|
|
27
41
|
focused={focused}
|
|
28
42
|
parentFocus={parentFocus}
|
|
43
|
+
componentsMapExtraProps={componentsMapExtraPropsMemo}
|
|
29
44
|
/>
|
|
30
45
|
</HookContentFocusGroup>
|
|
31
46
|
);
|
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect } from "react";
|
|
|
2
2
|
import { TouchableOpacity, ViewStyle } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
+
import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
5
6
|
|
|
6
7
|
import Image from "./Image";
|
|
7
8
|
type Props = {
|
|
@@ -57,6 +58,7 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
57
58
|
) {
|
|
58
59
|
const { item, action, asset, flavour = "flavour_1", cellUUID } = props;
|
|
59
60
|
const actionContext = useActions(action?.identifier);
|
|
61
|
+
const component = useUIComponentContext();
|
|
60
62
|
|
|
61
63
|
// TODO: add subscription API for action availability
|
|
62
64
|
const actionDisabled =
|
|
@@ -81,8 +83,9 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
81
83
|
updateState: (state) => {
|
|
82
84
|
setActionState(state);
|
|
83
85
|
},
|
|
86
|
+
...(component ? { component } : {}),
|
|
84
87
|
});
|
|
85
|
-
}, [actionState, actionContext?.state, item
|
|
88
|
+
}, [actionState, actionContext?.state, item, component]);
|
|
86
89
|
|
|
87
90
|
useEffect(() => {
|
|
88
91
|
if (typeof actionContext?.addListener === "function") {
|
|
@@ -105,6 +108,7 @@ export const ActionButton = React.memo(function ActionButtonComponent(
|
|
|
105
108
|
testID={props?.testID || `${item?.id}`}
|
|
106
109
|
accessibilityLabel={props?.accessibilityLabel || `${item?.id}`}
|
|
107
110
|
accessibilityHint={props?.accessibilityHint}
|
|
111
|
+
accessibilityRole="button"
|
|
108
112
|
accessible={!!(props?.testID || props?.accessibilityLabel)}
|
|
109
113
|
style={props?.style}
|
|
110
114
|
>
|
|
@@ -6,6 +6,7 @@ import React, {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
9
|
+
import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
9
10
|
|
|
10
11
|
type ActionDefinition = {
|
|
11
12
|
identifier?: string;
|
|
@@ -61,6 +62,7 @@ export function ActionButtonController({
|
|
|
61
62
|
const resolvedEntry = entry;
|
|
62
63
|
const resolvedIdentifier = action?.identifier || pluginIdentifier || "";
|
|
63
64
|
const actionContext = useActions(resolvedIdentifier);
|
|
65
|
+
const component = useUIComponentContext();
|
|
64
66
|
|
|
65
67
|
const actionDisabled =
|
|
66
68
|
typeof actionContext?.isActionAvailable === "function" &&
|
|
@@ -128,20 +130,33 @@ export function ActionButtonController({
|
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
const invokeOptions: InvokeArgsOptions = {
|
|
134
|
+
...(component ? { component } : {}),
|
|
135
|
+
};
|
|
136
|
+
|
|
131
137
|
if (supportsEntryState) {
|
|
132
138
|
return actionContext.invokeAction?.(resolvedEntry, {
|
|
139
|
+
...invokeOptions,
|
|
133
140
|
updateState: setActionState,
|
|
134
141
|
});
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
if (typeof actionContext.invokeAction === "function") {
|
|
145
|
+
return actionContext.invokeAction(resolvedEntry, invokeOptions);
|
|
146
|
+
}
|
|
147
|
+
|
|
137
148
|
const favouritesAction = legacySelected
|
|
138
149
|
? actionContext.removeFavourite
|
|
139
150
|
: actionContext.addFavourite;
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
return favouritesAction?.(resolvedEntry);
|
|
153
|
+
}, [
|
|
154
|
+
actionContext,
|
|
155
|
+
supportsEntryState,
|
|
156
|
+
resolvedEntry,
|
|
157
|
+
legacySelected,
|
|
158
|
+
component,
|
|
159
|
+
]);
|
|
145
160
|
|
|
146
161
|
if (!actionContext || actionDisabled) {
|
|
147
162
|
if (!actionContext) {
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { Text } from "react-native";
|
|
3
3
|
import { render, fireEvent } from "@testing-library/react-native";
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
+
import { UIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
|
|
5
6
|
|
|
6
7
|
import { ActionButtonController } from "../ActionButtonController";
|
|
7
8
|
|
|
@@ -89,6 +90,47 @@ describe("ActionButtonController", () => {
|
|
|
89
90
|
);
|
|
90
91
|
});
|
|
91
92
|
|
|
93
|
+
it("passes the List UIComponentContext into invokeAction so the sheet can refresh it", () => {
|
|
94
|
+
const listComponent = {
|
|
95
|
+
id: "list-1",
|
|
96
|
+
data: { source: "https://example.com/user/collections/abc" },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const actionContext = {
|
|
100
|
+
isActionAvailable: jest.fn(() => true),
|
|
101
|
+
initialEntryState: jest.fn(() => ({ active: false })),
|
|
102
|
+
invokeAction: jest.fn(),
|
|
103
|
+
addListener: jest.fn(() => jest.fn()),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
mockUseActions.mockReturnValue(actionContext);
|
|
107
|
+
|
|
108
|
+
const { getByTestId } = render(
|
|
109
|
+
<UIComponentContext.Provider value={listComponent}>
|
|
110
|
+
<ActionButtonController
|
|
111
|
+
action={{ identifier: "open-modal-bottom-sheet-cell-action" }}
|
|
112
|
+
entry={entry}
|
|
113
|
+
>
|
|
114
|
+
{({ onPress }) => (
|
|
115
|
+
<Text testID="content" onPress={onPress}>
|
|
116
|
+
press
|
|
117
|
+
</Text>
|
|
118
|
+
)}
|
|
119
|
+
</ActionButtonController>
|
|
120
|
+
</UIComponentContext.Provider>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
fireEvent.press(getByTestId("content"));
|
|
124
|
+
|
|
125
|
+
expect(actionContext.invokeAction).toHaveBeenCalledWith(
|
|
126
|
+
entry,
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
updateState: expect.any(Function),
|
|
129
|
+
component: listComponent,
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
92
134
|
it("updates state from addListener", () => {
|
|
93
135
|
let listener;
|
|
94
136
|
|
|
@@ -328,7 +370,7 @@ describe("ActionButtonController", () => {
|
|
|
328
370
|
|
|
329
371
|
fireEvent.press(getByTestId("content"));
|
|
330
372
|
|
|
331
|
-
expect(invokeAction).toHaveBeenCalledWith(entry);
|
|
373
|
+
expect(invokeAction).toHaveBeenCalledWith(entry, {});
|
|
332
374
|
expect(addFavourite).not.toHaveBeenCalled();
|
|
333
375
|
expect(removeFavourite).not.toHaveBeenCalled();
|
|
334
376
|
});
|
|
@@ -91,6 +91,7 @@ function ButtonComponent(props: Props) {
|
|
|
91
91
|
onPress={onPress}
|
|
92
92
|
testID={props?.testID || `${item?.id}`}
|
|
93
93
|
accessibilityLabel={props?.accessibilityLabel || `${item?.id}`}
|
|
94
|
+
accessibilityRole="button"
|
|
94
95
|
accessible={!!(props?.testID || props?.accessibilityLabel)}
|
|
95
96
|
style={props?.style}
|
|
96
97
|
>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CollapsibleTextContainer } from "../index";
|
|
2
|
+
|
|
3
|
+
const getValue = (key: string) => {
|
|
4
|
+
const values: Record<string, unknown> = {
|
|
5
|
+
text_alignment: "left",
|
|
6
|
+
margin_top: 0,
|
|
7
|
+
margin_right: 0,
|
|
8
|
+
margin_bottom: 0,
|
|
9
|
+
margin_left: 0,
|
|
10
|
+
padding_top: 0,
|
|
11
|
+
padding_right: 0,
|
|
12
|
+
padding_bottom: 0,
|
|
13
|
+
padding_left: 0,
|
|
14
|
+
corner_radius: 0,
|
|
15
|
+
data_key: "title",
|
|
16
|
+
custom_data_key: undefined,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return values[key];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("CollapsibleTextContainer view tree", () => {
|
|
23
|
+
it("stretches to the parent width so labels wrap using the cell width", () => {
|
|
24
|
+
const result = CollapsibleTextContainer({
|
|
25
|
+
getValue,
|
|
26
|
+
elements: [],
|
|
27
|
+
additionalStyles: { backgroundColor: "transparent" },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(result.style.alignSelf).toBe("stretch");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -3,7 +3,6 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
|
|
|
3
3
|
import {
|
|
4
4
|
getColorFromData,
|
|
5
5
|
getKeyForLabel,
|
|
6
|
-
mapSelfAlignment,
|
|
7
6
|
} from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
8
7
|
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
9
8
|
|
|
@@ -23,7 +22,7 @@ export const CollapsibleTextContainer = ({
|
|
|
23
22
|
return {
|
|
24
23
|
type: "CollapsibleTextContainer", // Collapsible Text Container,
|
|
25
24
|
style: {
|
|
26
|
-
alignSelf:
|
|
25
|
+
alignSelf: "stretch",
|
|
27
26
|
marginTop: toNumberWithDefaultZero(getValue("margin_top")),
|
|
28
27
|
marginRight: toNumberWithDefaultZero(getValue("margin_right")),
|
|
29
28
|
marginBottom: toNumberWithDefaultZero(getValue("margin_bottom")),
|
|
@@ -12,6 +12,7 @@ export type ActionButtonProps = {
|
|
|
12
12
|
item: RegisteredAction;
|
|
13
13
|
configuration: any;
|
|
14
14
|
width: number;
|
|
15
|
+
component?: ZappUIComponent & { parent?: ZappUIComponent };
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
type Props = ActionButtonProps & {
|
|
@@ -27,7 +28,7 @@ type Props = ActionButtonProps & {
|
|
|
27
28
|
* (backed by the action's observable / `addListener`).
|
|
28
29
|
*/
|
|
29
30
|
export function ActionButton(props: Props) {
|
|
30
|
-
const { item, entry, onPress: onItemPress } = props;
|
|
31
|
+
const { item, entry, onPress: onItemPress, component } = props;
|
|
31
32
|
|
|
32
33
|
const action = item?.action;
|
|
33
34
|
|
|
@@ -38,9 +39,10 @@ export function ActionButton(props: Props) {
|
|
|
38
39
|
invokeAction({
|
|
39
40
|
context: "toast",
|
|
40
41
|
dismiss: () => onItemPress?.(item),
|
|
42
|
+
...(component ? { component } : {}),
|
|
41
43
|
});
|
|
42
44
|
},
|
|
43
|
-
[invokeAction, item, onItemPress]
|
|
45
|
+
[invokeAction, item, onItemPress, component]
|
|
44
46
|
);
|
|
45
47
|
|
|
46
48
|
if (!isActionAvailableFor(action, entry as ZappEntry)) return null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
2
|
+
import { boundActionButton } from "../boundActionButton";
|
|
3
|
+
import { openActionsBottomSheet } from "../openActionsBottomSheet";
|
|
4
|
+
|
|
5
|
+
jest.mock("@applicaster/zapp-react-native-utils/modalState", () => ({
|
|
6
|
+
openBottomSheetModal: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("../boundActionButton", () => ({
|
|
10
|
+
boundActionButton: jest.fn(() => jest.fn()),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("openActionsBottomSheet", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("binds the List component into the sheet row button", () => {
|
|
19
|
+
const entry = { id: "entry-1" } as ZappEntry;
|
|
20
|
+
|
|
21
|
+
const component = {
|
|
22
|
+
data: { source: "https://example.com/user/collections/abc" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const actions = [
|
|
26
|
+
{ identifier: "entry_action_0", action: { invokeAction: jest.fn() } },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
openActionsBottomSheet({
|
|
30
|
+
entry,
|
|
31
|
+
actions: actions as any,
|
|
32
|
+
component: component as any,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(boundActionButton).toHaveBeenCalledWith(entry, component);
|
|
36
|
+
expect(openBottomSheetModal).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -8,9 +8,12 @@ import { ActionButton, ActionButtonProps } from "./ActionButton";
|
|
|
8
8
|
* `BottomSheetModalContent` renders `buttonComponent` per item and does not
|
|
9
9
|
* know about entries, so the entry is closed over here instead.
|
|
10
10
|
*/
|
|
11
|
-
export const boundActionButton = (
|
|
11
|
+
export const boundActionButton = (
|
|
12
|
+
entry: ZappEntry | ZappFeed,
|
|
13
|
+
component?: ZappUIComponent & { parent?: ZappUIComponent }
|
|
14
|
+
) => {
|
|
12
15
|
const ActionButtonWithEntry = (props: ActionButtonProps) => (
|
|
13
|
-
<ActionButton {...props} entry={entry as ZappEntry} />
|
|
16
|
+
<ActionButton {...props} entry={entry as ZappEntry} component={component} />
|
|
14
17
|
);
|
|
15
18
|
|
|
16
19
|
ActionButtonWithEntry.displayName = "ActionButtonWithEntry";
|
|
@@ -17,6 +17,8 @@ export type OpenActionsBottomSheetArgs = {
|
|
|
17
17
|
actions: RegisteredAction[];
|
|
18
18
|
title?: string;
|
|
19
19
|
summary?: string;
|
|
20
|
+
/** List that opened the sheet; forwarded into row invokeAction context. */
|
|
21
|
+
component?: ZappUIComponent & { parent?: ZappUIComponent };
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -32,6 +34,7 @@ export function openActionsBottomSheet({
|
|
|
32
34
|
actions,
|
|
33
35
|
title,
|
|
34
36
|
summary,
|
|
37
|
+
component,
|
|
35
38
|
}: OpenActionsBottomSheetArgs): void {
|
|
36
39
|
if (!actions?.length) {
|
|
37
40
|
// Every action resolved away - unavailable for this entry, or not
|
|
@@ -50,7 +53,7 @@ export function openActionsBottomSheet({
|
|
|
50
53
|
// BottomSheetModalContent still requires an onPress.
|
|
51
54
|
onPress: noop,
|
|
52
55
|
items: actions,
|
|
53
|
-
buttonComponent: boundActionButton(entry),
|
|
56
|
+
buttonComponent: boundActionButton(entry, component),
|
|
54
57
|
title,
|
|
55
58
|
summary,
|
|
56
59
|
},
|
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
Text,
|
|
18
18
|
View,
|
|
19
19
|
} from "react-native";
|
|
20
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
21
|
+
|
|
22
|
+
const logger = createLogger({ category: "ModalComponent:AudioPlayerButton" });
|
|
20
23
|
|
|
21
24
|
const styles = StyleSheet.create({
|
|
22
25
|
container: {
|
|
@@ -81,6 +84,8 @@ export type LeadingIconConfiguration = {
|
|
|
81
84
|
enabled: boolean;
|
|
82
85
|
/** Decides which icon will be displayed when enabled */
|
|
83
86
|
actionType?: ButtonActionType;
|
|
87
|
+
/** Optional Studio / modal-blocks asset URI (overrides actionType mapping) */
|
|
88
|
+
asset?: string;
|
|
84
89
|
};
|
|
85
90
|
|
|
86
91
|
export type IconsConfiguration = {
|
|
@@ -128,6 +133,28 @@ export const DEFAULT_ICONS_CONFIGURATION: IconsConfiguration = {
|
|
|
128
133
|
},
|
|
129
134
|
};
|
|
130
135
|
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Variant configurations
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
export const VARIANT_TEXT_ONLY: ButtonConfiguration = {
|
|
141
|
+
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
142
|
+
title: DEFAULT_TITLE_CONFIGURATION,
|
|
143
|
+
icons: DEFAULT_ICONS_CONFIGURATION,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const VARIANT_WITH_ICON: ButtonConfiguration = {
|
|
147
|
+
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
148
|
+
title: DEFAULT_TITLE_CONFIGURATION,
|
|
149
|
+
icons: {
|
|
150
|
+
...DEFAULT_ICONS_CONFIGURATION,
|
|
151
|
+
leadingIcon: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
actionType: "addToPlaylist",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
131
158
|
// ---------------------------------------------------------------------------
|
|
132
159
|
// Data (runtime / localization — not styling config)
|
|
133
160
|
// ---------------------------------------------------------------------------
|
|
@@ -138,30 +165,15 @@ export type ButtonData = {
|
|
|
138
165
|
};
|
|
139
166
|
|
|
140
167
|
export type AudioPlayerButtonProps = {
|
|
141
|
-
configuration
|
|
142
|
-
data
|
|
168
|
+
configuration?: ButtonConfiguration;
|
|
169
|
+
data?: ButtonData;
|
|
143
170
|
disabled?: boolean;
|
|
144
171
|
focused?: boolean;
|
|
145
172
|
onPress?: () => void;
|
|
146
173
|
};
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
|
|
152
|
-
// Hardcoded: action type → icon asset mapping (base64 PNG)
|
|
153
|
-
const ACTION_ICON_ASSETS: Record<ButtonActionType, string> = {
|
|
154
|
-
addToQueue:
|
|
155
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVR42mNgGAVDCfxHw6MWjFpAvEGk4lELKLdgNJmOWkA7C0YBbQAAwiZvkR9eFgQAAAAASUVORK5CYII=",
|
|
156
|
-
addToPlaylist:
|
|
157
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJklEQVR42mNgGAUjFvzHgmluATF4cFswGsmjkTwayaORPAqGLwAA6Jpxj9Ms9QMAAAAASUVORK5CYII=",
|
|
158
|
-
goToPlaylist:
|
|
159
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAALklEQVR42mNgGAWjYNiD/7Q2/D8tLflPiSX/ycQ0t+D/yLGAEkeMWjAKRgG9AQB4zlSshwSzWwAAAABJRU5ErkJggg==",
|
|
160
|
-
confirm:
|
|
161
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJElEQVR42mNgGAWjYBSQCv4TwKMWDHELho/hNE/3o2AUDDcAAPv0QMAyy1CtAAAAAElFTkSuQmCC",
|
|
162
|
-
cancel:
|
|
163
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAASElEQVR42mNgGAWjGvynkVoUTf+pqA6nxv8UqiHbEooNx2cQ1QzHZiDVDcdlCcOQsoCmQUTTSKZpMqV5RqNLUUHTwm4UDBMAALBLQ73LTvoyAAAAAElFTkSuQmCC",
|
|
164
|
-
};
|
|
175
|
+
const PLACEHOLDER_RED_BOX =
|
|
176
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJ0lEQVR4nO3NMQEAAAjDsIF/z2ACvlRAU5NMHuvPOQAAAAAAAMBdC/07Ai5sNw+CAAAAAElFTkSuQmCC";
|
|
165
177
|
|
|
166
178
|
function getTextTransformStyle(
|
|
167
179
|
textTransform: TextTransform
|
|
@@ -170,7 +182,9 @@ function getTextTransformStyle(
|
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
function shouldShowLeadingIcon(leadingIcon: LeadingIconConfiguration) {
|
|
173
|
-
return
|
|
185
|
+
return (
|
|
186
|
+
leadingIcon.enabled && (!!leadingIcon.actionType || !!leadingIcon.asset)
|
|
187
|
+
);
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
// ---------------------------------------------------------------------------
|
|
@@ -268,17 +282,31 @@ function ButtonTitle({
|
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
type ButtonLeadingIconProps = {
|
|
271
|
-
actionType
|
|
285
|
+
actionType?: ButtonActionType;
|
|
272
286
|
configuration: IconsConfiguration;
|
|
273
287
|
};
|
|
274
288
|
|
|
275
|
-
function ButtonLeadingIcon({
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
289
|
+
function ButtonLeadingIcon({ configuration }: ButtonLeadingIconProps) {
|
|
290
|
+
const asset = configuration.leadingIcon.asset || PLACEHOLDER_RED_BOX;
|
|
291
|
+
|
|
292
|
+
React.useEffect(() => {
|
|
293
|
+
if (!configuration.leadingIcon.asset) {
|
|
294
|
+
logger.log_warning(
|
|
295
|
+
"AudioPlayerButton has leadingIcon enabled but no asset was provided. Falling back to placeholder red box. Ensure the relevant plugin (e.g. playlist-experience) provides leadingIcon.asset in styleOverrides.",
|
|
296
|
+
{
|
|
297
|
+
leadingIcon: configuration.leadingIcon,
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}, [configuration.leadingIcon.asset]);
|
|
302
|
+
|
|
303
|
+
if (!asset) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
279
307
|
return (
|
|
280
308
|
<Image
|
|
281
|
-
source={{ uri:
|
|
309
|
+
source={{ uri: asset }}
|
|
282
310
|
style={{
|
|
283
311
|
width: configuration.width,
|
|
284
312
|
height: configuration.height,
|
|
@@ -296,13 +324,14 @@ import { useModalBlocksStyle } from "../../ModalBlocksStyleContext";
|
|
|
296
324
|
import { mergeButtonConfiguration } from "../utils/mergeStyles";
|
|
297
325
|
|
|
298
326
|
export function AudioPlayerButton({
|
|
299
|
-
configuration,
|
|
300
|
-
data,
|
|
327
|
+
configuration = VARIANT_WITH_ICON,
|
|
328
|
+
data = { title: undefined },
|
|
301
329
|
disabled,
|
|
302
330
|
focused = false,
|
|
303
331
|
onPress,
|
|
304
332
|
}: AudioPlayerButtonProps) {
|
|
305
333
|
const modalBlocksStyle = useModalBlocksStyle();
|
|
334
|
+
|
|
306
335
|
const buttonBlock = modalBlocksStyle?.button;
|
|
307
336
|
const actionBlock = modalBlocksStyle?.action;
|
|
308
337
|
|
|
@@ -316,9 +345,21 @@ export function AudioPlayerButton({
|
|
|
316
345
|
icons: iconsConfiguration,
|
|
317
346
|
} = effectiveConfig;
|
|
318
347
|
|
|
319
|
-
const
|
|
348
|
+
const title = data?.title || buttonBlock?.title.default;
|
|
320
349
|
const isDisabled = disabled ?? data.disabled ?? false;
|
|
321
350
|
|
|
351
|
+
React.useEffect(() => {
|
|
352
|
+
if (!title) {
|
|
353
|
+
logger.log_warning(
|
|
354
|
+
"Create New Playlist button rendering with empty title. Ensure data.title is set for AudioPlayerButton or styleOverrides providing button title.",
|
|
355
|
+
{
|
|
356
|
+
leadingIcon: iconsConfiguration.leadingIcon,
|
|
357
|
+
modalBlocksStyle: modalBlocksStyle,
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}, [title]);
|
|
362
|
+
|
|
322
363
|
const { leadingIcon } = iconsConfiguration;
|
|
323
364
|
const showIcon = shouldShowLeadingIcon(leadingIcon);
|
|
324
365
|
|
|
@@ -349,47 +390,3 @@ export function AudioPlayerButton({
|
|
|
349
390
|
</ButtonContainer>
|
|
350
391
|
);
|
|
351
392
|
}
|
|
352
|
-
|
|
353
|
-
// ---------------------------------------------------------------------------
|
|
354
|
-
// Snack demo — two Button variants from configuration only
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
|
-
|
|
357
|
-
export const VARIANT_TEXT_ONLY: ButtonConfiguration = {
|
|
358
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
359
|
-
title: DEFAULT_TITLE_CONFIGURATION,
|
|
360
|
-
icons: DEFAULT_ICONS_CONFIGURATION,
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
export const VARIANT_WITH_ICON: ButtonConfiguration = {
|
|
364
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
365
|
-
title: DEFAULT_TITLE_CONFIGURATION,
|
|
366
|
-
icons: {
|
|
367
|
-
...DEFAULT_ICONS_CONFIGURATION,
|
|
368
|
-
leadingIcon: {
|
|
369
|
-
enabled: true,
|
|
370
|
-
actionType: "addToPlaylist",
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
export const VARIANT_UPPERCASE_TITLE: ButtonConfiguration = {
|
|
376
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
377
|
-
title: {
|
|
378
|
-
...DEFAULT_TITLE_CONFIGURATION,
|
|
379
|
-
textTransform: "uppercase",
|
|
380
|
-
},
|
|
381
|
-
icons: DEFAULT_ICONS_CONFIGURATION,
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
export const VARIANT_LARGE_ICON: ButtonConfiguration = {
|
|
385
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
386
|
-
title: DEFAULT_TITLE_CONFIGURATION,
|
|
387
|
-
icons: {
|
|
388
|
-
width: 32,
|
|
389
|
-
height: 32,
|
|
390
|
-
leadingIcon: {
|
|
391
|
-
enabled: true,
|
|
392
|
-
actionType: "addToQueue",
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
};
|
|
@@ -63,11 +63,7 @@ export type TextTransform =
|
|
|
63
63
|
export type TitleAlignment = "left" | "center";
|
|
64
64
|
|
|
65
65
|
/** Selects hardcoded container + action layout (white fields). */
|
|
66
|
-
export type HeaderVariant =
|
|
67
|
-
| "singleLine"
|
|
68
|
-
| "headerWithActions"
|
|
69
|
-
| "headerWithActionsCancelOnly"
|
|
70
|
-
| "section";
|
|
66
|
+
export type HeaderVariant = "singleLine" | "headerWithActions" | "section";
|
|
71
67
|
|
|
72
68
|
export type TitleConfiguration = {
|
|
73
69
|
fontColor: string;
|
|
@@ -198,13 +194,6 @@ const CONTAINER_SPEC: Record<HeaderVariant, ContainerSpec> = {
|
|
|
198
194
|
paddingBottom: 8,
|
|
199
195
|
paddingLeft: 20,
|
|
200
196
|
},
|
|
201
|
-
headerWithActionsCancelOnly: {
|
|
202
|
-
horizontalGutter: 12,
|
|
203
|
-
paddingTop: 0,
|
|
204
|
-
paddingRight: 20,
|
|
205
|
-
paddingBottom: 8,
|
|
206
|
-
paddingLeft: 20,
|
|
207
|
-
},
|
|
208
197
|
section: {
|
|
209
198
|
horizontalGutter: 12,
|
|
210
199
|
paddingTop: 0,
|
|
@@ -228,11 +217,6 @@ const ACTION_BUTTONS_BEHAVIOR: Record<
|
|
|
228
217
|
leading: { enabled: true, actionType: "cancel" },
|
|
229
218
|
trailing: { enabled: true, actionType: "done" },
|
|
230
219
|
},
|
|
231
|
-
headerWithActionsCancelOnly: {
|
|
232
|
-
sectionEnabled: true,
|
|
233
|
-
leading: { enabled: true, actionType: "cancel" },
|
|
234
|
-
trailing: { enabled: false },
|
|
235
|
-
},
|
|
236
220
|
section: {
|
|
237
221
|
sectionEnabled: true,
|
|
238
222
|
leading: { enabled: false },
|
|
@@ -725,25 +709,6 @@ export const VARIANT_HEADER_WITH_ACTIONS: HeaderConfiguration = {
|
|
|
725
709
|
paddingTop: 16,
|
|
726
710
|
};
|
|
727
711
|
|
|
728
|
-
/** Variant 2 — single side action (layout rule demo) */
|
|
729
|
-
export const VARIANT_HEADER_WITH_CANCEL_ONLY: HeaderConfiguration = {
|
|
730
|
-
variant: "headerWithActionsCancelOnly",
|
|
731
|
-
title: {
|
|
732
|
-
fontColor: "#FFFFFF",
|
|
733
|
-
iosFontFamily: "SFProText-Bold",
|
|
734
|
-
androidFontFamily: "Roboto-Bold",
|
|
735
|
-
fontSize: 22,
|
|
736
|
-
lineHeight: 28,
|
|
737
|
-
iosLetterSpacing: -0.6,
|
|
738
|
-
androidLetterSpacing: 0,
|
|
739
|
-
textTransform: "default",
|
|
740
|
-
alignment: "center",
|
|
741
|
-
},
|
|
742
|
-
closeButton: { enabled: false },
|
|
743
|
-
actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
|
|
744
|
-
paddingTop: 16,
|
|
745
|
-
};
|
|
746
|
-
|
|
747
712
|
/** Variant 3 — Section Header */
|
|
748
713
|
export const VARIANT_SECTION_HEADER: HeaderConfiguration = {
|
|
749
714
|
variant: "section",
|
|
@@ -565,7 +565,7 @@ export function AudioPlayerInput({
|
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
// ---------------------------------------------------------------------------
|
|
568
|
-
// Variant
|
|
568
|
+
// Variant configurations
|
|
569
569
|
// ---------------------------------------------------------------------------
|
|
570
570
|
|
|
571
571
|
/** Search pill — leading icon + clear button */
|
|
@@ -626,28 +626,3 @@ export const VARIANT_NAME_PLAYLIST_INPUT: InputConfiguration = {
|
|
|
626
626
|
},
|
|
627
627
|
},
|
|
628
628
|
};
|
|
629
|
-
|
|
630
|
-
/** Text only search pill — no leading icon */
|
|
631
|
-
export const VARIANT_TEXT_ONLY_INPUT: InputConfiguration = {
|
|
632
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
633
|
-
label: DEFAULT_LABEL_CONFIGURATION,
|
|
634
|
-
text: DEFAULT_TEXT_CONFIGURATION,
|
|
635
|
-
icons: {
|
|
636
|
-
...DEFAULT_ICONS_CONFIGURATION,
|
|
637
|
-
leadingIcon: { enabled: false },
|
|
638
|
-
},
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
/** Search without clear button */
|
|
642
|
-
export const VARIANT_SEARCH_WITHOUT_CLEAR: InputConfiguration = {
|
|
643
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
644
|
-
label: DEFAULT_LABEL_CONFIGURATION,
|
|
645
|
-
text: DEFAULT_TEXT_CONFIGURATION,
|
|
646
|
-
icons: {
|
|
647
|
-
...DEFAULT_ICONS_CONFIGURATION,
|
|
648
|
-
trailingIcon: {
|
|
649
|
-
...DEFAULT_ICONS_CONFIGURATION.trailingIcon,
|
|
650
|
-
enabled: false,
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
};
|