@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
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import { type PrizmaFormScreenProps } from './FormScreen';
3
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
4
+ import type { BiometricConsentState, BiometricConsentStrings } from './BiometricConsentStrings';
5
+ import './ScreenLayout.css';
6
+ export interface BiometricConsentScreenProps extends Omit<PrizmaFormScreenProps, 'title' | 'subtitle' | 'ctaLabel' | 'ctaDisabled' | 'verifiedLabel' | 'children' | 'navLabel' | 'linkLabel' | 'linkDisabled' | 'linkContent' | 'overlay' | 'fieldsOffset' | 'fieldsGap' | 'fieldsAlign' | 'scrollFields'> {
7
+ /** Module copy — this screen reads the `consent` and `quit` slices. */
8
+ strings: BiometricConsentStrings;
9
+ /** Which of the two section states to render. Default `default`. */
10
+ state?: BiometricConsentState;
11
+ /**
12
+ * Cancel was pressed: present the quit confirmation over the consent. The
13
+ * consent underneath keeps its `state` — the box is whatever the person had.
14
+ * Its two answers: the confirm emits `cta` (the session ends), the dismiss
15
+ * emits `secondary` (back to the consent). DEVIATIONS #BC-7.
16
+ */
17
+ quitConfirm?: boolean;
18
+ /**
19
+ * The box was ticked or unticked. A REAL change event from Core's own
20
+ * `<input type="checkbox">` — the card is a `<label>`, so tapping the copy
21
+ * toggles it too, which is the behaviour the request named ("tapping the body
22
+ * also toggles the checkbox"). The screen stays controlled by `state`; this
23
+ * only reports the press so a host (or the Core Lab prototype) can move the
24
+ * state itself. The sibling consent screen forwards no such handler and its
25
+ * prototype had to listen on a wrapper instead — this closes that gap for this
26
+ * module.
27
+ */
28
+ onConsentChange?: (checked: boolean) => void;
29
+ screenId?: string;
30
+ overrides?: PrizmaScreenOverrides;
31
+ onAction?: PrizmaScreenActionHandler;
32
+ }
33
+ /**
34
+ * Biometric Consent — the mandatory biometric-processing consent, built on the
35
+ * Form archetype (GUIDELINES.md §6-E, `PrizmaFormScreen`).
36
+ *
37
+ * ONE screen for the whole `biometricConsent` section (2001:16315): the two
38
+ * state frames (`empty` 2001:16361, `checked` 2001:16372) share an identical
39
+ * skeleton — nav → Title (H2 + H5) → one consent card → Continue → Cancel →
40
+ * tag — and differ ONLY in whether the box is ticked and the CTA released.
41
+ * Plus the quit confirmation (`quitConfirm`), a Core sheet or dialog presented
42
+ * over either state when Cancel is pressed.
43
+ *
44
+ * WHY ARCHETYPE E. The same reason the sibling consent screen gives: a
45
+ * `PrizmaCheckbox` is a real `<input type="checkbox">` that joins the form, and
46
+ * the gated CTA is the whole point of a consent screen — `PrizmaListScreen` has
47
+ * no `ctaDisabled` slot. E also carries the one tertiary action under the
48
+ * primary (`linkLabel`), which is exactly where the frame draws Cancel.
49
+ *
50
+ * THE CARD SITS AT THE BOTTOM OF THE COLUMN, NOT UNDER THE TITLE. The frame's
51
+ * middle (2001:15150) is a `justify-between` column: the Title block at the top,
52
+ * the consent card flush against the CTA band with the frame's Spacing/16
53
+ * between them, and everything in between left empty. That is the opposite of
54
+ * every other Form consumer, whose stack hangs off the title, so the screen
55
+ * SELECTS it through the archetype's `fieldsAlign="end"` slot rather than
56
+ * declaring a layout of its own — the value lives once, in `FormScreen.css`.
57
+ * DEVIATIONS #BC-1.
58
+ *
59
+ * Five things are deliberate:
60
+ *
61
+ * 1. The consent card is Core's `PrizmaCheckbox size="L"` — the component IS
62
+ * the tinted container Figma draws (padding, radius, `Surface/Brand/50`,
63
+ * the 28×28 box, the Size-S body). Its `segments` carry the inline
64
+ * "Incode's Privacy Policy" link, which opens the document without toggling
65
+ * the box. Nothing about the card is composed here.
66
+ * 2. The box is CONTROLLED by `state`, like every other spec state in this
67
+ * package, and the real change event is forwarded through `onConsentChange`
68
+ * so a host can drive it. `disabled` is NOT used — the frame draws the card
69
+ * on the live surface in both states (#PI-6: never borrow a semantic state
70
+ * to make something inert).
71
+ * 3. The CTA is gated by `ctaDisabled`, Core's own
72
+ * `button/primary/surface|text/disabled` treatment, until the box is ticked
73
+ * — the behaviour the requesting engineer asked for and the two sibling
74
+ * consents already ship. The frame paints Continue blue in both states;
75
+ * recorded, not resolved silently (DEVIATIONS #BC-2).
76
+ * 4. Cancel is the archetype's `tertiary-link` button and emits `secondary`.
77
+ * The frame draws it as the Button group's "Tertiary CTA" slot in body-800
78
+ * ink with no underline on the ES/PT copies and in brand blue on the EN one
79
+ * — Core ships one L tertiary (underlined, `button/tertiary/text/default`)
80
+ * and the component wins (DEVIATIONS #BC-4).
81
+ * 5. The quit confirmation rides archetype E's `overlay` slot: the screen owns
82
+ * the scrim and the placement (the foundation's `__sheet-scrim` /
83
+ * `__modal-panel` recipes), Core owns the card — `PrizmaModalContent`,
84
+ * the `default` variant exactly as the design system documents it: close
85
+ * icon, title, optional subtitle, and its own `vertical-v1` Button Group
86
+ * (Quit over Cancel). Placed the way Core's presenter places a modal —
87
+ * pinned to the bottom of the frame with a Spacing/12 margin, never
88
+ * centred — on EVERY platform. The owner ruled out a bottom sheet on the
89
+ * phone, then a centred card (Camila, 2026-10-13): "use the default modal
90
+ * … so we try with an existing component". The close icon dismisses, the
91
+ * same as Cancel. Nothing is drawn behind it that the consent did not
92
+ * already draw. DEVIATIONS #BC-7, #BC-8.
93
+ */
94
+ export declare const BiometricConsentScreen: React.ForwardRefExoticComponent<BiometricConsentScreenProps & React.RefAttributes<HTMLDivElement>>;
95
+ export default BiometricConsentScreen;
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef } from 'react';
4
+ import { PrizmaCheckbox } from '@incodetech/prizma-core-web/checkbox';
5
+ import { PrizmaModalContent } from '@incodetech/prizma-core-web/modal';
6
+ import { PrizmaFormScreen } from './FormScreen';
7
+ import './ScreenLayout.css';
8
+ /**
9
+ * Biometric Consent — the mandatory biometric-processing consent, built on the
10
+ * Form archetype (GUIDELINES.md §6-E, `PrizmaFormScreen`).
11
+ *
12
+ * ONE screen for the whole `biometricConsent` section (2001:16315): the two
13
+ * state frames (`empty` 2001:16361, `checked` 2001:16372) share an identical
14
+ * skeleton — nav → Title (H2 + H5) → one consent card → Continue → Cancel →
15
+ * tag — and differ ONLY in whether the box is ticked and the CTA released.
16
+ * Plus the quit confirmation (`quitConfirm`), a Core sheet or dialog presented
17
+ * over either state when Cancel is pressed.
18
+ *
19
+ * WHY ARCHETYPE E. The same reason the sibling consent screen gives: a
20
+ * `PrizmaCheckbox` is a real `<input type="checkbox">` that joins the form, and
21
+ * the gated CTA is the whole point of a consent screen — `PrizmaListScreen` has
22
+ * no `ctaDisabled` slot. E also carries the one tertiary action under the
23
+ * primary (`linkLabel`), which is exactly where the frame draws Cancel.
24
+ *
25
+ * THE CARD SITS AT THE BOTTOM OF THE COLUMN, NOT UNDER THE TITLE. The frame's
26
+ * middle (2001:15150) is a `justify-between` column: the Title block at the top,
27
+ * the consent card flush against the CTA band with the frame's Spacing/16
28
+ * between them, and everything in between left empty. That is the opposite of
29
+ * every other Form consumer, whose stack hangs off the title, so the screen
30
+ * SELECTS it through the archetype's `fieldsAlign="end"` slot rather than
31
+ * declaring a layout of its own — the value lives once, in `FormScreen.css`.
32
+ * DEVIATIONS #BC-1.
33
+ *
34
+ * Five things are deliberate:
35
+ *
36
+ * 1. The consent card is Core's `PrizmaCheckbox size="L"` — the component IS
37
+ * the tinted container Figma draws (padding, radius, `Surface/Brand/50`,
38
+ * the 28×28 box, the Size-S body). Its `segments` carry the inline
39
+ * "Incode's Privacy Policy" link, which opens the document without toggling
40
+ * the box. Nothing about the card is composed here.
41
+ * 2. The box is CONTROLLED by `state`, like every other spec state in this
42
+ * package, and the real change event is forwarded through `onConsentChange`
43
+ * so a host can drive it. `disabled` is NOT used — the frame draws the card
44
+ * on the live surface in both states (#PI-6: never borrow a semantic state
45
+ * to make something inert).
46
+ * 3. The CTA is gated by `ctaDisabled`, Core's own
47
+ * `button/primary/surface|text/disabled` treatment, until the box is ticked
48
+ * — the behaviour the requesting engineer asked for and the two sibling
49
+ * consents already ship. The frame paints Continue blue in both states;
50
+ * recorded, not resolved silently (DEVIATIONS #BC-2).
51
+ * 4. Cancel is the archetype's `tertiary-link` button and emits `secondary`.
52
+ * The frame draws it as the Button group's "Tertiary CTA" slot in body-800
53
+ * ink with no underline on the ES/PT copies and in brand blue on the EN one
54
+ * — Core ships one L tertiary (underlined, `button/tertiary/text/default`)
55
+ * and the component wins (DEVIATIONS #BC-4).
56
+ * 5. The quit confirmation rides archetype E's `overlay` slot: the screen owns
57
+ * the scrim and the placement (the foundation's `__sheet-scrim` /
58
+ * `__modal-panel` recipes), Core owns the card — `PrizmaModalContent`,
59
+ * the `default` variant exactly as the design system documents it: close
60
+ * icon, title, optional subtitle, and its own `vertical-v1` Button Group
61
+ * (Quit over Cancel). Placed the way Core's presenter places a modal —
62
+ * pinned to the bottom of the frame with a Spacing/12 margin, never
63
+ * centred — on EVERY platform. The owner ruled out a bottom sheet on the
64
+ * phone, then a centred card (Camila, 2026-10-13): "use the default modal
65
+ * … so we try with an existing component". The close icon dismisses, the
66
+ * same as Cancel. Nothing is drawn behind it that the consent did not
67
+ * already draw. DEVIATIONS #BC-7, #BC-8.
68
+ */
69
+ export const BiometricConsentScreen = forwardRef(({ strings, state = 'default', quitConfirm = false, onConsentChange, screenId = quitConfirm ? 'biometric-consent-quit' : `biometric-consent-${state}`, overrides, onAction, ...shell }, ref) => {
70
+ const accepted = state === 'accepted';
71
+ const quit = quitConfirm ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "prizma-screen-layout__sheet-scrim", "aria-hidden": "true" }), _jsx("div", { className: "prizma-screen-layout__modal-panel", children: _jsx(PrizmaModalContent, { variant: "default", title: strings.quit.title, subtitle: strings.quit.subtitle, closeLabel: strings.quit.closeLabel, onClose: () => onAction?.({ type: 'secondary', screenId }), buttonGroupProps: {
72
+ primaryLabel: strings.quit.confirmLabel,
73
+ secondaryLabel: strings.quit.dismissLabel,
74
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
75
+ onSecondaryClick: () => onAction?.({ type: 'secondary', screenId }),
76
+ } }) })] })) : undefined;
77
+ return (_jsx(PrizmaFormScreen, { ref: ref, title: strings.consent.title, subtitle: strings.consent.subtitle,
78
+ /* The card hangs off the CTA band, not the title — the frame's
79
+ justify-between middle. Selected from the archetype, not declared. */
80
+ fieldsAlign: "end", ctaLabel: strings.consent.ctaLabel,
81
+ /* "Continue" stays inert until the box is ticked. */
82
+ ctaDisabled: !accepted, linkLabel: strings.consent.cancelLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides,
83
+ /* With the sheet up, the consent's own buttons are under the scrim and
84
+ must not report: the sheet's two answers are the only actions. */
85
+ onAction: quitConfirm ? undefined : onAction, overlay: quit, ...shell, children: _jsx(PrizmaCheckbox, { size: "L", checked: accepted,
86
+ /* Mandatory for a11y (`aria-required`); the L container never paints
87
+ a `*` and the frame draws none. */
88
+ required: true, "aria-label": strings.consent.ariaLabel, segments: strings.consent.body, onCheckedChange: quitConfirm ? undefined : onConsentChange }) }));
89
+ });
90
+ BiometricConsentScreen.displayName = 'BiometricConsentScreen';
91
+ export default BiometricConsentScreen;
@@ -0,0 +1,84 @@
1
+ import type { PrizmaConsentSegment } from '@incodetech/prizma-core-web/checkbox';
2
+ /**
3
+ * The two states the section draws. Both share ONE layout — nav → Title →
4
+ * (space) → consent card → Continue → Cancel → tag — and differ only in whether
5
+ * the box is ticked and, with it, whether Continue is released:
6
+ *
7
+ * - `default` — `biometricConsent.empty` (2001:16361): the box unticked.
8
+ * Continue is DISABLED. The frame paints it brand blue; the
9
+ * requesting engineer asked for it greyed until the box is
10
+ * ticked, "like in Data Sharing Consent" (Uros, #uxp-design
11
+ * 2026-09-02), which is also what the two sibling consent
12
+ * screens do. DEVIATIONS #BC-2 — flip `ctaDisabled` in the
13
+ * screen if the frame is the final word.
14
+ * - `accepted` — `biometricConsent.checked` (2001:16372): the box ticked,
15
+ * Continue released to brand blue.
16
+ *
17
+ * There is NO refusal state. With Continue gated, "pressed with nothing ticked"
18
+ * cannot happen, and the file draws no such frame — the sibling modules' added
19
+ * `rejected` state is deliberately not copied here (DEVIATIONS #BC-3).
20
+ *
21
+ * The quit confirmation is NOT a third state: it is a dialog presented over
22
+ * either state (`quitConfirm` on the screen), because what is underneath — the
23
+ * box empty or ticked — is whatever the person had before they pressed Cancel.
24
+ */
25
+ export type BiometricConsentState = 'default' | 'accepted';
26
+ export interface BiometricConsentStrings {
27
+ /** "verified by" attribution in the footer tag. */
28
+ verifiedLabel: string;
29
+ /** Accessible name for the logo nav row. */
30
+ navLabel?: string;
31
+ /** The `biometricConsent` section. */
32
+ consent: {
33
+ /** H2 ("Request for Biometric Processing"). Centered, per the section. */
34
+ title: string;
35
+ /** H5 under the title ("Your verification is powered by Incode…"). The
36
+ * section draws one on every frame. */
37
+ subtitle: string;
38
+ /**
39
+ * The consent card's copy, as ordered segments: a plain string renders as
40
+ * body copy, an object renders as an inline `<a>` that opens the document
41
+ * WITHOUT toggling the box. Core's own `PrizmaConsentSegment` contract — the
42
+ * multi-link path on `PrizmaCheckbox size="L"` — so a consent citing two
43
+ * documents stays expressible (#PI-10: each link is separately actionable).
44
+ * In production this body arrives from the backend.
45
+ */
46
+ body: PrizmaConsentSegment[];
47
+ /** Accessible name for the box — the card copy is a paragraph, too long to
48
+ * serve as one. */
49
+ ariaLabel?: string;
50
+ /** Primary CTA ("Continue"). Disabled until the box is ticked. */
51
+ ctaLabel: string;
52
+ /** The tertiary action under Continue ("Cancel"). Declining is a hard stop:
53
+ * the session cannot process the person's biometrics without this consent. */
54
+ cancelLabel: string;
55
+ };
56
+ /**
57
+ * The confirmation presented when Cancel is pressed, before the session ends.
58
+ *
59
+ * NOT IN THE FIGMA FILE, and not in the shipped Web SDK either — today
60
+ * `manager.cancel()` ends the session the moment Cancel is tapped. Added at
61
+ * the owner's request (Camila, 2026-10-13): Cancel is irreversible here, the
62
+ * person cannot be verified without the consent, and a mis-tap on the
63
+ * tertiary under Continue should not cost them the session. It is Core's
64
+ * Modal, `default` variant, on EVERY platform — the existing component as
65
+ * the design system documents it, close icon and all, pinned to the bottom
66
+ * of the frame the way Core's presenter places it; not a bottom sheet and
67
+ * not a centred card (owner calls, same day). The copy is the
68
+ * SDK's own close dialog (`page.close.confirmQuit` / `page.close.confirm` /
69
+ * `page.close.cancel`), so nothing is invented. DEVIATIONS #BC-7.
70
+ */
71
+ quit: {
72
+ /** Dialog title ("Are you sure you want to quit?"). */
73
+ title: string;
74
+ /** Optional supporting line. The SDK's close dialog carries none. */
75
+ subtitle?: string;
76
+ /** The destructive confirmation ("Quit") — primary, on top. */
77
+ confirmLabel: string;
78
+ /** Dismiss and stay on the consent ("Cancel") — secondary, below it. */
79
+ dismissLabel: string;
80
+ /** Accessible name of the card's close icon ("Close"), which also
81
+ * dismisses. Core's Default modal always draws the icon. */
82
+ closeLabel: string;
83
+ };
84
+ }
@@ -0,0 +1,31 @@
1
+ // Biometric Consent — copy contract.
2
+ //
3
+ // The mandatory biometric consent the BACKEND decides to show — at the start of
4
+ // a flow, or right after ID capture — when the session comes from a protected
5
+ // geography (Illinois BIPA and the handful of states like it), or when the state
6
+ // on the captured ID says so. It is NOT the User Consent / Data Sharing Consent
7
+ // module: it is the flow builder's "Required biometric consent" setting, it uses
8
+ // its own endpoint, and declining it is a hard stop. Uros Milivojevic
9
+ // (#uxp-design, 2026-09-01): "Compared to User Consent it has a Cancel button
10
+ // next to Continue, a subtitle, and a long consent body loaded from the BE with
11
+ // tappable links (tapping the body also toggles the checkbox)."
12
+ //
13
+ // Figma: `Biometric Consent Screen` (vXvIMQF5DSJgkQHhFAQcWC), section
14
+ // `biometricConsent` (2001:16315). ONE screen, drawn across:
15
+ // · 2001:16361 `biometricConsent.empty` the master — box unticked
16
+ // · 2001:16372 `biometricConsent.checked` the box ticked
17
+ // · `🔧 Behavior`, `🖤 Dark mode`, `🇪🇸 ES`, `🇵🇹 PT` and the six device
18
+ // sections are the same two states re-presented: dark is token-driven
19
+ // (`[data-prizma-theme='dark']`), the locales are the host's locale pack,
20
+ // and the widths are excluded by the no-per-width-variants rule (DEVIATIONS
21
+ // #17).
22
+ //
23
+ // The screen is: nav → Title (H2 + H5 subtitle) → one consent card pinned to
24
+ // the BOTTOM of the column → Continue → Cancel → tag. No disclosure paragraph —
25
+ // unlike the consent screen the two sibling modules share, the legal body here
26
+ // lives INSIDE the card, and the backend supplies it.
27
+ //
28
+ // No hardcoded English anywhere in a screen: every visible string arrives here,
29
+ // so the host's locale pack drives the module (the Figma file ships ES + PT
30
+ // columns beside the EN one).
31
+ export {};
@@ -27,6 +27,19 @@ export interface PrizmaCaptureScreenProps extends Omit<PrizmaScreenShellProps, '
27
27
  manualFeedbackText?: string;
28
28
  /** Per-state status copy (uploading / success / error / warning). */
29
29
  statusLabels?: Partial<Record<PrizmaSelfieCaptureState, string>>;
30
+ /**
31
+ * A notice band above the footer — a Snackbar the module raises over the live
32
+ * feed without taking the camera away (Face Capture's "Need more time?" offer
33
+ * is the first consumer).
34
+ *
35
+ * ANCHORED, like the footer and the control strip, and for the same reason:
36
+ * the owner's rule is that anchored chrome sits at the same position on every
37
+ * screen (2026-08-18). Flowing it would push Core's ring up, and the ring
38
+ * offset is the one thing this archetype is forbidden to touch
39
+ * (DEVIATIONS #12). It rides the empty band under the instruction line, which
40
+ * is where the module Figma puts it.
41
+ */
42
+ notice?: ReactNode;
30
43
  /** "verified by" attribution copy for the footer tag. */
31
44
  verifiedLabel: string;
32
45
  navLabel?: string;
@@ -19,12 +19,12 @@ import './ScreenLayout.css';
19
19
  * loading and result screens of a capture module use this archetype too —
20
20
  * not the centered Processing archetype (DEVIATIONS #11).
21
21
  */
22
- export const PrizmaCaptureScreen = forwardRef(({ showHeader, showFooter = true, cameraOpen = true, state, camera, feedbackText, isManual, manualFeedbackText, statusLabels, verifiedLabel, navLabel, screenId, overrides, onAction, ...shell }, ref) => (
22
+ export const PrizmaCaptureScreen = forwardRef(({ showHeader, showFooter = true, cameraOpen = true, state, camera, feedbackText, isManual, manualFeedbackText, statusLabels, notice, verifiedLabel, navLabel, screenId, overrides, onAction, ...shell }, ref) => (
23
23
  // Pin light ONLY while the camera feed is open: Core paints the live
24
24
  // capture area with Static inks because "the capture screen stays WHITE in
25
25
  // both modes — light is needed for the shot" (SelfieCapture.css, owner
26
26
  // 2026-07-14). Feed-closed screens are ordinary screens and follow the app.
27
27
  // DEVIATIONS #F-27.
28
- _jsxs(PrizmaScreenShell, { ref: ref, theme: cameraOpen ? 'light' : undefined, ...shell, children: [_jsx(PrizmaScreenNav, { reserved: !(showHeader ?? (cameraOpen ? Boolean(overrides?.logo) : true)), logo: overrides?.logo, navLabel: navLabel }), _jsxs("div", { className: "prizma-screen-layout__content prizma-screen-layout__content--capture", children: [_jsx(PrizmaSelfieCapture, { state: state, camera: camera, showFeedback: Boolean(feedbackText ?? manualFeedbackText), feedbackText: feedbackText, isManual: isManual, manualFeedbackText: manualFeedbackText, statusLabels: statusLabels }), showFooter ? (_jsx("div", { className: "prizma-screen-layout__footer prizma-screen-layout__footer--pinned", children: _jsx(PrizmaTag, { label: verifiedLabel }) })) : null] })] })));
28
+ _jsxs(PrizmaScreenShell, { ref: ref, theme: cameraOpen ? 'light' : undefined, ...shell, children: [_jsx(PrizmaScreenNav, { reserved: !(showHeader ?? (cameraOpen ? Boolean(overrides?.logo) : true)), logo: overrides?.logo, navLabel: navLabel }), _jsxs("div", { className: "prizma-screen-layout__content prizma-screen-layout__content--capture", children: [_jsx(PrizmaSelfieCapture, { state: state, camera: camera, showFeedback: Boolean(feedbackText ?? manualFeedbackText), feedbackText: feedbackText, isManual: isManual, manualFeedbackText: manualFeedbackText, statusLabels: statusLabels }), notice ? (_jsx("div", { className: "prizma-screen-layout__capture-notice", children: notice })) : null, showFooter ? (_jsx("div", { className: "prizma-screen-layout__footer prizma-screen-layout__footer--pinned", children: _jsx(PrizmaTag, { label: verifiedLabel }) })) : null] })] })));
29
29
  PrizmaCaptureScreen.displayName = 'PrizmaCaptureScreen';
30
30
  export default PrizmaCaptureScreen;
@@ -46,6 +46,13 @@ export interface CurpEnterScreenProps extends Omit<PrizmaFormScreenProps, 'title
46
46
  * `linkContent` slot: no primitive is invented, no layout is declared, and the
47
47
  * two actions stay distinguishable (`cta` vs `secondary`, the #PI-10 rule).
48
48
  * The gap is reported for promotion — see the module's DEVIATIONS rows.
49
+ *
50
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
51
+ * GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
52
+ * typed. The field renders empty with no placeholder, in Core's error state
53
+ * with `requiredError`, and the primary CTA is disabled, following the drawn
54
+ * precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
55
+ * so the user can reach this state.
49
56
  */
50
57
  export declare const CurpEnterScreen: React.ForwardRefExoticComponent<CurpEnterScreenProps & React.RefAttributes<HTMLDivElement>>;
51
58
  export default CurpEnterScreen;
@@ -37,6 +37,13 @@ import './CurpLayout.css';
37
37
  * `linkContent` slot: no primitive is invented, no layout is declared, and the
38
38
  * two actions stay distinguishable (`cta` vs `secondary`, the #PI-10 rule).
39
39
  * The gap is reported for promotion — see the module's DEVIATIONS rows.
40
+ *
41
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
42
+ * GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
43
+ * typed. The field renders empty with no placeholder, in Core's error state
44
+ * with `requiredError`, and the primary CTA is disabled, following the drawn
45
+ * precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
46
+ * so the user can reach this state.
40
47
  */
41
48
  export const CurpEnterScreen = forwardRef(({ strings, state = 'default', value, screenId = `curp-enter-${state}`, className, overrides, onAction, ...shell }, ref) => {
42
49
  // Figma treats the five sections as four distinct field treatments:
@@ -47,6 +54,9 @@ export const CurpEnterScreen = forwardRef(({ strings, state = 'default', value,
47
54
  // surface.
48
55
  const busy = state === 'loading';
49
56
  const invalid = state === 'error';
57
+ // Empty submit: the field stays empty, drops its placeholder and takes
58
+ // Core's error state with the shared "required" row; the CTA greys.
59
+ const emptyError = state === 'emptyError';
50
60
  const focused = state === 'focused' || state === 'filled';
51
61
  return (_jsx(PrizmaFormScreen, { ref: ref,
52
62
  /* Scopes the one module rule that lets the `error` section keep the
@@ -63,11 +73,15 @@ export const CurpEnterScreen = forwardRef(({ strings, state = 'default', value,
63
73
  .filter(Boolean)
64
74
  .join(' '), title: strings.enter.title, subtitle: strings.enter.subtitle, ctaLabel: strings.enter.ctaLabel,
65
75
  /* The `loading` section swaps the CTA label for Core's spinner. */
66
- ctaLoading: busy, ctaLoadingLabel: strings.enter.ctaLoadingLabel, linkContent: _jsx(PrizmaButton, { variant: "secondary", size: "L",
76
+ ctaLoading: busy, ctaDisabled: emptyError, ctaLoadingLabel: strings.enter.ctaLoadingLabel, linkContent: _jsx(PrizmaButton, { variant: "secondary", size: "L",
67
77
  /* Figma greys this action too while the lookup runs. */
68
- disabled: busy, onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.enter.noCurpLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, "aria-label": strings.enter.fieldLabel, placeholder: strings.enter.placeholder, defaultValue: value ?? '',
78
+ disabled: busy, onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.enter.noCurpLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, "aria-label": strings.enter.fieldLabel, placeholder: emptyError ? undefined : strings.enter.placeholder, defaultValue: emptyError ? '' : value ?? '',
69
79
  /* A CURP is an 18-character uppercase key. */
70
- autoCapitalize: "characters", autoComplete: "off", spellCheck: false, disabled: busy, error: invalid, errorText: invalid ? strings.enter.errorText : undefined,
80
+ autoCapitalize: "characters", autoComplete: "off", spellCheck: false, disabled: busy, error: invalid || emptyError, errorText: invalid
81
+ ? strings.enter.errorText
82
+ : emptyError
83
+ ? strings.requiredError
84
+ : undefined,
71
85
  /* The clear ✕ is drawn only on the error section (76:36434); the
72
86
  `filled` section (79:1553) draws none. Core still owns WHEN it
73
87
  shows — its `--show` state needs a value in the field. */
@@ -42,6 +42,14 @@ export interface CurpGenerateScreenProps extends Omit<PrizmaFormScreenProps, 'ti
42
42
  * The `dropdown` section IS the open-menu spec state, which Core exposes as the
43
43
  * documentation-only `forceOpen` (a screen is one static state, never a live
44
44
  * interaction) — the same mechanism Phone Input's `countryCode` section uses.
45
+ *
46
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
47
+ * GUIDELINES §6-E empty-submit rule): "Generate" was pressed with the form
48
+ * empty. All six fields carry the required `*`, so all six render empty with no
49
+ * placeholder, in Core's error state with `requiredError`; no menu is open and
50
+ * the primary CTA is disabled, following the drawn precedent in
51
+ * `FormsFieldScreen`. The resting `default` keeps the CTA enabled so the user
52
+ * can reach this state.
45
53
  */
46
54
  export declare const CurpGenerateScreen: React.ForwardRefExoticComponent<CurpGenerateScreenProps & React.RefAttributes<HTMLDivElement>>;
47
55
  export default CurpGenerateScreen;
@@ -29,29 +29,44 @@ import './CurpLayout.css';
29
29
  * The `dropdown` section IS the open-menu spec state, which Core exposes as the
30
30
  * documentation-only `forceOpen` (a screen is one static state, never a live
31
31
  * interaction) — the same mechanism Phone Input's `countryCode` section uses.
32
+ *
33
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
34
+ * GUIDELINES §6-E empty-submit rule): "Generate" was pressed with the form
35
+ * empty. All six fields carry the required `*`, so all six render empty with no
36
+ * placeholder, in Core's error state with `requiredError`; no menu is open and
37
+ * the primary CTA is disabled, following the drawn precedent in
38
+ * `FormsFieldScreen`. The resting `default` keeps the CTA enabled so the user
39
+ * can reach this state.
32
40
  */
33
41
  export const CurpGenerateScreen = forwardRef(({ strings, state = 'default', values, genders = [], states = [], screenId = `curp-generate-${state}`, className, overrides, onAction, ...shell }, ref) => {
34
42
  const copy = strings.generate;
43
+ // Empty submit: every required field drops its value and placeholder and
44
+ // takes Core's error state with the shared "required" row; the CTA greys.
45
+ const emptyError = state === 'emptyError';
46
+ const ph = (placeholder) => (emptyError ? undefined : placeholder);
47
+ const err = {
48
+ error: emptyError,
49
+ errorText: emptyError ? strings.requiredError : undefined,
50
+ };
35
51
  return (_jsxs(PrizmaFormScreen, { ref: ref,
36
- /* Scopes the two module rules in CurpLayout.css (see the foundation and
37
- Core requests recorded there):
38
- - `--scroll` lets the six-field stack scroll inside the 844 frame
39
- instead of overflowing its box and painting over the CTA. EVERY
40
- section gets it, `dropdown` included — Figma draws "Generate"
41
- clear and unobstructed under the open menu.
42
- - `--menu-open` flips Core's absolutely-positioned menu above its
43
- trigger on the one section that forces it open, so the three
44
- gender rows stay inside the scrolling column. */
45
- className: [
52
+ /* The six-field stack scrolls inside the 844 frame through the
53
+ foundation's `scrollFields` (overflow + the Spacing/16 that keeps the
54
+ cut clear of "Generate"). EVERY section gets it, `dropdown` included:
55
+ Figma draws "Generate" clear and unobstructed under the open menu.
56
+ It replaces the module's old `--scroll` rule, which scrolled but
57
+ carried no spacing, so the last field ran flush into the button.
58
+ `--menu-open` (CurpLayout.css) flips Core's absolutely-positioned
59
+ menu above its trigger on the one section that forces it open, so
60
+ the three gender rows stay inside the scrolling column. */
61
+ scrollFields: true, className: [
46
62
  'curp-generate',
47
- 'curp-generate--scroll',
48
63
  state === 'dropdown' ? 'curp-generate--menu-open' : '',
49
64
  className,
50
65
  ]
51
66
  .filter(Boolean)
52
- .join(' '), title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel, ctaLoadingLabel: copy.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: [_jsx(PrizmaTextField, { label: copy.firstName.label, placeholder: copy.firstName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.firstName ?? '' }), _jsx(PrizmaTextField, { label: copy.firstLastName.label, placeholder: copy.firstLastName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.firstLastName ?? '' }), _jsx(PrizmaTextField, { label: copy.secondLastName.label, placeholder: copy.secondLastName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.secondLastName ?? '' }), _jsx(PrizmaDropdown, { label: copy.gender.label, placeholder: copy.gender.placeholder, required: true, requiredHint: strings.requiredHint, options: genders, defaultValue: values?.gender ?? null, searchPlaceholder: copy.gender.searchPlaceholder,
67
+ .join(' '), title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel, ctaDisabled: emptyError, ctaLoadingLabel: copy.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: [_jsx(PrizmaTextField, { label: copy.firstName.label, placeholder: ph(copy.firstName.placeholder), required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: emptyError ? '' : values?.firstName ?? '', ...err }), _jsx(PrizmaTextField, { label: copy.firstLastName.label, placeholder: ph(copy.firstLastName.placeholder), required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: emptyError ? '' : values?.firstLastName ?? '', ...err }), _jsx(PrizmaTextField, { label: copy.secondLastName.label, placeholder: ph(copy.secondLastName.placeholder), required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: emptyError ? '' : values?.secondLastName ?? '', ...err }), _jsx(PrizmaDropdown, { label: copy.gender.label, placeholder: ph(copy.gender.placeholder), required: true, requiredHint: strings.requiredHint, options: genders, defaultValue: emptyError ? null : values?.gender ?? null, ...err, searchPlaceholder: copy.gender.searchPlaceholder,
53
68
  /* The `dropdown` section is the open Gender menu. */
54
- forceOpen: state === 'dropdown' }), _jsx(PrizmaDateField, { label: copy.dateOfBirth.label, placeholder: copy.dateOfBirth.placeholder, required: true, requiredHint: strings.requiredHint, calendarLabel: copy.dateOfBirth.calendarLabel, defaultValue: values?.dateOfBirth ?? '' }), _jsx(PrizmaDropdown, { label: copy.birthState.label, placeholder: copy.birthState.placeholder, required: true, requiredHint: strings.requiredHint, options: states, defaultValue: values?.birthState ?? null, searchable: true, searchPlaceholder: copy.birthState.searchPlaceholder })] }));
69
+ forceOpen: state === 'dropdown' }), _jsx(PrizmaDateField, { label: copy.dateOfBirth.label, placeholder: ph(copy.dateOfBirth.placeholder), required: true, requiredHint: strings.requiredHint, calendarLabel: copy.dateOfBirth.calendarLabel, defaultValue: emptyError ? '' : values?.dateOfBirth ?? '', ...err }), _jsx(PrizmaDropdown, { label: copy.birthState.label, placeholder: ph(copy.birthState.placeholder), required: true, requiredHint: strings.requiredHint, options: states, defaultValue: emptyError ? null : values?.birthState ?? null, ...err, searchable: true, searchPlaceholder: copy.birthState.searchPlaceholder })] }));
55
70
  });
56
71
  CurpGenerateScreen.displayName = 'CurpGenerateScreen';
57
72
  export default CurpGenerateScreen;
@@ -1,6 +1,6 @@
1
1
  /* CURP — module layout additions.
2
2
  *
3
- * THREE rules, and none of them declares spacing: the content column, the 24px
3
+ * TWO rules, and none of them declares spacing: the content column, the 24px
4
4
  * gutters, the title → fields Spacing/32, the field gaps, the CTA column and
5
5
  * the anchored footer all stay the foundation's (`ScreenLayout.css`,
6
6
  * `FormScreen.css`). Nothing here re-declares an archetype value, invents a
@@ -10,40 +10,14 @@
10
10
  */
11
11
 
12
12
  /* ── The generator's field stack is taller than the screen ───────────────────
13
- * `CURP.Gene.default` is a 1051-tall frame: its `input-list` alone is 548 tall
14
- * (six 78px labelled fields at Spacing/16) and the CONTENT frame grows to 861
15
- * instead of the usual 654. At the 390×844 reference the column has ~470 for
16
- * it, so the last dropdown and the CTA fall past the fold.
17
- *
18
- * Archetype E's `.prizma-form-screen__fields` is `flex: 1; min-height: 0` with
19
- * no overflow behaviour — it was measured against Phone Input, whose forms are
20
- * one or two fields — so the surplus is silently clipped by `ScreenShell`'s
21
- * `overflow: hidden`, and (because the fields cannot shrink below
22
- * `--prizma-input-size-field-height`) it first PAINTS OVER the sibling CTA.
23
- * Screens are authored at 390×844 and never reflow (README / DEVIATIONS #17),
24
- * so growing the frame is not an option either.
25
- *
26
- * The stack is therefore allowed to scroll INSIDE the column: the title, the
27
- * CTA and the anchored tag stay put and the six fields pan, which is the
28
- * closest a fixed 844 frame gets to Figma's taller page. It adds NO padding, NO
29
- * gap and NO size of its own.
30
- *
31
- * Applied to EVERY `CURP.Gene.*` section including `dropdown`. The earlier
32
- * build withheld it from `dropdown` to keep Core's absolutely-positioned menu
33
- * from being clipped, and that traded one clipped element for a worse one: with
34
- * `overflow: visible` the 548-tall stack overflowed its box and painted the
35
- * open menu straight across the "Generate" button, which Figma (2058:16373)
36
- * draws clear and unobstructed under the menu. The CTA is never allowed to be
37
- * overlapped; the menu is handled by the next rule instead.
38
- *
39
- * FOUNDATION REQUEST: promote this to `FormScreen.css` as a
40
- * `.prizma-form-screen__fields--scroll` variant (or make overflow the default),
41
- * driven by a `scrollFields` prop on `PrizmaFormScreen`. Every long-form module
42
- * still to come in Wave 3 — eKYC, eKYB, address entry — will hit the same wall.
13
+ * `CURP.Gene.default` is a 1051-tall frame (six 78px labelled fields at
14
+ * Spacing/16), so at 390×844 the stack scrolls inside the column. That is the
15
+ * foundation's `scrollFields` on `PrizmaFormScreen` now (FormScreen.css
16
+ * `__fields--scroll`), which also carries the Spacing/16 between the cut and
17
+ * the CTA. The module used to hand-roll `overflow-y: auto` here with no
18
+ * spacing, so the last field ran flush into "Generate" (Elena, 2026-09-30).
19
+ * Removed in favour of the foundation rule.
43
20
  */
44
- .curp-generate--scroll .prizma-form-screen__fields {
45
- overflow-y: auto;
46
- }
47
21
 
48
22
  /* ── The open Gender menu must stay inside the scrolling column ──────────────
49
23
  * `CURP.Gene.dropdown` exists to show the three gender rows, so they may not be
@@ -11,15 +11,23 @@
11
11
  * - `error` — negative field + clear ✕ + the "Invalid CURP" row
12
12
  * - `loading` — both actions inert while the lookup runs; the spinner rides the
13
13
  * CTA, never the field (the Phone Input lesson, DEVIATIONS #PI-5)
14
+ * - `emptyError` — "Continue" pressed with the field still empty: the field
15
+ * stays empty (no placeholder) in Core's error state with `requiredError`,
16
+ * and the CTA is disabled. GUIDELINES §6-E empty-submit rule (Elena,
17
+ * 2026-09-30).
14
18
  */
15
- export type CurpEnterState = 'default' | 'focused' | 'filled' | 'error' | 'loading';
19
+ export type CurpEnterState = 'default' | 'focused' | 'filled' | 'error' | 'loading' | 'emptyError';
16
20
  /**
17
21
  * The `CURP.Gene.*` form sections — the six-field generator:
18
22
  * - `default` — every field empty on its placeholder
19
23
  * - `dropdown` — the Gender menu open over the form (Core's `forceOpen`)
20
24
  * - `filled` — every field carrying a value
25
+ * - `emptyError` — "Generate" pressed with the form still empty: every required
26
+ * field stays empty (no placeholder) in Core's error state with
27
+ * `requiredError`, no menu is open, and the CTA is disabled. GUIDELINES §6-E
28
+ * empty-submit rule (Elena, 2026-09-30).
21
29
  */
22
- export type CurpGenerateState = 'default' | 'dropdown' | 'filled';
30
+ export type CurpGenerateState = 'default' | 'dropdown' | 'filled' | 'emptyError';
23
31
  /**
24
32
  * The two spinner sections. `verifying` is `CURP.EnterCURP.veryfing` (the
25
33
  * Figma layer name carries the typo); `generating` is `CURP.Gene.generating`.
@@ -73,6 +81,12 @@ export interface CurpStrings {
73
81
  * the marker is localizable and never hardcoded in a screen.
74
82
  */
75
83
  requiredHint?: string;
84
+ /**
85
+ * Error row under every required field left empty when the primary CTA is
86
+ * pressed (the `emptyError` state of the entry and generator forms):
87
+ * "This field is required". SDK key `dynamicForms.fieldRequired`.
88
+ */
89
+ requiredError: string;
76
90
  /** CURP.EnterCURP.{default,focused,filled,error,loading} */
77
91
  enter: {
78
92
  /** H2 ("Enter your CURP"). */
@@ -389,6 +389,22 @@
389
389
  background: var(--prizma-surface-neutral-50);
390
390
  align-items: center;
391
391
  justify-content: center;
392
+ /* The card hugs its content and is taller than a short window: on the Duo
393
+ * open full screen (626) unsafe centring put 90px of it above the page and
394
+ * 90 below, heading cut off (sweep 2026-10-20). `safe` starts the card at
395
+ * the top of the page when it does not fit, and does nothing when it does
396
+ * (#F-37, same mechanism). The plain `center` is the fallback. */
397
+ justify-content: safe center;
398
+ }
399
+
400
+ /* …and under 720 the page scrolls, as a desktop page does when the window is
401
+ * shorter than its content. The shell is `overflow: hidden` for the screens
402
+ * that pin chrome to its edges; this page pins nothing, the tag rides in the
403
+ * card's band. */
404
+ @container pcs-screen (max-height: 720px) {
405
+ .prizma-screen-shell.demo-screens-redirect {
406
+ overflow-y: auto;
407
+ }
392
408
  }
393
409
 
394
410
  .demo-screens-redirect__card {
@@ -13,8 +13,13 @@ import './DemoScreensLayout.css';
13
13
  * - `resend` "Link sent!", resend available
14
14
  * - `resent` "Link sent!" with the "resent" snackbar
15
15
  * - `tooMany` "Too many attempts"
16
+ * - `invalidUuid` the link's UUID was rejected (expired or unknown): no leg
17
+ * to offer, only the error. One message for Flows and
18
+ * Workflows alike, since the backend answers the same way for
19
+ * both (Elena, 2026-10-21: web SDK 2.0 showed "QR code
20
+ * expired" for Flows and "Session expired" with 2.0 off).
16
21
  */
17
- export type DemoScreensRedirectStage = 'qr' | 'sms' | 'smsFilled' | 'sent' | 'resend' | 'resent' | 'tooMany';
22
+ export type DemoScreensRedirectStage = 'qr' | 'sms' | 'smsFilled' | 'sent' | 'resend' | 'resent' | 'tooMany' | 'invalidUuid';
18
23
  export interface DemoScreensRedirectScreenProps extends Omit<PrizmaScreenShellProps, 'dark' | 'title' | 'children'> {
19
24
  /** Family copy — this screen reads the `redirect` slice. */
20
25
  strings: DemoScreensStrings;