@myazahq/kyc-sdk-react-native 2.4.0 → 2.6.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/README.md +151 -5
- package/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +77 -0
- package/app.plugin.js +89 -8
- package/ios/HybridMyazaFaceDetector.swift +16 -3
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
- package/package.json +14 -2
- package/src/MyazaBiometricAuth.tsx +119 -0
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/CountryField.tsx +8 -0
- package/src/components/CountryRegionPicker.tsx +184 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +53 -67
- package/src/components/DialCodeRow.tsx +102 -0
- package/src/components/FramedMapPicker.tsx +122 -0
- package/src/components/GeoBadge.tsx +34 -0
- package/src/components/Icon.tsx +2 -176
- package/src/components/KycFlow.tsx +60 -32
- package/src/components/LineSkeleton.tsx +100 -0
- package/src/components/MapChrome.tsx +67 -0
- package/src/components/MapPinMarker.tsx +54 -0
- package/src/components/MapPinPicker.tsx +179 -0
- package/src/components/MilestoneTrack.tsx +155 -0
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaInput.tsx +39 -4
- package/src/components/MyazaSelect.tsx +2 -1
- package/src/components/OptionRow.tsx +22 -3
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +45 -4
- package/src/components/PoweredBy.tsx +5 -5
- package/src/components/PresenceBlocks.tsx +153 -0
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepView.tsx +43 -0
- package/src/components/StickyActions.tsx +56 -0
- package/src/components/dialCodeRows.ts +64 -0
- package/src/components/glass/FloatingSheet.tsx +73 -21
- package/src/components/glass/GlassSheet.tsx +14 -2
- package/src/components/icon-map.ts +176 -0
- package/src/components/stepHeaderMeta.tsx +45 -5
- package/src/config/addressCollection.ts +125 -0
- package/src/config/biometricOptions.ts +100 -0
- package/src/config/business.ts +26 -7
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +20 -3
- package/src/config/consentStep.ts +19 -0
- package/src/config/keyPeople.ts +111 -7
- package/src/config/keyPeopleCorporate.ts +19 -0
- package/src/config/keyPeopleOwnerTypes.ts +12 -0
- package/src/config/keyPeopleOwners.ts +26 -0
- package/src/config/keyPeoplePrefill.ts +138 -0
- package/src/config/keyPeopleSectionDefs.ts +65 -0
- package/src/config/keyPeopleSections.ts +167 -0
- package/src/config/proofOfAddress.ts +54 -11
- package/src/config/regions.ts +24 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +100 -6
- package/src/config/theme.ts +8 -2
- package/src/config/uploadLimits.ts +35 -0
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +16 -5
- package/src/emrtd/activeAuth.ts +100 -0
- package/src/emrtd/extras.ts +76 -0
- package/src/emrtd/files.ts +5 -0
- package/src/emrtd/index.ts +1 -0
- package/src/emrtd/read.ts +8 -1
- package/src/emrtd/session.ts +41 -0
- package/src/emrtd/stages.ts +11 -0
- package/src/index.ts +54 -1
- package/src/lib/address-current-location.ts +171 -0
- package/src/lib/address-field-modes.ts +119 -0
- package/src/lib/address-flow.ts +172 -0
- package/src/lib/address-helpers.ts +48 -0
- package/src/lib/address-line.ts +76 -0
- package/src/lib/address-pin-move.ts +97 -0
- package/src/lib/address-step-recovery.ts +63 -0
- package/src/lib/authed-image.ts +78 -0
- package/src/lib/biometric-auth.ts +52 -0
- package/src/lib/biometric-copy.ts +51 -0
- package/src/lib/captureRing.ts +83 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/country-adoption.ts +89 -0
- package/src/lib/inferred-country.ts +65 -0
- package/src/lib/livenessLayout.ts +49 -0
- package/src/lib/map-frame.ts +159 -0
- package/src/lib/map-tiles.ts +143 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/poa-country-gate.ts +27 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/result-copy.ts +116 -0
- package/src/lib/result-wait.ts +53 -0
- package/src/lib/review-map-surface.ts +26 -0
- package/src/lib/scope.ts +31 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/selfie-upload-wait.ts +79 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/street-view-fov.ts +42 -0
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/lib/webview-available.ts +47 -0
- package/src/liveness/useLiveness.ts +6 -1
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/presence/background-math.ts +100 -0
- package/src/presence/background-store.ts +82 -0
- package/src/presence/background.ts +165 -0
- package/src/presence/foreground-service.ts +193 -0
- package/src/presence/fs.ts +51 -0
- package/src/presence/geofence.ts +41 -0
- package/src/presence/math.ts +44 -0
- package/src/presence/post.ts +40 -0
- package/src/presence/report.ts +87 -0
- package/src/presence/sampler.ts +110 -0
- package/src/presence/status.ts +92 -0
- package/src/presence/store.ts +94 -0
- package/src/presence/tier.ts +36 -0
- package/src/presence/watch-wait.ts +81 -0
- package/src/screens/AddressCountryControl.tsx +115 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +112 -0
- package/src/screens/BusinessDetailsStep.tsx +133 -145
- package/src/screens/BusinessDocumentSlot.tsx +5 -5
- package/src/screens/BusinessDocumentsStep.tsx +5 -56
- package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
- package/src/screens/BusinessPickedCard.tsx +86 -0
- package/src/screens/BusinessPickedSection.tsx +51 -0
- package/src/screens/BusinessRegistryPickers.tsx +67 -0
- package/src/screens/BusinessSandboxToggle.tsx +114 -0
- package/src/screens/BusinessSearch.tsx +199 -0
- package/src/screens/BusinessSearchResults.tsx +115 -0
- package/src/screens/CompanyInfoFields.tsx +91 -51
- package/src/screens/ConsentStep.tsx +37 -17
- package/src/screens/ContactActions.tsx +54 -0
- package/src/screens/ContactDestinationField.tsx +5 -0
- package/src/screens/ContactEntryPanel.tsx +75 -0
- package/src/screens/ContactVerificationStep.tsx +43 -42
- package/src/screens/CountrySelectStep.tsx +13 -147
- package/src/screens/IdTypeStep.tsx +12 -5
- package/src/screens/KeepLinksSheet.tsx +88 -0
- package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
- package/src/screens/KeyPeopleAwaitList.tsx +126 -0
- package/src/screens/KeyPeoplePending.tsx +154 -0
- package/src/screens/KeyPeopleSection.tsx +162 -0
- package/src/screens/KeyPeopleSectionsList.tsx +103 -0
- package/src/screens/KeyPeopleUboExemption.tsx +72 -0
- package/src/screens/KeyPersonCard.tsx +36 -4
- package/src/screens/KeyPersonForm.tsx +195 -36
- package/src/screens/KeyPersonKindToggle.tsx +68 -0
- package/src/screens/KeyPersonOwners.tsx +109 -0
- package/src/screens/KeyPersonRoleChips.tsx +68 -0
- package/src/screens/KeyPersonSheet.tsx +92 -108
- package/src/screens/LivenessAvatar.tsx +15 -3
- package/src/screens/LivenessStep.tsx +172 -52
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/PoaDocumentTypeList.tsx +66 -0
- package/src/screens/ProofOfAddressParts.tsx +151 -0
- package/src/screens/ProofOfAddressStep.tsx +58 -96
- package/src/screens/SubmittedBadge.tsx +25 -0
- package/src/screens/SubmittedError.tsx +64 -0
- package/src/screens/SubmittedResult.tsx +117 -0
- package/src/screens/SubmittedStep.tsx +82 -119
- package/src/screens/SubmittedSuccess.tsx +127 -0
- package/src/screens/SubmittedWaiting.tsx +45 -0
- package/src/screens/address/AddressEntranceStep.tsx +161 -0
- package/src/screens/address/AddressIntroGate.tsx +148 -0
- package/src/screens/address/AddressMapStub.tsx +50 -0
- package/src/screens/address/AddressPinStep.tsx +178 -0
- package/src/screens/address/AddressReviewStep.tsx +162 -0
- package/src/screens/address/AddressSandboxTabs.tsx +158 -0
- package/src/screens/address/AddressSearchField.tsx +115 -0
- package/src/screens/address/AddressSearchStep.tsx +94 -0
- package/src/screens/address/CurrentLocationRow.tsx +135 -0
- package/src/screens/address/DetailsSheet.tsx +175 -0
- package/src/screens/address/DetailsSheetFields.tsx +184 -0
- package/src/screens/address/EntranceDropzone.tsx +178 -0
- package/src/screens/address/EntranceFraming.tsx +66 -0
- package/src/screens/address/EntrancePills.tsx +60 -0
- package/src/screens/address/FramedStreetView.tsx +155 -0
- package/src/screens/address/IntroDisclosures.tsx +210 -0
- package/src/screens/address/LabelDecisionRow.tsx +113 -0
- package/src/screens/address/PinSummaryRow.tsx +105 -0
- package/src/screens/address/ReviewAddressBand.tsx +111 -0
- package/src/screens/address/ReviewEntranceThumbs.tsx +64 -0
- package/src/screens/address/ReviewMapPicture.tsx +106 -0
- package/src/screens/address/SearchResults.tsx +105 -0
- package/src/screens/address/SearchScreen.tsx +176 -0
- package/src/screens/address/SkipForNow.tsx +43 -0
- package/src/screens/address/StreetViewChrome.tsx +81 -0
- package/src/screens/address/detail-values.ts +22 -0
- package/src/screens/address/fix-source.ts +27 -0
- package/src/screens/address/index.ts +8 -0
- package/src/screens/address/meta.ts +44 -0
- package/src/screens/address/use-address-flow.ts +200 -0
- package/src/screens/address/use-label-pin.ts +80 -0
- package/src/screens/address/use-pin-actions.ts +192 -0
- package/src/screens/biometric/BiometricAuthFlow.tsx +170 -0
- package/src/screens/consent/model.ts +79 -10
- package/src/screens/liveness/CaptureRing.tsx +91 -0
- package/src/screens/liveness/LivenessCamera.tsx +63 -0
- package/src/screens/liveness/LivenessHandover.tsx +43 -0
- package/src/screens/liveness/LivenessOutcome.tsx +4 -3
- package/src/screens/liveness/SelfiePreview.tsx +13 -4
- package/src/screens/liveness/ShutterFlash.tsx +25 -0
- package/src/screens/liveness/index.ts +4 -0
- package/src/screens/liveness/useSelfieUpload.ts +23 -3
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +7 -18
- package/src/screens/useAddressPhotoAttach.ts +77 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/screens/useBusinessDocumentAttach.ts +93 -0
- package/src/screens/usePoaAttach.ts +12 -15
- package/src/services/api-biometric.ts +37 -0
- package/src/services/api-types-biometric.ts +41 -0
- package/src/services/api-types.ts +251 -3
- package/src/services/api-verify-types.ts +90 -1
- package/src/services/api.ts +237 -3
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/location.ts +234 -0
- package/src/services/uploadErrors.ts +30 -0
- package/src/services/workflowGate.ts +8 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/address.ts +97 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +44 -4
- package/src/store/kycStore.ts +352 -16
- package/src/store/serverConfig.ts +20 -0
- package/src/store/session.ts +227 -0
- package/src/store/state.ts +289 -2
- package/src/store/submit.ts +80 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +70 -2
- package/src/types/verification.ts +13 -1
- package/src/types/workflow.ts +112 -1
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
|
+
import { useTheme } from './runtime';
|
|
6
|
+
import { Icon, type IconName } from './Icon';
|
|
7
|
+
import { MyazaText } from './Typography';
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// The presence story's three-node track, shared by the intro gate (the promise)
|
|
11
|
+
// and the success card (the same promise, now running) so the two read as two
|
|
12
|
+
// frames of ONE story rather than two unrelated notices.
|
|
13
|
+
//
|
|
14
|
+
// Nodes ahead of the active one are TINTED, not greyed out: they are the plan,
|
|
15
|
+
// not disabled controls. Flat fills only.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface Milestone {
|
|
19
|
+
icon: IconName;
|
|
20
|
+
/** The when: "Your part", "Today", "Then". */
|
|
21
|
+
stage: string;
|
|
22
|
+
title: string;
|
|
23
|
+
caption: string;
|
|
24
|
+
state: 'active' | 'ahead';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function MilestoneTrack({
|
|
28
|
+
milestones,
|
|
29
|
+
numbered = false,
|
|
30
|
+
nodeSize = 40,
|
|
31
|
+
}: {
|
|
32
|
+
milestones: readonly Milestone[];
|
|
33
|
+
/** Wear the step number on each node — the sequence is the point on the
|
|
34
|
+
* intro screen, where nothing has happened yet. */
|
|
35
|
+
numbered?: boolean;
|
|
36
|
+
/** Node diameter, and therefore where the connecting rail has to sit: 40 on
|
|
37
|
+
* the primer (web h-10), 32 on the success card (web h-8). */
|
|
38
|
+
nodeSize?: number;
|
|
39
|
+
}): React.ReactElement {
|
|
40
|
+
const { colors } = useTheme();
|
|
41
|
+
const NODE = nodeSize;
|
|
42
|
+
// The active node's halo: web's `shadow-[0_0_0_5px] shadow-primary/15`
|
|
43
|
+
// (4px on the smaller success-card node), drawn as a ring behind it.
|
|
44
|
+
const halo = NODE >= 40 ? 5 : 4;
|
|
45
|
+
return (
|
|
46
|
+
<View style={{ paddingHorizontal: spacing.md, paddingVertical: spacing.md }}>
|
|
47
|
+
{milestones.map((m, i) => {
|
|
48
|
+
const active = m.state === 'active';
|
|
49
|
+
const last = i === milestones.length - 1;
|
|
50
|
+
return (
|
|
51
|
+
<View
|
|
52
|
+
key={m.title}
|
|
53
|
+
style={{ flexDirection: 'row', gap: spacing.md, paddingBottom: last ? 0 : spacing.md }}
|
|
54
|
+
>
|
|
55
|
+
{!last ? (
|
|
56
|
+
<View
|
|
57
|
+
pointerEvents="none"
|
|
58
|
+
style={{
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
left: NODE / 2 - 0.5,
|
|
61
|
+
top: NODE + 4,
|
|
62
|
+
bottom: 0,
|
|
63
|
+
width: 1,
|
|
64
|
+
backgroundColor: `${colors.primary}33`,
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
) : null}
|
|
68
|
+
|
|
69
|
+
<View style={{ width: NODE }}>
|
|
70
|
+
{active ? (
|
|
71
|
+
<View
|
|
72
|
+
pointerEvents="none"
|
|
73
|
+
style={{
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
left: -halo,
|
|
76
|
+
top: -halo,
|
|
77
|
+
width: NODE + halo * 2,
|
|
78
|
+
height: NODE + halo * 2,
|
|
79
|
+
borderRadius: radius.full,
|
|
80
|
+
backgroundColor: `${colors.primary}26`,
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
) : null}
|
|
84
|
+
<View
|
|
85
|
+
style={{
|
|
86
|
+
width: NODE,
|
|
87
|
+
height: NODE,
|
|
88
|
+
borderRadius: radius.full,
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
backgroundColor: active ? colors.primary : `${colors.primary}1A`,
|
|
92
|
+
...(active ? {} : { borderWidth: 1, borderColor: `${colors.primary}33` }),
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<Icon name={m.icon} size={NODE * 0.45} color={active ? colors.onPrimary : colors.primary} />
|
|
96
|
+
</View>
|
|
97
|
+
{numbered ? (
|
|
98
|
+
<View
|
|
99
|
+
style={{
|
|
100
|
+
position: 'absolute',
|
|
101
|
+
right: -4,
|
|
102
|
+
top: -4,
|
|
103
|
+
width: 18,
|
|
104
|
+
height: 18,
|
|
105
|
+
borderRadius: radius.full,
|
|
106
|
+
borderWidth: 1,
|
|
107
|
+
borderColor: active ? colors.primary : colors.border,
|
|
108
|
+
backgroundColor: colors.background,
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<MyazaText
|
|
114
|
+
variant="bodySmall"
|
|
115
|
+
color={active ? colors.primary : colors.textSecondary}
|
|
116
|
+
style={{ fontSize: 9, fontWeight: '700', lineHeight: 11 }}
|
|
117
|
+
>
|
|
118
|
+
{i + 1}
|
|
119
|
+
</MyazaText>
|
|
120
|
+
</View>
|
|
121
|
+
) : null}
|
|
122
|
+
</View>
|
|
123
|
+
|
|
124
|
+
<View style={{ flex: 1, paddingTop: 2 }}>
|
|
125
|
+
{/* Uppercased by STYLE, not by the string: a screen reader
|
|
126
|
+
should hear the words, not shouted initials. */}
|
|
127
|
+
<MyazaText
|
|
128
|
+
variant="bodySmall"
|
|
129
|
+
color={active ? colors.primary : colors.textSecondary}
|
|
130
|
+
style={{
|
|
131
|
+
fontSize: 10,
|
|
132
|
+
fontWeight: '600',
|
|
133
|
+
letterSpacing: 0.8,
|
|
134
|
+
textTransform: 'uppercase',
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{m.stage}
|
|
138
|
+
</MyazaText>
|
|
139
|
+
<MyazaText variant="bodyMedium" style={{ fontWeight: '600', marginTop: 1 }}>
|
|
140
|
+
{m.title}
|
|
141
|
+
</MyazaText>
|
|
142
|
+
<MyazaText
|
|
143
|
+
variant="bodySmall"
|
|
144
|
+
color={colors.textSecondary}
|
|
145
|
+
style={{ marginTop: 2 }}
|
|
146
|
+
>
|
|
147
|
+
{m.caption}
|
|
148
|
+
</MyazaText>
|
|
149
|
+
</View>
|
|
150
|
+
</View>
|
|
151
|
+
);
|
|
152
|
+
})}
|
|
153
|
+
</View>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
|
+
import { resolveIdTypeDefinition } from '../config/idTypes';
|
|
6
|
+
import type { MultiIdPlan } from '../lib/multi-id';
|
|
7
|
+
import type { MultiIdSlot } from '../store/state';
|
|
8
|
+
import { useEffectiveCountry, useKyc, useTheme } from './runtime';
|
|
9
|
+
import { MyazaText } from './Typography';
|
|
10
|
+
import { Icon } from './Icon';
|
|
11
|
+
|
|
12
|
+
// The multi-ID run's position strip: one chip per check, filling in with the
|
|
13
|
+
// picked ID's name as each check commits, the current one highlighted.
|
|
14
|
+
//
|
|
15
|
+
// A PORT of the web SDK's MultiIdProgress. Rendered above the picker, the
|
|
16
|
+
// evidence steps and liveness so a reader always knows which of the run's IDs
|
|
17
|
+
// the screen is about and how many remain — without it a three-ID run is three
|
|
18
|
+
// visits to the same-looking screen with nothing saying which is which.
|
|
19
|
+
|
|
20
|
+
export function MultiIdProgress({ plan }: { plan: MultiIdPlan }): React.ReactElement {
|
|
21
|
+
const { colors } = useTheme();
|
|
22
|
+
const country = useEffectiveCountry();
|
|
23
|
+
const serverConfig = useKyc((s) => s.serverConfig);
|
|
24
|
+
const slots: MultiIdSlot[] = useKyc((s) => s.multiIdSlots);
|
|
25
|
+
|
|
26
|
+
const labelFor = (idType: string): string => {
|
|
27
|
+
const row = serverConfig.idTypes.find(
|
|
28
|
+
(r) => r.country === country && r.idType === idType,
|
|
29
|
+
);
|
|
30
|
+
return resolveIdTypeDefinition(country, idType, {
|
|
31
|
+
label: row?.label,
|
|
32
|
+
requiresDocumentCapture: row?.requiresDocumentCapture,
|
|
33
|
+
scanSides: row?.scanSides,
|
|
34
|
+
supportsNfc: row?.supportsNfc,
|
|
35
|
+
}).label;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View
|
|
40
|
+
accessibilityLabel={`ID ${Math.min(plan.index + 1, plan.count)} of ${plan.count}`}
|
|
41
|
+
style={{
|
|
42
|
+
flexDirection: 'row',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
gap: spacing.sm,
|
|
45
|
+
marginBottom: spacing.md,
|
|
46
|
+
paddingHorizontal: spacing.md,
|
|
47
|
+
paddingVertical: spacing.sm,
|
|
48
|
+
borderRadius: radius.md,
|
|
49
|
+
borderWidth: 1,
|
|
50
|
+
borderColor: colors.border,
|
|
51
|
+
backgroundColor: colors.backgroundSecondary,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{Array.from({ length: plan.count }, (_, i) => {
|
|
55
|
+
const committed = slots[i];
|
|
56
|
+
const active = i === plan.index;
|
|
57
|
+
return (
|
|
58
|
+
<View
|
|
59
|
+
key={i}
|
|
60
|
+
style={{ flexDirection: 'row', alignItems: 'center', gap: spacing.sm, minWidth: 0 }}
|
|
61
|
+
>
|
|
62
|
+
{i > 0 && (
|
|
63
|
+
<View
|
|
64
|
+
style={{
|
|
65
|
+
height: 1,
|
|
66
|
+
width: 16,
|
|
67
|
+
backgroundColor: committed || active ? colors.primary : colors.border,
|
|
68
|
+
opacity: committed || active ? 0.5 : 1,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
<View
|
|
73
|
+
style={{
|
|
74
|
+
height: 20,
|
|
75
|
+
width: 20,
|
|
76
|
+
borderRadius: 10,
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
justifyContent: 'center',
|
|
79
|
+
backgroundColor: committed ? colors.primary : 'transparent',
|
|
80
|
+
borderWidth: committed ? 0 : active ? 2 : 1,
|
|
81
|
+
borderColor: active ? colors.primary : colors.border,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{committed ? (
|
|
85
|
+
<Icon name="check" size={12} color={colors.onPrimary} />
|
|
86
|
+
) : (
|
|
87
|
+
<MyazaText
|
|
88
|
+
variant="bodySmall"
|
|
89
|
+
color={active ? colors.primary : colors.textMuted}
|
|
90
|
+
>
|
|
91
|
+
{String(i + 1)}
|
|
92
|
+
</MyazaText>
|
|
93
|
+
)}
|
|
94
|
+
</View>
|
|
95
|
+
<MyazaText
|
|
96
|
+
variant="bodySmall"
|
|
97
|
+
color={active ? colors.textDark : colors.textMuted}
|
|
98
|
+
numberOfLines={1}
|
|
99
|
+
>
|
|
100
|
+
{committed ? labelFor(committed.idType) : `ID ${i + 1}`}
|
|
101
|
+
</MyazaText>
|
|
102
|
+
</View>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -21,6 +21,9 @@ export interface MyazaInputProps {
|
|
|
21
21
|
value: string;
|
|
22
22
|
onChangeText: (text: string) => void;
|
|
23
23
|
label?: string;
|
|
24
|
+
/** Marks the label with an asterisk in the error colour: a field the flow
|
|
25
|
+
* will not continue without. */
|
|
26
|
+
required?: boolean;
|
|
24
27
|
placeholder?: string;
|
|
25
28
|
error?: string | null;
|
|
26
29
|
helper?: string;
|
|
@@ -39,6 +42,21 @@ export interface MyazaInputProps {
|
|
|
39
42
|
/** Rendered inside the field, after the text — a unit like "%". Same
|
|
40
43
|
* bordered-row treatment as `prefix`. */
|
|
41
44
|
suffix?: React.ReactNode;
|
|
45
|
+
/** Field height override — the results filter runs at 40 where every other
|
|
46
|
+
* input is 48, matching the web SDK's h-10 filter beside h-12 inputs. */
|
|
47
|
+
height?: number;
|
|
48
|
+
/** Text size override, for the compact filter (web: text-sm = 14). */
|
|
49
|
+
fontSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Off for proper-noun fields (company names, registration numbers): iOS
|
|
52
|
+
* autocorrect rewrites them right before the person submits, so a search
|
|
53
|
+
* for the company they typed silently becomes a search for a word the
|
|
54
|
+
* keyboard preferred. Defaults on, like the platform.
|
|
55
|
+
*/
|
|
56
|
+
autoCorrect?: boolean;
|
|
57
|
+
/** Multi-line entry (directions, notes): three visible lines, text anchored
|
|
58
|
+
* to the top on Android (which centres multiline text by default). */
|
|
59
|
+
multiline?: boolean;
|
|
42
60
|
onFocus?: FocusHandler;
|
|
43
61
|
onBlur?: BlurHandler;
|
|
44
62
|
}
|
|
@@ -47,6 +65,7 @@ export function MyazaInput({
|
|
|
47
65
|
value,
|
|
48
66
|
onChangeText,
|
|
49
67
|
label,
|
|
68
|
+
required = false,
|
|
50
69
|
placeholder,
|
|
51
70
|
error,
|
|
52
71
|
helper,
|
|
@@ -54,9 +73,13 @@ export function MyazaInput({
|
|
|
54
73
|
autoCapitalize = 'none',
|
|
55
74
|
maxLength,
|
|
56
75
|
editable = true,
|
|
76
|
+
multiline = false,
|
|
57
77
|
autoFocus = false,
|
|
58
78
|
prefix,
|
|
59
79
|
suffix,
|
|
80
|
+
height,
|
|
81
|
+
fontSize,
|
|
82
|
+
autoCorrect = true,
|
|
60
83
|
onFocus,
|
|
61
84
|
onBlur,
|
|
62
85
|
}: MyazaInputProps): React.ReactElement {
|
|
@@ -86,6 +109,11 @@ export function MyazaInput({
|
|
|
86
109
|
{label ? (
|
|
87
110
|
<MyazaText variant="label" style={{ marginBottom: spacing.sm }}>
|
|
88
111
|
{label}
|
|
112
|
+
{required ? (
|
|
113
|
+
<MyazaText variant="label" color={colors.error}>
|
|
114
|
+
{' *'}
|
|
115
|
+
</MyazaText>
|
|
116
|
+
) : null}
|
|
89
117
|
</MyazaText>
|
|
90
118
|
) : null}
|
|
91
119
|
{prefix || suffix ? (
|
|
@@ -95,7 +123,7 @@ export function MyazaInput({
|
|
|
95
123
|
style={{
|
|
96
124
|
flexDirection: 'row',
|
|
97
125
|
alignItems: 'center',
|
|
98
|
-
height: sizing.inputHeight,
|
|
126
|
+
height: height ?? sizing.inputHeight,
|
|
99
127
|
borderWidth,
|
|
100
128
|
borderColor,
|
|
101
129
|
borderRadius: radius.sm,
|
|
@@ -112,12 +140,14 @@ export function MyazaInput({
|
|
|
112
140
|
placeholderTextColor={colors.textMuted}
|
|
113
141
|
keyboardType={keyboardType}
|
|
114
142
|
autoCapitalize={autoCapitalize}
|
|
143
|
+
autoCorrect={autoCorrect}
|
|
144
|
+
spellCheck={autoCorrect}
|
|
115
145
|
maxLength={maxLength}
|
|
116
146
|
editable={editable}
|
|
117
147
|
autoFocus={autoFocus}
|
|
118
148
|
onFocus={handleFocus}
|
|
119
149
|
onBlur={handleBlur}
|
|
120
|
-
style={{ flex: 1, height: '100%', color: colors.textDark, fontSize: 16, fontFamily }}
|
|
150
|
+
style={{ flex: 1, height: '100%', color: colors.textDark, fontSize: fontSize ?? 16, fontFamily }}
|
|
121
151
|
/>
|
|
122
152
|
{suffix ? <View style={{ width: spacing.sm }} /> : null}
|
|
123
153
|
{suffix}
|
|
@@ -130,13 +160,18 @@ export function MyazaInput({
|
|
|
130
160
|
placeholderTextColor={colors.textMuted}
|
|
131
161
|
keyboardType={keyboardType}
|
|
132
162
|
autoCapitalize={autoCapitalize}
|
|
163
|
+
autoCorrect={autoCorrect}
|
|
164
|
+
spellCheck={autoCorrect}
|
|
133
165
|
maxLength={maxLength}
|
|
134
166
|
editable={editable}
|
|
135
167
|
autoFocus={autoFocus}
|
|
168
|
+
multiline={multiline}
|
|
169
|
+
numberOfLines={multiline ? 3 : 1}
|
|
170
|
+
textAlignVertical={multiline ? 'top' : 'center'}
|
|
136
171
|
onFocus={handleFocus}
|
|
137
172
|
onBlur={handleBlur}
|
|
138
173
|
style={{
|
|
139
|
-
height: sizing.inputHeight,
|
|
174
|
+
height: height ?? (multiline ? sizing.inputHeight * 2 : sizing.inputHeight),
|
|
140
175
|
borderWidth,
|
|
141
176
|
borderColor,
|
|
142
177
|
borderRadius: radius.sm,
|
|
@@ -145,7 +180,7 @@ export function MyazaInput({
|
|
|
145
180
|
paddingHorizontal: spacing.md + (2 - borderWidth),
|
|
146
181
|
color: colors.textDark,
|
|
147
182
|
backgroundColor: colors.background,
|
|
148
|
-
fontSize: 16,
|
|
183
|
+
fontSize: fontSize ?? 16,
|
|
149
184
|
fontFamily,
|
|
150
185
|
}}
|
|
151
186
|
/>
|
|
@@ -147,7 +147,8 @@ export function MyazaSelect<T extends string | number>({
|
|
|
147
147
|
style={{
|
|
148
148
|
fontWeight: '700',
|
|
149
149
|
paddingHorizontal: spacing.md,
|
|
150
|
-
|
|
150
|
+
// Tight under the handle, the way the system sheet titles sit.
|
|
151
|
+
paddingTop: spacing.xs,
|
|
151
152
|
paddingBottom: spacing.sm,
|
|
152
153
|
}}
|
|
153
154
|
>
|
|
@@ -22,27 +22,40 @@ export function OptionRow({
|
|
|
22
22
|
label,
|
|
23
23
|
selected,
|
|
24
24
|
multi,
|
|
25
|
+
role,
|
|
26
|
+
icon,
|
|
25
27
|
leading,
|
|
26
28
|
trailing,
|
|
29
|
+
disabled,
|
|
27
30
|
onPress,
|
|
28
31
|
}: {
|
|
29
32
|
label: string;
|
|
30
33
|
selected: boolean;
|
|
31
34
|
/** Renders a square check instead of a radio dot. */
|
|
32
35
|
multi?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* What the row IS to a screen reader. Defaults from `multi`; a single-pick
|
|
38
|
+
* list wearing the square check (the PoA document kind) passes 'radio'.
|
|
39
|
+
*/
|
|
40
|
+
role?: 'checkbox' | 'radio';
|
|
41
|
+
/** A glyph between the mark and the label — the mark stays. */
|
|
42
|
+
icon?: React.ReactNode;
|
|
33
43
|
/** Something before the label — a flag, an icon. Replaces the radio mark. */
|
|
34
44
|
leading?: React.ReactNode;
|
|
35
45
|
trailing?: React.ReactNode;
|
|
46
|
+
/** Greyed and inert — a choice that is locked, not one that is gone. */
|
|
47
|
+
disabled?: boolean;
|
|
36
48
|
onPress: () => void;
|
|
37
49
|
}): React.ReactElement {
|
|
38
50
|
const { colors } = useTheme();
|
|
39
51
|
return (
|
|
40
52
|
<Pressable
|
|
41
|
-
onPress={onPress}
|
|
42
|
-
accessibilityRole={multi ? 'checkbox' : 'radio'}
|
|
43
|
-
accessibilityState={{ checked: selected }}
|
|
53
|
+
onPress={disabled ? undefined : onPress}
|
|
54
|
+
accessibilityRole={role ?? (multi ? 'checkbox' : 'radio')}
|
|
55
|
+
accessibilityState={{ checked: selected, disabled: disabled === true }}
|
|
44
56
|
accessibilityLabel={label}
|
|
45
57
|
style={{
|
|
58
|
+
opacity: disabled ? 0.5 : 1,
|
|
46
59
|
flexDirection: 'row',
|
|
47
60
|
alignItems: 'center',
|
|
48
61
|
// The checkbox variant matches the web SDK's multi-select card exactly
|
|
@@ -59,6 +72,12 @@ export function OptionRow({
|
|
|
59
72
|
>
|
|
60
73
|
{leading ?? <SelectionMark selected={selected} multi={multi} />}
|
|
61
74
|
<View style={{ width: multi ? 10 : spacing.sm }} />
|
|
75
|
+
{icon ? (
|
|
76
|
+
<>
|
|
77
|
+
{icon}
|
|
78
|
+
<View style={{ width: 10 }} />
|
|
79
|
+
</>
|
|
80
|
+
) : null}
|
|
62
81
|
<MyazaText variant="body" style={{ flex: 1 }}>
|
|
63
82
|
{label}
|
|
64
83
|
</MyazaText>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import { PanResponder, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from './runtime';
|
|
5
|
+
|
|
6
|
+
// ─── A 0–100 slider for the ownership field ──────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// The fast coarse gesture beside the exact box: dragging writes whole numbers
|
|
9
|
+
// into the same field, and a register stake like 48.42 is still typed (no
|
|
10
|
+
// thumb lands on it). Dependency-free — a track, a fill and a thumb under one
|
|
11
|
+
// PanResponder — because a native slider module is a heavy import for one
|
|
12
|
+
// field. Mirrors the web SDK's range input under the same box.
|
|
13
|
+
|
|
14
|
+
const THUMB = 22;
|
|
15
|
+
const TRACK = 5;
|
|
16
|
+
|
|
17
|
+
export function OwnershipSlider({
|
|
18
|
+
value,
|
|
19
|
+
onChange,
|
|
20
|
+
}: {
|
|
21
|
+
/** The current stake 0–100; an undeclared one rests the thumb at 0. */
|
|
22
|
+
value: number;
|
|
23
|
+
onChange: (next: number) => void;
|
|
24
|
+
}): React.ReactElement {
|
|
25
|
+
const { colors } = useTheme();
|
|
26
|
+
const [width, setWidth] = useState(0);
|
|
27
|
+
const widthRef = useRef(0);
|
|
28
|
+
const onChangeRef = useRef(onChange);
|
|
29
|
+
onChangeRef.current = onChange;
|
|
30
|
+
|
|
31
|
+
const setFromX = (x: number): void => {
|
|
32
|
+
const usable = widthRef.current - THUMB;
|
|
33
|
+
if (usable <= 0) return;
|
|
34
|
+
const ratio = (x - THUMB / 2) / usable;
|
|
35
|
+
const next = Math.round(Math.min(1, Math.max(0, ratio)) * 100);
|
|
36
|
+
onChangeRef.current(next);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const pan = useRef(
|
|
40
|
+
PanResponder.create({
|
|
41
|
+
onStartShouldSetPanResponder: () => true,
|
|
42
|
+
onMoveShouldSetPanResponder: () => true,
|
|
43
|
+
onPanResponderGrant: (e) => setFromX(e.nativeEvent.locationX),
|
|
44
|
+
onPanResponderMove: (e) => setFromX(e.nativeEvent.locationX),
|
|
45
|
+
}),
|
|
46
|
+
).current;
|
|
47
|
+
|
|
48
|
+
const clamped = Math.min(100, Math.max(0, value));
|
|
49
|
+
const left = width > 0 ? (clamped / 100) * (width - THUMB) : 0;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View
|
|
53
|
+
{...pan.panHandlers}
|
|
54
|
+
onLayout={(e) => {
|
|
55
|
+
widthRef.current = e.nativeEvent.layout.width;
|
|
56
|
+
setWidth(e.nativeEvent.layout.width);
|
|
57
|
+
}}
|
|
58
|
+
accessibilityRole="adjustable"
|
|
59
|
+
accessibilityLabel="Ownership percentage"
|
|
60
|
+
accessibilityValue={{ min: 0, max: 100, now: clamped }}
|
|
61
|
+
// Tall hit area; the visible track stays thin.
|
|
62
|
+
style={{ height: 32, justifyContent: 'center' }}
|
|
63
|
+
>
|
|
64
|
+
<View
|
|
65
|
+
style={{
|
|
66
|
+
height: TRACK,
|
|
67
|
+
borderRadius: TRACK / 2,
|
|
68
|
+
backgroundColor: colors.border,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
<View
|
|
72
|
+
pointerEvents="none"
|
|
73
|
+
style={{
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
left: 0,
|
|
76
|
+
width: left + THUMB / 2,
|
|
77
|
+
height: TRACK,
|
|
78
|
+
borderRadius: TRACK / 2,
|
|
79
|
+
backgroundColor: colors.primary,
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
<View
|
|
83
|
+
pointerEvents="none"
|
|
84
|
+
style={{
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
left,
|
|
87
|
+
width: THUMB,
|
|
88
|
+
height: THUMB,
|
|
89
|
+
borderRadius: THUMB / 2,
|
|
90
|
+
backgroundColor: colors.primary,
|
|
91
|
+
borderWidth: 3,
|
|
92
|
+
borderColor: colors.background,
|
|
93
|
+
shadowColor: '#000',
|
|
94
|
+
shadowOpacity: 0.15,
|
|
95
|
+
shadowRadius: 3,
|
|
96
|
+
shadowOffset: { width: 0, height: 1 },
|
|
97
|
+
elevation: 2,
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
</View>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Pressable, View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -34,13 +34,32 @@ import { COUNTRY_NAMES } from '../config/countryNames.g';
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
|
|
36
36
|
export function PhoneNumberInput({
|
|
37
|
+
value,
|
|
37
38
|
defaultCountry,
|
|
39
|
+
geoCountry,
|
|
38
40
|
disabled,
|
|
41
|
+
autoFocus = false,
|
|
39
42
|
onChange,
|
|
40
43
|
}: {
|
|
44
|
+
/**
|
|
45
|
+
* E.164 seed. The field itself stays uncontrolled (the formatter owns the
|
|
46
|
+
* text), but a value arriving after mount — the register's, prefilled a
|
|
47
|
+
* moment later — has to reach the field, or we hold a number the applicant
|
|
48
|
+
* can neither see nor correct and submit it as though they gave it.
|
|
49
|
+
*/
|
|
50
|
+
value?: string;
|
|
41
51
|
/** Seeds the picker (ISO-2). Falls back to NG. */
|
|
42
52
|
defaultCountry?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The visitor's IP country. Pinned to the top of the picker and tagged, so
|
|
55
|
+
* a guess we made on their behalf is visible AS a guess and one tap away
|
|
56
|
+
* rather than buried at its alphabetical position. Mirrors the web SDK.
|
|
57
|
+
*/
|
|
58
|
+
geoCountry?: string | null;
|
|
43
59
|
disabled?: boolean;
|
|
60
|
+
/** Focus + keyboard on mount. Opt-in: right for a single-field OTP screen,
|
|
61
|
+
* wrong for a row in a company-profile form. */
|
|
62
|
+
autoFocus?: boolean;
|
|
44
63
|
/** Fires on every edit with the E.164 value ('' until parseable) + validity. */
|
|
45
64
|
onChange: (value: { e164: string; isValid: boolean; country: string }) => void;
|
|
46
65
|
}): React.ReactElement {
|
|
@@ -64,11 +83,32 @@ export function PhoneNumberInput({
|
|
|
64
83
|
[],
|
|
65
84
|
);
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
// A supplied number decides its OWN dial code — it is a fact, where the
|
|
87
|
+
// country prop is a guess. Falls through to the guess when there is no
|
|
88
|
+
// number or it cannot be parsed.
|
|
89
|
+
const supplied = value ? parsePhoneNumberFromString(value) : null;
|
|
90
|
+
const seed = (supplied?.country ??
|
|
91
|
+
defaultCountry?.toUpperCase() ??
|
|
92
|
+
'NG') as CountryCode;
|
|
68
93
|
const [country, setCountry] = useState<CountryCode>(
|
|
69
94
|
options.some((o) => o.code === seed) ? seed : ('NG' as CountryCode),
|
|
70
95
|
);
|
|
71
|
-
const [national, setNational] = useState(
|
|
96
|
+
const [national, setNational] = useState(() =>
|
|
97
|
+
supplied ? formatNationalNumber(supplied.nationalNumber, seed) : '',
|
|
98
|
+
);
|
|
99
|
+
// A number that arrives AFTER mount — the register's, prefilled a moment
|
|
100
|
+
// later — has to reach the field. Seeding is one-way and keyed on the value,
|
|
101
|
+
// so the applicant's own edits are never fought.
|
|
102
|
+
const seededRef = useRef(value ?? '');
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!value || value === seededRef.current) return;
|
|
105
|
+
seededRef.current = value;
|
|
106
|
+
const parsed = parsePhoneNumberFromString(value);
|
|
107
|
+
if (!parsed) return;
|
|
108
|
+
const next = (parsed.country ?? country) as CountryCode;
|
|
109
|
+
setCountry(next);
|
|
110
|
+
setNational(formatNationalNumber(parsed.nationalNumber, next));
|
|
111
|
+
}, [value, country]);
|
|
72
112
|
const [open, setOpen] = useState(false);
|
|
73
113
|
|
|
74
114
|
const selected = options.find((o) => o.code === country);
|
|
@@ -132,7 +172,7 @@ export function PhoneNumberInput({
|
|
|
132
172
|
placeholder="803 123 4567"
|
|
133
173
|
keyboardType="phone-pad"
|
|
134
174
|
editable={!disabled}
|
|
135
|
-
autoFocus
|
|
175
|
+
autoFocus={autoFocus}
|
|
136
176
|
/>
|
|
137
177
|
</View>
|
|
138
178
|
</View>
|
|
@@ -141,6 +181,7 @@ export function PhoneNumberInput({
|
|
|
141
181
|
visible={open}
|
|
142
182
|
options={options}
|
|
143
183
|
selected={country}
|
|
184
|
+
pinned={geoCountry}
|
|
144
185
|
onPick={pickCountry}
|
|
145
186
|
onClose={() => setOpen(false)}
|
|
146
187
|
/>
|
|
@@ -36,12 +36,12 @@ export function PoweredBy({ bottomInset = 0 }: { bottomInset?: number }): React.
|
|
|
36
36
|
return (
|
|
37
37
|
<View
|
|
38
38
|
style={{
|
|
39
|
-
paddingTop:
|
|
39
|
+
paddingTop: 10,
|
|
40
40
|
// Symmetric with the top. It was `lg`, which read as a heavy band on the
|
|
41
41
|
// one screen where vertical space is contested — the searchable country
|
|
42
42
|
// list, where the footer costs a visible row. The home-indicator inset
|
|
43
43
|
// rides on top, so notched devices still clear it comfortably.
|
|
44
|
-
paddingBottom:
|
|
44
|
+
paddingBottom: 12 + bottomInset,
|
|
45
45
|
alignItems: 'center',
|
|
46
46
|
}}
|
|
47
47
|
>
|
|
@@ -70,9 +70,9 @@ export function PoweredBy({ bottomInset = 0 }: { bottomInset?: number }): React.
|
|
|
70
70
|
hitSlop={8}
|
|
71
71
|
style={{ flexDirection: 'row', alignItems: 'center', gap: spacing.sm }}
|
|
72
72
|
>
|
|
73
|
-
<MyazaWordmark height={
|
|
73
|
+
<MyazaWordmark height={24} wordmark={markColor} />
|
|
74
74
|
|
|
75
|
-
<View style={{ width: 1, height:
|
|
75
|
+
<View style={{ width: 1, height: 20, backgroundColor: markColor, opacity: 0.35 }} />
|
|
76
76
|
|
|
77
77
|
{/* ~half the wordmark's height — the ratio the dashboard lockup uses. */}
|
|
78
78
|
{/* Pinned to the brand face rather than inheriting: this word is part
|
|
@@ -83,7 +83,7 @@ export function PoweredBy({ bottomInset = 0 }: { bottomInset?: number }): React.
|
|
|
83
83
|
brandMark
|
|
84
84
|
variant="body"
|
|
85
85
|
color={markColor}
|
|
86
|
-
style={{ flexShrink: 1, fontSize:
|
|
86
|
+
style={{ flexShrink: 1, fontSize: 12, fontWeight: '600', letterSpacing: 1.68 }}
|
|
87
87
|
>
|
|
88
88
|
TRUST
|
|
89
89
|
</MyazaText>
|