@terreno/ui 0.13.0 → 0.14.0
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/dist/ActionSheet.d.ts +4 -4
- package/dist/ActionSheet.js.map +1 -1
- package/dist/Avatar.js +1 -1
- package/dist/Avatar.js.map +1 -1
- package/dist/Banner.js.map +1 -1
- package/dist/Box.js +2 -0
- package/dist/Box.js.map +1 -1
- package/dist/Button.d.ts +2 -2
- package/dist/Button.js +35 -23
- package/dist/Button.js.map +1 -1
- package/dist/Common.d.ts +8 -2
- package/dist/Common.js.map +1 -1
- package/dist/ConsentFormScreen.js +9 -8
- package/dist/ConsentFormScreen.js.map +1 -1
- package/dist/ConsentNavigator.d.ts +1 -1
- package/dist/ConsentNavigator.js +2 -1
- package/dist/ConsentNavigator.js.map +1 -1
- package/dist/CustomSelectField.js +3 -1
- package/dist/CustomSelectField.js.map +1 -1
- package/dist/DataTable.js +1 -1
- package/dist/DataTable.js.map +1 -1
- package/dist/DateTimeActionSheet.js +2 -1
- package/dist/DateTimeActionSheet.js.map +1 -1
- package/dist/DateTimeField.js +3 -2
- package/dist/DateTimeField.js.map +1 -1
- package/dist/DateUtilities.js.map +1 -1
- package/dist/HeightField.js.map +1 -1
- package/dist/Hyperlink.js +19 -9
- package/dist/Hyperlink.js.map +1 -1
- package/dist/IconButton.js.map +1 -1
- package/dist/ImageBackground.d.ts +2 -5
- package/dist/ImageBackground.js +1 -1
- package/dist/ImageBackground.js.map +1 -1
- package/dist/ModalSheet.d.ts +3 -2
- package/dist/ModalSheet.js +1 -1
- package/dist/ModalSheet.js.map +1 -1
- package/dist/OfflineBanner.d.ts +21 -0
- package/dist/OfflineBanner.js +25 -0
- package/dist/OfflineBanner.js.map +1 -0
- package/dist/OpenAPIContext.js +1 -1
- package/dist/OpenAPIContext.js.map +1 -1
- package/dist/Page.js +1 -0
- package/dist/Page.js.map +1 -1
- package/dist/Pagination.js.map +1 -1
- package/dist/Permissions.js +3 -0
- package/dist/Permissions.js.map +1 -1
- package/dist/PickerSelect.d.ts +22 -10
- package/dist/PickerSelect.js +14 -9
- package/dist/PickerSelect.js.map +1 -1
- package/dist/SelectBadge.js +11 -1
- package/dist/SelectBadge.js.map +1 -1
- package/dist/SelectField.js +3 -3
- package/dist/SelectField.js.map +1 -1
- package/dist/SidebarNavigation.native.js.map +1 -1
- package/dist/Signature.js +4 -0
- package/dist/Signature.js.map +1 -1
- package/dist/Signature.native.js +4 -0
- package/dist/Signature.native.js.map +1 -1
- package/dist/SplitPage.js +7 -2
- package/dist/SplitPage.js.map +1 -1
- package/dist/SplitPage.native.js +4 -1
- package/dist/SplitPage.native.js.map +1 -1
- package/dist/TapToEdit.js +10 -11
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Theme.d.ts +1 -1
- package/dist/Theme.js.map +1 -1
- package/dist/Toast.js.map +1 -1
- package/dist/ToastNotifications.js.map +1 -1
- package/dist/Unifier.d.ts +2 -2
- package/dist/Unifier.js +1 -1
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +8 -4
- package/dist/Utilities.js +1 -1
- package/dist/Utilities.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/useConsentForms.d.ts +4 -3
- package/dist/useConsentForms.js +26 -4
- package/dist/useConsentForms.js.map +1 -1
- package/dist/useConsentHistory.d.ts +4 -3
- package/dist/useConsentHistory.js +26 -4
- package/dist/useConsentHistory.js.map +1 -1
- package/dist/useSubmitConsent.d.ts +7 -6
- package/dist/useSubmitConsent.js +25 -3
- package/dist/useSubmitConsent.js.map +1 -1
- package/package.json +2 -1
- package/src/ActionSheet.test.tsx +1 -0
- package/src/ActionSheet.tsx +6 -4
- package/src/Avatar.tsx +9 -2
- package/src/Badge.test.tsx +1 -0
- package/src/Banner.tsx +1 -1
- package/src/Box.test.tsx +1 -0
- package/src/Box.tsx +10 -6
- package/src/Button.test.tsx +35 -0
- package/src/Button.tsx +65 -34
- package/src/Common.ts +32 -15
- package/src/ConsentFormScreen.test.tsx +149 -0
- package/src/ConsentFormScreen.tsx +20 -3
- package/src/ConsentNavigator.test.tsx +1 -0
- package/src/ConsentNavigator.tsx +5 -3
- package/src/CustomSelectField.tsx +3 -1
- package/src/DataTable.test.tsx +1 -0
- package/src/DataTable.tsx +1 -1
- package/src/DateTimeActionSheet.tsx +7 -3
- package/src/DateTimeField.test.tsx +1 -0
- package/src/DateTimeField.tsx +3 -2
- package/src/DateUtilities.test.ts +111 -0
- package/src/DateUtilities.tsx +6 -6
- package/src/DecimalRangeActionSheet.test.tsx +28 -0
- package/src/ErrorBoundary.test.tsx +1 -0
- package/src/HeightField.test.tsx +68 -0
- package/src/HeightField.tsx +2 -1
- package/src/Hyperlink.tsx +83 -52
- package/src/IconButton.tsx +1 -1
- package/src/ImageBackground.tsx +5 -6
- package/src/Modal.tsx +2 -2
- package/src/ModalSheet.test.tsx +1 -5
- package/src/ModalSheet.tsx +15 -6
- package/src/NumberField.test.tsx +14 -0
- package/src/OfflineBanner.test.tsx +70 -0
- package/src/OfflineBanner.tsx +54 -0
- package/src/OpenAPIContext.tsx +3 -2
- package/src/Page.tsx +1 -0
- package/src/Pagination.tsx +1 -1
- package/src/Permissions.ts +3 -0
- package/src/PickerSelect.tsx +48 -31
- package/src/SelectBadge.test.tsx +1 -0
- package/src/SelectBadge.tsx +7 -3
- package/src/SelectField.tsx +3 -3
- package/src/SidebarNavigation.native.tsx +6 -2
- package/src/Signature.native.tsx +4 -0
- package/src/Signature.test.tsx +11 -0
- package/src/Signature.tsx +4 -0
- package/src/SplitPage.native.tsx +2 -0
- package/src/SplitPage.tsx +6 -1
- package/src/TapToEdit.test.tsx +17 -0
- package/src/TapToEdit.tsx +11 -11
- package/src/Theme.tsx +17 -14
- package/src/Toast.tsx +1 -1
- package/src/ToastNotifications.tsx +1 -4
- package/src/Tooltip.test.tsx +40 -28
- package/src/Unifier.ts +6 -5
- package/src/Utilities.tsx +9 -6
- package/src/__snapshots__/AddressField.test.tsx.snap +3 -2
- package/src/__snapshots__/Button.test.tsx.snap +92 -50
- package/src/__snapshots__/CustomSelectField.test.tsx.snap +21 -14
- package/src/__snapshots__/DecimalRangeActionSheet.test.tsx.snap +14 -8
- package/src/__snapshots__/ErrorPage.test.tsx.snap +7 -4
- package/src/__snapshots__/Field.test.tsx.snap +18 -12
- package/src/__snapshots__/HeightActionSheet.test.tsx.snap +14 -8
- package/src/__snapshots__/HeightField.test.tsx.snap +35 -20
- package/src/__snapshots__/InfoModalIcon.test.tsx.snap +28 -16
- package/src/__snapshots__/Modal.test.tsx.snap +19 -10
- package/src/__snapshots__/ModalSheet.test.tsx.snap +0 -1
- package/src/__snapshots__/NumberPickerActionSheet.test.tsx.snap +14 -8
- package/src/__snapshots__/Page.test.tsx.snap +7 -4
- package/src/__snapshots__/PickerSelect.test.tsx.snap +0 -7
- package/src/__snapshots__/SelectField.test.tsx.snap +18 -12
- package/src/__snapshots__/TerrenoProvider.test.tsx.snap +2 -18
- package/src/__snapshots__/TimezonePicker.test.tsx.snap +18 -12
- package/src/bunSetup.ts +45 -0
- package/src/index.tsx +1 -0
- package/src/login/__snapshots__/LoginScreen.test.tsx.snap +15 -6
- package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +10 -4
- package/src/table/__snapshots__/TableBadge.test.tsx.snap +3 -2
- package/src/types/react-native-swiper-flatlist.d.ts +1 -0
- package/src/useConsentForms.test.ts +25 -0
- package/src/useConsentForms.ts +32 -7
- package/src/useConsentHistory.test.ts +99 -0
- package/src/useConsentHistory.ts +31 -6
- package/src/useSubmitConsent.test.ts +24 -0
- package/src/useSubmitConsent.ts +35 -10
package/src/Pagination.tsx
CHANGED
package/src/Permissions.ts
CHANGED
|
@@ -10,6 +10,7 @@ export async function requestPermissions(_kind: PermissionKind): Promise<Permiss
|
|
|
10
10
|
// const userPropertyKey = `PermissionsFor${capitalize(kind)}`;
|
|
11
11
|
|
|
12
12
|
// let k = kind;
|
|
13
|
+
// // noExplicitAny: Dead commented-out code; types cannot be resolved without the full uncommented context and Permissions library types
|
|
13
14
|
// let options: any = undefined;
|
|
14
15
|
// if (kind === "locationAlways") {
|
|
15
16
|
// k = "location";
|
|
@@ -21,6 +22,7 @@ export async function requestPermissions(_kind: PermissionKind): Promise<Permiss
|
|
|
21
22
|
|
|
22
23
|
// // TODO check soft request status.
|
|
23
24
|
|
|
25
|
+
// // noExplicitAny: Dead commented-out code; MAP[k] type depends on unreferenced MAP constant
|
|
24
26
|
// const current = await Permissions.check(MAP[k] as any);
|
|
25
27
|
// // Tracking.log(`[permissions] ${k} permissions are ${current}`);
|
|
26
28
|
// if (current === "denied" || current === "limited") {
|
|
@@ -31,6 +33,7 @@ export async function requestPermissions(_kind: PermissionKind): Promise<Permiss
|
|
|
31
33
|
// return resolve("authorized");
|
|
32
34
|
// }
|
|
33
35
|
|
|
36
|
+
// // noExplicitAny: Dead commented-out code; MAP[k] type depends on unreferenced MAP constant
|
|
34
37
|
// const response = await Permissions.request(MAP[k] as any, options);
|
|
35
38
|
// if (response === "granted") {
|
|
36
39
|
// // Tracking.setUserProperty(userPropertyKey, "true");
|
package/src/PickerSelect.tsx
CHANGED
|
@@ -25,15 +25,19 @@
|
|
|
25
25
|
|
|
26
26
|
import {Picker} from "@react-native-picker/picker";
|
|
27
27
|
import isEqual from "lodash/isEqual";
|
|
28
|
-
import {useCallback, useEffect, useMemo, useState} from "react";
|
|
28
|
+
import {type ComponentType, type ReactNode, useCallback, useEffect, useMemo, useState} from "react";
|
|
29
29
|
import {
|
|
30
30
|
Keyboard,
|
|
31
31
|
Modal,
|
|
32
|
+
type ModalProps,
|
|
33
|
+
type NativeSyntheticEvent,
|
|
32
34
|
Platform,
|
|
33
35
|
Pressable,
|
|
36
|
+
type PressableProps,
|
|
34
37
|
StyleSheet,
|
|
35
38
|
Text,
|
|
36
39
|
TextInput,
|
|
40
|
+
type TextInputProps,
|
|
37
41
|
TouchableOpacity,
|
|
38
42
|
View,
|
|
39
43
|
} from "react-native";
|
|
@@ -67,14 +71,23 @@ export const defaultStyles = StyleSheet.create({
|
|
|
67
71
|
},
|
|
68
72
|
});
|
|
69
73
|
|
|
74
|
+
/** A single option for the picker select component. */
|
|
75
|
+
export interface PickerSelectItem {
|
|
76
|
+
label: string;
|
|
77
|
+
value: string | number | null;
|
|
78
|
+
key?: string | number;
|
|
79
|
+
color?: string;
|
|
80
|
+
inputLabel?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
export interface RNPickerSelectProps {
|
|
71
|
-
onValueChange: (value:
|
|
72
|
-
items:
|
|
73
|
-
value?:
|
|
74
|
-
placeholder?:
|
|
84
|
+
onValueChange: (value: string | number | null, index: number) => void;
|
|
85
|
+
items: PickerSelectItem[];
|
|
86
|
+
value?: string | number | null;
|
|
87
|
+
placeholder?: Partial<PickerSelectItem>;
|
|
75
88
|
disabled?: boolean;
|
|
76
89
|
itemKey?: string | number;
|
|
77
|
-
children?:
|
|
90
|
+
children?: ReactNode;
|
|
78
91
|
onOpen?: () => void;
|
|
79
92
|
useNativeAndroidPickerStyle?: boolean;
|
|
80
93
|
fixAndroidTouchableBug?: boolean;
|
|
@@ -87,18 +100,18 @@ export interface RNPickerSelectProps {
|
|
|
87
100
|
onClose?: () => void;
|
|
88
101
|
|
|
89
102
|
// Modal props (iOS only)
|
|
90
|
-
modalProps?:
|
|
103
|
+
modalProps?: Partial<ModalProps>;
|
|
91
104
|
|
|
92
105
|
// TextInput props
|
|
93
|
-
textInputProps?:
|
|
106
|
+
textInputProps?: Partial<TextInputProps>;
|
|
94
107
|
|
|
95
108
|
// Touchable Done props (iOS only)
|
|
96
|
-
touchableDoneProps?:
|
|
109
|
+
touchableDoneProps?: Partial<PressableProps>;
|
|
97
110
|
|
|
98
111
|
// Touchable wrapper props
|
|
99
|
-
touchableWrapperProps?:
|
|
112
|
+
touchableWrapperProps?: Partial<PressableProps>;
|
|
100
113
|
|
|
101
|
-
InputAccessoryView?:
|
|
114
|
+
InputAccessoryView?: ComponentType<{testID?: string}>;
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
export function RNPickerSelect({
|
|
@@ -125,7 +138,7 @@ export function RNPickerSelect({
|
|
|
125
138
|
InputAccessoryView,
|
|
126
139
|
}: RNPickerSelectProps) {
|
|
127
140
|
const [showPicker, setShowPicker] = useState<boolean>(false);
|
|
128
|
-
const [animationType, setAnimationType] = useState(undefined);
|
|
141
|
+
const [animationType, setAnimationType] = useState<ModalProps["animationType"]>(undefined);
|
|
129
142
|
const [orientation, setOrientation] = useState<"portrait" | "landscape">("portrait");
|
|
130
143
|
const [doneDepressed, setDoneDepressed] = useState<boolean>(false);
|
|
131
144
|
const {theme} = useTheme();
|
|
@@ -159,25 +172,25 @@ export function RNPickerSelect({
|
|
|
159
172
|
}, [items, placeholder]);
|
|
160
173
|
|
|
161
174
|
const getSelectedItem = useCallback(
|
|
162
|
-
(key:
|
|
163
|
-
let idx = options.findIndex((item
|
|
164
|
-
if (item
|
|
175
|
+
(key: string | number | undefined, val: string | number | null | undefined) => {
|
|
176
|
+
let idx = options.findIndex((item) => {
|
|
177
|
+
if (item?.key && key) {
|
|
165
178
|
return isEqual(item.key, key);
|
|
166
179
|
}
|
|
167
|
-
return isEqual(item
|
|
180
|
+
return isEqual(item?.value, val);
|
|
168
181
|
});
|
|
169
182
|
if (idx === -1) {
|
|
170
183
|
idx = 0;
|
|
171
184
|
}
|
|
172
185
|
return {
|
|
173
186
|
idx,
|
|
174
|
-
selectedItem: options[idx] || {}
|
|
187
|
+
selectedItem: (options[idx] || {}) as Partial<PickerSelectItem>,
|
|
175
188
|
};
|
|
176
189
|
},
|
|
177
190
|
[options]
|
|
178
191
|
);
|
|
179
192
|
|
|
180
|
-
const [selectedItem, setSelectedItem] = useState<
|
|
193
|
+
const [selectedItem, setSelectedItem] = useState<Partial<PickerSelectItem>>(() => {
|
|
181
194
|
return getSelectedItem(itemKey, value).selectedItem;
|
|
182
195
|
});
|
|
183
196
|
|
|
@@ -195,13 +208,15 @@ export function RNPickerSelect({
|
|
|
195
208
|
togglePicker(false, onDownArrow);
|
|
196
209
|
};
|
|
197
210
|
|
|
198
|
-
const onValueChangeEvent = (val:
|
|
211
|
+
const onValueChangeEvent = (val: string | number | null, index: number) => {
|
|
199
212
|
const item = getSelectedItem(itemKey, val);
|
|
200
213
|
onValueChange(val, index);
|
|
201
214
|
setSelectedItem(item.selectedItem);
|
|
202
215
|
};
|
|
203
216
|
|
|
204
|
-
const onOrientationChange = ({
|
|
217
|
+
const onOrientationChange = ({
|
|
218
|
+
nativeEvent,
|
|
219
|
+
}: NativeSyntheticEvent<{orientation: "portrait" | "landscape"}>) => {
|
|
205
220
|
setOrientation(nativeEvent.orientation);
|
|
206
221
|
};
|
|
207
222
|
|
|
@@ -215,7 +230,7 @@ export function RNPickerSelect({
|
|
|
215
230
|
}
|
|
216
231
|
};
|
|
217
232
|
|
|
218
|
-
const togglePicker = (animate = false, postToggleCallback?:
|
|
233
|
+
const togglePicker = (animate = false, postToggleCallback?: () => void) => {
|
|
219
234
|
if (disabled) {
|
|
220
235
|
return;
|
|
221
236
|
}
|
|
@@ -237,7 +252,8 @@ export function RNPickerSelect({
|
|
|
237
252
|
};
|
|
238
253
|
|
|
239
254
|
const renderPickerItems = () => {
|
|
240
|
-
return options?.map((item
|
|
255
|
+
return options?.map((item) => {
|
|
256
|
+
if (!item) return null;
|
|
241
257
|
return (
|
|
242
258
|
<Picker.Item
|
|
243
259
|
color={item.color}
|
|
@@ -408,7 +424,6 @@ export function RNPickerSelect({
|
|
|
408
424
|
]}
|
|
409
425
|
>
|
|
410
426
|
<Pressable
|
|
411
|
-
activeOpacity={1}
|
|
412
427
|
onPress={() => {
|
|
413
428
|
togglePicker(true);
|
|
414
429
|
}}
|
|
@@ -467,14 +482,16 @@ export function RNPickerSelect({
|
|
|
467
482
|
};
|
|
468
483
|
|
|
469
484
|
const renderAndroidHeadless = () => {
|
|
470
|
-
|
|
485
|
+
// `View` and `Pressable` accept disjoint prop sets; the fork swaps between them to work
|
|
486
|
+
// around an Android touchable bug, so we cast to a structural component type that accepts
|
|
487
|
+
// the union of props actually used in JSX below.
|
|
488
|
+
const Component = (fixAndroidTouchableBug ? View : Pressable) as ComponentType<{
|
|
489
|
+
onPress?: PressableProps["onPress"];
|
|
490
|
+
testID?: string;
|
|
491
|
+
children?: ReactNode;
|
|
492
|
+
}>;
|
|
471
493
|
return (
|
|
472
|
-
<Component
|
|
473
|
-
activeOpacity={1}
|
|
474
|
-
onPress={onOpen}
|
|
475
|
-
testID="android_touchable_wrapper"
|
|
476
|
-
{...touchableWrapperProps}
|
|
477
|
-
>
|
|
494
|
+
<Component onPress={onOpen} testID="android_touchable_wrapper" {...touchableWrapperProps}>
|
|
478
495
|
<View>
|
|
479
496
|
{renderTextInputOrChildren()}
|
|
480
497
|
<Picker
|
|
@@ -636,7 +653,7 @@ export function RNPickerSelect({
|
|
|
636
653
|
// Pass the original (non-stringified) value through so lodash
|
|
637
654
|
// `isEqual` matching in `getSelectedItem` works for number /
|
|
638
655
|
// object values.
|
|
639
|
-
const originalValue = options[originalIndex]?.value;
|
|
656
|
+
const originalValue = options[originalIndex]?.value ?? null;
|
|
640
657
|
onValueChangeEvent(originalValue, originalIndex);
|
|
641
658
|
closeWebMenu();
|
|
642
659
|
}}
|
package/src/SelectBadge.test.tsx
CHANGED
package/src/SelectBadge.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import type React from "react";
|
|
|
3
3
|
import {useCallback, useMemo, useState} from "react";
|
|
4
4
|
import {Modal, Platform, Text, TouchableOpacity, View} from "react-native";
|
|
5
5
|
|
|
6
|
-
import type {FieldOption, SelectBadgeProps, SurfaceTheme, TextTheme} from "./Common";
|
|
6
|
+
import type {FieldOption, IconColor, SelectBadgeProps, SurfaceTheme, TextTheme} from "./Common";
|
|
7
7
|
import {Icon} from "./Icon";
|
|
8
8
|
import {useTheme} from "./Theme";
|
|
9
9
|
import {useWebDropdownAnchor, WebDropdownMenu, type WebDropdownMenuOption} from "./WebDropdownMenu";
|
|
@@ -98,7 +98,7 @@ export const SelectBadge = ({
|
|
|
98
98
|
);
|
|
99
99
|
|
|
100
100
|
const renderPickerItems = useCallback(() => {
|
|
101
|
-
return options?.map((item:
|
|
101
|
+
return options?.map((item: FieldOption) => (
|
|
102
102
|
<Picker.Item key={item.key || item.label} label={item.label} value={item.value} />
|
|
103
103
|
));
|
|
104
104
|
}, [options]);
|
|
@@ -321,7 +321,11 @@ export const SelectBadge = ({
|
|
|
321
321
|
}}
|
|
322
322
|
>
|
|
323
323
|
<Icon
|
|
324
|
-
|
|
324
|
+
// noExplicitAny: textColor is a resolved hex string from the theme, but Icon's
|
|
325
|
+
// color prop expects an IconColor key. Icon falls back to the raw string at runtime
|
|
326
|
+
// (theme.text[color] ?? color), but the type cannot be narrowed without changing
|
|
327
|
+
// Icon's type signature to accept arbitrary strings.
|
|
328
|
+
color={textColor as unknown as IconColor}
|
|
325
329
|
iconName={showPicker ? "chevron-up" : "chevron-down"}
|
|
326
330
|
size="sm"
|
|
327
331
|
/>
|
package/src/SelectField.tsx
CHANGED
|
@@ -19,17 +19,17 @@ export const SelectField: FC<SelectFieldProps> = ({
|
|
|
19
19
|
const clearOption = {label: placeholder ?? "---", value: ""};
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<View>
|
|
22
|
+
<View style={{width: "100%"}}>
|
|
23
23
|
{Boolean(title) && <FieldTitle text={title!} />}
|
|
24
24
|
{Boolean(errorText) && <FieldError text={errorText!} />}
|
|
25
25
|
<RNPickerSelect
|
|
26
26
|
disabled={disabled}
|
|
27
27
|
items={options}
|
|
28
28
|
onValueChange={(v) => {
|
|
29
|
-
if (v === undefined || v === "") {
|
|
29
|
+
if (v === undefined || v === null || v === "") {
|
|
30
30
|
onChange("");
|
|
31
31
|
} else {
|
|
32
|
-
onChange(v);
|
|
32
|
+
onChange(String(v));
|
|
33
33
|
}
|
|
34
34
|
}}
|
|
35
35
|
placeholder={!requireValue ? clearOption : {}}
|
|
@@ -5,7 +5,7 @@ import {Navigator, Slot} from "expo-router";
|
|
|
5
5
|
// update the import path here — this is the only place in the codebase that references it.
|
|
6
6
|
// eslint-disable-next-line import/no-internal-modules
|
|
7
7
|
import {Screen} from "expo-router/build/views/Screen";
|
|
8
|
-
import {type FC, useCallback, useEffect, useMemo, useRef, useState} from "react";
|
|
8
|
+
import {type FC, type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from "react";
|
|
9
9
|
import {
|
|
10
10
|
Animated,
|
|
11
11
|
Dimensions,
|
|
@@ -361,7 +361,11 @@ const SidebarHeader: FC<{onOpen: () => void}> = ({onOpen}) => {
|
|
|
361
361
|
const insets = useSafeAreaInsets();
|
|
362
362
|
const {state, descriptors} = Navigator.useContext();
|
|
363
363
|
const activeRoute = state.routes[state.index];
|
|
364
|
-
const {headerLeft, headerRight, title} = (descriptors[activeRoute?.key]?.options ?? {}) as
|
|
364
|
+
const {headerLeft, headerRight, title} = (descriptors[activeRoute?.key]?.options ?? {}) as {
|
|
365
|
+
headerLeft?: (props: object) => ReactNode;
|
|
366
|
+
headerRight?: (props: object) => ReactNode;
|
|
367
|
+
title?: string;
|
|
368
|
+
};
|
|
365
369
|
|
|
366
370
|
return (
|
|
367
371
|
<View
|
package/src/Signature.native.tsx
CHANGED
|
@@ -18,6 +18,10 @@ export const Signature: FC<Props> = ({onChange, onStart, onEnd}: Props) => {
|
|
|
18
18
|
|
|
19
19
|
const handleClear = () => {
|
|
20
20
|
ref.current?.clearSignature();
|
|
21
|
+
// `clearSignature` on the underlying canvas does not fire `onOK`, so the
|
|
22
|
+
// parent never learns the signature is gone. Push an empty value so any
|
|
23
|
+
// "signature required" gating reflects the cleared state immediately.
|
|
24
|
+
onChange("");
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
const onBegin = () => {
|
package/src/Signature.test.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
3
|
import {fireEvent} from "@testing-library/react-native";
|
|
3
4
|
import {forwardRef, useImperativeHandle} from "react";
|
|
@@ -49,6 +50,16 @@ describe("Signature", () => {
|
|
|
49
50
|
expect(clearMock).toHaveBeenCalledTimes(1);
|
|
50
51
|
});
|
|
51
52
|
|
|
53
|
+
it("notifies the parent with an empty value when Clear is pressed", () => {
|
|
54
|
+
clearMock.mockClear();
|
|
55
|
+
const mockOnChange = mock(() => {});
|
|
56
|
+
const {getByText} = renderWithTheme(<Signature onChange={mockOnChange} />);
|
|
57
|
+
fireEvent.press(getByText("Clear"));
|
|
58
|
+
// Without this, "signature required" gating in parents would never reset
|
|
59
|
+
// because the underlying canvas clear() does not fire onEnd/onOK.
|
|
60
|
+
expect(mockOnChange).toHaveBeenCalledWith("");
|
|
61
|
+
});
|
|
62
|
+
|
|
52
63
|
it("calls onChange with the data URL when a stroke ends", () => {
|
|
53
64
|
toDataURLReturn = "data:image/png;base64,abc";
|
|
54
65
|
const mockOnChange = mock(() => {});
|
package/src/Signature.tsx
CHANGED
|
@@ -17,6 +17,10 @@ export const Signature = ({onChange}: SignatureProps): ReactElement | null => {
|
|
|
17
17
|
|
|
18
18
|
const onClear = () => {
|
|
19
19
|
ref.current?.clear();
|
|
20
|
+
// `clear()` on the underlying canvas does not fire `onEnd`, so the parent
|
|
21
|
+
// never learns the signature is gone. Push an empty value so any
|
|
22
|
+
// "signature required" gating reflects the cleared state immediately.
|
|
23
|
+
onChange("");
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
const onUpdatedSignature = () => {
|
package/src/SplitPage.native.tsx
CHANGED
|
@@ -34,6 +34,7 @@ export const SplitPage = ({
|
|
|
34
34
|
const {width} = Dimensions.get("window");
|
|
35
35
|
|
|
36
36
|
const onItemSelect = useCallback(
|
|
37
|
+
// biome-ignore lint/suspicious/noExplicitAny: SplitPage accepts heterogeneous list item shapes from consumers; the generic propagates from listViewData
|
|
37
38
|
async (item: ListRenderItemInfo<any>) => {
|
|
38
39
|
setSelectedId(item.index);
|
|
39
40
|
await onSelectionChange(item);
|
|
@@ -58,6 +59,7 @@ export const SplitPage = ({
|
|
|
58
59
|
return null;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// biome-ignore lint/suspicious/noExplicitAny: SplitPage accepts heterogeneous list item shapes from consumers; the generic propagates from listViewData
|
|
61
63
|
const renderItem = (itemInfo: ListRenderItemInfo<any>) => {
|
|
62
64
|
return (
|
|
63
65
|
<Box
|
package/src/SplitPage.tsx
CHANGED
|
@@ -40,6 +40,7 @@ export const SplitPage = ({
|
|
|
40
40
|
const elementArray = Children.toArray(children).filter((c) => c !== null);
|
|
41
41
|
|
|
42
42
|
const onItemSelect = useCallback(
|
|
43
|
+
// biome-ignore lint/suspicious/noExplicitAny: SplitPage accepts heterogeneous list item shapes from consumers; the generic propagates from listViewData
|
|
43
44
|
async (item: ListRenderItemInfo<any>): Promise<void> => {
|
|
44
45
|
setSelectedId(item.index);
|
|
45
46
|
await onSelectionChange(item);
|
|
@@ -69,6 +70,7 @@ export const SplitPage = ({
|
|
|
69
70
|
return null;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// biome-ignore lint/suspicious/noExplicitAny: SplitPage accepts heterogeneous list item shapes from consumers; the generic propagates from listViewData
|
|
72
74
|
const renderItem = (itemInfo: ListRenderItemInfo<any>) => {
|
|
73
75
|
return (
|
|
74
76
|
<Box
|
|
@@ -297,7 +299,10 @@ export const SplitPage = ({
|
|
|
297
299
|
padding={2}
|
|
298
300
|
width="100%"
|
|
299
301
|
>
|
|
300
|
-
{loading === true &&
|
|
302
|
+
{loading === true && (
|
|
303
|
+
// biome-ignore lint/suspicious/noExplicitAny: Spinner color is a token enum but the legacy code passes theme.text.primary (a resolved hex string); preserving original behavior
|
|
304
|
+
<Spinner color={theme.text.primary as any} size="md" />
|
|
305
|
+
)}
|
|
301
306
|
{isMobileDevice ? renderMobileSplitPage() : renderSplitPage()}
|
|
302
307
|
</Box>
|
|
303
308
|
);
|
package/src/TapToEdit.test.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
3
|
import {act, fireEvent} from "@testing-library/react-native";
|
|
3
4
|
import {Linking} from "react-native";
|
|
@@ -211,6 +212,22 @@ describe("TapToEdit", () => {
|
|
|
211
212
|
expect(setValue).toHaveBeenCalled();
|
|
212
213
|
});
|
|
213
214
|
|
|
215
|
+
it("reverts to original value (not edited value) when Cancel is pressed", async () => {
|
|
216
|
+
const setValue = mock((_v: unknown) => {});
|
|
217
|
+
const {getByLabelText, getByText} = renderWithTheme(
|
|
218
|
+
<TapToEdit setValue={setValue} title="Name" value="Jane" />
|
|
219
|
+
);
|
|
220
|
+
await act(async () => {
|
|
221
|
+
fireEvent.press(getByLabelText("Edit"));
|
|
222
|
+
});
|
|
223
|
+
// Simulate user having changed the value during editing
|
|
224
|
+
setValue("Edited Value");
|
|
225
|
+
await act(async () => {
|
|
226
|
+
fireEvent.press(getByText("Cancel"));
|
|
227
|
+
});
|
|
228
|
+
expect(setValue).toHaveBeenLastCalledWith("Jane");
|
|
229
|
+
});
|
|
230
|
+
|
|
214
231
|
it("clears value when Clear button is pressed", async () => {
|
|
215
232
|
const setValue = mock(() => {});
|
|
216
233
|
const onSave = mock(() => Promise.resolve());
|
package/src/TapToEdit.tsx
CHANGED
|
@@ -81,15 +81,11 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
81
81
|
...fieldProps
|
|
82
82
|
}) => {
|
|
83
83
|
const [editing, setEditing] = useState(false);
|
|
84
|
-
const
|
|
84
|
+
const initialValueRef = useRef<unknown>(undefined);
|
|
85
85
|
const helperText: string | undefined = propsHelperText;
|
|
86
|
-
// setInitialValue is called after initial render to handle the case where the value is updated
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
setInitialValue(value);
|
|
89
|
-
// do not update if value changes
|
|
90
|
-
}, [value]);
|
|
91
86
|
|
|
92
87
|
// TODO: Auto focus on input when editing for field types other than text for accessibility
|
|
88
|
+
// biome-ignore lint/suspicious/noExplicitAny: inputRef references various RN input components (TextInput, etc.) depending on the field type
|
|
93
89
|
const inputRef = useRef<any>(null);
|
|
94
90
|
|
|
95
91
|
// bring the bring the input into focus when editing from within the component,
|
|
@@ -116,13 +112,16 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
116
112
|
helperText={helperText}
|
|
117
113
|
inputRef={
|
|
118
114
|
["text", "textarea", "url", "email", "number"].includes(fieldProps?.type)
|
|
119
|
-
? (ref:
|
|
115
|
+
? (ref: unknown) => {
|
|
116
|
+
inputRef.current = ref;
|
|
117
|
+
}
|
|
120
118
|
: undefined
|
|
121
119
|
}
|
|
122
120
|
onChange={setValue ?? (() => {})}
|
|
123
121
|
row={fieldProps?.type === "textarea" ? 5 : undefined}
|
|
124
122
|
type={(fieldProps?.type ?? "text") as NonNullable<FieldProps["type"]>}
|
|
125
123
|
value={value}
|
|
124
|
+
// biome-ignore lint/suspicious/noExplicitAny: fieldProps is a discriminated union (FieldProps) but the spread loses narrowing; type-checking each variant individually is impractical here
|
|
126
125
|
{...(fieldProps as any)}
|
|
127
126
|
/>
|
|
128
127
|
{editing && !isEditing && (
|
|
@@ -130,7 +129,7 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
130
129
|
<Button
|
|
131
130
|
onClick={(): void => {
|
|
132
131
|
if (setValue) {
|
|
133
|
-
setValue(
|
|
132
|
+
setValue(initialValueRef.current);
|
|
134
133
|
}
|
|
135
134
|
setEditing(false);
|
|
136
135
|
}}
|
|
@@ -142,7 +141,6 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
142
141
|
onClick={(): void => {
|
|
143
142
|
if (setValue) {
|
|
144
143
|
setValue("");
|
|
145
|
-
setInitialValue("" as any);
|
|
146
144
|
}
|
|
147
145
|
if (onSave) {
|
|
148
146
|
onSave("");
|
|
@@ -161,7 +159,6 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
161
159
|
if (!onSave) {
|
|
162
160
|
console.error("No onSave provided for editable TapToEdit");
|
|
163
161
|
} else {
|
|
164
|
-
setInitialValue(value);
|
|
165
162
|
await onSave(value);
|
|
166
163
|
}
|
|
167
164
|
setEditing(false);
|
|
@@ -271,7 +268,10 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
271
268
|
accessibilityHint=""
|
|
272
269
|
accessibilityLabel="Edit"
|
|
273
270
|
marginLeft={2}
|
|
274
|
-
onClick={(): void =>
|
|
271
|
+
onClick={(): void => {
|
|
272
|
+
initialValueRef.current = value;
|
|
273
|
+
setEditing(true);
|
|
274
|
+
}}
|
|
275
275
|
width={16}
|
|
276
276
|
>
|
|
277
277
|
<Icon iconName="pencil" size="md" />
|
package/src/Theme.tsx
CHANGED
|
@@ -172,18 +172,21 @@ const computeTheme = (
|
|
|
172
172
|
return acc;
|
|
173
173
|
}
|
|
174
174
|
const value = themeConfig[key as keyof TerrenoThemeConfig] ?? {};
|
|
175
|
-
acc
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
(acc as unknown as Record<string, unknown>)[key] = Object.keys(value).reduce(
|
|
176
|
+
(accKey, valueKey) => {
|
|
177
|
+
const primitiveKey = value[valueKey as keyof typeof value] as keyof ThemePrimitives;
|
|
178
|
+
if (key === "font") {
|
|
179
|
+
accKey[valueKey] = primitiveKey;
|
|
180
|
+
} else {
|
|
181
|
+
if (primitives[primitiveKey] === undefined) {
|
|
182
|
+
console.error(`Primitive ${primitiveKey} not found in theme.`);
|
|
183
|
+
}
|
|
184
|
+
accKey[valueKey] = primitives[primitiveKey];
|
|
182
185
|
}
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
return accKey;
|
|
187
|
+
},
|
|
188
|
+
{} as Record<string, string | number>
|
|
189
|
+
);
|
|
187
190
|
return acc;
|
|
188
191
|
}, {} as TerrenoTheme);
|
|
189
192
|
return {...theme, primitives};
|
|
@@ -199,7 +202,7 @@ export const ThemeContext = createContext({
|
|
|
199
202
|
});
|
|
200
203
|
|
|
201
204
|
interface ThemeProviderProps {
|
|
202
|
-
children:
|
|
205
|
+
children: React.ReactNode;
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
export const ThemeProvider = ({children}: ThemeProviderProps) => {
|
|
@@ -225,12 +228,12 @@ export const ThemeProvider = ({children}: ThemeProviderProps) => {
|
|
|
225
228
|
const prevSubTheme = prev[key as keyof TerrenoThemeConfig];
|
|
226
229
|
|
|
227
230
|
if (newSubTheme && typeof newSubTheme === "object") {
|
|
228
|
-
(mergedTheme as
|
|
231
|
+
(mergedTheme as Record<string, unknown>)[key] = {
|
|
229
232
|
...prevSubTheme,
|
|
230
233
|
...newSubTheme,
|
|
231
234
|
};
|
|
232
235
|
} else {
|
|
233
|
-
mergedTheme
|
|
236
|
+
(mergedTheme as Record<string, unknown>)[key] = newSubTheme;
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
}
|
package/src/Toast.tsx
CHANGED
|
@@ -48,7 +48,7 @@ export const useToast = (): {
|
|
|
48
48
|
};
|
|
49
49
|
return {
|
|
50
50
|
catch: (error: unknown, message?: string, options?: UseToastVariantOptions): void => {
|
|
51
|
-
let exceptionMsg;
|
|
51
|
+
let exceptionMsg: string;
|
|
52
52
|
if (isAPIError(error)) {
|
|
53
53
|
// Get the error without details.
|
|
54
54
|
exceptionMsg = `${message}: ${printAPIError(error)}`;
|
|
@@ -177,10 +177,7 @@ export interface ToastOptions {
|
|
|
177
177
|
/**
|
|
178
178
|
* Payload data for custom toasts. You can pass whatever you want
|
|
179
179
|
*/
|
|
180
|
-
// noExplicitAny: This is the public API of a vendored 3rd-party library
|
|
181
|
-
// (react-native-toast-notifications); the `data` field is an opaque
|
|
182
|
-
// user-provided payload. Tightening to `unknown` breaks downstream consumers
|
|
183
|
-
// that spread `data` into `Toast` (e.g. TerrenoProvider) without refactor.
|
|
180
|
+
// biome-ignore lint/suspicious/noExplicitAny: This is the public API of a vendored 3rd-party library (react-native-toast-notifications); the data field is an opaque user-provided payload. Tightening to unknown breaks downstream consumers that spread data into Toast (e.g. TerrenoProvider) without refactor.
|
|
184
181
|
data?: any;
|
|
185
182
|
|
|
186
183
|
swipeEnabled?: boolean;
|