@incodetech/prizma-core-screens 0.2.7 → 0.2.14

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 (191) 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/DigitalIdDeviceWalletsDocumentSelectionScreen.d.ts +34 -0
  12. package/dist/DigitalIdDeviceWalletsDocumentSelectionScreen.js +47 -0
  13. package/dist/DigitalIdDeviceWalletsResultScreen.d.ts +28 -0
  14. package/dist/DigitalIdDeviceWalletsResultScreen.js +23 -0
  15. package/dist/DigitalIdDeviceWalletsStrings.d.ts +55 -0
  16. package/dist/DigitalIdDeviceWalletsStrings.js +19 -0
  17. package/dist/DigitalIdProvidersDocumentSelectionScreen.d.ts +31 -0
  18. package/dist/DigitalIdProvidersDocumentSelectionScreen.js +37 -0
  19. package/dist/DigitalIdProvidersErrorScreen.d.ts +33 -0
  20. package/dist/DigitalIdProvidersErrorScreen.js +33 -0
  21. package/dist/DigitalIdProvidersLayout.css +59 -0
  22. package/dist/DigitalIdProvidersRedirectScreen.d.ts +24 -0
  23. package/dist/DigitalIdProvidersRedirectScreen.js +18 -0
  24. package/dist/DigitalIdProvidersStrings.d.ts +72 -0
  25. package/dist/DigitalIdProvidersStrings.js +15 -0
  26. package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
  27. package/dist/DigitalIdUploadErrorScreen.js +26 -0
  28. package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
  29. package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
  30. package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
  31. package/dist/DigitalIdUploadReviewScreen.js +44 -0
  32. package/dist/DigitalIdUploadStrings.d.ts +64 -0
  33. package/dist/DigitalIdUploadStrings.js +19 -0
  34. package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
  35. package/dist/DigitalIdUploadTutorialScreen.js +23 -0
  36. package/dist/DocumentCaptureIllustration.d.ts +1 -1
  37. package/dist/EkybCountryScreen.d.ts +6 -6
  38. package/dist/EkybCountryScreen.js +20 -7
  39. package/dist/EkybStrings.d.ts +6 -1
  40. package/dist/ErrorScreensScreen.d.ts +5 -2
  41. package/dist/ErrorScreensScreen.js +15 -4
  42. package/dist/ErrorScreensStrings.d.ts +17 -2
  43. package/dist/ErrorScreensStrings.js +18 -10
  44. package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
  45. package/dist/FaceCaptureCapturingScreen.js +62 -2
  46. package/dist/FaceCaptureLayout.css +69 -4
  47. package/dist/FaceCaptureStrings.d.ts +23 -0
  48. package/dist/FormScreen.css +14 -0
  49. package/dist/FormScreen.d.ts +28 -0
  50. package/dist/FormScreen.js +3 -2
  51. package/dist/IdCaptureLayout.css +165 -2
  52. package/dist/IdCaptureStageScreen.d.ts +47 -0
  53. package/dist/IdCaptureStageScreen.js +39 -3
  54. package/dist/IdCaptureStrings.d.ts +19 -0
  55. package/dist/ListScreen.d.ts +19 -0
  56. package/dist/ListScreen.js +7 -2
  57. package/dist/LoaderScreen.d.ts +7 -5
  58. package/dist/LoaderScreen.js +7 -5
  59. package/dist/LoadersStrings.d.ts +18 -5
  60. package/dist/OcrReviewErrorScreen.d.ts +30 -0
  61. package/dist/OcrReviewErrorScreen.js +24 -0
  62. package/dist/OcrReviewLayout.css +24 -0
  63. package/dist/OcrReviewLoadingScreen.d.ts +25 -0
  64. package/dist/OcrReviewLoadingScreen.js +19 -0
  65. package/dist/OcrReviewScreen.d.ts +76 -0
  66. package/dist/OcrReviewScreen.js +65 -0
  67. package/dist/OcrReviewStrings.d.ts +47 -0
  68. package/dist/OcrReviewStrings.js +17 -0
  69. package/dist/OcrReviewSuccessScreen.d.ts +30 -0
  70. package/dist/OcrReviewSuccessScreen.js +24 -0
  71. package/dist/PermissionsAllowScreen.d.ts +8 -11
  72. package/dist/PermissionsAllowScreen.js +8 -11
  73. package/dist/PermissionsAskScreen.d.ts +41 -0
  74. package/dist/PermissionsAskScreen.js +43 -0
  75. package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
  76. package/dist/PermissionsBrowserStepsScreen.js +12 -3
  77. package/dist/PermissionsLayout.css +47 -0
  78. package/dist/PermissionsStrings.d.ts +20 -9
  79. package/dist/PermissionsStrings.js +10 -4
  80. package/dist/PhoneInputOtpScreen.d.ts +8 -0
  81. package/dist/PhoneInputOtpScreen.js +26 -19
  82. package/dist/PhoneInputStrings.d.ts +11 -3
  83. package/dist/PhoneInputVerifyScreen.js +21 -5
  84. package/dist/QualifiedSignatureDocumentScreen.d.ts +8 -5
  85. package/dist/QualifiedSignatureDocumentScreen.js +9 -6
  86. package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
  87. package/dist/QualifiedSignatureResultScreen.js +17 -18
  88. package/dist/QualifiedSignatureStrings.d.ts +11 -4
  89. package/dist/ResultScreen.d.ts +8 -0
  90. package/dist/ResultScreen.js +14 -3
  91. package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
  92. package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
  93. package/dist/ReusabilityLayout.css +241 -0
  94. package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
  95. package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
  96. package/dist/ReusabilityStrings.d.ts +85 -0
  97. package/dist/ReusabilityStrings.js +37 -0
  98. package/dist/ReviewScreen.d.ts +38 -12
  99. package/dist/ReviewScreen.js +41 -8
  100. package/dist/ScreenLayout.css +382 -6
  101. package/dist/VideoSelfieErrorScreen.js +1 -1
  102. package/dist/VideoSelfieIdCaptureScreen.js +1 -1
  103. package/dist/VideoSelfieLayout.css +304 -2
  104. package/dist/VideoSelfieSelfieCaptureScreen.js +1 -1
  105. package/dist/VideoSelfieSpeakScreen.js +1 -1
  106. package/dist/VideoSelfieStrings.d.ts +4 -1
  107. package/dist/VideoSelfieTurnHeadScreen.js +14 -11
  108. package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
  109. package/dist/VideoSelfieTutorialScreen.js +10 -1
  110. package/dist/VideoSelfieWaveScreen.js +1 -1
  111. package/dist/ViewfinderScreen.css +36 -0
  112. package/dist/flows.d.ts +8 -0
  113. package/dist/flows.js +17 -0
  114. package/dist/index.d.ts +23 -0
  115. package/dist/index.js +17 -0
  116. package/dist/styles.css +4 -0
  117. package/package.json +94 -3
  118. package/samples/video-selfie-camera-wide.jpg +0 -0
  119. package/samples/video-selfie-room-wide.jpg +0 -0
  120. package/src/BiometricConsentScreen.tsx +192 -0
  121. package/src/BiometricConsentStrings.ts +119 -0
  122. package/src/CaptureScreen.tsx +17 -0
  123. package/src/DemoScreensLayout.css +16 -0
  124. package/src/DemoScreensRedirectScreen.tsx +14 -6
  125. package/src/DemoScreensStrings.ts +5 -0
  126. package/src/DigitalIdDeviceWalletsDocumentSelectionScreen.tsx +108 -0
  127. package/src/DigitalIdDeviceWalletsResultScreen.tsx +76 -0
  128. package/src/DigitalIdDeviceWalletsStrings.ts +80 -0
  129. package/src/DigitalIdProvidersDocumentSelectionScreen.tsx +112 -0
  130. package/src/DigitalIdProvidersErrorScreen.tsx +104 -0
  131. package/src/DigitalIdProvidersLayout.css +59 -0
  132. package/src/DigitalIdProvidersRedirectScreen.tsx +65 -0
  133. package/src/DigitalIdProvidersStrings.ts +98 -0
  134. package/src/DigitalIdUploadErrorScreen.tsx +83 -0
  135. package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
  136. package/src/DigitalIdUploadReviewScreen.tsx +119 -0
  137. package/src/DigitalIdUploadStrings.ts +88 -0
  138. package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
  139. package/src/DocumentCaptureIllustration.tsx +1 -1
  140. package/src/EkybCountryScreen.tsx +23 -6
  141. package/src/EkybStrings.ts +6 -1
  142. package/src/ErrorScreensScreen.tsx +15 -4
  143. package/src/ErrorScreensStrings.ts +37 -12
  144. package/src/FaceCaptureCapturingScreen.tsx +102 -13
  145. package/src/FaceCaptureLayout.css +69 -4
  146. package/src/FaceCaptureStrings.ts +24 -0
  147. package/src/FormScreen.css +14 -0
  148. package/src/FormScreen.tsx +34 -0
  149. package/src/IdCaptureLayout.css +165 -2
  150. package/src/IdCaptureStageScreen.tsx +75 -0
  151. package/src/IdCaptureStrings.ts +19 -0
  152. package/src/ListScreen.tsx +48 -9
  153. package/src/LoaderScreen.tsx +7 -5
  154. package/src/LoadersStrings.ts +18 -5
  155. package/src/OcrReviewErrorScreen.tsx +53 -0
  156. package/src/OcrReviewLayout.css +24 -0
  157. package/src/OcrReviewLoadingScreen.tsx +48 -0
  158. package/src/OcrReviewScreen.tsx +190 -0
  159. package/src/OcrReviewStrings.ts +69 -0
  160. package/src/OcrReviewSuccessScreen.tsx +52 -0
  161. package/src/PermissionsAllowScreen.tsx +9 -10
  162. package/src/PermissionsAskScreen.tsx +105 -0
  163. package/src/PermissionsBrowserStepsScreen.tsx +11 -3
  164. package/src/PermissionsLayout.css +47 -0
  165. package/src/PermissionsStrings.ts +42 -12
  166. package/src/PhoneInputOtpScreen.tsx +23 -3
  167. package/src/PhoneInputStrings.ts +11 -3
  168. package/src/PhoneInputVerifyScreen.tsx +24 -4
  169. package/src/QualifiedSignatureDocumentScreen.tsx +9 -7
  170. package/src/QualifiedSignatureResultScreen.tsx +28 -26
  171. package/src/QualifiedSignatureStrings.ts +11 -4
  172. package/src/ResultScreen.tsx +33 -2
  173. package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
  174. package/src/ReusabilityLayout.css +241 -0
  175. package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
  176. package/src/ReusabilityStrings.ts +127 -0
  177. package/src/ReviewScreen.tsx +96 -27
  178. package/src/ScreenLayout.css +382 -6
  179. package/src/VideoSelfieErrorScreen.tsx +1 -1
  180. package/src/VideoSelfieIdCaptureScreen.tsx +9 -1
  181. package/src/VideoSelfieLayout.css +304 -2
  182. package/src/VideoSelfieSelfieCaptureScreen.tsx +9 -1
  183. package/src/VideoSelfieSpeakScreen.tsx +9 -1
  184. package/src/VideoSelfieStrings.ts +4 -2
  185. package/src/VideoSelfieTurnHeadScreen.tsx +25 -14
  186. package/src/VideoSelfieTutorialScreen.tsx +11 -0
  187. package/src/VideoSelfieWaveScreen.tsx +9 -1
  188. package/src/ViewfinderScreen.css +36 -0
  189. package/src/flows.ts +25 -0
  190. package/src/index.ts +116 -3
  191. package/src/styles.css +4 -0
@@ -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"). */
@@ -77,6 +77,14 @@ export interface PrizmaResultScreenProps
77
77
  * job (Face Match's verdict Snackbar). DEVIATIONS #VS-17.
78
78
  */
79
79
  linkLabel?: string;
80
+ /**
81
+ * Where the tertiary link sits in the stack. `below` (default) is archetype
82
+ * A's position; `above` puts it over the primary CTA, which is what the
83
+ * permission frames draw ("Quit process" over "Allow permission",
84
+ * DEVIATIONS #GEN-3, #VS-17). The slot those rows escalated: the archetype
85
+ * still owns both positions, a screen only picks one of them.
86
+ */
87
+ linkPosition?: 'below' | 'above';
80
88
  /** "verified by" attribution copy for the footer tag. */
81
89
  verifiedLabel: string;
82
90
  navLabel?: string;
@@ -118,6 +126,7 @@ export const PrizmaResultScreen = forwardRef<HTMLDivElement, PrizmaResultScreenP
118
126
  ctaLabel,
119
127
  secondaryCtaLabel,
120
128
  linkLabel,
129
+ linkPosition = 'below',
121
130
  verifiedLabel,
122
131
  navLabel,
123
132
  screenId,
@@ -152,7 +161,20 @@ export const PrizmaResultScreen = forwardRef<HTMLDivElement, PrizmaResultScreenP
152
161
  {children}
153
162
  </div>
154
163
  {ctaLabel || secondaryCtaLabel || linkLabel || disclaimer || bandContent ? (
155
- <div className="prizma-screen-layout__band prizma-screen-layout__band--result">
164
+ <div
165
+ className={[
166
+ 'prizma-screen-layout__band',
167
+ 'prizma-screen-layout__band--result',
168
+ // No footer tag to reserve `--pcs-footer-space` for — same gap the
169
+ // Review archetype's `--bare` fallback exists for (ScreenLayout.css).
170
+ // Every existing F consumer always shows the tag, so this has never
171
+ // fired before; without it the CTA sits ~40px further from the
172
+ // bottom than Figma's flat Spacing/24 draws.
173
+ !showFooter ? 'prizma-screen-layout__band--bare' : '',
174
+ ]
175
+ .filter(Boolean)
176
+ .join(' ')}
177
+ >
156
178
  {bandContent ? (
157
179
  <div className="prizma-screen-layout__band__content">{bandContent}</div>
158
180
  ) : null}
@@ -168,6 +190,15 @@ export const PrizmaResultScreen = forwardRef<HTMLDivElement, PrizmaResultScreenP
168
190
  2026-08-19, DEVIATIONS #GEO-1). Not a per-screen choice and not
169
191
  a prop — a screen never picks its own button order. `row` pairs
170
192
  them side by side instead, where the rule does not apply. */}
193
+ {linkLabel && linkPosition === 'above' ? (
194
+ <PrizmaButton
195
+ variant="tertiary-link"
196
+ size="L"
197
+ onClick={() => onAction?.({ type: 'tertiary', screenId })}
198
+ >
199
+ {linkLabel}
200
+ </PrizmaButton>
201
+ ) : null}
171
202
  {ctaLabel ? (
172
203
  <PrizmaButton
173
204
  variant="primary"
@@ -186,7 +217,7 @@ export const PrizmaResultScreen = forwardRef<HTMLDivElement, PrizmaResultScreenP
186
217
  {secondaryCtaLabel}
187
218
  </PrizmaButton>
188
219
  ) : null}
189
- {linkLabel ? (
220
+ {linkLabel && linkPosition === 'below' ? (
190
221
  <PrizmaButton
191
222
  variant="tertiary-link"
192
223
  size="L"
@@ -0,0 +1,246 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { PrizmaBottomSheetContent } from '@incodetech/prizma-core-web/bottom-sheet';
3
+ import { PrizmaCountryFlag } from '@incodetech/prizma-core-web/combined';
4
+ import { PrizmaListScreen, type PrizmaListScreenProps } from './ListScreen';
5
+ import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
6
+ import type { ReusableCredential, ReusabilityStrings } from './ReusabilityStrings';
7
+ import './ScreenLayout.css';
8
+ import './ReusabilityLayout.css';
9
+
10
+ /** The sentinel the SDK submits for the capture row (`SCAN_NEW_DOCUMENT`,
11
+ * `identityReuseStateMachine.ts`): `{ accepted: false }`, then ID Capture. */
12
+ export const REUSABILITY_SCAN_NEW = 'scanNew' as const;
13
+
14
+ export type ReusabilitySelection = string | typeof REUSABILITY_SCAN_NEW;
15
+
16
+ export interface ReusabilityCredentialSelectionScreenProps
17
+ extends Omit<
18
+ PrizmaListScreenProps,
19
+ | 'status'
20
+ | 'title'
21
+ | 'subtitle'
22
+ | 'glyph'
23
+ | 'children'
24
+ | 'listAlign'
25
+ | 'scrollList'
26
+ | 'disclaimer'
27
+ | 'ctaLabel'
28
+ | 'secondaryCtaLabel'
29
+ | 'linkLabel'
30
+ | 'secondaryLinkLabel'
31
+ | 'overlay'
32
+ | 'verifiedLabel'
33
+ | 'navLabel'
34
+ > {
35
+ /** Module copy — this screen reads the `selection` and `sharedDetails` slices. */
36
+ strings: ReusabilityStrings;
37
+ /**
38
+ * The documents the network found for this person, best match FIRST — the
39
+ * backend orders them and the first row wears the "Recommended" pill (SDK
40
+ * `recommended={index === 0}`). One or more; the frame draws two
41
+ * (`Multiple documents`) and one (`Single document`).
42
+ */
43
+ credentials: ReusableCredential[];
44
+ /**
45
+ * "What data will be shared?" was pressed: Core's bottom sheet listing the
46
+ * recipient's data request is presented over the dimmed selection
47
+ * (`shared info`, 7877:68927). Its one button emits `cta` and closes it.
48
+ */
49
+ sharedDetails?: boolean;
50
+ /**
51
+ * Fired with the chosen credential's id, or `REUSABILITY_SCAN_NEW` for the
52
+ * capture row; `onAction` still reports the tap as `cta`. Named like ID
53
+ * Capture's `onDocumentSelect` — `onSelect` is a native <div> event the shell
54
+ * already passes through.
55
+ */
56
+ onCredentialSelect?: (selection: ReusabilitySelection) => void;
57
+ screenId?: string;
58
+ overrides?: PrizmaScreenOverrides;
59
+ onAction?: PrizmaScreenActionHandler;
60
+ }
61
+
62
+ /**
63
+ * Reusability (Incode Bridge) — choose the document to reuse, built on the
64
+ * List archetype (GUIDELINES.md §6-G, `PrizmaListScreen`).
65
+ *
66
+ * ONE screen for three of the section's four frames: `Multiple documents`
67
+ * (7877:68816) and `Single document` (7877:68875) are the same skeleton — nav →
68
+ * Title (H2 + H5) → the credential list → "Scan a new document" → two tertiary
69
+ * links → tag — differing only in how many rows the network returned; and
70
+ * `Shared data` (7877:68927) is that screen with Core's bottom sheet presented
71
+ * over it (`sharedDetails`).
72
+ *
73
+ * WHY ARCHETYPE G. The rows ARE the actions — tapping a document advances the
74
+ * session with no confirm step (consent review 2026-09-16: "remove the confirm
75
+ * button: selecting a document advances the user immediately"; the Web SDK's
76
+ * ENG-57939 built exactly that) — so there is no primary CTA, and G is the one
77
+ * archetype whose band may carry links alone. ID Capture's document chooser is
78
+ * the same shape on the same archetype.
79
+ *
80
+ * Six things are deliberate:
81
+ *
82
+ * 1. The rows are composed here from Core's tokens, not from Core's
83
+ * `PrizmaCardListVerificationOptions`. The frame calls them "Verification
84
+ * options card list", but a reusable credential has a different anatomy —
85
+ * type label + status pill, flag + masked number, verified date — and the
86
+ * capture row is one line with a pill. Core ships neither that row nor a
87
+ * plain status pill; both are proposed (DEVIATIONS #RU-1, #RU-2) and the
88
+ * rules live once in `ReusabilityLayout.css`.
89
+ * 2. Every row is a real `<button>` that emits `cta` and names its credential
90
+ * through `onCredentialSelect` — the one-click rule. No radio, no selected
91
+ * state: the frame draws none and the decision was to drop them. The SDK's
92
+ * shipped `role="radio"` + confirm is the generation BEFORE this frame.
93
+ * 3. The first row wears "Recommended", because the backend sorts best match
94
+ * first; the capture row wears "Slowest". The pills are the frame's own
95
+ * fills on Core tokens; their 12px type is the one value Core's scale
96
+ * cannot express (#RU-2).
97
+ * 4. The flag is Core's raster bank (`PrizmaCountryFlag`, 24×18), never the
98
+ * frame's emoji glyph (owner decision 2026-07-12). A country outside the
99
+ * bank falls back to the emoji the host supplies — Core's own fallback rule
100
+ * (#RU-3).
101
+ * 5. The two links ride the archetype's band: "What data will be shared?" in
102
+ * `linkLabel` (emits `secondary`) and "Incode’s Privacy Policy" in the new
103
+ * `secondaryLinkLabel` slot (emits `tertiary`) — two non-primary actions a
104
+ * host must tell apart (#PI-10), so each has its own action. The band's
105
+ * flat Spacing/16 stands between them where the frame's Button group
106
+ * tightens the pair by 8 (#RU-4).
107
+ * 6. The sheet rides the archetype's new `overlay` slot — the same contract E
108
+ * gained for Biometric Consent (#BC-8): the screen owns the scrim and the
109
+ * anchoring (the foundation's `__sheet-scrim` / `__sheet-panel`), Core owns
110
+ * the panel — `PrizmaBottomSheetContent variant="dialog"` with its H1
111
+ * title, H5 subtitle, the bullet list in `body`, and its own Button Group
112
+ * reduced to "Understood". With the sheet up the rows and links are under
113
+ * the scrim and do not report (#RU-5).
114
+ *
115
+ * The nav keeps the logo and draws no close: the frame's top-right ✕ is the
116
+ * SDK page chrome, not a Prizma screen control (GUIDELINES §3; #RU-6).
117
+ */
118
+ export const ReusabilityCredentialSelectionScreen = forwardRef<
119
+ HTMLDivElement,
120
+ ReusabilityCredentialSelectionScreenProps
121
+ >(
122
+ (
123
+ {
124
+ strings,
125
+ credentials,
126
+ sharedDetails = false,
127
+ onCredentialSelect,
128
+ screenId = sharedDetails
129
+ ? 'reusability-shared-details'
130
+ : `reusability-selection-${credentials.length > 1 ? 'multiple' : 'single'}`,
131
+ overrides,
132
+ onAction,
133
+ ...shell
134
+ },
135
+ ref,
136
+ ) => {
137
+ const { selection, sharedDetails: shared } = strings;
138
+
139
+ const choose = (choice: ReusabilitySelection) => {
140
+ if (sharedDetails) return;
141
+ onCredentialSelect?.(choice);
142
+ onAction?.({ type: 'cta', screenId });
143
+ };
144
+
145
+ const sheet = sharedDetails ? (
146
+ <>
147
+ <div className="prizma-screen-layout__sheet-scrim" aria-hidden="true" />
148
+ <div className="prizma-screen-layout__sheet-panel">
149
+ <PrizmaBottomSheetContent
150
+ variant="dialog"
151
+ title={shared.title}
152
+ subtitle={shared.subtitle}
153
+ body={
154
+ <ul className="reusability-shared-list">
155
+ {shared.items.map((item) => (
156
+ <li key={item}>{item}</li>
157
+ ))}
158
+ </ul>
159
+ }
160
+ buttonGroupProps={{
161
+ primaryLabel: shared.ctaLabel,
162
+ showSecondary: false,
163
+ onPrimaryClick: () => onAction?.({ type: 'cta', screenId }),
164
+ }}
165
+ />
166
+ </div>
167
+ </>
168
+ ) : undefined;
169
+
170
+ return (
171
+ <PrizmaListScreen
172
+ ref={ref}
173
+ title={selection.title}
174
+ subtitle={selection.subtitle}
175
+ linkLabel={selection.sharedDataLink}
176
+ secondaryLinkLabel={selection.privacyPolicyLink}
177
+ verifiedLabel={strings.verifiedLabel}
178
+ navLabel={strings.navLabel}
179
+ screenId={screenId}
180
+ overrides={overrides}
181
+ /* With the sheet up, the links are under the scrim and must not
182
+ report: the sheet's "Understood" is the only action. */
183
+ onAction={sharedDetails ? undefined : onAction}
184
+ overlay={sheet}
185
+ {...shell}
186
+ >
187
+ <div className="reusability-credentials" role="group" aria-label={selection.listLabel}>
188
+ <div className="reusability-credentials__group">
189
+ {credentials.map((credential, index) => (
190
+ <button
191
+ key={credential.id}
192
+ type="button"
193
+ className="reusability-credential"
194
+ onClick={() => choose(credential.id)}
195
+ tabIndex={sharedDetails ? -1 : undefined}
196
+ >
197
+ <span className="reusability-credential__head">
198
+ <span className="reusability-credential__type">
199
+ {selection.documentType[credential.documentType]}
200
+ </span>
201
+ {index === 0 ? (
202
+ <span className="reusability-pill reusability-pill--recommended">
203
+ {selection.recommendedTag}
204
+ </span>
205
+ ) : null}
206
+ </span>
207
+ <span className="reusability-credential__number">
208
+ <span className="reusability-credential__flag" aria-hidden="true">
209
+ <PrizmaCountryFlag
210
+ country={{
211
+ code: credential.countryCode,
212
+ name: credential.countryCode,
213
+ dialCode: '',
214
+ flag: credential.countryFlag ?? '',
215
+ }}
216
+ />
217
+ </span>
218
+ <span>{credential.maskedNumber}</span>
219
+ </span>
220
+ <span className="reusability-credential__verified">
221
+ <span>{selection.verifiedPrefix}</span> <span>{credential.verifiedOn}</span>
222
+ </span>
223
+ </button>
224
+ ))}
225
+ </div>
226
+ <div className="reusability-credentials__group">
227
+ <button
228
+ type="button"
229
+ className="reusability-credential"
230
+ onClick={() => choose(REUSABILITY_SCAN_NEW)}
231
+ tabIndex={sharedDetails ? -1 : undefined}
232
+ >
233
+ <span className="reusability-credential__head">
234
+ <span className="reusability-credential__type">{selection.scanNewLabel}</span>
235
+ <span className="reusability-pill reusability-pill--neutral">{selection.slowestTag}</span>
236
+ </span>
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </PrizmaListScreen>
241
+ );
242
+ },
243
+ );
244
+ ReusabilityCredentialSelectionScreen.displayName = 'ReusabilityCredentialSelectionScreen';
245
+
246
+ export default ReusabilityCredentialSelectionScreen;