@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
@@ -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
@@ -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;
@@ -28,8 +28,9 @@ export type PhoneInputOtpState = 'typing' | 'filled' | 'processing' | 'error';
28
28
  */
29
29
  export type PhoneInputErrorVariant = 'tryAgain' | 'unknown' | 'noNetwork';
30
30
  /** One selectable country in the dial-code dropdown. Runtime data, not copy —
31
- * the host supplies its own list; `flag` is the emoji Figma draws (Core has no
32
- * 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. */
33
34
  export interface PhoneInputCountry {
34
35
  /** Stable value, e.g. an ISO 3166-1 alpha-2 code. */
35
36
  value: string;
@@ -37,7 +38,7 @@ export interface PhoneInputCountry {
37
38
  dialCode: string;
38
39
  /** Country name for the open menu row. */
39
40
  label: string;
40
- /** Emoji flag, e.g. `🇷🇸`. */
41
+ /** Emoji fallback, e.g. `🇷🇸`, drawn only when the bank has no raster. */
41
42
  flag?: string;
42
43
  }
43
44
  export interface PhoneInputStrings {
@@ -102,6 +103,13 @@ export interface PhoneInputStrings {
102
103
  resendSeparator?: string;
103
104
  /** Second link — returns to phone entry. Emits `tertiary`. */
104
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;
105
113
  /** Primary CTA. */
106
114
  ctaLabel: string;
107
115
  /** Accessible / visible busy copy while the code is checked (`processing`). */
@@ -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';
@@ -43,10 +44,13 @@ export const PhoneInputVerifyScreen = forwardRef(({ strings, state = 'default',
43
44
  const options = countries.map((c) => ({
44
45
  value: c.value,
45
46
  // The open menu row reads flag + dial code + country name
46
- // ("🇷🇸 +381 Serbia"); the closed field keeps only flag + dial code
47
- // ("🇷🇸 +381") through Core's `fieldLabel`, exactly as Figma draws it.
48
- label: [c.flag, c.dialCode, c.label].filter(Boolean).join(' '),
49
- fieldLabel: [c.flag, c.dialCode].filter(Boolean).join(' '),
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,
50
54
  }));
51
55
  // Figma treats the states as three distinct field treatments, not one:
52
56
  // `loading` greys BOTH fields (the request is in flight and the form is
@@ -64,7 +68,19 @@ export const PhoneInputVerifyScreen = forwardRef(({ strings, state = 'default',
64
68
  /* Figma's `loading` section swaps the CTA label for Core's spinner. */
65
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,
66
70
  /* The `countryCode` section IS the open-menu spec state. */
67
- 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: [
68
84
  'phone-input-combined__number',
69
85
  /* Core's own static-focus modifier — the same mechanism the
70
86
  Dropdown exposes as `forceState`, which `PrizmaTextField` has
@@ -35,16 +35,19 @@ 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
43
  * THREE deltas from the frames:
44
- * 1. Both sections draw a back chevron at the nav's left. It is BUILT (owner
45
- * call 2026-09-29): these are viewers the user opened from the accept
46
- * screen, so they need a way back. It is Core's chevron through the opt-in
47
- * `PrizmaScreenNav.backLabel`, emitting `back`; the logo stays centred.
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.
48
51
  * `terms` replacing the logo row with a chevron + heading bar is still not
49
52
  * followed — the title stays the archetype's H2 below the nav.
50
53
  * 2. `viewdoc` draws NO heading. GUIDELINES §4 is explicit that every screen has
@@ -19,16 +19,19 @@ import './QualifiedSignatureLayout.css';
19
19
  *
20
20
  * Archetype G is the fit: its `__list` middle is "content filling the space
21
21
  * between the title block and the CTA band", which is exactly what a document
22
- * page is, and both sections carry no CTA at all so the band never renders. The
22
+ * page is, and the band carries only the "Back" secondary (delta 1). The
23
23
  * body is allowed to scroll inside that middle — one module rule RELEASING an
24
24
  * overflow constraint, declaring no spacing or size, the same idiom as CURP's
25
25
  * taller-than-844 form (DEVIATIONS #CURP-1).
26
26
  *
27
27
  * THREE deltas from the frames:
28
- * 1. Both sections draw a back chevron at the nav's left. It is BUILT (owner
29
- * call 2026-09-29): these are viewers the user opened from the accept
30
- * screen, so they need a way back. It is Core's chevron through the opt-in
31
- * `PrizmaScreenNav.backLabel`, emitting `back`; the logo stays centred.
28
+ * 1. Both sections draw a back chevron at the nav's left. These are viewers
29
+ * the user opened from the accept screen, so they need a way back (owner
30
+ * call 2026-09-29). It ships as a full-width SECONDARY "Back" button in the
31
+ * CTA band instead, emitting `secondary` (owner call 2026-10-08): the person
32
+ * reads and returns to sign, and a real button at the thumb is easier to
33
+ * find than a chevron. "Back" over "Done" because nothing is finished here;
34
+ * the signature still waits on the accept screen. The nav stays logo-only.
32
35
  * `terms` replacing the logo row with a chevron + heading bar is still not
33
36
  * followed — the title stays the archetype's H2 below the nav.
34
37
  * 2. `viewdoc` draws NO heading. GUIDELINES §4 is explicit that every screen has
@@ -48,7 +51,7 @@ import './QualifiedSignatureLayout.css';
48
51
  */
49
52
  export const QualifiedSignatureDocumentScreen = forwardRef(({ strings, variant = 'document', body, screenId = `qualified-signature-document-${variant}`, overrides, onAction, ...shell }, ref) => {
50
53
  const copy = variant === 'terms' ? strings.document.terms : strings.document.document;
51
- return (_jsx(PrizmaListScreen, { ref: ref, title: copy.title, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, backLabel: strings.document.backLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx("div", { className: [
54
+ return (_jsx(PrizmaListScreen, { ref: ref, title: copy.title, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, secondaryCtaLabel: strings.document.backLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx("div", { className: [
52
55
  'qualified-signature-document',
53
56
  'qualified-signature-document--framed',
54
57
  `qualified-signature-document--${variant}`,
@@ -3,11 +3,16 @@ import { type PrizmaResultScreenProps } from './ResultScreen';
3
3
  import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
4
4
  import type { QualifiedSignatureResultVariant, QualifiedSignatureStrings } from './QualifiedSignatureStrings';
5
5
  import './ScreenLayout.css';
6
- export interface QualifiedSignatureResultScreenProps extends Omit<PrizmaResultScreenProps, 'status' | 'title' | 'subtitle' | 'glyph' | 'bandContent' | 'disclaimer' | 'ctaLabel' | 'secondaryCtaLabel' | 'verifiedLabel' | 'navLabel'> {
6
+ export interface QualifiedSignatureResultScreenProps extends Omit<PrizmaResultScreenProps, 'status' | 'title' | 'subtitle' | 'glyph' | 'bandContent' | 'disclaimer' | 'ctaLabel' | 'secondaryCtaLabel' | 'progress' | 'verifiedLabel' | 'navLabel'> {
7
7
  /** Module copy — this screen reads the `result` slice its `variant` names. */
8
8
  strings: QualifiedSignatureStrings;
9
9
  /** Which verdict section to render. Default `signed`. */
10
10
  variant?: QualifiedSignatureResultVariant;
11
+ /** `signed` · `signedPartner` only — draw the "document downloaded" Snackbar,
12
+ * the state after the person pressed the download control. HOST state, like
13
+ * Error screens' `copied`: the host sets it on the `secondary` action and
14
+ * clears it when its own timer runs out. Default `false`. */
15
+ downloaded?: boolean;
11
16
  screenId?: string;
12
17
  overrides?: PrizmaScreenOverrides;
13
18
  onAction?: PrizmaScreenActionHandler;
@@ -20,8 +25,10 @@ export interface QualifiedSignatureResultScreenProps extends Omit<PrizmaResultSc
20
25
  * nav → centred status mark + title block → CTA band → anchored tag — and differ
21
26
  * in status, copy and which actions exist:
22
27
  * - `signed` — `QSign.success` (10196:67485): the positive mark over a
23
- * `Button group` 124 tall = the "Continue" primary plus the "Download PDF"
24
- * underlined link.
28
+ * STACKED pair — "Continue" primary above a "Download PDF" OUTLINED
29
+ * secondary. Figma draws the download as an underlined link; it ships as the
30
+ * partner's secondary Button instead (owner call 2026-10-08, "make the
31
+ * generic one look like Namirial"), so both success endings read alike.
25
32
  * - `signedPlain` — `QSign.success` (10196:67494): the SAME mark and line
26
33
  * with no actions at all, so the band never renders and the screen reads as
27
34
  * the pure §6-C verdict.
@@ -50,16 +57,12 @@ export interface QualifiedSignatureResultScreenProps extends Omit<PrizmaResultSc
50
57
  * The owner rule settles the split in favour of keeping it (#PI-8, #F-30) so
51
58
  * nothing shifts between states.
52
59
  *
53
- * ONE reported delta: on `signed`, Figma puts "Download PDF" BELOW the primary,
54
- * at Core's `PrizmaButtonGroup` Spacing/12. Archetype F has no tertiary-link
55
- * slot — its only band opening above the buttons is `bandContent` — so Core's
56
- * own `tertiary-link` Button is composed there and lands ABOVE the primary at
57
- * the band's Spacing/32 (16 + 16). Ink, semantics and the distinct `secondary`
58
- * action are all correct (#PI-10); only the position moves. #GEO-1 fixes the
59
- * tertiary link's position PER ARCHETYPE and F has never declared one — the
60
- * archetype request is logged instead of a local override. `signedPartner` is
61
- * unaffected: its download control is a real outlined secondary Button, which
62
- * the archetype already places correctly.
60
+ * The download confirmation. Pressing "Download PDF" / "Download document"
61
+ * emits `secondary`; the host then sets `downloaded` and the screen raises a
62
+ * success Snackbar ("Document downloaded") in the archetype's `progress` slot,
63
+ * between the nav and the content, the same slot and component Error screens
64
+ * use for "link copied" (owner call 2026-10-08). Dismissal is the host's: the
65
+ * Snackbar is presentational (snackbar.md §9 item 6).
63
66
  */
64
67
  export declare const QualifiedSignatureResultScreen: React.ForwardRefExoticComponent<QualifiedSignatureResultScreenProps & React.RefAttributes<HTMLDivElement>>;
65
68
  export default QualifiedSignatureResultScreen;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { forwardRef } from 'react';
4
- import { PrizmaButton } from '@incodetech/prizma-core-web/button';
4
+ import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
5
5
  import { PrizmaResultScreen } from './ResultScreen';
6
6
  import './ScreenLayout.css';
7
7
  /**
@@ -12,8 +12,10 @@ import './ScreenLayout.css';
12
12
  * nav → centred status mark + title block → CTA band → anchored tag — and differ
13
13
  * in status, copy and which actions exist:
14
14
  * - `signed` — `QSign.success` (10196:67485): the positive mark over a
15
- * `Button group` 124 tall = the "Continue" primary plus the "Download PDF"
16
- * underlined link.
15
+ * STACKED pair — "Continue" primary above a "Download PDF" OUTLINED
16
+ * secondary. Figma draws the download as an underlined link; it ships as the
17
+ * partner's secondary Button instead (owner call 2026-10-08, "make the
18
+ * generic one look like Namirial"), so both success endings read alike.
17
19
  * - `signedPlain` — `QSign.success` (10196:67494): the SAME mark and line
18
20
  * with no actions at all, so the band never renders and the screen reads as
19
21
  * the pure §6-C verdict.
@@ -42,18 +44,14 @@ import './ScreenLayout.css';
42
44
  * The owner rule settles the split in favour of keeping it (#PI-8, #F-30) so
43
45
  * nothing shifts between states.
44
46
  *
45
- * ONE reported delta: on `signed`, Figma puts "Download PDF" BELOW the primary,
46
- * at Core's `PrizmaButtonGroup` Spacing/12. Archetype F has no tertiary-link
47
- * slot — its only band opening above the buttons is `bandContent` — so Core's
48
- * own `tertiary-link` Button is composed there and lands ABOVE the primary at
49
- * the band's Spacing/32 (16 + 16). Ink, semantics and the distinct `secondary`
50
- * action are all correct (#PI-10); only the position moves. #GEO-1 fixes the
51
- * tertiary link's position PER ARCHETYPE and F has never declared one — the
52
- * archetype request is logged instead of a local override. `signedPartner` is
53
- * unaffected: its download control is a real outlined secondary Button, which
54
- * the archetype already places correctly.
47
+ * The download confirmation. Pressing "Download PDF" / "Download document"
48
+ * emits `secondary`; the host then sets `downloaded` and the screen raises a
49
+ * success Snackbar ("Document downloaded") in the archetype's `progress` slot,
50
+ * between the nav and the content, the same slot and component Error screens
51
+ * use for "link copied" (owner call 2026-10-08). Dismissal is the host's: the
52
+ * Snackbar is presentational (snackbar.md §9 item 6).
55
53
  */
56
- export const QualifiedSignatureResultScreen = forwardRef(({ strings, variant = 'signed', screenId = `qualified-signature-result-${variant}`, overrides, onAction, ...shell }, ref) => {
54
+ export const QualifiedSignatureResultScreen = forwardRef(({ strings, variant = 'signed', downloaded = false, screenId = `qualified-signature-result-${variant}`, overrides, onAction, ...shell }, ref) => {
57
55
  // One slice per frame — nothing is shared between variants, so the frames
58
56
  // can never collapse into each other visually.
59
57
  const signedCopy = variant === 'signed' ? strings.result.signed : undefined;
@@ -63,14 +61,15 @@ export const QualifiedSignatureResultScreen = forwardRef(({ strings, variant = '
63
61
  partnerCopy ??
64
62
  retryCopy ??
65
63
  (variant === 'error' ? strings.result.error : strings.result.signedPlain);
66
- return (_jsx(PrizmaResultScreen, { ref: ref, status: variant === 'error' || variant === 'errorRetry' ? 'warning' : 'success', title: copy.title, subtitle: copy.subtitle,
64
+ return (_jsx(PrizmaResultScreen, { ref: ref, status: variant === 'error' || variant === 'errorRetry' ? 'warning' : 'success', title: copy.title, subtitle: copy.subtitle, progress: downloaded && (signedCopy?.downloadLabel || partnerCopy) ? (_jsx(PrizmaSnackbar, { state: "success", contentResizing: "fill", children: strings.result.downloadedLabel })) : undefined,
67
65
  /* "Continue" · "Done" · "Try again" — the archetype's own primary. */
68
66
  ctaLabel: signedCopy?.ctaLabel ?? partnerCopy?.ctaLabel ?? retryCopy?.ctaLabel,
69
- /* The partner's outlined "Download document", below the primary. */
70
- secondaryCtaLabel: partnerCopy?.secondaryCtaLabel,
67
+ /* The outlined download, below the primary: "Download PDF" on the
68
+ generic ending, "Download document" on the partner's. */
69
+ secondaryCtaLabel: signedCopy?.downloadLabel ?? partnerCopy?.secondaryCtaLabel,
71
70
  /* The partner's "Signed by …" row — the band's centred text slot, which
72
71
  puts it above the button pair exactly as Figma draws it. */
73
- disclaimer: partnerCopy?.signedByLabel, bandContent: signedCopy?.downloadLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: signedCopy.downloadLabel })) : undefined, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell }));
72
+ disclaimer: partnerCopy?.signedByLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell }));
74
73
  });
75
74
  QualifiedSignatureResultScreen.displayName = 'QualifiedSignatureResultScreen';
76
75
  export default QualifiedSignatureResultScreen;
@@ -33,7 +33,8 @@ export type QualifiedSignatureDocumentVariant = 'document' | 'terms';
33
33
  * The FIVE verdict frames. Both purple parents draw success twice and error
34
34
  * twice, and each pair differs in what the signer can do next:
35
35
  * - `signed` — `QSign.success` (10196:67485): the positive mark with
36
- * "Continue" and a "Download PDF" underlined link
36
+ * "Continue" primary above a "Download PDF" outlined secondary (was an
37
+ * underlined link in Figma; owner call 2026-10-08, matches the partner)
37
38
  * - `signedPlain` — `QSign.success` (10196:67494): the same mark and line
38
39
  * with NO actions at all
39
40
  * - `signedPartner` — the `x Namirial` parent's own success frame (6086:40135),
@@ -154,9 +155,11 @@ export interface QualifiedSignatureStrings {
154
155
  terms: {
155
156
  title: string;
156
157
  };
157
- /** Accessible name of the nav's back chevron on both document screens
158
+ /** The way back on both document screens
158
159
  * ("Back") — they are opened from the accept screen, so they carry a way
159
- * back to it. Emits `back`. Omit to render the logo-only nav. */
160
+ * back to it. It is the label of a full-width SECONDARY button in the CTA
161
+ * band (owner call 2026-10-08, was the nav chevron) and emits
162
+ * `secondary`. Omit it and no way back renders. */
160
163
  backLabel?: string;
161
164
  };
162
165
  /** The two in-flight screens. */
@@ -191,7 +194,8 @@ export interface QualifiedSignatureStrings {
191
194
  subtitle?: string;
192
195
  /** Primary CTA ("Continue"). */
193
196
  ctaLabel: string;
194
- /** Underlined tertiary link ("Download PDF"). Emits `secondary`. */
197
+ /** OUTLINED secondary below the primary ("Download PDF"), the same
198
+ * control as the partner's "Download document". Emits `secondary`. */
195
199
  downloadLabel?: string;
196
200
  };
197
201
  /** QSign.success drawn with no actions. */
@@ -216,6 +220,9 @@ export interface QualifiedSignatureStrings {
216
220
  * `secondary`. A real Button, not `signed`'s underlined link. */
217
221
  secondaryCtaLabel: string;
218
222
  };
223
+ /** The success Snackbar raised after either download control was pressed
224
+ * ("Document downloaded"). Shared by `signed` and `signedPartner`. */
225
+ downloadedLabel: string;
219
226
  /** QSign.AcceptSign.error ("Something went wrong"). Figma draws no button. */
220
227
  error: {
221
228
  title: string;