@incodetech/prizma-core-screens 0.2.7 → 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 (168) hide show
  1. package/dist/BiometricConsentScreen.d.ts +95 -0
  2. package/dist/BiometricConsentScreen.js +91 -0
  3. package/dist/BiometricConsentStrings.d.ts +84 -0
  4. package/dist/BiometricConsentStrings.js +31 -0
  5. package/dist/CaptureScreen.d.ts +13 -0
  6. package/dist/CaptureScreen.js +2 -2
  7. package/dist/DemoScreensLayout.css +16 -0
  8. package/dist/DemoScreensRedirectScreen.d.ts +6 -1
  9. package/dist/DemoScreensRedirectScreen.js +3 -2
  10. package/dist/DemoScreensStrings.d.ts +5 -0
  11. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  12. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  13. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  14. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  15. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  16. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  17. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  18. package/dist/DigitalIdUploadStrings.js +19 -0
  19. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  20. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  21. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  22. package/dist/EkybCountryScreen.d.ts +6 -6
  23. package/dist/EkybCountryScreen.js +20 -7
  24. package/dist/EkybStrings.d.ts +6 -1
  25. package/dist/ErrorScreensScreen.d.ts +5 -2
  26. package/dist/ErrorScreensScreen.js +15 -4
  27. package/dist/ErrorScreensStrings.d.ts +17 -2
  28. package/dist/ErrorScreensStrings.js +18 -10
  29. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  30. package/dist/FaceCaptureCapturingScreen.js +62 -2
  31. package/dist/FaceCaptureLayout.css +69 -4
  32. package/dist/FaceCaptureStrings.d.ts +23 -0
  33. package/dist/FormScreen.css +14 -0
  34. package/dist/FormScreen.d.ts +28 -0
  35. package/dist/FormScreen.js +3 -2
  36. package/dist/IdCaptureLayout.css +165 -2
  37. package/dist/IdCaptureStageScreen.d.ts +47 -0
  38. package/dist/IdCaptureStageScreen.js +39 -3
  39. package/dist/IdCaptureStrings.d.ts +19 -0
  40. package/dist/ListScreen.d.ts +19 -0
  41. package/dist/ListScreen.js +7 -2
  42. package/dist/LoaderScreen.d.ts +7 -5
  43. package/dist/LoaderScreen.js +7 -5
  44. package/dist/LoadersStrings.d.ts +18 -5
  45. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  46. package/dist/OcrReviewErrorScreen.js +24 -0
  47. package/dist/OcrReviewLayout.css +24 -0
  48. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  49. package/dist/OcrReviewLoadingScreen.js +19 -0
  50. package/dist/OcrReviewScreen.d.ts +76 -0
  51. package/dist/OcrReviewScreen.js +65 -0
  52. package/dist/OcrReviewStrings.d.ts +47 -0
  53. package/dist/OcrReviewStrings.js +17 -0
  54. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  55. package/dist/OcrReviewSuccessScreen.js +24 -0
  56. package/dist/PermissionsAllowScreen.d.ts +8 -11
  57. package/dist/PermissionsAllowScreen.js +8 -11
  58. package/dist/PermissionsAskScreen.d.ts +41 -0
  59. package/dist/PermissionsAskScreen.js +43 -0
  60. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  61. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  62. package/dist/PermissionsLayout.css +47 -0
  63. package/dist/PermissionsStrings.d.ts +20 -9
  64. package/dist/PermissionsStrings.js +10 -4
  65. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  66. package/dist/PhoneInputOtpScreen.js +26 -19
  67. package/dist/PhoneInputStrings.d.ts +11 -3
  68. package/dist/PhoneInputVerifyScreen.js +21 -5
  69. package/dist/QualifiedSignatureDocumentScreen.d.ts +8 -5
  70. package/dist/QualifiedSignatureDocumentScreen.js +9 -6
  71. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  72. package/dist/QualifiedSignatureResultScreen.js +17 -18
  73. package/dist/QualifiedSignatureStrings.d.ts +11 -4
  74. package/dist/ResultScreen.d.ts +8 -0
  75. package/dist/ResultScreen.js +14 -3
  76. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  77. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  78. package/dist/ReusabilityLayout.css +241 -0
  79. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  80. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  81. package/dist/ReusabilityStrings.d.ts +85 -0
  82. package/dist/ReusabilityStrings.js +37 -0
  83. package/dist/ReviewScreen.d.ts +38 -12
  84. package/dist/ReviewScreen.js +41 -8
  85. package/dist/ScreenLayout.css +382 -6
  86. package/dist/VideoSelfieErrorScreen.js +1 -1
  87. package/dist/VideoSelfieIdCaptureScreen.js +1 -1
  88. package/dist/VideoSelfieLayout.css +304 -2
  89. package/dist/VideoSelfieSelfieCaptureScreen.js +1 -1
  90. package/dist/VideoSelfieSpeakScreen.js +1 -1
  91. package/dist/VideoSelfieStrings.d.ts +4 -1
  92. package/dist/VideoSelfieTurnHeadScreen.js +14 -11
  93. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  94. package/dist/VideoSelfieTutorialScreen.js +10 -1
  95. package/dist/VideoSelfieWaveScreen.js +1 -1
  96. package/dist/ViewfinderScreen.css +36 -0
  97. package/dist/flows.d.ts +8 -0
  98. package/dist/flows.js +17 -0
  99. package/dist/index.d.ts +16 -0
  100. package/dist/index.js +12 -0
  101. package/dist/styles.css +3 -0
  102. package/package.json +68 -3
  103. package/samples/video-selfie-camera-wide.jpg +0 -0
  104. package/samples/video-selfie-room-wide.jpg +0 -0
  105. package/src/BiometricConsentScreen.tsx +192 -0
  106. package/src/BiometricConsentStrings.ts +119 -0
  107. package/src/CaptureScreen.tsx +17 -0
  108. package/src/DemoScreensLayout.css +16 -0
  109. package/src/DemoScreensRedirectScreen.tsx +14 -6
  110. package/src/DemoScreensStrings.ts +5 -0
  111. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  112. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  113. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  114. package/src/DigitalIdUploadStrings.ts +88 -0
  115. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  116. package/src/DocumentCaptureIllustration.tsx +1 -1
  117. package/src/EkybCountryScreen.tsx +23 -6
  118. package/src/EkybStrings.ts +6 -1
  119. package/src/ErrorScreensScreen.tsx +15 -4
  120. package/src/ErrorScreensStrings.ts +37 -12
  121. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  122. package/src/FaceCaptureLayout.css +69 -4
  123. package/src/FaceCaptureStrings.ts +24 -0
  124. package/src/FormScreen.css +14 -0
  125. package/src/FormScreen.tsx +34 -0
  126. package/src/IdCaptureLayout.css +165 -2
  127. package/src/IdCaptureStageScreen.tsx +75 -0
  128. package/src/IdCaptureStrings.ts +19 -0
  129. package/src/ListScreen.tsx +48 -9
  130. package/src/LoaderScreen.tsx +7 -5
  131. package/src/LoadersStrings.ts +18 -5
  132. package/src/OcrReviewErrorScreen.tsx +53 -0
  133. package/src/OcrReviewLayout.css +24 -0
  134. package/src/OcrReviewLoadingScreen.tsx +48 -0
  135. package/src/OcrReviewScreen.tsx +190 -0
  136. package/src/OcrReviewStrings.ts +69 -0
  137. package/src/OcrReviewSuccessScreen.tsx +52 -0
  138. package/src/PermissionsAllowScreen.tsx +9 -10
  139. package/src/PermissionsAskScreen.tsx +105 -0
  140. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  141. package/src/PermissionsLayout.css +47 -0
  142. package/src/PermissionsStrings.ts +42 -12
  143. package/src/PhoneInputOtpScreen.tsx +23 -3
  144. package/src/PhoneInputStrings.ts +11 -3
  145. package/src/PhoneInputVerifyScreen.tsx +24 -4
  146. package/src/QualifiedSignatureDocumentScreen.tsx +9 -7
  147. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  148. package/src/QualifiedSignatureStrings.ts +11 -4
  149. package/src/ResultScreen.tsx +33 -2
  150. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  151. package/src/ReusabilityLayout.css +241 -0
  152. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  153. package/src/ReusabilityStrings.ts +127 -0
  154. package/src/ReviewScreen.tsx +96 -27
  155. package/src/ScreenLayout.css +382 -6
  156. package/src/VideoSelfieErrorScreen.tsx +1 -1
  157. package/src/VideoSelfieIdCaptureScreen.tsx +9 -1
  158. package/src/VideoSelfieLayout.css +304 -2
  159. package/src/VideoSelfieSelfieCaptureScreen.tsx +9 -1
  160. package/src/VideoSelfieSpeakScreen.tsx +9 -1
  161. package/src/VideoSelfieStrings.ts +4 -2
  162. package/src/VideoSelfieTurnHeadScreen.tsx +25 -14
  163. package/src/VideoSelfieTutorialScreen.tsx +11 -0
  164. package/src/VideoSelfieWaveScreen.tsx +9 -1
  165. package/src/ViewfinderScreen.css +36 -0
  166. package/src/flows.ts +25 -0
  167. package/src/index.ts +74 -3
  168. package/src/styles.css +3 -0
@@ -0,0 +1,105 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaBottomSheetContent } from '@incodetech/prizma-core-web/bottom-sheet';
3
+ import { PrizmaModalContent } from '@incodetech/prizma-core-web/modal';
4
+ import { PrizmaScreenShell, type PrizmaScreenShellProps } from './ScreenShell';
5
+ import type { PrizmaScreenActionHandler } from './types';
6
+ import type { PermissionsStrings } from './PermissionsStrings';
7
+ import './ScreenLayout.css';
8
+ import './PermissionsLayout.css';
9
+
10
+ export interface PermissionsAskScreenProps extends Omit<
11
+ PrizmaScreenShellProps,
12
+ 'dark' | 'title' | 'children'
13
+ > {
14
+ /** Family copy — this screen reads the `ask` slice. */
15
+ strings: PermissionsStrings;
16
+ /**
17
+ * How the ask is presented. `sheet` (default) is the phone frame's bottom
18
+ * sheet. `dialog` is Core's centred Modal, for a desktop browser window: a
19
+ * sheet pinned to the bottom of a wide window reads as a phone screen pasted
20
+ * on the page (Camila, 2026-10-07). Same copy, same two choices.
21
+ */
22
+ presentation?: 'sheet' | 'dialog';
23
+ screenId?: string;
24
+ onAction?: PrizmaScreenActionHandler;
25
+ }
26
+
27
+ /**
28
+ * Permissions — the pre-permission sheet, "Allow camera access".
29
+ *
30
+ * Figma: `1-3-1-id-permission-modal` (qGJjfL4O0JSjz1x4tOjJn8), the first frame
31
+ * of all three full-flow boards (Web 3:620, iOS 3:7788, Android 4:11304). It is
32
+ * the SDK's own sheet, not the browser's prompt: Core's Bottom Sheet `dialog`
33
+ * (handle, title, supporting line) carrying a horizontal Button Group with
34
+ * "Don't allow" (secondary) left of "Allow" (primary), which is
35
+ * `horizontal-v1`'s order. "Allow" is what raises the real OS prompt.
36
+ *
37
+ * On desktop (`presentation="dialog"`) the same ask is Core's Modal `default`
38
+ * variant, centred over the scrim the way NFC's settings dialog is: Core
39
+ * renders the card and its Button Group (its own `vertical-v1` default, Allow
40
+ * over Don't allow), the screen adds only the scrim and the centring. No close
41
+ * icon: the two buttons are the only answers the ask has.
42
+ *
43
+ * Nothing is drawn behind the sheet: every board shows the sheet over an empty
44
+ * frame, so the screen is the scrim alone. Same scrim + panel split as
45
+ * Document Capture's method sheet (#DC-30).
46
+ */
47
+ export const PermissionsAskScreen = forwardRef<HTMLDivElement, PermissionsAskScreenProps>(
48
+ (
49
+ {
50
+ strings,
51
+ presentation = 'sheet',
52
+ screenId = presentation === 'dialog' ? 'permissions-ask-dialog' : 'permissions-ask',
53
+ onAction,
54
+ ...shell
55
+ },
56
+ ref,
57
+ ) => (
58
+ <PrizmaScreenShell
59
+ ref={ref}
60
+ data-screen-id={screenId}
61
+ // A dialog dims the whole browser window, not a column of it. Hosts that
62
+ // frame screens (Core Lab's desktop card) read this to let the screen
63
+ // span the window.
64
+ data-pcs-overlay={presentation === 'dialog' ? 'window' : undefined}
65
+ {...shell}
66
+ >
67
+ <div className="permissions-sheet-scrim" aria-hidden="true" />
68
+ {presentation === 'dialog' ? (
69
+ <div className="permissions-dialog-panel">
70
+ <PrizmaModalContent
71
+ title={strings.ask.title}
72
+ subtitle={strings.ask.subtitle}
73
+ showCloseIcon={false}
74
+ buttonGroupProps={{
75
+ primaryLabel: strings.ask.allowLabel,
76
+ secondaryLabel: strings.ask.denyLabel,
77
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
78
+ onSecondaryClick: () => onAction?.({ type: 'secondary', screenId }),
79
+ }}
80
+ />
81
+ </div>
82
+ ) : (
83
+ <div className="permissions-sheet-panel">
84
+ <PrizmaBottomSheetContent
85
+ variant="dialog"
86
+ ariaLabel={strings.ask.title}
87
+ title={strings.ask.title}
88
+ subtitle={strings.ask.subtitle}
89
+ buttonGroupProps={{
90
+ orientation: 'horizontal-v1',
91
+ primaryLabel: strings.ask.allowLabel,
92
+ secondaryLabel: strings.ask.denyLabel,
93
+ showTertiary: false,
94
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
95
+ onSecondaryClick: () => onAction?.({ type: 'secondary', screenId }),
96
+ }}
97
+ />
98
+ </div>
99
+ )}
100
+ </PrizmaScreenShell>
101
+ ),
102
+ );
103
+ PermissionsAskScreen.displayName = 'PermissionsAskScreen';
104
+
105
+ export default PermissionsAskScreen;
@@ -29,6 +29,7 @@ import './ScreenLayout.css';
29
29
  * both forbidden (rule 1, GUIDELINES §10), so each row takes the nearest
30
30
  * canonical glyph:
31
31
  *
32
+ * addressBar → IconTextAi the address bar's page / site control
32
33
  * settings → IconLock site settings sit behind the address-bar lock
33
34
  * browser → IconTextAi Safari's "AA" is the address-bar text control
34
35
  * menu → IconNumberedList the ⋮ overflow opens a menu list
@@ -45,6 +46,7 @@ import './ScreenLayout.css';
45
46
  * glyphs or bless the substitutions — one decision covers both families.
46
47
  */
47
48
  const STEP_ICONS: Record<PermissionsStepKind, ReactNode> = {
49
+ addressBar: <IconTextAi size="m" />,
48
50
  settings: <IconLock size="m" />,
49
51
  browser: <IconTextAi size="m" />,
50
52
  menu: <IconNumberedList size="m" />,
@@ -83,15 +85,22 @@ export interface PermissionsBrowserStepsScreenProps
83
85
  *
84
86
  * Figma: `1-4-2-id` of the `Permissions` section, plus the four
85
87
  * `Android browsers` frames of the Web full-flow board (4:8159 — Chrome,
86
- * Firefox, Samsung Internet, Opera). ONE screen for all five: they draw the
88
+ * Firefox, Samsung Internet, Opera). The browser set and every step path are
89
+ * Geolocation's (`GeolocationBrowserStepsScreen`), nine browsers, with the
90
+ * camera in place of the location. ONE screen for all of them: they draw the
87
91
  * identical skeleton — title block → a step list filling the middle → the
88
92
  * refresh CTA — and differ only in the rows, because re-enabling the camera is
89
- * a different journey in each browser. Five files would have been five chances
93
+ * a different journey in each browser. Nine files would have been nine chances
90
94
  * to drift.
91
95
  *
92
96
  * The rows are Core's `PrizmaCardListCommonIssues` verbatim; only the leading
93
97
  * glyph is chosen here, from the canonical bank (see `STEP_ICONS`).
94
98
  *
99
+ * No subtitle: none of the frames draws the browser name under the title,
100
+ * and the same call was made for Geolocation's step lists (owner call, Elena
101
+ * 2026-09-30). `label` stays in the strings as the section's identifier for
102
+ * hosts and catalogs; the screen itself shows title + steps.
103
+ *
95
104
  * The title block is LEFT-aligned for this whole family. Every frame starts the
96
105
  * two-line headline flush at the gutter over left-led step rows; a centred
97
106
  * headline above a left-aligned list reads as a mistake. The screen therefore
@@ -133,7 +142,6 @@ export const PermissionsBrowserStepsScreen = forwardRef<
133
142
  ref={ref}
134
143
  showHeader={showHeader}
135
144
  title={strings.browserSteps.title}
136
- subtitle={slice?.label}
137
145
  ctaLabel={strings.browserSteps.ctaLabel}
138
146
  verifiedLabel={strings.verifiedLabel}
139
147
  navLabel={strings.navLabel}
@@ -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
+ }
@@ -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
@@ -49,18 +55,30 @@ export interface PermissionsReasonRow {
49
55
  }
50
56
 
51
57
  /**
52
- * Which browser's "turn it back on" journey to show. `safariIos` is the base
53
- * `1-4-2-id` frame; the other four are the `Android browsers` section of the
54
- * Web flow (4:8159). Re-enabling a permission is a different journey in every
55
- * browser, which is why one screen carries five step lists rather than five
56
- * screens carrying one skeleton.
58
+ * Which browser's "turn it back on" journey to show. The SAME nine browsers,
59
+ * keys and order as Geolocation's (`GeolocationBrowser`), because a refused
60
+ * camera and a refused location are the same journey through the same browser
61
+ * settings; only the permission's name changes (Camila, 2026-10-07). The
62
+ * Permissions Figma draws five of them (`1-4-2-id` for Safari plus the four
63
+ * `Android browsers` frames, 4:8159); the other four follow Geolocation.
57
64
  */
58
65
  export type PermissionsBrowser =
59
66
  | 'safariIos'
67
+ | 'chromeIos'
68
+ | 'firefoxIos'
69
+ | 'operaIos'
60
70
  | 'chromeAndroid'
61
71
  | 'firefoxAndroid'
72
+ | 'operaAndroid'
62
73
  | 'samsungAndroid'
63
- | 'operaAndroid';
74
+ | 'unrecognizedAndroid'
75
+ // Desktop browsers. Not in Geolocation's set: a phone's lists send the user
76
+ // to the Settings app on an iPhone, which means nothing on a computer, so the
77
+ // desktop flow carries its own four (2026-10-07).
78
+ | 'chromeDesktop'
79
+ | 'edgeDesktop'
80
+ | 'firefoxDesktop'
81
+ | 'safariMac';
64
82
 
65
83
  /**
66
84
  * Step kind → canonical bank glyph, resolved in
@@ -73,6 +91,7 @@ export type PermissionsBrowser =
73
91
  * than inventing a second one.
74
92
  */
75
93
  export type PermissionsStepKind =
94
+ | 'addressBar'
76
95
  | 'settings'
77
96
  | 'browser'
78
97
  | 'menu'
@@ -90,7 +109,7 @@ export interface PermissionsStep {
90
109
 
91
110
  export interface PermissionsBrowserSteps {
92
111
  /** Names the browser under the headline. Figma labels the section, not the
93
- * screen; the label keeps five otherwise near-identical cards apart. */
112
+ * screen; the label keeps nine otherwise near-identical cards apart. */
94
113
  label: string;
95
114
  steps: PermissionsStep[];
96
115
  }
@@ -101,6 +120,17 @@ export interface PermissionsStrings {
101
120
  /** "verified by" attribution copy for the footer tag. */
102
121
  verifiedLabel: string;
103
122
 
123
+ /** `1-3-1-id-permission-modal` — the pre-permission sheet, the very first
124
+ * ask. Its "Allow" is what raises the real OS prompt. */
125
+ ask: {
126
+ title: string;
127
+ subtitle?: string;
128
+ /** Primary — raises the OS prompt. */
129
+ allowLabel: string;
130
+ /** Secondary — declines; the flow moves on to `required`. */
131
+ denyLabel: string;
132
+ };
133
+
104
134
  /** `1-3-2-id` — the permission was refused and the flow explains why it needs it. */
105
135
  required: {
106
136
  title: string;
@@ -112,7 +142,7 @@ export interface PermissionsStrings {
112
142
  ctaLabel: string;
113
143
  };
114
144
 
115
- /** `1-3-2-id-allow-camera` — the first ask, before any refusal. */
145
+ /** `1-3-2-id-allow-camera` — the softer ask after the REAL OS prompt was refused. */
116
146
  allow: {
117
147
  title: string;
118
148
  subtitle?: string;
@@ -19,6 +19,14 @@ export interface PhoneInputOtpScreenProps
19
19
  value?: string;
20
20
  /** How many digits the code carries. Default 6. */
21
21
  length?: number;
22
+ /**
23
+ * Seconds left before the code can be resent. While it is above 0 (and the
24
+ * copy carries `otp.resendCountdownLabel`), the resend line becomes one
25
+ * disabled link, "Resend code in 30s", exactly as Figma draws
26
+ * `phoneNumber.OTP.typing`. The host owns the timer; the screen only draws
27
+ * the number it is given.
28
+ */
29
+ resendIn?: number;
22
30
  screenId?: string;
23
31
  overrides?: PrizmaScreenOverrides;
24
32
  onAction?: PrizmaScreenActionHandler;
@@ -47,13 +55,16 @@ export const PhoneInputOtpScreen = forwardRef<HTMLDivElement, PhoneInputOtpScree
47
55
  state = 'typing',
48
56
  value,
49
57
  length = 6,
58
+ resendIn,
50
59
  screenId = `phone-input-otp-${state}`,
51
60
  overrides,
52
61
  onAction,
53
62
  ...shell
54
63
  },
55
64
  ref,
56
- ) => (
65
+ ) => {
66
+ const counting = (resendIn ?? 0) > 0 && Boolean(strings.otp.resendCountdownLabel);
67
+ return (
57
68
  <PrizmaFormScreen
58
69
  ref={ref}
59
70
  title={strings.otp.title}
@@ -70,8 +81,16 @@ export const PhoneInputOtpScreen = forwardRef<HTMLDivElement, PhoneInputOtpScree
70
81
  than `linkLabel` (a full-width 56px button cannot sit inside a line of
71
82
  body text, and one button could not tell the two actions apart —
72
83
  DEVIATIONS #PI-10). */
84
+ /* While the resend timer runs, the line is ONE disabled link (Figma
85
+ `OTP.typing`: Tertiary / Link · Disabled), not the composed sentence. */
86
+ linkLabel={
87
+ counting
88
+ ? strings.otp.resendCountdownLabel!.replace('{{time}}', String(resendIn))
89
+ : undefined
90
+ }
91
+ linkDisabled={counting}
73
92
  linkContent={
74
- strings.otp.resendPrompt ? (
93
+ !counting && strings.otp.resendPrompt ? (
75
94
  <p className="prizma-screen-layout__inline-prompt">
76
95
  {strings.otp.resendPrompt}{' '}
77
96
  {strings.otp.resendLabel ? (
@@ -119,7 +138,8 @@ export const PhoneInputOtpScreen = forwardRef<HTMLDivElement, PhoneInputOtpScree
119
138
  errorText={state === 'error' ? strings.otp.errorText : undefined}
120
139
  />
121
140
  </PrizmaFormScreen>
122
- ),
141
+ );
142
+ },
123
143
  );
124
144
  PhoneInputOtpScreen.displayName = 'PhoneInputOtpScreen';
125
145
 
@@ -50,8 +50,9 @@ export type PhoneInputOtpState = 'typing' | 'filled' | 'processing' | 'error';
50
50
  export type PhoneInputErrorVariant = 'tryAgain' | 'unknown' | 'noNetwork';
51
51
 
52
52
  /** One selectable country in the dial-code dropdown. Runtime data, not copy —
53
- * the host supplies its own list; `flag` is the emoji Figma draws (Core has no
54
- * country-flag primitive — DEVIATIONS #PI-3). */
53
+ * the host supplies its own list. The screen draws the flag from Core's
54
+ * raster bank by `value` (`PrizmaCountryFlag`, DEVIATIONS #PI-3); `flag` is
55
+ * only the emoji fallback for a country outside the bank. */
55
56
  export interface PhoneInputCountry {
56
57
  /** Stable value, e.g. an ISO 3166-1 alpha-2 code. */
57
58
  value: string;
@@ -59,7 +60,7 @@ export interface PhoneInputCountry {
59
60
  dialCode: string;
60
61
  /** Country name for the open menu row. */
61
62
  label: string;
62
- /** Emoji flag, e.g. `🇷🇸`. */
63
+ /** Emoji fallback, e.g. `🇷🇸`, drawn only when the bank has no raster. */
63
64
  flag?: string;
64
65
  }
65
66
 
@@ -127,6 +128,13 @@ export interface PhoneInputStrings {
127
128
  resendSeparator?: string;
128
129
  /** Second link — returns to phone entry. Emits `tertiary`. */
129
130
  changeNumberLabel?: string;
131
+ /**
132
+ * While the resend timer runs, the resend line is replaced by ONE disabled
133
+ * link: "Resend code in {{time}}s" (Figma `phoneNumber.OTP.typing`). The
134
+ * `{{time}}` placeholder is the SDK's own (`otp.resendCountdown`) and is
135
+ * filled with the screen's `resendIn` seconds.
136
+ */
137
+ resendCountdownLabel?: string;
130
138
  /** Primary CTA. */
131
139
  ctaLabel: string;
132
140
  /** Accessible / visible busy copy while the code is checked (`processing`). */
@@ -1,6 +1,7 @@
1
1
  import React, { forwardRef } from 'react';
2
2
  import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
3
3
  import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
4
+ import { PrizmaCountryFlag } from '@incodetech/prizma-core-web/combined';
4
5
  import { PrizmaFormScreen, type PrizmaFormScreenProps } from './FormScreen';
5
6
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
6
7
  import type {
@@ -83,10 +84,13 @@ export const PhoneInputVerifyScreen = forwardRef<HTMLDivElement, PhoneInputVerif
83
84
  const options = countries.map((c) => ({
84
85
  value: c.value,
85
86
  // The open menu row reads flag + dial code + country name
86
- // ("🇷🇸 +381 Serbia"); the closed field keeps only flag + dial code
87
- // ("🇷🇸 +381") through Core's `fieldLabel`, exactly as Figma draws it.
88
- label: [c.flag, c.dialCode, c.label].filter(Boolean).join(' '),
89
- fieldLabel: [c.flag, c.dialCode].filter(Boolean).join(' '),
87
+ // ("+381 Serbia" after the flag); the closed field keeps only flag +
88
+ // dial code ("+381") through Core's `fieldLabel`, as Figma draws it.
89
+ // The flag is not text: it rides the leading slot below (DEVIATIONS
90
+ // #PI-3), so it is Core's raster on every OS instead of an emoji that
91
+ // Windows draws as the letters "RS".
92
+ label: [c.dialCode, c.label].join(' '),
93
+ fieldLabel: c.dialCode,
90
94
  }));
91
95
 
92
96
  // Figma treats the states as three distinct field treatments, not one:
@@ -133,6 +137,22 @@ export const PhoneInputVerifyScreen = forwardRef<HTMLDivElement, PhoneInputVerif
133
137
  searchPlaceholder={strings.verify.countrySearchPlaceholder}
134
138
  /* The `countryCode` section IS the open-menu spec state. */
135
139
  forceOpen={state === 'countryCode'}
140
+ /* Core's canonical flag raster in the closed field and every menu
141
+ row, the WatchlistBusinessFormScreen precedent. A country
142
+ outside the bank falls back to its `flag` emoji. */
143
+ renderLeading={(option) => {
144
+ const match = countries.find((c) => c.value === option.value);
145
+ return match ? (
146
+ <PrizmaCountryFlag
147
+ country={{
148
+ code: match.value,
149
+ name: match.label,
150
+ dialCode: match.dialCode,
151
+ flag: match.flag ?? '',
152
+ }}
153
+ />
154
+ ) : null;
155
+ }}
136
156
  disabled={busy}
137
157
  />
138
158
  <PrizmaTextField
@@ -55,16 +55,19 @@ export interface QualifiedSignatureDocumentScreenProps
55
55
  *
56
56
  * Archetype G is the fit: its `__list` middle is "content filling the space
57
57
  * between the title block and the CTA band", which is exactly what a document
58
- * page is, and both sections carry no CTA at all so the band never renders. The
58
+ * page is, and the band carries only the "Back" secondary (delta 1). The
59
59
  * body is allowed to scroll inside that middle — one module rule RELEASING an
60
60
  * overflow constraint, declaring no spacing or size, the same idiom as CURP's
61
61
  * taller-than-844 form (DEVIATIONS #CURP-1).
62
62
  *
63
63
  * THREE deltas from the frames:
64
- * 1. Both sections draw a back chevron at the nav's left. It is BUILT (owner
65
- * call 2026-09-29): these are viewers the user opened from the accept
66
- * screen, so they need a way back. It is Core's chevron through the opt-in
67
- * `PrizmaScreenNav.backLabel`, emitting `back`; the logo stays centred.
64
+ * 1. Both sections draw a back chevron at the nav's left. These are viewers
65
+ * the user opened from the accept screen, so they need a way back (owner
66
+ * call 2026-09-29). It ships as a full-width SECONDARY "Back" button in the
67
+ * CTA band instead, emitting `secondary` (owner call 2026-10-08): the person
68
+ * reads and returns to sign, and a real button at the thumb is easier to
69
+ * find than a chevron. "Back" over "Done" because nothing is finished here;
70
+ * the signature still waits on the accept screen. The nav stays logo-only.
68
71
  * `terms` replacing the logo row with a chevron + heading bar is still not
69
72
  * followed — the title stays the archetype's H2 below the nav.
70
73
  * 2. `viewdoc` draws NO heading. GUIDELINES §4 is explicit that every screen has
@@ -100,14 +103,13 @@ export const QualifiedSignatureDocumentScreen = forwardRef<
100
103
  ) => {
101
104
  const copy = variant === 'terms' ? strings.document.terms : strings.document.document;
102
105
 
103
-
104
106
  return (
105
107
  <PrizmaListScreen
106
108
  ref={ref}
107
109
  title={copy.title}
108
110
  verifiedLabel={strings.verifiedLabel}
109
111
  navLabel={strings.navLabel}
110
- backLabel={strings.document.backLabel}
112
+ secondaryCtaLabel={strings.document.backLabel}
111
113
  screenId={screenId}
112
114
  overrides={overrides}
113
115
  onAction={onAction}
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef } from 'react';
2
- import { PrizmaButton } from '@incodetech/prizma-core-web/button';
2
+ import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
3
3
  import { PrizmaResultScreen, type PrizmaResultScreenProps } from './ResultScreen';
4
4
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
5
5
  import type {
@@ -19,6 +19,7 @@ export interface QualifiedSignatureResultScreenProps
19
19
  | 'disclaimer'
20
20
  | 'ctaLabel'
21
21
  | 'secondaryCtaLabel'
22
+ | 'progress'
22
23
  | 'verifiedLabel'
23
24
  | 'navLabel'
24
25
  > {
@@ -26,6 +27,11 @@ export interface QualifiedSignatureResultScreenProps
26
27
  strings: QualifiedSignatureStrings;
27
28
  /** Which verdict section to render. Default `signed`. */
28
29
  variant?: QualifiedSignatureResultVariant;
30
+ /** `signed` · `signedPartner` only — draw the "document downloaded" Snackbar,
31
+ * the state after the person pressed the download control. HOST state, like
32
+ * Error screens' `copied`: the host sets it on the `secondary` action and
33
+ * clears it when its own timer runs out. Default `false`. */
34
+ downloaded?: boolean;
29
35
  screenId?: string;
30
36
  overrides?: PrizmaScreenOverrides;
31
37
  onAction?: PrizmaScreenActionHandler;
@@ -39,8 +45,10 @@ export interface QualifiedSignatureResultScreenProps
39
45
  * nav → centred status mark + title block → CTA band → anchored tag — and differ
40
46
  * in status, copy and which actions exist:
41
47
  * - `signed` — `QSign.success` (10196:67485): the positive mark over a
42
- * `Button group` 124 tall = the "Continue" primary plus the "Download PDF"
43
- * underlined link.
48
+ * STACKED pair — "Continue" primary above a "Download PDF" OUTLINED
49
+ * secondary. Figma draws the download as an underlined link; it ships as the
50
+ * partner's secondary Button instead (owner call 2026-10-08, "make the
51
+ * generic one look like Namirial"), so both success endings read alike.
44
52
  * - `signedPlain` — `QSign.success` (10196:67494): the SAME mark and line
45
53
  * with no actions at all, so the band never renders and the screen reads as
46
54
  * the pure §6-C verdict.
@@ -69,16 +77,12 @@ export interface QualifiedSignatureResultScreenProps
69
77
  * The owner rule settles the split in favour of keeping it (#PI-8, #F-30) so
70
78
  * nothing shifts between states.
71
79
  *
72
- * ONE reported delta: on `signed`, Figma puts "Download PDF" BELOW the primary,
73
- * at Core's `PrizmaButtonGroup` Spacing/12. Archetype F has no tertiary-link
74
- * slot — its only band opening above the buttons is `bandContent` — so Core's
75
- * own `tertiary-link` Button is composed there and lands ABOVE the primary at
76
- * the band's Spacing/32 (16 + 16). Ink, semantics and the distinct `secondary`
77
- * action are all correct (#PI-10); only the position moves. #GEO-1 fixes the
78
- * tertiary link's position PER ARCHETYPE and F has never declared one — the
79
- * archetype request is logged instead of a local override. `signedPartner` is
80
- * unaffected: its download control is a real outlined secondary Button, which
81
- * the archetype already places correctly.
80
+ * The download confirmation. Pressing "Download PDF" / "Download document"
81
+ * emits `secondary`; the host then sets `downloaded` and the screen raises a
82
+ * success Snackbar ("Document downloaded") in the archetype's `progress` slot,
83
+ * between the nav and the content, the same slot and component Error screens
84
+ * use for "link copied" (owner call 2026-10-08). Dismissal is the host's: the
85
+ * Snackbar is presentational (snackbar.md §9 item 6).
82
86
  */
83
87
  export const QualifiedSignatureResultScreen = forwardRef<
84
88
  HTMLDivElement,
@@ -88,6 +92,7 @@ export const QualifiedSignatureResultScreen = forwardRef<
88
92
  {
89
93
  strings,
90
94
  variant = 'signed',
95
+ downloaded = false,
91
96
  screenId = `qualified-signature-result-${variant}`,
92
97
  overrides,
93
98
  onAction,
@@ -113,24 +118,21 @@ export const QualifiedSignatureResultScreen = forwardRef<
113
118
  status={variant === 'error' || variant === 'errorRetry' ? 'warning' : 'success'}
114
119
  title={copy.title}
115
120
  subtitle={copy.subtitle}
121
+ progress={
122
+ downloaded && (signedCopy?.downloadLabel || partnerCopy) ? (
123
+ <PrizmaSnackbar state="success" contentResizing="fill">
124
+ {strings.result.downloadedLabel}
125
+ </PrizmaSnackbar>
126
+ ) : undefined
127
+ }
116
128
  /* "Continue" · "Done" · "Try again" — the archetype's own primary. */
117
129
  ctaLabel={signedCopy?.ctaLabel ?? partnerCopy?.ctaLabel ?? retryCopy?.ctaLabel}
118
- /* The partner's outlined "Download document", below the primary. */
119
- secondaryCtaLabel={partnerCopy?.secondaryCtaLabel}
130
+ /* The outlined download, below the primary: "Download PDF" on the
131
+ generic ending, "Download document" on the partner's. */
132
+ secondaryCtaLabel={signedCopy?.downloadLabel ?? partnerCopy?.secondaryCtaLabel}
120
133
  /* The partner's "Signed by …" row — the band's centred text slot, which
121
134
  puts it above the button pair exactly as Figma draws it. */
122
135
  disclaimer={partnerCopy?.signedByLabel}
123
- bandContent={
124
- signedCopy?.downloadLabel ? (
125
- <PrizmaButton
126
- variant="tertiary-link"
127
- size="L"
128
- onClick={() => onAction?.({ type: 'secondary', screenId })}
129
- >
130
- {signedCopy.downloadLabel}
131
- </PrizmaButton>
132
- ) : undefined
133
- }
134
136
  verifiedLabel={strings.verifiedLabel}
135
137
  navLabel={strings.navLabel}
136
138
  screenId={screenId}
@@ -62,7 +62,8 @@ export type QualifiedSignatureDocumentVariant = 'document' | 'terms';
62
62
  * The FIVE verdict frames. Both purple parents draw success twice and error
63
63
  * twice, and each pair differs in what the signer can do next:
64
64
  * - `signed` — `QSign.success` (10196:67485): the positive mark with
65
- * "Continue" and a "Download PDF" underlined link
65
+ * "Continue" primary above a "Download PDF" outlined secondary (was an
66
+ * underlined link in Figma; owner call 2026-10-08, matches the partner)
66
67
  * - `signedPlain` — `QSign.success` (10196:67494): the same mark and line
67
68
  * with NO actions at all
68
69
  * - `signedPartner` — the `x Namirial` parent's own success frame (6086:40135),
@@ -192,9 +193,11 @@ export interface QualifiedSignatureStrings {
192
193
  document: { title: string };
193
194
  /** QSign.T&C ("Certificate Application Form"), left-aligned in Figma. */
194
195
  terms: { title: string };
195
- /** Accessible name of the nav's back chevron on both document screens
196
+ /** The way back on both document screens
196
197
  * ("Back") — they are opened from the accept screen, so they carry a way
197
- * back to it. Emits `back`. Omit to render the logo-only nav. */
198
+ * back to it. It is the label of a full-width SECONDARY button in the CTA
199
+ * band (owner call 2026-10-08, was the nav chevron) and emits
200
+ * `secondary`. Omit it and no way back renders. */
198
201
  backLabel?: string;
199
202
  };
200
203
 
@@ -231,7 +234,8 @@ export interface QualifiedSignatureStrings {
231
234
  subtitle?: string;
232
235
  /** Primary CTA ("Continue"). */
233
236
  ctaLabel: string;
234
- /** Underlined tertiary link ("Download PDF"). Emits `secondary`. */
237
+ /** OUTLINED secondary below the primary ("Download PDF"), the same
238
+ * control as the partner's "Download document". Emits `secondary`. */
235
239
  downloadLabel?: string;
236
240
  };
237
241
  /** QSign.success drawn with no actions. */
@@ -253,6 +257,9 @@ export interface QualifiedSignatureStrings {
253
257
  * `secondary`. A real Button, not `signed`'s underlined link. */
254
258
  secondaryCtaLabel: string;
255
259
  };
260
+ /** The success Snackbar raised after either download control was pressed
261
+ * ("Document downloaded"). Shared by `signed` and `signedPartner`. */
262
+ downloadedLabel: string;
256
263
  /** QSign.AcceptSign.error ("Something went wrong"). Figma draws no button. */
257
264
  error: { title: string; subtitle?: string };
258
265
  /** The retry frame ("Signature failed" + "Try again"). */