@myazahq/kyc-sdk-react-native 2.0.2 → 2.1.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.
Files changed (281) hide show
  1. package/README.md +111 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/AndroidManifest.xml +14 -4
  4. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
  8. package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
  9. package/app.plugin.js +102 -13
  10. package/ios/HybridMyazaEmrtd.swift +447 -0
  11. package/ios/HybridMyazaFaceDetector.swift +104 -2
  12. package/ios/HybridMyazaRectDetector.swift +103 -0
  13. package/ios/HybridMyazaTextRecognizer.swift +111 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
  16. package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
  18. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
  19. package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
  20. package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
  21. package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
  22. package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
  23. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
  24. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
  26. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
  27. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
  28. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
  29. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
  30. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
  31. package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
  42. package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
  44. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
  45. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
  46. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
  47. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
  48. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
  49. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
  50. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
  51. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
  52. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
  54. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
  56. package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
  57. package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
  58. package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
  59. package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
  60. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  61. package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
  62. package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
  63. package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
  64. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  66. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
  67. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
  68. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
  70. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
  71. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
  72. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
  73. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
  74. package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
  75. package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
  76. package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
  77. package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
  78. package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
  79. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
  80. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
  81. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
  82. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
  83. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
  84. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
  85. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
  86. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
  87. package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
  88. package/package.json +16 -4
  89. package/src/MyazaKYC.tsx +115 -22
  90. package/src/capture/documentIdentity.ts +153 -0
  91. package/src/capture/documentSignals.ts +177 -0
  92. package/src/capture/documentTextGate.ts +171 -0
  93. package/src/capture/hints.ts +57 -0
  94. package/src/capture/index.ts +24 -0
  95. package/src/capture/rectDetector.ts +84 -0
  96. package/src/capture/rectGate.ts +91 -0
  97. package/src/capture/useAutoCapture.ts +198 -0
  98. package/src/components/CameraViewfinder.tsx +226 -70
  99. package/src/components/ContactCodeEntry.tsx +139 -0
  100. package/src/components/CountryField.tsx +87 -0
  101. package/src/components/CountryFlag.tsx +15 -15
  102. package/src/components/DialCodePicker.tsx +186 -0
  103. package/src/components/DocumentCropper.tsx +1 -1
  104. package/src/components/DocumentReview.tsx +142 -0
  105. package/src/components/DocumentReviewSide.tsx +135 -0
  106. package/src/components/DocumentReviewZoom.tsx +119 -0
  107. package/src/components/ExpiryCountdown.tsx +52 -0
  108. package/src/components/FlashOverlay.tsx +156 -0
  109. package/src/components/Icon.tsx +67 -1
  110. package/src/components/KycFlow.tsx +66 -94
  111. package/src/components/KycSheet.tsx +148 -18
  112. package/src/components/MediaSourceSheet.tsx +163 -0
  113. package/src/components/MyazaButton.tsx +14 -2
  114. package/src/components/MyazaDateField.tsx +240 -0
  115. package/src/components/MyazaInput.tsx +73 -26
  116. package/src/components/MyazaSelect.tsx +283 -0
  117. package/src/components/MyazaWordmark.tsx +33 -0
  118. package/src/components/OptionRow.tsx +132 -0
  119. package/src/components/PhoneNumberInput.tsx +149 -0
  120. package/src/components/PoweredBy.tsx +89 -0
  121. package/src/components/ReadyPrimer.tsx +149 -0
  122. package/src/components/StepView.tsx +91 -0
  123. package/src/components/Typography.tsx +18 -3
  124. package/src/components/VerifiedNotice.tsx +38 -0
  125. package/src/components/WhatsAppIcon.tsx +29 -0
  126. package/src/components/WorkflowGate.tsx +128 -0
  127. package/src/components/brand-font.ts +113 -0
  128. package/src/components/flashHoleGeometry.ts +35 -0
  129. package/src/components/flow/FatalConfigError.tsx +56 -0
  130. package/src/components/fonts.ts +2 -19
  131. package/src/components/readyPrimerContent.ts +52 -0
  132. package/src/components/runtime.tsx +38 -43
  133. package/src/components/stepHeaderMeta.tsx +87 -0
  134. package/src/components/theme-provider.tsx +89 -0
  135. package/src/components/useWorkflowMount.ts +116 -0
  136. package/src/components/viewfinder/CardGuide.tsx +60 -0
  137. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  138. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  139. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  140. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  141. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  142. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  143. package/src/config/brand.ts +97 -0
  144. package/src/config/business.ts +179 -0
  145. package/src/config/businessSteps.ts +105 -0
  146. package/src/config/contact.ts +159 -0
  147. package/src/config/countryNames.g.ts +254 -0
  148. package/src/config/currencyFlags.ts +144 -0
  149. package/src/config/font-resolve.ts +78 -0
  150. package/src/config/idTypes.ts +96 -1
  151. package/src/config/keyPeople.ts +152 -0
  152. package/src/config/phone.ts +39 -0
  153. package/src/config/proofOfAddress.ts +75 -0
  154. package/src/config/questionnaire.ts +153 -0
  155. package/src/config/regions.ts +118 -0
  156. package/src/config/registrationHint.ts +87 -0
  157. package/src/config/stepOrder.ts +118 -0
  158. package/src/config/theme.ts +38 -2
  159. package/src/config/workflowMerge.ts +124 -0
  160. package/src/emrtd/bac.ts +128 -0
  161. package/src/emrtd/bytes.ts +127 -0
  162. package/src/emrtd/crypto.ts +163 -0
  163. package/src/emrtd/der.ts +192 -0
  164. package/src/emrtd/dg2.ts +111 -0
  165. package/src/emrtd/files.ts +215 -0
  166. package/src/emrtd/index.ts +30 -0
  167. package/src/emrtd/mrzKey.ts +54 -0
  168. package/src/emrtd/native.ts +79 -0
  169. package/src/emrtd/optionalRead.ts +42 -0
  170. package/src/emrtd/read.ts +224 -0
  171. package/src/emrtd/retry.ts +98 -0
  172. package/src/emrtd/secureMessaging.ts +178 -0
  173. package/src/emrtd/session.ts +200 -0
  174. package/src/emrtd/stages.ts +122 -0
  175. package/src/index.ts +110 -0
  176. package/src/lib/calendar.ts +71 -0
  177. package/src/lib/use-keyboard-inset.ts +34 -0
  178. package/src/liveness/challengeManager.ts +27 -1
  179. package/src/liveness/faceContinuity.ts +153 -0
  180. package/src/liveness/flashDetector.ts +145 -0
  181. package/src/liveness/flashReadyGate.ts +106 -0
  182. package/src/liveness/flashRunner.ts +118 -0
  183. package/src/liveness/integritySignals.ts +60 -0
  184. package/src/liveness/livenessState.ts +139 -0
  185. package/src/liveness/speech.ts +19 -1
  186. package/src/liveness/types.ts +48 -1
  187. package/src/liveness/useFlashPhase.ts +78 -0
  188. package/src/liveness/useLiveness.ts +260 -75
  189. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  190. package/src/mrz/extract.ts +101 -0
  191. package/src/mrz/parse.ts +178 -0
  192. package/src/mrz/textRecognizer.ts +149 -0
  193. package/src/screens/ApplicantRoleStep.tsx +331 -0
  194. package/src/screens/BusinessDetailsStep.tsx +212 -0
  195. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  196. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  197. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  198. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  199. package/src/screens/CompanyInfoFields.tsx +83 -0
  200. package/src/screens/ConsentStep.tsx +68 -68
  201. package/src/screens/ContactChannelChoice.tsx +133 -0
  202. package/src/screens/ContactCodeStep.tsx +130 -0
  203. package/src/screens/ContactDestinationField.tsx +70 -0
  204. package/src/screens/ContactFooterNote.tsx +34 -0
  205. package/src/screens/ContactVerificationStep.tsx +199 -0
  206. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  207. package/src/screens/CountrySelectStep.tsx +210 -0
  208. package/src/screens/DocumentCaptureStep.tsx +156 -132
  209. package/src/screens/IdInputStep.tsx +13 -5
  210. package/src/screens/IdTypeStep.tsx +74 -33
  211. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  212. package/src/screens/LivenessStep.tsx +155 -247
  213. package/src/screens/MrzScanView.tsx +173 -0
  214. package/src/screens/NfcStep.tsx +290 -0
  215. package/src/screens/ProofOfAddressStep.tsx +238 -0
  216. package/src/screens/QuestionnaireField.tsx +219 -0
  217. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  218. package/src/screens/QuestionnaireStep.tsx +76 -0
  219. package/src/screens/SubmittedStep.tsx +5 -0
  220. package/src/screens/consent/model.ts +109 -0
  221. package/src/screens/document/CameraPhase.tsx +150 -0
  222. package/src/screens/document/RequiredPill.tsx +60 -0
  223. package/src/screens/liveness/LightingBanner.tsx +50 -0
  224. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  225. package/src/screens/liveness/ProgressDots.tsx +67 -0
  226. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  227. package/src/screens/liveness/constants.ts +31 -0
  228. package/src/screens/liveness/index.ts +8 -0
  229. package/src/screens/liveness/resolveGuidance.ts +27 -0
  230. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  231. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  232. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  233. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  234. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  235. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  236. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  237. package/src/screens/nfc/availability.ts +73 -0
  238. package/src/screens/nfc/readErrorMessage.ts +93 -0
  239. package/src/screens/nfc/useChipPortrait.ts +58 -0
  240. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  241. package/src/screens/useFlashHole.ts +105 -0
  242. package/src/screens/useFlashSequence.ts +96 -0
  243. package/src/screens/usePoaAttach.ts +121 -0
  244. package/src/services/api-types.ts +164 -0
  245. package/src/services/api-verify-types.ts +99 -0
  246. package/src/services/api.ts +72 -102
  247. package/src/services/cardCrop.ts +25 -10
  248. package/src/services/contactErrors.ts +43 -0
  249. package/src/services/deviceMetadata.ts +1 -1
  250. package/src/services/documentPicker.ts +20 -0
  251. package/src/services/fingerprint-sources.ts +129 -0
  252. package/src/services/fingerprint.ts +101 -0
  253. package/src/services/haptics.ts +38 -0
  254. package/src/services/mediaCompress.ts +7 -2
  255. package/src/services/resolveUrl.ts +2 -2
  256. package/src/services/workflowGate.ts +138 -0
  257. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  258. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  259. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  260. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  261. package/src/store/derive.ts +225 -0
  262. package/src/store/kycStore.ts +250 -149
  263. package/src/store/state.ts +253 -0
  264. package/src/store/submit.ts +101 -0
  265. package/src/store/submitApplicant.ts +81 -0
  266. package/src/types/appearance.ts +113 -0
  267. package/src/types/business.ts +131 -0
  268. package/src/types/config.ts +150 -124
  269. package/src/types/id-types.ts +57 -0
  270. package/src/types/verification.ts +5 -0
  271. package/src/types/workflow.ts +141 -0
  272. package/src/utils/amountFormat.ts +73 -0
  273. package/src/utils/tokens.ts +4 -2
  274. package/src/__tests__/cardCrop.test.ts +0 -39
  275. package/src/__tests__/deviceMetadata.test.ts +0 -34
  276. package/src/__tests__/errors.test.ts +0 -34
  277. package/src/__tests__/flow.test.ts +0 -61
  278. package/src/__tests__/gestureDetector.test.ts +0 -37
  279. package/src/__tests__/liveness.test.ts +0 -112
  280. package/src/__tests__/resolveUrl.test.ts +0 -64
  281. package/src/__tests__/validators.test.ts +0 -38
@@ -24,11 +24,20 @@ export interface MyazaInputProps {
24
24
  error?: string | null;
25
25
  helper?: string;
26
26
  keyboardType?: KeyboardTypeOptions;
27
- autoCapitalize?: 'none' | 'characters';
27
+ autoCapitalize?: 'none' | 'characters' | 'words';
28
28
  maxLength?: number;
29
29
  editable?: boolean;
30
30
  /** Focus the field on mount (matches the Flutter SDK's id-input autofocus). */
31
31
  autoFocus?: boolean;
32
+ /**
33
+ * Rendered inside the field, before the text — a search glyph, typically.
34
+ * When set, the BORDER moves to a wrapping row and the TextInput itself is
35
+ * drawn borderless, since the icon has to sit inside the same outline.
36
+ */
37
+ prefix?: React.ReactNode;
38
+ /** Rendered inside the field, after the text — a unit like "%". Same
39
+ * bordered-row treatment as `prefix`. */
40
+ suffix?: React.ReactNode;
32
41
  onFocus?: FocusHandler;
33
42
  onBlur?: BlurHandler;
34
43
  }
@@ -45,6 +54,8 @@ export function MyazaInput({
45
54
  maxLength,
46
55
  editable = true,
47
56
  autoFocus = false,
57
+ prefix,
58
+ suffix,
48
59
  onFocus,
49
60
  onBlur,
50
61
  }: MyazaInputProps): React.ReactElement {
@@ -73,31 +84,67 @@ export function MyazaInput({
73
84
  {label}
74
85
  </MyazaText>
75
86
  ) : null}
76
- <TextInput
77
- value={value}
78
- onChangeText={onChangeText}
79
- placeholder={placeholder}
80
- placeholderTextColor={colors.textMuted}
81
- keyboardType={keyboardType}
82
- autoCapitalize={autoCapitalize}
83
- maxLength={maxLength}
84
- editable={editable}
85
- autoFocus={autoFocus}
86
- onFocus={handleFocus}
87
- onBlur={handleBlur}
88
- style={{
89
- height: sizing.inputHeight,
90
- borderWidth,
91
- borderColor,
92
- borderRadius: radius.sm,
93
- // Keep text from shifting when the border thickens on focus: pad so
94
- // (border + padding) stays constant across states (max border = 2).
95
- paddingHorizontal: spacing.md + (2 - borderWidth),
96
- color: colors.textDark,
97
- backgroundColor: colors.background,
98
- fontSize: 16,
99
- }}
100
- />
87
+ {prefix || suffix ? (
88
+ // Border on the ROW so the adornment sits inside the same outline as
89
+ // the text. Same border/padding arithmetic as below, kept in step.
90
+ <View
91
+ style={{
92
+ flexDirection: 'row',
93
+ alignItems: 'center',
94
+ height: sizing.inputHeight,
95
+ borderWidth,
96
+ borderColor,
97
+ borderRadius: radius.sm,
98
+ paddingHorizontal: spacing.md + (2 - borderWidth),
99
+ backgroundColor: colors.background,
100
+ }}
101
+ >
102
+ {prefix}
103
+ {prefix ? <View style={{ width: spacing.sm }} /> : null}
104
+ <TextInput
105
+ value={value}
106
+ onChangeText={onChangeText}
107
+ placeholder={placeholder}
108
+ placeholderTextColor={colors.textMuted}
109
+ keyboardType={keyboardType}
110
+ autoCapitalize={autoCapitalize}
111
+ maxLength={maxLength}
112
+ editable={editable}
113
+ autoFocus={autoFocus}
114
+ onFocus={handleFocus}
115
+ onBlur={handleBlur}
116
+ style={{ flex: 1, height: '100%', color: colors.textDark, fontSize: 16 }}
117
+ />
118
+ {suffix ? <View style={{ width: spacing.sm }} /> : null}
119
+ {suffix}
120
+ </View>
121
+ ) : (
122
+ <TextInput
123
+ value={value}
124
+ onChangeText={onChangeText}
125
+ placeholder={placeholder}
126
+ placeholderTextColor={colors.textMuted}
127
+ keyboardType={keyboardType}
128
+ autoCapitalize={autoCapitalize}
129
+ maxLength={maxLength}
130
+ editable={editable}
131
+ autoFocus={autoFocus}
132
+ onFocus={handleFocus}
133
+ onBlur={handleBlur}
134
+ style={{
135
+ height: sizing.inputHeight,
136
+ borderWidth,
137
+ borderColor,
138
+ borderRadius: radius.sm,
139
+ // Keep text from shifting when the border thickens on focus: pad so
140
+ // (border + padding) stays constant across states (max border = 2).
141
+ paddingHorizontal: spacing.md + (2 - borderWidth),
142
+ color: colors.textDark,
143
+ backgroundColor: colors.background,
144
+ fontSize: 16,
145
+ }}
146
+ />
147
+ )}
101
148
  {error ? (
102
149
  <MyazaText variant="bodySmall" color={colors.error} style={{ marginTop: spacing.xs }}>
103
150
  {error}
@@ -0,0 +1,283 @@
1
+ import React, { useState } from 'react';
2
+ import { Modal, Pressable, ScrollView, TextInput, useWindowDimensions, View } from 'react-native';
3
+
4
+ import { radius, spacing } from '../config/theme';
5
+ import { useTheme } from './runtime';
6
+ import { MyazaText } from './Typography';
7
+ import { Icon } from './Icon';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // The select field — a collapsed value that opens a sheet of options.
11
+ //
12
+ // The counterpart to OptionRow, not a replacement: OptionRow lays every choice
13
+ // out because the choices ARE the information (which ID type, which country).
14
+ // A select is for the case where the field is a detail on a form full of other
15
+ // fields — the proof-of-address document kind sits above an upload zone, and
16
+ // four stacked cards there push the actual task off the screen.
17
+ //
18
+ // Mirrors the Flutter SDK's MyazaSelect so the two platforms present the same
19
+ // control for the same job.
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface MyazaSelectOption<T> {
23
+ value: T;
24
+ label: string;
25
+ /** Something before the label — a flag, an icon. */
26
+ leading?: React.ReactNode;
27
+ /**
28
+ * Secondary line under the label, shown in the SHEET only — the collapsed
29
+ * field stays as short as its label. Currency codes need it: "GHS" and
30
+ * "GMD" are one letter apart and mean different money.
31
+ */
32
+ description?: string;
33
+ }
34
+
35
+ export function MyazaSelect<T extends string | number>({
36
+ value,
37
+ options,
38
+ onChange,
39
+ hint = 'Select an option',
40
+ sheetTitle,
41
+ enabled = true,
42
+ height,
43
+ compact = false,
44
+ searchable = false,
45
+ }: {
46
+ value: T | null | undefined;
47
+ options: Array<MyazaSelectOption<T>>;
48
+ onChange: (value: T) => void;
49
+ /** Shown when nothing is selected yet. */
50
+ hint?: string;
51
+ /** Sheet header; falls back to `hint`. */
52
+ sheetTitle?: string;
53
+ enabled?: boolean;
54
+ /**
55
+ * Fixes the field's height. Needed when the select sits beside another
56
+ * control (the questionnaire's currency next to its amount) — vertical
57
+ * padding alone leaves the two boxes different heights.
58
+ */
59
+ height?: number;
60
+ /**
61
+ * Shrink to the width of the value instead of filling the row — the inline
62
+ * variant (the questionnaire's currency beside its amount). Without it the
63
+ * label competes for a fixed width and a code like "NGN" gets ellipsised to
64
+ * "N…". Mirrors the Flutter SDK's `compact`.
65
+ */
66
+ compact?: boolean;
67
+ /**
68
+ * Pin a search field to the top of the sheet, filtering options by label —
69
+ * for lists too long to scroll (the every-ISO-country selects). Mirrors the
70
+ * Flutter SDK's `searchable`.
71
+ */
72
+ searchable?: boolean;
73
+ }): React.ReactElement {
74
+ const { colors } = useTheme();
75
+ const { height: windowHeight } = useWindowDimensions();
76
+ const [open, setOpen] = useState(false);
77
+ const [query, setQuery] = useState('');
78
+
79
+ const selected = options.find((o) => o.value === value) ?? null;
80
+ const normalized = query.trim().toLowerCase();
81
+ const visible =
82
+ searchable && normalized !== ''
83
+ ? options.filter((o) => o.label.toLowerCase().includes(normalized))
84
+ : options;
85
+
86
+ return (
87
+ <>
88
+ <Pressable
89
+ onPress={() => {
90
+ if (enabled && options.length > 0) setOpen(true);
91
+ }}
92
+ disabled={!enabled}
93
+ accessibilityRole="button"
94
+ accessibilityState={{ disabled: !enabled, expanded: open }}
95
+ accessibilityLabel={selected?.label ?? hint}
96
+ style={{
97
+ flexDirection: 'row',
98
+ alignItems: 'center',
99
+ paddingHorizontal: spacing.md,
100
+ // A fixed height wins over padding when the field has to line up
101
+ // with a neighbouring input.
102
+ ...(height == null ? { paddingVertical: spacing.md } : { height }),
103
+ borderRadius: radius.sm,
104
+ borderWidth: 1,
105
+ borderColor: colors.border,
106
+ backgroundColor: enabled ? colors.background : colors.backgroundSecondary,
107
+ opacity: enabled ? 1 : 0.6,
108
+ ...(compact ? { alignSelf: 'flex-start' as const } : null),
109
+ }}
110
+ >
111
+ {selected?.leading ? (
112
+ <>
113
+ {selected.leading}
114
+ <View style={{ width: spacing.sm }} />
115
+ </>
116
+ ) : null}
117
+ <MyazaText
118
+ variant="body"
119
+ color={selected ? undefined : colors.textMuted}
120
+ // Compact sizes to its content, so the value is never ellipsised.
121
+ style={compact ? undefined : { flex: 1 }}
122
+ numberOfLines={1}
123
+ >
124
+ {selected?.label ?? hint}
125
+ </MyazaText>
126
+ <View style={{ width: spacing.sm }} />
127
+ <Icon name="chevron-down" size={18} color={colors.textSecondary} />
128
+ </Pressable>
129
+
130
+ <Modal
131
+ visible={open}
132
+ transparent
133
+ animationType="slide"
134
+ onRequestClose={() => {
135
+ setOpen(false);
136
+ setQuery('');
137
+ }}
138
+ >
139
+ {/* Tapping the backdrop dismisses, matching the platform sheet. */}
140
+ <Pressable
141
+ onPress={() => {
142
+ setOpen(false);
143
+ setQuery('');
144
+ }}
145
+ style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'flex-end' }}
146
+ >
147
+ {/* Stops a tap inside the sheet from closing it. */}
148
+ <Pressable
149
+ onPress={() => {}}
150
+ style={{
151
+ backgroundColor: colors.background,
152
+ borderTopLeftRadius: radius.lg,
153
+ borderTopRightRadius: radius.lg,
154
+ paddingBottom: spacing.lg,
155
+ // Long option lists stay inside the sheet and scroll.
156
+ maxHeight: windowHeight * 0.6,
157
+ }}
158
+ >
159
+ <View
160
+ style={{
161
+ alignSelf: 'center',
162
+ width: 36,
163
+ height: 4,
164
+ borderRadius: radius.full,
165
+ backgroundColor: colors.border,
166
+ marginTop: spacing.sm,
167
+ }}
168
+ />
169
+ <MyazaText
170
+ variant="body"
171
+ style={{
172
+ fontWeight: '700',
173
+ paddingHorizontal: spacing.md,
174
+ paddingTop: spacing.md,
175
+ paddingBottom: spacing.sm,
176
+ }}
177
+ >
178
+ {sheetTitle ?? hint}
179
+ </MyazaText>
180
+
181
+ {searchable ? (
182
+ // Pinned above the list, so it stays put while results scroll.
183
+ <View
184
+ style={{
185
+ flexDirection: 'row',
186
+ alignItems: 'center',
187
+ marginHorizontal: spacing.md,
188
+ marginBottom: spacing.sm,
189
+ paddingHorizontal: spacing.sm + 4,
190
+ borderWidth: 1,
191
+ borderColor: colors.border,
192
+ borderRadius: radius.sm,
193
+ backgroundColor: colors.backgroundSecondary,
194
+ }}
195
+ >
196
+ <Icon name="search" size={16} color={colors.textMuted} />
197
+ <TextInput
198
+ value={query}
199
+ onChangeText={setQuery}
200
+ placeholder="Search"
201
+ placeholderTextColor={colors.textMuted}
202
+ autoCorrect={false}
203
+ autoCapitalize="none"
204
+ style={{
205
+ flex: 1,
206
+ paddingVertical: spacing.sm + 2,
207
+ paddingHorizontal: spacing.sm,
208
+ color: colors.textDark,
209
+ fontSize: 15,
210
+ }}
211
+ />
212
+ {query !== '' ? (
213
+ <Pressable onPress={() => setQuery('')} hitSlop={8} accessibilityLabel="Clear search">
214
+ <Icon name="x" size={16} color={colors.textMuted} />
215
+ </Pressable>
216
+ ) : null}
217
+ </View>
218
+ ) : null}
219
+
220
+ <ScrollView bounces={false} keyboardShouldPersistTaps="handled">
221
+ {visible.length === 0 ? (
222
+ <MyazaText
223
+ variant="bodySmall"
224
+ color={colors.textMuted}
225
+ style={{ padding: spacing.md, textAlign: 'center' }}
226
+ >
227
+ No matches
228
+ </MyazaText>
229
+ ) : null}
230
+ {visible.map((option) => {
231
+ const isSelected = option.value === value;
232
+ return (
233
+ <Pressable
234
+ key={String(option.value)}
235
+ onPress={() => {
236
+ setOpen(false);
237
+ setQuery('');
238
+ if (!isSelected) onChange(option.value);
239
+ }}
240
+ accessibilityRole="radio"
241
+ accessibilityState={{ checked: isSelected }}
242
+ style={{
243
+ flexDirection: 'row',
244
+ alignItems: 'center',
245
+ paddingHorizontal: spacing.md,
246
+ paddingVertical: spacing.md,
247
+ }}
248
+ >
249
+ {option.leading ? (
250
+ <>
251
+ {option.leading}
252
+ <View style={{ width: spacing.sm }} />
253
+ </>
254
+ ) : null}
255
+ <View style={{ flex: 1, minWidth: 0 }}>
256
+ <MyazaText
257
+ variant="body"
258
+ color={isSelected ? colors.primary : undefined}
259
+ style={{ fontWeight: isSelected ? '600' : '400' }}
260
+ >
261
+ {option.label}
262
+ </MyazaText>
263
+ {option.description ? (
264
+ <MyazaText
265
+ variant="bodySmall"
266
+ color={colors.textSecondary}
267
+ numberOfLines={1}
268
+ >
269
+ {option.description}
270
+ </MyazaText>
271
+ ) : null}
272
+ </View>
273
+ {isSelected ? <Icon name="check" size={18} color={colors.primary} /> : null}
274
+ </Pressable>
275
+ );
276
+ })}
277
+ </ScrollView>
278
+ </Pressable>
279
+ </Pressable>
280
+ </Modal>
281
+ </>
282
+ );
283
+ }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ /**
5
+ * The Myaza wordmark — icon + "myaza", inlined from the brand SVG so the SDK
6
+ * ships no image asset and stays crisp at any density.
7
+ *
8
+ * The three icon paths keep their brand colours in both themes; the wordmark
9
+ * paths take `wordmark`, the only part that flips (brand ink on light,
10
+ * near-white on dark). Generated from kyc-dashboard/public/assets/images/
11
+ * logo-white.svg — regenerate rather than hand-editing the path data.
12
+ */
13
+ export function MyazaWordmark({
14
+ height = 13,
15
+ wordmark,
16
+ }: {
17
+ height?: number;
18
+ /** Wordmark fill — `#19156F` on light, `#F6F5FE` on dark. */
19
+ wordmark: string;
20
+ }): React.ReactElement {
21
+ return (
22
+ <Svg viewBox="0 0 81 25" height={height} width={height * (81 / 25)} fill="none">
23
+ <Path d="M18.6008 22.9368C14.2202 24.4324 9.83824 24.4324 5.45764 22.9368C3.41995 22.2414 1.81712 20.6385 1.12163 18.6008C-0.373877 14.2202 -0.373877 9.83824 1.12163 5.45764C1.81712 3.41995 3.41995 1.81712 5.45764 1.12163C9.83824 -0.373877 14.2202 -0.373877 18.6008 1.12163C20.6385 1.81712 22.2414 3.41995 22.9368 5.45764C24.4324 9.83824 24.4324 14.2202 22.9368 18.6008C22.2414 20.6385 20.6385 22.2414 18.6008 22.9368Z" fill="#BDB6FB" />
24
+ <Path d="M13.0606 6.06204L18.1298 11.2775C18.6887 11.8517 18.8517 12.7033 18.5465 13.4434L16.7931 17.6874C16.5813 18.2003 15.9945 18.4443 15.4816 18.2324C15.3562 18.1808 15.2419 18.1042 15.1471 18.0066L12.1059 14.8999L8.92116 18.1056C8.5309 18.4986 7.89535 18.5014 7.50091 18.1097C7.40474 18.0136 7.32808 17.8993 7.27791 17.7725L5.50086 13.3626C5.19841 12.6113 5.37681 11.7528 5.95244 11.1841L11.1581 6.04253C11.6836 5.52265 12.531 5.52823 13.0509 6.05507C13.0537 6.05786 13.0565 6.06065 13.0579 6.06204H13.0606Z" fill="#5645F5" />
25
+ <Path d="M8.52707 11.2556L12.8422 15.6502L12.8199 15.6279C12.431 15.232 11.7955 15.2265 11.3996 15.6139C11.3954 15.6181 11.3913 15.6223 11.3871 15.6265L11.3634 15.6502L8.91872 18.106C8.52707 18.499 7.89151 18.5004 7.49847 18.1088C7.4023 18.0126 7.32704 17.8983 7.27547 17.7729L5.47891 13.3142C5.11653 12.4139 5.52908 11.3867 6.41412 10.988C7.14724 10.6577 7.8511 10.7469 8.52568 11.2556H8.52707Z" fill="#19156F" />
26
+ <Path d="M69.6211 7.02734H62.7402V9.20492H67.4864L62.7402 14.0465V16.2249H63.8347H70.5984V14.0465H65.9335L70.5984 9.20492V7.02734H69.6211Z" fill={wordmark} />
27
+ <Path d="M58.7524 7.02734V7.715C58.6177 7.63101 58.4786 7.5549 58.3343 7.48491C57.7288 7.1927 57.0491 7.02734 56.3308 7.02734C53.7919 7.02734 51.7324 9.08506 51.7324 11.6248C51.7324 14.1646 53.7919 16.2241 56.3308 16.2241C57.0491 16.2241 57.7288 16.0587 58.3343 15.7656C58.4777 15.6956 58.6168 15.6195 58.7524 15.5355V16.2241H60.93V7.02734H58.7524ZM56.3308 14.0185C55.0097 14.0185 53.938 12.9468 53.938 11.6257C53.938 10.3046 55.0097 9.23291 56.3308 9.23291C57.6518 9.23291 58.7244 10.3038 58.7244 11.6257C58.7244 12.9476 57.6527 14.0185 56.3308 14.0185Z" fill={wordmark} />
28
+ <Path d="M78.8228 7.02734V7.715C78.688 7.63101 78.5489 7.5549 78.4046 7.48491C77.7991 7.1927 77.1194 7.02734 76.4011 7.02734C73.8622 7.02734 71.8027 9.08506 71.8027 11.6248C71.8027 14.1646 73.8622 16.2241 76.4011 16.2241C77.1194 16.2241 77.7991 16.0587 78.4046 15.7656C78.548 15.6956 78.6872 15.6195 78.8228 15.5355V16.2241H81.0003V7.02734H78.8228ZM76.4011 14.0185C75.08 14.0185 74.0083 12.9468 74.0083 11.6257C74.0083 10.3046 75.08 9.23291 76.4011 9.23291C77.7222 9.23291 78.7948 10.3038 78.7948 11.6257C78.7948 12.9476 77.723 14.0185 76.4011 14.0185Z" fill={wordmark} />
29
+ <Path d="M48.8464 6.99219L46.4562 12.4051L44.1754 6.99219H41.5762L45.1422 15.1399L42.8946 19.7558H45.5184L51.4798 6.99219H48.8464Z" fill={wordmark} />
30
+ <Path d="M37.0021 6.91581C35.8727 6.91581 34.8167 7.37862 34.0564 8.18264C34.0319 8.15639 34.0065 8.13102 33.9812 8.10565C33.2139 7.33838 32.1938 6.91581 31.1089 6.91581C30.4685 6.91581 29.8474 7.06542 29.2866 7.348V6.91581H27.0469V16.5369H29.2866V11.1231C29.2866 10.1187 30.1037 9.30073 31.1089 9.30073C31.5962 9.30073 32.0538 9.49058 32.3976 9.83441C32.7415 10.1782 32.9313 10.6358 32.9313 11.1222V16.536H35.1797V11.1222C35.1797 10.1179 35.9969 9.29986 37.0021 9.29986C37.4894 9.29986 37.947 9.4897 38.2908 9.83353C38.6346 10.1774 38.8245 10.6349 38.8245 11.1214V16.5351H41.0642V10.9752C41.0642 9.8904 40.6416 8.87029 39.8743 8.1039C39.1071 7.33663 38.087 6.91406 37.0021 6.91406V6.91581Z" fill={wordmark} />
31
+ </Svg>
32
+ );
33
+ }
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import { Pressable, View } from 'react-native';
3
+
4
+ import { radius, spacing } from '../config/theme';
5
+ import { useTheme } from './runtime';
6
+ import { MyazaText } from './Typography';
7
+ import { Icon } from './Icon';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // The tappable choice card.
11
+ //
12
+ // Every step that offers a set of options renders one of these: questionnaire
13
+ // selects, the proof-of-address document kind, the KYB registry country and
14
+ // product, the applicant's role. They were four near-identical copies before
15
+ // this, which is four places for the selected state to drift apart.
16
+ //
17
+ // A card rather than a picker wheel because the options are the information —
18
+ // hiding them behind a tap makes the user open it to find out what is on offer.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export function OptionRow({
22
+ label,
23
+ selected,
24
+ multi,
25
+ leading,
26
+ trailing,
27
+ onPress,
28
+ }: {
29
+ label: string;
30
+ selected: boolean;
31
+ /** Renders a square check instead of a radio dot. */
32
+ multi?: boolean;
33
+ /** Something before the label — a flag, an icon. Replaces the radio mark. */
34
+ leading?: React.ReactNode;
35
+ trailing?: React.ReactNode;
36
+ onPress: () => void;
37
+ }): React.ReactElement {
38
+ const { colors } = useTheme();
39
+ return (
40
+ <Pressable
41
+ onPress={onPress}
42
+ accessibilityRole={multi ? 'checkbox' : 'radio'}
43
+ accessibilityState={{ checked: selected }}
44
+ accessibilityLabel={label}
45
+ style={{
46
+ flexDirection: 'row',
47
+ alignItems: 'center',
48
+ // The checkbox variant matches the web SDK's multi-select card exactly
49
+ // (`rounded-xl border p-3`), since the same questionnaire renders on
50
+ // both and a differently-proportioned card reads as a different control.
51
+ paddingHorizontal: spacing.sm + 4,
52
+ paddingVertical: multi ? spacing.sm + 4 : spacing.sm + 2,
53
+ borderRadius: multi ? radius.sm : radius.md,
54
+ borderWidth: multi ? 1 : 1.5,
55
+ borderColor: selected ? colors.primary : colors.border,
56
+ backgroundColor: selected ? `${colors.primary}0D` : 'transparent',
57
+ marginBottom: spacing.sm,
58
+ }}
59
+ >
60
+ {leading ?? <SelectionMark selected={selected} multi={multi} />}
61
+ <View style={{ width: multi ? 10 : spacing.sm }} />
62
+ <MyazaText variant="body" style={{ flex: 1 }}>
63
+ {label}
64
+ </MyazaText>
65
+ {trailing}
66
+ </Pressable>
67
+ );
68
+ }
69
+
70
+ function SelectionMark({
71
+ selected,
72
+ multi,
73
+ }: {
74
+ selected: boolean;
75
+ multi?: boolean;
76
+ }): React.ReactElement {
77
+ const { colors } = useTheme();
78
+ return (
79
+ <View
80
+ style={{
81
+ width: 20,
82
+ height: 20,
83
+ // A checkbox is a rounded SQUARE (web's `rounded-md`), not a rounded
84
+ // rectangle — radius.sm (12) on a 20px box reads almost circular.
85
+ borderRadius: multi ? 6 : radius.full,
86
+ borderWidth: multi ? 1 : 1.5,
87
+ borderColor: selected ? colors.primary : colors.border,
88
+ backgroundColor: selected ? colors.primary : 'transparent',
89
+ alignItems: 'center',
90
+ justifyContent: 'center',
91
+ }}
92
+ >
93
+ {selected ? <Icon name="check" size={multi ? 14 : 13} color={colors.onPrimary} /> : null}
94
+ </View>
95
+ );
96
+ }
97
+
98
+ /** A compact pill for choices that read better inline (currencies, roles). */
99
+ export function OptionPill({
100
+ label,
101
+ selected,
102
+ onPress,
103
+ }: {
104
+ label: string;
105
+ selected: boolean;
106
+ onPress: () => void;
107
+ }): React.ReactElement {
108
+ const { colors } = useTheme();
109
+ return (
110
+ <Pressable
111
+ onPress={onPress}
112
+ accessibilityRole="radio"
113
+ accessibilityState={{ checked: selected }}
114
+ style={{
115
+ paddingHorizontal: spacing.sm + 2,
116
+ paddingVertical: 8,
117
+ borderRadius: radius.full,
118
+ borderWidth: 1.5,
119
+ borderColor: selected ? colors.primary : colors.border,
120
+ backgroundColor: selected ? `${colors.primary}14` : 'transparent',
121
+ }}
122
+ >
123
+ <MyazaText
124
+ variant="bodySmall"
125
+ color={selected ? colors.primary : colors.textMuted}
126
+ style={{ fontWeight: '600' }}
127
+ >
128
+ {label}
129
+ </MyazaText>
130
+ </Pressable>
131
+ );
132
+ }