@incodetech/prizma-core-screens 0.2.4 → 0.2.12

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 (243) 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/CurpEnterScreen.d.ts +7 -0
  8. package/dist/CurpEnterScreen.js +17 -3
  9. package/dist/CurpGenerateScreen.d.ts +8 -0
  10. package/dist/CurpGenerateScreen.js +28 -13
  11. package/dist/CurpLayout.css +8 -34
  12. package/dist/CurpStrings.d.ts +16 -2
  13. package/dist/DemoScreensLayout.css +16 -0
  14. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  15. package/dist/DemoScreensRedirectScreen.js +3 -2
  16. package/dist/DemoScreensStrings.d.ts +5 -0
  17. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  18. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  19. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  20. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  21. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  22. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  23. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  24. package/dist/DigitalIdUploadStrings.js +19 -0
  25. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  26. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  27. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  28. package/dist/EkybAddressDetailsScreen.d.ts +7 -0
  29. package/dist/EkybAddressDetailsScreen.js +25 -7
  30. package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
  31. package/dist/EkybBusinessDetailsScreen.js +23 -6
  32. package/dist/EkybCountryScreen.d.ts +13 -5
  33. package/dist/EkybCountryScreen.js +41 -10
  34. package/dist/EkybOwnersScreen.d.ts +8 -0
  35. package/dist/EkybOwnersScreen.js +26 -3
  36. package/dist/EkybStrings.d.ts +32 -10
  37. package/dist/EkycCountryCatalog.d.ts +4 -1
  38. package/dist/EkycCountryCatalog.js +5 -3
  39. package/dist/EkycCountryFormScreen.d.ts +12 -3
  40. package/dist/EkycCountryFormScreen.js +28 -10
  41. package/dist/EkycDetailsScreen.d.ts +7 -0
  42. package/dist/EkycDetailsScreen.js +21 -4
  43. package/dist/EkycStrings.d.ts +14 -2
  44. package/dist/ElectronicSignatureLayout.css +6 -0
  45. package/dist/EmailInputStrings.d.ts +11 -1
  46. package/dist/EmailInputVerifyScreen.d.ts +7 -0
  47. package/dist/EmailInputVerifyScreen.js +17 -3
  48. package/dist/ErrorScreensScreen.d.ts +5 -2
  49. package/dist/ErrorScreensScreen.js +15 -4
  50. package/dist/ErrorScreensStrings.d.ts +17 -2
  51. package/dist/ErrorScreensStrings.js +18 -10
  52. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  53. package/dist/FaceCaptureCapturingScreen.js +62 -2
  54. package/dist/FaceCaptureLayout.css +69 -4
  55. package/dist/FaceCaptureStrings.d.ts +23 -0
  56. package/dist/FaceMatchComparisonScreen.d.ts +3 -3
  57. package/dist/FaceMatchLayout.css +21 -12
  58. package/dist/FormScreen.css +30 -0
  59. package/dist/FormScreen.d.ts +28 -0
  60. package/dist/FormScreen.js +3 -2
  61. package/dist/FormsLongFormScreen.d.ts +8 -0
  62. package/dist/FormsLongFormScreen.js +34 -13
  63. package/dist/FormsStrings.d.ts +11 -1
  64. package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
  65. package/dist/GeolocationBrowserStepsScreen.js +7 -3
  66. package/dist/GeolocationStrings.d.ts +3 -0
  67. package/dist/IdCaptureLayout.css +205 -2
  68. package/dist/IdCaptureStageScreen.d.ts +47 -0
  69. package/dist/IdCaptureStageScreen.js +39 -3
  70. package/dist/IdCaptureStrings.d.ts +19 -0
  71. package/dist/ListScreen.d.ts +21 -0
  72. package/dist/ListScreen.js +7 -2
  73. package/dist/LoaderScreen.d.ts +7 -5
  74. package/dist/LoaderScreen.js +7 -5
  75. package/dist/LoadersStrings.d.ts +18 -5
  76. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  77. package/dist/OcrReviewErrorScreen.js +24 -0
  78. package/dist/OcrReviewLayout.css +24 -0
  79. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  80. package/dist/OcrReviewLoadingScreen.js +19 -0
  81. package/dist/OcrReviewScreen.d.ts +76 -0
  82. package/dist/OcrReviewScreen.js +65 -0
  83. package/dist/OcrReviewStrings.d.ts +47 -0
  84. package/dist/OcrReviewStrings.js +17 -0
  85. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  86. package/dist/OcrReviewSuccessScreen.js +24 -0
  87. package/dist/PermissionsAllowScreen.d.ts +8 -11
  88. package/dist/PermissionsAllowScreen.js +8 -11
  89. package/dist/PermissionsAskScreen.d.ts +41 -0
  90. package/dist/PermissionsAskScreen.js +43 -0
  91. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  92. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  93. package/dist/PermissionsLayout.css +47 -0
  94. package/dist/PermissionsStrings.d.ts +20 -9
  95. package/dist/PermissionsStrings.js +10 -4
  96. package/dist/PhoneInputLayout.css +10 -9
  97. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  98. package/dist/PhoneInputOtpScreen.js +26 -19
  99. package/dist/PhoneInputStrings.d.ts +22 -4
  100. package/dist/PhoneInputVerifyScreen.d.ts +8 -0
  101. package/dist/PhoneInputVerifyScreen.js +36 -7
  102. package/dist/QualifiedSignatureAcceptScreen.js +6 -1
  103. package/dist/QualifiedSignatureDocumentScreen.d.ts +17 -10
  104. package/dist/QualifiedSignatureDocumentScreen.js +22 -17
  105. package/dist/QualifiedSignatureLayout.css +76 -0
  106. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  107. package/dist/QualifiedSignatureResultScreen.js +17 -18
  108. package/dist/QualifiedSignatureStrings.d.ts +19 -2
  109. package/dist/ResultScreen.d.ts +8 -0
  110. package/dist/ResultScreen.js +14 -3
  111. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  112. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  113. package/dist/ReusabilityLayout.css +241 -0
  114. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  115. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  116. package/dist/ReusabilityStrings.d.ts +85 -0
  117. package/dist/ReusabilityStrings.js +37 -0
  118. package/dist/ReviewScreen.d.ts +38 -12
  119. package/dist/ReviewScreen.js +41 -8
  120. package/dist/ScreenLayout.css +382 -6
  121. package/dist/ScreenNav.css +8 -0
  122. package/dist/ScreenNav.d.ts +15 -2
  123. package/dist/ScreenNav.js +10 -3
  124. package/dist/VideoSelfieErrorScreen.js +1 -1
  125. package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
  126. package/dist/VideoSelfieIdCaptureScreen.js +45 -14
  127. package/dist/VideoSelfieLayout.css +467 -34
  128. package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
  129. package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
  130. package/dist/VideoSelfieSpeakScreen.js +19 -2
  131. package/dist/VideoSelfieStrings.d.ts +82 -13
  132. package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
  133. package/dist/VideoSelfieTurnHeadScreen.js +66 -0
  134. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  135. package/dist/VideoSelfieTutorialScreen.js +22 -5
  136. package/dist/VideoSelfieWaveScreen.d.ts +53 -0
  137. package/dist/VideoSelfieWaveScreen.js +62 -0
  138. package/dist/ViewfinderScreen.css +36 -0
  139. package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
  140. package/dist/WatchlistBusinessFormScreen.js +27 -13
  141. package/dist/WatchlistBusinessStrings.d.ts +21 -7
  142. package/dist/flows.d.ts +8 -0
  143. package/dist/flows.js +17 -0
  144. package/dist/index.d.ts +19 -1
  145. package/dist/index.js +14 -0
  146. package/dist/styles.css +3 -0
  147. package/dist/types.d.ts +3 -1
  148. package/package.json +79 -3
  149. package/samples/video-selfie-camera-wide.jpg +0 -0
  150. package/samples/video-selfie-room-wide.jpg +0 -0
  151. package/samples/video-selfie-room.png +0 -0
  152. package/src/BiometricConsentScreen.tsx +192 -0
  153. package/src/BiometricConsentStrings.ts +119 -0
  154. package/src/CaptureScreen.tsx +17 -0
  155. package/src/CurpEnterScreen.tsx +21 -4
  156. package/src/CurpGenerateScreen.tsx +46 -22
  157. package/src/CurpLayout.css +8 -34
  158. package/src/CurpStrings.ts +22 -2
  159. package/src/DemoScreensLayout.css +16 -0
  160. package/src/DemoScreensRedirectScreen.tsx +14 -6
  161. package/src/DemoScreensStrings.ts +5 -0
  162. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  163. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  164. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  165. package/src/DigitalIdUploadStrings.ts +88 -0
  166. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  167. package/src/DocumentCaptureIllustration.tsx +1 -1
  168. package/src/EkybAddressDetailsScreen.tsx +32 -4
  169. package/src/EkybBusinessDetailsScreen.tsx +23 -5
  170. package/src/EkybCountryScreen.tsx +45 -7
  171. package/src/EkybOwnersScreen.tsx +34 -3
  172. package/src/EkybStrings.ts +34 -10
  173. package/src/EkycCountryCatalog.ts +9 -4
  174. package/src/EkycCountryFormScreen.tsx +51 -12
  175. package/src/EkycDetailsScreen.tsx +22 -1
  176. package/src/EkycStrings.ts +14 -2
  177. package/src/ElectronicSignatureLayout.css +6 -0
  178. package/src/EmailInputStrings.ts +12 -1
  179. package/src/EmailInputVerifyScreen.tsx +21 -4
  180. package/src/ErrorScreensScreen.tsx +15 -4
  181. package/src/ErrorScreensStrings.ts +37 -12
  182. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  183. package/src/FaceCaptureLayout.css +69 -4
  184. package/src/FaceCaptureStrings.ts +24 -0
  185. package/src/FaceMatchComparisonScreen.tsx +3 -3
  186. package/src/FaceMatchLayout.css +21 -12
  187. package/src/FormScreen.css +30 -0
  188. package/src/FormScreen.tsx +34 -0
  189. package/src/FormsLongFormScreen.tsx +41 -13
  190. package/src/FormsStrings.ts +12 -1
  191. package/src/GeolocationBrowserStepsScreen.tsx +6 -3
  192. package/src/GeolocationStrings.ts +3 -0
  193. package/src/IdCaptureLayout.css +205 -2
  194. package/src/IdCaptureStageScreen.tsx +75 -0
  195. package/src/IdCaptureStrings.ts +19 -0
  196. package/src/ListScreen.tsx +58 -10
  197. package/src/LoaderScreen.tsx +7 -5
  198. package/src/LoadersStrings.ts +18 -5
  199. package/src/OcrReviewErrorScreen.tsx +53 -0
  200. package/src/OcrReviewLayout.css +24 -0
  201. package/src/OcrReviewLoadingScreen.tsx +48 -0
  202. package/src/OcrReviewScreen.tsx +190 -0
  203. package/src/OcrReviewStrings.ts +69 -0
  204. package/src/OcrReviewSuccessScreen.tsx +52 -0
  205. package/src/PermissionsAllowScreen.tsx +9 -10
  206. package/src/PermissionsAskScreen.tsx +105 -0
  207. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  208. package/src/PermissionsLayout.css +47 -0
  209. package/src/PermissionsStrings.ts +42 -12
  210. package/src/PhoneInputLayout.css +10 -9
  211. package/src/PhoneInputOtpScreen.tsx +23 -3
  212. package/src/PhoneInputStrings.ts +23 -4
  213. package/src/PhoneInputVerifyScreen.tsx +41 -5
  214. package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
  215. package/src/QualifiedSignatureDocumentScreen.tsx +31 -19
  216. package/src/QualifiedSignatureLayout.css +76 -0
  217. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  218. package/src/QualifiedSignatureStrings.ts +19 -2
  219. package/src/ResultScreen.tsx +33 -2
  220. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  221. package/src/ReusabilityLayout.css +241 -0
  222. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  223. package/src/ReusabilityStrings.ts +127 -0
  224. package/src/ReviewScreen.tsx +96 -27
  225. package/src/ScreenLayout.css +382 -6
  226. package/src/ScreenNav.css +8 -0
  227. package/src/ScreenNav.tsx +35 -5
  228. package/src/VideoSelfieErrorScreen.tsx +1 -1
  229. package/src/VideoSelfieIdCaptureScreen.tsx +125 -31
  230. package/src/VideoSelfieLayout.css +467 -34
  231. package/src/VideoSelfieSelfieCaptureScreen.tsx +110 -57
  232. package/src/VideoSelfieSpeakScreen.tsx +34 -15
  233. package/src/VideoSelfieStrings.ts +89 -14
  234. package/src/VideoSelfieTurnHeadScreen.tsx +162 -0
  235. package/src/VideoSelfieTutorialScreen.tsx +45 -23
  236. package/src/VideoSelfieWaveScreen.tsx +143 -0
  237. package/src/ViewfinderScreen.css +36 -0
  238. package/src/WatchlistBusinessFormScreen.tsx +35 -16
  239. package/src/WatchlistBusinessStrings.ts +21 -7
  240. package/src/flows.ts +25 -0
  241. package/src/index.ts +88 -3
  242. package/src/styles.css +3 -0
  243. package/src/types.ts +3 -1
@@ -21,7 +21,7 @@ export interface ErrorScreensScreenProps extends Omit<PrizmaResultScreenProps, '
21
21
  * Error screens — the session-level failures, built on the Result archetype
22
22
  * (GUIDELINES.md §6-F, `PrizmaResultScreen`).
23
23
  *
24
- * ONE screen for all three generic sections. They draw the identical skeleton —
24
+ * ONE screen for all four generic sections. They draw the identical skeleton —
25
25
  * a centred mark + title + supporting line over a CTA band — and differ only in
26
26
  * copy, in which mark leads, and in what the band carries:
27
27
  *
@@ -29,8 +29,11 @@ export interface ErrorScreensScreenProps extends Omit<PrizmaResultScreenProps, '
29
29
  * - `noConnection` — Wi-Fi glyph, "Refresh"
30
30
  * - `unsupportedBrowser` — canonical negative mark, the session URL above
31
31
  * "Copy link", with "Continue anyways" under it
32
+ * - `somethingWentWrong` — canonical negative mark, "Try again" when the copy
33
+ * carries a CTA, "Quit process" under it when it
34
+ * carries a quit label; a dead end when it has neither
32
35
  *
33
- * Chrome is CONSTANT. All three frames are drawn without the logo header and
36
+ * Chrome is CONSTANT. All the frames are drawn without the logo header and
34
37
  * without the verified tag; both are kept here, because the owner rule of
35
38
  * 2026-08-18 (#F-30, #F-34) makes the chrome the same on every screen so
36
39
  * nothing shifts between states, and every module's connectivity screen must
@@ -9,7 +9,7 @@ import './ScreenLayout.css';
9
9
  * Error screens — the session-level failures, built on the Result archetype
10
10
  * (GUIDELINES.md §6-F, `PrizmaResultScreen`).
11
11
  *
12
- * ONE screen for all three generic sections. They draw the identical skeleton —
12
+ * ONE screen for all four generic sections. They draw the identical skeleton —
13
13
  * a centred mark + title + supporting line over a CTA band — and differ only in
14
14
  * copy, in which mark leads, and in what the band carries:
15
15
  *
@@ -17,8 +17,11 @@ import './ScreenLayout.css';
17
17
  * - `noConnection` — Wi-Fi glyph, "Refresh"
18
18
  * - `unsupportedBrowser` — canonical negative mark, the session URL above
19
19
  * "Copy link", with "Continue anyways" under it
20
+ * - `somethingWentWrong` — canonical negative mark, "Try again" when the copy
21
+ * carries a CTA, "Quit process" under it when it
22
+ * carries a quit label; a dead end when it has neither
20
23
  *
21
- * Chrome is CONSTANT. All three frames are drawn without the logo header and
24
+ * Chrome is CONSTANT. All the frames are drawn without the logo header and
22
25
  * without the verified tag; both are kept here, because the owner rule of
23
26
  * 2026-08-18 (#F-30, #F-34) makes the chrome the same on every screen so
24
27
  * nothing shifts between states, and every module's connectivity screen must
@@ -49,11 +52,19 @@ import './ScreenLayout.css';
49
52
  export const ErrorScreensScreen = forwardRef(({ strings, variant = 'sessionExpired', sessionUrl, copied = false, screenId = `error-screens-${variant}`, overrides, onAction, ...shell }, ref) => {
50
53
  const unsupported = variant === 'unsupportedBrowser';
51
54
  const noConnection = variant === 'noConnection';
55
+ const generic = variant === 'somethingWentWrong';
52
56
  const copy = unsupported
53
57
  ? strings.unsupportedBrowser
54
58
  : noConnection
55
59
  ? strings.noConnection
56
- : strings.sessionExpired;
60
+ : generic
61
+ ? strings.somethingWentWrong
62
+ : strings.sessionExpired;
63
+ const linkLabel = unsupported
64
+ ? strings.unsupportedBrowser.continueLabel
65
+ : generic
66
+ ? strings.somethingWentWrong.quitLabel
67
+ : undefined;
57
68
  return (_jsx(PrizmaResultScreen, { ref: ref,
58
69
  /* The connectivity screen leads with the Wi-Fi glyph instead of a
59
70
  status mark; the other two carry the canonical negative mark. */
@@ -65,7 +76,7 @@ export const ErrorScreensScreen = forwardRef(({ strings, variant = 'sessionExpir
65
76
  .filter(Boolean)
66
77
  .join(' '), children: _jsx(IconWifiOff, { size: "l" }) })) : undefined, progress: unsupported && copied ? (_jsx(PrizmaSnackbar, { state: "success", contentResizing: "fill", children: strings.unsupportedBrowser.copiedLabel })) : undefined, title: copy.title, subtitle: copy.subtitle, disclaimer: unsupported
67
78
  ? sessionUrl ?? strings.unsupportedBrowser.urlPlaceholder
68
- : undefined, ctaLabel: copy.ctaLabel, linkLabel: unsupported ? strings.unsupportedBrowser.continueLabel : undefined, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell }));
79
+ : undefined, ctaLabel: copy.ctaLabel, linkLabel: linkLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell }));
69
80
  });
70
81
  ErrorScreensScreen.displayName = 'ErrorScreensScreen';
71
82
  export default ErrorScreensScreen;
@@ -1,10 +1,11 @@
1
1
  /**
2
- * The three sections.
2
+ * The four sections.
3
3
  * - `sessionExpired` — the link is dead; the user must go back to the provider
4
4
  * - `noConnection` — the network went away; retry when it returns
5
5
  * - `unsupportedBrowser` — the SDK cannot run here; copy the link and move
6
+ * - `somethingWentWrong` — the generic failure; try again, or nothing, or quit
6
7
  */
7
- export type ErrorScreensVariant = 'sessionExpired' | 'noConnection' | 'unsupportedBrowser';
8
+ export type ErrorScreensVariant = 'sessionExpired' | 'noConnection' | 'unsupportedBrowser' | 'somethingWentWrong';
8
9
  export interface ErrorScreensCopy {
9
10
  title: string;
10
11
  subtitle?: string;
@@ -28,4 +29,18 @@ export interface ErrorScreensStrings {
28
29
  /** Snackbar copy after the link is copied — the frame's second state. */
29
30
  copiedLabel: string;
30
31
  };
32
+ /**
33
+ * The generic failure. Three ways the modules fill it, all the same screen:
34
+ * `ctaLabel` "Try again" (eKYC, eKYB, Forms, Email Input);
35
+ * no `ctaLabel`, a dead end (Certificate Issuance, Qualified Signature);
36
+ * `ctaLabel` plus `quitLabel`, the way out under the retry (Video Selfie).
37
+ * Phone Input's `unknown` fills it with "Continue" instead, because a retry
38
+ * may not be possible there (owner, 2026-10-09) — the CTA is the module's
39
+ * call; the shape and the title are what the family fixes.
40
+ * The title never carries an ellipsis — a failure is not in progress.
41
+ */
42
+ somethingWentWrong: ErrorScreensCopy & {
43
+ /** Tertiary link under the CTA — "Quit process". Omit for no way out. */
44
+ quitLabel?: string;
45
+ };
31
46
  }
@@ -6,19 +6,27 @@
6
6
  // 214:3357 Geolocation.error.noNetwork → `noConnection`
7
7
  // 240:154 Error screen - Unsupported browser → `unsupportedBrowser`
8
8
  //
9
- // The three failures that belong to the SESSION rather than to any module: the
10
- // link has expired, the network went away, the browser cannot run the SDK.
11
- // Every module can hit all three, which is why they are drawn once in the
12
- // generic file — a user who sees three different "no internet" screens in one
13
- // journey reads them as three different products.
9
+ // plus the one failure every module draws for itself and the file never did:
10
+ //
11
+ // — <module>.error "Something went wrong" → `somethingWentWrong`
12
+ //
13
+ // The failures that belong to the SESSION rather than to any module: the link
14
+ // has expired, the network went away, the browser cannot run the SDK, the
15
+ // server said no for a reason the user cannot act on. Every module can hit all
16
+ // four, which is why they are recorded once in the generic family — a user who
17
+ // sees three different "no internet" screens in one journey reads them as three
18
+ // different products.
14
19
  //
15
20
  // `noConnection` is deliberately the same screen every module already ships
16
21
  // (`FaceCaptureNoConnectionScreen`, `IdCaptureNoConnectionScreen`,
17
- // `DocumentCaptureNoConnectionScreen`, `GeolocationErrorScreen`'s `noNetwork`).
18
- // The generic entry is where the shape is DOCUMENTED; the module copies stay
19
- // put until someone retires them deliberately, because deleting four exported
20
- // components to save one screen is a breaking change for every host that
21
- // imports them. Flagged for the owner.
22
+ // `DocumentCaptureNoConnectionScreen`, `GeolocationErrorScreen`'s `noNetwork`,
23
+ // Phone Input's and Video Selfie's `noConnection`). `somethingWentWrong` is the
24
+ // same screen eKYC, eKYB, Forms, Email Input, Phone Input, Certificate
25
+ // Issuance, both signatures and Video Selfie already ship. The generic entry is
26
+ // where the shape and the copy are DOCUMENTED; the module copies stay put until
27
+ // someone retires them deliberately, because deleting exported components to
28
+ // save one screen is a breaking change for every host that imports them.
29
+ // Flagged for the owner (DEVIATIONS #GEN-7, #GEN-18).
22
30
  //
23
31
  // The family has no flow of its own: each of these is entered from wherever the
24
32
  // session broke and exits to a retry or to nothing. Core Lab lists it as a
@@ -9,6 +9,19 @@ export interface FaceCaptureCapturingScreenProps extends Omit<PrizmaCaptureScree
9
9
  state?: FaceCaptureCapturingState;
10
10
  /** Live camera feed (<video>) or sample still (<img>) for the ring window. */
11
11
  camera?: ReactNode;
12
+ /**
13
+ * Raise the "Need more time?" offer over the feed — the accessibility
14
+ * notification shown 20 s before auto capture falls back to manual.
15
+ *
16
+ * It is a MODIFIER of whatever capturing state is already on screen, not a
17
+ * sixteenth state: the timer runs underneath the guidance, so the user can be
18
+ * told to move closer and offered more time in the same frame. Needs
19
+ * `strings.extendTime`; without it the flag is a no-op, which is what a host
20
+ * that has not localized the copy should get rather than English.
21
+ */
22
+ extendTime?: boolean;
23
+ /** Tapping "Extend auto-capture". The host resets its own fallback timer. */
24
+ onExtendTime?: () => void;
12
25
  }
13
26
  /**
14
27
  * Face Capture — auto-capture stage (Archetype B, GUIDELINES.md §6-B).
@@ -27,6 +40,56 @@ export interface FaceCaptureCapturingScreenProps extends Omit<PrizmaCaptureScree
27
40
  *
28
41
  * The archetype adds no offsets of its own — the component owns that spacing
29
42
  * (same deviation row).
43
+ *
44
+ * ── The "Need more time?" offer (`extendTime`) ──────────────────────────────
45
+ *
46
+ * Auto capture gives up after a configured timeout and drops the user into
47
+ * manual capture. Someone who is struggling — a tremor, low vision, bad light,
48
+ * an unfamiliar phone — gets moved off the EASIER path precisely because they
49
+ * were slow, which is backwards. So 20 s before the switch, the screen offers
50
+ * to stay on auto. A client turns it on per flow from the dashboard; with it
51
+ * off, nothing about today's capture changes.
52
+ *
53
+ * NOT a nicety — it is the conformance route for WCAG 2.2.1 Timing Adjustable
54
+ * via the *Extend* clause (warn before the limit, ≥20 s to act, ≥10 extensions).
55
+ * Two things follow that the visuals cannot settle: the notice must be able to
56
+ * come back ten times, and it must be ANNOUNCED early enough to be acted on.
57
+ *
58
+ * ── Live-region politeness: `role="alert"`, not the default ────────────────
59
+ *
60
+ * SETTLED 2026-10-05 (Camila). The Snackbar computes `role="status"` (polite)
61
+ * for every state but error, and this instance overrides it to `alert`.
62
+ *
63
+ * A polite region is allowed to wait until the screen reader finishes whatever
64
+ * it is already saying — and on this screen it always IS saying something: the
65
+ * capture instruction under the ring changes as the face moves, so the queue
66
+ * is rarely empty. The offer has a 20 s life. Queued behind two or three
67
+ * guidance announcements it can be read out after it has already expired,
68
+ * which turns the one affordance a struggling user needs into an
69
+ * announcement that the limit has passed.
70
+ *
71
+ * That is not a polish issue, it is the conformance failing: WCAG 2.2.1's
72
+ * Extend clause requires the warning to be given in time to ACT on it. An
73
+ * announcement the user cannot act on does not satisfy it, however correct the
74
+ * visuals. `alert` is assertive, so it interrupts the guidance and is heard
75
+ * while the 20 s is still running.
76
+ *
77
+ * The usual objection to `alert` is that interrupting is rude. It applies to
78
+ * notices that can wait; this one cannot, and it is strictly better to
79
+ * interrupt "Move closer" than to let the window close in silence. It is also
80
+ * why the offer must not be re-raised on a timer — each new `alert` interrupts
81
+ * again. It appears once per countdown, and WCAG allows that up to ten times.
82
+ *
83
+ * ⚠ NEW, not yet authored in Core. Two parts of this notice have no Figma
84
+ * variant behind them and are drawn from the exploration, not from the SoT:
85
+ * • the bold title line — the Snackbar has no `title` slot, so it rides
86
+ * inside `children` as a <strong>. Core to decide whether to adopt a real
87
+ * slot, at which point this moves onto it.
88
+ * • the state — it is `neutral` (blue surface, ⓘ glyph), Camila's call
89
+ * 2026-10-05. `warning` would signal attention more sharply but turns the
90
+ * surface amber, and the exploration's open question is whether amber
91
+ * reads as "hurry up" to someone who was already struggling. The icon
92
+ * cannot be chosen independently: `ICON_BY_STATE` binds it to `state`.
30
93
  */
31
94
  export declare const FaceCaptureCapturingScreen: React.ForwardRefExoticComponent<FaceCaptureCapturingScreenProps & React.RefAttributes<HTMLDivElement>>;
32
95
  export default FaceCaptureCapturingScreen;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef } from 'react';
4
+ import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
4
5
  import { PrizmaCaptureScreen } from './CaptureScreen';
5
6
  import './FaceCaptureLayout.css';
6
7
  /**
@@ -47,7 +48,66 @@ const SELFIE_STATE = {
47
48
  *
48
49
  * The archetype adds no offsets of its own — the component owns that spacing
49
50
  * (same deviation row).
51
+ *
52
+ * ── The "Need more time?" offer (`extendTime`) ──────────────────────────────
53
+ *
54
+ * Auto capture gives up after a configured timeout and drops the user into
55
+ * manual capture. Someone who is struggling — a tremor, low vision, bad light,
56
+ * an unfamiliar phone — gets moved off the EASIER path precisely because they
57
+ * were slow, which is backwards. So 20 s before the switch, the screen offers
58
+ * to stay on auto. A client turns it on per flow from the dashboard; with it
59
+ * off, nothing about today's capture changes.
60
+ *
61
+ * NOT a nicety — it is the conformance route for WCAG 2.2.1 Timing Adjustable
62
+ * via the *Extend* clause (warn before the limit, ≥20 s to act, ≥10 extensions).
63
+ * Two things follow that the visuals cannot settle: the notice must be able to
64
+ * come back ten times, and it must be ANNOUNCED early enough to be acted on.
65
+ *
66
+ * ── Live-region politeness: `role="alert"`, not the default ────────────────
67
+ *
68
+ * SETTLED 2026-10-05 (Camila). The Snackbar computes `role="status"` (polite)
69
+ * for every state but error, and this instance overrides it to `alert`.
70
+ *
71
+ * A polite region is allowed to wait until the screen reader finishes whatever
72
+ * it is already saying — and on this screen it always IS saying something: the
73
+ * capture instruction under the ring changes as the face moves, so the queue
74
+ * is rarely empty. The offer has a 20 s life. Queued behind two or three
75
+ * guidance announcements it can be read out after it has already expired,
76
+ * which turns the one affordance a struggling user needs into an
77
+ * announcement that the limit has passed.
78
+ *
79
+ * That is not a polish issue, it is the conformance failing: WCAG 2.2.1's
80
+ * Extend clause requires the warning to be given in time to ACT on it. An
81
+ * announcement the user cannot act on does not satisfy it, however correct the
82
+ * visuals. `alert` is assertive, so it interrupts the guidance and is heard
83
+ * while the 20 s is still running.
84
+ *
85
+ * The usual objection to `alert` is that interrupting is rude. It applies to
86
+ * notices that can wait; this one cannot, and it is strictly better to
87
+ * interrupt "Move closer" than to let the window close in silence. It is also
88
+ * why the offer must not be re-raised on a timer — each new `alert` interrupts
89
+ * again. It appears once per countdown, and WCAG allows that up to ten times.
90
+ *
91
+ * ⚠ NEW, not yet authored in Core. Two parts of this notice have no Figma
92
+ * variant behind them and are drawn from the exploration, not from the SoT:
93
+ * • the bold title line — the Snackbar has no `title` slot, so it rides
94
+ * inside `children` as a <strong>. Core to decide whether to adopt a real
95
+ * slot, at which point this moves onto it.
96
+ * • the state — it is `neutral` (blue surface, ⓘ glyph), Camila's call
97
+ * 2026-10-05. `warning` would signal attention more sharply but turns the
98
+ * surface amber, and the exploration's open question is whether amber
99
+ * reads as "hurry up" to someone who was already struggling. The icon
100
+ * cannot be chosen independently: `ICON_BY_STATE` binds it to `state`.
50
101
  */
51
- export const FaceCaptureCapturingScreen = forwardRef(({ strings, state = 'initiating', camera, screenId = 'face-capture-capturing', className, ...rest }, ref) => (_jsx(PrizmaCaptureScreen, { ref: ref, className: className, state: SELFIE_STATE[state], camera: camera, feedbackText: strings.capturing[state], verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, ...rest })));
102
+ export const FaceCaptureCapturingScreen = forwardRef(({ strings, state = 'initiating', camera, extendTime, onExtendTime, screenId = 'face-capture-capturing', className, ...rest }, ref) => {
103
+ const offer = strings.extendTime;
104
+ // No copy, no notice: a host that has not localized this must not get
105
+ // English, and the flag alone is not permission to invent it.
106
+ const notice = extendTime && offer ? (_jsxs(PrizmaSnackbar, { state: "neutral", isMultiline: true,
107
+ // Assertive on purpose — see the live-region note in this file's
108
+ // header. The component default here would be `status`.
109
+ role: "alert", linkLabel: offer.linkLabel, onLinkClick: onExtendTime, children: [_jsx("strong", { children: offer.title }), _jsx("br", {}), offer.description] })) : undefined;
110
+ return (_jsx(PrizmaCaptureScreen, { ref: ref, className: className, state: SELFIE_STATE[state], camera: camera, feedbackText: strings.capturing[state], notice: notice, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, ...rest }));
111
+ });
52
112
  FaceCaptureCapturingScreen.displayName = 'FaceCaptureCapturingScreen';
53
113
  export default FaceCaptureCapturingScreen;
@@ -12,7 +12,72 @@
12
12
  * inherit all three as correct defaults.
13
13
  */
14
14
 
15
- /* Nothing module-specific is left: the non-status glyph slot this file used to
16
- * own (`.face-capture-glyph`) was the third byte-identical copy of one pattern,
17
- * so it moved to `ScreenLayout.css` as `__glyph` — DEVIATIONS #DC-29, closed.
18
- * The file is kept as the module's landing place for future additions. */
15
+ /* The non-status glyph slot this file used to own (`.face-capture-glyph`) was
16
+ * the third byte-identical copy of one pattern, so it moved to
17
+ * `ScreenLayout.css` as `__glyph` — DEVIATIONS #DC-29, closed. */
18
+
19
+ /* ── Short panels: the capture stack moves up, the ring never changes size ───
20
+ * `PrizmaSelfieCapture` is a fixed stack by design: Spacing/40, the 335 stage,
21
+ * Spacing/32, the instruction, and in manual mode another Spacing/32 and the 64
22
+ * shutter, then Spacing/40 — 583 tall on the manual screen, every number
23
+ * measured off the module Figma (DEVIATIONS #12). Under the nav and above the
24
+ * footer's reserve a closed iPhone Duo leaves 513 (678 − 103 − 62, measured
25
+ * 2026-10-14), so the manual stack ran 70 past its column and the shutter
26
+ * landed on the verified tag (shutter 582–646, tag 606–636). The auto-capture
27
+ * screens are 94 shorter and fit the Duo as authored.
28
+ *
29
+ * The RING is not what gives ground. Camila, 2026-10-14: the face frame is the
30
+ * same size on every screen, because the user has to see their face; if it
31
+ * ever had to shrink it would shrink on every capture screen together, and we
32
+ * avoid that. So the stack moves UP instead: the root's Spacing/40 above and
33
+ * below go (the ring sits flush under the nav row, 40 higher than on the
34
+ * reference) and the two Spacing/32 gaps become Spacing/16, which on the
35
+ * manual screen is 112 back — 103 + 334 + 16 + 41 + 16 + 64 = 574, 32 of air
36
+ * above the tag. Instruction and shutter keep their height, the ring its 334.
37
+ *
38
+ * EVERY capture screen takes it, not only the manual one that needed it
39
+ * (Camila, 2026-10-14: "put all face frames at the height that the manual
40
+ * capture one is, so we don't need a jump there"). The ring not moving between
41
+ * screens is the rule Video Selfie's #VS-24 was written for: a capture window
42
+ * that jumps as the step advances reads as a glitch. So on a short panel the
43
+ * whole module's ring is at 103, auto, manual, loading and verdict alike.
44
+ * Outside the query not one declaration exists, so the reference is
45
+ * byte-identical. DEVIATIONS #20. */
46
+ @container pcs-screen (max-height: 720px) {
47
+ .prizma-screen-layout__content--capture > .prizma-selfie-capture {
48
+ padding-block: 0;
49
+ gap: var(--prizma-spacing-16);
50
+ }
51
+ }
52
+
53
+ /* ── Big panels: the ring grows to the column and the stack moves down ───────
54
+ * The mirror of the block above, and the capture counterpart of the tutorial's
55
+ * #F-36. On the 768 × 1024 iPad the column widens to 468 but the stack kept
56
+ * its 335 ring flush under the nav, so the ring read small (335 in a 420
57
+ * content column, 85px of air either side) and high (its centre at 30% of the
58
+ * panel, where the 390 phone has it at 37%), with 300px of empty column under
59
+ * the instruction (Camila, 2026-10-19: "the face frame too small and way up
60
+ * the screen", on the tablet flow, grid and screen view alike).
61
+ *
62
+ * Two owner decisions, 2026-10-20. The RING GROWS: the 2026-10-14 rule that
63
+ * the ring never changes size was written so it would not SHRINK on a short
64
+ * panel, and on a panel this big the person's face is still what the screen is
65
+ * about, so the stage takes the content column's full measure (420 inside the
66
+ * 468 wide column), the way #F-36 lets the tutorial art grow. Every state
67
+ * grows together (auto, manual, loading, verdict) so the ring still never
68
+ * jumps between steps. The STACK MOVES DOWN BY A FIXED OFFSET, not to the
69
+ * centre: the stack under the ring is a different height on every screen
70
+ * (the manual one carries the shutter), so centring THAT would move the ring
71
+ * between steps, the bug #VS-24 exists for; the same reason Video Selfie's
72
+ * #VS-32 took an offset. Spacing/40 + Spacing/32 above the ring puts its
73
+ * centre at 102 + 72 + 210 = 384, 37.5% of 1024, the reference's own
74
+ * proportion (measured 2026-10-20); the manual screen's shutter ends at 765
75
+ * with the verified tag at 978. Both axes gate it for #F-36's reasons: `min-height: 845px` keeps the
76
+ * 844 reference and every short panel byte-identical, `min-width: 468px` keeps
77
+ * it off phones and foldables where the column stays 390. DEVIATIONS #21. */
78
+ @container pcs-screen ((min-height: 845px) and (min-width: 468px)) {
79
+ .prizma-screen-layout__content--capture > .prizma-selfie-capture {
80
+ max-width: 100%;
81
+ padding-top: calc(var(--prizma-spacing-40) + var(--prizma-spacing-32));
82
+ }
83
+ }
@@ -23,6 +23,29 @@ export interface FaceCaptureStrings {
23
23
  /** faceCapture.capturing.* — the H2 instruction under the ring, per state.
24
24
  * `initiating` has no instruction in the design; omit it. */
25
25
  capturing: Partial<Record<FaceCaptureCapturingState, string>>;
26
+ /**
27
+ * faceCapture.capturing.extendTime — the accessibility offer that rides any
28
+ * capturing state 20 s before auto capture falls back to manual.
29
+ *
30
+ * It exists for WCAG 2.2.1 Timing Adjustable, satisfied through the *Extend*
31
+ * clause: warn before the limit expires, give the user at least 20 s to
32
+ * extend with a simple action, and let them extend at least ten times. The
33
+ * 20 s is that threshold, not a chosen duration — which is why `description`
34
+ * is a whole string and not a number the screen formats: ten locales write
35
+ * that sentence ten different ways, and the count has to live inside it.
36
+ *
37
+ * Shown only when accessibility mode is ON for the flow (a per-flow
38
+ * dashboard setting). With it off, nothing about today's capture changes.
39
+ * Optional for that reason: a host that never turns it on never writes it.
40
+ */
41
+ extendTime?: {
42
+ /** Bold first line — "Need more time?" */
43
+ title: string;
44
+ /** "Manual capture starts in 20 s" — carries the count, see above. */
45
+ description: string;
46
+ /** The action that keeps the user on auto — "Extend auto-capture". */
47
+ linkLabel: string;
48
+ };
26
49
  /** faceCapture.manualCapture.default */
27
50
  manualCapture: {
28
51
  /** "Align your face within the silhouette and press the button to capture" */
@@ -8,9 +8,9 @@ import './FaceMatchLayout.css';
8
8
  * contract as `camera` on the capture screens) — Face Match never captures. */
9
9
  export type FaceMatchPhotoRole = 'selfie' | 'id' | 'nfc' | 'secondId';
10
10
  /** Host-supplied portraits, one per role the chosen variant asks for. A role
11
- * left out renders the empty state: a Brand/50 disc, marked with
12
- * `.face-match-cluster__photo--empty`. Interim treatment (2026-09-25) while
13
- * the designed no-photo state is drawn; see FaceMatchLayout.css. */
11
+ * left out renders the empty state (capture missing or not retrievable): a
12
+ * grey Surface/Neutral/100 disc under its usual label tooltip, marked with
13
+ * `.face-match-cluster__photo--empty`. See FaceMatchLayout.css. */
14
14
  export type FaceMatchComparisonPhotos = Partial<Record<FaceMatchPhotoRole, ReactNode>>;
15
15
  /**
16
16
  * Where the comparison is in its own choreography — the module MOVING rather
@@ -57,8 +57,14 @@
57
57
 
58
58
  /* The disc's diameter, named once. `--matching`'s rosette and `--animated`'s
59
59
  * convergence both have to know it, and a second literal is a second thing to
60
- * forget. Still a design literal, still DEVIATIONS #FM-22 — just not repeated. */
61
- --face-match-photo: 160px;
60
+ * forget. Still a design literal, still DEVIATIONS #FM-22 — just not repeated.
61
+ *
62
+ * 160 at most, and smaller when two discs and their gap do not fit the
63
+ * content column: on a 320 phone the column is 272, so each disc takes
64
+ * (272 − 16) / 2 = 128 (Camila, 2026-09-28). `100cqi` is the screen shell's
65
+ * width (ScreenShell.css, `container-type: size`), less its two Spacing/24
66
+ * gutters. At 390 and up this resolves to 160, unchanged. */
67
+ --face-match-photo: min(160px, calc((100cqi - 2 * var(--prizma-spacing-24) - var(--prizma-spacing-16)) / 2));
62
68
  /* Half the distance between two neighbouring discs: what one of them has to
63
69
  * travel to reach the pair's centre line. */
64
70
  --face-match-half-step: calc((var(--face-match-photo, 160px) + var(--prizma-spacing-16)) / 2);
@@ -118,16 +124,16 @@
118
124
  background: var(--prizma-surface-neutral-100);
119
125
  }
120
126
 
121
- /* No photo supplied for this role.
127
+ /* No photo for this role: the capture was never taken, or could not be
128
+ * retrieved.
122
129
  *
123
- * INTERIM (2026-09-25, owner call): colour is the whole state for now, so the
124
- * slot reads as a portrait that belongs here rather than an undesigned hole.
125
- * Brand/50 is the same disc PrizmaIllustrationAvatar sits on, which is where
126
- * the designed state is most likely to land. The real treatment (bust,
127
- * outline, or copy) replaces this rule; the modifier and its class name stay,
128
- * so swapping it in is a one-rule change and nothing downstream moves. */
130
+ * Owner call 2026-09-28 (replaces the 2026-09-25 Brand/50 interim): a plain
131
+ * grey disc, Surface/Neutral/100, the same neutral Core puts behind an image
132
+ * that is not there yet (CaptureDigitalId.css, list.css). The callout above it
133
+ * still renders, so the tooltip names which capture is missing. The modifier
134
+ * stays a named state so a richer treatment is still a one-rule change. */
129
135
  .face-match-cluster__photo--empty {
130
- background: var(--prizma-surface-brand-50);
136
+ background: var(--prizma-surface-neutral-100);
131
137
  }
132
138
 
133
139
  .face-match-cluster__photo > img,
@@ -153,11 +159,14 @@
153
159
  }
154
160
 
155
161
  .face-match-cluster--matching .face-match-cluster__face + .face-match-cluster__face {
156
- margin-left: -44px; /* hardcoded — Figma converged overlap; no token (verified absent) */
162
+ /* Figma converged overlap, 44 on the 160 disc; no token (verified absent).
163
+ * Kept as that ratio so the rosette holds its shape on a smaller disc. */
164
+ margin-left: calc(var(--face-match-photo, 160px) * -44 / 160);
157
165
  }
158
166
 
159
167
  .face-match-cluster--matching .face-match-cluster__row + .face-match-cluster__row {
160
- margin-top: calc(-2 * var(--prizma-spacing-40));
168
+ /* Half a disc: 80 on the 160 disc (`2 × Spacing/40`), proportional below it. */
169
+ margin-top: calc(var(--face-match-photo, 160px) / -2);
161
170
  }
162
171
 
163
172
  /* ── The comparison, moving (`phase` on FaceMatchComparisonScreen) ───────────
@@ -29,6 +29,20 @@
29
29
  padding-top: var(--pfs-fields-offset, var(--prizma-spacing-32));
30
30
  }
31
31
 
32
+ /* ── Field stack pinned to the CTA band ──────────────────────────────────────
33
+ * `fieldsAlign="end"` on `PrizmaFormScreen`. The stack keeps its `flex: 1`
34
+ * column and simply packs its rows at the bottom, so the space between the
35
+ * title block and the first row is what is left over rather than a value
36
+ * anybody declares. The `margin-bottom` is the frame's own Spacing/16 between
37
+ * the card and the Button group (`biometricConsent.empty`, 2001:16361 — the
38
+ * CONTENT column's gap), the same value the scrolling stack uses for the same
39
+ * seam, so the two shapes agree on how far a row sits from Continue. Biometric
40
+ * Consent is the first consumer. DEVIATIONS #BC-1. */
41
+ .prizma-form-screen__fields--end {
42
+ justify-content: flex-end;
43
+ margin-bottom: var(--prizma-spacing-16);
44
+ }
45
+
32
46
  /* ── Scrolling field stack ───────────────────────────────────────────────────
33
47
  * Screens are authored at 390×844 and never reflow (DEVIATIONS #17), but some
34
48
  * Form sections draw a middle column taller than the frame and let it pan:
@@ -129,6 +143,22 @@
129
143
  flex: none;
130
144
  width: calc(50% - var(--prizma-spacing-16) / 2);
131
145
  min-width: 0;
146
+ position: relative;
147
+ }
148
+ /* An error or helper line under ONE field of a pair made that field taller,
149
+ * and the bottom alignment above lifted its input above its neighbour's
150
+ * (Elena, 2026-10-02: "estos inputs tienen que estar alineados"). The line
151
+ * hangs below its input instead, out of the alignment, and the row reserves
152
+ * its height so the next row does not ride over it. */
153
+ .prizma-form-screen--responsive .prizma-form-screen__row > * > [class$="__error"],
154
+ .prizma-form-screen--responsive .prizma-form-screen__row > * > [class$="__helper"] {
155
+ position: absolute;
156
+ top: 100%;
157
+ left: 0;
158
+ margin-top: var(--prizma-spacing-8);
159
+ }
160
+ .prizma-form-screen--responsive .prizma-form-screen__row:has(> * > [class$="__error"], > * > [class$="__helper"]) {
161
+ padding-bottom: calc(var(--prizma-spacing-8) + var(--prizma-body-m-regular-line-height));
132
162
  }
133
163
 
134
164
  /* Continue and Back share a row: Back on the left, Continue on the right, as
@@ -50,6 +50,13 @@ export interface PrizmaFormScreenProps extends Omit<PrizmaScreenShellProps, 'dar
50
50
  * body text.
51
51
  */
52
52
  linkLabel?: string;
53
+ /**
54
+ * Renders `linkLabel` in Core's disabled treatment, for a link that exists
55
+ * but cannot be pressed yet — Phone Input's "Resend code in 30s" while the
56
+ * resend timer runs (Figma `phoneNumber.OTP.typing`, Tertiary / Link ·
57
+ * Disabled).
58
+ */
59
+ linkDisabled?: boolean;
53
60
  /**
54
61
  * A composed line below the primary: body copy with inline links, e.g. Phone
55
62
  * Input's "Didn't receive the code? **Resend code** or **Change phone
@@ -86,6 +93,27 @@ export interface PrizmaFormScreenProps extends Omit<PrizmaScreenShellProps, 'dar
86
93
  * eKYB, Data Sharing Consent); it is the archetype's now. DEVIATIONS #DSC-2.
87
94
  */
88
95
  scrollFields?: boolean;
96
+ /**
97
+ * Where the field stack sits in the column when it is SHORTER than the
98
+ * space it has. `start` (the default) hangs it off the title block, which is
99
+ * every form so far. `end` pins it to the CTA band instead, leaving the space
100
+ * between the title and the stack empty — Biometric Consent's section draws
101
+ * exactly that: a `justify-between` middle with the Title at the top and the
102
+ * one consent card flush against Continue, Spacing/16 above it
103
+ * (`biometricConsent.empty`, 2001:16361). Selected here so the module
104
+ * declares no layout of its own; the rule lives once in `FormScreen.css`.
105
+ * DEVIATIONS #BC-1.
106
+ */
107
+ fieldsAlign?: 'start' | 'end';
108
+ /**
109
+ * A shell-level layer painted OVER the whole screen — the same slot archetype
110
+ * A carries for Document Capture's method sheet (#F-32, #DC-30): a scrim plus
111
+ * Core's `PrizmaBottomSheetContent` or `PrizmaModalContent`, presented on top
112
+ * of the dimmed form. It cannot ride in the content column: it is absolutely
113
+ * positioned against the shell. Biometric Consent's quit confirmation is the
114
+ * first Form consumer (DEVIATIONS #BC-8).
115
+ */
116
+ overlay?: ReactNode;
89
117
  /**
90
118
  * Let the field stack change SHAPE on a wide screen: `PrizmaFormRow` pairs
91
119
  * become two lanes and Continue + Back share a row, once the shell's own
@@ -23,11 +23,12 @@ export function PrizmaFormRow({ children, className }) {
23
23
  * (typing / filled / loading) are separate screens sharing this identical
24
24
  * layout, changing only field content.
25
25
  */
26
- export const PrizmaFormScreen = forwardRef(({ progress, title, subtitle, ctaLabel, ctaDisabled, ctaLoading = false, ctaLoadingLabel, linkLabel, linkContent, verifiedLabel, children, fieldsOffset, fieldsGap, scrollFields = false, responsive = false, navLabel, screenId, showHeader = true, showFooter = true, overrides, onAction, ...shell }, ref) => (_jsxs(PrizmaScreenShell, { ref: ref, ...shell, className: [shell.className, responsive ? 'prizma-form-screen--responsive' : null]
26
+ export const PrizmaFormScreen = forwardRef(({ progress, title, subtitle, ctaLabel, ctaDisabled, ctaLoading = false, ctaLoadingLabel, linkLabel, linkDisabled, linkContent, verifiedLabel, children, fieldsOffset, fieldsGap, scrollFields = false, fieldsAlign = 'start', overlay, responsive = false, navLabel, screenId, showHeader = true, showFooter = true, overrides, onAction, ...shell }, ref) => (_jsxs(PrizmaScreenShell, { ref: ref, ...shell, className: [shell.className, responsive ? 'prizma-form-screen--responsive' : null]
27
27
  .filter(Boolean)
28
28
  .join(' ') || undefined, children: [_jsx(PrizmaScreenNav, { reserved: !showHeader, logo: overrides?.logo, navLabel: navLabel }), _jsxs("div", { className: "prizma-screen-layout__content", children: [progress ? (_jsx("div", { className: "prizma-screen-layout__progress", children: progress })) : null, _jsx(PrizmaTitle, { variant: overrides?.align === 'left' ? 'left' : 'centered', title: title, showSubtitle: Boolean(subtitle), subtitle: subtitle }), _jsx("div", { className: [
29
29
  'prizma-form-screen__fields',
30
30
  scrollFields ? 'prizma-form-screen__fields--scroll' : null,
31
+ fieldsAlign === 'end' ? 'prizma-form-screen__fields--end' : null,
31
32
  ]
32
33
  .filter(Boolean)
33
34
  .join(' '),
@@ -42,6 +43,6 @@ export const PrizmaFormScreen = forwardRef(({ progress, title, subtitle, ctaLabe
42
43
  ? { '--pfs-fields-gap': `var(--prizma-spacing-${fieldsGap})` }
43
44
  : null),
44
45
  }
45
- : undefined, children: children }), ctaLabel || linkLabel || linkContent ? (_jsxs("div", { className: "prizma-screen-layout__cta prizma-form-screen__cta", children: [ctaLabel ? (_jsx(PrizmaButton, { variant: "primary", size: "L", disabled: ctaDisabled, loading: ctaLoading, loadingLabel: ctaLoadingLabel, onClick: () => onAction?.({ type: 'cta', screenId }), children: ctaLabel })) : null, linkLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: linkLabel })) : null, linkContent] })) : null, showFooter ? (_jsx("div", { className: "prizma-screen-layout__footer prizma-screen-layout__footer--pinned", children: _jsx(PrizmaTag, { label: verifiedLabel }) })) : null] })] })));
46
+ : undefined, children: children }), ctaLabel || linkLabel || linkContent ? (_jsxs("div", { className: "prizma-screen-layout__cta prizma-form-screen__cta", children: [ctaLabel ? (_jsx(PrizmaButton, { variant: "primary", size: "L", disabled: ctaDisabled, loading: ctaLoading, loadingLabel: ctaLoadingLabel, onClick: () => onAction?.({ type: 'cta', screenId }), children: ctaLabel })) : null, linkLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: linkDisabled, onClick: () => onAction?.({ type: 'secondary', screenId }), children: linkLabel })) : null, linkContent] })) : null, showFooter ? (_jsx("div", { className: "prizma-screen-layout__footer prizma-screen-layout__footer--pinned", children: _jsx(PrizmaTag, { label: verifiedLabel }) })) : null] }), overlay] })));
46
47
  PrizmaFormScreen.displayName = 'PrizmaFormScreen';
47
48
  export default PrizmaFormScreen;
@@ -65,6 +65,14 @@ export interface FormsLongFormScreenProps extends Omit<PrizmaFormScreenProps, 't
65
65
  * into the CTA band — the half a module stylesheet kept losing. It is a no-op
66
66
  * on the short `name` preset, whose rows never overflow. DEVIATIONS #DSC-2,
67
67
  * restating #CURP-1.
68
+ *
69
+ * `emptyError` is the GUIDELINES §6-E empty-submit rule (Elena 2026-09-30):
70
+ * the user pressed the CTA with the form still empty. Every REQUIRED row
71
+ * renders empty, with no placeholder, in Core's own error state carrying
72
+ * `strings.requiredError`; optional and unmarked rows stay on their resting
73
+ * surface with no error. The CTA greys there and ONLY there: the resting
74
+ * `default` keeps it enabled, so the user can press it to reach the error —
75
+ * the `FormsFieldScreen` `emptyError` precedent applied to a stack.
68
76
  */
69
77
  export declare const FormsLongFormScreen: React.ForwardRefExoticComponent<FormsLongFormScreenProps & React.RefAttributes<HTMLDivElement>>;
70
78
  export default FormsLongFormScreen;