@terreno/ui 0.13.3 → 0.14.1
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 +5 -5
- package/dist/ActionSheet.js +2 -2
- 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 +16 -4
- package/dist/Common.js +4 -4
- package/dist/Common.js.map +1 -1
- package/dist/ConsentFormScreen.js +3 -3
- 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.d.ts +25 -25
- package/dist/DateUtilities.js +31 -32
- 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/MediaQuery.d.ts +4 -4
- package/dist/MediaQuery.js +8 -8
- package/dist/MediaQuery.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.d.ts +1 -0
- package/dist/Page.js +7 -2
- 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 +1 -1
- package/dist/PickerSelect.js +9 -6
- package/dist/PickerSelect.js.map +1 -1
- package/dist/SelectField.js +1 -1
- package/dist/SelectField.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.d.ts +1 -1
- package/dist/TapToEdit.js +12 -14
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Toast.js.map +1 -1
- package/dist/ToastNotifications.js +2 -2
- package/dist/ToastNotifications.js.map +1 -1
- package/dist/Tooltip.d.ts +24 -1
- package/dist/Tooltip.js +2 -2
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.d.ts +3 -3
- package/dist/Unifier.js +15 -12
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +12 -8
- package/dist/Utilities.js +13 -15
- package/dist/Utilities.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/signUp/PasswordRequirements.js +3 -3
- package/dist/signUp/PasswordRequirements.js.map +1 -1
- package/dist/table/TableHeaderCell.js +1 -9
- package/dist/table/TableHeaderCell.js.map +1 -1
- package/dist/table/tableContext.d.ts +1 -1
- package/dist/table/tableContext.js +2 -2
- package/dist/table/tableContext.js.map +1 -1
- package/package.json +2 -1
- package/src/ActionSheet.test.tsx +1 -0
- package/src/ActionSheet.tsx +8 -6
- package/src/Avatar.tsx +9 -2
- package/src/Badge.test.tsx +1 -0
- package/src/Banner.test.tsx +71 -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 +42 -19
- package/src/ConsentFormScreen.test.tsx +124 -0
- package/src/ConsentFormScreen.tsx +18 -6
- package/src/ConsentNavigator.test.tsx +1 -0
- package/src/ConsentNavigator.tsx +5 -3
- package/src/CustomSelectField.tsx +3 -1
- package/src/DataTable.test.tsx +218 -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 +43 -44
- package/src/DecimalRangeActionSheet.test.tsx +28 -0
- package/src/ErrorBoundary.test.tsx +1 -0
- package/src/HeightActionSheet.test.tsx +16 -0
- package/src/HeightField.test.tsx +106 -1
- 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/MediaQuery.ts +8 -8
- package/src/MobileAddressAutoComplete.test.tsx +20 -1
- 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.test.tsx +28 -0
- package/src/Page.tsx +18 -2
- package/src/Pagination.tsx +1 -1
- package/src/Permissions.ts +3 -0
- package/src/PickerSelect.tsx +20 -17
- package/src/SelectBadge.test.tsx +1 -0
- package/src/SelectField.tsx +1 -1
- package/src/Signature.test.tsx +1 -0
- package/src/SplitPage.native.tsx +2 -0
- package/src/SplitPage.tsx +6 -1
- package/src/TapToEdit.test.tsx +48 -0
- package/src/TapToEdit.tsx +13 -14
- package/src/Toast.tsx +1 -1
- package/src/ToastNotifications.test.tsx +738 -0
- package/src/ToastNotifications.tsx +3 -6
- package/src/Tooltip.test.tsx +586 -8
- package/src/Tooltip.tsx +2 -2
- package/src/Unifier.ts +20 -16
- package/src/Utilities.tsx +20 -19
- package/src/WebAddressAutocomplete.test.tsx +138 -0
- package/src/WebDropdownMenu.test.tsx +23 -0
- package/src/__snapshots__/AddressField.test.tsx.snap +3 -1
- package/src/__snapshots__/Button.test.tsx.snap +92 -50
- package/src/__snapshots__/CustomSelectField.test.tsx.snap +21 -7
- 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 -6
- 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__/SelectField.test.tsx.snap +18 -6
- package/src/__snapshots__/TerrenoProvider.test.tsx.snap +0 -2
- package/src/__snapshots__/TimezonePicker.test.tsx.snap +18 -6
- package/src/bunSetup.ts +25 -2
- package/src/index.tsx +2 -1
- package/src/login/LoginScreen.test.tsx +23 -1
- package/src/login/__snapshots__/LoginScreen.test.tsx.snap +15 -6
- package/src/signUp/PasswordRequirements.tsx +9 -6
- package/src/signUp/__snapshots__/PasswordRequirements.test.tsx.snap +50 -2
- package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +35 -5
- package/src/table/TableHeaderCell.tsx +8 -11
- package/src/table/TableRow.test.tsx +31 -1
- package/src/table/__snapshots__/TableBadge.test.tsx.snap +3 -1
- package/src/table/__snapshots__/TableHeaderCell.test.tsx.snap +2 -0
- package/src/table/tableContext.tsx +2 -2
- package/src/types/react-native-swiper-flatlist.d.ts +1 -0
- package/src/useStoredState.test.tsx +47 -0
package/src/Common.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {ReactElement, ReactNode} from "react";
|
|
|
4
4
|
import type {
|
|
5
5
|
ImageStyle,
|
|
6
6
|
ListRenderItemInfo,
|
|
7
|
+
ScrollView,
|
|
7
8
|
StyleProp,
|
|
8
9
|
TextInput,
|
|
9
10
|
TextStyle,
|
|
@@ -394,12 +395,12 @@ export const SPACING_MAP = {
|
|
|
394
395
|
12: 80,
|
|
395
396
|
};
|
|
396
397
|
|
|
397
|
-
export
|
|
398
|
+
export const getSpacing = (spacing: SignedUpTo12) => {
|
|
398
399
|
if (spacing < 0) {
|
|
399
400
|
return SPACING_MAP[Math.abs(spacing) as UnsignedUpTo12] * -1;
|
|
400
401
|
}
|
|
401
402
|
return SPACING_MAP[spacing as UnsignedUpTo12];
|
|
402
|
-
}
|
|
403
|
+
};
|
|
403
404
|
|
|
404
405
|
export type TextFieldType =
|
|
405
406
|
| "date"
|
|
@@ -463,7 +464,7 @@ export interface BoxPropsBase {
|
|
|
463
464
|
lgColumn?: UnsignedUpTo12;
|
|
464
465
|
dangerouslySetInlineStyle?: {
|
|
465
466
|
__style: {
|
|
466
|
-
// noExplicitAny: escape hatch for arbitrary inline style values
|
|
467
|
+
// biome-ignore lint/suspicious/noExplicitAny: escape hatch for arbitrary inline style values that users may need to set
|
|
467
468
|
[key: string]: any;
|
|
468
469
|
};
|
|
469
470
|
};
|
|
@@ -549,7 +550,7 @@ export interface BoxPropsBase {
|
|
|
549
550
|
|
|
550
551
|
avoidKeyboard?: boolean;
|
|
551
552
|
keyboardOffset?: number;
|
|
552
|
-
scrollRef?: React.RefObject<
|
|
553
|
+
scrollRef?: React.RefObject<ScrollView | null>;
|
|
553
554
|
onScroll?: (offsetY: number) => void;
|
|
554
555
|
onLayout?: (event: LayoutChangeEvent) => void;
|
|
555
556
|
testID?: string;
|
|
@@ -752,9 +753,9 @@ const ROUNDING_MAP = {
|
|
|
752
753
|
xl: 32,
|
|
753
754
|
};
|
|
754
755
|
|
|
755
|
-
export
|
|
756
|
+
export const getRounding = (rounding: Rounding) => {
|
|
756
757
|
return ROUNDING_MAP[rounding];
|
|
757
|
-
}
|
|
758
|
+
};
|
|
758
759
|
|
|
759
760
|
export interface HeadingProps {
|
|
760
761
|
align?: "left" | "right" | "center" | "justify"; // default "left"
|
|
@@ -858,17 +859,17 @@ export interface SplitPageProps {
|
|
|
858
859
|
loading?: boolean;
|
|
859
860
|
color?: SurfaceColor;
|
|
860
861
|
keyboardOffset?: number;
|
|
861
|
-
// noExplicitAny: ListRenderItemInfo generic type depends on the consumer's data shape
|
|
862
|
+
// biome-ignore lint/suspicious/noExplicitAny: ListRenderItemInfo generic type depends on the consumer's data shape
|
|
862
863
|
renderListViewItem: (itemInfo: ListRenderItemInfo<any>) => ReactElement | null;
|
|
863
864
|
renderListViewHeader?: () => ReactElement | null;
|
|
864
865
|
renderContent?: (index?: number) => ReactElement | ReactElement[] | null;
|
|
865
|
-
// noExplicitAny: list data type varies by consumer's data model
|
|
866
|
+
// biome-ignore lint/suspicious/noExplicitAny: list data type varies by consumer's data model
|
|
866
867
|
listViewData: any[];
|
|
867
868
|
listViewExtraData?: unknown;
|
|
868
869
|
listViewWidth?: number;
|
|
869
870
|
listViewMaxWidth?: number;
|
|
870
871
|
renderChild?: () => ReactChild;
|
|
871
|
-
// noExplicitAny: callback value type varies by consumer's data model
|
|
872
|
+
// biome-ignore lint/suspicious/noExplicitAny: callback value type varies by consumer's data model
|
|
872
873
|
onSelectionChange?: (value?: any) => void | Promise<void>;
|
|
873
874
|
}
|
|
874
875
|
|
|
@@ -1500,6 +1501,8 @@ export interface BodyProps {
|
|
|
1500
1501
|
children?: ReactNode;
|
|
1501
1502
|
}
|
|
1502
1503
|
|
|
1504
|
+
export type ButtonPressAnimation = "scale" | "opacity" | "none";
|
|
1505
|
+
|
|
1503
1506
|
export interface ButtonProps {
|
|
1504
1507
|
/**
|
|
1505
1508
|
* The text content of the confirmation modal.
|
|
@@ -1538,6 +1541,11 @@ export interface ButtonProps {
|
|
|
1538
1541
|
* The subtitle of the confirmation modal.
|
|
1539
1542
|
*/
|
|
1540
1543
|
modalSubTitle?: string;
|
|
1544
|
+
/**
|
|
1545
|
+
* The press animation to use when the button is touched.
|
|
1546
|
+
* @default "scale"
|
|
1547
|
+
*/
|
|
1548
|
+
pressAnimation?: ButtonPressAnimation;
|
|
1541
1549
|
/**
|
|
1542
1550
|
* The test ID for the button, used for testing purposes.
|
|
1543
1551
|
*/
|
|
@@ -1675,6 +1683,7 @@ export interface DateTimeActionSheetProps {
|
|
|
1675
1683
|
type?: "date" | "time" | "datetime";
|
|
1676
1684
|
// Returns an ISO 8601 string. If mode is "time", the date portion is today.
|
|
1677
1685
|
onChange: OnChangeCallback;
|
|
1686
|
+
// biome-ignore lint/suspicious/noExplicitAny: ActionSheet class lives in ActionSheet.tsx which imports from Common.ts; typing this would create a circular import
|
|
1678
1687
|
actionSheetRef: React.RefObject<any>;
|
|
1679
1688
|
visible: boolean;
|
|
1680
1689
|
onDismiss: () => void;
|
|
@@ -1686,6 +1695,7 @@ export interface DecimalRangeActionSheetProps {
|
|
|
1686
1695
|
min: number;
|
|
1687
1696
|
max: number;
|
|
1688
1697
|
onChange: OnChangeCallback;
|
|
1698
|
+
// biome-ignore lint/suspicious/noExplicitAny: ActionSheet class lives in ActionSheet.tsx which imports from Common.ts; typing this would create a circular import
|
|
1689
1699
|
actionSheetRef: React.RefObject<any>;
|
|
1690
1700
|
}
|
|
1691
1701
|
|
|
@@ -1745,6 +1755,7 @@ export type FieldProps =
|
|
|
1745
1755
|
export interface HeightActionSheetProps {
|
|
1746
1756
|
value?: string;
|
|
1747
1757
|
onChange: OnChangeCallback;
|
|
1758
|
+
// biome-ignore lint/suspicious/noExplicitAny: ActionSheet class lives in ActionSheet.tsx which imports from Common.ts; typing this would create a circular import
|
|
1748
1759
|
actionSheetRef: React.RefObject<any>;
|
|
1749
1760
|
/** Minimum height in total inches */
|
|
1750
1761
|
min?: number;
|
|
@@ -1756,14 +1767,17 @@ export interface HeightActionSheetProps {
|
|
|
1756
1767
|
|
|
1757
1768
|
export interface HyperlinkProps {
|
|
1758
1769
|
linkDefault?: boolean;
|
|
1759
|
-
// noExplicitAny:
|
|
1770
|
+
// biome-ignore lint/suspicious/noExplicitAny: linkify-it library's main export lacks a TypeScript type definition
|
|
1760
1771
|
linkify?: any;
|
|
1772
|
+
// biome-ignore lint/suspicious/noExplicitAny: StyleProp's generic is heterogeneous (TextStyle | ViewStyle) for link contexts
|
|
1761
1773
|
linkStyle?: StyleProp<any>;
|
|
1762
1774
|
linkText?: string | ((url: string) => string);
|
|
1763
1775
|
onPress?: (url: string) => void;
|
|
1764
1776
|
onLongPress?: (url: string, text: string) => void;
|
|
1777
|
+
// biome-ignore lint/suspicious/noExplicitAny: returned view props are spread onto a heterogeneous View; consumers pass arbitrary props
|
|
1765
1778
|
injectViewProps?: (url: string) => any;
|
|
1766
1779
|
children?: React.ReactNode;
|
|
1780
|
+
// biome-ignore lint/suspicious/noExplicitAny: StyleProp's generic is heterogeneous for the container which holds mixed Text/View children
|
|
1767
1781
|
style?: StyleProp<any>;
|
|
1768
1782
|
}
|
|
1769
1783
|
|
|
@@ -1913,12 +1927,12 @@ export interface ModalProps {
|
|
|
1913
1927
|
/**
|
|
1914
1928
|
* The function to call when the primary button is clicked.
|
|
1915
1929
|
*/
|
|
1916
|
-
// noExplicitAny: callback value type varies by consumer context
|
|
1930
|
+
// biome-ignore lint/suspicious/noExplicitAny: callback value type varies by consumer context
|
|
1917
1931
|
primaryButtonOnClick?: (value?: any) => void | Promise<void>;
|
|
1918
1932
|
/**
|
|
1919
1933
|
* The function to call when the secondary button is clicked.
|
|
1920
1934
|
*/
|
|
1921
|
-
// noExplicitAny: callback value type varies by consumer context
|
|
1935
|
+
// biome-ignore lint/suspicious/noExplicitAny: callback value type varies by consumer context
|
|
1922
1936
|
secondaryButtonOnClick?: (value?: any) => void | Promise<void>;
|
|
1923
1937
|
}
|
|
1924
1938
|
|
|
@@ -1927,11 +1941,12 @@ export interface NumberPickerActionSheetProps {
|
|
|
1927
1941
|
min: number;
|
|
1928
1942
|
max: number;
|
|
1929
1943
|
onChange: OnChangeCallback;
|
|
1944
|
+
// biome-ignore lint/suspicious/noExplicitAny: ActionSheet class lives in ActionSheet.tsx which imports from Common.ts; typing this would create a circular import
|
|
1930
1945
|
actionSheetRef: React.RefObject<any>;
|
|
1931
1946
|
}
|
|
1932
1947
|
|
|
1933
1948
|
export interface PageProps {
|
|
1934
|
-
// noExplicitAny: React Navigation type varies by navigation stack configuration
|
|
1949
|
+
// biome-ignore lint/suspicious/noExplicitAny: React Navigation type varies by navigation stack configuration
|
|
1935
1950
|
navigation?: any;
|
|
1936
1951
|
scroll?: boolean;
|
|
1937
1952
|
loading?: boolean;
|
|
@@ -1950,6 +1965,12 @@ export interface PageProps {
|
|
|
1950
1965
|
rightButtonOnClick?: () => void;
|
|
1951
1966
|
children?: ReactChildren;
|
|
1952
1967
|
onError?: (error: Error, stack: string) => void;
|
|
1968
|
+
/**
|
|
1969
|
+
* When true, wraps content in SafeAreaView so it respects top/bottom device
|
|
1970
|
+
* insets (camera notches, home indicator, status bar). Opt-in to avoid
|
|
1971
|
+
* regressing existing screens that handle insets at the navigation level.
|
|
1972
|
+
*/
|
|
1973
|
+
safeArea?: boolean;
|
|
1953
1974
|
}
|
|
1954
1975
|
|
|
1955
1976
|
export interface ProgressBarProps {
|
|
@@ -2252,6 +2273,7 @@ export interface TextFieldPickerActionSheetProps {
|
|
|
2252
2273
|
value?: string;
|
|
2253
2274
|
mode?: "date" | "time";
|
|
2254
2275
|
onChange: OnChangeCallback;
|
|
2276
|
+
// biome-ignore lint/suspicious/noExplicitAny: ActionSheet class lives in ActionSheet.tsx which imports from Common.ts; typing this would create a circular import
|
|
2255
2277
|
actionSheetRef: React.RefObject<any>;
|
|
2256
2278
|
}
|
|
2257
2279
|
|
|
@@ -2371,19 +2393,19 @@ export type TapToEditProps =
|
|
|
2371
2393
|
|
|
2372
2394
|
export interface BaseTapToEditProps extends Omit<FieldProps, "onChange" | "value"> {
|
|
2373
2395
|
title: string;
|
|
2374
|
-
// noExplicitAny: value type varies across TapToEdit field types (text, number, date, etc.)
|
|
2396
|
+
// biome-ignore lint/suspicious/noExplicitAny: value type varies across TapToEdit field types (text, number, date, etc.)
|
|
2375
2397
|
value: any;
|
|
2376
2398
|
|
|
2377
2399
|
/**
|
|
2378
2400
|
* Not required if not editable.
|
|
2379
2401
|
*/
|
|
2380
|
-
// noExplicitAny: value type varies across TapToEdit field types
|
|
2402
|
+
// biome-ignore lint/suspicious/noExplicitAny: value type varies across TapToEdit field types
|
|
2381
2403
|
setValue?: (value: any) => void;
|
|
2382
2404
|
|
|
2383
2405
|
/**
|
|
2384
2406
|
* Not required if not editable.
|
|
2385
2407
|
*/
|
|
2386
|
-
// noExplicitAny: value type varies across TapToEdit field types
|
|
2408
|
+
// biome-ignore lint/suspicious/noExplicitAny: value type varies across TapToEdit field types
|
|
2387
2409
|
onSave?: (value: any) => void | Promise<void>;
|
|
2388
2410
|
|
|
2389
2411
|
/**
|
|
@@ -2396,7 +2418,7 @@ export interface BaseTapToEditProps extends Omit<FieldProps, "onChange" | "value
|
|
|
2396
2418
|
* Enable edit mode from outside the component.
|
|
2397
2419
|
*/
|
|
2398
2420
|
isEditing?: boolean;
|
|
2399
|
-
// noExplicitAny: input value type varies across TapToEdit field types
|
|
2421
|
+
// biome-ignore lint/suspicious/noExplicitAny: input value type varies across TapToEdit field types
|
|
2400
2422
|
transform?: (value: any) => string;
|
|
2401
2423
|
/**
|
|
2402
2424
|
* Show a confirmation modal before saving the value.
|
|
@@ -2485,11 +2507,12 @@ export interface ModelFields {
|
|
|
2485
2507
|
|
|
2486
2508
|
export interface OpenAPISpec {
|
|
2487
2509
|
paths: {
|
|
2488
|
-
// noExplicitAny: OpenAPI path items are deeply accessed with chained property lookups
|
|
2510
|
+
// biome-ignore lint/suspicious/noExplicitAny: OpenAPI path items are deeply accessed with chained property lookups
|
|
2489
2511
|
[key: string]: any;
|
|
2490
2512
|
};
|
|
2491
2513
|
}
|
|
2492
2514
|
|
|
2515
|
+
// biome-ignore lint/suspicious/noExplicitAny: ModelFieldConfig is a passthrough for arbitrary field configuration objects from various model contexts
|
|
2493
2516
|
export type ModelFieldConfig = any;
|
|
2494
2517
|
|
|
2495
2518
|
export interface OpenAPIProviderProps {
|
|
@@ -2518,7 +2541,7 @@ export interface ModelAdminFieldConfig {
|
|
|
2518
2541
|
|
|
2519
2542
|
// The props for a custom column component for ModelAdmin.
|
|
2520
2543
|
export interface ModelAdminCustomComponentProps extends Omit<FieldProps, "name"> {
|
|
2521
|
-
// noExplicitAny: document shape varies by model used with ModelAdmin
|
|
2544
|
+
// biome-ignore lint/suspicious/noExplicitAny: document shape varies by model used with ModelAdmin
|
|
2522
2545
|
doc: any;
|
|
2523
2546
|
fieldKey: string; // Dot notation representation of the field.
|
|
2524
2547
|
// user: User;
|
|
@@ -2,6 +2,7 @@ import {describe, expect, it, mock} from "bun:test";
|
|
|
2
2
|
import {act, fireEvent} from "@testing-library/react-native";
|
|
3
3
|
|
|
4
4
|
import {ConsentFormScreen} from "./ConsentFormScreen";
|
|
5
|
+
import {SignatureField} from "./SignatureField";
|
|
5
6
|
import {renderWithTheme} from "./test-utils";
|
|
6
7
|
import type {ConsentFormPublic} from "./useConsentForms";
|
|
7
8
|
|
|
@@ -208,6 +209,108 @@ describe("ConsentFormScreen", () => {
|
|
|
208
209
|
expect(queryByTestId("consent-form-required-legend")).toBeNull();
|
|
209
210
|
});
|
|
210
211
|
|
|
212
|
+
it("confirms the modal and toggles the checkbox on", () => {
|
|
213
|
+
const form: ConsentFormPublic = {
|
|
214
|
+
...baseForm,
|
|
215
|
+
checkboxes: [{confirmationPrompt: "Are you sure?", label: "Tricky", required: true}],
|
|
216
|
+
};
|
|
217
|
+
const {getByTestId, getByText, queryByTestId} = renderWithTheme(
|
|
218
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
219
|
+
);
|
|
220
|
+
act(() => {
|
|
221
|
+
fireEvent.press(getByTestId("consent-form-checkbox-0"));
|
|
222
|
+
});
|
|
223
|
+
expect(getByText("Are you sure?")).toBeTruthy();
|
|
224
|
+
// Press the "Confirm" button inside the modal
|
|
225
|
+
act(() => {
|
|
226
|
+
fireEvent.press(getByText("Confirm"));
|
|
227
|
+
});
|
|
228
|
+
return new Promise<void>((resolve) => {
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
// Checkbox hint should be gone because the required checkbox was toggled on
|
|
231
|
+
expect(queryByTestId("consent-footer-checkboxes-hint")).toBeNull();
|
|
232
|
+
resolve();
|
|
233
|
+
}, 600);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("dismisses the modal without toggling the checkbox", () => {
|
|
238
|
+
const form: ConsentFormPublic = {
|
|
239
|
+
...baseForm,
|
|
240
|
+
checkboxes: [{confirmationPrompt: "Are you sure?", label: "Tricky", required: true}],
|
|
241
|
+
};
|
|
242
|
+
const {getByTestId, getByText} = renderWithTheme(
|
|
243
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
244
|
+
);
|
|
245
|
+
act(() => {
|
|
246
|
+
fireEvent.press(getByTestId("consent-form-checkbox-0"));
|
|
247
|
+
});
|
|
248
|
+
expect(getByText("Are you sure?")).toBeTruthy();
|
|
249
|
+
// Press the "Cancel" button inside the modal
|
|
250
|
+
act(() => {
|
|
251
|
+
fireEvent.press(getByText("Cancel"));
|
|
252
|
+
});
|
|
253
|
+
return new Promise<void>((resolve) => {
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
// The checkbox hint should still show because the checkbox was not toggled
|
|
256
|
+
expect(getByTestId("consent-footer-checkboxes-hint")).toBeTruthy();
|
|
257
|
+
resolve();
|
|
258
|
+
}, 600);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("auto-satisfies scroll requirement when content fits the viewport via contentSizeChange", () => {
|
|
263
|
+
const form = {...baseForm, requireScrollToBottom: true};
|
|
264
|
+
const {getByTestId, queryByTestId} = renderWithTheme(
|
|
265
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
266
|
+
);
|
|
267
|
+
const scroll = getByTestId("consent-form-scroll-view");
|
|
268
|
+
// First set the layout height, then content size smaller than layout
|
|
269
|
+
act(() => {
|
|
270
|
+
fireEvent(scroll, "layout", {nativeEvent: {layout: {height: 500}}});
|
|
271
|
+
});
|
|
272
|
+
act(() => {
|
|
273
|
+
fireEvent(scroll, "contentSizeChange", 0, 400);
|
|
274
|
+
});
|
|
275
|
+
expect(queryByTestId("consent-form-scroll-hint")).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("auto-satisfies scroll requirement when content fits the viewport via layout", () => {
|
|
279
|
+
const form = {...baseForm, requireScrollToBottom: true};
|
|
280
|
+
const {getByTestId, queryByTestId} = renderWithTheme(
|
|
281
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
282
|
+
);
|
|
283
|
+
const scroll = getByTestId("consent-form-scroll-view");
|
|
284
|
+
// First set the content size, then layout height larger than content
|
|
285
|
+
act(() => {
|
|
286
|
+
fireEvent(scroll, "contentSizeChange", 0, 300);
|
|
287
|
+
});
|
|
288
|
+
act(() => {
|
|
289
|
+
fireEvent(scroll, "layout", {nativeEvent: {layout: {height: 500}}});
|
|
290
|
+
});
|
|
291
|
+
expect(queryByTestId("consent-form-scroll-hint")).toBeNull();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("handleScroll returns early when already scrolled to bottom", () => {
|
|
295
|
+
const form = {...baseForm, requireScrollToBottom: false};
|
|
296
|
+
const {getByTestId} = renderWithTheme(
|
|
297
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
298
|
+
);
|
|
299
|
+
const scroll = getByTestId("consent-form-scroll-view");
|
|
300
|
+
// requireScrollToBottom is false so hasScrolledToBottom starts as true.
|
|
301
|
+
// Firing scroll should hit the early return at line 81.
|
|
302
|
+
act(() => {
|
|
303
|
+
fireEvent(scroll, "scroll", {
|
|
304
|
+
nativeEvent: {
|
|
305
|
+
contentOffset: {y: 0},
|
|
306
|
+
contentSize: {height: 1000},
|
|
307
|
+
layoutMeasurement: {height: 500},
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
expect(scroll).toBeTruthy();
|
|
312
|
+
});
|
|
313
|
+
|
|
211
314
|
it("shows the checkbox footer hint when a required checkbox is unchecked", () => {
|
|
212
315
|
const form: ConsentFormPublic = {
|
|
213
316
|
...baseForm,
|
|
@@ -232,4 +335,25 @@ describe("ConsentFormScreen", () => {
|
|
|
232
335
|
});
|
|
233
336
|
expect(queryByTestId("consent-footer-checkboxes-hint")).toBeNull();
|
|
234
337
|
});
|
|
338
|
+
|
|
339
|
+
it("exercises SignatureField onChange, onStart, and onEnd callbacks", () => {
|
|
340
|
+
const form: ConsentFormPublic = {
|
|
341
|
+
...baseForm,
|
|
342
|
+
captureSignature: true,
|
|
343
|
+
};
|
|
344
|
+
const {UNSAFE_getByType} = renderWithTheme(
|
|
345
|
+
<ConsentFormScreen form={form} locale="en" onAgree={() => {}} />
|
|
346
|
+
);
|
|
347
|
+
const sig = UNSAFE_getByType(SignatureField);
|
|
348
|
+
act(() => {
|
|
349
|
+
sig.props.onChange("data:image/png;base64,abc");
|
|
350
|
+
});
|
|
351
|
+
act(() => {
|
|
352
|
+
sig.props.onStart();
|
|
353
|
+
});
|
|
354
|
+
act(() => {
|
|
355
|
+
sig.props.onEnd();
|
|
356
|
+
});
|
|
357
|
+
expect(sig).toBeTruthy();
|
|
358
|
+
});
|
|
235
359
|
});
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import React, {useState} from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type LayoutChangeEvent,
|
|
4
|
+
type NativeScrollEvent,
|
|
5
|
+
type NativeSyntheticEvent,
|
|
6
|
+
Platform,
|
|
7
|
+
Pressable,
|
|
8
|
+
ScrollView,
|
|
9
|
+
View,
|
|
10
|
+
} from "react-native";
|
|
3
11
|
|
|
4
12
|
import {Box} from "./Box";
|
|
5
13
|
import {Button} from "./Button";
|
|
@@ -62,7 +70,7 @@ export const ConsentFormScreen: React.FC<ConsentFormScreenProps> = ({
|
|
|
62
70
|
}
|
|
63
71
|
};
|
|
64
72
|
|
|
65
|
-
const handleLayout = (event:
|
|
73
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
66
74
|
const h = event.nativeEvent.layout.height;
|
|
67
75
|
setLayoutHeight(h);
|
|
68
76
|
if (!hasScrolledToBottom && contentHeight > 0 && h > 0 && contentHeight <= h) {
|
|
@@ -70,7 +78,7 @@ export const ConsentFormScreen: React.FC<ConsentFormScreenProps> = ({
|
|
|
70
78
|
}
|
|
71
79
|
};
|
|
72
80
|
|
|
73
|
-
const handleScroll = (event:
|
|
81
|
+
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
74
82
|
if (hasScrolledToBottom) {
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
@@ -160,7 +168,7 @@ export const ConsentFormScreen: React.FC<ConsentFormScreenProps> = ({
|
|
|
160
168
|
);
|
|
161
169
|
|
|
162
170
|
return (
|
|
163
|
-
<Page color="base" footer={footer} maxWidth="100%" scroll={false} title={form.title}>
|
|
171
|
+
<Page color="base" footer={footer} maxWidth="100%" safeArea scroll={false} title={form.title}>
|
|
164
172
|
<ScrollView
|
|
165
173
|
onContentSizeChange={handleContentSizeChange}
|
|
166
174
|
onLayout={handleLayout}
|
|
@@ -206,7 +214,11 @@ export const ConsentFormScreen: React.FC<ConsentFormScreenProps> = ({
|
|
|
206
214
|
)}
|
|
207
215
|
|
|
208
216
|
{Boolean(form.captureSignature) && (
|
|
209
|
-
<
|
|
217
|
+
<View
|
|
218
|
+
onTouchEnd={Platform.OS === "ios" ? () => setScrollEnabled(true) : undefined}
|
|
219
|
+
onTouchStart={Platform.OS === "ios" ? () => setScrollEnabled(false) : undefined}
|
|
220
|
+
testID="consent-form-signature"
|
|
221
|
+
>
|
|
210
222
|
<SignatureField
|
|
211
223
|
onChange={(value) => setSignatureValue(value)}
|
|
212
224
|
onEnd={() => setScrollEnabled(true)}
|
|
@@ -214,7 +226,7 @@ export const ConsentFormScreen: React.FC<ConsentFormScreenProps> = ({
|
|
|
214
226
|
title="Signature"
|
|
215
227
|
value={signatureValue}
|
|
216
228
|
/>
|
|
217
|
-
</
|
|
229
|
+
</View>
|
|
218
230
|
)}
|
|
219
231
|
|
|
220
232
|
{Boolean(form.requireScrollToBottom && !hasScrolledToBottom) && (
|
package/src/ConsentNavigator.tsx
CHANGED
|
@@ -10,11 +10,12 @@ import type {SubmitConsentBody} from "./useSubmitConsent";
|
|
|
10
10
|
import {useSubmitConsent} from "./useSubmitConsent";
|
|
11
11
|
|
|
12
12
|
interface ConsentNavigatorProps {
|
|
13
|
+
// biome-ignore lint/suspicious/noExplicitAny: RTK Query api instance is a complex generic type that varies per consumer
|
|
13
14
|
api: any;
|
|
14
15
|
baseUrl?: string;
|
|
15
16
|
children: React.ReactNode;
|
|
16
17
|
extraScreens?: React.ReactNode[];
|
|
17
|
-
onError?: (error:
|
|
18
|
+
onError?: (error: unknown) => void;
|
|
18
19
|
variables?: Record<string, string>;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -48,7 +49,8 @@ export const ConsentNavigator: React.FC<ConsentNavigatorProps> = ({
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
if (error) {
|
|
51
|
-
const
|
|
52
|
+
const errorObj = error as {status?: number; originalStatus?: number};
|
|
53
|
+
const status = errorObj?.status ?? errorObj?.originalStatus;
|
|
52
54
|
console.warn("[ConsentNavigator] Error fetching pending consents:", {error, status});
|
|
53
55
|
// On auth errors, pass through to let the app handle re-authentication
|
|
54
56
|
if (status === 401 || status === 403) {
|
|
@@ -80,7 +82,7 @@ export const ConsentNavigator: React.FC<ConsentNavigatorProps> = ({
|
|
|
80
82
|
`[ConsentNavigator] Showing extra screen ${extraScreenIndex + 1}/${validExtraScreens.length}`
|
|
81
83
|
);
|
|
82
84
|
if (React.isValidElement(currentScreen)) {
|
|
83
|
-
return React.cloneElement(currentScreen as React.ReactElement<
|
|
85
|
+
return React.cloneElement(currentScreen as React.ReactElement<{onNext?: () => void}>, {
|
|
84
86
|
onNext: () => setExtraScreenIndex((i) => i + 1),
|
|
85
87
|
});
|
|
86
88
|
}
|
|
@@ -101,7 +101,9 @@ export const CustomSelectField: FC<CustomSelectFieldProps> = ({
|
|
|
101
101
|
<TextField
|
|
102
102
|
disabled={disabled}
|
|
103
103
|
id="customOptions"
|
|
104
|
-
inputRef={(ref:
|
|
104
|
+
inputRef={(ref: TextInput | null) => {
|
|
105
|
+
textInputRef.current = ref;
|
|
106
|
+
}}
|
|
105
107
|
onChange={onChange}
|
|
106
108
|
placeholder="None selected"
|
|
107
109
|
type="text"
|