@applicaster/zapp-react-native-ui-components 15.1.13-alpha.1034774223 → 15.1.13
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/MasterCell/DefaultComponents/ActionButton.tsx +1 -0
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +87 -33
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +74 -57
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +107 -142
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +159 -410
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +4 -8
- package/Components/ModalComponent/BottomSheetModalContent.tsx +61 -379
- package/Components/ModalComponent/Button/index.tsx +3 -5
- package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
- package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
- package/Components/ModalComponent/Header/index.tsx +7 -20
- package/Components/ModalComponent/Header/utils.ts +1 -1
- package/Components/ModalComponent/index.tsx +0 -11
- package/Components/ModalComponent/utils.ts +21 -82
- package/package.json +5 -6
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +0 -54
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +0 -21
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +0 -9
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +0 -58
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +0 -653
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +0 -89
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +0 -230
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +0 -332
- package/Components/ModalComponent/ModalBlocksStyleContext.tsx +0 -10
- package/Components/ModalComponent/SortableList.tsx +0 -142
- package/Components/ModalComponent/SortableList.web.tsx +0 -102
- package/Components/ModalComponent/TextInputContent.tsx +0 -100
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +0 -238
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +0 -120
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +0 -95
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +0 -66
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +0 -78
- package/Components/ModalComponent/presets/index.ts +0 -27
- package/Components/ModalComponent/presets/selectableCell.tsx +0 -28
- package/Components/ModalComponent/presets/standardCell.tsx +0 -41
- package/Components/ModalComponent/presets/types.ts +0 -35
- package/Components/ModalComponent/showTextInput.ts +0 -43
- package/Hooks/useEntryActionsExpander.ts +0 -63
- /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.tsx → Hero.js} +0 -0
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Item as AudioPlayerItem } from "./Item";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Item as AudioPlayerHeader } from "./Header";
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export { Item as AudioPlayerButton } from "./Button";
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
export { NowPlayingHeaderSection } from "./NowPlayingHeaderSection";
|
|
10
|
-
|
|
11
|
-
export { AudioPlayerInput } from "./Input";
|
|
7
|
+
export { Item as AudioPlayerAction } from "./Action";
|
|
@@ -1,54 +1,16 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ActivityIndicator,
|
|
4
|
-
ScrollView,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text,
|
|
7
|
-
View,
|
|
8
|
-
} from "react-native";
|
|
2
|
+
import { View, ScrollView } from "react-native";
|
|
9
3
|
import { Button } from "./Button";
|
|
10
4
|
import { ItemIconProps } from "./Button/ItemIcon";
|
|
11
5
|
import { ItemProps } from "./Button/Item";
|
|
12
6
|
import { ItemLabelProps } from "./Button/ItemLabel";
|
|
13
7
|
import { getItemIconProps, getItemLabelProps, getItemProps } from "./utils";
|
|
14
8
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
15
|
-
import {
|
|
16
|
-
ContentViewModel,
|
|
17
|
-
OPERATIONS,
|
|
18
|
-
hasOperation,
|
|
19
|
-
useBottomSheetContent,
|
|
20
|
-
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
21
|
-
import { getCell } from "./presets";
|
|
22
9
|
import type { PluginConfiguration } from "./";
|
|
10
|
+
|
|
23
11
|
import { ModalHeader as DefaultModalHeader } from "./Header";
|
|
24
12
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
25
|
-
import { SortableList } from "./SortableList";
|
|
26
|
-
import {
|
|
27
|
-
AudioPlayerButton,
|
|
28
|
-
NowPlayingHeaderSection,
|
|
29
|
-
VARIANT_WITH_ICON,
|
|
30
|
-
} from "./AudioPlayer/Components";
|
|
31
13
|
import { BottomSheetDragArea } from "./BottomSheetDragArea";
|
|
32
|
-
import { ModalBlocksStyleContext } from "./ModalBlocksStyleContext";
|
|
33
|
-
import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
|
|
34
|
-
import {
|
|
35
|
-
ModalBlocksConfig,
|
|
36
|
-
parseModalBlocksConfiguration,
|
|
37
|
-
} from "@applicaster/zapp-react-native-utils/configurationUtils/modalBlocksParser";
|
|
38
|
-
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
39
|
-
|
|
40
|
-
const styles = StyleSheet.create({
|
|
41
|
-
centered: {
|
|
42
|
-
justifyContent: "center",
|
|
43
|
-
alignItems: "center",
|
|
44
|
-
padding: 16,
|
|
45
|
-
},
|
|
46
|
-
errorText: {
|
|
47
|
-
color: "#ff453a",
|
|
48
|
-
fontSize: 14,
|
|
49
|
-
textAlign: "center",
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
14
|
|
|
53
15
|
type ModalComponentProps = {
|
|
54
16
|
items: any[];
|
|
@@ -60,7 +22,13 @@ type ModalComponentProps = {
|
|
|
60
22
|
title?: string;
|
|
61
23
|
maxHeight?: number;
|
|
62
24
|
dismiss: () => void;
|
|
63
|
-
headerComponent?: React.ComponentType
|
|
25
|
+
headerComponent?: React.ComponentType;
|
|
26
|
+
|
|
27
|
+
scrollViewContent?: {
|
|
28
|
+
renderItem: (item: any, index: number) => React.ReactNode;
|
|
29
|
+
items: any[];
|
|
30
|
+
} | null;
|
|
31
|
+
|
|
64
32
|
buttonComponent?: React.ComponentType;
|
|
65
33
|
iconProps?: ItemIconProps | ((theme: PluginConfiguration) => ItemIconProps);
|
|
66
34
|
itemProps?:
|
|
@@ -69,18 +37,13 @@ type ModalComponentProps = {
|
|
|
69
37
|
labelProps?:
|
|
70
38
|
| ItemLabelProps
|
|
71
39
|
| ((theme: PluginConfiguration, width: number) => ItemLabelProps);
|
|
72
|
-
getSelectedItemIcon
|
|
40
|
+
getSelectedItemIcon: (
|
|
73
41
|
theme: PluginConfiguration
|
|
74
42
|
) => ItemIconProps["asset"] | null;
|
|
75
|
-
getDefaultItemIcon
|
|
43
|
+
getDefaultItemIcon: (
|
|
76
44
|
theme: PluginConfiguration
|
|
77
45
|
) => ItemIconProps["asset"] | null;
|
|
78
46
|
iconPlacement?: "left" | "right";
|
|
79
|
-
viewModel?: ContentViewModel;
|
|
80
|
-
/** Optional body that replaces the items list (header stays DefaultModalHeader). */
|
|
81
|
-
contentComponent?: React.ComponentType<any>;
|
|
82
|
-
contentComponentProps?: Record<string, any>;
|
|
83
|
-
styleOverrides?: ModalBlocksConfig | Record<string, any>;
|
|
84
47
|
};
|
|
85
48
|
|
|
86
49
|
export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
@@ -92,17 +55,15 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
92
55
|
dismiss,
|
|
93
56
|
summary,
|
|
94
57
|
title,
|
|
95
|
-
headerComponent,
|
|
96
|
-
|
|
58
|
+
headerComponent = DefaultModalHeader,
|
|
59
|
+
scrollViewContent = null,
|
|
60
|
+
|
|
97
61
|
buttonComponent: ButtonComponent = Button,
|
|
98
62
|
getSelectedItemIcon = ({
|
|
99
63
|
modal_bottom_sheet_item_selected_icon,
|
|
100
64
|
}: BaseThemePropertiesMobile) => modal_bottom_sheet_item_selected_icon,
|
|
101
65
|
getDefaultItemIcon = () => null,
|
|
102
66
|
iconPlacement,
|
|
103
|
-
contentComponent: ContentComponent,
|
|
104
|
-
contentComponentProps = {},
|
|
105
|
-
styleOverrides: propStyleOverrides,
|
|
106
67
|
} = props;
|
|
107
68
|
|
|
108
69
|
const route = useRef(currentRoute);
|
|
@@ -110,67 +71,7 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
110
71
|
const paddingTop = Number(theme.modal_bottom_sheet_padding_top);
|
|
111
72
|
const paddingBottom = Number(theme.modal_bottom_sheet_padding_bottom);
|
|
112
73
|
|
|
113
|
-
const
|
|
114
|
-
items: fetchedItems,
|
|
115
|
-
showCentralLoading,
|
|
116
|
-
error,
|
|
117
|
-
handleItemPress,
|
|
118
|
-
role,
|
|
119
|
-
behavior,
|
|
120
|
-
editableCollection,
|
|
121
|
-
themePluginId,
|
|
122
|
-
} = useBottomSheetContent({
|
|
123
|
-
onPress,
|
|
124
|
-
viewModel,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const plugins = usePlugins();
|
|
128
|
-
|
|
129
|
-
const plugin = useMemo(
|
|
130
|
-
() => plugins?.find((p) => p.identifier === themePluginId),
|
|
131
|
-
[plugins, themePluginId]
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const styleOverrides = useMemo(() => {
|
|
135
|
-
if (propStyleOverrides) {
|
|
136
|
-
return propStyleOverrides;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const configuration =
|
|
140
|
-
plugin?.configuration || (plugin as any)?.configuration_json;
|
|
141
|
-
|
|
142
|
-
if (!configuration || !themePluginId) return null;
|
|
143
|
-
|
|
144
|
-
return parseModalBlocksConfiguration(configuration, themePluginId);
|
|
145
|
-
}, [propStyleOverrides, plugin, themePluginId]);
|
|
146
|
-
|
|
147
|
-
const pluginLocalizations = useLocalizedStrings({
|
|
148
|
-
localizations:
|
|
149
|
-
plugin?.configuration?.localizations ||
|
|
150
|
-
(plugin as any)?.configuration_json?.localizations,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const normalizedPluginId = themePluginId?.replace(/-/g, "_");
|
|
154
|
-
|
|
155
|
-
const localizedHeaderTitle = normalizedPluginId
|
|
156
|
-
? pluginLocalizations?.[`${normalizedPluginId}_header_title`]
|
|
157
|
-
: undefined;
|
|
158
|
-
|
|
159
|
-
const createPlaylistTitle =
|
|
160
|
-
pluginLocalizations?.create_new_playlist_title || "Create New Playlist";
|
|
161
|
-
|
|
162
|
-
const resolvedTitle = props.title || localizedHeaderTitle || title;
|
|
163
|
-
|
|
164
|
-
const itemsToRender = viewModel ? fetchedItems : items;
|
|
165
|
-
const operations = editableCollection?.operations || [];
|
|
166
|
-
const isSortable = hasOperation(operations, OPERATIONS.REORDER);
|
|
167
|
-
|
|
168
|
-
const cell = getCell(
|
|
169
|
-
viewModel ? role : undefined,
|
|
170
|
-
viewModel ? behavior : undefined
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const Header = headerComponent ?? DefaultModalHeader;
|
|
74
|
+
const Header = headerComponent;
|
|
174
75
|
|
|
175
76
|
useEffect(() => {
|
|
176
77
|
if (currentRoute !== route.current) {
|
|
@@ -183,24 +84,14 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
183
84
|
}, [theme, props.iconProps]);
|
|
184
85
|
|
|
185
86
|
const itemBaseProps = useMemo<ItemProps>(() => {
|
|
186
|
-
return getItemProps(
|
|
187
|
-
|
|
188
|
-
props.width,
|
|
189
|
-
props.itemProps,
|
|
190
|
-
styleOverrides?.item
|
|
191
|
-
);
|
|
192
|
-
}, [theme, props.width, props.itemProps, styleOverrides?.item]);
|
|
87
|
+
return getItemProps(theme, props.width, props.itemProps);
|
|
88
|
+
}, [theme, props.width, props.itemProps]);
|
|
193
89
|
|
|
194
90
|
const labelBaseProps = useMemo<ItemLabelProps>(() => {
|
|
195
|
-
return getItemLabelProps(
|
|
196
|
-
|
|
197
|
-
props.width,
|
|
198
|
-
props.labelProps,
|
|
199
|
-
styleOverrides?.item
|
|
200
|
-
);
|
|
201
|
-
}, [theme, props.width, props.labelProps, styleOverrides?.item]);
|
|
91
|
+
return getItemLabelProps(theme, props.width, props.labelProps);
|
|
92
|
+
}, [theme, props.width, props.labelProps]);
|
|
202
93
|
|
|
203
|
-
const
|
|
94
|
+
const handlePress = useCallback(
|
|
204
95
|
(item: any) => {
|
|
205
96
|
onPress(item);
|
|
206
97
|
dismiss();
|
|
@@ -208,267 +99,58 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
208
99
|
[onPress, dismiss]
|
|
209
100
|
);
|
|
210
101
|
|
|
211
|
-
const onItemPress = viewModel ? handleItemPress : handleStaticPress;
|
|
212
|
-
|
|
213
|
-
const handleDragEnd = useCallback(
|
|
214
|
-
(params: any) => {
|
|
215
|
-
const from = params?.fromIndex ?? params?.from;
|
|
216
|
-
const to = params?.toIndex ?? params?.to;
|
|
217
|
-
|
|
218
|
-
const reorderedItemIds = Array.isArray(params?.data)
|
|
219
|
-
? params.data
|
|
220
|
-
.filter((item: any) => !item?.isHeader)
|
|
221
|
-
.map((item: any) => item?.id)
|
|
222
|
-
.filter(Boolean)
|
|
223
|
-
: undefined;
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
(reorderedItemIds?.length ||
|
|
227
|
-
(typeof from === "number" &&
|
|
228
|
-
typeof to === "number" &&
|
|
229
|
-
from !== to)) &&
|
|
230
|
-
editableCollection?.move
|
|
231
|
-
) {
|
|
232
|
-
editableCollection.move(reorderedItemIds || []);
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
[editableCollection]
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
const renderContext = useMemo(
|
|
239
|
-
() => ({
|
|
240
|
-
theme,
|
|
241
|
-
ButtonComponent,
|
|
242
|
-
current_selection,
|
|
243
|
-
iconBaseProps,
|
|
244
|
-
itemBaseProps,
|
|
245
|
-
labelBaseProps,
|
|
246
|
-
getSelectedItemIcon,
|
|
247
|
-
getDefaultItemIcon,
|
|
248
|
-
iconPlacement,
|
|
249
|
-
editableCollection,
|
|
250
|
-
behavior,
|
|
251
|
-
}),
|
|
252
|
-
[
|
|
253
|
-
theme,
|
|
254
|
-
ButtonComponent,
|
|
255
|
-
current_selection,
|
|
256
|
-
iconBaseProps,
|
|
257
|
-
itemBaseProps,
|
|
258
|
-
labelBaseProps,
|
|
259
|
-
getSelectedItemIcon,
|
|
260
|
-
getDefaultItemIcon,
|
|
261
|
-
iconPlacement,
|
|
262
|
-
editableCollection,
|
|
263
|
-
behavior,
|
|
264
|
-
]
|
|
265
|
-
);
|
|
266
|
-
|
|
267
102
|
const bottomInset = useSafeAreaInsets().bottom;
|
|
268
103
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
id: "create_new_playlist",
|
|
280
|
-
title: createPlaylistTitle,
|
|
281
|
-
});
|
|
282
|
-
}, [editableCollection, createPlaylistTitle]);
|
|
283
|
-
|
|
284
|
-
const headers = useMemo(() => {
|
|
285
|
-
const list: Array<{ index: number; component: () => React.ReactElement }> =
|
|
286
|
-
[];
|
|
287
|
-
|
|
288
|
-
if (hasNowPlaying) {
|
|
289
|
-
list.push({
|
|
290
|
-
index: list.length,
|
|
291
|
-
component: () => (
|
|
292
|
-
<NowPlayingHeaderSection
|
|
293
|
-
onClear={hasClearAll ? handleDeleteCollection : undefined}
|
|
294
|
-
/>
|
|
295
|
-
),
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (hasAdd) {
|
|
300
|
-
list.push({
|
|
301
|
-
index: list.length,
|
|
302
|
-
component: () => (
|
|
303
|
-
<View
|
|
304
|
-
style={{ paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 }}
|
|
305
|
-
>
|
|
306
|
-
<AudioPlayerButton
|
|
307
|
-
configuration={VARIANT_WITH_ICON}
|
|
308
|
-
data={{ title: createPlaylistTitle }}
|
|
309
|
-
onPress={handleCreatePlaylist}
|
|
310
|
-
/>
|
|
311
|
-
</View>
|
|
312
|
-
),
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return list.length > 0 ? list : undefined;
|
|
317
|
-
}, [
|
|
318
|
-
hasNowPlaying,
|
|
319
|
-
hasClearAll,
|
|
320
|
-
handleDeleteCollection,
|
|
321
|
-
hasAdd,
|
|
322
|
-
handleCreatePlaylist,
|
|
323
|
-
]);
|
|
324
|
-
|
|
325
|
-
const renderBody = () => {
|
|
326
|
-
if (ContentComponent) {
|
|
327
|
-
const shouldWrapInScrollView =
|
|
328
|
-
!contentComponentProps?.disableScrollViewWrap &&
|
|
329
|
-
!(ContentComponent as any)?.disableScrollViewWrap;
|
|
330
|
-
|
|
331
|
-
if (shouldWrapInScrollView) {
|
|
332
|
-
return (
|
|
333
|
-
<ScrollView
|
|
334
|
-
contentContainerStyle={{
|
|
335
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
336
|
-
}}
|
|
337
|
-
>
|
|
338
|
-
<ContentComponent
|
|
339
|
-
{...contentComponentProps}
|
|
340
|
-
dismiss={dismiss}
|
|
341
|
-
width={props.width}
|
|
342
|
-
maxHeight={props.maxHeight}
|
|
343
|
-
/>
|
|
344
|
-
</ScrollView>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return (
|
|
349
|
-
<View
|
|
350
|
-
style={{
|
|
351
|
-
flexShrink: 1,
|
|
352
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
353
|
-
}}
|
|
354
|
-
>
|
|
355
|
-
<ContentComponent
|
|
356
|
-
{...contentComponentProps}
|
|
104
|
+
return (
|
|
105
|
+
<View
|
|
106
|
+
style={{
|
|
107
|
+
maxHeight: props.maxHeight,
|
|
108
|
+
paddingTop: paddingTop,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{Header ? (
|
|
112
|
+
<BottomSheetDragArea>
|
|
113
|
+
<Header
|
|
357
114
|
dismiss={dismiss}
|
|
358
|
-
|
|
359
|
-
|
|
115
|
+
configuration={theme}
|
|
116
|
+
summary={summary}
|
|
117
|
+
title={title}
|
|
360
118
|
/>
|
|
361
|
-
</
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (showCentralLoading) {
|
|
366
|
-
const loaderColor =
|
|
367
|
-
(theme as any)?.modal_bottom_sheet_activity_indicator_color ||
|
|
368
|
-
(theme as any)?.cell_title_color ||
|
|
369
|
-
"#ffffff";
|
|
370
|
-
|
|
371
|
-
return (
|
|
372
|
-
<View
|
|
373
|
-
style={[
|
|
374
|
-
styles.centered,
|
|
375
|
-
{ backgroundColor: theme.modal_bottom_sheet_background_color },
|
|
376
|
-
]}
|
|
377
|
-
>
|
|
378
|
-
<ActivityIndicator color={loaderColor} />
|
|
379
|
-
</View>
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (error) {
|
|
384
|
-
return (
|
|
385
|
-
<View style={styles.centered}>
|
|
386
|
-
<Text style={styles.errorText}>
|
|
387
|
-
{typeof error === "string" ? error : error.message}
|
|
388
|
-
</Text>
|
|
389
|
-
</View>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (isSortable) {
|
|
394
|
-
return (
|
|
395
|
-
<SortableList
|
|
396
|
-
sortableData={itemsToRender}
|
|
397
|
-
headers={headers}
|
|
398
|
-
scrollViewStyle={{
|
|
399
|
-
flexShrink: 1,
|
|
400
|
-
}}
|
|
401
|
-
contentContainerStyle={{
|
|
402
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
403
|
-
}}
|
|
404
|
-
onDragEnd={handleDragEnd}
|
|
405
|
-
renderItem={({ item, index, renderHandle }) =>
|
|
406
|
-
cell.renderItem({
|
|
407
|
-
item,
|
|
408
|
-
index,
|
|
409
|
-
onPress: onItemPress,
|
|
410
|
-
context: renderContext,
|
|
411
|
-
renderHandle,
|
|
412
|
-
})
|
|
413
|
-
}
|
|
414
|
-
/>
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return (
|
|
119
|
+
</BottomSheetDragArea>
|
|
120
|
+
) : null}
|
|
419
121
|
<ScrollView
|
|
420
122
|
overScrollMode="never"
|
|
421
123
|
bounces={false}
|
|
124
|
+
showsVerticalScrollIndicator={false}
|
|
422
125
|
contentContainerStyle={{
|
|
423
126
|
paddingBottom: paddingBottom + bottomInset,
|
|
424
127
|
}}
|
|
425
128
|
>
|
|
426
|
-
{
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
{
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
129
|
+
{scrollViewContent
|
|
130
|
+
? scrollViewContent.items.map((item, index) =>
|
|
131
|
+
scrollViewContent.renderItem(item, index)
|
|
132
|
+
)
|
|
133
|
+
: null}
|
|
134
|
+
{!scrollViewContent &&
|
|
135
|
+
items.map((item, index) =>
|
|
136
|
+
item ? (
|
|
137
|
+
<ButtonComponent
|
|
138
|
+
key={index}
|
|
139
|
+
configuration={theme}
|
|
140
|
+
selectedItem={current_selection}
|
|
141
|
+
item={item}
|
|
142
|
+
onPress={handlePress}
|
|
143
|
+
label={theme[item?.label] ?? item?.label}
|
|
144
|
+
iconBaseProps={iconBaseProps}
|
|
145
|
+
itemBaseProps={itemBaseProps}
|
|
146
|
+
labelBaseProps={labelBaseProps}
|
|
147
|
+
selectedItemIcon={getSelectedItemIcon(theme)}
|
|
148
|
+
defaultItemIcon={getDefaultItemIcon(theme)}
|
|
149
|
+
iconPlacement={iconPlacement}
|
|
150
|
+
/>
|
|
151
|
+
) : null
|
|
152
|
+
)}
|
|
450
153
|
</ScrollView>
|
|
451
|
-
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
return (
|
|
455
|
-
<ModalBlocksStyleContext.Provider value={styleOverrides as any}>
|
|
456
|
-
<View
|
|
457
|
-
style={{
|
|
458
|
-
maxHeight: props.maxHeight,
|
|
459
|
-
paddingTop: paddingTop,
|
|
460
|
-
}}
|
|
461
|
-
>
|
|
462
|
-
<BottomSheetDragArea>
|
|
463
|
-
<Header
|
|
464
|
-
dismiss={dismiss}
|
|
465
|
-
configuration={theme}
|
|
466
|
-
summary={summary}
|
|
467
|
-
title={resolvedTitle}
|
|
468
|
-
/>
|
|
469
|
-
</BottomSheetDragArea>
|
|
470
|
-
{renderBody()}
|
|
471
|
-
</View>
|
|
472
|
-
</ModalBlocksStyleContext.Provider>
|
|
154
|
+
</View>
|
|
473
155
|
);
|
|
474
156
|
}
|
|
@@ -43,13 +43,11 @@ export function Button({
|
|
|
43
43
|
}: ButtonProps) {
|
|
44
44
|
const [focused, setFocused] = useState(false);
|
|
45
45
|
|
|
46
|
-
const selected = selectedItem && item
|
|
46
|
+
const selected = selectedItem && item.value === selectedItem?.value;
|
|
47
47
|
|
|
48
48
|
const itemIconPropsAssets = useMemo<ItemIconProps["asset"]>(
|
|
49
|
-
() =>
|
|
50
|
-
|
|
51
|
-
defaultItemIcon,
|
|
52
|
-
[item?.asset, defaultItemIcon]
|
|
49
|
+
() => configuration[item.asset] ?? item.asset ?? defaultItemIcon,
|
|
50
|
+
[item.asset, defaultItemIcon]
|
|
53
51
|
);
|
|
54
52
|
|
|
55
53
|
const selectedItemIconPropsAssets = useMemo<ItemIconProps["asset"]>(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { TouchableOpacity } from "react-native";
|
|
3
3
|
import { QBImage } from "@applicaster/zapp-react-native-ui-components/Components";
|
|
4
|
+
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
4
5
|
// @ts-ignore
|
|
5
6
|
import { defaultCloseAsset } from "./assets";
|
|
6
7
|
|
|
@@ -40,9 +41,20 @@ export function CloseButton(props: CloseButtonProps) {
|
|
|
40
41
|
|
|
41
42
|
if (!enabled) return null;
|
|
42
43
|
|
|
44
|
+
// Expose the close button as its own accessible control so screen readers
|
|
45
|
+
// (e.g. iOS VoiceOver) can focus and activate it. The image-only button
|
|
46
|
+
// otherwise has no label/role, leaving it unreachable/unannounced. Uses the
|
|
47
|
+
// framework's localized "close" button config (label + hint + button role).
|
|
48
|
+
// Computed each render (not memoized) so runtime localization updates via
|
|
49
|
+
// AccessibilityManager.updateLocalizations() are picked up on re-render.
|
|
50
|
+
const accessibilityProps =
|
|
51
|
+
AccessibilityManager.getInstance().getButtonAccessibilityProps("close");
|
|
52
|
+
|
|
43
53
|
return (
|
|
44
54
|
<TouchableOpacity
|
|
45
55
|
activeOpacity={1}
|
|
56
|
+
testID="closeButton"
|
|
57
|
+
{...accessibilityProps}
|
|
46
58
|
style={{
|
|
47
59
|
marginBottom,
|
|
48
60
|
marginTop,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react-native";
|
|
3
|
+
import { CloseButton, CloseButtonProps } from "../CloseButton";
|
|
4
|
+
|
|
5
|
+
jest.mock("@applicaster/zapp-react-native-ui-components/Components", () => {
|
|
6
|
+
const { Image } = require("react-native");
|
|
7
|
+
|
|
8
|
+
return { QBImage: Image };
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const mockGetButtonA11yProps = jest.fn(() => ({
|
|
12
|
+
accessible: true,
|
|
13
|
+
accessibilityLabel: "Close",
|
|
14
|
+
accessibilityHint: "Closes the sheet",
|
|
15
|
+
accessibilityRole: "button",
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock(
|
|
19
|
+
"@applicaster/zapp-react-native-utils/appUtils/accessibilityManager",
|
|
20
|
+
() => ({
|
|
21
|
+
AccessibilityManager: {
|
|
22
|
+
getInstance: () => ({
|
|
23
|
+
getButtonAccessibilityProps: mockGetButtonA11yProps,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const baseProps: CloseButtonProps = {
|
|
30
|
+
enabled: true,
|
|
31
|
+
close: jest.fn(),
|
|
32
|
+
asset: "close.png",
|
|
33
|
+
assetFocused: "close_focused.png",
|
|
34
|
+
height: 24,
|
|
35
|
+
width: 24,
|
|
36
|
+
marginTop: 0,
|
|
37
|
+
marginBottom: 0,
|
|
38
|
+
marginLeft: 0,
|
|
39
|
+
marginRight: 0,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe("ModalComponent Header CloseButton", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.clearAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders as an accessible button reachable by screen readers", () => {
|
|
48
|
+
render(<CloseButton {...baseProps} />);
|
|
49
|
+
|
|
50
|
+
const button = screen.getByTestId("closeButton");
|
|
51
|
+
|
|
52
|
+
expect(button.props.accessible).toBe(true);
|
|
53
|
+
expect(button.props.accessibilityRole).toBe("button");
|
|
54
|
+
expect(button.props.accessibilityLabel).toBe("Close");
|
|
55
|
+
expect(mockGetButtonA11yProps).toHaveBeenCalledWith("close");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("invokes close on press", () => {
|
|
59
|
+
const close = jest.fn();
|
|
60
|
+
render(<CloseButton {...baseProps} close={close} />);
|
|
61
|
+
|
|
62
|
+
fireEvent.press(screen.getByTestId("closeButton"));
|
|
63
|
+
|
|
64
|
+
expect(close).toHaveBeenCalledTimes(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("renders nothing when disabled", () => {
|
|
68
|
+
render(<CloseButton {...baseProps} enabled={false} />);
|
|
69
|
+
|
|
70
|
+
expect(screen.queryByTestId("closeButton")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
});
|