@applicaster/zapp-react-native-ui-components 16.0.0-rc.80 → 16.0.0-rc.82

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.
@@ -81,6 +81,8 @@ export type LeadingIconConfiguration = {
81
81
  enabled: boolean;
82
82
  /** Decides which icon will be displayed when enabled */
83
83
  actionType?: ButtonActionType;
84
+ /** Optional Studio / modal-blocks asset URI (overrides actionType mapping) */
85
+ asset?: string;
84
86
  };
85
87
 
86
88
  export type IconsConfiguration = {
@@ -170,7 +172,9 @@ function getTextTransformStyle(
170
172
  }
171
173
 
172
174
  function shouldShowLeadingIcon(leadingIcon: LeadingIconConfiguration) {
173
- return leadingIcon.enabled && !!leadingIcon.actionType;
175
+ return (
176
+ leadingIcon.enabled && (!!leadingIcon.actionType || !!leadingIcon.asset)
177
+ );
174
178
  }
175
179
 
176
180
  // ---------------------------------------------------------------------------
@@ -268,7 +272,7 @@ function ButtonTitle({
268
272
  }
269
273
 
270
274
  type ButtonLeadingIconProps = {
271
- actionType: ButtonActionType;
275
+ actionType?: ButtonActionType;
272
276
  configuration: IconsConfiguration;
273
277
  };
274
278
 
@@ -276,9 +280,17 @@ function ButtonLeadingIcon({
276
280
  actionType,
277
281
  configuration,
278
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
+
279
291
  return (
280
292
  <Image
281
- source={{ uri: ACTION_ICON_ASSETS[actionType] }}
293
+ source={{ uri: asset }}
282
294
  style={{
283
295
  width: configuration.width,
284
296
  height: configuration.height,
@@ -56,6 +56,10 @@ export type BackgroundConfiguration = {
56
56
  defaultColor: string;
57
57
  focusedColor: string;
58
58
  cornerRadius: number;
59
+ paddingTop?: number;
60
+ paddingRight?: number;
61
+ paddingBottom?: number;
62
+ paddingLeft?: number;
59
63
  };
60
64
 
61
65
  export type ImageConfiguration = {
@@ -82,6 +86,20 @@ export type NowPlayingAssetConfiguration = {
82
86
  asset?: string;
83
87
  };
84
88
 
89
+ export type CheckboxAssetsConfiguration = {
90
+ /** Unchecked multiSelect control (item.checkbox_asset). */
91
+ asset?: string;
92
+ /** Checked multiSelect control (item.checked_checkbox_asset). */
93
+ checkedAsset?: string;
94
+ };
95
+
96
+ export type ActionButtonAssetsConfiguration = {
97
+ /** Leading remove control on Edit Order rows. */
98
+ removeAsset?: string;
99
+ /** Trailing drag handle on Edit Order / queue rows. */
100
+ dragAsset?: string;
101
+ };
102
+
85
103
  export type ItemConfiguration = {
86
104
  background: BackgroundConfiguration;
87
105
  image: ImageConfiguration;
@@ -89,6 +107,8 @@ export type ItemConfiguration = {
89
107
  nowPlayingLabel: LabelConfiguration;
90
108
  textLabel1: LabelConfiguration;
91
109
  textLabel2: LabelConfiguration;
110
+ checkboxAssets?: CheckboxAssetsConfiguration;
111
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
92
112
  };
93
113
 
94
114
  export const DEFAULT_TEXT_LABEL_1_CONFIGURATION: LabelConfiguration = {
@@ -301,10 +321,13 @@ export function mapMenuItemToPlaylistData(
301
321
  const showSelectionWidget =
302
322
  behavior?.selectMode !== "none" && item.trailingButton !== "none";
303
323
 
324
+ const summary =
325
+ typeof item.summary === "string" ? item.summary.trim() : item.summary;
326
+
304
327
  return {
305
328
  imageUri: item.icon,
306
329
  textLabel1: item.title,
307
- textLabel2: item.summary ?? "",
330
+ textLabel2: summary || "",
308
331
  trailingButton: showSelectionWidget
309
332
  ? item.trailingButton || ("multiSelect" as ItemButtonAction)
310
333
  : undefined,
@@ -340,10 +363,10 @@ function ItemContainer({
340
363
  style={({ pressed }) => ({
341
364
  backgroundColor: pressed ? background.focusedColor : bg,
342
365
  borderRadius: background.cornerRadius,
343
- paddingTop: container.paddingTop,
344
- paddingRight: container.paddingRight,
345
- paddingBottom: container.paddingBottom,
346
- paddingLeft: container.paddingLeft,
366
+ paddingTop: background.paddingTop ?? container.paddingTop,
367
+ paddingRight: background.paddingRight ?? container.paddingRight,
368
+ paddingBottom: background.paddingBottom ?? container.paddingBottom,
369
+ paddingLeft: background.paddingLeft ?? container.paddingLeft,
347
370
  })}
348
371
  >
349
372
  {children}
@@ -450,6 +473,8 @@ type ItemButtonProps = {
450
473
  focused?: boolean;
451
474
  onPress?: () => void;
452
475
  isSelected?: boolean;
476
+ checkboxAssets?: CheckboxAssetsConfiguration;
477
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
453
478
  };
454
479
 
455
480
  function ItemButton({
@@ -457,6 +482,8 @@ function ItemButton({
457
482
  focused = false,
458
483
  onPress,
459
484
  isSelected = false,
485
+ checkboxAssets,
486
+ actionButtonAssets,
460
487
  }: ItemButtonProps) {
461
488
  const layout = BUTTON_LAYOUT_SPEC[action];
462
489
 
@@ -531,6 +558,19 @@ function ItemButton({
531
558
  }
532
559
 
533
560
  case "drag": {
561
+ if (actionButtonAssets?.dragAsset) {
562
+ return (
563
+ <Image
564
+ source={{ uri: actionButtonAssets.dragAsset }}
565
+ style={{
566
+ width: layout.assetWidth,
567
+ height: layout.assetHeight,
568
+ }}
569
+ resizeMode="contain"
570
+ />
571
+ );
572
+ }
573
+
534
574
  return (
535
575
  <View
536
576
  style={{
@@ -565,6 +605,19 @@ function ItemButton({
565
605
  }
566
606
 
567
607
  case "remove": {
608
+ if (actionButtonAssets?.removeAsset) {
609
+ return (
610
+ <Image
611
+ source={{ uri: actionButtonAssets.removeAsset }}
612
+ style={{
613
+ width: layout.assetWidth,
614
+ height: layout.assetHeight,
615
+ }}
616
+ resizeMode="contain"
617
+ />
618
+ );
619
+ }
620
+
568
621
  return (
569
622
  <View
570
623
  style={{
@@ -590,6 +643,23 @@ function ItemButton({
590
643
  }
591
644
 
592
645
  case "multiSelect": {
646
+ const studioAsset = isSelected
647
+ ? checkboxAssets?.checkedAsset
648
+ : checkboxAssets?.asset;
649
+
650
+ if (studioAsset) {
651
+ return (
652
+ <Image
653
+ source={{ uri: studioAsset }}
654
+ style={{
655
+ width: layout.assetWidth,
656
+ height: layout.assetHeight,
657
+ }}
658
+ resizeMode="contain"
659
+ />
660
+ );
661
+ }
662
+
593
663
  if (!isSelected) {
594
664
  return (
595
665
  <View
@@ -666,6 +736,8 @@ type ItemButtonsProps = {
666
736
  onTrailingButtonPress?: () => void;
667
737
  isSelected?: boolean;
668
738
  renderHandle?: (element: React.ReactElement) => React.ReactElement;
739
+ checkboxAssets?: CheckboxAssetsConfiguration;
740
+ actionButtonAssets?: ActionButtonAssetsConfiguration;
669
741
  };
670
742
 
671
743
  function ItemButtons({
@@ -677,6 +749,8 @@ function ItemButtons({
677
749
  onTrailingButtonPress,
678
750
  isSelected = false,
679
751
  renderHandle,
752
+ checkboxAssets,
753
+ actionButtonAssets,
680
754
  }: ItemButtonsProps) {
681
755
  if (!leadingButton && !trailingButton) {
682
756
  return null;
@@ -691,6 +765,8 @@ function ItemButtons({
691
765
  focused={focusedLeadingButton}
692
766
  onPress={onLeadingButtonPress}
693
767
  isSelected={isSelected}
768
+ checkboxAssets={checkboxAssets}
769
+ actionButtonAssets={actionButtonAssets}
694
770
  />
695
771
  <View style={{ width: BUTTONS_CONTAINER_HORIZONTAL_GUTTER }} />
696
772
  </>
@@ -704,6 +780,8 @@ function ItemButtons({
704
780
  focused={focusedTrailingButton}
705
781
  onPress={onTrailingButtonPress}
706
782
  isSelected={isSelected}
783
+ checkboxAssets={checkboxAssets}
784
+ actionButtonAssets={actionButtonAssets}
707
785
  />
708
786
  )
709
787
  ) : (
@@ -712,6 +790,8 @@ function ItemButtons({
712
790
  focused={focusedTrailingButton}
713
791
  onPress={onTrailingButtonPress}
714
792
  isSelected={isSelected}
793
+ checkboxAssets={checkboxAssets}
794
+ actionButtonAssets={actionButtonAssets}
715
795
  />
716
796
  )
717
797
  ) : (
@@ -720,6 +800,8 @@ function ItemButtons({
720
800
  focused={focusedTrailingButton}
721
801
  onPress={onTrailingButtonPress}
722
802
  isSelected={isSelected}
803
+ checkboxAssets={checkboxAssets}
804
+ actionButtonAssets={actionButtonAssets}
723
805
  />
724
806
  )
725
807
  ) : null}
@@ -761,6 +843,8 @@ export function AudioPlayerTrackItem({
761
843
  textLabel2,
762
844
  nowPlayingLabel,
763
845
  nowPlayingAsset,
846
+ checkboxAssets,
847
+ actionButtonAssets,
764
848
  } = effectiveConfig;
765
849
 
766
850
  const container = resolveContainerSpec(image);
@@ -803,6 +887,8 @@ export function AudioPlayerTrackItem({
803
887
  onTrailingButtonPress={onTrailingButtonPress}
804
888
  isSelected={data.isSelected}
805
889
  renderHandle={renderHandle}
890
+ checkboxAssets={checkboxAssets}
891
+ actionButtonAssets={actionButtonAssets}
806
892
  />
807
893
  </View>
808
894
  </ItemContainer>
@@ -0,0 +1,230 @@
1
+ import {
2
+ mergeItemConfiguration,
3
+ mergeButtonConfiguration,
4
+ } from "../mergeStyles";
5
+
6
+ const base = {
7
+ background: {
8
+ defaultColor: "transparent",
9
+ focusedColor: "rgba(62, 62, 62, 1)",
10
+ cornerRadius: 12,
11
+ },
12
+ image: {
13
+ width: 48,
14
+ height: 48,
15
+ borderWidth: 1,
16
+ borderColor: "rgba(255, 255, 255, 0.1)",
17
+ cornerRadius: 6,
18
+ },
19
+ nowPlayingAsset: {},
20
+ nowPlayingLabel: { fontColor: "#fff", fontSize: 12 },
21
+ textLabel1: { fontColor: "#fff", fontSize: 15 },
22
+ textLabel2: { fontColor: "#999", fontSize: 13 },
23
+ };
24
+
25
+ describe("mergeItemConfiguration background", () => {
26
+ it("applies Studio focus background to focusedColor", () => {
27
+ const merged = mergeItemConfiguration(base as any, {
28
+ background: {
29
+ default: { backgroundColor: "transparent" },
30
+ focused: { backgroundColor: "#FF3B30" },
31
+ },
32
+ });
33
+
34
+ expect(merged.background.defaultColor).toBe("transparent");
35
+ expect(merged.background.focusedColor).toBe("#FF3B30");
36
+ });
37
+
38
+ it("accepts focus alias bucket from parser output", () => {
39
+ const merged = mergeItemConfiguration(base as any, {
40
+ background: {
41
+ default: { backgroundColor: "transparent" },
42
+ focus: { backgroundColor: "#00FF66" },
43
+ },
44
+ });
45
+
46
+ expect(merged.background.focusedColor).toBe("#00FF66");
47
+ });
48
+
49
+ it("merges text label lineHeight from Studio keys", () => {
50
+ const merged = mergeItemConfiguration(base as any, {
51
+ textLabel1: {
52
+ default: { fontSize: 28, lineHeight: 40 },
53
+ },
54
+ });
55
+
56
+ expect(merged.textLabel1.fontSize).toBe(28);
57
+ expect(merged.textLabel1.lineHeight).toBe(40);
58
+ });
59
+
60
+ it("merges background padding from Studio keys", () => {
61
+ const merged = mergeItemConfiguration(base as any, {
62
+ background: {
63
+ default: {
64
+ backgroundColor: "#2E7D32",
65
+ paddingTop: 32,
66
+ paddingBottom: 32,
67
+ paddingLeft: 40,
68
+ paddingRight: 40,
69
+ },
70
+ },
71
+ });
72
+
73
+ expect(merged.background.paddingTop).toBe(32);
74
+ expect(merged.background.paddingBottom).toBe(32);
75
+ expect(merged.background.paddingLeft).toBe(40);
76
+ expect(merged.background.paddingRight).toBe(40);
77
+ // Default-only override must not steal the base focused highlight color.
78
+ expect(merged.background.focusedColor).toBe("rgba(62, 62, 62, 1)");
79
+ });
80
+
81
+ it("preserves base focusedColor when only default background is set", () => {
82
+ const merged = mergeItemConfiguration(base as any, {
83
+ background: {
84
+ default: { backgroundColor: "#2E7D32" },
85
+ },
86
+ });
87
+
88
+ expect(merged.background.defaultColor).toBe("#2E7D32");
89
+ expect(merged.background.focusedColor).toBe("rgba(62, 62, 62, 1)");
90
+ });
91
+
92
+ it("applies Studio checkbox assets onto checkboxAssets", () => {
93
+ const merged = mergeItemConfiguration(base as any, {
94
+ checkbox: {
95
+ default: {
96
+ asset: "https://placehold.co/24x24/FF3B30/FFFFFF/png?text=CB",
97
+ checkedAsset: "https://placehold.co/24x24/FFD60A/000000/png?text=CK",
98
+ },
99
+ },
100
+ });
101
+
102
+ expect(merged.checkboxAssets?.asset).toBe(
103
+ "https://placehold.co/24x24/FF3B30/FFFFFF/png?text=CB"
104
+ );
105
+
106
+ expect(merged.checkboxAssets?.checkedAsset).toBe(
107
+ "https://placehold.co/24x24/FFD60A/000000/png?text=CK"
108
+ );
109
+ });
110
+
111
+ it("applies Studio reorder/remove assets onto actionButtonAssets", () => {
112
+ const merged = mergeItemConfiguration(base as any, {
113
+ actionButtons: {
114
+ default: {
115
+ removeAsset: "https://placehold.co/24x24/00FF00/000000/png?text=RM",
116
+ dragAsset: "https://placehold.co/24x24/FF3B30/FFFFFF/png?text=RH",
117
+ },
118
+ },
119
+ });
120
+
121
+ expect(merged.actionButtonAssets?.removeAsset).toBe(
122
+ "https://placehold.co/24x24/00FF00/000000/png?text=RM"
123
+ );
124
+
125
+ expect(merged.actionButtonAssets?.dragAsset).toBe(
126
+ "https://placehold.co/24x24/FF3B30/FFFFFF/png?text=RH"
127
+ );
128
+ });
129
+ });
130
+
131
+ describe("mergeButtonConfiguration background", () => {
132
+ const buttonBase = {
133
+ background: {
134
+ defaultColor: "#FFFFFF",
135
+ focusedColor: "#E0E0E0",
136
+ inactiveColor: "#CCCCCC",
137
+ cornerRadius: 12,
138
+ paddingTop: 12,
139
+ paddingRight: 12,
140
+ paddingBottom: 12,
141
+ paddingLeft: 12,
142
+ horizontalGutter: 8,
143
+ },
144
+ title: {
145
+ fontColor: "#000000",
146
+ focusedFontColor: "#000000",
147
+ inactiveFontColor: "#666666",
148
+ iosFontFamily: "SFProText-Semibold",
149
+ androidFontFamily: "Roboto-Medium",
150
+ fontSize: 15,
151
+ lineHeight: 24,
152
+ iosLetterSpacing: 0,
153
+ androidLetterSpacing: 0,
154
+ textTransform: "default" as const,
155
+ },
156
+ icons: {
157
+ width: 24,
158
+ height: 24,
159
+ leadingIcon: { enabled: true },
160
+ },
161
+ };
162
+
163
+ it("applies Studio button focus background to focusedColor", () => {
164
+ const merged = mergeButtonConfiguration(buttonBase as any, {
165
+ background: {
166
+ default: { backgroundColor: "#FFFFFF" },
167
+ focused: { backgroundColor: "#00FF00" },
168
+ },
169
+ });
170
+
171
+ expect(merged.background.defaultColor).toBe("#FFFFFF");
172
+ expect(merged.background.focusedColor).toBe("#00FF00");
173
+ });
174
+
175
+ it("applies Studio button title lineHeight", () => {
176
+ const merged = mergeButtonConfiguration(buttonBase as any, {
177
+ title: {
178
+ default: { fontColor: "#000000", fontSize: 15, lineHeight: 40 },
179
+ },
180
+ });
181
+
182
+ expect(merged.title.lineHeight).toBe(40);
183
+ expect(merged.title.fontSize).toBe(15);
184
+ });
185
+
186
+ it("applies Studio button background padding", () => {
187
+ const merged = mergeButtonConfiguration(buttonBase as any, {
188
+ background: {
189
+ default: {
190
+ backgroundColor: "#FFFFFF",
191
+ paddingTop: 32,
192
+ paddingRight: 20,
193
+ paddingBottom: 8,
194
+ paddingLeft: 4,
195
+ },
196
+ },
197
+ });
198
+
199
+ expect(merged.background.paddingTop).toBe(32);
200
+ expect(merged.background.paddingRight).toBe(20);
201
+ expect(merged.background.paddingBottom).toBe(8);
202
+ expect(merged.background.paddingLeft).toBe(4);
203
+ expect(merged.background.focusedColor).toBe("#E0E0E0");
204
+ });
205
+
206
+ it("preserves base button focusedColor when only default background is set", () => {
207
+ const merged = mergeButtonConfiguration(buttonBase as any, {
208
+ background: {
209
+ default: { backgroundColor: "#ABCDEF" },
210
+ },
211
+ });
212
+
213
+ expect(merged.background.defaultColor).toBe("#ABCDEF");
214
+ expect(merged.background.focusedColor).toBe("#E0E0E0");
215
+ });
216
+
217
+ it("applies Studio create-new leading icon asset", () => {
218
+ const merged = mergeButtonConfiguration(buttonBase as any, {
219
+ leadingIcon: {
220
+ default: {
221
+ asset: "https://placehold.co/24x24/FF00FF/FFFFFF/png?text=CN",
222
+ },
223
+ },
224
+ });
225
+
226
+ expect(merged.icons.leadingIcon.asset).toBe(
227
+ "https://placehold.co/24x24/FF00FF/FFFFFF/png?text=CN"
228
+ );
229
+ });
230
+ });
@@ -3,6 +3,8 @@ import {
3
3
  ImageConfiguration as ItemImageConfiguration,
4
4
  LabelConfiguration as ItemLabelConfiguration,
5
5
  NowPlayingAssetConfiguration as ItemNowPlayingAssetConfiguration,
6
+ CheckboxAssetsConfiguration as ItemCheckboxAssetsConfiguration,
7
+ ActionButtonAssetsConfiguration as ItemActionButtonAssetsConfiguration,
6
8
  ItemConfiguration,
7
9
  } from "../Components/Item";
8
10
  import {
@@ -21,20 +23,39 @@ import {
21
23
 
22
24
  function mergeItemBackground(
23
25
  base: ItemBackgroundConfiguration,
24
- override?: Record<string, any>
26
+ defaultOverride?: Record<string, any>,
27
+ focusedOverride?: Record<string, any>
25
28
  ): ItemBackgroundConfiguration {
26
- if (!override) return base;
29
+ if (!defaultOverride && !focusedOverride) return base;
30
+
31
+ // Only take focused color from the focused/pressed bucket (or legacy
32
+ // mis-parsed focus* aliases). Never fall back to default background —
33
+ // that would overwrite base.focusedColor whenever Studio only sets default.
34
+ const focusedColor =
35
+ focusedOverride?.backgroundColor ??
36
+ defaultOverride?.focusColor ??
37
+ defaultOverride?.focusBackgroundColor;
27
38
 
28
39
  return {
29
40
  ...base,
30
- ...(override.backgroundColor
31
- ? { defaultColor: override.backgroundColor }
41
+ ...(defaultOverride?.backgroundColor
42
+ ? { defaultColor: defaultOverride.backgroundColor }
32
43
  : {}),
33
- ...(override.focusColor || override.backgroundColor
34
- ? { focusedColor: override.focusColor || override.backgroundColor }
44
+ ...(focusedColor != null && focusedColor !== "" ? { focusedColor } : {}),
45
+ ...(defaultOverride?.borderRadius !== undefined
46
+ ? { cornerRadius: defaultOverride.borderRadius }
35
47
  : {}),
36
- ...(override.borderRadius !== undefined
37
- ? { cornerRadius: override.borderRadius }
48
+ ...(defaultOverride?.paddingTop !== undefined
49
+ ? { paddingTop: defaultOverride.paddingTop }
50
+ : {}),
51
+ ...(defaultOverride?.paddingRight !== undefined
52
+ ? { paddingRight: defaultOverride.paddingRight }
53
+ : {}),
54
+ ...(defaultOverride?.paddingBottom !== undefined
55
+ ? { paddingBottom: defaultOverride.paddingBottom }
56
+ : {}),
57
+ ...(defaultOverride?.paddingLeft !== undefined
58
+ ? { paddingLeft: defaultOverride.paddingLeft }
38
59
  : {}),
39
60
  };
40
61
  }
@@ -69,6 +90,9 @@ function mergeItemLabel(
69
90
  ...base,
70
91
  ...(override.fontColor ? { fontColor: override.fontColor } : {}),
71
92
  ...(override.fontSize !== undefined ? { fontSize: override.fontSize } : {}),
93
+ ...(override.lineHeight !== undefined
94
+ ? { lineHeight: override.lineHeight }
95
+ : {}),
72
96
  };
73
97
  }
74
98
 
@@ -84,13 +108,45 @@ function mergeItemNowPlayingAsset(
84
108
  };
85
109
  }
86
110
 
111
+ function mergeItemCheckboxAssets(
112
+ base: ItemCheckboxAssetsConfiguration,
113
+ override?: Record<string, any>
114
+ ): ItemCheckboxAssetsConfiguration {
115
+ if (!override) return base;
116
+
117
+ return {
118
+ ...base,
119
+ ...(override.asset ? { asset: override.asset } : {}),
120
+ ...(override.checkedAsset ? { checkedAsset: override.checkedAsset } : {}),
121
+ };
122
+ }
123
+
124
+ function mergeItemActionButtonAssets(
125
+ base: ItemActionButtonAssetsConfiguration,
126
+ override?: Record<string, any>
127
+ ): ItemActionButtonAssetsConfiguration {
128
+ if (!override) return base;
129
+
130
+ return {
131
+ ...base,
132
+ ...(override.removeAsset ? { removeAsset: override.removeAsset } : {}),
133
+ ...(override.dragAsset ? { dragAsset: override.dragAsset } : {}),
134
+ };
135
+ }
136
+
87
137
  export function mergeItemConfiguration(
88
138
  base: ItemConfiguration,
89
139
  itemBlock?: Record<string, any>
90
140
  ): ItemConfiguration {
91
141
  if (!itemBlock) return base;
92
142
 
93
- const bg = itemBlock.background?.default || itemBlock.background;
143
+ const bgDefault = itemBlock.background?.default || itemBlock.background;
144
+
145
+ const bgFocused =
146
+ itemBlock.background?.focused ||
147
+ itemBlock.background?.focus ||
148
+ itemBlock.background?.pressed;
149
+
94
150
  const img = itemBlock.image?.default || itemBlock.image;
95
151
  const label1 = itemBlock.textLabel1?.default || itemBlock.textLabel1;
96
152
  const label2 = itemBlock.textLabel2?.default || itemBlock.textLabel2;
@@ -101,14 +157,27 @@ export function mergeItemConfiguration(
101
157
  const npAsset =
102
158
  itemBlock.nowPlayingAsset?.default || itemBlock.nowPlayingAsset;
103
159
 
160
+ const checkbox = itemBlock.checkbox?.default || itemBlock.checkbox;
161
+
162
+ const actionButtons =
163
+ itemBlock.actionButtons?.default || itemBlock.actionButtons;
164
+
104
165
  return {
105
166
  ...base,
106
- background: mergeItemBackground(base.background, bg),
167
+ background: mergeItemBackground(base.background, bgDefault, bgFocused),
107
168
  image: mergeItemImage(base.image, img),
108
169
  textLabel1: mergeItemLabel(base.textLabel1, label1),
109
170
  textLabel2: mergeItemLabel(base.textLabel2, label2),
110
171
  nowPlayingLabel: mergeItemLabel(base.nowPlayingLabel, npLabel),
111
172
  nowPlayingAsset: mergeItemNowPlayingAsset(base.nowPlayingAsset, npAsset),
173
+ checkboxAssets: mergeItemCheckboxAssets(
174
+ base.checkboxAssets || {},
175
+ checkbox
176
+ ),
177
+ actionButtonAssets: mergeItemActionButtonAssets(
178
+ base.actionButtonAssets || {},
179
+ actionButtons
180
+ ),
112
181
  };
113
182
  }
114
183
 
@@ -150,17 +219,37 @@ export function mergeHeaderConfiguration(
150
219
 
151
220
  function mergeButtonBackground(
152
221
  base: ButtonBackgroundConfiguration,
153
- override?: Record<string, any>
222
+ defaultOverride?: Record<string, any>,
223
+ focusedOverride?: Record<string, any>
154
224
  ): ButtonBackgroundConfiguration {
155
- if (!override) return base;
225
+ if (!defaultOverride && !focusedOverride) return base;
226
+
227
+ // Same as mergeItemBackground: do not use default background as focusedColor.
228
+ const focusedColor =
229
+ focusedOverride?.backgroundColor ??
230
+ defaultOverride?.focusColor ??
231
+ defaultOverride?.focusBackgroundColor;
156
232
 
157
233
  return {
158
234
  ...base,
159
- ...(override.backgroundColor
160
- ? { defaultColor: override.backgroundColor }
235
+ ...(defaultOverride?.backgroundColor
236
+ ? { defaultColor: defaultOverride.backgroundColor }
161
237
  : {}),
162
- ...(override.borderRadius !== undefined
163
- ? { cornerRadius: override.borderRadius }
238
+ ...(focusedColor != null && focusedColor !== "" ? { focusedColor } : {}),
239
+ ...(defaultOverride?.borderRadius !== undefined
240
+ ? { cornerRadius: defaultOverride.borderRadius }
241
+ : {}),
242
+ ...(defaultOverride?.paddingTop !== undefined
243
+ ? { paddingTop: defaultOverride.paddingTop }
244
+ : {}),
245
+ ...(defaultOverride?.paddingRight !== undefined
246
+ ? { paddingRight: defaultOverride.paddingRight }
247
+ : {}),
248
+ ...(defaultOverride?.paddingBottom !== undefined
249
+ ? { paddingBottom: defaultOverride.paddingBottom }
250
+ : {}),
251
+ ...(defaultOverride?.paddingLeft !== undefined
252
+ ? { paddingLeft: defaultOverride.paddingLeft }
164
253
  : {}),
165
254
  };
166
255
  }
@@ -175,6 +264,38 @@ function mergeButtonTitle(
175
264
  ...base,
176
265
  ...(override.fontColor ? { fontColor: override.fontColor } : {}),
177
266
  ...(override.fontSize !== undefined ? { fontSize: override.fontSize } : {}),
267
+ ...(override.lineHeight !== undefined
268
+ ? { lineHeight: override.lineHeight }
269
+ : {}),
270
+ };
271
+ }
272
+
273
+ function mergeButtonIcons(
274
+ base: ButtonConfiguration["icons"],
275
+ iconsOverride?: Record<string, any>,
276
+ leadingOverride?: Record<string, any>
277
+ ): ButtonConfiguration["icons"] {
278
+ if (!iconsOverride && !leadingOverride) return base;
279
+
280
+ const iconsDefault = iconsOverride?.default || iconsOverride;
281
+ const leadingDefault = leadingOverride?.default || leadingOverride;
282
+
283
+ return {
284
+ ...base,
285
+ ...(iconsDefault?.width !== undefined ? { width: iconsDefault.width } : {}),
286
+ ...(iconsDefault?.height !== undefined
287
+ ? { height: iconsDefault.height }
288
+ : {}),
289
+ leadingIcon: {
290
+ ...base.leadingIcon,
291
+ ...(leadingDefault?.enabled !== undefined
292
+ ? { enabled: leadingDefault.enabled }
293
+ : {}),
294
+ ...(leadingDefault?.actionType
295
+ ? { actionType: leadingDefault.actionType }
296
+ : {}),
297
+ ...(leadingDefault?.asset ? { asset: leadingDefault.asset } : {}),
298
+ },
178
299
  };
179
300
  }
180
301
 
@@ -185,15 +306,27 @@ export function mergeButtonConfiguration(
185
306
  ): ButtonConfiguration {
186
307
  if (!buttonBlock && !actionBlock) return base;
187
308
 
188
- const bgOverride =
309
+ const bgDefault =
189
310
  buttonBlock?.background?.default || actionBlock?.background?.default;
190
311
 
312
+ const bgFocused =
313
+ buttonBlock?.background?.focused ||
314
+ buttonBlock?.background?.focus ||
315
+ actionBlock?.background?.focused ||
316
+ actionBlock?.background?.focus ||
317
+ buttonBlock?.background?.pressed ||
318
+ actionBlock?.background?.pressed;
319
+
191
320
  const titleOverride =
192
321
  buttonBlock?.title?.default || actionBlock?.title?.default;
193
322
 
323
+ const iconsOverride = buttonBlock?.icons || actionBlock?.icons;
324
+ const leadingOverride = buttonBlock?.leadingIcon || actionBlock?.leadingIcon;
325
+
194
326
  return {
195
327
  ...base,
196
- background: mergeButtonBackground(base.background, bgOverride),
328
+ background: mergeButtonBackground(base.background, bgDefault, bgFocused),
197
329
  title: mergeButtonTitle(base.title, titleOverride),
330
+ icons: mergeButtonIcons(base.icons, iconsOverride, leadingOverride),
198
331
  };
199
332
  }
@@ -27,7 +27,7 @@ function mapMenuItemToDynamicData(
27
27
 
28
28
  const summary =
29
29
  typeof item?.summary === "string"
30
- ? item.summary
30
+ ? item.summary.trim()
31
31
  : item?.summary != null
32
32
  ? String(item.summary)
33
33
  : "";
@@ -31,10 +31,15 @@ export function defaultItemProps(
31
31
 
32
32
  const bgDefault = blockItemStyle?.background?.default;
33
33
 
34
+ const bgFocused =
35
+ blockItemStyle?.background?.focused || blockItemStyle?.background?.focus;
36
+
34
37
  return {
35
38
  backgroundColor:
36
39
  bgDefault?.backgroundColor || modal_bottom_sheet_item_background_color,
37
- focusedBackgroundColor: modal_bottom_sheet_item_focus_background_color,
40
+ focusedBackgroundColor:
41
+ bgFocused?.backgroundColor ||
42
+ modal_bottom_sheet_item_focus_background_color,
38
43
  selectedBackgroundColor: modal_bottom_sheet_item_selected_background_color,
39
44
  focusedSelectedBackgroundColor:
40
45
  modal_bottom_sheet_item_focused_selected_background_color,
@@ -5,7 +5,6 @@ import {
5
5
  platformSelect,
6
6
  } from "@applicaster/zapp-react-native-utils/reactUtils";
7
7
  import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
8
- import { useSafeAreaInsets } from "react-native-safe-area-context";
9
8
 
10
9
  import { usePlugins } from "@applicaster/zapp-react-native-redux";
11
10
  import { textTransform } from "@applicaster/zapp-react-native-utils/cellUtils";
@@ -19,17 +18,6 @@ const NETWORK_STATUS_LOCALIZATIONS_KEY = "network_status_localizations";
19
18
 
20
19
  const THEME_STORAGE_NAMESPACE = "quick-brick-theme";
21
20
 
22
- export const useNotificationHeight = () => {
23
- const insets = useSafeAreaInsets();
24
-
25
- const navBarHeight = platformSelect({ ios: 44, android: 56 });
26
-
27
- const statusHeight = insets.top;
28
- const notificationHeight = statusHeight + navBarHeight;
29
-
30
- return { statusHeight, notificationHeight };
31
- };
32
-
33
21
  export function useNetworkStatusLocalizations() {
34
22
  const [storedLocalizations, setStoredLocalizations] = React.useState<Record<
35
23
  string,
@@ -20,6 +20,8 @@ type Props = ZappScreenProps & {
20
20
  setIsScreenWrappedInContainer: (isInsideContainer: boolean) => void;
21
21
  screenContext: ZappRiver;
22
22
  river: ZappRiver;
23
+ /** Result channel for screens presented as a modal; forwarded to the resolved screen. */
24
+ resultCallback?: hookCallback;
23
25
  };
24
26
 
25
27
  export class RiverComponent extends React.Component<Props> {
@@ -64,6 +66,7 @@ export class RiverComponent extends React.Component<Props> {
64
66
  isInsideContainer,
65
67
  groupId,
66
68
  scrollViewExtraProps,
69
+ resultCallback,
67
70
  } = this.props;
68
71
 
69
72
  const { id, type } = river;
@@ -77,6 +80,7 @@ export class RiverComponent extends React.Component<Props> {
77
80
  })}
78
81
  screenId={id}
79
82
  screenType={type}
83
+ resultCallback={resultCallback}
80
84
  />
81
85
  );
82
86
  }
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+
4
+ import { RiverComponent } from "../River";
5
+
6
+ const mockResolverProps: any[] = [];
7
+
8
+ jest.mock("../../ScreenResolver", () => ({
9
+ ScreenResolver: (props: any) => {
10
+ mockResolverProps.push(props);
11
+
12
+ return null;
13
+ },
14
+ }));
15
+
16
+ const river = { id: "river-parent-lock", type: "parent_lock" } as any;
17
+
18
+ const baseProps = {
19
+ river,
20
+ screenData: {},
21
+ groupId: "group-1",
22
+ scrollViewExtraProps: {},
23
+ setIsScreenWrappedInContainer: jest.fn(),
24
+ screenContext: { navBar: { setTitle: jest.fn(), setSummary: jest.fn() } },
25
+ } as any;
26
+
27
+ const resolved = () => mockResolverProps[mockResolverProps.length - 1];
28
+
29
+ describe("RiverComponent", () => {
30
+ beforeEach(() => {
31
+ mockResolverProps.length = 0;
32
+ });
33
+
34
+ it("forwards resultCallback to the screen it resolves", () => {
35
+ const resultCallback = jest.fn();
36
+
37
+ render(<RiverComponent {...baseProps} resultCallback={resultCallback} />);
38
+
39
+ expect(resolved().resultCallback).toBe(resultCallback);
40
+ });
41
+
42
+ it("resolves the screen without a resultCallback when none was given", () => {
43
+ render(<RiverComponent {...baseProps} />);
44
+
45
+ expect(resolved().resultCallback).toBeUndefined();
46
+ });
47
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "16.0.0-rc.80",
3
+ "version": "16.0.0-rc.82",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-rc.80",
32
- "@applicaster/zapp-react-native-bridge": "16.0.0-rc.80",
33
- "@applicaster/zapp-react-native-redux": "16.0.0-rc.80",
34
- "@applicaster/zapp-react-native-utils": "16.0.0-rc.80",
31
+ "@applicaster/applicaster-types": "16.0.0-rc.82",
32
+ "@applicaster/zapp-react-native-bridge": "16.0.0-rc.82",
33
+ "@applicaster/zapp-react-native-redux": "16.0.0-rc.82",
34
+ "@applicaster/zapp-react-native-utils": "16.0.0-rc.82",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "react-native-sortables": "1.7.1",