@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1593888345 → 16.0.0-alpha.1701179921
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/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -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/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -3
- 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 +58 -73
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +65 -56
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +140 -65
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +93 -46
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +148 -32
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +113 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +0 -2
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +132 -6
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +202 -13
- package/Components/ModalComponent/BottomSheetModalContent.tsx +187 -73
- package/Components/ModalComponent/SortableList.tsx +36 -22
- package/Components/ModalComponent/TextInputContent.tsx +42 -7
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +111 -6
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +50 -1
- package/Components/ModalComponent/index.tsx +7 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +24 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +1 -1
- package/Components/ModalComponent/showTextInput.ts +11 -2
- package/Components/PlayerContainer/PlayerContainer.tsx +8 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +10 -12
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +138 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- 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/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
- package/Components/Transitioner/Transitioner.tsx +2 -3
- package/Components/Transitioner/__tests__/transitioner.test.js +83 -2
- package/Components/VideoLive/LiveImageManager.ts +8 -2
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/assets/empty-Info.png +0 -0
- package/package.json +5 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -314
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
memo,
|
|
3
|
+
useCallback,
|
|
4
|
+
useDeferredValue,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
2
9
|
import {
|
|
3
10
|
ActivityIndicator,
|
|
4
11
|
ScrollView,
|
|
@@ -26,7 +33,6 @@ import { SortableList } from "./SortableList";
|
|
|
26
33
|
import {
|
|
27
34
|
AudioPlayerButton,
|
|
28
35
|
NowPlayingHeaderSection,
|
|
29
|
-
VARIANT_WITH_ICON,
|
|
30
36
|
} from "./AudioPlayer/Components";
|
|
31
37
|
import { BottomSheetDragArea } from "./BottomSheetDragArea";
|
|
32
38
|
import { ModalBlocksStyleContext } from "./ModalBlocksStyleContext";
|
|
@@ -48,6 +54,10 @@ const styles = StyleSheet.create({
|
|
|
48
54
|
fontSize: 14,
|
|
49
55
|
textAlign: "center",
|
|
50
56
|
},
|
|
57
|
+
scrollView: {
|
|
58
|
+
flexShrink: 1,
|
|
59
|
+
},
|
|
60
|
+
someStyles: { paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 },
|
|
51
61
|
});
|
|
52
62
|
|
|
53
63
|
type ModalComponentProps = {
|
|
@@ -81,9 +91,12 @@ type ModalComponentProps = {
|
|
|
81
91
|
contentComponent?: React.ComponentType<any>;
|
|
82
92
|
contentComponentProps?: Record<string, any>;
|
|
83
93
|
styleOverrides?: ModalBlocksConfig | Record<string, any>;
|
|
94
|
+
measurementPhase?: boolean;
|
|
95
|
+
/** Copy under Next Up when the queue has no upcoming items. */
|
|
96
|
+
emptyMessage?: string;
|
|
84
97
|
};
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
87
100
|
const {
|
|
88
101
|
items,
|
|
89
102
|
currentRoute,
|
|
@@ -103,13 +116,22 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
103
116
|
contentComponent: ContentComponent,
|
|
104
117
|
contentComponentProps = {},
|
|
105
118
|
styleOverrides: propStyleOverrides,
|
|
119
|
+
measurementPhase = false,
|
|
120
|
+
emptyMessage,
|
|
106
121
|
} = props;
|
|
107
122
|
|
|
108
123
|
const route = useRef(currentRoute);
|
|
109
124
|
const theme = useTheme<BaseThemePropertiesMobile>();
|
|
125
|
+
const bottomInset = useSafeAreaInsets().bottom;
|
|
110
126
|
const paddingTop = Number(theme.modal_bottom_sheet_padding_top);
|
|
111
127
|
const paddingBottom = Number(theme.modal_bottom_sheet_padding_bottom);
|
|
112
128
|
|
|
129
|
+
const calculatedcontentContainerStyle = useMemo(() => {
|
|
130
|
+
return {
|
|
131
|
+
paddingBottom: paddingBottom + bottomInset,
|
|
132
|
+
};
|
|
133
|
+
}, [paddingBottom, bottomInset]);
|
|
134
|
+
|
|
113
135
|
const {
|
|
114
136
|
items: fetchedItems,
|
|
115
137
|
showCentralLoading,
|
|
@@ -131,17 +153,81 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
131
153
|
[plugins, themePluginId]
|
|
132
154
|
);
|
|
133
155
|
|
|
134
|
-
const styleOverrides = useMemo(() => {
|
|
135
|
-
if (propStyleOverrides) {
|
|
136
|
-
return propStyleOverrides;
|
|
137
|
-
}
|
|
138
|
-
|
|
156
|
+
const styleOverrides = useMemo<ModalBlocksConfig | null>(() => {
|
|
139
157
|
const configuration =
|
|
140
158
|
plugin?.configuration || (plugin as any)?.configuration_json;
|
|
141
159
|
|
|
142
|
-
|
|
160
|
+
const parsed =
|
|
161
|
+
configuration && themePluginId
|
|
162
|
+
? parseModalBlocksConfiguration(configuration, themePluginId)
|
|
163
|
+
: null;
|
|
164
|
+
|
|
165
|
+
if (!propStyleOverrides) {
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// hydrate(QueueStyles) only emits sectionHeader after the plugin class
|
|
170
|
+
// includes it. Until then, Studio/local keys in configuration_json still
|
|
171
|
+
// apply via the parser.
|
|
172
|
+
const hydratedSection = propStyleOverrides.sectionHeader;
|
|
173
|
+
|
|
174
|
+
const hasTitleStyle = (style?: Record<string, any>) =>
|
|
175
|
+
Boolean(
|
|
176
|
+
style &&
|
|
177
|
+
(style.fontColor ||
|
|
178
|
+
style.fontSize !== undefined ||
|
|
179
|
+
style.lineHeight !== undefined ||
|
|
180
|
+
style.fontFamily ||
|
|
181
|
+
style.iosFontFamily ||
|
|
182
|
+
style.androidFontFamily ||
|
|
183
|
+
style.letterSpacing !== undefined ||
|
|
184
|
+
style.iosLetterSpacing !== undefined ||
|
|
185
|
+
style.androidLetterSpacing !== undefined ||
|
|
186
|
+
style.textTransform ||
|
|
187
|
+
style.alignment)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const hasActionStyle = (style?: Record<string, any>) =>
|
|
191
|
+
Boolean(
|
|
192
|
+
style &&
|
|
193
|
+
(style.backgroundColor ||
|
|
194
|
+
style.fontColor ||
|
|
195
|
+
style.fontSize !== undefined ||
|
|
196
|
+
style.lineHeight !== undefined ||
|
|
197
|
+
style.fontFamily ||
|
|
198
|
+
style.iosFontFamily ||
|
|
199
|
+
style.androidFontFamily ||
|
|
200
|
+
style.letterSpacing !== undefined ||
|
|
201
|
+
style.iosLetterSpacing !== undefined ||
|
|
202
|
+
style.androidLetterSpacing !== undefined ||
|
|
203
|
+
style.textTransform ||
|
|
204
|
+
style.borderRadius !== undefined ||
|
|
205
|
+
style.cornerRadius !== undefined ||
|
|
206
|
+
style.paddingTop !== undefined ||
|
|
207
|
+
style.paddingRight !== undefined ||
|
|
208
|
+
style.paddingBottom !== undefined ||
|
|
209
|
+
style.paddingLeft !== undefined)
|
|
210
|
+
);
|
|
143
211
|
|
|
144
|
-
|
|
212
|
+
const actionNode = hydratedSection?.actionButtons;
|
|
213
|
+
|
|
214
|
+
// An empty `default: {}` is truthy and would hide fields set on the flat
|
|
215
|
+
// object or on the focused bucket.
|
|
216
|
+
const hasHydratedSection =
|
|
217
|
+
hasTitleStyle(hydratedSection?.title?.default) ||
|
|
218
|
+
hasTitleStyle(hydratedSection?.title) ||
|
|
219
|
+
hasActionStyle(actionNode?.default) ||
|
|
220
|
+
hasActionStyle(actionNode?.focused) ||
|
|
221
|
+
hasActionStyle(actionNode?.focus) ||
|
|
222
|
+
hasActionStyle(actionNode);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
...parsed,
|
|
226
|
+
...propStyleOverrides,
|
|
227
|
+
sectionHeader: hasHydratedSection
|
|
228
|
+
? hydratedSection
|
|
229
|
+
: parsed?.sectionHeader,
|
|
230
|
+
};
|
|
145
231
|
}, [propStyleOverrides, plugin, themePluginId]);
|
|
146
232
|
|
|
147
233
|
const pluginLocalizations = useLocalizedStrings({
|
|
@@ -156,14 +242,37 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
156
242
|
? pluginLocalizations?.[`${normalizedPluginId}_header_title`]
|
|
157
243
|
: undefined;
|
|
158
244
|
|
|
159
|
-
const
|
|
160
|
-
pluginLocalizations?.
|
|
245
|
+
const nextUpTitle =
|
|
246
|
+
pluginLocalizations?.next_up_title ||
|
|
247
|
+
(normalizedPluginId
|
|
248
|
+
? pluginLocalizations?.[`${normalizedPluginId}_next_up_title`] ||
|
|
249
|
+
pluginLocalizations?.[`${normalizedPluginId}_label_next_up`]
|
|
250
|
+
: undefined) ||
|
|
251
|
+
"Next Up";
|
|
252
|
+
|
|
253
|
+
const nowPlayingTitle =
|
|
254
|
+
pluginLocalizations?.now_playing_title ||
|
|
255
|
+
(normalizedPluginId
|
|
256
|
+
? pluginLocalizations?.[`${normalizedPluginId}_now_playing_title`] ||
|
|
257
|
+
pluginLocalizations?.[`${normalizedPluginId}_label_now_playing`]
|
|
258
|
+
: undefined) ||
|
|
259
|
+
"Now Playing";
|
|
260
|
+
|
|
261
|
+
const clearLabel =
|
|
262
|
+
pluginLocalizations?.clear_label ||
|
|
263
|
+
(normalizedPluginId
|
|
264
|
+
? pluginLocalizations?.[`${normalizedPluginId}_label_clear`]
|
|
265
|
+
: undefined) ||
|
|
266
|
+
"Clear";
|
|
161
267
|
|
|
162
268
|
const resolvedTitle = props.title || localizedHeaderTitle || title;
|
|
163
269
|
|
|
164
270
|
const itemsToRender = viewModel ? fetchedItems : items;
|
|
165
271
|
const operations = editableCollection?.operations || [];
|
|
166
|
-
|
|
272
|
+
|
|
273
|
+
const isSortable = useDeferredValue(
|
|
274
|
+
!measurementPhase && hasOperation(operations, OPERATIONS.REORDER)
|
|
275
|
+
);
|
|
167
276
|
|
|
168
277
|
const cell = getCell(
|
|
169
278
|
viewModel ? role : undefined,
|
|
@@ -264,11 +373,22 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
264
373
|
]
|
|
265
374
|
);
|
|
266
375
|
|
|
267
|
-
const
|
|
376
|
+
const sortableListRenderItem = useCallback(
|
|
377
|
+
({ item, index, renderHandle }) =>
|
|
378
|
+
cell.renderItem({
|
|
379
|
+
item,
|
|
380
|
+
index,
|
|
381
|
+
onPress: onItemPress,
|
|
382
|
+
context: renderContext,
|
|
383
|
+
renderHandle,
|
|
384
|
+
}),
|
|
385
|
+
[cell, onItemPress, renderContext]
|
|
386
|
+
);
|
|
268
387
|
|
|
269
388
|
const hasNowPlaying = hasOperation(operations, OPERATIONS.NOW_PLAYING);
|
|
270
389
|
const hasClearAll = hasOperation(operations, OPERATIONS.CLEAR_ALL);
|
|
271
390
|
const hasAdd = hasOperation(operations, OPERATIONS.ADD);
|
|
391
|
+
const isQueueEmpty = hasNowPlaying && (itemsToRender?.length ?? 0) === 0;
|
|
272
392
|
|
|
273
393
|
const handleDeleteCollection = useCallback(() => {
|
|
274
394
|
editableCollection?.deleteCollection?.();
|
|
@@ -277,9 +397,9 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
277
397
|
const handleCreatePlaylist = useCallback(() => {
|
|
278
398
|
editableCollection?.add?.({
|
|
279
399
|
id: "create_new_playlist",
|
|
280
|
-
title:
|
|
400
|
+
title: "unknown title",
|
|
281
401
|
});
|
|
282
|
-
}, [editableCollection
|
|
402
|
+
}, [editableCollection]);
|
|
283
403
|
|
|
284
404
|
const headers = useMemo(() => {
|
|
285
405
|
const list: Array<{ index: number; component: () => React.ReactElement }> =
|
|
@@ -291,6 +411,11 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
291
411
|
component: () => (
|
|
292
412
|
<NowPlayingHeaderSection
|
|
293
413
|
onClear={hasClearAll ? handleDeleteCollection : undefined}
|
|
414
|
+
nextUpTitle={nextUpTitle}
|
|
415
|
+
nowPlayingTitle={nowPlayingTitle}
|
|
416
|
+
isEmpty={isQueueEmpty}
|
|
417
|
+
emptyMessage={emptyMessage}
|
|
418
|
+
clearLabel={clearLabel}
|
|
294
419
|
/>
|
|
295
420
|
),
|
|
296
421
|
});
|
|
@@ -303,11 +428,7 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
303
428
|
<View
|
|
304
429
|
style={{ paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 }}
|
|
305
430
|
>
|
|
306
|
-
<AudioPlayerButton
|
|
307
|
-
configuration={VARIANT_WITH_ICON}
|
|
308
|
-
data={{ title: createPlaylistTitle }}
|
|
309
|
-
onPress={handleCreatePlaylist}
|
|
310
|
-
/>
|
|
431
|
+
<AudioPlayerButton onPress={handleCreatePlaylist} />
|
|
311
432
|
</View>
|
|
312
433
|
),
|
|
313
434
|
});
|
|
@@ -318,11 +439,34 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
318
439
|
hasNowPlaying,
|
|
319
440
|
hasClearAll,
|
|
320
441
|
handleDeleteCollection,
|
|
442
|
+
nextUpTitle,
|
|
443
|
+
nowPlayingTitle,
|
|
444
|
+
isQueueEmpty,
|
|
445
|
+
emptyMessage,
|
|
446
|
+
clearLabel,
|
|
321
447
|
hasAdd,
|
|
322
448
|
handleCreatePlaylist,
|
|
323
449
|
]);
|
|
324
450
|
|
|
325
|
-
const
|
|
451
|
+
const _items = useMemo(() => {
|
|
452
|
+
return (itemsToRender || []).map((item, index) =>
|
|
453
|
+
cell.renderItem({
|
|
454
|
+
item,
|
|
455
|
+
index,
|
|
456
|
+
onPress: onItemPress,
|
|
457
|
+
context: renderContext,
|
|
458
|
+
})
|
|
459
|
+
);
|
|
460
|
+
}, [itemsToRender, cell, onItemPress, renderContext]);
|
|
461
|
+
|
|
462
|
+
const _styles = useMemo(() => {
|
|
463
|
+
return {
|
|
464
|
+
maxHeight: props.maxHeight,
|
|
465
|
+
paddingTop: paddingTop,
|
|
466
|
+
};
|
|
467
|
+
}, [props.maxHeight, paddingTop]);
|
|
468
|
+
|
|
469
|
+
const renderedBody = (() => {
|
|
326
470
|
if (ContentComponent) {
|
|
327
471
|
const shouldWrapInScrollView =
|
|
328
472
|
!contentComponentProps?.disableScrollViewWrap &&
|
|
@@ -331,9 +475,8 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
331
475
|
if (shouldWrapInScrollView) {
|
|
332
476
|
return (
|
|
333
477
|
<ScrollView
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}}
|
|
478
|
+
keyboardShouldPersistTaps="handled"
|
|
479
|
+
contentContainerStyle={calculatedcontentContainerStyle}
|
|
337
480
|
>
|
|
338
481
|
<ContentComponent
|
|
339
482
|
{...contentComponentProps}
|
|
@@ -346,12 +489,7 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
346
489
|
}
|
|
347
490
|
|
|
348
491
|
return (
|
|
349
|
-
<View
|
|
350
|
-
style={{
|
|
351
|
-
flexShrink: 1,
|
|
352
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
353
|
-
}}
|
|
354
|
-
>
|
|
492
|
+
<View style={[styles.scrollView, calculatedcontentContainerStyle]}>
|
|
355
493
|
<ContentComponent
|
|
356
494
|
{...contentComponentProps}
|
|
357
495
|
dismiss={dismiss}
|
|
@@ -395,70 +533,44 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
395
533
|
<SortableList
|
|
396
534
|
sortableData={itemsToRender}
|
|
397
535
|
headers={headers}
|
|
398
|
-
scrollViewStyle={
|
|
399
|
-
|
|
400
|
-
}}
|
|
401
|
-
contentContainerStyle={{
|
|
402
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
403
|
-
}}
|
|
536
|
+
scrollViewStyle={styles.scrollView}
|
|
537
|
+
contentContainerStyle={calculatedcontentContainerStyle}
|
|
404
538
|
onDragEnd={handleDragEnd}
|
|
405
|
-
renderItem={
|
|
406
|
-
cell.renderItem({
|
|
407
|
-
item,
|
|
408
|
-
index,
|
|
409
|
-
onPress: onItemPress,
|
|
410
|
-
context: renderContext,
|
|
411
|
-
renderHandle,
|
|
412
|
-
})
|
|
413
|
-
}
|
|
539
|
+
renderItem={sortableListRenderItem}
|
|
414
540
|
/>
|
|
415
541
|
);
|
|
416
542
|
}
|
|
417
543
|
|
|
418
544
|
return (
|
|
419
545
|
<ScrollView
|
|
546
|
+
keyboardShouldPersistTaps="handled"
|
|
420
547
|
overScrollMode="never"
|
|
421
548
|
bounces={false}
|
|
422
|
-
contentContainerStyle={
|
|
423
|
-
paddingBottom: paddingBottom + bottomInset,
|
|
424
|
-
}}
|
|
549
|
+
contentContainerStyle={calculatedcontentContainerStyle}
|
|
425
550
|
>
|
|
426
551
|
{hasNowPlaying ? (
|
|
427
552
|
<NowPlayingHeaderSection
|
|
428
553
|
onClear={hasClearAll ? handleDeleteCollection : undefined}
|
|
554
|
+
nextUpTitle={nextUpTitle}
|
|
555
|
+
nowPlayingTitle={nowPlayingTitle}
|
|
556
|
+
isEmpty={isQueueEmpty}
|
|
557
|
+
emptyMessage={emptyMessage}
|
|
558
|
+
clearLabel={clearLabel}
|
|
429
559
|
/>
|
|
430
560
|
) : null}
|
|
431
561
|
{hasAdd ? (
|
|
432
|
-
<View
|
|
433
|
-
|
|
434
|
-
>
|
|
435
|
-
<AudioPlayerButton
|
|
436
|
-
configuration={VARIANT_WITH_ICON}
|
|
437
|
-
data={{ title: createPlaylistTitle }}
|
|
438
|
-
onPress={handleCreatePlaylist}
|
|
439
|
-
/>
|
|
562
|
+
<View style={styles.someStyles}>
|
|
563
|
+
<AudioPlayerButton onPress={handleCreatePlaylist} />
|
|
440
564
|
</View>
|
|
441
565
|
) : null}
|
|
442
|
-
{
|
|
443
|
-
cell.renderItem({
|
|
444
|
-
item,
|
|
445
|
-
index,
|
|
446
|
-
onPress: onItemPress,
|
|
447
|
-
context: renderContext,
|
|
448
|
-
})
|
|
449
|
-
)}
|
|
566
|
+
{_items}
|
|
450
567
|
</ScrollView>
|
|
451
568
|
);
|
|
452
|
-
};
|
|
569
|
+
})();
|
|
453
570
|
|
|
454
571
|
return (
|
|
455
572
|
<ModalBlocksStyleContext.Provider value={styleOverrides as any}>
|
|
456
|
-
<View
|
|
457
|
-
style={{
|
|
458
|
-
maxHeight: props.maxHeight,
|
|
459
|
-
paddingTop: paddingTop,
|
|
460
|
-
}}
|
|
461
|
-
>
|
|
573
|
+
<View style={_styles}>
|
|
462
574
|
<BottomSheetDragArea>
|
|
463
575
|
<Header
|
|
464
576
|
dismiss={dismiss}
|
|
@@ -467,8 +579,10 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
467
579
|
title={resolvedTitle}
|
|
468
580
|
/>
|
|
469
581
|
</BottomSheetDragArea>
|
|
470
|
-
{
|
|
582
|
+
{renderedBody}
|
|
471
583
|
</View>
|
|
472
584
|
</ModalBlocksStyleContext.Provider>
|
|
473
585
|
);
|
|
474
586
|
}
|
|
587
|
+
|
|
588
|
+
export const BottomSheetModalContent = memo(_BottomSheetModalContent);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import Sortable from "react-native-sortables";
|
|
3
3
|
import { Text, View, ViewStyle } from "react-native";
|
|
4
4
|
import Animated, { useAnimatedRef } from "react-native-reanimated";
|
|
@@ -65,6 +65,9 @@ export type SortableListProps = {
|
|
|
65
65
|
}) => void;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
const defaultKeyExtractor = (item, index) =>
|
|
69
|
+
`sortable-item-${item?.id ?? item?.title ?? index}`;
|
|
70
|
+
|
|
68
71
|
export const SortableList = ({
|
|
69
72
|
scrollViewStyle,
|
|
70
73
|
contentContainerStyle,
|
|
@@ -73,8 +76,7 @@ export const SortableList = ({
|
|
|
73
76
|
columns = 1,
|
|
74
77
|
rowGap = 0,
|
|
75
78
|
columnGap = 0,
|
|
76
|
-
keyExtractor =
|
|
77
|
-
`sortable-item-${item?.id ?? item?.title ?? index}`,
|
|
79
|
+
keyExtractor = defaultKeyExtractor,
|
|
78
80
|
renderItem,
|
|
79
81
|
sortableData = DEFAULT_SORTABLE_DATA,
|
|
80
82
|
headers = DEFAULT_HEADERS,
|
|
@@ -101,13 +103,24 @@ export const SortableList = ({
|
|
|
101
103
|
[onDragEnd]
|
|
102
104
|
);
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
const _renderItem = useCallback(
|
|
107
|
+
({ item, index }) => (
|
|
108
|
+
<View key={keyExtractor(item, index)} style={itemStyle}>
|
|
109
|
+
{renderItem({
|
|
110
|
+
item,
|
|
111
|
+
index,
|
|
112
|
+
renderHandle: (children) => (
|
|
113
|
+
<Sortable.Handle>{children}</Sortable.Handle>
|
|
114
|
+
),
|
|
115
|
+
})}
|
|
116
|
+
</View>
|
|
117
|
+
),
|
|
118
|
+
[itemStyle, keyExtractor, renderItem]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const renderHeader = useMemo(
|
|
122
|
+
() =>
|
|
123
|
+
(headers || []).map((header, idx) => (
|
|
111
124
|
<View key={`static-header-${idx}`} style={headerStyle}>
|
|
112
125
|
{header.title ? (
|
|
113
126
|
<SortableListHeader title={header.title} />
|
|
@@ -115,8 +128,19 @@ export const SortableList = ({
|
|
|
115
128
|
renderHeaderComponent(header.component)
|
|
116
129
|
)}
|
|
117
130
|
</View>
|
|
118
|
-
))
|
|
131
|
+
)),
|
|
132
|
+
[headers, headerStyle]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Animated.ScrollView
|
|
137
|
+
ref={scrollableRef}
|
|
138
|
+
style={scrollViewStyle}
|
|
139
|
+
contentContainerStyle={contentContainerStyle}
|
|
140
|
+
>
|
|
141
|
+
{renderHeader}
|
|
119
142
|
<Sortable.Grid
|
|
143
|
+
key={`sortable-grid-${localData.length}`}
|
|
120
144
|
scrollableRef={scrollableRef}
|
|
121
145
|
customHandle
|
|
122
146
|
columns={columns}
|
|
@@ -125,17 +149,7 @@ export const SortableList = ({
|
|
|
125
149
|
data={localData}
|
|
126
150
|
keyExtractor={keyExtractor}
|
|
127
151
|
onDragEnd={handleDragEnd}
|
|
128
|
-
renderItem={
|
|
129
|
-
<View key={keyExtractor(item, index)} style={itemStyle}>
|
|
130
|
-
{renderItem({
|
|
131
|
-
item,
|
|
132
|
-
index,
|
|
133
|
-
renderHandle: (children) => (
|
|
134
|
-
<Sortable.Handle>{children}</Sortable.Handle>
|
|
135
|
-
),
|
|
136
|
-
})}
|
|
137
|
-
</View>
|
|
138
|
-
)}
|
|
152
|
+
renderItem={_renderItem}
|
|
139
153
|
/>
|
|
140
154
|
</Animated.ScrollView>
|
|
141
155
|
);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AudioPlayerButton,
|
|
10
10
|
VARIANT_TEXT_ONLY,
|
|
11
11
|
} from "./AudioPlayer/Components/Button";
|
|
12
|
+
import { logger } from "@applicaster/zapp-react-native-utils/logger";
|
|
12
13
|
|
|
13
14
|
export type TextInputContentProps = {
|
|
14
15
|
/** Injected by the sheet host; unused for layout (header is owned by ModalComponent). */
|
|
@@ -19,16 +20,25 @@ export type TextInputContentProps = {
|
|
|
19
20
|
inputLabel?: string;
|
|
20
21
|
defaultValue?: string;
|
|
21
22
|
buttonLabel: string;
|
|
23
|
+
validationLabel?: string;
|
|
24
|
+
maxLength?: number;
|
|
22
25
|
actions: ActionType[];
|
|
26
|
+
actionContext?: Record<string, any>;
|
|
23
27
|
};
|
|
24
28
|
|
|
29
|
+
// Spacing mirrors the `component_style_*` keys in text input component.
|
|
25
30
|
const styles = StyleSheet.create({
|
|
26
31
|
container: {
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
backgroundColor: "transparent", // component_style_background_color
|
|
33
|
+
paddingLeft: 40, // component_style_padding_left
|
|
34
|
+
paddingRight: 40, // component_style_padding_right
|
|
35
|
+
paddingTop: 0, // component_style_padding_top
|
|
36
|
+
paddingBottom: 0, // component_style_padding_bottom
|
|
37
|
+
marginTop: 20, // component_style_margin_top
|
|
38
|
+
marginBottom: 20, // component_style_margin_bottom
|
|
29
39
|
},
|
|
30
40
|
inputWrap: {
|
|
31
|
-
|
|
41
|
+
// Gap down to the submit button; owned by the modal, not the reference.
|
|
32
42
|
marginBottom: 24,
|
|
33
43
|
},
|
|
34
44
|
});
|
|
@@ -38,11 +48,30 @@ const styles = StyleSheet.create({
|
|
|
38
48
|
* Standard dismiss header comes from BottomSheetModalContent / ModalHeader.
|
|
39
49
|
*/
|
|
40
50
|
export function TextInputContent(props: TextInputContentProps) {
|
|
41
|
-
const {
|
|
51
|
+
const {
|
|
52
|
+
inputLabel,
|
|
53
|
+
defaultValue = "",
|
|
54
|
+
buttonLabel,
|
|
55
|
+
actions,
|
|
56
|
+
actionContext,
|
|
57
|
+
maxLength = 255,
|
|
58
|
+
validationLabel,
|
|
59
|
+
} = props;
|
|
42
60
|
|
|
43
61
|
const [textValue, setTextValue] = useState(defaultValue);
|
|
44
62
|
const [submitting, setSubmitting] = useState(false);
|
|
45
63
|
|
|
64
|
+
const validationMessage =
|
|
65
|
+
textValue.length > maxLength
|
|
66
|
+
? validationLabel || "Too many characters"
|
|
67
|
+
: undefined;
|
|
68
|
+
|
|
69
|
+
if (!validationLabel) {
|
|
70
|
+
logger.warn(
|
|
71
|
+
"Modal/TextInputContent: Validation label (validationLabel) is not provided, using default message 'Too many characters'."
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
46
75
|
const onSubmit = useCallback(async () => {
|
|
47
76
|
if (submitting) {
|
|
48
77
|
return;
|
|
@@ -68,12 +97,17 @@ export function TextInputContent(props: TextInputContentProps) {
|
|
|
68
97
|
return item;
|
|
69
98
|
});
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
if (actionContext) {
|
|
101
|
+
await actionExecutor.handleActions(updatedActions, actionContext);
|
|
102
|
+
} else {
|
|
103
|
+
await actionExecutor.handleActions(updatedActions);
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
await actionExecutor.handleAction({ type: "dismissBottomSheet" });
|
|
73
107
|
} finally {
|
|
74
108
|
setSubmitting(false);
|
|
75
109
|
}
|
|
76
|
-
}, [submitting, actions, textValue]);
|
|
110
|
+
}, [submitting, actions, textValue, actionContext]);
|
|
77
111
|
|
|
78
112
|
return (
|
|
79
113
|
<View style={styles.container}>
|
|
@@ -85,6 +119,7 @@ export function TextInputContent(props: TextInputContentProps) {
|
|
|
85
119
|
label: inputLabel,
|
|
86
120
|
}}
|
|
87
121
|
value={textValue}
|
|
122
|
+
validationMessage={validationMessage}
|
|
88
123
|
onChangeText={setTextValue}
|
|
89
124
|
onClearPress={() => setTextValue("")}
|
|
90
125
|
/>
|
|
@@ -93,7 +128,7 @@ export function TextInputContent(props: TextInputContentProps) {
|
|
|
93
128
|
configuration={VARIANT_TEXT_ONLY}
|
|
94
129
|
data={{ title: buttonLabel }}
|
|
95
130
|
onPress={onSubmit}
|
|
96
|
-
disabled={submitting}
|
|
131
|
+
disabled={!!validationMessage || submitting}
|
|
97
132
|
/>
|
|
98
133
|
</View>
|
|
99
134
|
);
|