@incodetech/prizma-core-screens 0.2.7 → 0.2.14

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 (191) hide show
  1. package/dist/BiometricConsentScreen.d.ts +95 -0
  2. package/dist/BiometricConsentScreen.js +91 -0
  3. package/dist/BiometricConsentStrings.d.ts +84 -0
  4. package/dist/BiometricConsentStrings.js +31 -0
  5. package/dist/CaptureScreen.d.ts +13 -0
  6. package/dist/CaptureScreen.js +2 -2
  7. package/dist/DemoScreensLayout.css +16 -0
  8. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  9. package/dist/DemoScreensRedirectScreen.js +3 -2
  10. package/dist/DemoScreensStrings.d.ts +5 -0
  11. package/dist/DigitalIdDeviceWalletsDocumentSelectionScreen.d.ts +34 -0
  12. package/dist/DigitalIdDeviceWalletsDocumentSelectionScreen.js +47 -0
  13. package/dist/DigitalIdDeviceWalletsResultScreen.d.ts +28 -0
  14. package/dist/DigitalIdDeviceWalletsResultScreen.js +23 -0
  15. package/dist/DigitalIdDeviceWalletsStrings.d.ts +55 -0
  16. package/dist/DigitalIdDeviceWalletsStrings.js +19 -0
  17. package/dist/DigitalIdProvidersDocumentSelectionScreen.d.ts +31 -0
  18. package/dist/DigitalIdProvidersDocumentSelectionScreen.js +37 -0
  19. package/dist/DigitalIdProvidersErrorScreen.d.ts +33 -0
  20. package/dist/DigitalIdProvidersErrorScreen.js +33 -0
  21. package/dist/DigitalIdProvidersLayout.css +59 -0
  22. package/dist/DigitalIdProvidersRedirectScreen.d.ts +24 -0
  23. package/dist/DigitalIdProvidersRedirectScreen.js +18 -0
  24. package/dist/DigitalIdProvidersStrings.d.ts +72 -0
  25. package/dist/DigitalIdProvidersStrings.js +15 -0
  26. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  27. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  28. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  29. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  30. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  31. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  32. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  33. package/dist/DigitalIdUploadStrings.js +19 -0
  34. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  35. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  36. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  37. package/dist/EkybCountryScreen.d.ts +6 -6
  38. package/dist/EkybCountryScreen.js +20 -7
  39. package/dist/EkybStrings.d.ts +6 -1
  40. package/dist/ErrorScreensScreen.d.ts +5 -2
  41. package/dist/ErrorScreensScreen.js +15 -4
  42. package/dist/ErrorScreensStrings.d.ts +17 -2
  43. package/dist/ErrorScreensStrings.js +18 -10
  44. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  45. package/dist/FaceCaptureCapturingScreen.js +62 -2
  46. package/dist/FaceCaptureLayout.css +69 -4
  47. package/dist/FaceCaptureStrings.d.ts +23 -0
  48. package/dist/FormScreen.css +14 -0
  49. package/dist/FormScreen.d.ts +28 -0
  50. package/dist/FormScreen.js +3 -2
  51. package/dist/IdCaptureLayout.css +165 -2
  52. package/dist/IdCaptureStageScreen.d.ts +47 -0
  53. package/dist/IdCaptureStageScreen.js +39 -3
  54. package/dist/IdCaptureStrings.d.ts +19 -0
  55. package/dist/ListScreen.d.ts +19 -0
  56. package/dist/ListScreen.js +7 -2
  57. package/dist/LoaderScreen.d.ts +7 -5
  58. package/dist/LoaderScreen.js +7 -5
  59. package/dist/LoadersStrings.d.ts +18 -5
  60. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  61. package/dist/OcrReviewErrorScreen.js +24 -0
  62. package/dist/OcrReviewLayout.css +24 -0
  63. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  64. package/dist/OcrReviewLoadingScreen.js +19 -0
  65. package/dist/OcrReviewScreen.d.ts +76 -0
  66. package/dist/OcrReviewScreen.js +65 -0
  67. package/dist/OcrReviewStrings.d.ts +47 -0
  68. package/dist/OcrReviewStrings.js +17 -0
  69. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  70. package/dist/OcrReviewSuccessScreen.js +24 -0
  71. package/dist/PermissionsAllowScreen.d.ts +8 -11
  72. package/dist/PermissionsAllowScreen.js +8 -11
  73. package/dist/PermissionsAskScreen.d.ts +41 -0
  74. package/dist/PermissionsAskScreen.js +43 -0
  75. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  76. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  77. package/dist/PermissionsLayout.css +47 -0
  78. package/dist/PermissionsStrings.d.ts +20 -9
  79. package/dist/PermissionsStrings.js +10 -4
  80. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  81. package/dist/PhoneInputOtpScreen.js +26 -19
  82. package/dist/PhoneInputStrings.d.ts +11 -3
  83. package/dist/PhoneInputVerifyScreen.js +21 -5
  84. package/dist/QualifiedSignatureDocumentScreen.d.ts +8 -5
  85. package/dist/QualifiedSignatureDocumentScreen.js +9 -6
  86. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  87. package/dist/QualifiedSignatureResultScreen.js +17 -18
  88. package/dist/QualifiedSignatureStrings.d.ts +11 -4
  89. package/dist/ResultScreen.d.ts +8 -0
  90. package/dist/ResultScreen.js +14 -3
  91. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  92. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  93. package/dist/ReusabilityLayout.css +241 -0
  94. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  95. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  96. package/dist/ReusabilityStrings.d.ts +85 -0
  97. package/dist/ReusabilityStrings.js +37 -0
  98. package/dist/ReviewScreen.d.ts +38 -12
  99. package/dist/ReviewScreen.js +41 -8
  100. package/dist/ScreenLayout.css +382 -6
  101. package/dist/VideoSelfieErrorScreen.js +1 -1
  102. package/dist/VideoSelfieIdCaptureScreen.js +1 -1
  103. package/dist/VideoSelfieLayout.css +304 -2
  104. package/dist/VideoSelfieSelfieCaptureScreen.js +1 -1
  105. package/dist/VideoSelfieSpeakScreen.js +1 -1
  106. package/dist/VideoSelfieStrings.d.ts +4 -1
  107. package/dist/VideoSelfieTurnHeadScreen.js +14 -11
  108. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  109. package/dist/VideoSelfieTutorialScreen.js +10 -1
  110. package/dist/VideoSelfieWaveScreen.js +1 -1
  111. package/dist/ViewfinderScreen.css +36 -0
  112. package/dist/flows.d.ts +8 -0
  113. package/dist/flows.js +17 -0
  114. package/dist/index.d.ts +23 -0
  115. package/dist/index.js +17 -0
  116. package/dist/styles.css +4 -0
  117. package/package.json +94 -3
  118. package/samples/video-selfie-camera-wide.jpg +0 -0
  119. package/samples/video-selfie-room-wide.jpg +0 -0
  120. package/src/BiometricConsentScreen.tsx +192 -0
  121. package/src/BiometricConsentStrings.ts +119 -0
  122. package/src/CaptureScreen.tsx +17 -0
  123. package/src/DemoScreensLayout.css +16 -0
  124. package/src/DemoScreensRedirectScreen.tsx +14 -6
  125. package/src/DemoScreensStrings.ts +5 -0
  126. package/src/DigitalIdDeviceWalletsDocumentSelectionScreen.tsx +108 -0
  127. package/src/DigitalIdDeviceWalletsResultScreen.tsx +76 -0
  128. package/src/DigitalIdDeviceWalletsStrings.ts +80 -0
  129. package/src/DigitalIdProvidersDocumentSelectionScreen.tsx +112 -0
  130. package/src/DigitalIdProvidersErrorScreen.tsx +104 -0
  131. package/src/DigitalIdProvidersLayout.css +59 -0
  132. package/src/DigitalIdProvidersRedirectScreen.tsx +65 -0
  133. package/src/DigitalIdProvidersStrings.ts +98 -0
  134. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  135. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  136. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  137. package/src/DigitalIdUploadStrings.ts +88 -0
  138. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  139. package/src/DocumentCaptureIllustration.tsx +1 -1
  140. package/src/EkybCountryScreen.tsx +23 -6
  141. package/src/EkybStrings.ts +6 -1
  142. package/src/ErrorScreensScreen.tsx +15 -4
  143. package/src/ErrorScreensStrings.ts +37 -12
  144. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  145. package/src/FaceCaptureLayout.css +69 -4
  146. package/src/FaceCaptureStrings.ts +24 -0
  147. package/src/FormScreen.css +14 -0
  148. package/src/FormScreen.tsx +34 -0
  149. package/src/IdCaptureLayout.css +165 -2
  150. package/src/IdCaptureStageScreen.tsx +75 -0
  151. package/src/IdCaptureStrings.ts +19 -0
  152. package/src/ListScreen.tsx +48 -9
  153. package/src/LoaderScreen.tsx +7 -5
  154. package/src/LoadersStrings.ts +18 -5
  155. package/src/OcrReviewErrorScreen.tsx +53 -0
  156. package/src/OcrReviewLayout.css +24 -0
  157. package/src/OcrReviewLoadingScreen.tsx +48 -0
  158. package/src/OcrReviewScreen.tsx +190 -0
  159. package/src/OcrReviewStrings.ts +69 -0
  160. package/src/OcrReviewSuccessScreen.tsx +52 -0
  161. package/src/PermissionsAllowScreen.tsx +9 -10
  162. package/src/PermissionsAskScreen.tsx +105 -0
  163. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  164. package/src/PermissionsLayout.css +47 -0
  165. package/src/PermissionsStrings.ts +42 -12
  166. package/src/PhoneInputOtpScreen.tsx +23 -3
  167. package/src/PhoneInputStrings.ts +11 -3
  168. package/src/PhoneInputVerifyScreen.tsx +24 -4
  169. package/src/QualifiedSignatureDocumentScreen.tsx +9 -7
  170. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  171. package/src/QualifiedSignatureStrings.ts +11 -4
  172. package/src/ResultScreen.tsx +33 -2
  173. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  174. package/src/ReusabilityLayout.css +241 -0
  175. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  176. package/src/ReusabilityStrings.ts +127 -0
  177. package/src/ReviewScreen.tsx +96 -27
  178. package/src/ScreenLayout.css +382 -6
  179. package/src/VideoSelfieErrorScreen.tsx +1 -1
  180. package/src/VideoSelfieIdCaptureScreen.tsx +9 -1
  181. package/src/VideoSelfieLayout.css +304 -2
  182. package/src/VideoSelfieSelfieCaptureScreen.tsx +9 -1
  183. package/src/VideoSelfieSpeakScreen.tsx +9 -1
  184. package/src/VideoSelfieStrings.ts +4 -2
  185. package/src/VideoSelfieTurnHeadScreen.tsx +25 -14
  186. package/src/VideoSelfieTutorialScreen.tsx +11 -0
  187. package/src/VideoSelfieWaveScreen.tsx +9 -1
  188. package/src/ViewfinderScreen.css +36 -0
  189. package/src/flows.ts +25 -0
  190. package/src/index.ts +116 -3
  191. package/src/styles.css +4 -0
@@ -6,6 +6,7 @@ import {
6
6
  } from '@incodetech/prizma-core-web/id-capture';
7
7
  import { PrizmaCaptureManual } from '@incodetech/prizma-core-web/capture-manual';
8
8
  import { PrizmaCaptureFeedback } from '@incodetech/prizma-core-web/capture-feedback';
9
+ import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
9
10
  import { PrizmaTag } from '@incodetech/prizma-core-web/tag';
10
11
  import { PrizmaScreenShell, type PrizmaScreenShellProps } from './ScreenShell';
11
12
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
@@ -80,6 +81,28 @@ export interface IdCaptureStageScreenProps
80
81
  still?: ReactNode;
81
82
  /** Digit shown by the countdown Timer (`*-countdown` sections 1..3). */
82
83
  countdownValue?: 1 | 2 | 3;
84
+ /**
85
+ * Raise the "Need more time?" offer over the feed — the accessibility
86
+ * notification shown 20 s before auto-capture falls back to the shutter.
87
+ *
88
+ * A MODIFIER of whatever stage state is already showing, not a twelfth
89
+ * state: the 30 s fallback timer runs underneath the hint overlay, so the
90
+ * person can be shown the hand-and-document ghost and offered more time in
91
+ * the same frame. Needs `strings.stage.extendTime`; without it the flag is a
92
+ * no-op, which is what a host that has not localized the copy should get
93
+ * rather than English. Same contract as `FaceCaptureCapturingScreen`.
94
+ */
95
+ extendTime?: boolean;
96
+ /** Tapping "Extend auto-capture". The host resets its own fallback timer. */
97
+ onExtendTime?: () => void;
98
+ /**
99
+ * A notice band of the host's own, in the slot `extendTime` uses. Wins over
100
+ * `extendTime` when both are given. Exists for a host that needs the
101
+ * Snackbar to do something the static one cannot — Core Lab's prototype
102
+ * swaps in a copy whose count actually runs down — without reaching into
103
+ * the screen. The static `extendTime` notice stays the default.
104
+ */
105
+ notice?: ReactNode;
83
106
  screenId?: string;
84
107
  overrides?: PrizmaScreenOverrides;
85
108
  onAction?: PrizmaScreenActionHandler;
@@ -113,6 +136,31 @@ export interface IdCaptureStageScreenProps
113
136
  * to the column's bottom padding) — no offsets are invented here, and
114
137
  * PrizmaIdCapture's own frame→encrypted-row adjacency is left untouched
115
138
  * (DEVIATIONS.md #12's lesson: never stack an offset a Core component carries).
139
+ *
140
+ * ── The "Need more time?" offer (`extendTime`) ──────────────────────────────
141
+ *
142
+ * Auto-capture gives up after a configured timeout (30 s in the module's own
143
+ * flow frames) and drops the person onto the shutter. Someone who is
144
+ * struggling — a tremor, low vision, a dark room, a card that will not sit
145
+ * flat — gets moved off the EASIER path precisely because they were slow,
146
+ * which is backwards. So 20 s before the switch, the stage offers to stay on
147
+ * auto. A client turns it on per flow from the dashboard; with it off, nothing
148
+ * about today's capture changes. It is the conformance route for WCAG 2.2.1
149
+ * Timing Adjustable via the *Extend* clause (warn before the limit, ≥20 s to
150
+ * act, ≥10 extensions) — the reasoning, the `role="alert"` decision and the
151
+ * `neutral` state are all Face Capture's (2026-10-05) and are not re-argued
152
+ * here: `FaceCaptureCapturingScreen`'s header is the record. One feature, two
153
+ * modules, one Snackbar.
154
+ *
155
+ * WHERE IT SITS is the one thing this module decides differently. Face
156
+ * Capture anchors the notice on the shutter-strip rail because, there, notice
157
+ * and shutter are alternatives — a capturing screen either raises the offer or
158
+ * shows the button. ID Capture's strip is never free: the "?" help control
159
+ * rides it on every stage, auto or manual (IDC-29). So the notice takes the
160
+ * BOTTOM BAND instead — the flexible air between the encrypted row and the
161
+ * strip's reserved space — which is the one slot on this stage nothing else
162
+ * owns. It bottom-anchors there like the band's other content would, so the
163
+ * frame above does not move when the offer arrives. DEVIATIONS.md #IDC-32.
116
164
  */
117
165
  export const IdCaptureStageScreen = forwardRef<HTMLDivElement, IdCaptureStageScreenProps>(
118
166
  (
@@ -128,6 +176,9 @@ export const IdCaptureStageScreen = forwardRef<HTMLDivElement, IdCaptureStageScr
128
176
  camera,
129
177
  still,
130
178
  countdownValue = 3,
179
+ extendTime,
180
+ onExtendTime,
181
+ notice,
131
182
  screenId = `id-capture-${documentType}-${side}-${state}`,
132
183
  overrides,
133
184
  onAction,
@@ -146,6 +197,29 @@ export const IdCaptureStageScreen = forwardRef<HTMLDivElement, IdCaptureStageScr
146
197
  const pillKind = feedback === undefined ? STAGE_FEEDBACK[state] : feedback;
147
198
  const pillCopy = pillKind ? strings.feedback[pillKind] : undefined;
148
199
 
200
+ const offer = strings.stage.extendTime;
201
+ // No copy, no notice: a host that has not localized this must not get
202
+ // English, and the flag alone is not permission to invent it.
203
+ const band =
204
+ notice ??
205
+ (extendTime && offer ? (
206
+ <PrizmaSnackbar
207
+ state="neutral"
208
+ isMultiline
209
+ // Assertive on purpose — a polite region can queue behind the
210
+ // stage's own state announcements and be read out after the 20 s
211
+ // has already run out. FaceCaptureCapturingScreen's header has the
212
+ // full reasoning; this instance follows it.
213
+ role="alert"
214
+ linkLabel={offer.linkLabel}
215
+ onLinkClick={onExtendTime}
216
+ >
217
+ <strong>{offer.title}</strong>
218
+ <br />
219
+ {offer.description}
220
+ </PrizmaSnackbar>
221
+ ) : undefined);
222
+
149
223
  return (
150
224
  <PrizmaScreenShell
151
225
  ref={ref}
@@ -213,6 +287,7 @@ export const IdCaptureStageScreen = forwardRef<HTMLDivElement, IdCaptureStageScr
213
287
  still={still}
214
288
  />
215
289
  <div className="id-capture-band">
290
+ {band ? <div className="id-capture-stage__notice">{band}</div> : null}
216
291
  </div>
217
292
  </div>
218
293
  <div className="prizma-screen-layout__capture-strip">
@@ -126,6 +126,25 @@ export interface IdCaptureStrings {
126
126
  /** Visually-hidden state announcements, per stage state — forwarded to
127
127
  * `PrizmaIdCapture.stateLabels`. */
128
128
  stateLabels?: Partial<Record<IdCaptureStageState, string>>;
129
+ /**
130
+ * stage.extendTime — the accessibility offer that rides the camera stage
131
+ * 20 s before auto-capture gives up and hands over to the shutter
132
+ * ("Need more time? / Manual capture starts in 20 s / Extend auto-capture").
133
+ * The same contract as Face Capture's `extendTime`: one feature, two
134
+ * modules, identical copy, so the locale overlay translates both at once.
135
+ *
136
+ * `description` is one localized sentence that CONTAINS the count, not a
137
+ * template — ten locales put the number in ten places. Optional: a host
138
+ * that has not localized it gets no notice rather than English.
139
+ */
140
+ extendTime?: {
141
+ /** Bold first line — "Need more time?" */
142
+ title: string;
143
+ /** "Manual capture starts in 20 s" — the count lives inside the copy. */
144
+ description: string;
145
+ /** The action that keeps the user on auto — "Extend auto-capture". */
146
+ linkLabel: string;
147
+ };
129
148
  };
130
149
 
131
150
  /** The on-camera feedback pills the error stages carry. */
@@ -74,6 +74,25 @@ export interface PrizmaListScreenProps
74
74
  * (common issues) and "Quit process" (permission). Figma draws Core's
75
75
  * `tertiary-link` composite in the band's top slot. */
76
76
  linkLabel?: string;
77
+ /**
78
+ * A SECOND tertiary link, under the first. Reusability's selection draws
79
+ * "What data will be shared?" over "Incode’s Privacy Policy" with no primary
80
+ * between them — two non-primary actions a host has to tell apart, so this
81
+ * one emits `tertiary` where `linkLabel` emits `secondary` (the #PI-10
82
+ * reasoning). Stacked at the band's flat Spacing/16 like everything else in
83
+ * it. Omit it and nothing renders, so every existing consumer is untouched.
84
+ * DEVIATIONS #RU-4.
85
+ */
86
+ secondaryLinkLabel?: string;
87
+ /**
88
+ * A shell-level layer painted OVER the whole screen — the same slot
89
+ * archetypes A and E carry (#F-32, #BC-8): a scrim plus Core's
90
+ * `PrizmaBottomSheetContent` or `PrizmaModalContent`, presented on top of the
91
+ * dimmed list. It cannot ride in the content column: it is absolutely
92
+ * positioned against the shell. Reusability's shared-details sheet is the
93
+ * first List consumer (DEVIATIONS #RU-5).
94
+ */
95
+ overlay?: ReactNode;
77
96
  /** "verified by" attribution copy for the footer tag. */
78
97
  verifiedLabel: string;
79
98
  navLabel?: string;
@@ -122,6 +141,8 @@ export const PrizmaListScreen = forwardRef<HTMLDivElement, PrizmaListScreenProps
122
141
  ctaLabel,
123
142
  secondaryCtaLabel,
124
143
  linkLabel,
144
+ secondaryLinkLabel,
145
+ overlay,
125
146
  verifiedLabel,
126
147
  navLabel,
127
148
  backLabel,
@@ -171,17 +192,34 @@ export const PrizmaListScreen = forwardRef<HTMLDivElement, PrizmaListScreenProps
171
192
  >
172
193
  {children}
173
194
  </div>
174
- {ctaLabel || secondaryCtaLabel || linkLabel || disclaimer ? (
195
+ {ctaLabel || secondaryCtaLabel || linkLabel || secondaryLinkLabel || disclaimer ? (
175
196
  <div className="prizma-screen-layout__band">
176
197
  {disclaimer ? <PrizmaDisclaimer text={disclaimer} /> : null}
177
- {linkLabel ? (
178
- <PrizmaButton
179
- variant="tertiary-link"
180
- size="L"
181
- onClick={() => onAction?.({ type: 'secondary', screenId })}
182
- >
183
- {linkLabel}
184
- </PrizmaButton>
198
+ {linkLabel || secondaryLinkLabel ? (
199
+ /* The links share ONE band slot, stacked flush: two 56px link
200
+ rows with the band's 16 between them read as two distant
201
+ lines (Camila, 2026-10-13); the row height is already the
202
+ touch target. ScreenLayout.css `__band__links`. */
203
+ <div className="prizma-screen-layout__band__links">
204
+ {linkLabel ? (
205
+ <PrizmaButton
206
+ variant="tertiary-link"
207
+ size="L"
208
+ onClick={() => onAction?.({ type: 'secondary', screenId })}
209
+ >
210
+ {linkLabel}
211
+ </PrizmaButton>
212
+ ) : null}
213
+ {secondaryLinkLabel ? (
214
+ <PrizmaButton
215
+ variant="tertiary-link"
216
+ size="L"
217
+ onClick={() => onAction?.({ type: 'tertiary', screenId })}
218
+ >
219
+ {secondaryLinkLabel}
220
+ </PrizmaButton>
221
+ ) : null}
222
+ </div>
185
223
  ) : null}
186
224
  {/* THE PRIMARY ALWAYS SITS ABOVE THE SECONDARY (owner rule
187
225
  2026-08-19, DEVIATIONS #GEO-1). The rule governs the
@@ -214,6 +252,7 @@ export const PrizmaListScreen = forwardRef<HTMLDivElement, PrizmaListScreenProps
214
252
  <PrizmaTag label={verifiedLabel} />
215
253
  </div>
216
254
  ) : null}
255
+ {overlay}
217
256
  </PrizmaScreenShell>
218
257
  ),
219
258
  );
@@ -23,11 +23,13 @@ export interface LoaderScreenProps
23
23
  * (GUIDELINES.md §6-C, `PrizmaProcessingScreen`).
24
24
  *
25
25
  * Figma: `Module Generic Screens` frames `67:2388` ("Processing…") and
26
- * `67:3593` ("Just a moment…"). ONE screen for both, and for every module
27
- * loader after them: logo header → Core's spinner → status line → reassurance
28
- * line → the verified tag, with no CTA. The archetype already IS this recipe;
29
- * this component exists so the family has a name a designer can point at and a
30
- * copy contract a host can fill, not to add layout.
26
+ * `67:3593` ("Just a moment…"), plus the SDK's own "Hold on a sec…" wait that
27
+ * ID Capture, NFC and the Demo App show (`verifyingIdentity`). ONE screen for
28
+ * all of them, and for every module loader after them: logo header → Core's
29
+ * spinner → status line → reassurance line → the verified tag, with no CTA.
30
+ * The archetype already IS this recipe; this component exists so the family
31
+ * has a name a designer can point at and a copy contract a host can fill, not
32
+ * to add layout.
31
33
  *
32
34
  * Reach for `PrizmaResultScreen` the moment a wait screen needs a button — a
33
35
  * loader with a "Cancel" is a result screen, and the missing CTA band here is
@@ -15,12 +15,25 @@
15
15
  // No hardcoded English anywhere in a screen: every visible string arrives here.
16
16
 
17
17
  /**
18
- * The two frames in the generic file. They are the same screen with different
19
- * copy — which is the point of the family: the loader is a shape, and the two
20
- * entries here are examples of filling it, not a closed set. A module that
21
- * waits for something else supplies its own pair of strings under a new key.
18
+ * The copy pairs the family records. The loader is a shape, and each entry
19
+ * here is a way of filling it that more than one module already uses — not a
20
+ * closed set. A module that waits for something else supplies its own pair of
21
+ * strings under a new key.
22
+ *
23
+ * - `processing` — frame `67:2388`. "Processing…" over the generic
24
+ * reassurance line. Watchlist for Business, eKYC, eKYB,
25
+ * Email Input and the signatures all draw this title.
26
+ * - `validating` — frame `67:3593`. "Just a moment…", the documents
27
+ * are being validated.
28
+ * - `verifyingIdentity` — the SDK's own `loadingCircle.holdOn` wait: "Hold on
29
+ * a sec…" over "We're verifying your identity". ID
30
+ * Capture, NFC and the Demo App show it between the
31
+ * last capture and the verdict (Camila, 2026-10-09).
32
+ *
33
+ * Copy rule for every wait title: the ellipsis is the single `…` character,
34
+ * never two or three periods, so the same word reads the same in every module.
22
35
  */
23
- export type LoadersVariant = 'processing' | 'validating';
36
+ export type LoadersVariant = 'processing' | 'validating' | 'verifyingIdentity';
24
37
 
25
38
  export interface LoadersCopy {
26
39
  /** H2 status line ("Processing…"). */
@@ -0,0 +1,53 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaResultScreen, type PrizmaResultScreenProps } from './ResultScreen';
3
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
4
+ import type { OcrReviewStrings } from './OcrReviewStrings';
5
+ import './ScreenLayout.css';
6
+
7
+ export interface OcrReviewErrorScreenProps
8
+ extends Omit<
9
+ PrizmaResultScreenProps,
10
+ 'status' | 'title' | 'ctaLabel' | 'verifiedLabel' | 'navLabel'
11
+ > {
12
+ /** Module copy — this screen reads the `error` slice. */
13
+ strings: OcrReviewStrings;
14
+ screenId?: string;
15
+ overrides?: PrizmaScreenOverrides;
16
+ onAction?: PrizmaScreenActionHandler;
17
+ }
18
+
19
+ /**
20
+ * OCR Review — "Something went wrong", built on Archetype F (GUIDELINES.md
21
+ * §6-F, `PrizmaResultScreen`). Figma: `OCR Error` (31084:18797), drawn OUTSIDE
22
+ * the Happy Path section and reached off the "Processed successfully?"
23
+ * decision's "No" branch — a plain status + title + "Try again" CTA.
24
+ *
25
+ * Nav and footer tag are left at the archetype's DEFAULT (shown). The frame
26
+ * itself draws neither, matching `DigitalIdUploadFileTooLargeScreen.tsx` —
27
+ * but that screen is the ONE exception in the whole package, not the pattern:
28
+ * every other error screen (`EkycErrorScreen`, `EkybErrorScreen`,
29
+ * `EmailInputErrorScreen`, `FormsErrorScreen`, `IdCaptureDocumentErrorScreen`)
30
+ * keeps both. Hiding them here, with only one CTA to balance the bottom,
31
+ * pushed the status + title below where every sibling error screen centres it
32
+ * (Mili, 2026-10-12 — checked against the system's own screens, not re-derived
33
+ * from the frame).
34
+ */
35
+ export const OcrReviewErrorScreen = forwardRef<HTMLDivElement, OcrReviewErrorScreenProps>(
36
+ ({ strings, screenId = 'ocr-review-error', overrides, onAction, ...shell }, ref) => (
37
+ <PrizmaResultScreen
38
+ ref={ref}
39
+ status="error"
40
+ title={strings.error.title}
41
+ ctaLabel={strings.error.ctaLabel}
42
+ verifiedLabel={strings.verifiedLabel}
43
+ navLabel={strings.navLabel}
44
+ screenId={screenId}
45
+ overrides={overrides}
46
+ onAction={onAction}
47
+ {...shell}
48
+ />
49
+ ),
50
+ );
51
+ OcrReviewErrorScreen.displayName = 'OcrReviewErrorScreen';
52
+
53
+ export default OcrReviewErrorScreen;
@@ -0,0 +1,24 @@
1
+ /* OCR Review — the one piece of chrome the archetype doesn't own.
2
+ *
3
+ * `PrizmaFormScreen.media` places whatever the module hands it; the circular
4
+ * crop and fixed size are the module's own, same split `progress` already
5
+ * draws (the archetype positions a `PrizmaStepper`, never sizes it). 90×90,
6
+ * centred — Figma's `Image / Selfie` instance, read straight off the frame. A
7
+ * `fields` row defaults to `align-items: stretch` (FormScreen.css), so the
8
+ * thumbnail is the one row that opts itself out with `align-self`.
9
+ */
10
+ .ocr-review__photo {
11
+ width: 90px;
12
+ height: 90px;
13
+ flex-shrink: 0;
14
+ align-self: center;
15
+ border-radius: var(--prizma-border-radius-full);
16
+ overflow: hidden;
17
+ }
18
+
19
+ .ocr-review__photo img {
20
+ display: block;
21
+ width: 100%;
22
+ height: 100%;
23
+ object-fit: cover;
24
+ }
@@ -0,0 +1,48 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaResultScreen, type PrizmaResultScreenProps } from './ResultScreen';
3
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
4
+ import type { OcrReviewStrings } from './OcrReviewStrings';
5
+ import './ScreenLayout.css';
6
+
7
+ export interface OcrReviewLoadingScreenProps
8
+ extends Omit<
9
+ PrizmaResultScreenProps,
10
+ 'status' | 'title' | 'subtitle' | 'verifiedLabel' | 'navLabel'
11
+ > {
12
+ /** Module copy — this screen reads the `loading` slice. */
13
+ strings: OcrReviewStrings;
14
+ screenId?: string;
15
+ overrides?: PrizmaScreenOverrides;
16
+ onAction?: PrizmaScreenActionHandler;
17
+ }
18
+
19
+ /**
20
+ * OCR Review — server-side verification wait ("Hold on a sec…"). Figma:
21
+ * `1-18-1-id-hold-on` (31084:18795) — ID Capture's own frame, redrawn here
22
+ * rather than imported by reference: every module in this package owns its
23
+ * hold-on-shaped screen independently (`IdCaptureHoldOnScreen.tsx` is ID
24
+ * Capture's), and a cross-module import was tried once (Digital ID Upload)
25
+ * and dropped rather than kept.
26
+ *
27
+ * Archetype C (§6-C), `PrizmaResultScreen` with `status="loading"` and no CTA
28
+ * — identical shape to `IdCaptureHoldOnScreen.tsx`.
29
+ */
30
+ export const OcrReviewLoadingScreen = forwardRef<HTMLDivElement, OcrReviewLoadingScreenProps>(
31
+ ({ strings, screenId = 'ocr-review-loading', overrides, onAction, ...shell }, ref) => (
32
+ <PrizmaResultScreen
33
+ ref={ref}
34
+ status="loading"
35
+ title={strings.loading.title}
36
+ subtitle={strings.loading.description}
37
+ verifiedLabel={strings.verifiedLabel}
38
+ navLabel={strings.navLabel}
39
+ screenId={screenId}
40
+ overrides={overrides}
41
+ onAction={onAction}
42
+ {...shell}
43
+ />
44
+ ),
45
+ );
46
+ OcrReviewLoadingScreen.displayName = 'OcrReviewLoadingScreen';
47
+
48
+ export default OcrReviewLoadingScreen;
@@ -0,0 +1,190 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
3
+ import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
4
+ import { PrizmaRadio } from '@incodetech/prizma-core-web/radio';
5
+ import { PrizmaRadioGroup } from '@incodetech/prizma-core-web/radio-group';
6
+ import { PrizmaFormScreen, type PrizmaFormScreenProps } from './FormScreen';
7
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
8
+ import type { OcrReviewState, OcrReviewStrings } from './OcrReviewStrings';
9
+ import './ScreenLayout.css';
10
+ import './OcrReviewLayout.css';
11
+
12
+ export interface OcrReviewScreenProps
13
+ extends Omit<
14
+ PrizmaFormScreenProps,
15
+ 'title' | 'ctaLabel' | 'verifiedLabel' | 'children' | 'navLabel' | 'scrollFields'
16
+ > {
17
+ /** Module copy — this screen reads the `review` slice. */
18
+ strings: OcrReviewStrings;
19
+ /** Which `Review OCR Enable Edit` instance to render. Default `default`.
20
+ * Ignored when `editable` is `false` — the disabled frame draws none of
21
+ * these states (there is nothing to submit wrong when nothing can be
22
+ * typed). */
23
+ state?: OcrReviewState;
24
+ /**
25
+ * `Review OCR Enable Edit` vs `Review OCR Disable Edit` (30530:119373) — the
26
+ * module's own boolean axis, orthogonal to `state`. `false` renders every
27
+ * field `disabled` (Core's grey surface + dimmed text, same prop
28
+ * `PrizmaTextField` / `PrizmaDateField` / `PrizmaRadioGroup` already ship)
29
+ * with no required asterisks (the frame hides them — nothing to validate on
30
+ * a field nobody can edit) while Continue stays enabled. Default `true`.
31
+ */
32
+ editable?: boolean;
33
+ /** The subject photo — Figma's `Image / Selfie`, 90×90 circular. */
34
+ photoSrc: string;
35
+ /** Field values the fields start with. Every field is uncontrolled
36
+ * (`defaultValue`) — a screen is one static spec state, same rule
37
+ * `EkycDetailsScreen` draws (#PI-6). */
38
+ values?: {
39
+ fullName?: string;
40
+ dateOfBirth?: string;
41
+ gender?: 'male' | 'female';
42
+ documentNumber?: string;
43
+ expiryDate?: string;
44
+ address?: string;
45
+ };
46
+ screenId?: string;
47
+ overrides?: PrizmaScreenOverrides;
48
+ onAction?: PrizmaScreenActionHandler;
49
+ }
50
+
51
+ /**
52
+ * OCR Review — "Please check your data", built on the Form archetype
53
+ * (GUIDELINES.md §6-E, `PrizmaFormScreen`).
54
+ *
55
+ * ONE screen for both drawn states: `default` (31084:18792 / 18794 — every
56
+ * field filled, Continue enabled) and `documentNumberMissing` (31084:18793 —
57
+ * every OTHER field keeps its filled value, only Document number is cleared
58
+ * and carries Core's `error` + `documentNumberRequiredError`). Unlike eKYC's
59
+ * `emptyError` (every required field clears and errors together), this frame
60
+ * draws a SINGLE field in that state — the empty-submit rule still applies,
61
+ * scoped to the one field Figma actually shows failing it.
62
+ *
63
+ * The photo sits ABOVE Full name as the field stack's own first row — Figma's
64
+ * `input-list` draws it at the same Spacing/16 rhythm as every field below it.
65
+ * It is the field stack's plain FIRST CHILD, nothing more: `PrizmaFormScreen`
66
+ * already renders whatever `children` hands it with that same gap, so this
67
+ * needed no new slot on the archetype (the archetype is untouched).
68
+ *
69
+ * Date of birth and Expiry date are both Core's `PrizmaDateField` — the
70
+ * frame's "Dropdown" instance name is the shared component's internal name for
71
+ * a date-kind field, not a literal dropdown (same reading `EkycDetailsScreen`
72
+ * gives its own Date of birth field). Gender is Core's `PrizmaRadioGroup` +
73
+ * `PrizmaRadio` — first real consumer of either in this package.
74
+ *
75
+ * The field stack SCROLLS (`scrollFields`, the same archetype prop eKYB's
76
+ * `EkybOwnersScreen`, Forms' long form and Qualified Signature's accept screen
77
+ * already use): the photo plus six fields do not fit an 844 frame without it,
78
+ * and without the release the Address row paints under the CTA instead of
79
+ * above it. Not a prop a caller picks — this module always needs it, so it is
80
+ * omitted from `OcrReviewScreenProps` and hardcoded here, matching those three
81
+ * precedents. `Review OCR Disable Edit` overflows the same way (its own
82
+ * `input-list` measures 654 against a 448 middle container) — one more reason
83
+ * `editable` is a prop of this same screen rather than a second component.
84
+ */
85
+ export const OcrReviewScreen = forwardRef<HTMLDivElement, OcrReviewScreenProps>(
86
+ (
87
+ {
88
+ strings,
89
+ state = 'default',
90
+ editable = true,
91
+ photoSrc,
92
+ values,
93
+ screenId = editable ? `ocr-review-${state}` : 'ocr-review-disabled',
94
+ ctaDisabled,
95
+ className,
96
+ overrides,
97
+ onAction,
98
+ ...shell
99
+ },
100
+ ref,
101
+ ) => {
102
+ // The empty-submit retry only exists on the editable frame — nothing can
103
+ // be left empty on a field nobody can type into.
104
+ const documentNumberMissing = editable && state === 'documentNumberMissing';
105
+ const documentNumberError = documentNumberMissing
106
+ ? { error: true, errorText: strings.review.documentNumberRequiredError }
107
+ : {};
108
+ // `Review OCR Disable Edit` hides every required asterisk (31256:169923 is
109
+ // marked `hidden` on its own Gender row) — spread onto every field only
110
+ // when editable, rather than a `required={editable}` on each that would
111
+ // still render an empty, pointless suffix slot.
112
+ const requiredProps = editable
113
+ ? { required: true, requiredHint: strings.review.requiredHint }
114
+ : {};
115
+
116
+ return (
117
+ <PrizmaFormScreen
118
+ ref={ref}
119
+ className={className}
120
+ title={strings.review.title}
121
+ ctaLabel={strings.review.ctaLabel}
122
+ ctaDisabled={ctaDisabled}
123
+ verifiedLabel={strings.verifiedLabel}
124
+ navLabel={strings.navLabel}
125
+ screenId={screenId}
126
+ overrides={overrides}
127
+ onAction={onAction}
128
+ /* The archetype's own scrolling field stack — the photo plus six
129
+ fields do not fit an 844 frame. No module CSS. */
130
+ scrollFields
131
+ {...shell}
132
+ >
133
+ <div className="ocr-review__photo">
134
+ <img src={photoSrc} alt="" />
135
+ </div>
136
+ <PrizmaTextField
137
+ label={strings.review.fullNameLabel}
138
+ {...requiredProps}
139
+ disabled={!editable}
140
+ defaultValue={values?.fullName ?? ''}
141
+ autoComplete="off"
142
+ clearable={false}
143
+ />
144
+ <PrizmaDateField
145
+ label={strings.review.dateOfBirthLabel}
146
+ {...requiredProps}
147
+ disabled={!editable}
148
+ defaultValue={values?.dateOfBirth ?? ''}
149
+ autoComplete="off"
150
+ />
151
+ <PrizmaRadioGroup
152
+ name={`${screenId}-gender`}
153
+ legend={strings.review.genderLabel}
154
+ defaultValue={values?.gender}
155
+ disabled={!editable}
156
+ >
157
+ <PrizmaRadio textSize="m" value="male" text={strings.review.maleLabel} />
158
+ <PrizmaRadio textSize="m" value="female" text={strings.review.femaleLabel} />
159
+ </PrizmaRadioGroup>
160
+ <PrizmaTextField
161
+ label={strings.review.documentNumberLabel}
162
+ {...requiredProps}
163
+ {...documentNumberError}
164
+ disabled={!editable}
165
+ defaultValue={documentNumberMissing ? '' : values?.documentNumber ?? ''}
166
+ autoComplete="off"
167
+ clearable={false}
168
+ />
169
+ <PrizmaDateField
170
+ label={strings.review.expiryDateLabel}
171
+ {...requiredProps}
172
+ disabled={!editable}
173
+ defaultValue={values?.expiryDate ?? ''}
174
+ autoComplete="off"
175
+ />
176
+ <PrizmaTextField
177
+ label={strings.review.addressLabel}
178
+ {...requiredProps}
179
+ disabled={!editable}
180
+ defaultValue={values?.address ?? ''}
181
+ autoComplete="off"
182
+ clearable={false}
183
+ />
184
+ </PrizmaFormScreen>
185
+ );
186
+ },
187
+ );
188
+ OcrReviewScreen.displayName = 'OcrReviewScreen';
189
+
190
+ export default OcrReviewScreen;
@@ -0,0 +1,69 @@
1
+ // OCR Review module — copy contract.
2
+ //
3
+ // Every OCR Review screen receives the SAME `strings: OcrReviewStrings`
4
+ // object (rule: all copy via props, no hardcoded English — GUIDELINES.md §9).
5
+ //
6
+ // Authoritative screens: `ID Capture — Latest Design` file
7
+ // (U1EOfl9RQ1rOlOIuQw3rhL), section `OCR Review - 🍎 iOS - Full flow`
8
+ // (31084:18781) → `Happy Path` (31084:18782). Three `Review OCR Enable Edit`
9
+ // instances draw one screen in two states (filled, and Document number
10
+ // cleared + errored after an empty Continue press), `1-18-1-id-hold-on` is
11
+ // ID Capture's own wait screen redrawn here rather than reused by reference
12
+ // (see OcrReviewLoadingScreen.tsx — every module in this package owns its own
13
+ // hold-on-shaped screen; Digital ID Upload tried sharing one by reference and
14
+ // it was dropped, not kept), `OCR Success` and `OCR Error` (the latter OUTSIDE
15
+ // the Happy Path section, reached off the "Processed successfully?" decision's
16
+ // "No" branch) close the module on Archetype F.
17
+
18
+ export type OcrReviewState = 'default' | 'documentNumberMissing';
19
+
20
+ export interface OcrReviewStrings {
21
+ /** Accessible name for the logo nav region (i18n — no baked-in English). */
22
+ navLabel?: string;
23
+ /** "verified by" attribution copy for the footer tag. */
24
+ verifiedLabel: string;
25
+
26
+ /** `Review OCR Enable Edit` (31084:18792 / 18793 / 18794) — one screen, two
27
+ * states (`default`, `documentNumberMissing`). */
28
+ review: {
29
+ /** "Please check your data" */
30
+ title: string;
31
+ /** The asterisk suffix on every required field's label ("*"). */
32
+ requiredHint: string;
33
+ fullNameLabel: string;
34
+ dateOfBirthLabel: string;
35
+ genderLabel: string;
36
+ maleLabel: string;
37
+ femaleLabel: string;
38
+ documentNumberLabel: string;
39
+ /** "Field can't be empty" — Document number's error in `documentNumberMissing`. */
40
+ documentNumberRequiredError: string;
41
+ expiryDateLabel: string;
42
+ addressLabel: string;
43
+ /** "Continue" */
44
+ ctaLabel: string;
45
+ };
46
+
47
+ /** `1-18-1-id-hold-on`, redrawn for this module (see OcrReviewLoadingScreen.tsx). */
48
+ loading: {
49
+ /** "Hold on a sec.." */
50
+ title: string;
51
+ /** "We're verifying your identity" */
52
+ description: string;
53
+ };
54
+
55
+ /** `OCR Success` (31084:18796). */
56
+ success: {
57
+ /** "Identity successfully verified" */
58
+ title: string;
59
+ };
60
+
61
+ /** `OCR Error` (31084:18797) — off the "Processed successfully?" decision's
62
+ * "No" branch, drawn outside the Happy Path section. */
63
+ error: {
64
+ /** "Something went wrong" */
65
+ title: string;
66
+ /** "Try again" */
67
+ ctaLabel: string;
68
+ };
69
+ }