@applicaster/zapp-react-native-ui-components 15.1.12 → 15.1.13-alpha.1034774223
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/ModalComponent/ActionsBottomSheet/ActionButton.tsx +54 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +21 -0
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +58 -0
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -87
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +57 -74
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +142 -107
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +410 -159
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +8 -4
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +230 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +332 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +379 -61
- package/Components/ModalComponent/Button/index.tsx +5 -3
- package/Components/ModalComponent/Header/index.tsx +20 -7
- package/Components/ModalComponent/Header/utils.ts +1 -1
- package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
- package/Components/ModalComponent/SortableList.tsx +142 -0
- package/Components/ModalComponent/SortableList.web.tsx +102 -0
- package/Components/ModalComponent/TextInputContent.tsx +100 -0
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +120 -0
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
- package/Components/ModalComponent/index.tsx +11 -0
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -0
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
- package/Components/ModalComponent/presets/index.ts +27 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
- package/Components/ModalComponent/presets/standardCell.tsx +41 -0
- package/Components/ModalComponent/presets/types.ts +35 -0
- package/Components/ModalComponent/showTextInput.ts +43 -0
- package/Components/ModalComponent/utils.ts +82 -21
- package/Hooks/useEntryActionsExpander.ts +63 -0
- package/package.json +6 -5
- /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useEntryActionState } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
3
|
+
import {
|
|
4
|
+
isActionAvailableFor,
|
|
5
|
+
RegisteredAction,
|
|
6
|
+
} from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
|
|
7
|
+
|
|
8
|
+
import { Button } from "../Button";
|
|
9
|
+
|
|
10
|
+
export type ActionButtonProps = {
|
|
11
|
+
entry: ZappEntry;
|
|
12
|
+
item: RegisteredAction;
|
|
13
|
+
configuration: any;
|
|
14
|
+
width: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type Props = ActionButtonProps & {
|
|
18
|
+
onPress?: (item: any) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Unified action button for the modal bottom sheet.
|
|
23
|
+
*
|
|
24
|
+
* Every item is a `RegisteredAction` resolved from the registry (entry actions,
|
|
25
|
+
* registry actions and legacy context-provider actions alike), so the button
|
|
26
|
+
* simply reads `item.action` and drives it through `useEntryActionState`
|
|
27
|
+
* (backed by the action's observable / `addListener`).
|
|
28
|
+
*/
|
|
29
|
+
export function ActionButton(props: Props) {
|
|
30
|
+
const { item, entry, onPress: onItemPress } = props;
|
|
31
|
+
|
|
32
|
+
const action = item?.action;
|
|
33
|
+
|
|
34
|
+
const { state, invokeAction } = useEntryActionState(action, entry);
|
|
35
|
+
|
|
36
|
+
const onPress = React.useCallback(
|
|
37
|
+
(_item) => {
|
|
38
|
+
invokeAction({
|
|
39
|
+
context: "toast",
|
|
40
|
+
dismiss: () => onItemPress?.(item),
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
[invokeAction, item, onItemPress]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!isActionAvailableFor(action, entry as ZappEntry)) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Button {...props} item={state} onPress={onPress} label={state?.label} />
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Explicitly set display name for the component
|
|
54
|
+
ActionButton.displayName = "ActionButton";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { ActionButton, ActionButtonProps } from "./ActionButton";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Binds an entry to the sheet's row button.
|
|
7
|
+
*
|
|
8
|
+
* `BottomSheetModalContent` renders `buttonComponent` per item and does not
|
|
9
|
+
* know about entries, so the entry is closed over here instead.
|
|
10
|
+
*/
|
|
11
|
+
export const boundActionButton = (entry: ZappEntry | ZappFeed) => {
|
|
12
|
+
const ActionButtonWithEntry = (props: ActionButtonProps) => (
|
|
13
|
+
<ActionButton {...props} entry={entry as ZappEntry} />
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
ActionButtonWithEntry.displayName = "ActionButtonWithEntry";
|
|
17
|
+
|
|
18
|
+
return ActionButtonWithEntry;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
boundActionButton.displayName = "boundActionButton";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ActionButton } from "./ActionButton";
|
|
2
|
+
|
|
3
|
+
export type { ActionButtonProps } from "./ActionButton";
|
|
4
|
+
|
|
5
|
+
export { boundActionButton } from "./boundActionButton";
|
|
6
|
+
|
|
7
|
+
export { openActionsBottomSheet } from "./openActionsBottomSheet";
|
|
8
|
+
|
|
9
|
+
export type { OpenActionsBottomSheetArgs } from "./openActionsBottomSheet";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
2
|
+
import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
3
|
+
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
4
|
+
import { RegisteredAction } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
|
|
5
|
+
|
|
6
|
+
import { boundActionButton } from "./boundActionButton";
|
|
7
|
+
|
|
8
|
+
const { log_warning } = createLogger({
|
|
9
|
+
subsystem: "ActionsBottomSheet",
|
|
10
|
+
category: "Presentation",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type OpenActionsBottomSheetArgs = {
|
|
14
|
+
/** Entry the actions act on; bound into every row button. */
|
|
15
|
+
entry: ZappEntry | ZappFeed;
|
|
16
|
+
/** Already resolved and merged actions, in the order they should appear. */
|
|
17
|
+
actions: RegisteredAction[];
|
|
18
|
+
title?: string;
|
|
19
|
+
summary?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Presents a list of resolved actions in the shared bottom sheet.
|
|
24
|
+
*
|
|
25
|
+
* This is the core entry point for "show these actions in a sheet": the
|
|
26
|
+
* open-modal-bottom-sheet plugin uses it for its configured list, and the
|
|
27
|
+
* player overflow uses it for the actions that did not fit on the overlay.
|
|
28
|
+
* Neither has to know about the other, and neither needs the plugin installed.
|
|
29
|
+
*/
|
|
30
|
+
export function openActionsBottomSheet({
|
|
31
|
+
entry,
|
|
32
|
+
actions,
|
|
33
|
+
title,
|
|
34
|
+
summary,
|
|
35
|
+
}: OpenActionsBottomSheetArgs): void {
|
|
36
|
+
if (!actions?.length) {
|
|
37
|
+
// Every action resolved away - unavailable for this entry, or not
|
|
38
|
+
// invokable. An empty sheet looks broken, so say why and stay put.
|
|
39
|
+
log_warning(
|
|
40
|
+
"openActionsBottomSheet: nothing to present - not opening the sheet",
|
|
41
|
+
{ entryId: (entry as ZappEntry)?.id }
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
openBottomSheetModal({
|
|
48
|
+
modalBottomSheetContentProps: {
|
|
49
|
+
// TODO: should be optional - the button handles its own tap, but
|
|
50
|
+
// BottomSheetModalContent still requires an onPress.
|
|
51
|
+
onPress: noop,
|
|
52
|
+
items: actions,
|
|
53
|
+
buttonComponent: boundActionButton(entry),
|
|
54
|
+
title,
|
|
55
|
+
summary,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -11,31 +11,20 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import React from "react";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Image,
|
|
16
|
+
Platform,
|
|
17
|
+
Pressable,
|
|
18
|
+
StyleSheet,
|
|
19
|
+
Text,
|
|
20
|
+
View,
|
|
21
|
+
} from "react-native";
|
|
15
22
|
|
|
16
23
|
const styles = StyleSheet.create({
|
|
17
24
|
row: {
|
|
18
25
|
flexDirection: "row",
|
|
19
26
|
alignItems: "center",
|
|
20
27
|
},
|
|
21
|
-
demoScreen: {
|
|
22
|
-
flex: 1,
|
|
23
|
-
backgroundColor: "#1C1C1E",
|
|
24
|
-
paddingTop: 64,
|
|
25
|
-
paddingHorizontal: 16,
|
|
26
|
-
gap: 12,
|
|
27
|
-
},
|
|
28
|
-
demoHeading: {
|
|
29
|
-
color: "#FFFFFF",
|
|
30
|
-
fontSize: 20,
|
|
31
|
-
fontWeight: "600",
|
|
32
|
-
marginBottom: 8,
|
|
33
|
-
},
|
|
34
|
-
demoLabel: {
|
|
35
|
-
color: "#999999",
|
|
36
|
-
fontSize: 13,
|
|
37
|
-
marginTop: 8,
|
|
38
|
-
},
|
|
39
28
|
});
|
|
40
29
|
|
|
41
30
|
// ---------------------------------------------------------------------------
|
|
@@ -123,10 +112,11 @@ export type ActionData = {
|
|
|
123
112
|
showTrailingChevron?: boolean;
|
|
124
113
|
};
|
|
125
114
|
|
|
126
|
-
export type
|
|
115
|
+
export type AudioPlayerActionProps = {
|
|
127
116
|
configuration: ActionConfiguration;
|
|
128
117
|
data: ActionData;
|
|
129
118
|
focused?: boolean;
|
|
119
|
+
onPress?: () => void;
|
|
130
120
|
};
|
|
131
121
|
|
|
132
122
|
// ---------------------------------------------------------------------------
|
|
@@ -187,28 +177,32 @@ type ActionContainerProps = {
|
|
|
187
177
|
configuration: BackgroundConfiguration;
|
|
188
178
|
focused: boolean;
|
|
189
179
|
children: React.ReactNode;
|
|
180
|
+
onPress?: () => void;
|
|
190
181
|
};
|
|
191
182
|
|
|
192
183
|
function ActionContainer({
|
|
193
184
|
configuration,
|
|
194
185
|
focused,
|
|
195
186
|
children,
|
|
187
|
+
onPress,
|
|
196
188
|
}: ActionContainerProps) {
|
|
197
189
|
return (
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
190
|
+
<Pressable
|
|
191
|
+
onPress={onPress}
|
|
192
|
+
style={({ pressed }) => ({
|
|
193
|
+
backgroundColor:
|
|
194
|
+
pressed || focused
|
|
195
|
+
? configuration.focusedColor
|
|
196
|
+
: configuration.defaultColor,
|
|
203
197
|
borderRadius: configuration.cornerRadius,
|
|
204
198
|
paddingTop: CONTAINER_SPEC.paddingTop,
|
|
205
199
|
paddingRight: CONTAINER_SPEC.paddingRight,
|
|
206
200
|
paddingBottom: CONTAINER_SPEC.paddingBottom,
|
|
207
201
|
paddingLeft: CONTAINER_SPEC.paddingLeft,
|
|
208
|
-
}}
|
|
202
|
+
})}
|
|
209
203
|
>
|
|
210
204
|
{children}
|
|
211
|
-
</
|
|
205
|
+
</Pressable>
|
|
212
206
|
);
|
|
213
207
|
}
|
|
214
208
|
|
|
@@ -279,10 +273,15 @@ function ActionTrailingIcon({ configuration }: ActionTrailingIconProps) {
|
|
|
279
273
|
}
|
|
280
274
|
|
|
281
275
|
// ---------------------------------------------------------------------------
|
|
282
|
-
//
|
|
276
|
+
// AudioPlayerAction
|
|
283
277
|
// ---------------------------------------------------------------------------
|
|
284
278
|
|
|
285
|
-
export function
|
|
279
|
+
export function AudioPlayerAction({
|
|
280
|
+
configuration,
|
|
281
|
+
data,
|
|
282
|
+
focused = false,
|
|
283
|
+
onPress,
|
|
284
|
+
}: AudioPlayerActionProps) {
|
|
286
285
|
const { background, title, trailingIcon } = configuration;
|
|
287
286
|
const { title: label, leadingActionType, showTrailingChevron } = data;
|
|
288
287
|
|
|
@@ -290,7 +289,11 @@ export function Item({ configuration, data, focused = false }: ItemProps) {
|
|
|
290
289
|
const showTrailing = shouldShowTrailingIcon(showTrailingChevron);
|
|
291
290
|
|
|
292
291
|
return (
|
|
293
|
-
<ActionContainer
|
|
292
|
+
<ActionContainer
|
|
293
|
+
configuration={background}
|
|
294
|
+
focused={focused}
|
|
295
|
+
onPress={onPress}
|
|
296
|
+
>
|
|
294
297
|
<View style={styles.row}>
|
|
295
298
|
{showLeading ? (
|
|
296
299
|
<>
|
|
@@ -309,60 +312,3 @@ export function Item({ configuration, data, focused = false }: ItemProps) {
|
|
|
309
312
|
</ActionContainer>
|
|
310
313
|
);
|
|
311
314
|
}
|
|
312
|
-
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
// Demo
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
export default function App() {
|
|
318
|
-
return (
|
|
319
|
-
<View style={styles.demoScreen}>
|
|
320
|
-
<Text style={styles.demoHeading}>Modal Action Building Block</Text>
|
|
321
|
-
|
|
322
|
-
<Text style={styles.demoLabel}>
|
|
323
|
-
Leading icon + text — Add To Queue (focused)
|
|
324
|
-
</Text>
|
|
325
|
-
<Item
|
|
326
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
327
|
-
data={{ title: "Add To Queue", leadingActionType: "addToQueue" }}
|
|
328
|
-
focused
|
|
329
|
-
/>
|
|
330
|
-
|
|
331
|
-
<Text style={styles.demoLabel}>
|
|
332
|
-
Leading icon + text + trailing chevron — Add To Playlist
|
|
333
|
-
</Text>
|
|
334
|
-
<Item
|
|
335
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
336
|
-
data={{
|
|
337
|
-
title: "Add To Playlist",
|
|
338
|
-
leadingActionType: "addToPlaylist",
|
|
339
|
-
showTrailingChevron: true,
|
|
340
|
-
}}
|
|
341
|
-
/>
|
|
342
|
-
|
|
343
|
-
<Text style={styles.demoLabel}>Text only — Go To Playlist</Text>
|
|
344
|
-
<Item
|
|
345
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
346
|
-
data={{ title: "Go To Playlist" }}
|
|
347
|
-
/>
|
|
348
|
-
|
|
349
|
-
<Text style={styles.demoLabel}>Leading icon + text — Favorite</Text>
|
|
350
|
-
<Item
|
|
351
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
352
|
-
data={{ title: "Favorite", leadingActionType: "favorite" }}
|
|
353
|
-
/>
|
|
354
|
-
|
|
355
|
-
<Text style={styles.demoLabel}>Leading icon + text — Share</Text>
|
|
356
|
-
<Item
|
|
357
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
358
|
-
data={{ title: "Share", leadingActionType: "share" }}
|
|
359
|
-
/>
|
|
360
|
-
|
|
361
|
-
<Text style={styles.demoLabel}>Text only — Go To Artist</Text>
|
|
362
|
-
<Item
|
|
363
|
-
configuration={DEFAULT_ACTION_CONFIGURATION}
|
|
364
|
-
data={{ title: "Go To Artist" }}
|
|
365
|
-
/>
|
|
366
|
-
</View>
|
|
367
|
-
);
|
|
368
|
-
}
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React from "react";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
Image,
|
|
14
|
+
Platform,
|
|
15
|
+
Pressable,
|
|
16
|
+
StyleSheet,
|
|
17
|
+
Text,
|
|
18
|
+
View,
|
|
19
|
+
} from "react-native";
|
|
13
20
|
|
|
14
21
|
const styles = StyleSheet.create({
|
|
15
22
|
container: {
|
|
@@ -25,24 +32,6 @@ const styles = StyleSheet.create({
|
|
|
25
32
|
title: {
|
|
26
33
|
textAlign: "center",
|
|
27
34
|
},
|
|
28
|
-
demoScreen: {
|
|
29
|
-
flex: 1,
|
|
30
|
-
backgroundColor: "#1C1C1E",
|
|
31
|
-
paddingTop: 64,
|
|
32
|
-
paddingHorizontal: 24,
|
|
33
|
-
gap: 12,
|
|
34
|
-
},
|
|
35
|
-
demoHeading: {
|
|
36
|
-
color: "#FFFFFF",
|
|
37
|
-
fontSize: 20,
|
|
38
|
-
fontWeight: "600",
|
|
39
|
-
marginBottom: 8,
|
|
40
|
-
},
|
|
41
|
-
demoLabel: {
|
|
42
|
-
color: "#999999",
|
|
43
|
-
fontSize: 13,
|
|
44
|
-
marginTop: 8,
|
|
45
|
-
},
|
|
46
35
|
});
|
|
47
36
|
|
|
48
37
|
// ---------------------------------------------------------------------------
|
|
@@ -92,6 +81,8 @@ export type LeadingIconConfiguration = {
|
|
|
92
81
|
enabled: boolean;
|
|
93
82
|
/** Decides which icon will be displayed when enabled */
|
|
94
83
|
actionType?: ButtonActionType;
|
|
84
|
+
/** Optional Studio / modal-blocks asset URI (overrides actionType mapping) */
|
|
85
|
+
asset?: string;
|
|
95
86
|
};
|
|
96
87
|
|
|
97
88
|
export type IconsConfiguration = {
|
|
@@ -148,11 +139,12 @@ export type ButtonData = {
|
|
|
148
139
|
disabled?: boolean;
|
|
149
140
|
};
|
|
150
141
|
|
|
151
|
-
export type
|
|
142
|
+
export type AudioPlayerButtonProps = {
|
|
152
143
|
configuration: ButtonConfiguration;
|
|
153
144
|
data: ButtonData;
|
|
154
145
|
disabled?: boolean;
|
|
155
146
|
focused?: boolean;
|
|
147
|
+
onPress?: () => void;
|
|
156
148
|
};
|
|
157
149
|
|
|
158
150
|
// ---------------------------------------------------------------------------
|
|
@@ -170,7 +162,7 @@ const ACTION_ICON_ASSETS: Record<ButtonActionType, string> = {
|
|
|
170
162
|
confirm:
|
|
171
163
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJElEQVR42mNgGAWjYBSQCv4TwKMWDHELho/hNE/3o2AUDDcAAPv0QMAyy1CtAAAAAElFTkSuQmCC",
|
|
172
164
|
cancel:
|
|
173
|
-
"data:image/png;base64,
|
|
165
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAASElEQVR42mNgGAWjGvynkVoUTf+pqA6nxv8UqiHbEooNx2cQ1QzHZiDVDcdlCcOQsoCmQUTTSKZpMqV5RqNLUUHTwm4UDBMAALBLQ73LTvoyAAAAAElFTkSuQmCC",
|
|
174
166
|
};
|
|
175
167
|
|
|
176
168
|
function getTextTransformStyle(
|
|
@@ -180,7 +172,9 @@ function getTextTransformStyle(
|
|
|
180
172
|
}
|
|
181
173
|
|
|
182
174
|
function shouldShowLeadingIcon(leadingIcon: LeadingIconConfiguration) {
|
|
183
|
-
return
|
|
175
|
+
return (
|
|
176
|
+
leadingIcon.enabled && (!!leadingIcon.actionType || !!leadingIcon.asset)
|
|
177
|
+
);
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
// ---------------------------------------------------------------------------
|
|
@@ -192,6 +186,7 @@ type ButtonContainerProps = {
|
|
|
192
186
|
disabled: boolean;
|
|
193
187
|
configuration: BackgroundConfiguration;
|
|
194
188
|
children: React.ReactNode;
|
|
189
|
+
onPress?: () => void;
|
|
195
190
|
};
|
|
196
191
|
|
|
197
192
|
function ButtonContainer({
|
|
@@ -199,19 +194,22 @@ function ButtonContainer({
|
|
|
199
194
|
disabled,
|
|
200
195
|
configuration,
|
|
201
196
|
children,
|
|
197
|
+
onPress,
|
|
202
198
|
}: ButtonContainerProps) {
|
|
203
|
-
const
|
|
199
|
+
const defaultBgColor = disabled
|
|
204
200
|
? configuration.inactiveColor
|
|
205
201
|
: focused
|
|
206
202
|
? configuration.focusedColor
|
|
207
203
|
: configuration.defaultColor;
|
|
208
204
|
|
|
209
205
|
return (
|
|
210
|
-
<
|
|
211
|
-
|
|
206
|
+
<Pressable
|
|
207
|
+
onPress={disabled ? undefined : onPress}
|
|
208
|
+
style={({ pressed }) => [
|
|
212
209
|
styles.container,
|
|
213
210
|
{
|
|
214
|
-
backgroundColor
|
|
211
|
+
backgroundColor:
|
|
212
|
+
pressed && !disabled ? configuration.focusedColor : defaultBgColor,
|
|
215
213
|
borderRadius: configuration.cornerRadius,
|
|
216
214
|
paddingTop: configuration.paddingTop,
|
|
217
215
|
paddingRight: configuration.paddingRight,
|
|
@@ -221,7 +219,7 @@ function ButtonContainer({
|
|
|
221
219
|
]}
|
|
222
220
|
>
|
|
223
221
|
{children}
|
|
224
|
-
</
|
|
222
|
+
</Pressable>
|
|
225
223
|
);
|
|
226
224
|
}
|
|
227
225
|
|
|
@@ -274,7 +272,7 @@ function ButtonTitle({
|
|
|
274
272
|
}
|
|
275
273
|
|
|
276
274
|
type ButtonLeadingIconProps = {
|
|
277
|
-
actionType
|
|
275
|
+
actionType?: ButtonActionType;
|
|
278
276
|
configuration: IconsConfiguration;
|
|
279
277
|
};
|
|
280
278
|
|
|
@@ -282,9 +280,17 @@ function ButtonLeadingIcon({
|
|
|
282
280
|
actionType,
|
|
283
281
|
configuration,
|
|
284
282
|
}: ButtonLeadingIconProps) {
|
|
283
|
+
const asset =
|
|
284
|
+
configuration.leadingIcon.asset ||
|
|
285
|
+
(actionType ? ACTION_ICON_ASSETS[actionType] : undefined);
|
|
286
|
+
|
|
287
|
+
if (!asset) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
285
291
|
return (
|
|
286
292
|
<Image
|
|
287
|
-
source={{ uri:
|
|
293
|
+
source={{ uri: asset }}
|
|
288
294
|
style={{
|
|
289
295
|
width: configuration.width,
|
|
290
296
|
height: configuration.height,
|
|
@@ -295,20 +301,32 @@ function ButtonLeadingIcon({
|
|
|
295
301
|
}
|
|
296
302
|
|
|
297
303
|
// ---------------------------------------------------------------------------
|
|
298
|
-
//
|
|
304
|
+
// AudioPlayerButton — single building block driven by configuration + data
|
|
299
305
|
// ---------------------------------------------------------------------------
|
|
300
306
|
|
|
301
|
-
|
|
307
|
+
import { useModalBlocksStyle } from "../../ModalBlocksStyleContext";
|
|
308
|
+
import { mergeButtonConfiguration } from "../utils/mergeStyles";
|
|
309
|
+
|
|
310
|
+
export function AudioPlayerButton({
|
|
302
311
|
configuration,
|
|
303
312
|
data,
|
|
304
313
|
disabled,
|
|
305
314
|
focused = false,
|
|
306
|
-
|
|
315
|
+
onPress,
|
|
316
|
+
}: AudioPlayerButtonProps) {
|
|
317
|
+
const modalBlocksStyle = useModalBlocksStyle();
|
|
318
|
+
const buttonBlock = modalBlocksStyle?.button;
|
|
319
|
+
const actionBlock = modalBlocksStyle?.action;
|
|
320
|
+
|
|
321
|
+
const effectiveConfig = React.useMemo(() => {
|
|
322
|
+
return mergeButtonConfiguration(configuration, buttonBlock, actionBlock);
|
|
323
|
+
}, [configuration, buttonBlock, actionBlock]);
|
|
324
|
+
|
|
307
325
|
const {
|
|
308
|
-
background:
|
|
309
|
-
title:
|
|
326
|
+
background: effectiveBgConfig,
|
|
327
|
+
title: effectiveTitleConfig,
|
|
310
328
|
icons: iconsConfiguration,
|
|
311
|
-
} =
|
|
329
|
+
} = effectiveConfig;
|
|
312
330
|
|
|
313
331
|
const { title } = data;
|
|
314
332
|
const isDisabled = disabled ?? data.disabled ?? false;
|
|
@@ -320,7 +338,8 @@ export function Item({
|
|
|
320
338
|
<ButtonContainer
|
|
321
339
|
focused={focused}
|
|
322
340
|
disabled={isDisabled}
|
|
323
|
-
configuration={
|
|
341
|
+
configuration={effectiveBgConfig}
|
|
342
|
+
onPress={onPress}
|
|
324
343
|
>
|
|
325
344
|
<View style={styles.contentRow}>
|
|
326
345
|
{showIcon ? (
|
|
@@ -329,14 +348,14 @@ export function Item({
|
|
|
329
348
|
actionType={leadingIcon.actionType}
|
|
330
349
|
configuration={iconsConfiguration}
|
|
331
350
|
/>
|
|
332
|
-
<View style={{ width:
|
|
351
|
+
<View style={{ width: effectiveBgConfig.horizontalGutter }} />
|
|
333
352
|
</>
|
|
334
353
|
) : null}
|
|
335
354
|
<ButtonTitle
|
|
336
355
|
title={title}
|
|
337
356
|
focused={focused}
|
|
338
357
|
disabled={isDisabled}
|
|
339
|
-
configuration={
|
|
358
|
+
configuration={effectiveTitleConfig}
|
|
340
359
|
/>
|
|
341
360
|
</View>
|
|
342
361
|
</ButtonContainer>
|
|
@@ -386,39 +405,3 @@ export const VARIANT_LARGE_ICON: ButtonConfiguration = {
|
|
|
386
405
|
},
|
|
387
406
|
},
|
|
388
407
|
};
|
|
389
|
-
|
|
390
|
-
export default function App() {
|
|
391
|
-
return (
|
|
392
|
-
<View style={styles.demoScreen}>
|
|
393
|
-
<Text style={styles.demoHeading}>Modal Button Building Block</Text>
|
|
394
|
-
|
|
395
|
-
<Text style={styles.demoLabel}>Variant 1 — Text only (confirmation)</Text>
|
|
396
|
-
<Item configuration={VARIANT_TEXT_ONLY} data={{ title: "Confirm" }} />
|
|
397
|
-
|
|
398
|
-
<Text style={styles.demoLabel}>Variant 2 — Leading icon + text</Text>
|
|
399
|
-
<Item
|
|
400
|
-
configuration={VARIANT_WITH_ICON}
|
|
401
|
-
data={{ title: "Add To Playlist" }}
|
|
402
|
-
/>
|
|
403
|
-
|
|
404
|
-
<Text style={styles.demoLabel}>Inactive state</Text>
|
|
405
|
-
<Item
|
|
406
|
-
configuration={VARIANT_TEXT_ONLY}
|
|
407
|
-
data={{ title: "Unavailable" }}
|
|
408
|
-
disabled
|
|
409
|
-
/>
|
|
410
|
-
|
|
411
|
-
<Text style={styles.demoLabel}>Custom title config — uppercase</Text>
|
|
412
|
-
<Item
|
|
413
|
-
configuration={VARIANT_UPPERCASE_TITLE}
|
|
414
|
-
data={{ title: "Save Changes" }}
|
|
415
|
-
/>
|
|
416
|
-
|
|
417
|
-
<Text style={styles.demoLabel}>Custom icons config — 32×32</Text>
|
|
418
|
-
<Item
|
|
419
|
-
configuration={VARIANT_LARGE_ICON}
|
|
420
|
-
data={{ title: "Add To Queue" }}
|
|
421
|
-
/>
|
|
422
|
-
</View>
|
|
423
|
-
);
|
|
424
|
-
}
|