@onehat/ui 0.4.59 → 0.4.61
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/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Fab, FabIcon, FabLabel,
|
|
4
|
+
ScrollView,
|
|
5
|
+
VStack,
|
|
6
|
+
} from '@project-components/Gluestack';
|
|
7
|
+
import Animated, {
|
|
8
|
+
useSharedValue,
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
withTiming,
|
|
11
|
+
interpolate,
|
|
12
|
+
} from 'react-native-reanimated';
|
|
13
|
+
import EllipsisVertical from '../Icons/EllipsisVertical.js';
|
|
14
|
+
import Xmark from '../Icons/Xmark.js';
|
|
15
|
+
|
|
16
|
+
// This component creates a floating action button (FAB)
|
|
17
|
+
// that can expand and collapse to show multiple FABs beneath it.
|
|
18
|
+
|
|
19
|
+
export default function DynamicFab(props) {
|
|
20
|
+
const {
|
|
21
|
+
fabs,
|
|
22
|
+
icon = null,
|
|
23
|
+
label = null,
|
|
24
|
+
collapseOnPress = true,
|
|
25
|
+
} = props,
|
|
26
|
+
isExpanded = useSharedValue(0),
|
|
27
|
+
toggleFab = useCallback(() => {
|
|
28
|
+
isExpanded.value = isExpanded.value ? 0 : 1;
|
|
29
|
+
}, []),
|
|
30
|
+
fabSpacing = 50,
|
|
31
|
+
verticalOffset = 15; // to shift the entire expanded group up
|
|
32
|
+
|
|
33
|
+
let className = `
|
|
34
|
+
DynamicFab
|
|
35
|
+
fixed
|
|
36
|
+
pb-[20px]
|
|
37
|
+
bottom-4
|
|
38
|
+
right-4
|
|
39
|
+
`;
|
|
40
|
+
if (props.className) {
|
|
41
|
+
className += ` ${props.className}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return <VStack className={className}>
|
|
45
|
+
{fabs
|
|
46
|
+
.slice() // clone, so we don't mutate the original array
|
|
47
|
+
.reverse() // so fabs appear in the correct order
|
|
48
|
+
.map((fab, index) => {
|
|
49
|
+
const {
|
|
50
|
+
icon,
|
|
51
|
+
label,
|
|
52
|
+
onPress,
|
|
53
|
+
} = fab,
|
|
54
|
+
animatedStyle = useAnimatedStyle(() => {
|
|
55
|
+
const translateY = interpolate(
|
|
56
|
+
isExpanded.value,
|
|
57
|
+
[0, 1],
|
|
58
|
+
[0, -(fabSpacing * (index + 1)) - verticalOffset]
|
|
59
|
+
);
|
|
60
|
+
return {
|
|
61
|
+
transform: [{ translateY }],
|
|
62
|
+
opacity: withTiming(isExpanded.value, { duration: 200 }),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return <Animated.View
|
|
67
|
+
key={index}
|
|
68
|
+
style={animatedStyle}
|
|
69
|
+
className="absolute bottom-0 right-0"
|
|
70
|
+
>
|
|
71
|
+
<Fab
|
|
72
|
+
size="md"
|
|
73
|
+
className="bg-primary-600"
|
|
74
|
+
onPress={() => {
|
|
75
|
+
onPress();
|
|
76
|
+
if (collapseOnPress) {
|
|
77
|
+
isExpanded.value = 0;
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
style={{
|
|
81
|
+
shadowColor: 'transparent', // otherwise, on collapse a bunch of shadows build up for a moment!
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<FabIcon as={icon} />
|
|
85
|
+
{label && <FabLabel>{label}</FabLabel>}
|
|
86
|
+
</Fab>
|
|
87
|
+
</Animated.View>;
|
|
88
|
+
})}
|
|
89
|
+
<Fab
|
|
90
|
+
size="lg"
|
|
91
|
+
onPress={toggleFab}
|
|
92
|
+
className="z-100 bg-primary-600"
|
|
93
|
+
>
|
|
94
|
+
<FabIcon as={isExpanded.value ? Xmark : icon || EllipsisVertical} />
|
|
95
|
+
{label && <FabLabel>{label}</FabLabel>}
|
|
96
|
+
</Fab>
|
|
97
|
+
</VStack>;
|
|
98
|
+
};
|
|
@@ -40,10 +40,17 @@ import testProps from '../../Functions/testProps.js';
|
|
|
40
40
|
import Toolbar from '../Toolbar/Toolbar.js';
|
|
41
41
|
import Button from '../Buttons/Button.js';
|
|
42
42
|
import IconButton from '../Buttons/IconButton.js';
|
|
43
|
+
import DynamicFab from '../Buttons/DynamicFab.js';
|
|
43
44
|
import AngleLeft from '../Icons/AngleLeft.js';
|
|
44
45
|
import Eye from '../Icons/Eye.js';
|
|
45
46
|
import Rotate from '../Icons/Rotate.js';
|
|
46
47
|
import Pencil from '../Icons/Pencil.js';
|
|
48
|
+
import Plus from '../Icons/Plus.js';
|
|
49
|
+
import FloppyDiskRegular from '../Icons/FloppyDiskRegular.js';
|
|
50
|
+
import Trash from '../Icons/Trash.js';
|
|
51
|
+
import ArrowUp from '../Icons/ArrowUp.js';
|
|
52
|
+
import Xmark from '../Icons/Xmark.js';
|
|
53
|
+
import Check from '../Icons/Check.js';
|
|
47
54
|
import Footer from '../Layout/Footer.js';
|
|
48
55
|
import Label from '../Form/Label.js';
|
|
49
56
|
import _ from 'lodash';
|
|
@@ -132,6 +139,8 @@ function Form(props) {
|
|
|
132
139
|
|
|
133
140
|
} = props,
|
|
134
141
|
formRef = useRef(),
|
|
142
|
+
ancillaryItemsRef = useRef({}),
|
|
143
|
+
ancillaryFabs = useRef([]),
|
|
135
144
|
styles = UiGlobals.styles,
|
|
136
145
|
record = props.record?.length === 1 ? props.record[0] : props.record;
|
|
137
146
|
|
|
@@ -893,12 +902,21 @@ function Form(props) {
|
|
|
893
902
|
},
|
|
894
903
|
buildAncillary = () => {
|
|
895
904
|
const components = [];
|
|
905
|
+
ancillaryFabs.current = [];
|
|
896
906
|
if (ancillaryItems.length) {
|
|
907
|
+
|
|
908
|
+
// add the "scroll to top" button
|
|
909
|
+
ancillaryFabs.current.push({
|
|
910
|
+
icon: ArrowUp,
|
|
911
|
+
onPress: () => scrollToAncillaryItem(0),
|
|
912
|
+
});
|
|
913
|
+
|
|
897
914
|
_.each(ancillaryItems, (item, ix) => {
|
|
898
915
|
let {
|
|
899
916
|
type,
|
|
900
917
|
title = null,
|
|
901
918
|
description = null,
|
|
919
|
+
icon,
|
|
902
920
|
selectorId,
|
|
903
921
|
selectorSelectedField,
|
|
904
922
|
...itemPropsToPass
|
|
@@ -906,6 +924,14 @@ function Form(props) {
|
|
|
906
924
|
if (isMultiple && type !== 'Attachments') {
|
|
907
925
|
return;
|
|
908
926
|
}
|
|
927
|
+
if (icon) {
|
|
928
|
+
// TODO: this assumes that if one Ancillary item has an icon, they all do.
|
|
929
|
+
// If they don't, the ix will be wrong.
|
|
930
|
+
ancillaryFabs.current.push({
|
|
931
|
+
icon,
|
|
932
|
+
onPress: () => scrollToAncillaryItem(ix +1), // offset for the "scroll to top" button
|
|
933
|
+
});
|
|
934
|
+
}
|
|
909
935
|
if (type.match(/Grid/) && !itemPropsToPass.h) {
|
|
910
936
|
itemPropsToPass.h = 400;
|
|
911
937
|
}
|
|
@@ -932,6 +958,9 @@ function Form(props) {
|
|
|
932
958
|
${styles.FORM_ANCILLARY_TITLE_CLASSNAME}
|
|
933
959
|
`}
|
|
934
960
|
>{title}</Text>;
|
|
961
|
+
if (icon) {
|
|
962
|
+
title = <HStack className="items-center"><Icon as={icon} size="lg" className="mr-2" />{title}</HStack>
|
|
963
|
+
}
|
|
935
964
|
}
|
|
936
965
|
if (description) {
|
|
937
966
|
description = <Text
|
|
@@ -943,6 +972,7 @@ function Form(props) {
|
|
|
943
972
|
>{description}</Text>;
|
|
944
973
|
}
|
|
945
974
|
components.push(<VStack
|
|
975
|
+
ref={(el) => (ancillaryItemsRef.current[ix +1 /* offset for "scroll to top" */] = el)}
|
|
946
976
|
key={'ancillary-' + ix}
|
|
947
977
|
className={`
|
|
948
978
|
Form-VStack12
|
|
@@ -958,6 +988,12 @@ function Form(props) {
|
|
|
958
988
|
}
|
|
959
989
|
return components;
|
|
960
990
|
},
|
|
991
|
+
scrollToAncillaryItem = (ix) => {
|
|
992
|
+
ancillaryItemsRef.current[ix]?.scrollIntoView({
|
|
993
|
+
behavior: 'smooth',
|
|
994
|
+
block: 'start',
|
|
995
|
+
});
|
|
996
|
+
},
|
|
961
997
|
onSubmitError = (errors, e) => {
|
|
962
998
|
if (editorType === EDITOR_TYPE__INLINE) {
|
|
963
999
|
alert(errors.message);
|
|
@@ -1082,6 +1118,7 @@ function Form(props) {
|
|
|
1082
1118
|
formComponents,
|
|
1083
1119
|
editor,
|
|
1084
1120
|
additionalButtons,
|
|
1121
|
+
fab = null,
|
|
1085
1122
|
isSaveDisabled = false,
|
|
1086
1123
|
isSubmitDisabled = false,
|
|
1087
1124
|
showDeleteBtn = false,
|
|
@@ -1095,13 +1132,13 @@ function Form(props) {
|
|
|
1095
1132
|
// create editor
|
|
1096
1133
|
if (editorType === EDITOR_TYPE__INLINE) {
|
|
1097
1134
|
editor = buildFromColumnsConfig();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1135
|
+
// } else if (editorType === EDITOR_TYPE__PLAIN) {
|
|
1136
|
+
// formComponents = buildFromItems();
|
|
1137
|
+
// const formAncillaryComponents = buildAncillary();
|
|
1138
|
+
// editor = <>
|
|
1139
|
+
// <VStack className="p-4">{formComponents}</VStack>
|
|
1140
|
+
// <VStack className="pt-4">{formAncillaryComponents}</VStack>
|
|
1141
|
+
// </>;
|
|
1105
1142
|
} else {
|
|
1106
1143
|
formComponents = buildFromItems();
|
|
1107
1144
|
const formAncillaryComponents = buildAncillary();
|
|
@@ -1155,10 +1192,15 @@ function Form(props) {
|
|
|
1155
1192
|
{additionalButtons}
|
|
1156
1193
|
</Toolbar>)
|
|
1157
1194
|
}
|
|
1195
|
+
if (!_.isEmpty(ancillaryFabs.current)) {
|
|
1196
|
+
fab = <DynamicFab
|
|
1197
|
+
fabs={ancillaryFabs.current}
|
|
1198
|
+
collapseOnPress={false}
|
|
1199
|
+
className="bottom-[55px]"
|
|
1200
|
+
/>;
|
|
1201
|
+
}
|
|
1158
1202
|
}
|
|
1159
1203
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
1204
|
// create footer
|
|
1163
1205
|
if (!formState.isValid) {
|
|
1164
1206
|
isSaveDisabled = true;
|
|
@@ -1216,6 +1258,7 @@ function Form(props) {
|
|
|
1216
1258
|
{...testProps('deleteBtn')}
|
|
1217
1259
|
key="deleteBtn"
|
|
1218
1260
|
onPress={onDelete}
|
|
1261
|
+
icon={Trash}
|
|
1219
1262
|
className={`
|
|
1220
1263
|
bg-warning-500
|
|
1221
1264
|
hover:bg-warning-700
|
|
@@ -1239,6 +1282,7 @@ function Form(props) {
|
|
|
1239
1282
|
{...testProps('cancelBtn')}
|
|
1240
1283
|
key="cancelBtn"
|
|
1241
1284
|
variant={editorType === EDITOR_TYPE__INLINE ? 'solid' : 'outline'}
|
|
1285
|
+
icon={Xmark}
|
|
1242
1286
|
onPress={onCancel}
|
|
1243
1287
|
className="text-white"
|
|
1244
1288
|
text="Cancel"
|
|
@@ -1249,6 +1293,7 @@ function Form(props) {
|
|
|
1249
1293
|
{...testProps('closeBtn')}
|
|
1250
1294
|
key="closeBtn"
|
|
1251
1295
|
variant={editorType === EDITOR_TYPE__INLINE ? 'solid' : 'outline'}
|
|
1296
|
+
icon={Xmark}
|
|
1252
1297
|
onPress={onClose}
|
|
1253
1298
|
className="text-white"
|
|
1254
1299
|
text="Close"
|
|
@@ -1259,6 +1304,7 @@ function Form(props) {
|
|
|
1259
1304
|
{...testProps('saveBtn')}
|
|
1260
1305
|
key="saveBtn"
|
|
1261
1306
|
onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
|
|
1307
|
+
icon={getEditorMode() === EDITOR_MODE__ADD ? Plus : FloppyDiskRegular}
|
|
1262
1308
|
isDisabled={isSaveDisabled}
|
|
1263
1309
|
className="text-white"
|
|
1264
1310
|
text={getEditorMode() === EDITOR_MODE__ADD ? 'Add' : 'Save'}
|
|
@@ -1268,6 +1314,7 @@ function Form(props) {
|
|
|
1268
1314
|
<Button
|
|
1269
1315
|
{...testProps('submitBtn')}
|
|
1270
1316
|
key="submitBtn"
|
|
1317
|
+
icon={Check}
|
|
1271
1318
|
onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
|
|
1272
1319
|
isDisabled={isSubmitDisabled}
|
|
1273
1320
|
className="text-white"
|
|
@@ -1283,6 +1330,7 @@ function Form(props) {
|
|
|
1283
1330
|
{...testProps('additionalFooterBtn-' + props.key)}
|
|
1284
1331
|
{...props}
|
|
1285
1332
|
onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
|
|
1333
|
+
icon={props.icon || null}
|
|
1286
1334
|
text={props.text}
|
|
1287
1335
|
isDisabled={isDisabled}
|
|
1288
1336
|
/>;
|
|
@@ -1344,6 +1392,7 @@ function Form(props) {
|
|
|
1344
1392
|
|
|
1345
1393
|
let className = props.className || '';
|
|
1346
1394
|
className += ' Form-VStackNative';
|
|
1395
|
+
const scrollToTopAnchor = <Box ref={(el) => (ancillaryItemsRef.current[0] = el)} className="h-0" />;
|
|
1347
1396
|
return <VStackNative
|
|
1348
1397
|
ref={formRef}
|
|
1349
1398
|
{...testProps(self)}
|
|
@@ -1367,6 +1416,7 @@ function Form(props) {
|
|
|
1367
1416
|
// height: '100%',
|
|
1368
1417
|
}}
|
|
1369
1418
|
>
|
|
1419
|
+
{scrollToTopAnchor}
|
|
1370
1420
|
{modeHeader}
|
|
1371
1421
|
{formHeader}
|
|
1372
1422
|
{formButtons}
|
|
@@ -1374,6 +1424,7 @@ function Form(props) {
|
|
|
1374
1424
|
</ScrollView>}
|
|
1375
1425
|
|
|
1376
1426
|
{footer}
|
|
1427
|
+
{fab}
|
|
1377
1428
|
|
|
1378
1429
|
</>}
|
|
1379
1430
|
</VStackNative>;
|
|
@@ -424,9 +424,9 @@ function GridComponent(props) {
|
|
|
424
424
|
} else {
|
|
425
425
|
let canDoEdit = false,
|
|
426
426
|
canDoView = false;
|
|
427
|
-
if (onEdit && canUser && canUser(EDIT) && canRecordBeEdited
|
|
427
|
+
if (onEdit && canUser && canUser(EDIT) && (!canRecordBeEdited || canRecordBeEdited(selection))) {
|
|
428
428
|
canDoEdit = true;
|
|
429
|
-
}
|
|
429
|
+
} else
|
|
430
430
|
if (onView && canUser && canUser(VIEW)) {
|
|
431
431
|
canDoView = true;
|
|
432
432
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createIcon } from "../Gluestack/icon";
|
|
2
|
+
// Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.
|
|
3
|
+
import { Path, Svg } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
const SvgComponent = createIcon({
|
|
6
|
+
Root: Svg,
|
|
7
|
+
viewBox: '0 0 384 512',
|
|
8
|
+
path: <Path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z" />,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default SvgComponent
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, isValidElement, } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Box,
|
|
3
4
|
HStack,
|
|
5
|
+
Icon,
|
|
4
6
|
ScrollView,
|
|
5
7
|
Text,
|
|
6
8
|
VStack,
|
|
@@ -24,7 +26,9 @@ import inArray from '../../Functions/inArray.js';
|
|
|
24
26
|
import getComponentFromType from '../../Functions/getComponentFromType.js';
|
|
25
27
|
import buildAdditionalButtons from '../../Functions/buildAdditionalButtons.js';
|
|
26
28
|
import testProps from '../../Functions/testProps.js';
|
|
29
|
+
import DynamicFab from '../Buttons/DynamicFab.js';
|
|
27
30
|
import Toolbar from '../Toolbar/Toolbar.js';
|
|
31
|
+
import ArrowUp from '../Icons/ArrowUp.js';
|
|
28
32
|
import Button from '../Buttons/Button.js';
|
|
29
33
|
import Label from '../Form/Label.js';
|
|
30
34
|
import Pencil from '../Icons/Pencil.js';
|
|
@@ -66,6 +70,8 @@ function Viewer(props) {
|
|
|
66
70
|
|
|
67
71
|
} = props,
|
|
68
72
|
scrollViewRef = useRef(),
|
|
73
|
+
ancillaryItemsRef = useRef({}),
|
|
74
|
+
ancillaryFabs = useRef([]),
|
|
69
75
|
isMultiple = _.isArray(record),
|
|
70
76
|
[containerWidth, setContainerWidth] = useState(),
|
|
71
77
|
isSideEditor = editorType === EDITOR_TYPE__SIDE,
|
|
@@ -295,17 +301,34 @@ function Viewer(props) {
|
|
|
295
301
|
},
|
|
296
302
|
buildAncillary = () => {
|
|
297
303
|
const components = [];
|
|
304
|
+
ancillaryFabs.current = [];
|
|
298
305
|
if (ancillaryItems.length) {
|
|
306
|
+
|
|
307
|
+
// add the "scroll to top" button
|
|
308
|
+
ancillaryFabs.current.push({
|
|
309
|
+
icon: ArrowUp,
|
|
310
|
+
onPress: () => scrollToAncillaryItem(0),
|
|
311
|
+
});
|
|
312
|
+
|
|
299
313
|
_.each(ancillaryItems, (item, ix) => {
|
|
300
314
|
let {
|
|
301
315
|
type,
|
|
302
316
|
title = null,
|
|
317
|
+
icon,
|
|
303
318
|
selectorId = null,
|
|
304
319
|
...itemPropsToPass
|
|
305
320
|
} = item;
|
|
306
321
|
if (isMultiple && type !== 'Attachments') {
|
|
307
322
|
return;
|
|
308
323
|
}
|
|
324
|
+
if (icon) {
|
|
325
|
+
// NOTE: this assumes that if one Ancillary item has an icon, they all do.
|
|
326
|
+
// If they don't, the ix will be wrong.
|
|
327
|
+
ancillaryFabs.current.push({
|
|
328
|
+
icon,
|
|
329
|
+
onPress: () => scrollToAncillaryItem(ix +1), // offset for the "scroll to top" button
|
|
330
|
+
});
|
|
331
|
+
}
|
|
309
332
|
if (type.match(/Grid/) && !itemPropsToPass.h) {
|
|
310
333
|
itemPropsToPass.h = 400;
|
|
311
334
|
}
|
|
@@ -334,12 +357,28 @@ function Viewer(props) {
|
|
|
334
357
|
title += ' for ' + record.displayValue;
|
|
335
358
|
}
|
|
336
359
|
title = <Text className={`${styles.VIEWER_ANCILLARY_FONTSIZE} font-bold`}>{title}</Text>;
|
|
360
|
+
if (icon) {
|
|
361
|
+
title = <HStack className="items-center"><Icon as={icon} size="lg" className="mr-2" />{title}</HStack>
|
|
362
|
+
}
|
|
337
363
|
}
|
|
338
|
-
components.push(<VStack
|
|
364
|
+
components.push(<VStack
|
|
365
|
+
ref={(el) => (ancillaryItemsRef.current[ix +1 /* offset for "scroll to top" */] = el)}
|
|
366
|
+
key={'ancillary-' + ix}
|
|
367
|
+
className="my-3"
|
|
368
|
+
>
|
|
369
|
+
{title}
|
|
370
|
+
{element}
|
|
371
|
+
</VStack>);
|
|
339
372
|
});
|
|
340
373
|
}
|
|
341
374
|
return components;
|
|
342
375
|
},
|
|
376
|
+
scrollToAncillaryItem = (ix) => {
|
|
377
|
+
ancillaryItemsRef.current[ix]?.scrollIntoView({
|
|
378
|
+
behavior: 'smooth',
|
|
379
|
+
block: 'start',
|
|
380
|
+
});
|
|
381
|
+
},
|
|
343
382
|
onLayout = (e) => {
|
|
344
383
|
setContainerWidth(e.nativeEvent.layout.width);
|
|
345
384
|
};
|
|
@@ -356,15 +395,25 @@ function Viewer(props) {
|
|
|
356
395
|
|
|
357
396
|
const
|
|
358
397
|
showDeleteBtn = onDelete && viewerCanDelete,
|
|
359
|
-
showCloseBtn = !isSideEditor
|
|
398
|
+
showCloseBtn = !isSideEditor,
|
|
399
|
+
showFooter = (showDeleteBtn || showCloseBtn);
|
|
360
400
|
let additionalButtons = null,
|
|
361
401
|
viewerComponents = null,
|
|
362
|
-
ancillaryComponents = null
|
|
402
|
+
ancillaryComponents = null,
|
|
403
|
+
fab = null;
|
|
363
404
|
|
|
364
405
|
if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
|
|
365
406
|
additionalButtons = buildAdditionalButtons(additionalViewButtons);
|
|
366
407
|
viewerComponents = buildFromItems();
|
|
367
408
|
ancillaryComponents = buildAncillary();
|
|
409
|
+
|
|
410
|
+
if (!_.isEmpty(ancillaryFabs.current)) {
|
|
411
|
+
fab = <DynamicFab
|
|
412
|
+
fabs={ancillaryFabs.current}
|
|
413
|
+
collapseOnPress={false}
|
|
414
|
+
verticalOffset={showFooter ? 15 : 0}
|
|
415
|
+
/>;
|
|
416
|
+
}
|
|
368
417
|
}
|
|
369
418
|
|
|
370
419
|
let canEdit = true;
|
|
@@ -385,6 +434,33 @@ function Viewer(props) {
|
|
|
385
434
|
className += ' ' + props.className;
|
|
386
435
|
}
|
|
387
436
|
|
|
437
|
+
const footer = showFooter ?
|
|
438
|
+
<Footer className="justify-end">
|
|
439
|
+
{showDeleteBtn &&
|
|
440
|
+
<HStack className="flex-1 justify-start">
|
|
441
|
+
<Button
|
|
442
|
+
{...testProps('deleteBtn')}
|
|
443
|
+
key="deleteBtn"
|
|
444
|
+
onPress={onDelete}
|
|
445
|
+
className={`
|
|
446
|
+
text-white
|
|
447
|
+
bg-warning-500
|
|
448
|
+
hover:bg-warning-600
|
|
449
|
+
`}
|
|
450
|
+
text="Delete"
|
|
451
|
+
/>
|
|
452
|
+
</HStack>}
|
|
453
|
+
{onClose && showCloseBtn &&
|
|
454
|
+
<Button
|
|
455
|
+
{...testProps('closeBtn')}
|
|
456
|
+
key="closeBtn"
|
|
457
|
+
onPress={onClose}
|
|
458
|
+
className="text-white"
|
|
459
|
+
text="Close"
|
|
460
|
+
/>}
|
|
461
|
+
</Footer> : null;
|
|
462
|
+
|
|
463
|
+
const scrollToTopAnchor = <Box ref={(el) => (ancillaryItemsRef.current[0] = el)} className="h-0" />;
|
|
388
464
|
return <VStackNative
|
|
389
465
|
{...testProps(self)}
|
|
390
466
|
style={style}
|
|
@@ -403,6 +479,7 @@ function Viewer(props) {
|
|
|
403
479
|
flex-1
|
|
404
480
|
`}
|
|
405
481
|
>
|
|
482
|
+
{scrollToTopAnchor}
|
|
406
483
|
{canEdit && onEditMode &&
|
|
407
484
|
<Toolbar className="justify-end">
|
|
408
485
|
<HStack className="flex-1 items-center">
|
|
@@ -434,31 +511,8 @@ function Viewer(props) {
|
|
|
434
511
|
<VStack className="Viewer-AncillaryComponents m-2 pt-4 px-2">{ancillaryComponents}</VStack>
|
|
435
512
|
</ScrollView>
|
|
436
513
|
|
|
437
|
-
{
|
|
438
|
-
|
|
439
|
-
{showDeleteBtn &&
|
|
440
|
-
<HStack className="flex-1 justify-start">
|
|
441
|
-
<Button
|
|
442
|
-
{...testProps('deleteBtn')}
|
|
443
|
-
key="deleteBtn"
|
|
444
|
-
onPress={onDelete}
|
|
445
|
-
className={`
|
|
446
|
-
text-white
|
|
447
|
-
bg-warning-500
|
|
448
|
-
hover:bg-warning-600
|
|
449
|
-
`}
|
|
450
|
-
text="Delete"
|
|
451
|
-
/>
|
|
452
|
-
</HStack>}
|
|
453
|
-
{onClose && showCloseBtn &&
|
|
454
|
-
<Button
|
|
455
|
-
{...testProps('closeBtn')}
|
|
456
|
-
key="closeBtn"
|
|
457
|
-
onPress={onClose}
|
|
458
|
-
className="text-white"
|
|
459
|
-
text="Close"
|
|
460
|
-
/>}
|
|
461
|
-
</Footer>}
|
|
514
|
+
{footer}
|
|
515
|
+
{fab}
|
|
462
516
|
|
|
463
517
|
</>}
|
|
464
518
|
</VStackNative>;
|
package/src/Components/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import AngleRight from './Icons/AngleRight.js';
|
|
|
9
9
|
import AnglesLeft from './Icons/AnglesLeft.js';
|
|
10
10
|
import AnglesRight from './Icons/AnglesRight.js';
|
|
11
11
|
import Asterisk from './Icons/Asterisk.js';
|
|
12
|
+
import ArrowUp from './Icons/ArrowUp.js';
|
|
12
13
|
import Ban from './Icons/Ban.js';
|
|
13
14
|
import Bars from './Icons/Bars.js';
|
|
14
15
|
import BarsStaggered from './Icons/BarsStaggered.js';
|
|
@@ -253,6 +254,7 @@ const components = {
|
|
|
253
254
|
AnglesLeft,
|
|
254
255
|
AnglesRight,
|
|
255
256
|
Asterisk,
|
|
257
|
+
ArrowUp,
|
|
256
258
|
Ban,
|
|
257
259
|
Bars,
|
|
258
260
|
BarsStaggered,
|