@incodetech/prizma-core-screens 0.2.4 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BiometricConsentScreen.d.ts +95 -0
- package/dist/BiometricConsentScreen.js +91 -0
- package/dist/BiometricConsentStrings.d.ts +84 -0
- package/dist/BiometricConsentStrings.js +31 -0
- package/dist/CaptureScreen.d.ts +13 -0
- package/dist/CaptureScreen.js +2 -2
- 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/DemoScreensLayout.css +16 -0
- package/dist/DemoScreensRedirectScreen.d.ts +6 -1
- package/dist/DemoScreensRedirectScreen.js +3 -2
- package/dist/DemoScreensStrings.d.ts +5 -0
- package/dist/DigitalIdUploadErrorScreen.d.ts +31 -0
- package/dist/DigitalIdUploadErrorScreen.js +26 -0
- package/dist/DigitalIdUploadFileTooLargeScreen.d.ts +27 -0
- package/dist/DigitalIdUploadFileTooLargeScreen.js +21 -0
- package/dist/DigitalIdUploadReviewScreen.d.ts +57 -0
- package/dist/DigitalIdUploadReviewScreen.js +44 -0
- package/dist/DigitalIdUploadStrings.d.ts +64 -0
- package/dist/DigitalIdUploadStrings.js +19 -0
- package/dist/DigitalIdUploadTutorialScreen.d.ts +28 -0
- package/dist/DigitalIdUploadTutorialScreen.js +23 -0
- package/dist/DocumentCaptureIllustration.d.ts +1 -1
- 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 +41 -10
- package/dist/EkybOwnersScreen.d.ts +8 -0
- package/dist/EkybOwnersScreen.js +26 -3
- package/dist/EkybStrings.d.ts +32 -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/ErrorScreensScreen.d.ts +5 -2
- package/dist/ErrorScreensScreen.js +15 -4
- package/dist/ErrorScreensStrings.d.ts +17 -2
- package/dist/ErrorScreensStrings.js +18 -10
- package/dist/FaceCaptureCapturingScreen.d.ts +63 -0
- package/dist/FaceCaptureCapturingScreen.js +62 -2
- package/dist/FaceCaptureLayout.css +69 -4
- package/dist/FaceCaptureStrings.d.ts +23 -0
- package/dist/FaceMatchComparisonScreen.d.ts +3 -3
- package/dist/FaceMatchLayout.css +21 -12
- package/dist/FormScreen.css +30 -0
- package/dist/FormScreen.d.ts +28 -0
- package/dist/FormScreen.js +3 -2
- 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 +205 -2
- package/dist/IdCaptureStageScreen.d.ts +47 -0
- package/dist/IdCaptureStageScreen.js +39 -3
- package/dist/IdCaptureStrings.d.ts +19 -0
- package/dist/ListScreen.d.ts +21 -0
- package/dist/ListScreen.js +7 -2
- package/dist/LoaderScreen.d.ts +7 -5
- package/dist/LoaderScreen.js +7 -5
- package/dist/LoadersStrings.d.ts +18 -5
- package/dist/OcrReviewErrorScreen.d.ts +30 -0
- package/dist/OcrReviewErrorScreen.js +24 -0
- package/dist/OcrReviewLayout.css +24 -0
- package/dist/OcrReviewLoadingScreen.d.ts +25 -0
- package/dist/OcrReviewLoadingScreen.js +19 -0
- package/dist/OcrReviewScreen.d.ts +76 -0
- package/dist/OcrReviewScreen.js +65 -0
- package/dist/OcrReviewStrings.d.ts +47 -0
- package/dist/OcrReviewStrings.js +17 -0
- package/dist/OcrReviewSuccessScreen.d.ts +30 -0
- package/dist/OcrReviewSuccessScreen.js +24 -0
- package/dist/PermissionsAllowScreen.d.ts +8 -11
- package/dist/PermissionsAllowScreen.js +8 -11
- package/dist/PermissionsAskScreen.d.ts +41 -0
- package/dist/PermissionsAskScreen.js +43 -0
- package/dist/PermissionsBrowserStepsScreen.d.ts +9 -2
- package/dist/PermissionsBrowserStepsScreen.js +12 -3
- package/dist/PermissionsLayout.css +47 -0
- package/dist/PermissionsStrings.d.ts +20 -9
- package/dist/PermissionsStrings.js +10 -4
- package/dist/PhoneInputLayout.css +10 -9
- package/dist/PhoneInputOtpScreen.d.ts +8 -0
- package/dist/PhoneInputOtpScreen.js +26 -19
- package/dist/PhoneInputStrings.d.ts +22 -4
- package/dist/PhoneInputVerifyScreen.d.ts +8 -0
- package/dist/PhoneInputVerifyScreen.js +36 -7
- package/dist/QualifiedSignatureAcceptScreen.js +6 -1
- package/dist/QualifiedSignatureDocumentScreen.d.ts +17 -10
- package/dist/QualifiedSignatureDocumentScreen.js +22 -17
- package/dist/QualifiedSignatureLayout.css +76 -0
- package/dist/QualifiedSignatureResultScreen.d.ts +16 -13
- package/dist/QualifiedSignatureResultScreen.js +17 -18
- package/dist/QualifiedSignatureStrings.d.ts +19 -2
- package/dist/ResultScreen.d.ts +8 -0
- package/dist/ResultScreen.js +14 -3
- package/dist/ReusabilityCredentialSelectionScreen.d.ts +95 -0
- package/dist/ReusabilityCredentialSelectionScreen.js +94 -0
- package/dist/ReusabilityLayout.css +241 -0
- package/dist/ReusabilityPrivacyPolicyScreen.d.ts +55 -0
- package/dist/ReusabilityPrivacyPolicyScreen.js +44 -0
- package/dist/ReusabilityStrings.d.ts +85 -0
- package/dist/ReusabilityStrings.js +37 -0
- package/dist/ReviewScreen.d.ts +38 -12
- package/dist/ReviewScreen.js +41 -8
- package/dist/ScreenLayout.css +382 -6
- package/dist/ScreenNav.css +8 -0
- package/dist/ScreenNav.d.ts +15 -2
- package/dist/ScreenNav.js +10 -3
- package/dist/VideoSelfieErrorScreen.js +1 -1
- package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
- package/dist/VideoSelfieIdCaptureScreen.js +45 -14
- package/dist/VideoSelfieLayout.css +467 -34
- package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
- package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
- package/dist/VideoSelfieSpeakScreen.js +19 -2
- package/dist/VideoSelfieStrings.d.ts +82 -13
- package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
- package/dist/VideoSelfieTurnHeadScreen.js +66 -0
- package/dist/VideoSelfieTutorialScreen.d.ts +9 -0
- package/dist/VideoSelfieTutorialScreen.js +22 -5
- package/dist/VideoSelfieWaveScreen.d.ts +53 -0
- package/dist/VideoSelfieWaveScreen.js +62 -0
- package/dist/ViewfinderScreen.css +36 -0
- package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
- package/dist/WatchlistBusinessFormScreen.js +27 -13
- package/dist/WatchlistBusinessStrings.d.ts +21 -7
- package/dist/flows.d.ts +8 -0
- package/dist/flows.js +17 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.js +14 -0
- package/dist/styles.css +3 -0
- package/dist/types.d.ts +3 -1
- package/package.json +79 -3
- package/samples/video-selfie-camera-wide.jpg +0 -0
- package/samples/video-selfie-room-wide.jpg +0 -0
- package/samples/video-selfie-room.png +0 -0
- package/src/BiometricConsentScreen.tsx +192 -0
- package/src/BiometricConsentStrings.ts +119 -0
- package/src/CaptureScreen.tsx +17 -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/DemoScreensLayout.css +16 -0
- package/src/DemoScreensRedirectScreen.tsx +14 -6
- package/src/DemoScreensStrings.ts +5 -0
- package/src/DigitalIdUploadErrorScreen.tsx +83 -0
- package/src/DigitalIdUploadFileTooLargeScreen.tsx +67 -0
- package/src/DigitalIdUploadReviewScreen.tsx +119 -0
- package/src/DigitalIdUploadStrings.ts +88 -0
- package/src/DigitalIdUploadTutorialScreen.tsx +59 -0
- package/src/DocumentCaptureIllustration.tsx +1 -1
- package/src/EkybAddressDetailsScreen.tsx +32 -4
- package/src/EkybBusinessDetailsScreen.tsx +23 -5
- package/src/EkybCountryScreen.tsx +45 -7
- package/src/EkybOwnersScreen.tsx +34 -3
- package/src/EkybStrings.ts +34 -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/ErrorScreensScreen.tsx +15 -4
- package/src/ErrorScreensStrings.ts +37 -12
- package/src/FaceCaptureCapturingScreen.tsx +102 -13
- package/src/FaceCaptureLayout.css +69 -4
- package/src/FaceCaptureStrings.ts +24 -0
- package/src/FaceMatchComparisonScreen.tsx +3 -3
- package/src/FaceMatchLayout.css +21 -12
- package/src/FormScreen.css +30 -0
- package/src/FormScreen.tsx +34 -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 +205 -2
- package/src/IdCaptureStageScreen.tsx +75 -0
- package/src/IdCaptureStrings.ts +19 -0
- package/src/ListScreen.tsx +58 -10
- package/src/LoaderScreen.tsx +7 -5
- package/src/LoadersStrings.ts +18 -5
- package/src/OcrReviewErrorScreen.tsx +53 -0
- package/src/OcrReviewLayout.css +24 -0
- package/src/OcrReviewLoadingScreen.tsx +48 -0
- package/src/OcrReviewScreen.tsx +190 -0
- package/src/OcrReviewStrings.ts +69 -0
- package/src/OcrReviewSuccessScreen.tsx +52 -0
- package/src/PermissionsAllowScreen.tsx +9 -10
- package/src/PermissionsAskScreen.tsx +105 -0
- package/src/PermissionsBrowserStepsScreen.tsx +11 -3
- package/src/PermissionsLayout.css +47 -0
- package/src/PermissionsStrings.ts +42 -12
- package/src/PhoneInputLayout.css +10 -9
- package/src/PhoneInputOtpScreen.tsx +23 -3
- package/src/PhoneInputStrings.ts +23 -4
- package/src/PhoneInputVerifyScreen.tsx +41 -5
- package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
- package/src/QualifiedSignatureDocumentScreen.tsx +31 -19
- package/src/QualifiedSignatureLayout.css +76 -0
- package/src/QualifiedSignatureResultScreen.tsx +28 -26
- package/src/QualifiedSignatureStrings.ts +19 -2
- package/src/ResultScreen.tsx +33 -2
- package/src/ReusabilityCredentialSelectionScreen.tsx +246 -0
- package/src/ReusabilityLayout.css +241 -0
- package/src/ReusabilityPrivacyPolicyScreen.tsx +109 -0
- package/src/ReusabilityStrings.ts +127 -0
- package/src/ReviewScreen.tsx +96 -27
- package/src/ScreenLayout.css +382 -6
- package/src/ScreenNav.css +8 -0
- package/src/ScreenNav.tsx +35 -5
- package/src/VideoSelfieErrorScreen.tsx +1 -1
- package/src/VideoSelfieIdCaptureScreen.tsx +125 -31
- package/src/VideoSelfieLayout.css +467 -34
- package/src/VideoSelfieSelfieCaptureScreen.tsx +110 -57
- package/src/VideoSelfieSpeakScreen.tsx +34 -15
- package/src/VideoSelfieStrings.ts +89 -14
- package/src/VideoSelfieTurnHeadScreen.tsx +162 -0
- package/src/VideoSelfieTutorialScreen.tsx +45 -23
- package/src/VideoSelfieWaveScreen.tsx +143 -0
- package/src/ViewfinderScreen.css +36 -0
- package/src/WatchlistBusinessFormScreen.tsx +35 -16
- package/src/WatchlistBusinessStrings.ts +21 -7
- package/src/flows.ts +25 -0
- package/src/index.ts +88 -3
- package/src/styles.css +3 -0
- package/src/types.ts +3 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
|
|
5
|
+
import { PrizmaCountryFlag } from '@incodetech/prizma-core-web/combined';
|
|
5
6
|
import { PrizmaFormScreen } from './FormScreen';
|
|
6
7
|
import './ScreenLayout.css';
|
|
7
8
|
/**
|
|
@@ -26,33 +27,63 @@ import './ScreenLayout.css';
|
|
|
26
27
|
* live interaction) — the same mechanism Phone Input's `countryCode` and
|
|
27
28
|
* CURP's `Gene.dropdown` sections use. The search bar Figma draws inside the
|
|
28
29
|
* menu is Core's own `searchable`.
|
|
29
|
-
* 3.
|
|
30
|
-
* 5707:54174 draws
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* 3. Menu rows and the closed field read the same flag + "United States",
|
|
31
|
+
* no dial code. Figma 5707:54174 draws the rows with Phone Input's "+1"
|
|
32
|
+
* prefixes, dropped by the owner (Elena, 2026-09-28) because a country of
|
|
33
|
+
* registration has no phone prefix. The flag is Core's raster in the
|
|
34
|
+
* dropdown's leading slot, not an emoji in the label (Elena, 2026-10-23;
|
|
35
|
+
* #PI-3, the WatchlistBusinessFormScreen precedent).
|
|
34
36
|
*
|
|
35
37
|
* This page is the only eKYB form page with NO "Back" secondary and NO Stepper —
|
|
36
38
|
* it is step 0, before the three-step wizard starts.
|
|
39
|
+
*
|
|
40
|
+
* `emptyError` is Continue pressed with no country chosen (Elena, 2026-09-30,
|
|
41
|
+
* GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
|
|
42
|
+
* `emptyError`): the dropdown renders empty, with no placeholder, in Core's
|
|
43
|
+
* `error` state with `requiredError` under it, and Continue is disabled. The
|
|
44
|
+
* field draws no `*`, but it is the page's only input and the flow cannot go on
|
|
45
|
+
* without it, so it is treated as required. `empty` keeps Continue enabled.
|
|
37
46
|
*/
|
|
38
|
-
export const EkybCountryScreen = forwardRef(({ strings, state = 'empty', countries = [], value, screenId = `ekyb-country-${state}`, overrides, onAction, ...shell }, ref) => {
|
|
47
|
+
export const EkybCountryScreen = forwardRef(({ strings, state = 'empty', countries = [], value, screenId = `ekyb-country-${state}`, ctaDisabled, overrides, onAction, ...shell }, ref) => {
|
|
39
48
|
const copy = strings.country;
|
|
40
49
|
// `empty` is the only section drawn on the placeholder; both other sections
|
|
41
50
|
// draw a chosen country in the closed field.
|
|
42
|
-
const
|
|
43
|
-
|
|
51
|
+
const emptyError = state === 'emptyError';
|
|
52
|
+
const selected = state === 'empty' || emptyError ? null : (value ?? countries[0]?.value ?? null);
|
|
53
|
+
return (_jsx(PrizmaFormScreen, { ref: ref, title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel,
|
|
54
|
+
/* Disabled only after the empty submit; `empty` keeps it enabled. */
|
|
55
|
+
ctaDisabled: emptyError || ctaDisabled, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
|
|
44
56
|
/* Every eKYB form frame LEFT-aligns its title block, while the archetype
|
|
45
57
|
defaults to centred. The module supplies its designed alignment
|
|
46
58
|
through the sanctioned override rather than declaring any layout of
|
|
47
59
|
its own, and a host that passes `align` still wins (#GEO-2). */
|
|
48
|
-
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction, ...shell, children: _jsx(PrizmaDropdown, { label: copy.field.label,
|
|
60
|
+
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction, ...shell, children: _jsx(PrizmaDropdown, { label: copy.field.label,
|
|
61
|
+
/* Required, but UNMARKED: a single-input screen shows no asterisk
|
|
62
|
+
and no legend (Core pattern "Optimistic submission", single-input
|
|
63
|
+
screens; Elena 2026-09-30). The requirement surfaces on submit,
|
|
64
|
+
through the empty-submit error. */
|
|
65
|
+
/* The error state draws the field with nothing in it, not even the
|
|
66
|
+
placeholder (the `FormsFieldScreen` precedent). */
|
|
67
|
+
placeholder: emptyError ? undefined : copy.field.placeholder, error: emptyError, errorText: emptyError ? strings.requiredError : undefined, options: countries,
|
|
49
68
|
/* Uncontrolled: a screen is one static spec state, so no `onChange` is
|
|
50
69
|
needed. Core's `readOnly`/`disabled` are NOT used to freeze it —
|
|
51
70
|
they paint the DISABLED grey surface on a section Figma draws white
|
|
52
71
|
(DEVIATIONS #PI-6). */
|
|
53
72
|
defaultValue: selected, searchable: true, searchPlaceholder: copy.searchPlaceholder, clearSearchLabel: copy.clearSearchLabel,
|
|
54
73
|
/* The `dropdown` section IS the open-menu spec state. */
|
|
55
|
-
forceOpen: state === 'dropdown'
|
|
74
|
+
forceOpen: state === 'dropdown',
|
|
75
|
+
/* Core's canonical flag raster in the closed field and every menu
|
|
76
|
+
row (see note 3). A country outside the bank falls back to its
|
|
77
|
+
`flag` emoji. */
|
|
78
|
+
renderLeading: (option) => {
|
|
79
|
+
const match = countries.find((c) => c.value === option.value);
|
|
80
|
+
return match ? (_jsx(PrizmaCountryFlag, { country: {
|
|
81
|
+
code: match.value,
|
|
82
|
+
name: match.label,
|
|
83
|
+
dialCode: '',
|
|
84
|
+
flag: match.flag ?? '',
|
|
85
|
+
} })) : null;
|
|
86
|
+
} }) }));
|
|
56
87
|
});
|
|
57
88
|
EkybCountryScreen.displayName = 'EkybCountryScreen';
|
|
58
89
|
export default EkybCountryScreen;
|
|
@@ -38,6 +38,14 @@ export interface EkybOwnersScreenProps extends Omit<PrizmaFormScreenProps, 'titl
|
|
|
38
38
|
* - `loading` 5663:94390 — `directorFilled` with the submission in
|
|
39
39
|
* flight: every field, both "Add another" links and "Back" go inert, and the
|
|
40
40
|
* spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
|
|
41
|
+
* - `uboEmptyError` / `directorEmptyError` (no frame) — `uboEmpty` /
|
|
42
|
+
* `directorEmpty` after Continue was pressed with the fields still empty
|
|
43
|
+
* (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent
|
|
44
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the two
|
|
45
|
+
* REQUIRED UBO fields in Core's `error` state with `requiredError` and no
|
|
46
|
+
* placeholder, the optional Director fields unflagged, and Continue
|
|
47
|
+
* disabled. The `*Empty` states keep Continue enabled so the press can
|
|
48
|
+
* happen.
|
|
41
49
|
*
|
|
42
50
|
* Four things are deliberate:
|
|
43
51
|
*
|
package/dist/EkybOwnersScreen.js
CHANGED
|
@@ -30,6 +30,14 @@ import './ScreenLayout.css';
|
|
|
30
30
|
* - `loading` 5663:94390 — `directorFilled` with the submission in
|
|
31
31
|
* flight: every field, both "Add another" links and "Back" go inert, and the
|
|
32
32
|
* spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
|
|
33
|
+
* - `uboEmptyError` / `directorEmptyError` (no frame) — `uboEmpty` /
|
|
34
|
+
* `directorEmpty` after Continue was pressed with the fields still empty
|
|
35
|
+
* (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent
|
|
36
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the two
|
|
37
|
+
* REQUIRED UBO fields in Core's `error` state with `requiredError` and no
|
|
38
|
+
* placeholder, the optional Director fields unflagged, and Continue
|
|
39
|
+
* disabled. The `*Empty` states keep Continue enabled so the press can
|
|
40
|
+
* happen.
|
|
33
41
|
*
|
|
34
42
|
* Four things are deliberate:
|
|
35
43
|
*
|
|
@@ -65,24 +73,39 @@ import './ScreenLayout.css';
|
|
|
65
73
|
* distinguishes `loading` from `directorFilled` above the fold, so it is not
|
|
66
74
|
* optional chrome.
|
|
67
75
|
*/
|
|
68
|
-
export const EkybOwnersScreen = forwardRef(({ strings, state = 'uboEmpty', values, screenId = `ekyb-owners-${state}`, className, overrides, onAction, ...shell }, ref) => {
|
|
76
|
+
export const EkybOwnersScreen = forwardRef(({ strings, state = 'uboEmpty', values: valuesProp, screenId = `ekyb-owners-${state}`, ctaDisabled, className, overrides, onAction, ...shell }, ref) => {
|
|
69
77
|
const copy = strings.owners;
|
|
70
78
|
const busy = state === 'loading';
|
|
71
79
|
const secondUbo = state === 'uboAdded';
|
|
72
|
-
const
|
|
80
|
+
const emptyError = state === 'uboEmptyError' || state === 'directorEmptyError';
|
|
81
|
+
const values = emptyError ? undefined : valuesProp;
|
|
82
|
+
const showDirector = state === 'directorEmpty' ||
|
|
83
|
+
state === 'directorFilled' ||
|
|
84
|
+
state === 'directorEmptyError' ||
|
|
85
|
+
secondUbo ||
|
|
86
|
+
busy;
|
|
87
|
+
// Core's own error treatment, spread AFTER each required UBO field's own
|
|
88
|
+
// placeholder so the error state shows none (the `FormsFieldScreen`
|
|
89
|
+
// precedent). The Director fields are optional and never receive it.
|
|
90
|
+
const missing = emptyError
|
|
91
|
+
? { error: true, errorText: strings.requiredError, placeholder: undefined }
|
|
92
|
+
: {};
|
|
73
93
|
return (_jsxs(PrizmaFormScreen, { ref: ref, className: ['ekyb-owners', className].filter(Boolean).join(' '),
|
|
74
94
|
/* The archetype's own scrolling field stack — two titled blocks, four
|
|
75
95
|
fields and two links do not fit a 844 frame. No module CSS. */
|
|
76
96
|
scrollFields: true,
|
|
77
97
|
/* Step 3 of 3, and GREEN across all three once the submission runs. */
|
|
78
98
|
progress: _jsx(PrizmaStepper, { steps: 3, currentState: busy ? 'Completed' : 3, showIcons: false, "aria-label": strings.stepperLabel, completedValueText: strings.stepperCompletedLabel }), title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel,
|
|
99
|
+
/* Disabled only after the empty submit; the `*Empty` states keep it
|
|
100
|
+
enabled. */
|
|
101
|
+
ctaDisabled: emptyError || ctaDisabled,
|
|
79
102
|
/* `loading` swaps the CTA label for Core's designed spinner. */
|
|
80
103
|
ctaLoading: busy, ctaLoadingLabel: copy.ctaLoadingLabel, linkContent: _jsx(PrizmaButton, { variant: "secondary", size: "L",
|
|
81
104
|
/* 5663:94390 greys "Back" while the submission runs. */
|
|
82
105
|
disabled: busy, onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.backLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
83
106
|
/* Exploration ekyb-desktop: name + surname share a row per block on a
|
|
84
107
|
wide frame; on the phone nothing changes. */
|
|
85
|
-
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.uboName ?? '', disabled: busy }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, autoComplete: "off", defaultValue: values?.uboSurname ?? '', disabled: busy })] }), secondUbo ? (_jsxs(_Fragment, { children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.secondUboName ?? '' }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, autoComplete: "off", defaultValue: values?.secondUboSurname ?? '' })] }), copy.removeUboLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", onClick: () => onAction?.({ type: 'tertiary', screenId: `${screenId}-remove-ubo` }), children: copy.removeUboLabel })) : null] })) : null, _jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: busy,
|
|
108
|
+
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, ...missing, autoComplete: "off", defaultValue: values?.uboName ?? '', disabled: busy }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, required: true, requiredHint: strings.requiredHint, ...missing, autoComplete: "off", defaultValue: values?.uboSurname ?? '', disabled: busy })] }), secondUbo ? (_jsxs(_Fragment, { children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.secondUboName ?? '' }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.secondUboSurname ?? '' })] }), copy.removeUboLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", onClick: () => onAction?.({ type: 'tertiary', screenId: `${screenId}-remove-ubo` }), children: copy.removeUboLabel })) : null] })) : null, _jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: busy,
|
|
86
109
|
/* Action-qualified id: the two "Add another…" links are different
|
|
87
110
|
flows and must stay distinguishable (#PI-10), but the action
|
|
88
111
|
contract has no per-action discriminator yet. */
|
package/dist/EkybStrings.d.ts
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
* - `dropdown` — the same selection with the MENU OPEN over the form: search
|
|
8
8
|
* bar plus the country rows (Core's documentation-only `forceOpen`, since a
|
|
9
9
|
* screen is one static spec state and never a live interaction)
|
|
10
|
+
* - `emptyError` — Continue was pressed with no country chosen (GUIDELINES
|
|
11
|
+
* §6-E empty-submit rule, Elena 2026-09-30): the dropdown in Core's error
|
|
12
|
+
* state with `requiredError`, Continue disabled. Not a Figma frame.
|
|
10
13
|
*/
|
|
11
|
-
export type EkybCountryState = 'empty' | 'filled' | 'dropdown';
|
|
14
|
+
export type EkybCountryState = 'empty' | 'filled' | 'dropdown' | 'emptyError';
|
|
12
15
|
/** The two `eKYB.Bdetails` (business identity) sections: every field on its
|
|
13
|
-
* resting empty surface, or every field carrying a value
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type
|
|
16
|
+
* resting empty surface, or every field carrying a value — plus `emptyError`,
|
|
17
|
+
* Continue pressed with the fields empty (empty-submit rule, 2026-09-30):
|
|
18
|
+
* every required field in Core's error state, Continue disabled. */
|
|
19
|
+
export type EkybBusinessState = 'empty' | 'filled' | 'emptyError';
|
|
20
|
+
/** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis,
|
|
21
|
+
* same `emptyError` (the optional Address line 2 shows no error). */
|
|
22
|
+
export type EkybAddressState = 'empty' | 'filled' | 'emptyError';
|
|
17
23
|
/**
|
|
18
24
|
* The `eKYB.UBO.*` and `eKYB.director.*` sections — one screen family, because
|
|
19
25
|
* measured against the 390 frames the director frames ARE the UBO frames with
|
|
@@ -33,8 +39,13 @@ export type EkybAddressState = 'empty' | 'filled';
|
|
|
33
39
|
* flight, so EVERY field, both "Add another" links and "Back" go inert and
|
|
34
40
|
* the spinner rides the CTA, never a field (the Phone Input lesson,
|
|
35
41
|
* DEVIATIONS #PI-5)
|
|
42
|
+
* - `uboEmptyError` / `directorEmptyError` — `uboEmpty` / `directorEmpty` after
|
|
43
|
+
* Continue was pressed with the fields empty (GUIDELINES §6-E empty-submit
|
|
44
|
+
* rule, Elena 2026-09-30): the two REQUIRED UBO fields in Core's error state
|
|
45
|
+
* with `requiredError`, the optional Director fields unflagged, Continue
|
|
46
|
+
* disabled. Not Figma frames.
|
|
36
47
|
*/
|
|
37
|
-
export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'uboAdded' | 'loading';
|
|
48
|
+
export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'uboAdded' | 'loading' | 'uboEmptyError' | 'directorEmptyError';
|
|
38
49
|
/**
|
|
39
50
|
* One selectable row in the country / state menus. Runtime data, not copy — the
|
|
40
51
|
* host owns the catalogue (an ISO country list is ~250 entries and a state list
|
|
@@ -47,10 +58,14 @@ export interface EkybOption {
|
|
|
47
58
|
/** The row label as drawn in the open menu. */
|
|
48
59
|
label: string;
|
|
49
60
|
/** Compact face for the CLOSED field when it differs from the menu row —
|
|
50
|
-
* Core's own `PrizmaDropdownOption.fieldLabel`.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
61
|
+
* Core's own `PrizmaDropdownOption.fieldLabel`. The eKYB country list
|
|
62
|
+
* does not need it: rows and field both read "United States" after the
|
|
63
|
+
* flag. */
|
|
53
64
|
fieldLabel?: string;
|
|
65
|
+
/** Country menus only: the emoji fallback for a `value` outside Core's
|
|
66
|
+
* flag raster bank. The country screen draws the bank's flag by `value`,
|
|
67
|
+
* so this never travels inside `label`. */
|
|
68
|
+
flag?: string;
|
|
54
69
|
}
|
|
55
70
|
/** The values the business-identity page carries. Runtime data, never copy. */
|
|
56
71
|
export interface EkybBusinessValues {
|
|
@@ -97,6 +112,13 @@ export interface EkybStrings {
|
|
|
97
112
|
* localizable and never hardcoded in a screen.
|
|
98
113
|
*/
|
|
99
114
|
requiredHint?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Under every empty REQUIRED field once the primary was pressed with it
|
|
117
|
+
* still empty — the `emptyError` states (GUIDELINES §6-E empty-submit rule,
|
|
118
|
+
* Elena 2026-09-30). Core's own `errorText` slot; SDK key
|
|
119
|
+
* `dynamicForms.fieldRequired`.
|
|
120
|
+
*/
|
|
121
|
+
requiredError: string;
|
|
100
122
|
/** Shared "Back" secondary action drawn under "Continue" on every page except
|
|
101
123
|
* `country`. Emits `secondary`. */
|
|
102
124
|
backLabel: string;
|
|
@@ -170,7 +192,7 @@ export interface EkybStrings {
|
|
|
170
192
|
title: string;
|
|
171
193
|
subtitle?: string;
|
|
172
194
|
uboName: EkybFieldStrings;
|
|
173
|
-
/**
|
|
195
|
+
/** Required, like the name (Elena, 2026-09-28); the frames drew no `*`. */
|
|
174
196
|
uboSurname: EkybFieldStrings;
|
|
175
197
|
/** The tertiary link under the UBO block ("Add another UBO (Optional)"). */
|
|
176
198
|
addUboLabel: string;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/** `password`: Core's `PrizmaPassword`, the field with the eye that hides the
|
|
2
|
+
* typed value. For the identifiers a stranger must not read over a shoulder:
|
|
3
|
+
* the SSN / ITIN fields (Elena, 2026-09-28). No requirement checklist. */
|
|
4
|
+
export type EkycFieldKind = 'text' | 'date' | 'phone' | 'dropdown' | 'radio' | 'password';
|
|
2
5
|
export type EkycFieldKey = 'firstName' | 'middleName' | 'middleNameOptional' | 'lastName' | 'surname' | 'maternalSurname' | 'dateOfBirth' | 'email' | 'phone' | 'ssnLast4' | 'ssnOrItin' | 'nationality' | 'cpf' | 'nationalId' | 'taxId' | 'gender' | 'licenseNumber' | 'licenseState' | 'licenseExpiration' | 'street' | 'houseNumber' | 'city' | 'state' | 'stateCode' | 'zipCode' | 'postalCode';
|
|
3
6
|
export interface EkycField {
|
|
4
7
|
key: EkycFieldKey;
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// Verification", and no variant draws a close in the nav; steps after the
|
|
21
21
|
// first get the archetype's Back button instead of a back arrow (#EKC-2).
|
|
22
22
|
const t = (key, extra = {}) => ({ key, kind: 'text', ...extra });
|
|
23
|
+
/** A hidden-by-default identifier, with the eye to reveal it. */
|
|
24
|
+
const secret = (key, extra = {}) => ({ key, kind: 'password', ...extra });
|
|
23
25
|
const date = (key = 'dateOfBirth', extra = {}) => ({ key, kind: 'date', ...extra });
|
|
24
26
|
// Required fields carry Core's asterisk (`required` + `requiredHint`), the
|
|
25
27
|
// owner's rule (Elena, 2026-09-24, DEVIATIONS #EKC-6): first name, last name or
|
|
@@ -54,7 +56,7 @@ export const EKYC_COUNTRY_CATALOG = [
|
|
|
54
56
|
{
|
|
55
57
|
id: 'us-telco-1', country: 'US', label: 'United States', provider: 'Telco 1', stepper: 3,
|
|
56
58
|
steps: [
|
|
57
|
-
{ node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'),
|
|
59
|
+
{ node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'), secret('ssnLast4')] },
|
|
58
60
|
{ node: '5854:76222', section: 'license', fields: [t('licenseNumber'), { key: 'licenseState', kind: 'dropdown', placeholder: 'selectState' }] },
|
|
59
61
|
{ node: '5854:76221', section: 'address', fields: US_ADDRESS },
|
|
60
62
|
],
|
|
@@ -71,7 +73,7 @@ export const EKYC_COUNTRY_CATALOG = [
|
|
|
71
73
|
steps: [
|
|
72
74
|
/* Same First | Last pair as the licence variant; Middle name (Optional)
|
|
73
75
|
follows it and spans (#EKC-5). */
|
|
74
|
-
{ node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(),
|
|
76
|
+
{ node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(), secret('ssnOrItin')] },
|
|
75
77
|
{ node: '5857:34094', section: 'address', fields: US_ADDRESS },
|
|
76
78
|
],
|
|
77
79
|
},
|
|
@@ -90,7 +92,7 @@ export const EKYC_COUNTRY_CATALOG = [
|
|
|
90
92
|
{
|
|
91
93
|
id: 'ca-res-credit', country: 'CA', label: 'Canada', provider: 'Residential credit', stepper: 2,
|
|
92
94
|
steps: [
|
|
93
|
-
{ node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'),
|
|
95
|
+
{ node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'), secret('ssnOrItin')] },
|
|
94
96
|
{ node: '5857:36450', section: 'address', fields: CODE_ADDRESS('stateCodeCa') },
|
|
95
97
|
],
|
|
96
98
|
},
|
|
@@ -16,11 +16,20 @@ export interface EkycCountryFormScreenProps extends Omit<PrizmaFormScreenProps,
|
|
|
16
16
|
values?: EkycCountryValues;
|
|
17
17
|
/**
|
|
18
18
|
* The state after Continue was pressed with fields still empty: every empty
|
|
19
|
-
* field shows Core's error state and `country.requiredError` under
|
|
20
|
-
* Continue
|
|
21
|
-
*
|
|
19
|
+
* REQUIRED field shows Core's error state and `country.requiredError` under
|
|
20
|
+
* it, and Continue is disabled (GUIDELINES §6-E empty-submit rule, Elena
|
|
21
|
+
* 2026-09-30). Without it Continue stays enabled: validation happens on
|
|
22
|
+
* press, the resting page never greys the button.
|
|
22
23
|
*/
|
|
23
24
|
showErrors?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Field keys whose typed value failed the format check on Continue (an
|
|
27
|
+
* invalid CPF, say). Each keeps its value, shows Core's error state and
|
|
28
|
+
* `country.invalidErrors[key]` under it; Continue stays enabled so the user
|
|
29
|
+
* corrects it and presses again, the CURP precedent (`CurpEnterScreen`'s
|
|
30
|
+
* `invalid`). A key with no message in `invalidErrors` shows no error.
|
|
31
|
+
*/
|
|
32
|
+
invalidFields?: string[];
|
|
24
33
|
/** Dial codes the phone field offers. Runtime data, never copy. */
|
|
25
34
|
phoneCountries?: PrizmaCountry[];
|
|
26
35
|
/** Options for the dropdown fields, keyed by field key. Runtime data. */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
5
|
+
import { PrizmaPassword } from '@incodetech/prizma-core-web/password';
|
|
5
6
|
import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
|
|
6
7
|
import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
|
|
7
8
|
import { PrizmaCombined } from '@incodetech/prizma-core-web/combined';
|
|
@@ -23,10 +24,13 @@ import './EkycLayout.css';
|
|
|
23
24
|
* dropdown, radio pair — so a new country is a catalogue entry, never a file.
|
|
24
25
|
*
|
|
25
26
|
* Deliberate, per frame and per owner:
|
|
26
|
-
* - Continue is
|
|
27
|
-
* most frames draw): pressing it with empty fields is the
|
|
28
|
-
* Core's error ring and message under each empty
|
|
29
|
-
*
|
|
27
|
+
* - Continue is enabled on the resting page (Elena, 2026-09-24, overriding the
|
|
28
|
+
* grey button most frames draw): pressing it with empty fields is the
|
|
29
|
+
* `showErrors` state, Core's error ring and message under each empty
|
|
30
|
+
* required field, and THEN Continue is disabled. The "always enabled" half
|
|
31
|
+
* of DEVIATIONS #EKC-4 is superseded by the 2026-09-30 empty-submit rule
|
|
32
|
+
* (GUIDELINES §6-E). The radio pair has no error state in Core, so an empty
|
|
33
|
+
* gender still shows none.
|
|
30
34
|
* - ONE title per step (Elena, 2026-09-24): the personal-data step is "Enter
|
|
31
35
|
* your details" with its subtitle; a licence step is titled "Driver's
|
|
32
36
|
* License details" and an address step "Address details", with nothing
|
|
@@ -53,7 +57,7 @@ const groupRows = (fields) => {
|
|
|
53
57
|
}
|
|
54
58
|
return groups;
|
|
55
59
|
};
|
|
56
|
-
export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, values, showErrors = false, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
|
|
60
|
+
export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, values, showErrors = false, invalidFields, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
|
|
57
61
|
const v = typeof variant === 'string' ? ekycCountryVariant(variant) : variant;
|
|
58
62
|
if (!v)
|
|
59
63
|
return null;
|
|
@@ -61,14 +65,21 @@ export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, v
|
|
|
61
65
|
const c = strings.country;
|
|
62
66
|
const stepperState = step <= 1 ? 'Start' : Math.min(step, 6);
|
|
63
67
|
const label = (f) => c.fields[f.key] ?? f.key;
|
|
64
|
-
|
|
68
|
+
// A lone field in its empty-submit error drops its placeholder, as the
|
|
69
|
+
// single-input screens do (Phone, Email and CURP's `emptyError`).
|
|
70
|
+
const lone = s.fields.length === 1;
|
|
71
|
+
const placeholder = (f) => lone && showErrors && !values?.[f.key] ? undefined : f.placeholder ? c.placeholders[f.placeholder] : c.placeholders[f.key];
|
|
65
72
|
const renderField = (f) => {
|
|
66
73
|
// Only a REQUIRED empty field is an error on press (Elena, 2026-09-24,
|
|
67
|
-
// DEVIATIONS #EKC-6): an optional field left blank shows nothing.
|
|
68
|
-
|
|
74
|
+
// DEVIATIONS #EKC-6): an optional field left blank shows nothing. A
|
|
75
|
+
// step's lone field is required by definition, asterisk or not (the
|
|
76
|
+
// single-input rule, as Phone and Email Input: Elena, 2026-10-02).
|
|
77
|
+
const required = Boolean(f.required) || lone;
|
|
78
|
+
const missing = showErrors && required && !values?.[f.key];
|
|
79
|
+
const invalidText = invalidFields?.includes(f.key) ? c.invalidErrors?.[f.key] : undefined;
|
|
69
80
|
const common = {
|
|
70
81
|
key: f.key, label: label(f), required: f.required, requiredHint: strings.details.requiredHint,
|
|
71
|
-
error: missing || undefined, errorText: missing ? c.requiredError :
|
|
82
|
+
error: missing || Boolean(invalidText) || undefined, errorText: missing ? c.requiredError : invalidText,
|
|
72
83
|
};
|
|
73
84
|
switch (f.kind) {
|
|
74
85
|
case 'date':
|
|
@@ -79,13 +90,20 @@ export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, v
|
|
|
79
90
|
}
|
|
80
91
|
case 'dropdown':
|
|
81
92
|
return (_jsx(PrizmaDropdown, { ...common, placeholder: placeholder(f), options: options?.[f.key] ?? [], defaultValue: values?.[f.key] ?? null }));
|
|
93
|
+
case 'password':
|
|
94
|
+
/* The SSN / ITIN fields: Core's Password field, hidden by default, the
|
|
95
|
+
eye reveals it. No requirement checklist — this is an identifier,
|
|
96
|
+
not a password to compose (Elena, 2026-09-28). */
|
|
97
|
+
return (_jsx(PrizmaPassword, { ...common, placeholder: placeholder(f), autoComplete: "off", defaultValue: values?.[f.key] ?? '', showPasswordLabel: c.showValueLabel, hidePasswordLabel: c.hideValueLabel, showHelper: false, showRequirements: false }));
|
|
82
98
|
case 'radio':
|
|
83
99
|
return (_jsx(PrizmaRadioGroup, { name: f.key, legend: label(f), defaultValue: values?.[f.key], className: "ekyc-country__radios", children: c.genderOptions.map(o => (_jsx(PrizmaRadio, { value: o.value, textSize: "m", showText: true, text: o.label }, o.value))) }, f.key));
|
|
84
100
|
default:
|
|
85
101
|
return _jsx(PrizmaTextField, { ...common, placeholder: placeholder(f), autoComplete: "off", defaultValue: values?.[f.key] ?? '' });
|
|
86
102
|
}
|
|
87
103
|
};
|
|
88
|
-
return (_jsx(PrizmaFormScreen, { ref: ref, scrollFields: true, progress: v.stepper > 0 ? (_jsx(PrizmaStepper, { steps: v.stepper, currentState: stepperState, showIcons: false, "aria-label": strings.stepperLabel })) : undefined, title: s.section === 'address' ? c.addressSection : s.section === 'license' ? c.licenseSection : strings.details.title, subtitle: s.section ? undefined : strings.details.subtitle, linkContent: step > 1 ? (_jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: c.backLabel })) : undefined, ctaLabel: c.ctaLabel,
|
|
104
|
+
return (_jsx(PrizmaFormScreen, { ref: ref, scrollFields: true, progress: v.stepper > 0 ? (_jsx(PrizmaStepper, { steps: v.stepper, currentState: stepperState, showIcons: false, "aria-label": strings.stepperLabel })) : undefined, title: s.section === 'address' ? c.addressSection : s.section === 'license' ? c.licenseSection : strings.details.title, subtitle: s.section ? undefined : strings.details.subtitle, linkContent: step > 1 ? (_jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: c.backLabel })) : undefined, ctaLabel: c.ctaLabel,
|
|
105
|
+
/* Greyed only after the empty submit (2026-09-30 empty-submit rule). */
|
|
106
|
+
ctaDisabled: showErrors, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
89
107
|
/* The eKYB desktop shape: catalogue pairs become two lanes at 420 and
|
|
90
108
|
up, Back | Continue share a row. The phone stack is untouched. */
|
|
91
109
|
responsive: true, ...shell, children: groupRows(s.fields).map(group => group.length > 1 ? (_jsx(PrizmaFormRow, { children: group.map(renderField) }, group.map(f => f.key).join('+'))) : (renderField(group[0]))) }));
|
|
@@ -52,6 +52,13 @@ export interface EkycDetailsScreenProps extends Omit<PrizmaFormScreenProps, 'tit
|
|
|
52
52
|
* the label row Figma draws as "First name *" — so Middle name, the one
|
|
53
53
|
* optional field, correctly renders none.
|
|
54
54
|
*
|
|
55
|
+
* `emptyError` is the state after Continue was pressed with the fields still
|
|
56
|
+
* empty (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; drawn precedent
|
|
57
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the three
|
|
58
|
+
* REQUIRED ones carry Core's `error` + `country.requiredError`, Middle name
|
|
59
|
+
* stays unflagged (DEVIATIONS #EKC-6), and Continue is disabled. The resting
|
|
60
|
+
* `default` keeps Continue enabled so the press can happen at all.
|
|
61
|
+
*
|
|
55
62
|
* Figma draws a two-segment journey bar between the nav and the title on every
|
|
56
63
|
* page-1 frame, and it is the only chrome that separates page 1 from page 2.
|
|
57
64
|
* Archetype E had no slot above the title; the gap is now CLOSED in the
|
|
@@ -44,6 +44,13 @@ import './EkycLayout.css';
|
|
|
44
44
|
* the label row Figma draws as "First name *" — so Middle name, the one
|
|
45
45
|
* optional field, correctly renders none.
|
|
46
46
|
*
|
|
47
|
+
* `emptyError` is the state after Continue was pressed with the fields still
|
|
48
|
+
* empty (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; drawn precedent
|
|
49
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the three
|
|
50
|
+
* REQUIRED ones carry Core's `error` + `country.requiredError`, Middle name
|
|
51
|
+
* stays unflagged (DEVIATIONS #EKC-6), and Continue is disabled. The resting
|
|
52
|
+
* `default` keeps Continue enabled so the press can happen at all.
|
|
53
|
+
*
|
|
47
54
|
* Figma draws a two-segment journey bar between the nav and the title on every
|
|
48
55
|
* page-1 frame, and it is the only chrome that separates page 1 from page 2.
|
|
49
56
|
* Archetype E had no slot above the title; the gap is now CLOSED in the
|
|
@@ -53,11 +60,18 @@ import './EkycLayout.css';
|
|
|
53
60
|
* solid brand blue and segment 2 grey, i.e. `steps={2} currentState="Start"`,
|
|
54
61
|
* with the icons row off (the frames draw the bar alone). DEVIATIONS #EK-1.
|
|
55
62
|
*/
|
|
56
|
-
export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', values, screenId = `ekyc-details-${state}`, className, overrides, onAction, ...shell }, ref) => {
|
|
63
|
+
export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', values: valuesProp, screenId = `ekyc-details-${state}`, ctaDisabled, className, overrides, onAction, ...shell }, ref) => {
|
|
57
64
|
// The one field treatment that varies across the three sections: 2101:8159
|
|
58
65
|
// rings First name blue and shows its clear ✕; the other two draw every
|
|
59
66
|
// field resting.
|
|
60
67
|
const focused = state === 'focused';
|
|
68
|
+
// The empty-submit state: every field empty, required ones in error.
|
|
69
|
+
const emptyError = state === 'emptyError';
|
|
70
|
+
const values = emptyError ? undefined : valuesProp;
|
|
71
|
+
// Core's own error treatment, spread onto the REQUIRED fields only.
|
|
72
|
+
const missing = emptyError
|
|
73
|
+
? { error: true, errorText: strings.country.requiredError }
|
|
74
|
+
: {};
|
|
61
75
|
return (_jsxs(PrizmaFormScreen, { ref: ref,
|
|
62
76
|
/* Scopes the single rule that lets the focused field keep the clear ✕
|
|
63
77
|
Figma draws inside it. See EkycLayout.css. */
|
|
@@ -68,7 +82,10 @@ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', value
|
|
|
68
82
|
`Start` state at `steps={2}`. `showIcons={false}` because the frames
|
|
69
83
|
draw the bar with no glyph row (and it keeps the icon lookup, which
|
|
70
84
|
has no id+2 sequence, out of the render entirely). */
|
|
71
|
-
progress: _jsx(PrizmaStepper, { steps: 2, currentState: "Start", showIcons: false, "aria-label": strings.stepperLabel }), title: strings.details.title, subtitle: strings.details.subtitle, ctaLabel: strings.details.ctaLabel,
|
|
85
|
+
progress: _jsx(PrizmaStepper, { steps: 2, currentState: "Start", showIcons: false, "aria-label": strings.stepperLabel }), title: strings.details.title, subtitle: strings.details.subtitle, ctaLabel: strings.details.ctaLabel,
|
|
86
|
+
/* Disabled only after the empty submit; the resting page keeps it
|
|
87
|
+
enabled so the press can happen. */
|
|
88
|
+
ctaDisabled: emptyError || ctaDisabled, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
|
|
72
89
|
/* Every eKYC form frame LEFT-aligns its title block, while the archetype
|
|
73
90
|
defaults to centred. The module supplies its designed alignment through
|
|
74
91
|
the sanctioned override rather than declaring any layout, and a host
|
|
@@ -80,9 +97,9 @@ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', value
|
|
|
80
97
|
now FOLLOWS Last name, on the phone too (First, Last, Middle, Date of
|
|
81
98
|
birth), the one order change the pairing costs: the frames drew
|
|
82
99
|
First, Middle, Last. DEVIATIONS #EKC-5. */
|
|
83
|
-
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, label: strings.details.firstNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.firstName ?? '', autoComplete: "off",
|
|
100
|
+
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, label: strings.details.firstNameLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.firstName ?? '', autoComplete: "off",
|
|
84
101
|
/* Only 2101:8159 draws the ✕, and only in this field. */
|
|
85
|
-
clearable: focused, clearLabel: strings.details.clearLabel }), _jsx(PrizmaTextField, { label: strings.details.lastNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.lastName ?? '', autoComplete: "off", clearable: false })] }), _jsx(PrizmaTextField, { label: strings.details.middleNameLabel, defaultValue: values?.middleName ?? '', autoComplete: "off", clearable: false }), _jsx(PrizmaDateField, { label: strings.details.dateOfBirthLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.dateOfBirth ?? '', autoComplete: "off" })] }));
|
|
102
|
+
clearable: focused, clearLabel: strings.details.clearLabel }), _jsx(PrizmaTextField, { label: strings.details.lastNameLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.lastName ?? '', autoComplete: "off", clearable: false })] }), _jsx(PrizmaTextField, { label: strings.details.middleNameLabel, defaultValue: values?.middleName ?? '', autoComplete: "off", clearable: false }), _jsx(PrizmaDateField, { label: strings.details.dateOfBirthLabel, required: true, requiredHint: strings.details.requiredHint, ...missing, defaultValue: values?.dateOfBirth ?? '', autoComplete: "off" })] }));
|
|
86
103
|
});
|
|
87
104
|
EkycDetailsScreen.displayName = 'EkycDetailsScreen';
|
|
88
105
|
export default EkycDetailsScreen;
|
package/dist/EkycStrings.d.ts
CHANGED
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
* the clear ✕ (2101:8159 draws the OS keyboard over the CTA; the
|
|
10
10
|
* keyboard is OS chrome, never a screen element)
|
|
11
11
|
* - `filled` — all four fields resting with their values
|
|
12
|
+
* - `emptyError` — Continue was pressed with the fields empty (GUIDELINES §6-E
|
|
13
|
+
* empty-submit rule, Elena 2026-09-30): the three REQUIRED fields
|
|
14
|
+
* in Core's error state with `country.requiredError`, Middle name
|
|
15
|
+
* (optional) unflagged, and Continue disabled. Not a Figma frame.
|
|
12
16
|
*/
|
|
13
|
-
export type EkycDetailsState = 'default' | 'focused' | 'filled';
|
|
17
|
+
export type EkycDetailsState = 'default' | 'focused' | 'filled' | 'emptyError';
|
|
14
18
|
/**
|
|
15
19
|
* The page-2 sections (`eKYC.default` / `eKYC.filled` / `eKYC.loading`), same
|
|
16
20
|
* layout with the document field set:
|
|
@@ -109,6 +113,10 @@ export interface EkycStrings {
|
|
|
109
113
|
licenseSection: string;
|
|
110
114
|
/** Accessible name of the phone field's dial-code list. */
|
|
111
115
|
countryListLabel: string;
|
|
116
|
+
/** Accessible names for the eye on a `password` field (the SSN / ITIN
|
|
117
|
+
* fields): while the value is hidden, and while it is shown. */
|
|
118
|
+
showValueLabel: string;
|
|
119
|
+
hideValueLabel: string;
|
|
112
120
|
/** The gender radio pair, Argentina. */
|
|
113
121
|
genderOptions: {
|
|
114
122
|
value: string;
|
|
@@ -119,8 +127,12 @@ export interface EkycStrings {
|
|
|
119
127
|
ctaLabel: string;
|
|
120
128
|
/** The secondary button on a step after the first. */
|
|
121
129
|
backLabel: string;
|
|
122
|
-
/** Under every empty field once Continue was pressed
|
|
130
|
+
/** Under every empty REQUIRED field once Continue was pressed: the
|
|
131
|
+
* by-country `showErrors` state and page 1's `emptyError` both read it. */
|
|
123
132
|
requiredError: string;
|
|
133
|
+
/** Under a field whose value failed the format check on Continue, by
|
|
134
|
+
* field key (`invalidFields`). `cpf` is SDK key `dynamicForms.invalidCpf`. */
|
|
135
|
+
invalidErrors?: Record<string, string>;
|
|
124
136
|
};
|
|
125
137
|
/** `eKYC.processing` — spinner over the status line, no button. */
|
|
126
138
|
processing: {
|
|
@@ -47,6 +47,12 @@
|
|
|
47
47
|
border: var(--prizma-border-width-regular) solid var(--prizma-border-neutral-100);
|
|
48
48
|
border-radius: var(--prizma-border-radius-small);
|
|
49
49
|
text-align: start;
|
|
50
|
+
/* Air between the frame and the CTA band. Archetype G's list middle carries
|
|
51
|
+
no bottom gap (its content normally ends well above the band), so a frame
|
|
52
|
+
that fills the middle sat flush against Continue (owner-reported
|
|
53
|
+
2026-09-29). Spacing/16 is the archetype's own scrolling-middle → band gap
|
|
54
|
+
(`.prizma-screen-layout__list--scroll`, ScreenLayout.css). */
|
|
55
|
+
margin-bottom: var(--prizma-spacing-16);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/* The agreement's own typography — Body/M Regular on Text/Body/500, the pair
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
* - `filled` — focused field with a complete address
|
|
9
9
|
* - `error` — negative field + clear ✕ + the invalid-format row
|
|
10
10
|
* - `loading` — field greyed while the code is requested, spinner in the CTA
|
|
11
|
+
* - `emptyError` — "Continue" pressed with the field still empty: the field
|
|
12
|
+
* stays empty (no placeholder) in Core's error state with `requiredError`,
|
|
13
|
+
* and the CTA is disabled. GUIDELINES §6-E empty-submit rule (Elena,
|
|
14
|
+
* 2026-09-30).
|
|
11
15
|
*/
|
|
12
|
-
export type EmailInputVerifyState = 'default' | 'typing' | 'filled' | 'error' | 'loading';
|
|
16
|
+
export type EmailInputVerifyState = 'default' | 'typing' | 'filled' | 'error' | 'loading' | 'emptyError';
|
|
13
17
|
/**
|
|
14
18
|
* The `email.OTP.*` sections — same layout, different code-field state:
|
|
15
19
|
* - `typing` — focused field, partial code, CTA enabled, and the line
|
|
@@ -27,6 +31,12 @@ export interface EmailInputStrings {
|
|
|
27
31
|
verifiedLabel: string;
|
|
28
32
|
/** Accessible name for the logo nav row. */
|
|
29
33
|
navLabel?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Error row under the required field when the primary CTA is pressed with it
|
|
36
|
+
* still empty (the verify screen's `emptyError` state): "This field is
|
|
37
|
+
* required". SDK key `dynamicForms.fieldRequired`.
|
|
38
|
+
*/
|
|
39
|
+
requiredError: string;
|
|
30
40
|
/** email.verifyEmail.* */
|
|
31
41
|
verify: {
|
|
32
42
|
/** H2 ("Verify your email"). */
|
|
@@ -41,6 +41,13 @@ export interface EmailInputVerifyScreenProps extends Omit<PrizmaFormScreenProps,
|
|
|
41
41
|
* paints the DISABLED surface (owner definition 2026-07-12), which would grey
|
|
42
42
|
* the four resting states Figma draws white. The field is uncontrolled instead
|
|
43
43
|
* (DEVIATIONS #PI-6).
|
|
44
|
+
*
|
|
45
|
+
* `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
|
|
46
|
+
* GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
|
|
47
|
+
* typed. The field renders empty with no placeholder, in Core's error state
|
|
48
|
+
* with `requiredError`, and the primary CTA is disabled, following the drawn
|
|
49
|
+
* precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
|
|
50
|
+
* so the user can reach this state.
|
|
44
51
|
*/
|
|
45
52
|
export declare const EmailInputVerifyScreen: React.ForwardRefExoticComponent<EmailInputVerifyScreenProps & React.RefAttributes<HTMLDivElement>>;
|
|
46
53
|
export default EmailInputVerifyScreen;
|
|
@@ -31,23 +31,37 @@ import './ScreenLayout.css';
|
|
|
31
31
|
* paints the DISABLED surface (owner definition 2026-07-12), which would grey
|
|
32
32
|
* the four resting states Figma draws white. The field is uncontrolled instead
|
|
33
33
|
* (DEVIATIONS #PI-6).
|
|
34
|
+
*
|
|
35
|
+
* `emptyError` is the empty-submit state (rule from Elena, 2026-09-30,
|
|
36
|
+
* GUIDELINES §6-E empty-submit rule): "Continue" was pressed with nothing
|
|
37
|
+
* typed. The field renders empty with no placeholder, in Core's error state
|
|
38
|
+
* with `requiredError`, and the primary CTA is disabled, following the drawn
|
|
39
|
+
* precedent in `FormsFieldScreen`. The resting `default` keeps the CTA enabled
|
|
40
|
+
* so the user can reach this state.
|
|
34
41
|
*/
|
|
35
42
|
export const EmailInputVerifyScreen = forwardRef(({ strings, state = 'default', value, screenId = `email-input-verify-${state}`, overrides, onAction, ...shell }, ref) => {
|
|
36
43
|
const busy = state === 'loading';
|
|
37
44
|
const invalid = state === 'error';
|
|
45
|
+
// Empty submit: the field drops its placeholder and takes Core's error
|
|
46
|
+
// state with the shared "required" row; the CTA greys.
|
|
47
|
+
const emptyError = state === 'emptyError';
|
|
38
48
|
const focused = state === 'typing' || state === 'filled';
|
|
39
49
|
return (_jsx(PrizmaFormScreen, { ref: ref, title: strings.verify.title, subtitle: strings.verify.subtitle, ctaLabel: strings.verify.ctaLabel,
|
|
40
50
|
/* Figma's `loading` section swaps the CTA label for Core's spinner. */
|
|
41
|
-
ctaLoading: busy, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className:
|
|
51
|
+
ctaLoading: busy, ctaDisabled: emptyError, ctaLoadingLabel: strings.verify.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: overrides, onAction: onAction, ...shell, children: _jsx(PrizmaTextField, { className:
|
|
42
52
|
/* Core's OWN static-focus modifier — the class its CSS already
|
|
43
53
|
implements beside `:focus-within`. `PrizmaTextField` has no
|
|
44
54
|
`forceState` prop the way `PrizmaDropdown` does, so this is how a
|
|
45
55
|
static spec state draws the focus ring without redeclaring any
|
|
46
56
|
styling (DEVIATIONS #PI-7). */
|
|
47
|
-
focused ? 'prizma-input-textfield--focused' : undefined, type: "email", inputMode: "email", autoComplete: "email", "aria-label": strings.verify.fieldLabel, placeholder: strings.verify.placeholder,
|
|
57
|
+
focused ? 'prizma-input-textfield--focused' : undefined, type: "email", inputMode: "email", autoComplete: "email", "aria-label": strings.verify.fieldLabel, placeholder: emptyError ? undefined : strings.verify.placeholder,
|
|
48
58
|
/* Uncontrolled: a screen is one static spec state, so no `onChange`
|
|
49
59
|
is needed and no warning is raised. */
|
|
50
|
-
defaultValue: value ?? '', disabled: busy, error: invalid, errorText: invalid
|
|
60
|
+
defaultValue: emptyError ? '' : value ?? '', disabled: busy, error: invalid || emptyError, errorText: invalid
|
|
61
|
+
? strings.verify.errorText
|
|
62
|
+
: emptyError
|
|
63
|
+
? strings.requiredError
|
|
64
|
+
: undefined,
|
|
51
65
|
/* The clear ✕ is drawn on the `error` section only. */
|
|
52
66
|
clearable: invalid, clearLabel: strings.verify.clearLabel }) }));
|
|
53
67
|
});
|