@verifiedinc-public/shared-ui-elements 0.11.6 → 0.12.0
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/README.md +41 -0
- package/package.json +6 -24
- package/src/components/Alert/Alert.tsx +8 -0
- package/src/components/Alert/FullWidthAlert.tsx +27 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Button/index.tsx +8 -0
- package/src/components/CredentialRequestsEditor/CredentialRequestsEditor.context.tsx +98 -0
- package/src/components/CredentialRequestsEditor/components/CredentialRequestsField.tsx +103 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldAccordion.tsx +337 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldDeleteModal.tsx +64 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldDescription.tsx +68 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldMandatory.tsx +84 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldMulti.tsx +74 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldOptionType.tsx +84 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldSection.tsx +48 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldUserInput.tsx +71 -0
- package/src/components/CredentialRequestsEditor/components/RadioOption.tsx +89 -0
- package/src/components/CredentialRequestsEditor/contexts/CredentialRequestFieldContext.tsx +36 -0
- package/src/components/CredentialRequestsEditor/index.tsx +15 -0
- package/src/components/CredentialRequestsEditor/types/compositeCredentialSchema.ts +1 -0
- package/src/components/CredentialRequestsEditor/types/credentialSchemasDto.ts +3 -0
- package/src/components/CredentialRequestsEditor/types/form.ts +28 -0
- package/src/components/CredentialRequestsEditor/types/mandatoryEnum.ts +5 -0
- package/src/components/CredentialRequestsEditor/utils/buildDataFieldValue.ts +65 -0
- package/src/components/CredentialRequestsEditor/utils/prettyField.ts +16 -0
- package/src/components/Image.tsx +10 -0
- package/src/components/QRCodeDisplay/index.tsx +50 -0
- package/src/components/RequiredLabel/index.tsx +15 -0
- package/src/components/TextField/index.tsx +8 -0
- package/src/components/Tip/index.tsx +18 -0
- package/src/components/Typography/index.tsx +8 -0
- package/src/components/When.tsx +28 -0
- package/src/components/form/CountrySelector.tsx +96 -0
- package/src/components/form/DataFieldClearAdornment.tsx +28 -0
- package/src/components/form/DateInput.tsx +117 -0
- package/src/components/form/DefaultInput.tsx +28 -0
- package/src/components/form/InputMask.tsx +41 -0
- package/src/components/form/OTPInput.tsx +246 -0
- package/src/components/form/PhoneInput.tsx +153 -0
- package/src/components/form/SSNInput.tsx +100 -0
- package/src/components/form/SelectInput.tsx +89 -0
- package/src/components/form/TextMaskCustom.tsx +48 -0
- package/src/components/form/index.ts +5 -0
- package/src/components/form/styles/input.ts +24 -0
- package/src/components/index.ts +10 -0
- package/src/components/terms/AcceptTermsNotice.tsx +27 -0
- package/src/components/terms/LegalLink.tsx +22 -0
- package/src/components/verified/VerifiedImage.tsx +272 -0
- package/src/components/verified/VerifiedIncLogo.tsx +11 -0
- package/src/components/verified/index.ts +2 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useCallbackRef.ts +22 -0
- package/src/hooks/useCopyToClipboard.ts +76 -0
- package/src/hooks/useDisclosure.ts +96 -0
- package/src/hooks/useLocalStorage.ts +24 -0
- package/src/hooks/usePrevious.ts +17 -0
- package/src/hooks/useQRCode.ts +62 -0
- package/src/index.ts +5 -0
- package/src/stories/components/Alert.stories.tsx +41 -0
- package/src/stories/components/Button.stories.ts +49 -0
- package/src/stories/components/CredentialRequestsEditor.stories.tsx +98 -0
- package/src/stories/components/QRCodeDisplay.stories.tsx +60 -0
- package/src/stories/components/TextField.stories.ts +59 -0
- package/src/stories/components/Typography.stories.ts +140 -0
- package/src/stories/components/VerifiedImage.stories.tsx +32 -0
- package/src/stories/components/form/DateInput.stories.ts +39 -0
- package/src/stories/components/form/OTPInput.stories.tsx +90 -0
- package/src/stories/components/form/PhoneInput.stories.tsx +34 -0
- package/src/stories/components/form/SSNInput.stories.ts +30 -0
- package/src/stories/components/form/SelectInput.stories.ts +39 -0
- package/src/stories/hooks/useCopyToClipboard.stories.tsx +45 -0
- package/src/styles/colors.ts +34 -0
- package/src/styles/index.ts +2 -0
- package/src/styles/theme.ts +209 -0
- package/src/utils/date.ts +41 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/masks/index.ts +6 -0
- package/src/utils/omitProperty.ts +19 -0
- package/src/utils/phone.ts +76 -0
- package/src/utils/wrapPromise.ts +19 -0
- package/src/validations/birthDate.schema.ts +54 -0
- package/src/validations/date.schema.ts +10 -0
- package/src/validations/description.schema.ts +5 -0
- package/src/validations/email.schema.ts +3 -0
- package/src/validations/field.schema.ts +3 -0
- package/src/validations/index.ts +9 -0
- package/src/validations/phone.schema.ts +6 -0
- package/src/validations/ssn.schema.ts +6 -0
- package/src/validations/state.schema.ts +3 -0
- package/src/validations/unix.schema.ts +11 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
import {
|
2
|
+
Dialog,
|
3
|
+
DialogActions,
|
4
|
+
DialogContent,
|
5
|
+
DialogTitle,
|
6
|
+
type SxProps,
|
7
|
+
Typography,
|
8
|
+
} from '@mui/material';
|
9
|
+
|
10
|
+
import { Button } from '../../Button';
|
11
|
+
|
12
|
+
const buttonStyle: SxProps = {
|
13
|
+
minHeight: 20,
|
14
|
+
mt: 2,
|
15
|
+
py: 1,
|
16
|
+
px: 1.25,
|
17
|
+
fontWeight: '800',
|
18
|
+
fontSize: '13px',
|
19
|
+
};
|
20
|
+
|
21
|
+
interface DataFieldDeleteModalProps {
|
22
|
+
open: boolean;
|
23
|
+
onClose: () => void;
|
24
|
+
onConfirm: () => void;
|
25
|
+
}
|
26
|
+
|
27
|
+
export function DataFieldDeleteModal({
|
28
|
+
open,
|
29
|
+
onClose,
|
30
|
+
onConfirm,
|
31
|
+
}: DataFieldDeleteModalProps): React.JSX.Element {
|
32
|
+
return (
|
33
|
+
<Dialog open={open} onClose={onClose}>
|
34
|
+
<DialogTitle>Delete Data Field?</DialogTitle>
|
35
|
+
<DialogContent>
|
36
|
+
<Typography>
|
37
|
+
Are you sure you want to delete this data field?
|
38
|
+
</Typography>
|
39
|
+
</DialogContent>
|
40
|
+
<DialogActions sx={{ justifyContent: 'space-between' }}>
|
41
|
+
<Button
|
42
|
+
variant='text'
|
43
|
+
color='neutral'
|
44
|
+
size='small'
|
45
|
+
onClick={onClose}
|
46
|
+
sx={buttonStyle}
|
47
|
+
data-testid='custom-demo-dialog-data-field-delete-cancel-button'
|
48
|
+
>
|
49
|
+
Don't Delete
|
50
|
+
</Button>
|
51
|
+
<Button
|
52
|
+
variant='contained'
|
53
|
+
color='error'
|
54
|
+
size='small'
|
55
|
+
onClick={onConfirm}
|
56
|
+
sx={buttonStyle}
|
57
|
+
data-testid='custom-demo-dialog-data-field-delete-confirm-button'
|
58
|
+
>
|
59
|
+
Delete
|
60
|
+
</Button>
|
61
|
+
</DialogActions>
|
62
|
+
</Dialog>
|
63
|
+
);
|
64
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import { useRef, useState } from 'react';
|
2
|
+
import { useController } from 'react-hook-form';
|
3
|
+
import debounce from 'lodash/debounce';
|
4
|
+
import { TextField } from '@mui/material';
|
5
|
+
|
6
|
+
import { type CredentialRequestsEditorForm } from '../types/form';
|
7
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
8
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
9
|
+
import { DataFieldSection } from './DataFieldSection';
|
10
|
+
|
11
|
+
export function DataFieldDescription(): React.JSX.Element {
|
12
|
+
const { features } = useCredentialRequestsEditor();
|
13
|
+
const isFeatureDisabled = features?.description?.disabled === true;
|
14
|
+
|
15
|
+
const credentialRequestField = useCredentialRequestField();
|
16
|
+
const description = useController<CredentialRequestsEditorForm>({
|
17
|
+
name: `${credentialRequestField?.path as any}.description` as any,
|
18
|
+
});
|
19
|
+
const [value, setValue] = useState(description.field.value ?? '');
|
20
|
+
|
21
|
+
const debounceChange = useRef(
|
22
|
+
debounce((value: string) => {
|
23
|
+
// Update form state
|
24
|
+
description.field.onChange({ target: { value } });
|
25
|
+
}, 500),
|
26
|
+
).current;
|
27
|
+
|
28
|
+
const handleChange = (e: any): void => {
|
29
|
+
if (isFeatureDisabled) return;
|
30
|
+
setValue(e.target.value);
|
31
|
+
debounceChange(e.target.value);
|
32
|
+
};
|
33
|
+
|
34
|
+
return (
|
35
|
+
<DataFieldSection
|
36
|
+
title='Field Description'
|
37
|
+
description='What text appears under the field'
|
38
|
+
tip={
|
39
|
+
<>
|
40
|
+
<pre>POST /1-click</pre>
|
41
|
+
<pre>{`{\n description?: string\n}`}</pre>
|
42
|
+
</>
|
43
|
+
}
|
44
|
+
sx={{
|
45
|
+
opacity: isFeatureDisabled ? 0.5 : 1,
|
46
|
+
}}
|
47
|
+
>
|
48
|
+
<TextField
|
49
|
+
{...description.field}
|
50
|
+
value={value}
|
51
|
+
onChange={handleChange}
|
52
|
+
error={!!description.fieldState.error}
|
53
|
+
helperText={
|
54
|
+
description.fieldState.error?.message ??
|
55
|
+
'Optional — defaults to empty'
|
56
|
+
}
|
57
|
+
label='Description'
|
58
|
+
color='success'
|
59
|
+
size='small'
|
60
|
+
className='original'
|
61
|
+
inputProps={{
|
62
|
+
'data-testid': 'custom-demo-dialog-data-field-description-input',
|
63
|
+
}}
|
64
|
+
disabled={isFeatureDisabled}
|
65
|
+
/>
|
66
|
+
</DataFieldSection>
|
67
|
+
);
|
68
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { useController } from 'react-hook-form';
|
2
|
+
import { RadioGroup } from '@mui/material';
|
3
|
+
|
4
|
+
import { type CredentialRequestsEditorForm } from '../types/form';
|
5
|
+
import { MandatoryEnum } from '../types/mandatoryEnum';
|
6
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
7
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
8
|
+
import { RadioOption } from './RadioOption';
|
9
|
+
import { DataFieldSection } from './DataFieldSection';
|
10
|
+
|
11
|
+
export function DataFieldMandatory(): React.JSX.Element {
|
12
|
+
const { features } = useCredentialRequestsEditor();
|
13
|
+
const isFeatureDisabled = features?.mandatory?.disabled === true;
|
14
|
+
|
15
|
+
const credentialRequestField = useCredentialRequestField();
|
16
|
+
const mandatory = useController<CredentialRequestsEditorForm>({
|
17
|
+
name: `${credentialRequestField?.path as any}.mandatory` as any,
|
18
|
+
});
|
19
|
+
|
20
|
+
return (
|
21
|
+
<DataFieldSection
|
22
|
+
title='Optional or Required'
|
23
|
+
description="Whether it's optional or required for the user to share this data"
|
24
|
+
tip={
|
25
|
+
<>
|
26
|
+
<pre>POST /1-click</pre>
|
27
|
+
<pre>{`{\n mandatory?: enum\n}`}</pre>
|
28
|
+
</>
|
29
|
+
}
|
30
|
+
sx={{
|
31
|
+
opacity: isFeatureDisabled ? 0.5 : 1,
|
32
|
+
}}
|
33
|
+
>
|
34
|
+
<RadioGroup
|
35
|
+
value={mandatory.field.value}
|
36
|
+
onChange={(e) => {
|
37
|
+
if (isFeatureDisabled) return;
|
38
|
+
const value = e.target.value as MandatoryEnum;
|
39
|
+
|
40
|
+
// Update form state
|
41
|
+
mandatory.field.onChange({ target: { value } });
|
42
|
+
}}
|
43
|
+
>
|
44
|
+
<RadioOption
|
45
|
+
isDefault
|
46
|
+
value={MandatoryEnum.NO}
|
47
|
+
title='Optional'
|
48
|
+
description='Optional for the user to share'
|
49
|
+
tip={MandatoryEnum.NO}
|
50
|
+
inputProps={
|
51
|
+
{
|
52
|
+
'data-testid': 'custom-demo-dialog-mandatory-no-radio',
|
53
|
+
} as any
|
54
|
+
}
|
55
|
+
disabled={isFeatureDisabled}
|
56
|
+
/>
|
57
|
+
<RadioOption
|
58
|
+
value={MandatoryEnum.IF_AVAILABLE}
|
59
|
+
title='Required if available'
|
60
|
+
description='Required to share, if the user has it'
|
61
|
+
tip={MandatoryEnum.IF_AVAILABLE}
|
62
|
+
inputProps={
|
63
|
+
{
|
64
|
+
'data-testid': 'custom-demo-dialog-mandatory-if_available-radio',
|
65
|
+
} as any
|
66
|
+
}
|
67
|
+
disabled={isFeatureDisabled}
|
68
|
+
/>
|
69
|
+
<RadioOption
|
70
|
+
value={MandatoryEnum.YES}
|
71
|
+
title='Required'
|
72
|
+
description="Required — flow fails if user doesn't have it"
|
73
|
+
tip={MandatoryEnum.YES}
|
74
|
+
inputProps={
|
75
|
+
{
|
76
|
+
'data-testid': 'custom-demo-dialog-mandatory-yes-radio',
|
77
|
+
} as any
|
78
|
+
}
|
79
|
+
disabled={isFeatureDisabled}
|
80
|
+
/>
|
81
|
+
</RadioGroup>
|
82
|
+
</DataFieldSection>
|
83
|
+
);
|
84
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import { RadioGroup } from '@mui/material';
|
2
|
+
import { useController } from 'react-hook-form';
|
3
|
+
|
4
|
+
import { type CredentialRequestsEditorForm } from '../types/form';
|
5
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
6
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
7
|
+
import { RadioOption } from './RadioOption';
|
8
|
+
import { DataFieldSection } from './DataFieldSection';
|
9
|
+
|
10
|
+
export function DataFieldMulti(): React.JSX.Element | null {
|
11
|
+
const { features } = useCredentialRequestsEditor();
|
12
|
+
const isFeatureDisabled = features?.multi?.disabled === true;
|
13
|
+
|
14
|
+
const credentialRequestField = useCredentialRequestField();
|
15
|
+
const multi = useController<CredentialRequestsEditorForm>({
|
16
|
+
name: `${credentialRequestField?.path as any}.multi` as any,
|
17
|
+
});
|
18
|
+
|
19
|
+
if ((credentialRequestField?.level ?? 0) > 0) return null;
|
20
|
+
|
21
|
+
return (
|
22
|
+
<DataFieldSection
|
23
|
+
title='Multiple Values'
|
24
|
+
description='Whether multiple data values should be included if available'
|
25
|
+
tip={
|
26
|
+
<>
|
27
|
+
<pre>POST /1-click</pre>
|
28
|
+
<pre>{`{\n multi?: boolean\n}`}</pre>
|
29
|
+
</>
|
30
|
+
}
|
31
|
+
sx={{
|
32
|
+
opacity: isFeatureDisabled ? 0.5 : 1,
|
33
|
+
}}
|
34
|
+
>
|
35
|
+
<RadioGroup
|
36
|
+
value={multi.field.value ?? false}
|
37
|
+
onChange={(_, value) => {
|
38
|
+
if (isFeatureDisabled) return;
|
39
|
+
|
40
|
+
// Update form state
|
41
|
+
multi.field.onChange({
|
42
|
+
target: { value: value === 'true' },
|
43
|
+
});
|
44
|
+
}}
|
45
|
+
>
|
46
|
+
<RadioOption
|
47
|
+
value={true}
|
48
|
+
title='Yes'
|
49
|
+
description='Multiple values will be included if available'
|
50
|
+
tip='true'
|
51
|
+
inputProps={
|
52
|
+
{
|
53
|
+
'data-testid': 'custom-demo-dialog-multi-yes-radio',
|
54
|
+
} as any
|
55
|
+
}
|
56
|
+
disabled={isFeatureDisabled}
|
57
|
+
/>
|
58
|
+
<RadioOption
|
59
|
+
isDefault
|
60
|
+
value={false}
|
61
|
+
title='No'
|
62
|
+
description="Multiple values won't be included"
|
63
|
+
tip='false'
|
64
|
+
inputProps={
|
65
|
+
{
|
66
|
+
'data-testid': 'custom-demo-dialog-multi-no-radio',
|
67
|
+
} as any
|
68
|
+
}
|
69
|
+
disabled={isFeatureDisabled}
|
70
|
+
/>
|
71
|
+
</RadioGroup>
|
72
|
+
</DataFieldSection>
|
73
|
+
);
|
74
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { useMemo } from 'react';
|
2
|
+
import { useController } from 'react-hook-form';
|
3
|
+
import { Autocomplete, TextField } from '@mui/material';
|
4
|
+
|
5
|
+
import { prettyField } from '../utils/prettyField';
|
6
|
+
|
7
|
+
import {
|
8
|
+
type CredentialRequests,
|
9
|
+
type CredentialRequestsEditorForm,
|
10
|
+
} from '../types/form';
|
11
|
+
import { buildDataFieldValue } from '../utils/buildDataFieldValue';
|
12
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
13
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
14
|
+
import { DataFieldSection } from './DataFieldSection';
|
15
|
+
|
16
|
+
export function DataFieldOptionType(): React.JSX.Element {
|
17
|
+
const credentialRequestField = useCredentialRequestField();
|
18
|
+
const field = useController<CredentialRequestsEditorForm>({
|
19
|
+
name: `${credentialRequestField?.path as any}` as any,
|
20
|
+
});
|
21
|
+
|
22
|
+
const { schemas } = useCredentialRequestsEditor();
|
23
|
+
const schemaValues = useMemo(() => {
|
24
|
+
if (!schemas) return [];
|
25
|
+
return Object.values(schemas)
|
26
|
+
.map((schema) => ({
|
27
|
+
label: prettyField(schema.$id),
|
28
|
+
id: schema.$id as string,
|
29
|
+
}))
|
30
|
+
.filter((schema) => {
|
31
|
+
const blacklist = ['IdentityCredential'];
|
32
|
+
return !blacklist.includes(schema.id);
|
33
|
+
})
|
34
|
+
.sort((a, b) => (a.label < b.label ? -1 : 1));
|
35
|
+
}, [schemas]);
|
36
|
+
const selectedValue = useMemo(() => {
|
37
|
+
const type = (field.field?.value as CredentialRequests)?.type;
|
38
|
+
return schemaValues?.find((value) => value.id === type);
|
39
|
+
}, [field, schemaValues]);
|
40
|
+
|
41
|
+
return (
|
42
|
+
<DataFieldSection
|
43
|
+
key={JSON.stringify(selectedValue)}
|
44
|
+
title='Field Type'
|
45
|
+
description='What type of user data this field is for'
|
46
|
+
tip={
|
47
|
+
<>
|
48
|
+
<pre>POST /1-click</pre>
|
49
|
+
<pre>{`{\n type: string\n}`}</pre>
|
50
|
+
</>
|
51
|
+
}
|
52
|
+
>
|
53
|
+
<Autocomplete
|
54
|
+
value={selectedValue}
|
55
|
+
onChange={(_, value) => {
|
56
|
+
if (!value) return;
|
57
|
+
|
58
|
+
credentialRequestField?.fieldArray.update(
|
59
|
+
credentialRequestField?.index,
|
60
|
+
buildDataFieldValue(value.id, schemas),
|
61
|
+
);
|
62
|
+
}}
|
63
|
+
options={schemaValues}
|
64
|
+
disablePortal
|
65
|
+
renderInput={(params) => (
|
66
|
+
<TextField
|
67
|
+
{...params}
|
68
|
+
label='Type'
|
69
|
+
color='success'
|
70
|
+
size='small'
|
71
|
+
className='original'
|
72
|
+
fullWidth
|
73
|
+
inputProps={{
|
74
|
+
...params.inputProps,
|
75
|
+
'data-testid': 'custom-demo-dialog-data-field-type-input',
|
76
|
+
}}
|
77
|
+
placeholder='Choose a type...'
|
78
|
+
/>
|
79
|
+
)}
|
80
|
+
disabled={(credentialRequestField?.level ?? 0) > 0 || schemas === null}
|
81
|
+
/>
|
82
|
+
</DataFieldSection>
|
83
|
+
);
|
84
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { type ReactNode } from 'react';
|
2
|
+
import { Stack, SxProps, Typography } from '@mui/material';
|
3
|
+
|
4
|
+
import { Tip } from '../../Tip';
|
5
|
+
|
6
|
+
interface DataFieldSectionProps {
|
7
|
+
children: ReactNode;
|
8
|
+
title: string;
|
9
|
+
description?: string;
|
10
|
+
tip?: ReactNode;
|
11
|
+
sx?: SxProps;
|
12
|
+
}
|
13
|
+
|
14
|
+
export function DataFieldSection(
|
15
|
+
props: DataFieldSectionProps,
|
16
|
+
): React.JSX.Element {
|
17
|
+
const { children, title, description, tip, sx } = props;
|
18
|
+
|
19
|
+
return (
|
20
|
+
<Stack sx={sx}>
|
21
|
+
<Stack direction='row' alignItems='center' spacing={0.5}>
|
22
|
+
<Typography
|
23
|
+
variant='body1'
|
24
|
+
sx={{ fontSize: '16px', fontWeight: '700' }}
|
25
|
+
data-testid='custom-demo-dialog-data-field-title'
|
26
|
+
>
|
27
|
+
{title}
|
28
|
+
</Typography>
|
29
|
+
<Tip>{tip}</Tip>
|
30
|
+
</Stack>
|
31
|
+
{description && (
|
32
|
+
<Typography
|
33
|
+
variant='body2'
|
34
|
+
color='text.secondary'
|
35
|
+
sx={{
|
36
|
+
textAlign: 'left !important',
|
37
|
+
fontSize: '12px',
|
38
|
+
fontWeight: '400',
|
39
|
+
}}
|
40
|
+
data-testid='custom-demo-dialog-data-field-description'
|
41
|
+
>
|
42
|
+
{description}
|
43
|
+
</Typography>
|
44
|
+
)}
|
45
|
+
<Stack sx={{ mt: 3 }}>{children}</Stack>
|
46
|
+
</Stack>
|
47
|
+
);
|
48
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { RadioGroup } from '@mui/material';
|
2
|
+
import { useController } from 'react-hook-form';
|
3
|
+
|
4
|
+
import { type CredentialRequestsEditorForm } from '../types/form';
|
5
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
6
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
7
|
+
import { RadioOption } from './RadioOption';
|
8
|
+
import { DataFieldSection } from './DataFieldSection';
|
9
|
+
|
10
|
+
export function DataFieldUserInput(): React.JSX.Element {
|
11
|
+
const { features } = useCredentialRequestsEditor();
|
12
|
+
const isFeatureDisabled = features?.description?.disabled === true;
|
13
|
+
|
14
|
+
const credentialRequestField = useCredentialRequestField();
|
15
|
+
const allowUserInput = useController<CredentialRequestsEditorForm>({
|
16
|
+
name: `${credentialRequestField?.path as any}.allowUserInput` as any,
|
17
|
+
});
|
18
|
+
|
19
|
+
return (
|
20
|
+
<DataFieldSection
|
21
|
+
title='Allow User Input'
|
22
|
+
description='Whether the user is allowed to add or edit data for this field'
|
23
|
+
tip={
|
24
|
+
<>
|
25
|
+
<pre>POST /1-click</pre>
|
26
|
+
<pre>{`{\n allowUserInput?: boolean\n}`}</pre>
|
27
|
+
</>
|
28
|
+
}
|
29
|
+
sx={{
|
30
|
+
opacity: isFeatureDisabled ? 0.5 : 1,
|
31
|
+
}}
|
32
|
+
>
|
33
|
+
<RadioGroup
|
34
|
+
value={allowUserInput.field.value}
|
35
|
+
onChange={(_, value) => {
|
36
|
+
if (isFeatureDisabled) return;
|
37
|
+
// Update form state
|
38
|
+
allowUserInput.field.onChange({
|
39
|
+
target: { value: value === 'true' },
|
40
|
+
});
|
41
|
+
}}
|
42
|
+
>
|
43
|
+
<RadioOption
|
44
|
+
isDefault
|
45
|
+
value={true}
|
46
|
+
title='Yes'
|
47
|
+
description='The user can add or edit data for the user to share'
|
48
|
+
tip='true'
|
49
|
+
inputProps={
|
50
|
+
{
|
51
|
+
'data-testid': 'custom-demo-dialog-user-input-yes-radio',
|
52
|
+
} as any
|
53
|
+
}
|
54
|
+
disabled={isFeatureDisabled}
|
55
|
+
/>
|
56
|
+
<RadioOption
|
57
|
+
value={false}
|
58
|
+
title='No'
|
59
|
+
description="The user can't add or edit data"
|
60
|
+
tip='false'
|
61
|
+
inputProps={
|
62
|
+
{
|
63
|
+
'data-testid': 'custom-demo-dialog-user-input-no-radio',
|
64
|
+
} as any
|
65
|
+
}
|
66
|
+
disabled={isFeatureDisabled}
|
67
|
+
/>
|
68
|
+
</RadioGroup>
|
69
|
+
</DataFieldSection>
|
70
|
+
);
|
71
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import type { ReactNode } from 'react';
|
2
|
+
import {
|
3
|
+
Chip,
|
4
|
+
Radio,
|
5
|
+
type RadioProps,
|
6
|
+
Stack,
|
7
|
+
type SxProps,
|
8
|
+
Typography,
|
9
|
+
Box,
|
10
|
+
} from '@mui/material';
|
11
|
+
|
12
|
+
import { Tip } from '../../Tip';
|
13
|
+
|
14
|
+
type RadioOptionProps = RadioProps & {
|
15
|
+
isDefault?: boolean;
|
16
|
+
title: string;
|
17
|
+
description?: string;
|
18
|
+
tip?: ReactNode;
|
19
|
+
sx?: SxProps;
|
20
|
+
};
|
21
|
+
export function RadioOption(props: RadioOptionProps): React.JSX.Element {
|
22
|
+
const { isDefault, title, description, tip, sx, ...radioProps } = props;
|
23
|
+
return (
|
24
|
+
<Stack
|
25
|
+
direction='row'
|
26
|
+
justifyContent='space-between'
|
27
|
+
alignItems='center'
|
28
|
+
sx={{ mb: 1, ...(sx as any) }}
|
29
|
+
>
|
30
|
+
<Stack sx={{ alignItems: 'flex-start' }}>
|
31
|
+
<Stack direction='row' spacing={1}>
|
32
|
+
<Radio
|
33
|
+
{...(radioProps as any)}
|
34
|
+
sx={{
|
35
|
+
mt: '1px',
|
36
|
+
width: 34,
|
37
|
+
height: 34,
|
38
|
+
...sx,
|
39
|
+
'&.Mui-checked': {
|
40
|
+
color: '#0dbc3d',
|
41
|
+
},
|
42
|
+
}}
|
43
|
+
/>
|
44
|
+
<Stack>
|
45
|
+
<Stack direction='row' alignItems='center' spacing={1}>
|
46
|
+
<Typography
|
47
|
+
variant='body1'
|
48
|
+
sx={{
|
49
|
+
fontSize: '16px',
|
50
|
+
fontWeight: '400',
|
51
|
+
textAlign: 'left !important',
|
52
|
+
}}
|
53
|
+
>
|
54
|
+
{title}
|
55
|
+
</Typography>
|
56
|
+
<Tip>{tip}</Tip>
|
57
|
+
</Stack>
|
58
|
+
{description && (
|
59
|
+
<Typography
|
60
|
+
variant='body2'
|
61
|
+
color='text.disabled'
|
62
|
+
sx={{
|
63
|
+
textAlign: 'left !important',
|
64
|
+
alignSelf: 'flex-start',
|
65
|
+
fontSize: '12px',
|
66
|
+
fontWeight: '400',
|
67
|
+
mt: 0.5,
|
68
|
+
}}
|
69
|
+
>
|
70
|
+
{description}
|
71
|
+
</Typography>
|
72
|
+
)}
|
73
|
+
</Stack>
|
74
|
+
</Stack>
|
75
|
+
</Stack>
|
76
|
+
<Box sx={{ mt: 1, alignSelf: 'flex-start' }}>
|
77
|
+
{isDefault && (
|
78
|
+
<Chip
|
79
|
+
size='small'
|
80
|
+
label='Default'
|
81
|
+
color='info'
|
82
|
+
variant='outlined'
|
83
|
+
sx={{ fontWeight: 700 }}
|
84
|
+
/>
|
85
|
+
)}
|
86
|
+
</Box>
|
87
|
+
</Stack>
|
88
|
+
);
|
89
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import React, {
|
2
|
+
createContext,
|
3
|
+
type PropsWithChildren,
|
4
|
+
useContext,
|
5
|
+
} from 'react';
|
6
|
+
import {
|
7
|
+
type FieldArrayWithId,
|
8
|
+
type UseFieldArrayReturn,
|
9
|
+
} from 'react-hook-form';
|
10
|
+
import type { CredentialRequestsEditorForm } from '../types/form';
|
11
|
+
|
12
|
+
type CredentialRequestFieldContext = PropsWithChildren & {
|
13
|
+
path: string | undefined;
|
14
|
+
field: FieldArrayWithId<CredentialRequestsEditorForm, 'credentialRequests'>;
|
15
|
+
fieldArray: UseFieldArrayReturn<
|
16
|
+
CredentialRequestsEditorForm,
|
17
|
+
'credentialRequests'
|
18
|
+
>;
|
19
|
+
index: number;
|
20
|
+
level: number;
|
21
|
+
onAllFieldsDelete: () => void;
|
22
|
+
};
|
23
|
+
|
24
|
+
const Context = createContext<CredentialRequestFieldContext | null>(null);
|
25
|
+
|
26
|
+
export const useCredentialRequestField =
|
27
|
+
(): CredentialRequestFieldContext | null => {
|
28
|
+
return useContext(Context);
|
29
|
+
};
|
30
|
+
|
31
|
+
export function CredentialRequestFieldProvider({
|
32
|
+
children,
|
33
|
+
...props
|
34
|
+
}: CredentialRequestFieldContext): React.JSX.Element {
|
35
|
+
return <Context.Provider value={props}>{children}</Context.Provider>;
|
36
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import {
|
2
|
+
type CredentialRequestsEditorProps,
|
3
|
+
CredentialRequestsEditorProvider,
|
4
|
+
} from './CredentialRequestsEditor.context';
|
5
|
+
import { CredentialRequestsField } from './components/CredentialRequestsField';
|
6
|
+
|
7
|
+
export function CredentialRequestsEditor(
|
8
|
+
props: Omit<CredentialRequestsEditorProps, 'children'>,
|
9
|
+
): React.JSX.Element {
|
10
|
+
return (
|
11
|
+
<CredentialRequestsEditorProvider {...props}>
|
12
|
+
<CredentialRequestsField />
|
13
|
+
</CredentialRequestsEditorProvider>
|
14
|
+
);
|
15
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export type CompositeCredentialSchema = Record<string, any>;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { type MandatoryEnum } from './mandatoryEnum';
|
2
|
+
|
3
|
+
export interface CredentialRequests {
|
4
|
+
type: string;
|
5
|
+
issuers?: string[];
|
6
|
+
required?: boolean;
|
7
|
+
mandatory?: MandatoryEnum;
|
8
|
+
description?: string;
|
9
|
+
allowUserInput?: boolean;
|
10
|
+
multi?: boolean;
|
11
|
+
children?: CredentialRequests[];
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface CredentialRequestsWithNew {
|
15
|
+
type: string;
|
16
|
+
issuers?: string[];
|
17
|
+
required?: boolean;
|
18
|
+
mandatory?: MandatoryEnum;
|
19
|
+
description?: string;
|
20
|
+
allowUserInput?: boolean;
|
21
|
+
multi?: boolean;
|
22
|
+
children?: CredentialRequestsWithNew[];
|
23
|
+
isNew?: boolean;
|
24
|
+
}
|
25
|
+
|
26
|
+
export interface CredentialRequestsEditorForm {
|
27
|
+
credentialRequests: CredentialRequestsWithNew[];
|
28
|
+
}
|