@applicaster/zapp-react-native-ui-components 16.0.0-rc.33 → 16.0.0-rc.34
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/ActionButtonsCore/__tests__/placement.test.ts +47 -21
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +3 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +40 -2
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +368 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +424 -0
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +728 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +709 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +7 -0
- package/package.json +5 -5
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal Building Block — Header
|
|
3
|
+
*
|
|
4
|
+
* Single-file Expo Snack demo for the Header building block spec.
|
|
5
|
+
*
|
|
6
|
+
* Green (configurable): title, closeButton (enable + asset), actionButtons styling
|
|
7
|
+
* White (hardcoded): container, action button layout/enable/actions, close dimensions,
|
|
8
|
+
* action button padding/radius, action labels
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useCallback, useState } from "react";
|
|
12
|
+
import { Image, Platform, StyleSheet, Text, View } from "react-native";
|
|
13
|
+
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
row: {
|
|
16
|
+
flexDirection: "row",
|
|
17
|
+
alignItems: "center",
|
|
18
|
+
},
|
|
19
|
+
centerAlignedRow: {
|
|
20
|
+
flexDirection: "row",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
minHeight: 44,
|
|
23
|
+
position: "relative",
|
|
24
|
+
},
|
|
25
|
+
measureLayer: {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
opacity: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
top: 0,
|
|
30
|
+
flexDirection: "row",
|
|
31
|
+
},
|
|
32
|
+
sideSlotFixed: {
|
|
33
|
+
justifyContent: "center",
|
|
34
|
+
minHeight: 44,
|
|
35
|
+
},
|
|
36
|
+
centerSpacer: {
|
|
37
|
+
flex: 1,
|
|
38
|
+
},
|
|
39
|
+
absoluteTitleLayer: {
|
|
40
|
+
...StyleSheet.absoluteFillObject,
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
},
|
|
44
|
+
closeButton: {
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
},
|
|
48
|
+
demoScreen: {
|
|
49
|
+
flex: 1,
|
|
50
|
+
backgroundColor: "#1C1C1E",
|
|
51
|
+
paddingTop: 64,
|
|
52
|
+
paddingHorizontal: 0,
|
|
53
|
+
gap: 24,
|
|
54
|
+
},
|
|
55
|
+
demoBlock: {
|
|
56
|
+
gap: 8,
|
|
57
|
+
paddingHorizontal: 0,
|
|
58
|
+
},
|
|
59
|
+
demoHeading: {
|
|
60
|
+
color: "#FFFFFF",
|
|
61
|
+
fontSize: 20,
|
|
62
|
+
fontWeight: "600",
|
|
63
|
+
marginBottom: 8,
|
|
64
|
+
paddingHorizontal: 24,
|
|
65
|
+
},
|
|
66
|
+
demoLabel: {
|
|
67
|
+
color: "#999999",
|
|
68
|
+
fontSize: 13,
|
|
69
|
+
paddingHorizontal: 24,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Configuration (green / editable fields from spec)
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export type HeaderActionType = "cancel" | "done" | "clear";
|
|
78
|
+
|
|
79
|
+
export type TextTransform =
|
|
80
|
+
| "default"
|
|
81
|
+
| "lowercase"
|
|
82
|
+
| "uppercase"
|
|
83
|
+
| "capitalize";
|
|
84
|
+
|
|
85
|
+
export type TitleAlignment = "left" | "center";
|
|
86
|
+
|
|
87
|
+
/** Selects hardcoded container + action layout (white fields). */
|
|
88
|
+
export type HeaderVariant =
|
|
89
|
+
| "singleLine"
|
|
90
|
+
| "headerWithActions"
|
|
91
|
+
| "headerWithActionsCancelOnly"
|
|
92
|
+
| "section";
|
|
93
|
+
|
|
94
|
+
export type TitleConfiguration = {
|
|
95
|
+
fontColor: string;
|
|
96
|
+
iosFontFamily: string;
|
|
97
|
+
androidFontFamily: string;
|
|
98
|
+
fontSize: number;
|
|
99
|
+
lineHeight: number;
|
|
100
|
+
iosLetterSpacing: number;
|
|
101
|
+
androidLetterSpacing: number;
|
|
102
|
+
textTransform: TextTransform;
|
|
103
|
+
alignment: TitleAlignment;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type CloseButtonConfiguration = {
|
|
107
|
+
enabled: boolean;
|
|
108
|
+
asset?: string;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type ActionButtonsStyleConfiguration = {
|
|
112
|
+
defaultBackgroundColor: string;
|
|
113
|
+
focusedBackgroundColor: string;
|
|
114
|
+
fontColor: string;
|
|
115
|
+
focusedFontColor: string;
|
|
116
|
+
iosFontFamily: string;
|
|
117
|
+
androidFontFamily: string;
|
|
118
|
+
fontSize: number;
|
|
119
|
+
lineHeight: number;
|
|
120
|
+
iosLetterSpacing: number;
|
|
121
|
+
androidLetterSpacing: number;
|
|
122
|
+
textTransform: TextTransform;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type HeaderConfiguration = {
|
|
126
|
+
variant: HeaderVariant;
|
|
127
|
+
title: TitleConfiguration;
|
|
128
|
+
closeButton: CloseButtonConfiguration;
|
|
129
|
+
actionButtons: ActionButtonsStyleConfiguration;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const DEFAULT_TITLE_CONFIGURATION: TitleConfiguration = {
|
|
133
|
+
fontColor: "#FFFFFF",
|
|
134
|
+
iosFontFamily: "SFProText-Bold",
|
|
135
|
+
androidFontFamily: "Roboto-Bold",
|
|
136
|
+
fontSize: 22,
|
|
137
|
+
lineHeight: 28,
|
|
138
|
+
iosLetterSpacing: -0.6,
|
|
139
|
+
androidLetterSpacing: 0,
|
|
140
|
+
textTransform: "default",
|
|
141
|
+
alignment: "left",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const DEFAULT_CLOSE_BUTTON_CONFIGURATION: CloseButtonConfiguration = {
|
|
145
|
+
enabled: true,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION: ActionButtonsStyleConfiguration =
|
|
149
|
+
{
|
|
150
|
+
defaultBackgroundColor: "rgba(62, 62, 62, 1)",
|
|
151
|
+
focusedBackgroundColor: "rgba(78, 78, 78, 1)",
|
|
152
|
+
fontColor: "#FFFFFF",
|
|
153
|
+
focusedFontColor: "#FFFFFF",
|
|
154
|
+
iosFontFamily: "SFProText-Semibold",
|
|
155
|
+
androidFontFamily: "Roboto-Medium",
|
|
156
|
+
fontSize: 14,
|
|
157
|
+
lineHeight: 20,
|
|
158
|
+
iosLetterSpacing: 0,
|
|
159
|
+
androidLetterSpacing: 0,
|
|
160
|
+
textTransform: "default",
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Data (runtime / localization — not styling config)
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
export type HeaderData = {
|
|
168
|
+
title: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type ItemProps = {
|
|
172
|
+
configuration: HeaderConfiguration;
|
|
173
|
+
data: HeaderData;
|
|
174
|
+
focusedCloseButton?: boolean;
|
|
175
|
+
focusedLeadingAction?: boolean;
|
|
176
|
+
focusedTrailingAction?: boolean;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Hardcoded spec values (white fields)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
type ContainerSpec = {
|
|
184
|
+
horizontalGutter: number;
|
|
185
|
+
paddingTop: number;
|
|
186
|
+
paddingRight: number;
|
|
187
|
+
paddingBottom: number;
|
|
188
|
+
paddingLeft: number;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type ActionSlotSpec = {
|
|
192
|
+
enabled: boolean;
|
|
193
|
+
actionType?: HeaderActionType;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
type ActionButtonsBehaviorSpec = {
|
|
197
|
+
sectionEnabled: boolean;
|
|
198
|
+
leading: ActionSlotSpec;
|
|
199
|
+
trailing: ActionSlotSpec;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const CONTAINER_SPEC: Record<HeaderVariant, ContainerSpec> = {
|
|
203
|
+
singleLine: {
|
|
204
|
+
horizontalGutter: 16,
|
|
205
|
+
paddingTop: 0,
|
|
206
|
+
paddingRight: 14,
|
|
207
|
+
paddingBottom: 8,
|
|
208
|
+
paddingLeft: 20,
|
|
209
|
+
},
|
|
210
|
+
headerWithActions: {
|
|
211
|
+
horizontalGutter: 12,
|
|
212
|
+
paddingTop: 0,
|
|
213
|
+
paddingRight: 20,
|
|
214
|
+
paddingBottom: 8,
|
|
215
|
+
paddingLeft: 20,
|
|
216
|
+
},
|
|
217
|
+
headerWithActionsCancelOnly: {
|
|
218
|
+
horizontalGutter: 12,
|
|
219
|
+
paddingTop: 0,
|
|
220
|
+
paddingRight: 20,
|
|
221
|
+
paddingBottom: 8,
|
|
222
|
+
paddingLeft: 20,
|
|
223
|
+
},
|
|
224
|
+
section: {
|
|
225
|
+
horizontalGutter: 12,
|
|
226
|
+
paddingTop: 0,
|
|
227
|
+
paddingRight: 20,
|
|
228
|
+
paddingBottom: 0,
|
|
229
|
+
paddingLeft: 20,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const ACTION_BUTTONS_BEHAVIOR: Record<
|
|
234
|
+
HeaderVariant,
|
|
235
|
+
ActionButtonsBehaviorSpec
|
|
236
|
+
> = {
|
|
237
|
+
singleLine: {
|
|
238
|
+
sectionEnabled: false,
|
|
239
|
+
leading: { enabled: false },
|
|
240
|
+
trailing: { enabled: false },
|
|
241
|
+
},
|
|
242
|
+
headerWithActions: {
|
|
243
|
+
sectionEnabled: true,
|
|
244
|
+
leading: { enabled: true, actionType: "cancel" },
|
|
245
|
+
trailing: { enabled: true, actionType: "done" },
|
|
246
|
+
},
|
|
247
|
+
headerWithActionsCancelOnly: {
|
|
248
|
+
sectionEnabled: true,
|
|
249
|
+
leading: { enabled: true, actionType: "cancel" },
|
|
250
|
+
trailing: { enabled: false },
|
|
251
|
+
},
|
|
252
|
+
section: {
|
|
253
|
+
sectionEnabled: true,
|
|
254
|
+
leading: { enabled: false },
|
|
255
|
+
trailing: { enabled: true, actionType: "clear" },
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const DEFAULT_CLOSE_BUTTON_ASSET =
|
|
260
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAl0lEQVR42u2YQQ7AIAgE/f+naQ899dDYyiLT7DyAnRijwBjGGLNEnKCy4qJCNlVYKZ2aETeUshLhTGlZbUVh5UGkB8hlM4PKZDMCy2VXgrfJfhHYLvtGpI3sjFA72SextrKz0m1bUYwsTvhX97f9C4F8g5G/HLKPQHZqyF4YOW0g5znkxCytjV6koFZVqGVg5Yc0jDFmiQNENiwNUMoCZAAAAABJRU5ErkJggg==";
|
|
261
|
+
|
|
262
|
+
const CLOSE_BUTTON_SPEC = {
|
|
263
|
+
width: 44,
|
|
264
|
+
height: 44,
|
|
265
|
+
paddingTop: 0,
|
|
266
|
+
paddingRight: 0,
|
|
267
|
+
paddingBottom: 0,
|
|
268
|
+
paddingLeft: 0,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const ACTION_BUTTON_LAYOUT_SPEC = {
|
|
272
|
+
paddingTop: 6,
|
|
273
|
+
paddingRight: 10,
|
|
274
|
+
paddingBottom: 6,
|
|
275
|
+
paddingLeft: 10,
|
|
276
|
+
cornerRadius: 16,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const ACTION_LABELS: Record<HeaderActionType, string> = {
|
|
280
|
+
cancel: "Cancel",
|
|
281
|
+
done: "Done",
|
|
282
|
+
clear: "Clear",
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
function getTextTransformStyle(
|
|
286
|
+
textTransform: TextTransform
|
|
287
|
+
): "none" | "lowercase" | "uppercase" | "capitalize" {
|
|
288
|
+
return textTransform === "default" ? "none" : textTransform;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function shouldShowActionSlot(slot: ActionSlotSpec, sectionEnabled: boolean) {
|
|
292
|
+
return sectionEnabled && slot.enabled && !!slot.actionType;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Inner components
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
type HeaderContainerProps = {
|
|
300
|
+
variant: HeaderVariant;
|
|
301
|
+
children: React.ReactNode;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
function HeaderContainer({ variant, children }: HeaderContainerProps) {
|
|
305
|
+
const container = CONTAINER_SPEC[variant];
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<View
|
|
309
|
+
style={{
|
|
310
|
+
paddingTop: container.paddingTop,
|
|
311
|
+
paddingRight: container.paddingRight,
|
|
312
|
+
paddingBottom: container.paddingBottom,
|
|
313
|
+
paddingLeft: container.paddingLeft,
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
{children}
|
|
317
|
+
</View>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
type HeaderTitleProps = {
|
|
322
|
+
title: string;
|
|
323
|
+
configuration: TitleConfiguration;
|
|
324
|
+
centered: boolean;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
function HeaderTitle({ title, configuration, centered }: HeaderTitleProps) {
|
|
328
|
+
const letterSpacing =
|
|
329
|
+
Platform.OS === "ios"
|
|
330
|
+
? configuration.iosLetterSpacing
|
|
331
|
+
: configuration.androidLetterSpacing;
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<Text
|
|
335
|
+
style={{
|
|
336
|
+
flex: centered ? undefined : 1,
|
|
337
|
+
color: configuration.fontColor,
|
|
338
|
+
fontSize: configuration.fontSize,
|
|
339
|
+
lineHeight: configuration.lineHeight,
|
|
340
|
+
letterSpacing,
|
|
341
|
+
fontFamily: Platform.select({
|
|
342
|
+
ios: configuration.iosFontFamily,
|
|
343
|
+
android: configuration.androidFontFamily,
|
|
344
|
+
default: configuration.androidFontFamily,
|
|
345
|
+
}),
|
|
346
|
+
textTransform: getTextTransformStyle(configuration.textTransform),
|
|
347
|
+
textAlign: centered ? "center" : configuration.alignment,
|
|
348
|
+
}}
|
|
349
|
+
numberOfLines={1}
|
|
350
|
+
>
|
|
351
|
+
{title}
|
|
352
|
+
</Text>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
type HeaderCloseButtonProps = {
|
|
357
|
+
configuration: CloseButtonConfiguration;
|
|
358
|
+
focused?: boolean;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
function HeaderCloseButton({
|
|
362
|
+
configuration,
|
|
363
|
+
focused = false,
|
|
364
|
+
}: HeaderCloseButtonProps) {
|
|
365
|
+
if (!configuration.enabled) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const asset = configuration.asset ?? DEFAULT_CLOSE_BUTTON_ASSET;
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<View
|
|
373
|
+
style={[
|
|
374
|
+
styles.closeButton,
|
|
375
|
+
{
|
|
376
|
+
width: CLOSE_BUTTON_SPEC.width,
|
|
377
|
+
height: CLOSE_BUTTON_SPEC.height,
|
|
378
|
+
marginTop: CLOSE_BUTTON_SPEC.paddingTop,
|
|
379
|
+
marginRight: CLOSE_BUTTON_SPEC.paddingRight,
|
|
380
|
+
marginBottom: CLOSE_BUTTON_SPEC.paddingBottom,
|
|
381
|
+
marginLeft: CLOSE_BUTTON_SPEC.paddingLeft,
|
|
382
|
+
borderRadius: CLOSE_BUTTON_SPEC.width / 2,
|
|
383
|
+
borderWidth: 1,
|
|
384
|
+
borderColor: "#FFFFFF",
|
|
385
|
+
opacity: focused ? 0.7 : 1,
|
|
386
|
+
},
|
|
387
|
+
]}
|
|
388
|
+
>
|
|
389
|
+
<Image
|
|
390
|
+
source={{ uri: asset }}
|
|
391
|
+
style={{
|
|
392
|
+
width: CLOSE_BUTTON_SPEC.width * 0.55,
|
|
393
|
+
height: CLOSE_BUTTON_SPEC.height * 0.55,
|
|
394
|
+
}}
|
|
395
|
+
resizeMode="contain"
|
|
396
|
+
/>
|
|
397
|
+
</View>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
type HeaderActionButtonProps = {
|
|
402
|
+
actionType: HeaderActionType;
|
|
403
|
+
configuration: ActionButtonsStyleConfiguration;
|
|
404
|
+
focused?: boolean;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
function HeaderActionButton({
|
|
408
|
+
actionType,
|
|
409
|
+
configuration,
|
|
410
|
+
focused = false,
|
|
411
|
+
}: HeaderActionButtonProps) {
|
|
412
|
+
return (
|
|
413
|
+
<View
|
|
414
|
+
style={{
|
|
415
|
+
backgroundColor: focused
|
|
416
|
+
? configuration.focusedBackgroundColor
|
|
417
|
+
: configuration.defaultBackgroundColor,
|
|
418
|
+
borderRadius: ACTION_BUTTON_LAYOUT_SPEC.cornerRadius,
|
|
419
|
+
paddingTop: ACTION_BUTTON_LAYOUT_SPEC.paddingTop,
|
|
420
|
+
paddingRight: ACTION_BUTTON_LAYOUT_SPEC.paddingRight,
|
|
421
|
+
paddingBottom: ACTION_BUTTON_LAYOUT_SPEC.paddingBottom,
|
|
422
|
+
paddingLeft: ACTION_BUTTON_LAYOUT_SPEC.paddingLeft,
|
|
423
|
+
}}
|
|
424
|
+
>
|
|
425
|
+
<Text
|
|
426
|
+
style={{
|
|
427
|
+
color: focused
|
|
428
|
+
? configuration.focusedFontColor
|
|
429
|
+
: configuration.fontColor,
|
|
430
|
+
fontSize: configuration.fontSize,
|
|
431
|
+
lineHeight: configuration.lineHeight,
|
|
432
|
+
letterSpacing:
|
|
433
|
+
Platform.OS === "ios"
|
|
434
|
+
? configuration.iosLetterSpacing
|
|
435
|
+
: configuration.androidLetterSpacing,
|
|
436
|
+
fontFamily: Platform.select({
|
|
437
|
+
ios: configuration.iosFontFamily,
|
|
438
|
+
android: configuration.androidFontFamily,
|
|
439
|
+
default: configuration.androidFontFamily,
|
|
440
|
+
}),
|
|
441
|
+
textTransform: getTextTransformStyle(configuration.textTransform),
|
|
442
|
+
}}
|
|
443
|
+
numberOfLines={1}
|
|
444
|
+
>
|
|
445
|
+
{ACTION_LABELS[actionType]}
|
|
446
|
+
</Text>
|
|
447
|
+
</View>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
type CenterAlignedHeaderLayoutProps = {
|
|
452
|
+
horizontalGutter: number;
|
|
453
|
+
title: TitleConfiguration;
|
|
454
|
+
titleText: string;
|
|
455
|
+
leadingContent: React.ReactNode;
|
|
456
|
+
trailingContent: React.ReactNode;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
function CenterAlignedHeaderLayout({
|
|
460
|
+
horizontalGutter,
|
|
461
|
+
title,
|
|
462
|
+
titleText,
|
|
463
|
+
leadingContent,
|
|
464
|
+
trailingContent,
|
|
465
|
+
}: CenterAlignedHeaderLayoutProps) {
|
|
466
|
+
const [sideWidths, setSideWidths] = useState({ leading: 0, trailing: 0 });
|
|
467
|
+
const sideSlotWidth = Math.max(sideWidths.leading, sideWidths.trailing);
|
|
468
|
+
|
|
469
|
+
const updateSideWidth = useCallback(
|
|
470
|
+
(side: "leading" | "trailing", width: number) => {
|
|
471
|
+
setSideWidths((previous) => {
|
|
472
|
+
if (previous[side] === width) {
|
|
473
|
+
return previous;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return { ...previous, [side]: width };
|
|
477
|
+
});
|
|
478
|
+
},
|
|
479
|
+
[]
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<View style={styles.centerAlignedRow}>
|
|
484
|
+
<View style={styles.measureLayer} pointerEvents="none">
|
|
485
|
+
{leadingContent ? (
|
|
486
|
+
<View
|
|
487
|
+
onLayout={(event) =>
|
|
488
|
+
updateSideWidth("leading", event.nativeEvent.layout.width)
|
|
489
|
+
}
|
|
490
|
+
>
|
|
491
|
+
{leadingContent}
|
|
492
|
+
</View>
|
|
493
|
+
) : null}
|
|
494
|
+
{trailingContent ? (
|
|
495
|
+
<View
|
|
496
|
+
onLayout={(event) =>
|
|
497
|
+
updateSideWidth("trailing", event.nativeEvent.layout.width)
|
|
498
|
+
}
|
|
499
|
+
>
|
|
500
|
+
{trailingContent}
|
|
501
|
+
</View>
|
|
502
|
+
) : null}
|
|
503
|
+
</View>
|
|
504
|
+
|
|
505
|
+
<View
|
|
506
|
+
style={[
|
|
507
|
+
styles.sideSlotFixed,
|
|
508
|
+
{ width: sideSlotWidth, alignItems: "flex-start" },
|
|
509
|
+
]}
|
|
510
|
+
>
|
|
511
|
+
{leadingContent}
|
|
512
|
+
</View>
|
|
513
|
+
<View style={{ width: horizontalGutter }} />
|
|
514
|
+
<View style={styles.centerSpacer} />
|
|
515
|
+
<View style={{ width: horizontalGutter }} />
|
|
516
|
+
<View
|
|
517
|
+
style={[
|
|
518
|
+
styles.sideSlotFixed,
|
|
519
|
+
{ width: sideSlotWidth, alignItems: "flex-end" },
|
|
520
|
+
]}
|
|
521
|
+
>
|
|
522
|
+
{trailingContent}
|
|
523
|
+
</View>
|
|
524
|
+
|
|
525
|
+
<View
|
|
526
|
+
pointerEvents="none"
|
|
527
|
+
style={[
|
|
528
|
+
styles.absoluteTitleLayer,
|
|
529
|
+
{ paddingHorizontal: sideSlotWidth + horizontalGutter },
|
|
530
|
+
]}
|
|
531
|
+
>
|
|
532
|
+
<HeaderTitle title={titleText} configuration={title} centered />
|
|
533
|
+
</View>
|
|
534
|
+
</View>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Item
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
export function Item({
|
|
543
|
+
configuration,
|
|
544
|
+
data,
|
|
545
|
+
focusedCloseButton = false,
|
|
546
|
+
focusedLeadingAction = false,
|
|
547
|
+
focusedTrailingAction = false,
|
|
548
|
+
}: ItemProps) {
|
|
549
|
+
const { variant, title, closeButton, actionButtons } = configuration;
|
|
550
|
+
const { title: titleText } = data;
|
|
551
|
+
|
|
552
|
+
const container = CONTAINER_SPEC[variant];
|
|
553
|
+
const behavior = ACTION_BUTTONS_BEHAVIOR[variant];
|
|
554
|
+
const isCenterAligned = title.alignment === "center";
|
|
555
|
+
|
|
556
|
+
const showLeadingAction = shouldShowActionSlot(
|
|
557
|
+
behavior.leading,
|
|
558
|
+
behavior.sectionEnabled
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const showTrailingAction = shouldShowActionSlot(
|
|
562
|
+
behavior.trailing,
|
|
563
|
+
behavior.sectionEnabled
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
const leadingContent = showLeadingAction ? (
|
|
567
|
+
<HeaderActionButton
|
|
568
|
+
actionType={behavior.leading.actionType}
|
|
569
|
+
configuration={actionButtons}
|
|
570
|
+
focused={focusedLeadingAction}
|
|
571
|
+
/>
|
|
572
|
+
) : null;
|
|
573
|
+
|
|
574
|
+
const trailingContent = showTrailingAction ? (
|
|
575
|
+
<HeaderActionButton
|
|
576
|
+
actionType={behavior.trailing.actionType}
|
|
577
|
+
configuration={actionButtons}
|
|
578
|
+
focused={focusedTrailingAction}
|
|
579
|
+
/>
|
|
580
|
+
) : closeButton.enabled && !isCenterAligned ? (
|
|
581
|
+
<HeaderCloseButton
|
|
582
|
+
configuration={closeButton}
|
|
583
|
+
focused={focusedCloseButton}
|
|
584
|
+
/>
|
|
585
|
+
) : null;
|
|
586
|
+
|
|
587
|
+
if (isCenterAligned) {
|
|
588
|
+
return (
|
|
589
|
+
<HeaderContainer variant={variant}>
|
|
590
|
+
<CenterAlignedHeaderLayout
|
|
591
|
+
horizontalGutter={container.horizontalGutter}
|
|
592
|
+
title={title}
|
|
593
|
+
titleText={titleText}
|
|
594
|
+
leadingContent={leadingContent}
|
|
595
|
+
trailingContent={trailingContent}
|
|
596
|
+
/>
|
|
597
|
+
</HeaderContainer>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return (
|
|
602
|
+
<HeaderContainer variant={variant}>
|
|
603
|
+
<View style={styles.row}>
|
|
604
|
+
{leadingContent ? (
|
|
605
|
+
<>
|
|
606
|
+
{leadingContent}
|
|
607
|
+
<View style={{ width: container.horizontalGutter }} />
|
|
608
|
+
</>
|
|
609
|
+
) : null}
|
|
610
|
+
<HeaderTitle title={titleText} configuration={title} centered={false} />
|
|
611
|
+
{trailingContent ? (
|
|
612
|
+
<>
|
|
613
|
+
<View style={{ width: container.horizontalGutter }} />
|
|
614
|
+
{trailingContent}
|
|
615
|
+
</>
|
|
616
|
+
) : null}
|
|
617
|
+
</View>
|
|
618
|
+
</HeaderContainer>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
// Variant configurations (green defaults per PDF column)
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
/** Default / Variant 1 — Single Line Header */
|
|
627
|
+
export const VARIANT_SINGLE_LINE_HEADER: HeaderConfiguration = {
|
|
628
|
+
variant: "singleLine",
|
|
629
|
+
title: DEFAULT_TITLE_CONFIGURATION,
|
|
630
|
+
closeButton: DEFAULT_CLOSE_BUTTON_CONFIGURATION,
|
|
631
|
+
actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/** Variant 2 — Header With Actions */
|
|
635
|
+
export const VARIANT_HEADER_WITH_ACTIONS: HeaderConfiguration = {
|
|
636
|
+
variant: "headerWithActions",
|
|
637
|
+
title: {
|
|
638
|
+
fontColor: "#FFFFFF",
|
|
639
|
+
iosFontFamily: "SFProText-Bold",
|
|
640
|
+
androidFontFamily: "Roboto-Bold",
|
|
641
|
+
fontSize: 22,
|
|
642
|
+
lineHeight: 28,
|
|
643
|
+
iosLetterSpacing: -0.6,
|
|
644
|
+
androidLetterSpacing: 0,
|
|
645
|
+
textTransform: "default",
|
|
646
|
+
alignment: "center",
|
|
647
|
+
},
|
|
648
|
+
closeButton: { enabled: false },
|
|
649
|
+
actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
/** Variant 2 — single side action (layout rule demo) */
|
|
653
|
+
export const VARIANT_HEADER_WITH_CANCEL_ONLY: HeaderConfiguration = {
|
|
654
|
+
variant: "headerWithActionsCancelOnly",
|
|
655
|
+
title: {
|
|
656
|
+
fontColor: "#FFFFFF",
|
|
657
|
+
iosFontFamily: "SFProText-Bold",
|
|
658
|
+
androidFontFamily: "Roboto-Bold",
|
|
659
|
+
fontSize: 22,
|
|
660
|
+
lineHeight: 28,
|
|
661
|
+
iosLetterSpacing: -0.6,
|
|
662
|
+
androidLetterSpacing: 0,
|
|
663
|
+
textTransform: "default",
|
|
664
|
+
alignment: "center",
|
|
665
|
+
},
|
|
666
|
+
closeButton: { enabled: false },
|
|
667
|
+
actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
/** Variant 3 — Section Header */
|
|
671
|
+
export const VARIANT_SECTION_HEADER: HeaderConfiguration = {
|
|
672
|
+
variant: "section",
|
|
673
|
+
title: {
|
|
674
|
+
fontColor: "#FFFFFF",
|
|
675
|
+
iosFontFamily: "SFProText-Bold",
|
|
676
|
+
androidFontFamily: "Roboto-Bold",
|
|
677
|
+
fontSize: 13,
|
|
678
|
+
lineHeight: 32,
|
|
679
|
+
iosLetterSpacing: -0.1,
|
|
680
|
+
androidLetterSpacing: 0,
|
|
681
|
+
textTransform: "default",
|
|
682
|
+
alignment: "left",
|
|
683
|
+
},
|
|
684
|
+
closeButton: { enabled: false },
|
|
685
|
+
actionButtons: DEFAULT_ACTION_BUTTONS_STYLE_CONFIGURATION,
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
export default function App() {
|
|
689
|
+
return (
|
|
690
|
+
<View style={styles.demoScreen}>
|
|
691
|
+
<Text style={styles.demoHeading}>Modal Header Building Block</Text>
|
|
692
|
+
|
|
693
|
+
<View style={styles.demoBlock}>
|
|
694
|
+
<Text style={styles.demoLabel}>Single Line Header</Text>
|
|
695
|
+
<Item
|
|
696
|
+
configuration={VARIANT_SINGLE_LINE_HEADER}
|
|
697
|
+
data={{ title: "Playback Speed" }}
|
|
698
|
+
/>
|
|
699
|
+
</View>
|
|
700
|
+
|
|
701
|
+
<View style={styles.demoBlock}>
|
|
702
|
+
<Text style={styles.demoLabel}>Header With Actions</Text>
|
|
703
|
+
<Item
|
|
704
|
+
configuration={VARIANT_HEADER_WITH_ACTIONS}
|
|
705
|
+
data={{ title: "Playback Speed" }}
|
|
706
|
+
/>
|
|
707
|
+
</View>
|
|
708
|
+
|
|
709
|
+
<View style={styles.demoBlock}>
|
|
710
|
+
<Text style={styles.demoLabel}>
|
|
711
|
+
Header With Actions — single side action
|
|
712
|
+
</Text>
|
|
713
|
+
<Item
|
|
714
|
+
configuration={VARIANT_HEADER_WITH_CANCEL_ONLY}
|
|
715
|
+
data={{ title: "Playback Speed" }}
|
|
716
|
+
/>
|
|
717
|
+
</View>
|
|
718
|
+
|
|
719
|
+
<View style={styles.demoBlock}>
|
|
720
|
+
<Text style={styles.demoLabel}>Section Header</Text>
|
|
721
|
+
<Item
|
|
722
|
+
configuration={VARIANT_SECTION_HEADER}
|
|
723
|
+
data={{ title: "Next in Queue" }}
|
|
724
|
+
/>
|
|
725
|
+
</View>
|
|
726
|
+
</View>
|
|
727
|
+
);
|
|
728
|
+
}
|