@incodetech/prizma-core-screens 0.2.3 → 0.2.7
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.
- package/dist/CurpEnterScreen.d.ts +7 -0
- package/dist/CurpEnterScreen.js +17 -3
- package/dist/CurpGenerateScreen.d.ts +8 -0
- package/dist/CurpGenerateScreen.js +28 -13
- package/dist/CurpLayout.css +8 -34
- package/dist/CurpStrings.d.ts +16 -2
- package/dist/EkybAddressDetailsScreen.d.ts +7 -0
- package/dist/EkybAddressDetailsScreen.js +25 -7
- package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
- package/dist/EkybBusinessDetailsScreen.js +23 -6
- package/dist/EkybCountryScreen.d.ts +13 -5
- package/dist/EkybCountryScreen.js +27 -9
- package/dist/EkybOwnersScreen.d.ts +8 -0
- package/dist/EkybOwnersScreen.js +26 -3
- package/dist/EkybStrings.d.ts +27 -10
- package/dist/EkycCountryCatalog.d.ts +4 -1
- package/dist/EkycCountryCatalog.js +5 -3
- package/dist/EkycCountryFormScreen.d.ts +12 -3
- package/dist/EkycCountryFormScreen.js +28 -10
- package/dist/EkycDetailsScreen.d.ts +7 -0
- package/dist/EkycDetailsScreen.js +21 -4
- package/dist/EkycStrings.d.ts +14 -2
- package/dist/ElectronicSignatureLayout.css +6 -0
- package/dist/EmailInputStrings.d.ts +11 -1
- package/dist/EmailInputVerifyScreen.d.ts +7 -0
- package/dist/EmailInputVerifyScreen.js +17 -3
- package/dist/FaceMatchComparisonScreen.d.ts +3 -3
- package/dist/FaceMatchLayout.css +21 -12
- package/dist/FormScreen.css +16 -0
- package/dist/FormsLongFormScreen.d.ts +8 -0
- package/dist/FormsLongFormScreen.js +34 -13
- package/dist/FormsStrings.d.ts +11 -1
- package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
- package/dist/GeolocationBrowserStepsScreen.js +7 -3
- package/dist/GeolocationStrings.d.ts +3 -0
- package/dist/IdCaptureLayout.css +40 -0
- package/dist/ListScreen.d.ts +2 -0
- package/dist/ListScreen.js +1 -1
- package/dist/PhoneInputLayout.css +10 -9
- package/dist/PhoneInputStrings.d.ts +11 -1
- package/dist/PhoneInputVerifyScreen.d.ts +8 -0
- package/dist/PhoneInputVerifyScreen.js +19 -6
- package/dist/QualifiedSignatureAcceptScreen.js +6 -1
- package/dist/QualifiedSignatureDocumentScreen.d.ts +13 -9
- package/dist/QualifiedSignatureDocumentScreen.js +18 -16
- package/dist/QualifiedSignatureLayout.css +76 -0
- package/dist/QualifiedSignatureStrings.d.ts +10 -0
- package/dist/ScreenNav.css +8 -0
- package/dist/ScreenNav.d.ts +15 -2
- package/dist/ScreenNav.js +10 -3
- package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
- package/dist/VideoSelfieIdCaptureScreen.js +44 -13
- package/dist/VideoSelfieLayout.css +164 -33
- package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
- package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
- package/dist/VideoSelfieSpeakScreen.js +18 -1
- package/dist/VideoSelfieStrings.d.ts +78 -12
- package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
- package/dist/VideoSelfieTurnHeadScreen.js +63 -0
- package/dist/VideoSelfieTutorialScreen.js +13 -5
- package/dist/VideoSelfieWaveScreen.d.ts +53 -0
- package/dist/VideoSelfieWaveScreen.js +62 -0
- package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
- package/dist/WatchlistBusinessFormScreen.js +27 -13
- package/dist/WatchlistBusinessStrings.d.ts +21 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/types.d.ts +3 -1
- package/package.json +14 -3
- package/samples/video-selfie-room.png +0 -0
- package/src/CurpEnterScreen.tsx +21 -4
- package/src/CurpGenerateScreen.tsx +46 -22
- package/src/CurpLayout.css +8 -34
- package/src/CurpStrings.ts +22 -2
- package/src/EkybAddressDetailsScreen.tsx +32 -4
- package/src/EkybBusinessDetailsScreen.tsx +23 -5
- package/src/EkybCountryScreen.tsx +28 -7
- package/src/EkybOwnersScreen.tsx +34 -3
- package/src/EkybStrings.ts +29 -10
- package/src/EkycCountryCatalog.ts +9 -4
- package/src/EkycCountryFormScreen.tsx +51 -12
- package/src/EkycDetailsScreen.tsx +22 -1
- package/src/EkycStrings.ts +14 -2
- package/src/ElectronicSignatureLayout.css +6 -0
- package/src/EmailInputStrings.ts +12 -1
- package/src/EmailInputVerifyScreen.tsx +21 -4
- package/src/FaceMatchComparisonScreen.tsx +3 -3
- package/src/FaceMatchLayout.css +21 -12
- package/src/FormScreen.css +16 -0
- package/src/FormsLongFormScreen.tsx +41 -13
- package/src/FormsStrings.ts +12 -1
- package/src/GeolocationBrowserStepsScreen.tsx +6 -3
- package/src/GeolocationStrings.ts +3 -0
- package/src/IdCaptureLayout.css +40 -0
- package/src/ListScreen.tsx +10 -1
- package/src/PhoneInputLayout.css +10 -9
- package/src/PhoneInputStrings.ts +12 -1
- package/src/PhoneInputVerifyScreen.tsx +21 -5
- package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
- package/src/QualifiedSignatureDocumentScreen.tsx +27 -17
- package/src/QualifiedSignatureLayout.css +76 -0
- package/src/QualifiedSignatureStrings.ts +10 -0
- package/src/ScreenNav.css +8 -0
- package/src/ScreenNav.tsx +35 -5
- package/src/VideoSelfieIdCaptureScreen.tsx +116 -30
- package/src/VideoSelfieLayout.css +164 -33
- package/src/VideoSelfieSelfieCaptureScreen.tsx +104 -59
- package/src/VideoSelfieSpeakScreen.tsx +25 -14
- package/src/VideoSelfieStrings.ts +85 -12
- package/src/VideoSelfieTurnHeadScreen.tsx +151 -0
- package/src/VideoSelfieTutorialScreen.tsx +34 -23
- package/src/VideoSelfieWaveScreen.tsx +135 -0
- package/src/WatchlistBusinessFormScreen.tsx +35 -16
- package/src/WatchlistBusinessStrings.ts +21 -7
- package/src/index.ts +14 -0
- package/src/types.ts +3 -1
package/dist/ScreenNav.css
CHANGED
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
width: 100%;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* With the opt-in back chevron the nav sits on the content column's 24px
|
|
32
|
+
* gutter, so the chevron lines up with the title and content below it. Core's
|
|
33
|
+
* nav already pads its row by Spacing/2, hence the subtraction. A logo-only nav
|
|
34
|
+
* is centred and needs no gutter, so nothing else moves. */
|
|
35
|
+
.prizma-screen-nav--back {
|
|
36
|
+
padding-inline: calc(var(--prizma-spacing-24) - var(--prizma-spacing-2));
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
.prizma-screen-nav__custom-logo {
|
|
32
40
|
display: flex;
|
|
33
41
|
align-items: center;
|
package/dist/ScreenNav.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
1
|
+
import React, { type ComponentPropsWithoutRef, type MouseEventHandler, type ReactNode } from 'react';
|
|
2
2
|
import './ScreenNav.css';
|
|
3
3
|
export interface PrizmaScreenNavProps extends ComponentPropsWithoutRef<'header'> {
|
|
4
4
|
/** Custom brand logo (e.g. an <img> of the client logo). Rendered centered,
|
|
@@ -16,11 +16,24 @@ export interface PrizmaScreenNavProps extends ComponentPropsWithoutRef<'header'>
|
|
|
16
16
|
* only the logo disappears, and the row leaves the a11y tree.
|
|
17
17
|
*/
|
|
18
18
|
reserved?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Opt-in back chevron at the nav's LEFT — Core's own
|
|
21
|
+
* (`PrizmaNavigation.showLeftIcon`, IconChevronLeft). Passing the label turns
|
|
22
|
+
* it on; it is the button's accessible name (i18n). Off by default, so every
|
|
23
|
+
* existing screen keeps the logo-only nav. For a screen the user OPENED from
|
|
24
|
+
* another one (Qualified Signature's document viewers, reached from "View"
|
|
25
|
+
* and from the application-form link) — a way back to where they came from,
|
|
26
|
+
* not a dismiss of the flow, which stays out (#PI-9). Owner call 2026-09-29.
|
|
27
|
+
*/
|
|
28
|
+
backLabel?: string;
|
|
29
|
+
/** Click handler for the back chevron. */
|
|
30
|
+
onBack?: MouseEventHandler<HTMLButtonElement>;
|
|
19
31
|
}
|
|
20
32
|
/**
|
|
21
33
|
* ScreenNav — the logo header of every SDK screen.
|
|
22
34
|
* Sits 12px below the top of the shell (the host frame owns the OS status
|
|
23
|
-
* bar), logo centered, no
|
|
35
|
+
* bar), logo centered, no close button (GUIDELINES.md §3). A back chevron is
|
|
36
|
+
* opt-in through `backLabel`, for opened sub-screens only.
|
|
24
37
|
*/
|
|
25
38
|
export declare const PrizmaScreenNav: React.ForwardRefExoticComponent<PrizmaScreenNavProps & React.RefAttributes<HTMLElement>>;
|
|
26
39
|
export default PrizmaScreenNav;
|
package/dist/ScreenNav.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef } from 'react';
|
|
3
|
+
import { forwardRef, } from 'react';
|
|
4
4
|
import { PrizmaNavigation } from '@incodetech/prizma-core-web/navigation';
|
|
5
5
|
import './ScreenNav.css';
|
|
6
6
|
/**
|
|
7
7
|
* ScreenNav — the logo header of every SDK screen.
|
|
8
8
|
* Sits 12px below the top of the shell (the host frame owns the OS status
|
|
9
|
-
* bar), logo centered, no
|
|
9
|
+
* bar), logo centered, no close button (GUIDELINES.md §3). A back chevron is
|
|
10
|
+
* opt-in through `backLabel`, for opened sub-screens only.
|
|
10
11
|
*/
|
|
11
|
-
export const PrizmaScreenNav = forwardRef(({ logo, navLabel, reserved = false, className, ...rest }, ref) => (_jsx("header", { ref: ref, "aria-hidden": reserved || undefined, className: [
|
|
12
|
+
export const PrizmaScreenNav = forwardRef(({ logo, navLabel, reserved = false, backLabel, onBack, className, ...rest }, ref) => (_jsx("header", { ref: ref, "aria-hidden": reserved || undefined, className: [
|
|
13
|
+
'prizma-screen-nav',
|
|
14
|
+
backLabel && !reserved ? 'prizma-screen-nav--back' : '',
|
|
15
|
+
className ?? '',
|
|
16
|
+
]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' '), ...rest, children: reserved ? null : logo ? (_jsx("span", { className: "prizma-screen-nav__custom-logo", children: logo })) : (_jsx(PrizmaNavigation, { showIncodeLogo: true, "aria-label": navLabel, showLeftIcon: Boolean(backLabel), backLabel: backLabel, onLeftIconClick: onBack })) })));
|
|
12
19
|
PrizmaScreenNav.displayName = 'PrizmaScreenNav';
|
|
13
20
|
export default PrizmaScreenNav;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { type ReactNode } from 'react';
|
|
2
2
|
import { type PrizmaScreenShellProps } from './ScreenShell';
|
|
3
3
|
import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
|
|
4
|
-
import type { VideoSelfieDocumentType, VideoSelfieIdErrorSeverity, VideoSelfieIdSide, VideoSelfieStrings } from './VideoSelfieStrings';
|
|
4
|
+
import type { VideoSelfieDocumentType, VideoSelfieIdCondition, VideoSelfieIdErrorReason, VideoSelfieIdErrorSeverity, VideoSelfieIdSide, VideoSelfieStrings } from './VideoSelfieStrings';
|
|
5
5
|
import './ScreenLayout.css';
|
|
6
6
|
import './ViewfinderScreen.css';
|
|
7
7
|
import './VideoSelfieLayout.css';
|
|
8
|
-
/** Which purple stage section to render (groups 3, 5 and 6)
|
|
9
|
-
|
|
8
|
+
/** Which purple stage section to render (groups 3, 5 and 6), plus the live
|
|
9
|
+
* coaching beat the full-flow frames draw between them. */
|
|
10
|
+
export type VideoSelfieIdCaptureState = 'default' | 'shown' | 'processing' | 'allGood' | 'error' | 'condition';
|
|
10
11
|
export interface VideoSelfieIdCaptureScreenProps extends Omit<PrizmaScreenShellProps, 'dark' | 'title' | 'children'> {
|
|
11
12
|
/** Module copy — this screen reads the `idCapture` slice (+ shared labels). */
|
|
12
13
|
strings: VideoSelfieStrings;
|
|
@@ -21,6 +22,12 @@ export interface VideoSelfieIdCaptureScreenProps extends Omit<PrizmaScreenShellP
|
|
|
21
22
|
/** Whether the rejection invites a retry (`6.1`/`6.3`) or is terminal
|
|
22
23
|
* (`6.2`/`6.4`). Read only when `state="error"`. */
|
|
23
24
|
errorSeverity?: VideoSelfieIdErrorSeverity;
|
|
25
|
+
/** WHY the read was rejected — the `.1`–`.4` suffix of the group 6 section.
|
|
26
|
+
* Read only when `state="error"`. Default `scan`. */
|
|
27
|
+
errorReason?: VideoSelfieIdErrorReason;
|
|
28
|
+
/** Which live condition to coach the user out of. Read only when
|
|
29
|
+
* `state="condition"`. Default `notAligned`. */
|
|
30
|
+
condition?: VideoSelfieIdCondition;
|
|
24
31
|
/** Live camera feed (<video>) or sample still (<img>) behind the frame. */
|
|
25
32
|
camera?: ReactNode;
|
|
26
33
|
/** The captured document still, for the states Core draws one in. */
|
|
@@ -2,14 +2,46 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { PrizmaIdCapture, } from '@incodetech/prizma-core-web/id-capture';
|
|
5
|
+
import { PrizmaCaptureFeedback } from '@incodetech/prizma-core-web/capture-feedback';
|
|
5
6
|
import { PrizmaLoadingSpinner } from '@incodetech/prizma-core-web/loading-spinner';
|
|
6
|
-
import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
|
|
7
7
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
8
8
|
import { PrizmaTag } from '@incodetech/prizma-core-web/tag';
|
|
9
|
+
import { PrizmaTitle } from '@incodetech/prizma-core-web/title';
|
|
9
10
|
import { PrizmaScreenShell } from './ScreenShell';
|
|
10
11
|
import './ScreenLayout.css';
|
|
11
12
|
import './ViewfinderScreen.css';
|
|
12
13
|
import './VideoSelfieLayout.css';
|
|
14
|
+
/**
|
|
15
|
+
* Condition → the Core treatment that already exists for it.
|
|
16
|
+
*
|
|
17
|
+
* Four are `PrizmaIdCapture`'s own error states. `blurry` is the plain error
|
|
18
|
+
* border: blur is a DETECTION result, not a framing mistake, and the owner's
|
|
19
|
+
* 2026-07-28 call is that those pair the plain border with the feedback
|
|
20
|
+
* bubble rather than inventing a semantic overlay (`IdCapture.tsx`, the note
|
|
21
|
+
* above `error`). Either way the message is the bubble's, never the frame's.
|
|
22
|
+
*/
|
|
23
|
+
const CONDITION_STATE = {
|
|
24
|
+
wrongSide: 'error-wrong-side',
|
|
25
|
+
notAligned: 'error-not-aligned',
|
|
26
|
+
glare: 'error-glare',
|
|
27
|
+
tooDark: 'error-too-dark',
|
|
28
|
+
blurry: 'error',
|
|
29
|
+
};
|
|
30
|
+
/** Condition → the bubble's own variant. All six of Core's are used: the
|
|
31
|
+
* wrong-side bubble names the face the user SHOULD be showing, so it flips
|
|
32
|
+
* with the leg — `show-front` on the front, `show-back` on the back. */
|
|
33
|
+
const CONDITION_FEEDBACK = {
|
|
34
|
+
wrongSide: 'not-aligned', // unused — the wrong-side variant is side-keyed below
|
|
35
|
+
notAligned: 'not-aligned',
|
|
36
|
+
blurry: 'blurry',
|
|
37
|
+
glare: 'glare',
|
|
38
|
+
tooDark: 'too-dark',
|
|
39
|
+
};
|
|
40
|
+
/** The wrong-side bubble, per leg. */
|
|
41
|
+
const WRONG_SIDE_FEEDBACK = {
|
|
42
|
+
front: 'show-front',
|
|
43
|
+
back: 'show-back',
|
|
44
|
+
};
|
|
13
45
|
/** Purple stage state → Core `PrizmaIdCapture` state. The frame border, the
|
|
14
46
|
* analyzing sweep, the scrim and the encrypted row are ALL Core's. */
|
|
15
47
|
const CAPTURE_STATE = {
|
|
@@ -18,6 +50,8 @@ const CAPTURE_STATE = {
|
|
|
18
50
|
processing: 'analyzing',
|
|
19
51
|
allGood: 'success',
|
|
20
52
|
error: 'error',
|
|
53
|
+
// Replaced per-condition below — the map needs an entry for every key.
|
|
54
|
+
condition: 'error',
|
|
21
55
|
};
|
|
22
56
|
/** Which beat of the seven-step journey each side occupies — front is step 2
|
|
23
57
|
* (the microphone grant is step 1), back is step 3. */
|
|
@@ -64,23 +98,20 @@ const STEP = { front: 2, back: 3 };
|
|
|
64
98
|
* dropped from `VideoSelfieStrings` with it — no other screen in the module
|
|
65
99
|
* reads one.
|
|
66
100
|
*/
|
|
67
|
-
export const VideoSelfieIdCaptureScreen = forwardRef(({ strings, side = 'front', documentType = 'id', state = 'default', errorSeverity = 'tryAgain', camera, still, screenId = `video-selfie-id-capture-${side}-${state}`, overrides, onAction, className, ...shell }, ref) => {
|
|
68
|
-
const errorLabel = strings.idCapture.errors[errorSeverity];
|
|
101
|
+
export const VideoSelfieIdCaptureScreen = forwardRef(({ strings, side = 'front', documentType = 'id', state = 'default', errorSeverity = 'tryAgain', errorReason = 'scan', condition = 'notAligned', camera, still, screenId = `video-selfie-id-capture-${side}-${state}`, overrides, onAction, className, ...shell }, ref) => {
|
|
102
|
+
const errorLabel = strings.idCapture.errors[errorReason][errorSeverity];
|
|
103
|
+
/* Only the wrong-side bubble is keyed by side — it is the one that names
|
|
104
|
+
the face to show. The other four describe the shot. */
|
|
105
|
+
const conditionCopy = condition === 'wrongSide'
|
|
106
|
+
? strings.idCapture.conditions.wrongSide[side]
|
|
107
|
+
: strings.idCapture.conditions[condition];
|
|
108
|
+
const conditionFeedback = condition === 'wrongSide' ? WRONG_SIDE_FEEDBACK[side] : CONDITION_FEEDBACK[condition];
|
|
69
109
|
return (_jsxs(PrizmaScreenShell, { ref: ref, dark: true, className: className, ...shell, children: [_jsx("div", { className: "prizma-viewfinder__surface", children: camera }), _jsx("div", { className: "video-selfie-stepper", children: _jsx(PrizmaStepper, { steps: 7, currentState: STEP[side], documentType: documentType, completedValueText: strings.stepperCompletedLabel }) }), _jsxs("div", { className: [
|
|
70
110
|
'prizma-viewfinder__content',
|
|
71
111
|
overrides?.align === 'left' ? 'prizma-viewfinder__content--left' : '',
|
|
72
112
|
]
|
|
73
113
|
.filter(Boolean)
|
|
74
|
-
.join(' '), children: [_jsx("div", { className: "video-selfie-band" }), _jsx(PrizmaIdCapture, { type: documentType, state: CAPTURE_STATE[state], isVertical: true, mode: "overlay", scrim: true, showEncryptedLabel: false, still: still }), state === 'processing' ? (_jsx("div", { className: "video-selfie-stage-status", children: _jsx(PrizmaLoadingSpinner, { showTitle: true, titleText: strings.idCapture.processingLabel, loadingLabel: strings.idCapture.processingLabel }) })) : null, state === 'allGood' || state === 'error' ? (_jsx("div", { className: "video-selfie-stage-status", children: state === '
|
|
75
|
-
/* NOT `isMultiline`: that modifier switches the row to
|
|
76
|
-
`align-items: flex-start` so an icon anchors to the top of
|
|
77
|
-
WRAPPED text. The rejection copy renders on ONE line, so it
|
|
78
|
-
only pushed the icon 6.9px below the text centre
|
|
79
|
-
(owner-reported 2026-08-19: "the text is not even aligned to
|
|
80
|
-
the icon"). CORE REQUEST: have the Snackbar follow actual
|
|
81
|
-
wrapping rather than a prop the caller has to predict.
|
|
82
|
-
DEVIATIONS #VS-9. */
|
|
83
|
-
_jsx(PrizmaSnackbar, { state: "error", role: "alert", children: errorLabel })) })) : null] }), _jsx("div", { className: "prizma-screen-layout__footer", children: _jsx(PrizmaTag, { label: strings.verifiedLabel }) })] }));
|
|
114
|
+
.join(' '), children: [_jsx("div", { className: "video-selfie-band", children: state === 'condition' ? (_jsx("div", { className: "video-selfie-feedback", children: _jsx(PrizmaCaptureFeedback, { feedback: conditionFeedback, title: conditionCopy.title, subtitle: conditionCopy.subtitle }) })) : null }), _jsx(PrizmaIdCapture, { className: "video-selfie-stage", type: documentType, state: state === 'condition' ? CONDITION_STATE[condition] : CAPTURE_STATE[state], isVertical: true, mode: "overlay", scrim: true, showEncryptedLabel: false, still: still }), _jsx("div", { className: "video-selfie-band" }), state === 'processing' ? (_jsx("div", { className: "video-selfie-stage-status", children: _jsx("div", { className: "video-selfie-stage-status__card", children: _jsx(PrizmaLoadingSpinner, { showTitle: true, titleText: strings.idCapture.processingLabel, loadingLabel: strings.idCapture.processingLabel }) }) })) : null, state === 'allGood' || state === 'error' ? (_jsx("div", { className: "video-selfie-stage-status", children: _jsx("div", { className: "video-selfie-stage-status__card", role: state === 'error' ? 'alert' : 'status', children: _jsx(PrizmaTitle, { title: state === 'allGood' ? strings.idCapture.successLabel : errorLabel, icon: { type: 'status', status: state === 'allGood' ? 'success' : 'error' } }) }) })) : null] }), _jsx("div", { className: "prizma-screen-layout__footer", children: _jsx(PrizmaTag, { label: strings.verifiedLabel }) })] }));
|
|
84
115
|
});
|
|
85
116
|
VideoSelfieIdCaptureScreen.displayName = 'VideoSelfieIdCaptureScreen';
|
|
86
117
|
export default VideoSelfieIdCaptureScreen;
|
|
@@ -63,6 +63,27 @@
|
|
|
63
63
|
z-index: 1;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/* ── The face ring sits in the SAME place on every screen ────────────────────
|
|
67
|
+
* Figma puts `ring-uploading` at (27, 188) on every frame that draws it —
|
|
68
|
+
* `8.x`, `9.x`, the four head turns and the wave — and never moves it. Left
|
|
69
|
+
* to the flex band the ring drifted instead, because what sits UNDER it
|
|
70
|
+
* changes from screen to screen: an instruction on `8.0`, a spinner and a
|
|
71
|
+
* label on `8.1`, an arrow chip plus a line on the turns, nothing at all
|
|
72
|
+
* behind the wave's card. Centring a taller stack pushes the ring up
|
|
73
|
+
* (measured 150 / 165 / 185 across three screens where the frames say 188),
|
|
74
|
+
* and a capture window that moves between steps reads as a glitch.
|
|
75
|
+
*
|
|
76
|
+
* So the ring band is TOP-aligned and the offset is named once. 188 is
|
|
77
|
+
* Figma's; 84 is where the content column starts here (the status-bar reserve
|
|
78
|
+
* plus the 37 stepper band); 40 is Core's own padding above the stage inside
|
|
79
|
+
* `PrizmaSelfieCapture`. What is left is the air this rule owns. Everything
|
|
80
|
+
* under the ring then flows below it, which is what the frames draw.
|
|
81
|
+
* DEVIATIONS #VS-24. */
|
|
82
|
+
.video-selfie-band--ring {
|
|
83
|
+
justify-content: flex-start;
|
|
84
|
+
padding-top: calc(188px - var(--pcs-status-bar, 47px) - 37px - var(--prizma-spacing-40));
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
/* ── Recording indicator (10.1 / 11.1 / 11.3 / 11.5) ─────────────────────────
|
|
67
88
|
* A sound-wave glyph over the "Recording…" HUD line. The glyph ink is Core's
|
|
68
89
|
* own Static camera ink — the token `PrizmaCaptureManual` uses for the shutter
|
|
@@ -106,6 +127,33 @@
|
|
|
106
127
|
align-items: center;
|
|
107
128
|
}
|
|
108
129
|
|
|
130
|
+
/* The two verdict cards (`10.2` processing, `10.3` success) carry no buttons,
|
|
131
|
+
* so their content stack is two rows where the prompt's is three — left alone
|
|
132
|
+
* they came out ~70px shorter and the sheet jumped as the step advanced. The
|
|
133
|
+
* owner's call (2026-09-30) is that every card in the step is the same size,
|
|
134
|
+
* so the card takes the prompt's measure and centres its content in it.
|
|
135
|
+
*
|
|
136
|
+
* 184 is Figma's own `modal-light` height on `10.0` / `10.1`, which is what
|
|
137
|
+
* the prompt renders at; it is the module's geometry, not a token, and it is
|
|
138
|
+
* a MINIMUM — the card still grows if a translation needs more room.
|
|
139
|
+
* DEVIATIONS #VS-25. */
|
|
140
|
+
/* ⚠ The card, not the wrapper, has to do the centring. `height: 100%` on the
|
|
141
|
+
* wrapper resolved to `auto` — Core's `.prizma-modal` is not a flex container
|
|
142
|
+
* and its height is content-driven, so a percentage height had nothing to
|
|
143
|
+
* measure against and the block stayed pinned to the top (owner, 2026-09-30:
|
|
144
|
+
* "these are aligned to the top instead of centered"). Making the CARD the
|
|
145
|
+
* flex column is what actually distributes the extra height. */
|
|
146
|
+
.video-selfie-sheet-panel--status .prizma-modal {
|
|
147
|
+
min-height: 184px;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.video-selfie-sheet-panel--status .prizma-modal__title {
|
|
154
|
+
text-align: center;
|
|
155
|
+
}
|
|
156
|
+
|
|
109
157
|
/* The card's verdict mark (10.3 / 11.x success). The glyph itself is the
|
|
110
158
|
* canonical bank icon Core's own `PrizmaTitle` uses for `status: success`
|
|
111
159
|
* (`IconStatusPositive` at Title/Icon/Size); only the INK is set here, because
|
|
@@ -155,10 +203,49 @@
|
|
|
155
203
|
color: var(--prizma-text-body-500-secondary);
|
|
156
204
|
}
|
|
157
205
|
|
|
206
|
+
/* ── The document frame's own measure ────────────────────────────────────────
|
|
207
|
+
* `3.x` / `5.x` / `6.x` all draw `ID Capture Frame` at **286 × 455**, centred
|
|
208
|
+
* in the 390 column (52 either side) — and they keep that measure on every
|
|
209
|
+
* device section in the file: 320, 368, 390, 430 and the 892-wide Pixel Fold
|
|
210
|
+
* all show the same 286-wide frame, only the air around it changes.
|
|
211
|
+
*
|
|
212
|
+
* Core's `PrizmaIdCapture` is `width: 100%` + `aspect-ratio: 288/456`, so left
|
|
213
|
+
* alone it takes the whole content column: 342 × 541 at 390, which ran the
|
|
214
|
+
* frame down into the verified tag (owner-reported in the 320 sweep,
|
|
215
|
+
* 2026-09-28, and the reason the stage read broken at every width, 2026-09-30).
|
|
216
|
+
*
|
|
217
|
+
* So the MODULE caps it, exactly as the frames do. `min()` keeps the cap from
|
|
218
|
+
* turning into a floor: on a column narrower than the frame it gives ground
|
|
219
|
+
* instead of spilling, which is what the 320 sections show. The aspect ratio
|
|
220
|
+
* stays Core's. DEVIATIONS #VS-18. */
|
|
221
|
+
.video-selfie-stage {
|
|
222
|
+
/* Figma geometry, not a token: `ID Capture Frame` is 286 wide on every
|
|
223
|
+
device section of this module. */
|
|
224
|
+
width: min(286px, 100%);
|
|
225
|
+
/* The viewfinder column stretches its children, so a child that names its
|
|
226
|
+
own width lands flush LEFT — which put the frame 16px off the phone's
|
|
227
|
+
centre line while the status card floating on it stayed centred. */
|
|
228
|
+
margin-inline: auto;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* `IdCaptureLayout.css` gives every vertical stage inside a viewfinder a
|
|
232
|
+
* `flex-basis` of "the full column over the aspect", so the frame can give up
|
|
233
|
+
* height on a short screen and the aspect then narrows it back (Camila,
|
|
234
|
+
* 2026-09-28). That machinery is right and stays — it just has to start from
|
|
235
|
+
* THIS module's capped width, not the whole column: fed 342 it computed a
|
|
236
|
+
* 541-tall frame and then clamped the width back to 286, which is how the
|
|
237
|
+
* stage ended up 286 × 541 instead of 286 × 455 and grew into the verified
|
|
238
|
+
* tag. Same formula, capped input. */
|
|
239
|
+
.prizma-viewfinder__content
|
|
240
|
+
> .video-selfie-stage.prizma-id-capture--vertical:not(.prizma-id-capture--poa)
|
|
241
|
+
.prizma-id-capture__frame {
|
|
242
|
+
flex-basis: calc(min(286px, 100cqw - 2 * var(--prizma-spacing-24)) * 456 / 288);
|
|
243
|
+
}
|
|
244
|
+
|
|
158
245
|
/* ── The captured document inside the vertical frame ─────────────────────────
|
|
159
246
|
* Groups 3 and 5 draw the ID UPRIGHT in a portrait frame: the card is rotated a
|
|
160
|
-
* quarter turn so its long edge runs down the screen, exactly as `3.2`
|
|
161
|
-
* `5.2` show it (the licence's text reads bottom-to-top).
|
|
247
|
+
* quarter turn so its long edge runs down the screen, exactly as `3.1`/`3.2`
|
|
248
|
+
* and `5.1`/`5.2` show it (the licence's text reads bottom-to-top).
|
|
162
249
|
*
|
|
163
250
|
* The sample scans are landscape (328×202). Dropped into a portrait frame with
|
|
164
251
|
* Core's `object-fit: cover` they scaled 4.2× to fill the height and lost their
|
|
@@ -166,40 +253,41 @@
|
|
|
166
253
|
* zoomed in to fit height, when it should be 90 degrees to fit the frame").
|
|
167
254
|
* Rotating instead of cropping keeps the whole document legible.
|
|
168
255
|
*
|
|
169
|
-
*
|
|
170
|
-
* still lays out on its pre-transform box: the image is sized to the frame's
|
|
171
|
-
* height × width, then turned, so it lands exactly frame-sized. No literal —
|
|
172
|
-
* both are percentages of whatever box Core gives it. DEVIATIONS #VS-6.
|
|
173
|
-
*/
|
|
174
|
-
/* The captured document is landscape (a card scan); the VERTICAL frame is
|
|
175
|
-
* portrait. `object-fit: cover` cropped its sides away and blew it up 4.2× —
|
|
176
|
-
* the band of static the owner reported. Figma turns the card a quarter turn
|
|
177
|
-
* instead, so the whole document stays legible with its text running up the
|
|
178
|
-
* side, exactly as `3.2` / `5.2` draw it.
|
|
179
|
-
*
|
|
180
|
-
* The image is sized to the frame's height × width BEFORE the turn, because a
|
|
256
|
+
* The image is sized to the frame's HEIGHT × WIDTH before the turn, because a
|
|
181
257
|
* rotated element still lays out on its pre-transform box — so after the 90°
|
|
182
258
|
* it lands exactly frame-sized. Both values are percentages of whatever box
|
|
183
|
-
* Core gives it, so nothing is hardcoded.
|
|
259
|
+
* Core gives it, so nothing is hardcoded.
|
|
260
|
+
*
|
|
261
|
+
* ⚠ The first pass centred that oversized box with `display: grid;
|
|
262
|
+
* place-items: center`, which does NOT centre it: a grid item WIDER than its
|
|
263
|
+
* area is clamped to the start edge (the browsers' overflow-safety rule). The
|
|
264
|
+
* card therefore laid out at the frame's top-left, and turning it around ITS
|
|
265
|
+
* centre threw it 75px right and 75px up — hanging off two sides of the frame
|
|
266
|
+
* and across the camera. That is the "ID shown in a very weird way" report
|
|
267
|
+
* (owner, 2026-09-30). Absolute centring has no such clamp, so the turn
|
|
268
|
+
* happens around the frame's real middle. DEVIATIONS #VS-6. */
|
|
184
269
|
.prizma-id-capture--vertical .prizma-id-capture__still {
|
|
185
270
|
/* A size container, so the image can be measured against the frame's OWN
|
|
186
271
|
box — `cqh`/`cqw` below are the only way to swap width and height without
|
|
187
272
|
writing either number down. The frame has definite dimensions, which is
|
|
188
273
|
what `container-type: size` requires. */
|
|
189
274
|
container-type: size;
|
|
190
|
-
display: grid;
|
|
191
|
-
place-items: center;
|
|
192
275
|
overflow: hidden;
|
|
193
276
|
}
|
|
194
277
|
|
|
195
278
|
.prizma-id-capture--vertical .prizma-id-capture__still > img {
|
|
196
279
|
/* Swapped on purpose: the image is laid out at the frame's HEIGHT × WIDTH,
|
|
197
|
-
then turned
|
|
280
|
+
then turned, so it lands exactly frame-sized. */
|
|
281
|
+
position: absolute;
|
|
282
|
+
inset: auto;
|
|
283
|
+
top: 50%;
|
|
284
|
+
left: 50%;
|
|
198
285
|
width: 100cqh;
|
|
199
286
|
height: 100cqw;
|
|
200
287
|
max-width: none;
|
|
201
288
|
object-fit: cover;
|
|
202
|
-
|
|
289
|
+
/* Centre first, THEN turn — around the frame's own middle. */
|
|
290
|
+
transform: translate(-50%, -50%) rotate(90deg);
|
|
203
291
|
transform-origin: center;
|
|
204
292
|
}
|
|
205
293
|
|
|
@@ -222,26 +310,69 @@
|
|
|
222
310
|
pointer-events: none;
|
|
223
311
|
}
|
|
224
312
|
|
|
225
|
-
|
|
226
|
-
|
|
313
|
+
/* The card itself: hugs its content, never wider than the document frame it
|
|
314
|
+
* floats on (Figma's widest, `6.1.3`'s three-line rejection, is 260 inside the
|
|
315
|
+
* 286 frame), and always dark — the camera stage never mode-flips. The mark
|
|
316
|
+
* stacks above the label because the content is Core's `PrizmaTitle`; the card
|
|
317
|
+
* only supplies the surface and the air around it. */
|
|
318
|
+
.video-selfie-stage-status__card {
|
|
319
|
+
box-sizing: border-box;
|
|
320
|
+
max-width: min(260px, 100%);
|
|
321
|
+
padding: var(--prizma-spacing-24);
|
|
227
322
|
border-radius: var(--prizma-border-radius-medium);
|
|
228
|
-
background: var(--prizma-surface-neutral-900-
|
|
323
|
+
background: var(--prizma-surface-neutral-900-static);
|
|
229
324
|
}
|
|
230
325
|
|
|
231
|
-
/*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
326
|
+
/* Both pieces inside the card sit ON that dark surface, so their ink is the
|
|
327
|
+
* camera HUD's own Static white rather than the theme's primary — the same
|
|
328
|
+
* re-seat `SelfieCapture.css` does for its overlay status band. */
|
|
329
|
+
.video-selfie-stage-status__card .prizma-title__heading,
|
|
330
|
+
.video-selfie-stage-status__card .prizma-loading-spinner__title {
|
|
331
|
+
color: var(--prizma-text-body-0-static);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ── The coaching bubble above the document frame ────────────────────────────
|
|
335
|
+
* The five condition frames put `Capture feedback` at the TOP of the content,
|
|
336
|
+
* over the camera and above the frame. The band it sits in is already the
|
|
337
|
+
* flexible one; this only pins it to the band's top edge and lifts it over
|
|
338
|
+
* the stage's scrim (the trap `ViewfinderScreen.css` documents on `__intro`:
|
|
339
|
+
* the scrim is an absolutely positioned sibling with an escaping box-shadow,
|
|
340
|
+
* so unlifted content renders under an 80% veil). The bubble's own width,
|
|
341
|
+
* radius, ink and illustration are Core's. DEVIATIONS #VS-28. */
|
|
342
|
+
.video-selfie-feedback {
|
|
343
|
+
position: relative;
|
|
344
|
+
z-index: 1;
|
|
345
|
+
width: 100%;
|
|
346
|
+
/* Top of the band, which is directly under the stepper — where the five
|
|
347
|
+
condition frames seat it (bubble top y≈88 of 844). */
|
|
348
|
+
margin-block: 0 auto;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ── The head-turn mark and its instruction ──────────────────────────────────
|
|
352
|
+
* The `🙂↔️ Turn Head` band puts a 48 circled arrow between the ring and the
|
|
353
|
+
* line that says which way to turn (`element container` 342×48, the glyph
|
|
354
|
+
* centred, then 16 to the text). The gap is Core's own Spacing/16; the copy is
|
|
355
|
+
* the foundation's `.prizma-viewfinder__title`, unchanged. Lifted over the
|
|
356
|
+
* stage's scrim for the reason `ViewfinderScreen.css` documents on `__intro`.
|
|
357
|
+
* DEVIATIONS #VS-20. */
|
|
358
|
+
.video-selfie-turn {
|
|
359
|
+
position: relative;
|
|
360
|
+
z-index: 1;
|
|
237
361
|
display: flex;
|
|
238
362
|
flex-direction: column;
|
|
239
|
-
align-items:
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: var(--prizma-spacing-16);
|
|
240
365
|
width: 100%;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* The mark is ONE bank glyph at 2xl — a filled disc with the arrow knocked
|
|
369
|
+
* out — so this only inks it. The first pass wrapped a 24 bare arrow in a
|
|
370
|
+
* hand-made disc and read as a thin chevron on the camera. */
|
|
371
|
+
.video-selfie-turn__mark {
|
|
372
|
+
display: flex;
|
|
373
|
+
align-items: center;
|
|
374
|
+
justify-content: center;
|
|
375
|
+
color: var(--prizma-icon-neutral-50-static);
|
|
245
376
|
}
|
|
246
377
|
|
|
247
378
|
/* Step rows — the <ol> `PrizmaList` would supply, composed here only because
|
|
@@ -1,52 +1,69 @@
|
|
|
1
1
|
import React, { type ReactNode } from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type PrizmaScreenShellProps } from './ScreenShell';
|
|
3
3
|
import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
|
|
4
|
-
import type { VideoSelfieFeedbackKind, VideoSelfieStrings } from './VideoSelfieStrings';
|
|
4
|
+
import type { VideoSelfieDocumentType, VideoSelfieFeedbackKind, VideoSelfieStrings } from './VideoSelfieStrings';
|
|
5
5
|
import './ScreenLayout.css';
|
|
6
|
+
import './ViewfinderScreen.css';
|
|
7
|
+
import './VideoSelfieLayout.css';
|
|
6
8
|
/** Which purple liveness section to render (group 8). */
|
|
7
9
|
export type VideoSelfieSelfieState = 'start' | 'processing' | 'success' | 'failed';
|
|
8
|
-
export interface VideoSelfieSelfieCaptureScreenProps extends Omit<
|
|
10
|
+
export interface VideoSelfieSelfieCaptureScreenProps extends Omit<PrizmaScreenShellProps, 'dark' | 'title' | 'children'> {
|
|
9
11
|
/** Module copy — this screen reads the `selfieCapture` slice. */
|
|
10
12
|
strings: VideoSelfieStrings;
|
|
11
13
|
/** Which purple section of group 8 to render. Default `start`. */
|
|
12
14
|
state?: VideoSelfieSelfieState;
|
|
13
15
|
/**
|
|
14
|
-
* Which of group 9's
|
|
15
|
-
*
|
|
16
|
+
* Which of group 9's live-feedback lines to show under the ring. Only
|
|
17
|
+
* meaningful while the ring is live (`state="start"`): the sections of
|
|
16
18
|
* group 9 are that same frame with a different instruction. Omit for `8.0`,
|
|
17
19
|
* which shows the plain "Look at your front camera" line.
|
|
18
20
|
*/
|
|
19
21
|
feedback?: VideoSelfieFeedbackKind;
|
|
20
|
-
/**
|
|
22
|
+
/** Which document the journey collects — the Stepper's own `documentType`. */
|
|
23
|
+
documentType?: VideoSelfieDocumentType;
|
|
24
|
+
/** Live camera feed (<video>) or sample still (<img>), full-bleed behind the
|
|
25
|
+
* ring — NOT inside it. The overlay recipe makes the ring a window. */
|
|
21
26
|
camera?: ReactNode;
|
|
22
27
|
screenId?: string;
|
|
23
28
|
overrides?: PrizmaScreenOverrides;
|
|
24
29
|
onAction?: PrizmaScreenActionHandler;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
|
-
* Video Selfie — the liveness ring (Archetype
|
|
28
|
-
* ONE screen for
|
|
29
|
-
* `selfie capture feedback 9.
|
|
30
|
-
* variants of Core's `PrizmaSelfieCapture`, which
|
|
31
|
-
*
|
|
32
|
-
* typography and every verdict treatment. None of it is re-implemented (§7),
|
|
33
|
-
* and this file declares no layout at all.
|
|
32
|
+
* Video Selfie — the liveness ring (Archetype D, GUIDELINES.md §6-D; always
|
|
33
|
+
* dark). ONE screen for every purple section of groups 8 and 9: `selfie
|
|
34
|
+
* capture 8.0–8.3` and every `selfie capture feedback 9.x` message. They are
|
|
35
|
+
* `state` × `feedbackText` variants of Core's `PrizmaSelfieCapture`, which is
|
|
36
|
+
* exactly how the component is axed.
|
|
34
37
|
*
|
|
35
|
-
* **
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
38
|
+
* **Rebuilt 2026-09-30 to follow the frames.** The first pass shipped Core's
|
|
39
|
+
* INLINE recipe — the feed inside a circular window on a WHITE surface, the
|
|
40
|
+
* verdicts dissolving the ring into a centred mark — and logged the gap. On
|
|
41
|
+
* the module page that read as a different product from the rest of the flow:
|
|
42
|
+
* a small ring on white between two full-bleed dark camera screens, with the
|
|
43
|
+
* ring vanishing on the very beats Figma keeps it (owner, 2026-09-30: "they
|
|
44
|
+
* are looking very broken… a broken experience"). Rather than hand-roll the
|
|
45
|
+
* full-bleed composition here, Core's `PrizmaSelfieCapture` grew the
|
|
46
|
+
* `mode="overlay"` + `scrim` axis `PrizmaIdCapture` already carries — it is
|
|
47
|
+
* the Figma library's own `Capture / Selfie overlay` component — and this
|
|
48
|
+
* screen composes it. Nothing about the ring, the scrim, the glow sweeps, the
|
|
49
|
+
* verdict marks or the instruction typography is re-implemented (§7).
|
|
50
|
+
* DEVIATIONS #VS-22.
|
|
44
51
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
52
|
+
* The composition, measured off `8.0` (390×844):
|
|
53
|
+
*
|
|
54
|
+
* - the camera runs FULL-BLEED on `.prizma-viewfinder__surface` (`selfie bg
|
|
55
|
+
* image`, 390×844 — it passes under the browser chrome too);
|
|
56
|
+
* - the journey stepper sits in the band the logo nav normally occupies
|
|
57
|
+
* (`top content` y=56, the Stepper 342 wide at x=24), the module-wide
|
|
58
|
+
* treatment `VideoSelfieLayout.css` owns;
|
|
59
|
+
* - the ring is 335 centred in the remaining air (Figma 27,188 → 362,523
|
|
60
|
+
* against a 708-high content area), with the instruction stacked under it.
|
|
61
|
+
* Core's own Spacing/32 gap replaces Figma's 25.5 (the token wins, §4).
|
|
62
|
+
*
|
|
63
|
+
* NO verified tag: `8.x` and `9.x` draw nothing between the instruction and
|
|
64
|
+
* the bottom of the screen (GUIDELINES §1 — follow the module's section, not
|
|
65
|
+
* the default). The document stage keeps its tag because its own sections
|
|
66
|
+
* draw one (#VS-3).
|
|
50
67
|
*/
|
|
51
68
|
export declare const VideoSelfieSelfieCaptureScreen: React.ForwardRefExoticComponent<VideoSelfieSelfieCaptureScreenProps & React.RefAttributes<HTMLDivElement>>;
|
|
52
69
|
export default VideoSelfieSelfieCaptureScreen;
|