@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,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef } from 'react';
4
+ import { PrizmaBottomSheetContent } from '@incodetech/prizma-core-web/bottom-sheet';
5
+ import { PrizmaModalContent } from '@incodetech/prizma-core-web/modal';
6
+ import { PrizmaScreenShell } from './ScreenShell';
7
+ import './ScreenLayout.css';
8
+ import './PermissionsLayout.css';
9
+ /**
10
+ * Permissions โ€” the pre-permission sheet, "Allow camera access".
11
+ *
12
+ * Figma: `1-3-1-id-permission-modal` (qGJjfL4O0JSjz1x4tOjJn8), the first frame
13
+ * of all three full-flow boards (Web 3:620, iOS 3:7788, Android 4:11304). It is
14
+ * the SDK's own sheet, not the browser's prompt: Core's Bottom Sheet `dialog`
15
+ * (handle, title, supporting line) carrying a horizontal Button Group with
16
+ * "Don't allow" (secondary) left of "Allow" (primary), which is
17
+ * `horizontal-v1`'s order. "Allow" is what raises the real OS prompt.
18
+ *
19
+ * On desktop (`presentation="dialog"`) the same ask is Core's Modal `default`
20
+ * variant, centred over the scrim the way NFC's settings dialog is: Core
21
+ * renders the card and its Button Group (its own `vertical-v1` default, Allow
22
+ * over Don't allow), the screen adds only the scrim and the centring. No close
23
+ * icon: the two buttons are the only answers the ask has.
24
+ *
25
+ * Nothing is drawn behind the sheet: every board shows the sheet over an empty
26
+ * frame, so the screen is the scrim alone. Same scrim + panel split as
27
+ * Document Capture's method sheet (#DC-30).
28
+ */
29
+ export const PermissionsAskScreen = forwardRef(({ strings, presentation = 'sheet', screenId = presentation === 'dialog' ? 'permissions-ask-dialog' : 'permissions-ask', onAction, ...shell }, ref) => (_jsxs(PrizmaScreenShell, { ref: ref, "data-screen-id": screenId, "data-pcs-overlay": presentation === 'dialog' ? 'window' : undefined, ...shell, children: [_jsx("div", { className: "permissions-sheet-scrim", "aria-hidden": "true" }), presentation === 'dialog' ? (_jsx("div", { className: "permissions-dialog-panel", children: _jsx(PrizmaModalContent, { title: strings.ask.title, subtitle: strings.ask.subtitle, showCloseIcon: false, buttonGroupProps: {
30
+ primaryLabel: strings.ask.allowLabel,
31
+ secondaryLabel: strings.ask.denyLabel,
32
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
33
+ onSecondaryClick: () => onAction?.({ type: 'secondary', screenId }),
34
+ } }) })) : (_jsx("div", { className: "permissions-sheet-panel", children: _jsx(PrizmaBottomSheetContent, { variant: "dialog", ariaLabel: strings.ask.title, title: strings.ask.title, subtitle: strings.ask.subtitle, buttonGroupProps: {
35
+ orientation: 'horizontal-v1',
36
+ primaryLabel: strings.ask.allowLabel,
37
+ secondaryLabel: strings.ask.denyLabel,
38
+ showTertiary: false,
39
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
40
+ onSecondaryClick: () => onAction?.({ type: 'secondary', screenId }),
41
+ } }) }))] })));
42
+ PermissionsAskScreen.displayName = 'PermissionsAskScreen';
43
+ export default PermissionsAskScreen;
@@ -18,15 +18,22 @@ export interface PermissionsBrowserStepsScreenProps extends Omit<PrizmaListScree
18
18
  *
19
19
  * Figma: `1-4-2-id` of the `Permissions` section, plus the four
20
20
  * `Android browsers` frames of the Web full-flow board (4:8159 โ€” Chrome,
21
- * Firefox, Samsung Internet, Opera). ONE screen for all five: they draw the
21
+ * Firefox, Samsung Internet, Opera). The browser set and every step path are
22
+ * Geolocation's (`GeolocationBrowserStepsScreen`), nine browsers, with the
23
+ * camera in place of the location. ONE screen for all of them: they draw the
22
24
  * identical skeleton โ€” title block โ†’ a step list filling the middle โ†’ the
23
25
  * refresh CTA โ€” and differ only in the rows, because re-enabling the camera is
24
- * a different journey in each browser. Five files would have been five chances
26
+ * a different journey in each browser. Nine files would have been nine chances
25
27
  * to drift.
26
28
  *
27
29
  * The rows are Core's `PrizmaCardListCommonIssues` verbatim; only the leading
28
30
  * glyph is chosen here, from the canonical bank (see `STEP_ICONS`).
29
31
  *
32
+ * No subtitle: none of the frames draws the browser name under the title,
33
+ * and the same call was made for Geolocation's step lists (owner call, Elena
34
+ * 2026-09-30). `label` stays in the strings as the section's identifier for
35
+ * hosts and catalogs; the screen itself shows title + steps.
36
+ *
30
37
  * The title block is LEFT-aligned for this whole family. Every frame starts the
31
38
  * two-line headline flush at the gutter over left-led step rows; a centred
32
39
  * headline above a left-aligned list reads as a mistake. The screen therefore
@@ -15,6 +15,7 @@ import './ScreenLayout.css';
15
15
  * both forbidden (rule 1, GUIDELINES ยง10), so each row takes the nearest
16
16
  * canonical glyph:
17
17
  *
18
+ * addressBar โ†’ IconTextAi the address bar's page / site control
18
19
  * settings โ†’ IconLock site settings sit behind the address-bar lock
19
20
  * browser โ†’ IconTextAi Safari's "AA" is the address-bar text control
20
21
  * menu โ†’ IconNumberedList the โ‹ฎ overflow opens a menu list
@@ -31,6 +32,7 @@ import './ScreenLayout.css';
31
32
  * glyphs or bless the substitutions โ€” one decision covers both families.
32
33
  */
33
34
  const STEP_ICONS = {
35
+ addressBar: _jsx(IconTextAi, { size: "m" }),
34
36
  settings: _jsx(IconLock, { size: "m" }),
35
37
  browser: _jsx(IconTextAi, { size: "m" }),
36
38
  menu: _jsx(IconNumberedList, { size: "m" }),
@@ -47,15 +49,22 @@ const STEP_ICONS = {
47
49
  *
48
50
  * Figma: `1-4-2-id` of the `Permissions` section, plus the four
49
51
  * `Android browsers` frames of the Web full-flow board (4:8159 โ€” Chrome,
50
- * Firefox, Samsung Internet, Opera). ONE screen for all five: they draw the
52
+ * Firefox, Samsung Internet, Opera). The browser set and every step path are
53
+ * Geolocation's (`GeolocationBrowserStepsScreen`), nine browsers, with the
54
+ * camera in place of the location. ONE screen for all of them: they draw the
51
55
  * identical skeleton โ€” title block โ†’ a step list filling the middle โ†’ the
52
56
  * refresh CTA โ€” and differ only in the rows, because re-enabling the camera is
53
- * a different journey in each browser. Five files would have been five chances
57
+ * a different journey in each browser. Nine files would have been nine chances
54
58
  * to drift.
55
59
  *
56
60
  * The rows are Core's `PrizmaCardListCommonIssues` verbatim; only the leading
57
61
  * glyph is chosen here, from the canonical bank (see `STEP_ICONS`).
58
62
  *
63
+ * No subtitle: none of the frames draws the browser name under the title,
64
+ * and the same call was made for Geolocation's step lists (owner call, Elena
65
+ * 2026-09-30). `label` stays in the strings as the section's identifier for
66
+ * hosts and catalogs; the screen itself shows title + steps.
67
+ *
59
68
  * The title block is LEFT-aligned for this whole family. Every frame starts the
60
69
  * two-line headline flush at the gutter over left-led step rows; a centred
61
70
  * headline above a left-aligned list reads as a mistake. The screen therefore
@@ -76,7 +85,7 @@ export const PermissionsBrowserStepsScreen = forwardRef(({ strings, browser = 's
76
85
  ...overrides,
77
86
  align: overrides?.align ?? 'left',
78
87
  };
79
- return (_jsx(PrizmaListScreen, { ref: ref, showHeader: showHeader, title: strings.browserSteps.title, subtitle: slice?.label, ctaLabel: strings.browserSteps.ctaLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: alignedOverrides, onAction: onAction, ...shell, children: _jsx(PrizmaCardListCommonIssues, { items: (slice?.steps ?? []).map((step) => ({
88
+ return (_jsx(PrizmaListScreen, { ref: ref, showHeader: showHeader, title: strings.browserSteps.title, ctaLabel: strings.browserSteps.ctaLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: alignedOverrides, onAction: onAction, ...shell, children: _jsx(PrizmaCardListCommonIssues, { items: (slice?.steps ?? []).map((step) => ({
80
89
  icon: STEP_ICONS[step.kind],
81
90
  title: step.label,
82
91
  showSubtitle: false,
@@ -0,0 +1,47 @@
1
+ /* Permissions โ€” the pre-permission sheet (`PermissionsAskScreen`).
2
+ *
3
+ * Same idiom as Document Capture's method sheet (DEVIATIONS #DC-30): Core's
4
+ * `PrizmaBottomSheetContent` owns the panel (handle, radius, padding, title,
5
+ * button group); the screen owns the scrim, because the safe presenter portals
6
+ * to `document.body` and would escape the 390 ร— 844 frame. The scrim drinks
7
+ * Core's own `Surface/Overlay` token. No animation: a screen is one static
8
+ * spec state. */
9
+ .permissions-sheet-scrim {
10
+ position: absolute;
11
+ inset: 0;
12
+ z-index: 1;
13
+ background: var(--prizma-surface-overlay);
14
+ }
15
+
16
+ .permissions-sheet-panel {
17
+ position: absolute;
18
+ left: 0;
19
+ right: 0;
20
+ bottom: 0;
21
+ z-index: 2;
22
+ }
23
+
24
+ /* The frame's `System UI Bottom Bar` (34) sits INSIDE the sheet. Core reserves
25
+ * it as `env(safe-area-inset-bottom, var(--prizma-sheet-size-safe-area))`, but
26
+ * a browser defines the inset as 0 on a flat screen, so the fallback never
27
+ * applies and the CTAs drop to 12px off the frame's bottom. Screens in this
28
+ * package reserve device chrome as fixed values (`--pcs-status-bar`), so the
29
+ * sheet takes Core's own Sheet/Size/Safe-area token directly here. */
30
+ .permissions-sheet-panel .prizma-bottom-sheet__safe-area {
31
+ height: var(--prizma-sheet-size-safe-area);
32
+ }
33
+
34
+ /* Desktop: the same ask as Core's centred Modal (`presentation="dialog"`).
35
+ * Same split as NFC's settings dialog: Core's `PrizmaModalContent` is the card
36
+ * (width 100% up to its own Modal/Size/Max-width), this only centres it with
37
+ * the Spacing/12 gutter. */
38
+ .permissions-dialog-panel {
39
+ position: absolute;
40
+ inset: 0;
41
+ z-index: 2;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ padding-inline: var(--prizma-spacing-12);
46
+ box-sizing: border-box;
47
+ }
@@ -11,13 +11,14 @@ export interface PermissionsReasonRow {
11
11
  description?: string;
12
12
  }
13
13
  /**
14
- * Which browser's "turn it back on" journey to show. `safariIos` is the base
15
- * `1-4-2-id` frame; the other four are the `Android browsers` section of the
16
- * Web flow (4:8159). Re-enabling a permission is a different journey in every
17
- * browser, which is why one screen carries five step lists rather than five
18
- * screens carrying one skeleton.
14
+ * Which browser's "turn it back on" journey to show. The SAME nine browsers,
15
+ * keys and order as Geolocation's (`GeolocationBrowser`), because a refused
16
+ * camera and a refused location are the same journey through the same browser
17
+ * settings; only the permission's name changes (Camila, 2026-10-07). The
18
+ * Permissions Figma draws five of them (`1-4-2-id` for Safari plus the four
19
+ * `Android browsers` frames, 4:8159); the other four follow Geolocation.
19
20
  */
20
- export type PermissionsBrowser = 'safariIos' | 'chromeAndroid' | 'firefoxAndroid' | 'samsungAndroid' | 'operaAndroid';
21
+ export type PermissionsBrowser = 'safariIos' | 'chromeIos' | 'firefoxIos' | 'operaIos' | 'chromeAndroid' | 'firefoxAndroid' | 'operaAndroid' | 'samsungAndroid' | 'unrecognizedAndroid' | 'chromeDesktop' | 'edgeDesktop' | 'firefoxDesktop' | 'safariMac';
21
22
  /**
22
23
  * Step kind โ†’ canonical bank glyph, resolved in
23
24
  * `PermissionsBrowserStepsScreen`. Figma leads every row with OS/browser chrome
@@ -28,14 +29,14 @@ export type PermissionsBrowser = 'safariIos' | 'chromeAndroid' | 'firefoxAndroid
28
29
  * escalated (`GeolocationBrowserStepsScreen`); this family reuses it rather
29
30
  * than inventing a second one.
30
31
  */
31
- export type PermissionsStepKind = 'settings' | 'browser' | 'menu' | 'permissions' | 'camera' | 'toggle' | 'device' | 'apps' | 'return';
32
+ export type PermissionsStepKind = 'addressBar' | 'settings' | 'browser' | 'menu' | 'permissions' | 'camera' | 'toggle' | 'device' | 'apps' | 'return';
32
33
  export interface PermissionsStep {
33
34
  kind: PermissionsStepKind;
34
35
  label: string;
35
36
  }
36
37
  export interface PermissionsBrowserSteps {
37
38
  /** Names the browser under the headline. Figma labels the section, not the
38
- * screen; the label keeps five otherwise near-identical cards apart. */
39
+ * screen; the label keeps nine otherwise near-identical cards apart. */
39
40
  label: string;
40
41
  steps: PermissionsStep[];
41
42
  }
@@ -44,6 +45,16 @@ export interface PermissionsStrings {
44
45
  navLabel?: string;
45
46
  /** "verified by" attribution copy for the footer tag. */
46
47
  verifiedLabel: string;
48
+ /** `1-3-1-id-permission-modal` โ€” the pre-permission sheet, the very first
49
+ * ask. Its "Allow" is what raises the real OS prompt. */
50
+ ask: {
51
+ title: string;
52
+ subtitle?: string;
53
+ /** Primary โ€” raises the OS prompt. */
54
+ allowLabel: string;
55
+ /** Secondary โ€” declines; the flow moves on to `required`. */
56
+ denyLabel: string;
57
+ };
47
58
  /** `1-3-2-id` โ€” the permission was refused and the flow explains why it needs it. */
48
59
  required: {
49
60
  title: string;
@@ -54,7 +65,7 @@ export interface PermissionsStrings {
54
65
  quitLabel: string;
55
66
  ctaLabel: string;
56
67
  };
57
- /** `1-3-2-id-allow-camera` โ€” the first ask, before any refusal. */
68
+ /** `1-3-2-id-allow-camera` โ€” the softer ask after the REAL OS prompt was refused. */
58
69
  allow: {
59
70
  title: string;
60
71
  subtitle?: string;
@@ -12,20 +12,26 @@
12
12
  // is the ID Capture-flavoured copy of the same skeleton; it stays where it is so
13
13
  // nothing that imports it breaks (DEVIATIONS #IDC-26).
14
14
  //
15
- // The section's seven frames collapse onto THREE built screens:
15
+ // The section's seven frames collapse onto FOUR built screens:
16
16
  //
17
+ // 1-3-1-id-permission-modal โ†’ PermissionsAskScreen (the sheet)
17
18
  // 1-3-2-id โ†’ PermissionsRequiredScreen (reason rows)
18
19
  // 1-3-2-id-allow-camera โ†’ PermissionsAllowScreen (subtitle)
19
20
  // 1-4-2-id (+ 4 Android siblings) โ†’ PermissionsBrowserStepsScreen
20
21
  //
21
- // The other four are OS chrome and are NOT built:
22
+ // The other three are OS chrome and are NOT built:
22
23
  //
23
- // 1-3-1-id-permission-modal the browser's own "Allow camera access" sheet
24
24
  // 1-4-1-id the iOS "โ€ฆWould Like to Access the Camera" alert
25
25
  // 1-3-2-id-quit-process an iOS confirm alert layered over 1-3-2-id
26
26
  // 1-3-2-id-allow-camera-quit the same alert layered over the allow screen
27
27
  //
28
- // None of them is a Prizma surface: their chrome does not exist in Core and
28
+ // `1-3-1-id-permission-modal` was on this list until 2026-10-07. It is not the
29
+ // browser's prompt: it is the SDK's own camera pre-permission sheet (the SDK
30
+ // release notes call it "the camera permission sheet", shown by every module
31
+ // that asks for the camera), drawn with Core's Bottom Sheet dialog and Button
32
+ // Group. So it is built, from those two components (DEVIATIONS #GEN-1).
33
+ //
34
+ // None of the remaining three is a Prizma surface: their chrome does not exist in Core and
29
35
  // rebuilding it would mean inventing primitives and shipping platform rasters,
30
36
  // both forbidden (rule 1, GUIDELINES ยง10). Same call as Document Capture's iOS
31
37
  // action sheets (#DC-20) and Geolocation's system alert. Core Lab draws them as
@@ -21,6 +21,7 @@
21
21
  align-items: flex-start;
22
22
  gap: var(--prizma-spacing-8);
23
23
  width: 100%;
24
+ position: relative; /* the country menu's containing block โ€” see below */
24
25
  }
25
26
 
26
27
  /* The dial-code dropdown hugs its content ("๐Ÿ‡ท๐Ÿ‡ธ +381"), as Figma draws it.
@@ -47,15 +48,15 @@
47
48
  min-width: 0;
48
49
  }
49
50
 
50
- /* The open menu must NOT inherit the hugged anchor's width: Core anchors the
51
- * menu to the trigger, so once the trigger hugs "๐Ÿ‡ท๐Ÿ‡ธ +381" the option rows
52
- * truncate ("+โ€ฆ"). The menu keeps its own readable width โ€” it lists country
53
- * names and dial codes, which the closed field never shows. Sized to the
54
- * content and capped at the 342 column so it never escapes the screen.
51
+ /* The open menu spans the WHOLE row (dial code + number field), as Figma's
52
+ * `Input / Combined` draws it: Core anchors the menu overlay to the trigger's
53
+ * `__anchor` (`position: relative`, Dropdown.css), so once the trigger hugs
54
+ * "๐Ÿ‡ท๐Ÿ‡ธ +381" the menu came out as narrow as the dial code. Releasing the
55
+ * anchor's positioning hands the overlay's `left: 0; right: 0` to the row
56
+ * above (`position: relative`), so the menu matches the row's width exactly.
57
+ * Core's own overlay offset (`top: 100% + Spacing/8`) is left untouched.
55
58
  * DEVIATIONS #PI-1 (same root cause as the hug).
56
59
  */
57
- .phone-input-combined__country .prizma-dropdown__menu {
58
- width: max-content;
59
- min-width: 100%;
60
- max-width: 342px;
60
+ .phone-input-combined__country .prizma-dropdown__anchor {
61
+ position: static;
61
62
  }
@@ -13,6 +13,14 @@ export interface PhoneInputOtpScreenProps extends Omit<PrizmaFormScreenProps, 't
13
13
  value?: string;
14
14
  /** How many digits the code carries. Default 6. */
15
15
  length?: number;
16
+ /**
17
+ * Seconds left before the code can be resent. While it is above 0 (and the
18
+ * copy carries `otp.resendCountdownLabel`), the resend line becomes one
19
+ * disabled link, "Resend code in 30s", exactly as Figma draws
20
+ * `phoneNumber.OTP.typing`. The host owns the timer; the screen only draws
21
+ * the number it is given.
22
+ */
23
+ resendIn?: number;
16
24
  screenId?: string;
17
25
  overrides?: PrizmaScreenOverrides;
18
26
  onAction?: PrizmaScreenActionHandler;
@@ -21,24 +21,31 @@ import './PhoneInputLayout.css';
21
21
  * wins; `PrizmaOtp.loading` stays correct for hosts that want the in-field
22
22
  * spinner. DEVIATIONS #PI-5.
23
23
  */
24
- export const PhoneInputOtpScreen = forwardRef(({ strings, state = 'typing', value, length = 6, screenId = `phone-input-otp-${state}`, overrides, onAction, ...shell }, ref) => (_jsx(PrizmaFormScreen, { ref: ref, title: strings.otp.title, subtitle: strings.otp.subtitle, ctaLabel: strings.otp.ctaLabel,
25
- /* Figma greys the CTA until the code is complete. */
26
- ctaDisabled: state === 'typing',
27
- /* The spinner rides the CTA, not the field (see the note above). */
28
- ctaLoading: state === 'processing', ctaLoadingLabel: strings.otp.ctaLoadingLabel,
29
- /* "Didn't receive the code? Resend code or Change phone number" โ€” drawn
30
- under the CTA on every OTP section. ONE sentence, TWO independently
31
- actionable links, so it uses the archetype's composed-line slot rather
32
- than `linkLabel` (a full-width 56px button cannot sit inside a line of
33
- body text, and one button could not tell the two actions apart โ€”
34
- DEVIATIONS #PI-10). */
35
- linkContent: strings.otp.resendPrompt ? (_jsxs("p", { className: "prizma-screen-layout__inline-prompt", children: [strings.otp.resendPrompt, ' ', strings.otp.resendLabel ? (_jsx("button", { type: "button", className: "prizma-screen-layout__inline-link", onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.otp.resendLabel })) : null, strings.otp.changeNumberLabel ? (_jsxs(_Fragment, { children: [' ', strings.otp.resendSeparator, ' ', _jsx("button", { type: "button", className: "prizma-screen-layout__inline-link", onClick: () => onAction?.({ type: 'tertiary', screenId }), children: strings.otp.changeNumberLabel })] })) : null] })) : undefined, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaOtp, { length: length,
36
- /* Uncontrolled, for the same reason as the phone field: `readOnly`
37
- would paint Core's DISABLED surface on the resting states, which is
38
- grey Figma does not draw there (DEVIATIONS #PI-6). */
39
- defaultValue: value ?? '',
40
- /* `processing` is the one section Figma greys โ€” the code stays legible
41
- while the check runs. */
42
- disabled: state === 'processing', error: state === 'error', errorText: state === 'error' ? strings.otp.errorText : undefined }) })));
24
+ export const PhoneInputOtpScreen = forwardRef(({ strings, state = 'typing', value, length = 6, resendIn, screenId = `phone-input-otp-${state}`, overrides, onAction, ...shell }, ref) => {
25
+ const counting = (resendIn ?? 0) > 0 && Boolean(strings.otp.resendCountdownLabel);
26
+ return (_jsx(PrizmaFormScreen, { ref: ref, title: strings.otp.title, subtitle: strings.otp.subtitle, ctaLabel: strings.otp.ctaLabel,
27
+ /* Figma greys the CTA until the code is complete. */
28
+ ctaDisabled: state === 'typing',
29
+ /* The spinner rides the CTA, not the field (see the note above). */
30
+ ctaLoading: state === 'processing', ctaLoadingLabel: strings.otp.ctaLoadingLabel,
31
+ /* "Didn't receive the code? Resend code or Change phone number" โ€” drawn
32
+ under the CTA on every OTP section. ONE sentence, TWO independently
33
+ actionable links, so it uses the archetype's composed-line slot rather
34
+ than `linkLabel` (a full-width 56px button cannot sit inside a line of
35
+ body text, and one button could not tell the two actions apart โ€”
36
+ DEVIATIONS #PI-10). */
37
+ /* While the resend timer runs, the line is ONE disabled link (Figma
38
+ `OTP.typing`: Tertiary / Link ยท Disabled), not the composed sentence. */
39
+ linkLabel: counting
40
+ ? strings.otp.resendCountdownLabel.replace('{{time}}', String(resendIn))
41
+ : undefined, linkDisabled: counting, linkContent: !counting && strings.otp.resendPrompt ? (_jsxs("p", { className: "prizma-screen-layout__inline-prompt", children: [strings.otp.resendPrompt, ' ', strings.otp.resendLabel ? (_jsx("button", { type: "button", className: "prizma-screen-layout__inline-link", onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.otp.resendLabel })) : null, strings.otp.changeNumberLabel ? (_jsxs(_Fragment, { children: [' ', strings.otp.resendSeparator, ' ', _jsx("button", { type: "button", className: "prizma-screen-layout__inline-link", onClick: () => onAction?.({ type: 'tertiary', screenId }), children: strings.otp.changeNumberLabel })] })) : null] })) : undefined, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaOtp, { length: length,
42
+ /* Uncontrolled, for the same reason as the phone field: `readOnly`
43
+ would paint Core's DISABLED surface on the resting states, which is
44
+ grey Figma does not draw there (DEVIATIONS #PI-6). */
45
+ defaultValue: value ?? '',
46
+ /* `processing` is the one section Figma greys โ€” the code stays legible
47
+ while the check runs. */
48
+ disabled: state === 'processing', error: state === 'error', errorText: state === 'error' ? strings.otp.errorText : undefined }) }));
49
+ });
43
50
  PhoneInputOtpScreen.displayName = 'PhoneInputOtpScreen';
44
51
  export default PhoneInputOtpScreen;
@@ -7,8 +7,12 @@
7
7
  * - `typing` โ€” partial value, focused field
8
8
  * - `filled` โ€” complete value
9
9
  * - `loading` โ€” CTA in its loading treatment while the code is requested
10
+ * - `emptyError` โ€” "Continue" pressed with no number typed: the phone field
11
+ * stays empty (no placeholder) in Core's error state with `requiredError`,
12
+ * and the CTA is disabled. The dial code always carries a selection, so it
13
+ * takes no error. GUIDELINES ยง6-E empty-submit rule (Elena, 2026-09-30).
10
14
  */
11
- export type PhoneInputVerifyState = 'default' | 'countryCode' | 'typing' | 'filled' | 'loading';
15
+ export type PhoneInputVerifyState = 'default' | 'countryCode' | 'typing' | 'filled' | 'loading' | 'emptyError';
12
16
  /**
13
17
  * The `phoneNumber.OTP.*` sections โ€” same layout, different field state:
14
18
  * - `typing` โ€” partial code
@@ -24,8 +28,9 @@ export type PhoneInputOtpState = 'typing' | 'filled' | 'processing' | 'error';
24
28
  */
25
29
  export type PhoneInputErrorVariant = 'tryAgain' | 'unknown' | 'noNetwork';
26
30
  /** One selectable country in the dial-code dropdown. Runtime data, not copy โ€”
27
- * the host supplies its own list; `flag` is the emoji Figma draws (Core has no
28
- * country-flag primitive โ€” DEVIATIONS #PI-3). */
31
+ * the host supplies its own list. The screen draws the flag from Core's
32
+ * raster bank by `value` (`PrizmaCountryFlag`, DEVIATIONS #PI-3); `flag` is
33
+ * only the emoji fallback for a country outside the bank. */
29
34
  export interface PhoneInputCountry {
30
35
  /** Stable value, e.g. an ISO 3166-1 alpha-2 code. */
31
36
  value: string;
@@ -33,7 +38,7 @@ export interface PhoneInputCountry {
33
38
  dialCode: string;
34
39
  /** Country name for the open menu row. */
35
40
  label: string;
36
- /** Emoji flag, e.g. `๐Ÿ‡ท๐Ÿ‡ธ`. */
41
+ /** Emoji fallback, e.g. `๐Ÿ‡ท๐Ÿ‡ธ`, drawn only when the bank has no raster. */
37
42
  flag?: string;
38
43
  }
39
44
  export interface PhoneInputStrings {
@@ -41,6 +46,12 @@ export interface PhoneInputStrings {
41
46
  verifiedLabel: string;
42
47
  /** Accessible name for the logo nav row. */
43
48
  navLabel?: string;
49
+ /**
50
+ * Error row under the required field when the primary CTA is pressed with it
51
+ * still empty (the verify screen's `emptyError` state): "This field is
52
+ * required". SDK key `dynamicForms.fieldRequired`.
53
+ */
54
+ requiredError: string;
44
55
  /** phoneNumber.verify.* */
45
56
  verify: {
46
57
  /** H2 ("Verify your phone number"). */
@@ -92,6 +103,13 @@ export interface PhoneInputStrings {
92
103
  resendSeparator?: string;
93
104
  /** Second link โ€” returns to phone entry. Emits `tertiary`. */
94
105
  changeNumberLabel?: string;
106
+ /**
107
+ * While the resend timer runs, the resend line is replaced by ONE disabled
108
+ * link: "Resend code in {{time}}s" (Figma `phoneNumber.OTP.typing`). The
109
+ * `{{time}}` placeholder is the SDK's own (`otp.resendCountdown`) and is
110
+ * filled with the screen's `resendIn` seconds.
111
+ */
112
+ resendCountdownLabel?: string;
95
113
  /** Primary CTA. */
96
114
  ctaLabel: string;
97
115
  /** Accessible / visible busy copy while the code is checked (`processing`). */
@@ -36,6 +36,14 @@ export interface PhoneInputVerifyScreenProps extends Omit<PrizmaFormScreenProps,
36
36
  * primitives (`PrizmaDropdown`, `PrizmaTextField`) at the designed 56px min
37
37
  * height and `Border/Radius/small`, so the module layer contributes only the
38
38
  * row that pairs them (`PhoneInputLayout.css`) โ€” no primitive is re-implemented.
39
+ *
40
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
41
+ * GUIDELINES ยง6-E empty-submit rule): "Continue" was pressed with no number
42
+ * typed. The phone field renders empty with no placeholder, in Core's error
43
+ * state with `requiredError`, and the primary CTA is disabled, following the
44
+ * drawn precedent in `FormsFieldScreen`. The dial code always carries a
45
+ * selection, so it takes no error. The resting `default` keeps the CTA enabled
46
+ * so the user can reach this state.
39
47
  */
40
48
  export declare const PhoneInputVerifyScreen: React.ForwardRefExoticComponent<PhoneInputVerifyScreenProps & React.RefAttributes<HTMLDivElement>>;
41
49
  export default PhoneInputVerifyScreen;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef } from 'react';
4
4
  import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
5
5
  import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
6
+ import { PrizmaCountryFlag } from '@incodetech/prizma-core-web/combined';
6
7
  import { PrizmaFormScreen } from './FormScreen';
7
8
  import './ScreenLayout.css';
8
9
  import './PhoneInputLayout.css';
@@ -29,14 +30,27 @@ const DEFAULT_COUNTRIES = [
29
30
  * primitives (`PrizmaDropdown`, `PrizmaTextField`) at the designed 56px min
30
31
  * height and `Border/Radius/small`, so the module layer contributes only the
31
32
  * row that pairs them (`PhoneInputLayout.css`) โ€” no primitive is re-implemented.
33
+ *
34
+ * `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
35
+ * GUIDELINES ยง6-E empty-submit rule): "Continue" was pressed with no number
36
+ * typed. The phone field renders empty with no placeholder, in Core's error
37
+ * state with `requiredError`, and the primary CTA is disabled, following the
38
+ * drawn precedent in `FormsFieldScreen`. The dial code always carries a
39
+ * selection, so it takes no error. The resting `default` keeps the CTA enabled
40
+ * so the user can reach this state.
32
41
  */
33
42
  export const PhoneInputVerifyScreen = forwardRef(({ strings, state = 'default', countries = DEFAULT_COUNTRIES, country, value, screenId = `phone-input-verify-${state}`, overrides, onAction, ...shell }, ref) => {
34
43
  const selected = country ?? countries[0]?.value;
35
44
  const options = countries.map((c) => ({
36
45
  value: c.value,
37
- // The closed field shows flag + dial code exactly as Figma draws it
38
- // ("๐Ÿ‡ท๐Ÿ‡ธ +381"); the open menu row adds the country name.
39
- label: [c.flag, c.dialCode].filter(Boolean).join(' '),
46
+ // The open menu row reads flag + dial code + country name
47
+ // ("+381 Serbia" after the flag); the closed field keeps only flag +
48
+ // dial code ("+381") through Core's `fieldLabel`, as Figma draws it.
49
+ // The flag is not text: it rides the leading slot below (DEVIATIONS
50
+ // #PI-3), so it is Core's raster on every OS instead of an emoji that
51
+ // Windows draws as the letters "RS".
52
+ label: [c.dialCode, c.label].join(' '),
53
+ fieldLabel: c.dialCode,
40
54
  }));
41
55
  // Figma treats the states as three distinct field treatments, not one:
42
56
  // `loading` greys BOTH fields (the request is in flight and the form is
@@ -47,11 +61,26 @@ export const PhoneInputVerifyScreen = forwardRef(({ strings, state = 'default',
47
61
  // for none of them โ€” it would grey the resting states too (DEVIATIONS #PI-6).
48
62
  const busy = state === 'loading';
49
63
  const focused = state === 'typing';
64
+ // Empty submit: the number field drops its placeholder and takes Core's
65
+ // error state with the shared "required" row; the CTA greys.
66
+ const emptyError = state === 'emptyError';
50
67
  return (_jsx(PrizmaFormScreen, { ref: ref, title: strings.verify.title, subtitle: strings.verify.subtitle, ctaLabel: strings.verify.ctaLabel,
51
68
  /* Figma's `loading` section swaps the CTA label for Core's spinner. */
52
- ctaLoading: busy, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsxs("div", { className: "phone-input-combined", children: [_jsx(PrizmaDropdown, { className: "phone-input-combined__country", "aria-label": strings.verify.countryLabel, options: options, value: selected, searchable: true, searchPlaceholder: strings.verify.countrySearchPlaceholder,
69
+ ctaLoading: busy, ctaDisabled: emptyError, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsxs("div", { className: "phone-input-combined", children: [_jsx(PrizmaDropdown, { className: "phone-input-combined__country", "aria-label": strings.verify.countryLabel, options: options, value: selected, searchable: true, searchPlaceholder: strings.verify.countrySearchPlaceholder,
53
70
  /* The `countryCode` section IS the open-menu spec state. */
54
- forceOpen: state === 'countryCode', disabled: busy }), _jsx(PrizmaTextField, { className: [
71
+ forceOpen: state === 'countryCode',
72
+ /* Core's canonical flag raster in the closed field and every menu
73
+ row, the WatchlistBusinessFormScreen precedent. A country
74
+ outside the bank falls back to its `flag` emoji. */
75
+ renderLeading: (option) => {
76
+ const match = countries.find((c) => c.value === option.value);
77
+ return match ? (_jsx(PrizmaCountryFlag, { country: {
78
+ code: match.value,
79
+ name: match.label,
80
+ dialCode: match.dialCode,
81
+ flag: match.flag ?? '',
82
+ } })) : null;
83
+ }, disabled: busy }), _jsx(PrizmaTextField, { className: [
55
84
  'phone-input-combined__number',
56
85
  /* Core's own static-focus modifier โ€” the same mechanism the
57
86
  Dropdown exposes as `forceState`, which `PrizmaTextField` has
@@ -59,11 +88,11 @@ export const PhoneInputVerifyScreen = forwardRef(({ strings, state = 'default',
59
88
  focused ? 'prizma-input-textfield--focused' : '',
60
89
  ]
61
90
  .filter(Boolean)
62
- .join(' '), type: "tel", inputMode: "tel", "aria-label": strings.verify.fieldLabel, placeholder: strings.verify.placeholder,
91
+ .join(' '), type: "tel", inputMode: "tel", "aria-label": strings.verify.fieldLabel, placeholder: emptyError ? undefined : strings.verify.placeholder,
63
92
  /* Uncontrolled: a screen is one static spec state, and `value`
64
93
  without `onChange` would either warn or force `readOnly`, whose
65
94
  disabled surface is exactly the grey Figma does NOT draw here. */
66
- defaultValue: value ?? '', disabled: busy,
95
+ defaultValue: emptyError ? '' : value ?? '', disabled: busy, error: emptyError, errorText: emptyError ? strings.requiredError : undefined,
67
96
  /* Figma draws the clear โœ• only while the field is focused. */
68
97
  clearable: focused })] }) }));
69
98
  });
@@ -62,6 +62,7 @@ export const QualifiedSignatureAcceptScreen = forwardRef(({ strings, variant = '
62
62
  // `loading` makes the whole form inert.
63
63
  const busy = state === 'loading';
64
64
  const ticked = state !== 'default';
65
+ const requiredHint = strings.accept.requiredHint;
65
66
  return (_jsxs(PrizmaFormScreen, { ref: ref, className: ['qualified-signature-accept', className].filter(Boolean).join(' '), title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel, ctaDisabled: !ticked, ctaLoading: busy, ctaLoadingLabel: copy.ctaLoadingLabel,
66
67
  /* The clause stack pans inside the column โ€” the archetype owns this now
67
68
  (`--scroll`), so the module declares no overflow of its own. #QS-1 */
@@ -80,7 +81,11 @@ export const QualifiedSignatureAcceptScreen = forwardRef(({ strings, variant = '
80
81
  action outright while the partner's `loading` frame greys it. Two
81
82
  Figma readings of one inert state; Core models inert as disabled,
82
83
  so both take the greyed treatment and the removal is logged. */
83
- _jsx(PrizmaCardDocumentContainer, { filename: filename, actionLabel: strings.accept.viewLabel, actionDisabled: busy, onAction: () => onAction?.({ type: 'tertiary', screenId }) })) : null, _jsx("div", { className: "qualified-signature-terms", role: "group", "aria-label": strings.accept.termsLabel, children: terms.map((term) => (_jsx(PrizmaCheckbox, { size: "L", name: term.id, value: term.id, checked: ticked, disabled: busy, segments: term.segments }, term.id))) })] }));
84
+ _jsx(PrizmaCardDocumentContainer, { filename: filename, actionLabel: strings.accept.viewLabel, actionDisabled: busy, onAction: () => onAction?.({ type: 'tertiary', screenId }) })) : null, _jsxs("div", { className: "qualified-signature-terms", role: "group", "aria-label": strings.accept.termsLabel, children: [requiredHint && strings.accept.requiredLegend ? (_jsx("p", { className: "qualified-signature-terms__legend", children: strings.accept.requiredLegend })) : null, terms.map((term) => (_jsx(PrizmaCheckbox, { size: "L", name: term.id, value: term.id, checked: ticked, disabled: busy, required: Boolean(requiredHint),
85
+ /* The `*` rides the END of the clause copy, the Data Sharing
86
+ Consent idiom โ€” the L card paints no marker of its own beside
87
+ the box (owner, 2026-09-02). */
88
+ segments: requiredHint ? [...term.segments, ` ${requiredHint}`] : term.segments }, term.id)))] })] }));
84
89
  });
85
90
  QualifiedSignatureAcceptScreen.displayName = 'QualifiedSignatureAcceptScreen';
86
91
  export default QualifiedSignatureAcceptScreen;
@@ -35,16 +35,21 @@ export interface QualifiedSignatureDocumentScreenProps extends Omit<PrizmaListSc
35
35
  *
36
36
  * Archetype G is the fit: its `__list` middle is "content filling the space
37
37
  * between the title block and the CTA band", which is exactly what a document
38
- * page is, and both sections carry no CTA at all so the band never renders. The
38
+ * page is, and the band carries only the "Back" secondary (delta 1). The
39
39
  * body is allowed to scroll inside that middle โ€” one module rule RELEASING an
40
40
  * overflow constraint, declaring no spacing or size, the same idiom as CURP's
41
41
  * taller-than-844 form (DEVIATIONS #CURP-1).
42
42
  *
43
- * THREE deltas from the frames, all reported rather than worked around:
44
- * 1. Both sections draw a back chevron at the nav's left (and `terms` replaces
45
- * the logo row with a chevron + heading bar). GUIDELINES ยง3 stands: SDK
46
- * screens carry no dismiss or back affordance โ€” the standing ruling of
47
- * DEVIATIONS #PI-9, which is not re-litigated per module.
43
+ * THREE deltas from the frames:
44
+ * 1. Both sections draw a back chevron at the nav's left. These are viewers
45
+ * the user opened from the accept screen, so they need a way back (owner
46
+ * call 2026-09-29). It ships as a full-width SECONDARY "Back" button in the
47
+ * CTA band instead, emitting `secondary` (owner call 2026-10-08): the person
48
+ * reads and returns to sign, and a real button at the thumb is easier to
49
+ * find than a chevron. "Back" over "Done" because nothing is finished here;
50
+ * the signature still waits on the accept screen. The nav stays logo-only.
51
+ * `terms` replacing the logo row with a chevron + heading bar is still not
52
+ * followed โ€” the title stays the archetype's H2 below the nav.
48
53
  * 2. `viewdoc` draws NO heading. GUIDELINES ยง4 is explicit that every screen has
49
54
  * exactly one H2, and archetype G has no title-less path, so the screen
50
55
  * carries one and the Figma frame is the side to change. (An archetype
@@ -53,10 +58,12 @@ export interface QualifiedSignatureDocumentScreenProps extends Omit<PrizmaListSc
53
58
  * for attribution chrome (#PI-8, #F-30, #F-34), so both variants keep the
54
59
  * header and the footer and nothing shifts between states.
55
60
  *
56
- * `terms` is LEFT-aligned, as Figma draws it: a centred heading over a
57
- * left-aligned wall of legal text reads as a mistake. The alignment rides the
58
- * sanctioned `overrides.align` path (types.ts) โ€” nothing is re-styled here โ€” and
59
- * an explicit host override still wins. The #GEO-2 precedent.
61
+ * Both variants frame the page in the box Electronic Signature's review screen
62
+ * uses, so the title is CENTRED on both, like that screen (owner call
63
+ * 2026-09-29, "take reference from other screens"). Figma's `QSign.T&C` draws
64
+ * the title left over an unframed wall of text; once the text sits in its own
65
+ * left-aligned frame the centred title no longer reads as a mistake. An
66
+ * explicit `overrides.align` from the host still wins.
60
67
  */
61
68
  export declare const QualifiedSignatureDocumentScreen: React.ForwardRefExoticComponent<QualifiedSignatureDocumentScreenProps & React.RefAttributes<HTMLDivElement>>;
62
69
  export default QualifiedSignatureDocumentScreen;