@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
package/README.md
CHANGED
@@ -29,3 +29,44 @@ This project utilizes alias only for the storybook implementation, the modules t
|
|
29
29
|
## Adding dependencies
|
30
30
|
|
31
31
|
Ensure that new dependencies are added to both devDependencies and peerDependencies if required in the client's project.
|
32
|
+
|
33
|
+
## Installing in Your Client Session
|
34
|
+
|
35
|
+
When installing the `@verifiedinc/shared-ui-elements` package, you have access to different export paths optimized for specific use cases.
|
36
|
+
|
37
|
+
### Default ESM Export (Recommended)
|
38
|
+
|
39
|
+
The package’s default export path `'./'` uses ECMAScript modules (ESM), providing an optimized build for modern JavaScript environments. This approach is recommended for most applications:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
npm install @verifiedinc/shared-ui-elements
|
43
|
+
```
|
44
|
+
|
45
|
+
```typescript
|
46
|
+
import { SomeComponent } from '@verifiedinc/shared-ui-elements';
|
47
|
+
```
|
48
|
+
|
49
|
+
This setup is efficient and works seamlessly with frameworks like Next.js, Vite, and other ESM-compatible environments.
|
50
|
+
|
51
|
+
### Using with Remix
|
52
|
+
|
53
|
+
Some frontend frameworks, such as Remix, have partial compatibility with ESM-only packages. To address this, the package also exposes the source files directly. This approach ensures that the Remix transpiler can properly bundle the package.
|
54
|
+
|
55
|
+
To utilize the source files in Remix, reference them directly in your import statements and adjust your `remix.config.js` as follows:
|
56
|
+
|
57
|
+
```typescript
|
58
|
+
import { SomeComponent } from '@verifiedinc/shared-ui-elements/src';
|
59
|
+
```
|
60
|
+
|
61
|
+
```js
|
62
|
+
// remix.config.js
|
63
|
+
export default {
|
64
|
+
serverDependenciesToBundle: [/@verifiedinc\/shared-ui-elements/],
|
65
|
+
};
|
66
|
+
```
|
67
|
+
|
68
|
+
### Why Two Exports?
|
69
|
+
|
70
|
+
The default `'./'` export is optimized for environments that support ESM, providing better performance and tree-shaking. However, by also exposing the raw source files, we ensure compatibility with tools like Remix, where ESM-only packages might not work seamlessly without additional configuration.
|
71
|
+
|
72
|
+
Choose the export method that best suits your project setup.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@verifiedinc-public/shared-ui-elements",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.12.0",
|
4
4
|
"description": "A set of UI components, utilities that is shareable with the core apps.",
|
5
5
|
"private": false,
|
6
6
|
"keywords": [],
|
@@ -12,33 +12,16 @@
|
|
12
12
|
"main": "/dist/shared-ui-elements.mjs",
|
13
13
|
"types": "/dist/index.d.ts",
|
14
14
|
"files": [
|
15
|
-
"dist"
|
15
|
+
"dist",
|
16
|
+
"src"
|
16
17
|
],
|
17
18
|
"exports": {
|
18
19
|
".": {
|
20
|
+
"types": "./dist/index.d.ts",
|
19
21
|
"import": "./dist/shared-ui-elements.mjs",
|
20
|
-
"
|
22
|
+
"require": "./dist/shared-ui-elements.mjs"
|
21
23
|
},
|
22
|
-
"./
|
23
|
-
"import": "./dist/shared-ui-elements.mjs",
|
24
|
-
"types": "./dist/index.d.ts"
|
25
|
-
},
|
26
|
-
"./hooks": {
|
27
|
-
"import": "./dist/shared-ui-elements.mjs",
|
28
|
-
"types": "./dist/index.d.ts"
|
29
|
-
},
|
30
|
-
"./utils": {
|
31
|
-
"import": "./dist/shared-ui-elements.mjs",
|
32
|
-
"types": "./dist/index.d.ts"
|
33
|
-
},
|
34
|
-
"./validations": {
|
35
|
-
"import": "./dist/shared-ui-elements.mjs",
|
36
|
-
"types": "./dist/index.d.ts"
|
37
|
-
},
|
38
|
-
"./styles": {
|
39
|
-
"import": "./dist/shared-ui-elements.mjs",
|
40
|
-
"types": "./dist/index.d.ts"
|
41
|
-
}
|
24
|
+
"./src": "./src/index.ts"
|
42
25
|
},
|
43
26
|
"scripts": {
|
44
27
|
"vitest": "vitest",
|
@@ -116,7 +99,6 @@
|
|
116
99
|
"@mona-health/react-input-mask": "^3.0.3",
|
117
100
|
"@mui/icons-material": "^5",
|
118
101
|
"@mui/material": "^5",
|
119
|
-
"jsdom": "^24.1.0",
|
120
102
|
"libphonenumber-js": "^1.11.11",
|
121
103
|
"qrcode": "^1.5.4",
|
122
104
|
"react": "^18",
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { Alert, type AlertProps } from '@mui/material';
|
2
|
+
|
3
|
+
interface FullWidthAlertProps extends AlertProps {}
|
4
|
+
|
5
|
+
export function FullWidthAlert({
|
6
|
+
children,
|
7
|
+
sx,
|
8
|
+
...props
|
9
|
+
}: FullWidthAlertProps): React.JSX.Element {
|
10
|
+
return (
|
11
|
+
<>
|
12
|
+
<Alert
|
13
|
+
severity='info'
|
14
|
+
sx={{
|
15
|
+
maxWidth: '100%',
|
16
|
+
width: '100%',
|
17
|
+
textAlign: 'left',
|
18
|
+
alignItems: 'center',
|
19
|
+
...sx,
|
20
|
+
}}
|
21
|
+
{...props}
|
22
|
+
>
|
23
|
+
{children}
|
24
|
+
</Alert>
|
25
|
+
</>
|
26
|
+
);
|
27
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import MUIButton from '@mui/material/Button';
|
2
|
+
import type { ButtonProps as MUIButtonProps } from '@mui/material/Button';
|
3
|
+
|
4
|
+
export type ButtonProps = MUIButtonProps;
|
5
|
+
|
6
|
+
export function Button(props: ButtonProps): React.JSX.Element {
|
7
|
+
return <MUIButton {...props} />;
|
8
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import { createContext, type ReactNode, useContext, useEffect } from 'react';
|
2
|
+
import { FormProvider, useForm, type WatchObserver } from 'react-hook-form';
|
3
|
+
import debounce from 'lodash/debounce';
|
4
|
+
|
5
|
+
import { omitProperties } from '../../utils/omitProperty';
|
6
|
+
|
7
|
+
import {
|
8
|
+
type CredentialRequests,
|
9
|
+
type CredentialRequestsEditorForm,
|
10
|
+
type CredentialRequestsWithNew,
|
11
|
+
} from './types/form';
|
12
|
+
|
13
|
+
export interface CredentialRequestsEditorFeatures {
|
14
|
+
allowUserInput?: {
|
15
|
+
disabled?: boolean;
|
16
|
+
};
|
17
|
+
description?: {
|
18
|
+
disabled?: boolean;
|
19
|
+
};
|
20
|
+
mandatory?: {
|
21
|
+
disabled?: boolean;
|
22
|
+
};
|
23
|
+
multi?: {
|
24
|
+
disabled?: boolean;
|
25
|
+
};
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface CredentialRequestsEditorProps {
|
29
|
+
addButtonText?: string;
|
30
|
+
credentialRequests: CredentialRequestsWithNew[];
|
31
|
+
schemas: Record<string, any>;
|
32
|
+
children: ReactNode;
|
33
|
+
onChange: (credentialRequests: CredentialRequests[]) => void;
|
34
|
+
features?: CredentialRequestsEditorFeatures;
|
35
|
+
}
|
36
|
+
|
37
|
+
export interface CredentialRequestsEditorContext {
|
38
|
+
addButtonText?: string;
|
39
|
+
schemas: Record<string, any>;
|
40
|
+
features?: CredentialRequestsEditorFeatures;
|
41
|
+
}
|
42
|
+
|
43
|
+
const Context = createContext<CredentialRequestsEditorContext | null>(null);
|
44
|
+
|
45
|
+
export function useCredentialRequestsEditor(): CredentialRequestsEditorContext {
|
46
|
+
const context = useContext(Context);
|
47
|
+
if (!context) {
|
48
|
+
throw new Error(
|
49
|
+
'useCredentialRequestsEditor must be used within a CredentialRequestsEditorProvider',
|
50
|
+
);
|
51
|
+
}
|
52
|
+
return context;
|
53
|
+
}
|
54
|
+
|
55
|
+
export function CredentialRequestsEditorProvider(
|
56
|
+
props: CredentialRequestsEditorProps,
|
57
|
+
): React.JSX.Element {
|
58
|
+
const form = useForm<CredentialRequestsEditorForm>({
|
59
|
+
defaultValues: { credentialRequests: props.credentialRequests },
|
60
|
+
});
|
61
|
+
|
62
|
+
// Listen to credentialRequests changes and call onChange event
|
63
|
+
useEffect(() => {
|
64
|
+
// Debouncing the watch observer to prevent multiple calls in a short period of time since it may dispatch child object change plus the property change
|
65
|
+
const debouncedWatchObserver = debounce<
|
66
|
+
WatchObserver<CredentialRequestsEditorForm>
|
67
|
+
>((data, { name, type }) => {
|
68
|
+
if (data.credentialRequests) {
|
69
|
+
const credentialRequestsData = data.credentialRequests.filter(
|
70
|
+
(credentialRequest) => !!credentialRequest?.type,
|
71
|
+
);
|
72
|
+
|
73
|
+
props.onChange(
|
74
|
+
omitProperties(credentialRequestsData, [
|
75
|
+
'isNew',
|
76
|
+
'id',
|
77
|
+
]) as CredentialRequests[],
|
78
|
+
);
|
79
|
+
}
|
80
|
+
}, 100);
|
81
|
+
const subscription = form.watch(debouncedWatchObserver);
|
82
|
+
return subscription.unsubscribe;
|
83
|
+
}, [form.watch]);
|
84
|
+
|
85
|
+
return (
|
86
|
+
<FormProvider {...form}>
|
87
|
+
<Context.Provider
|
88
|
+
value={{
|
89
|
+
addButtonText: props.addButtonText,
|
90
|
+
schemas: props.schemas,
|
91
|
+
features: props.features,
|
92
|
+
}}
|
93
|
+
>
|
94
|
+
{props.children}
|
95
|
+
</Context.Provider>
|
96
|
+
</FormProvider>
|
97
|
+
);
|
98
|
+
}
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Stack } from '@mui/material';
|
3
|
+
import { Add } from '@mui/icons-material';
|
4
|
+
import {
|
5
|
+
useFieldArray,
|
6
|
+
type UseFieldArrayReturn,
|
7
|
+
useFormContext,
|
8
|
+
} from 'react-hook-form';
|
9
|
+
import { DndProvider } from 'react-dnd';
|
10
|
+
import { HTML5Backend } from 'react-dnd-html5-backend';
|
11
|
+
|
12
|
+
import { Button } from '../../Button';
|
13
|
+
|
14
|
+
import { buildDataFieldValue } from '../utils/buildDataFieldValue';
|
15
|
+
import { CredentialRequestFieldProvider } from '../contexts/CredentialRequestFieldContext';
|
16
|
+
import { DataFieldAccordion } from './DataFieldAccordion';
|
17
|
+
|
18
|
+
import { useCredentialRequestsEditor } from '../CredentialRequestsEditor.context';
|
19
|
+
import {
|
20
|
+
type CredentialRequestsEditorForm,
|
21
|
+
type CredentialRequestsWithNew,
|
22
|
+
} from '../types/form';
|
23
|
+
|
24
|
+
function CredentialRequestField({
|
25
|
+
path = 'credentialRequests',
|
26
|
+
parentFieldArray,
|
27
|
+
parentIndex = 0,
|
28
|
+
level = 0,
|
29
|
+
}: Readonly<{
|
30
|
+
path?: string;
|
31
|
+
parentFieldArray?: UseFieldArrayReturn<CredentialRequestsEditorForm>;
|
32
|
+
parentIndex?: number;
|
33
|
+
level?: number;
|
34
|
+
}>): React.JSX.Element {
|
35
|
+
const customConfig = useCredentialRequestsEditor();
|
36
|
+
const form = useFormContext<CredentialRequestsEditorForm>();
|
37
|
+
const fieldArray = useFieldArray<CredentialRequestsEditorForm>({
|
38
|
+
control: form.control,
|
39
|
+
name: path as any,
|
40
|
+
});
|
41
|
+
|
42
|
+
return (
|
43
|
+
<>
|
44
|
+
{fieldArray.fields.map((field, index) => {
|
45
|
+
const _path = `${path}.${index}`;
|
46
|
+
return (
|
47
|
+
<CredentialRequestFieldProvider
|
48
|
+
key={_path + field.type}
|
49
|
+
path={_path}
|
50
|
+
field={field}
|
51
|
+
fieldArray={fieldArray}
|
52
|
+
index={index}
|
53
|
+
level={level}
|
54
|
+
onAllFieldsDelete={() => {
|
55
|
+
(parentFieldArray ?? fieldArray)?.remove(parentIndex);
|
56
|
+
}}
|
57
|
+
>
|
58
|
+
<DataFieldAccordion />
|
59
|
+
{Array.isArray(field.children) && (
|
60
|
+
<CredentialRequestField
|
61
|
+
key={`${_path}.children`}
|
62
|
+
path={`${_path}.children`}
|
63
|
+
parentFieldArray={fieldArray}
|
64
|
+
parentIndex={index}
|
65
|
+
level={level + 1}
|
66
|
+
/>
|
67
|
+
)}
|
68
|
+
</CredentialRequestFieldProvider>
|
69
|
+
);
|
70
|
+
})}
|
71
|
+
{path === 'credentialRequests' && (
|
72
|
+
<Button
|
73
|
+
type='button'
|
74
|
+
onClick={() => {
|
75
|
+
if (!customConfig) return;
|
76
|
+
const newValue: CredentialRequestsWithNew = {
|
77
|
+
...buildDataFieldValue('', customConfig.schemas),
|
78
|
+
isNew: true,
|
79
|
+
};
|
80
|
+
fieldArray.append(newValue);
|
81
|
+
}}
|
82
|
+
size='large'
|
83
|
+
variant='outlined'
|
84
|
+
startIcon={<Add />}
|
85
|
+
fullWidth
|
86
|
+
sx={{ width: '100%' }}
|
87
|
+
>
|
88
|
+
{customConfig.addButtonText ?? 'Add Credential Request'}
|
89
|
+
</Button>
|
90
|
+
)}
|
91
|
+
</>
|
92
|
+
);
|
93
|
+
}
|
94
|
+
|
95
|
+
export function CredentialRequestsField(): React.JSX.Element {
|
96
|
+
return (
|
97
|
+
<DndProvider backend={HTML5Backend}>
|
98
|
+
<Stack spacing={2}>
|
99
|
+
<CredentialRequestField />
|
100
|
+
</Stack>
|
101
|
+
</DndProvider>
|
102
|
+
);
|
103
|
+
}
|
@@ -0,0 +1,337 @@
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2
|
+
import {
|
3
|
+
Accordion,
|
4
|
+
AccordionDetails,
|
5
|
+
AccordionSummary,
|
6
|
+
Box,
|
7
|
+
IconButton,
|
8
|
+
Paper,
|
9
|
+
Stack,
|
10
|
+
Typography,
|
11
|
+
useTheme,
|
12
|
+
} from '@mui/material';
|
13
|
+
import {
|
14
|
+
CheckCircle,
|
15
|
+
ChevronLeft,
|
16
|
+
Close,
|
17
|
+
Delete,
|
18
|
+
Menu,
|
19
|
+
} from '@mui/icons-material';
|
20
|
+
import { useDrag, useDrop } from 'react-dnd';
|
21
|
+
import { useController, useFormContext } from 'react-hook-form';
|
22
|
+
|
23
|
+
import { RequiredLabel } from '../../RequiredLabel';
|
24
|
+
|
25
|
+
import { prettyField } from '../utils/prettyField';
|
26
|
+
import {
|
27
|
+
type CredentialRequestsEditorForm,
|
28
|
+
type CredentialRequestsWithNew,
|
29
|
+
} from '../types/form';
|
30
|
+
import { MandatoryEnum } from '../types/mandatoryEnum';
|
31
|
+
import { useCredentialRequestField } from '../contexts/CredentialRequestFieldContext';
|
32
|
+
import { DataFieldOptionType } from './DataFieldOptionType';
|
33
|
+
import { DataFieldDescription } from './DataFieldDescription';
|
34
|
+
import { DataFieldMandatory } from './DataFieldMandatory';
|
35
|
+
import { DataFieldUserInput } from './DataFieldUserInput';
|
36
|
+
import { DataFieldDeleteModal } from './DataFieldDeleteModal';
|
37
|
+
import { DataFieldMulti } from './DataFieldMulti';
|
38
|
+
|
39
|
+
interface DataFieldAccordionProps {
|
40
|
+
defaultExpanded?: boolean;
|
41
|
+
}
|
42
|
+
|
43
|
+
export function DataFieldAccordion(
|
44
|
+
props: DataFieldAccordionProps,
|
45
|
+
): React.JSX.Element {
|
46
|
+
const { defaultExpanded } = props;
|
47
|
+
const credentialRequestField = useCredentialRequestField();
|
48
|
+
const formContext = useFormContext<CredentialRequestsEditorForm>();
|
49
|
+
const field = useController<CredentialRequestsEditorForm>({
|
50
|
+
name: `${credentialRequestField?.path as any}` as any,
|
51
|
+
});
|
52
|
+
const credentialRequest = field.field.value as CredentialRequestsWithNew;
|
53
|
+
const credentialRequests = formContext.watch('credentialRequests');
|
54
|
+
const isNew: boolean = (credentialRequestField?.field as any).isNew;
|
55
|
+
const [expanded, setOpen] = useState((defaultExpanded ?? isNew) || false);
|
56
|
+
const [modalOpen, setModalOpen] = useState(false);
|
57
|
+
|
58
|
+
const accordionRef = useRef<HTMLDivElement | null>(null);
|
59
|
+
|
60
|
+
const fieldType = String(credentialRequestField?.field.type);
|
61
|
+
const type = prettyField(fieldType || 'Choose a type...');
|
62
|
+
|
63
|
+
const theme = useTheme();
|
64
|
+
const chevronClassName = 'chevron';
|
65
|
+
|
66
|
+
const canDrop = useCallback(
|
67
|
+
(item: typeof credentialRequestField) => {
|
68
|
+
const source = item;
|
69
|
+
const target = credentialRequestField;
|
70
|
+
|
71
|
+
if (!source || !target) return false;
|
72
|
+
|
73
|
+
const getParentPath = (path: string): string =>
|
74
|
+
path.split('.').slice(0, -2).join('.');
|
75
|
+
|
76
|
+
const sourcePath = getParentPath(source?.path ?? '');
|
77
|
+
const targetPath = getParentPath(target?.path ?? '');
|
78
|
+
const isSameGroup = sourcePath === targetPath;
|
79
|
+
|
80
|
+
const fromLevel = source.level;
|
81
|
+
const fromIndex = source.index;
|
82
|
+
const toLevel = target.level;
|
83
|
+
const toIndex = target.index;
|
84
|
+
|
85
|
+
// Allow to drop only on the same level and different index
|
86
|
+
if (fromLevel !== toLevel || fromIndex === toIndex || !isSameGroup) {
|
87
|
+
return false;
|
88
|
+
}
|
89
|
+
|
90
|
+
return true;
|
91
|
+
},
|
92
|
+
[credentialRequestField],
|
93
|
+
);
|
94
|
+
|
95
|
+
const [{ opacity }, drag, preview] = useDrag(
|
96
|
+
() => ({
|
97
|
+
type: 'data-field-drag',
|
98
|
+
item: () => credentialRequestField,
|
99
|
+
collect: (monitor) => ({
|
100
|
+
opacity: monitor.isDragging() ? 0 : 1,
|
101
|
+
}),
|
102
|
+
}),
|
103
|
+
[credentialRequestField, credentialRequests],
|
104
|
+
);
|
105
|
+
|
106
|
+
const [{ opacity: dropOpacity }, drop] = useDrop(
|
107
|
+
() => ({
|
108
|
+
accept: 'data-field-drag',
|
109
|
+
canDrop(item) {
|
110
|
+
return canDrop(item as typeof credentialRequestField);
|
111
|
+
},
|
112
|
+
drop(item) {
|
113
|
+
const source = item as typeof credentialRequestField;
|
114
|
+
const target = credentialRequestField;
|
115
|
+
|
116
|
+
if (!source || !target) return;
|
117
|
+
if (!canDrop(source)) return;
|
118
|
+
|
119
|
+
const fromIndex = source.index;
|
120
|
+
const toIndex = target.index;
|
121
|
+
|
122
|
+
credentialRequestField.fieldArray.move(fromIndex, toIndex);
|
123
|
+
},
|
124
|
+
collect: (monitor) => {
|
125
|
+
if (monitor.isOver()) {
|
126
|
+
return {
|
127
|
+
opacity: monitor.canDrop() ? 0.4 : 1,
|
128
|
+
};
|
129
|
+
}
|
130
|
+
return {
|
131
|
+
opacity: 1,
|
132
|
+
};
|
133
|
+
},
|
134
|
+
}),
|
135
|
+
[credentialRequestField, credentialRequests],
|
136
|
+
);
|
137
|
+
|
138
|
+
const handleRemove = (): void => {
|
139
|
+
if (!credentialRequestField) return;
|
140
|
+
setModalOpen(false);
|
141
|
+
|
142
|
+
// Delete parent when the last field was removed from the stack of form fields.
|
143
|
+
// The validation should be against less or equal than 1 because is against a previous state check.
|
144
|
+
if (credentialRequestField.fieldArray.fields.length <= 1) {
|
145
|
+
credentialRequestField.onAllFieldsDelete();
|
146
|
+
return;
|
147
|
+
}
|
148
|
+
|
149
|
+
credentialRequestField.fieldArray.remove(credentialRequestField.index);
|
150
|
+
};
|
151
|
+
|
152
|
+
const renderTitle = (): React.JSX.Element => {
|
153
|
+
const typographyStyle = {
|
154
|
+
fontStyle: fieldType ? 'normal' : 'italic',
|
155
|
+
fontSize: '16px',
|
156
|
+
fontWeight: '800',
|
157
|
+
textAlign: 'left !important',
|
158
|
+
alignSelf: 'flex-start',
|
159
|
+
};
|
160
|
+
|
161
|
+
return (
|
162
|
+
<Typography variant='body1' sx={typographyStyle}>
|
163
|
+
{credentialRequest.mandatory !== MandatoryEnum.NO ? (
|
164
|
+
<RequiredLabel>{type}</RequiredLabel>
|
165
|
+
) : (
|
166
|
+
type
|
167
|
+
)}
|
168
|
+
</Typography>
|
169
|
+
);
|
170
|
+
};
|
171
|
+
|
172
|
+
const renderUserInput = (): React.JSX.Element => {
|
173
|
+
const allowUserInput = credentialRequest.allowUserInput;
|
174
|
+
|
175
|
+
return (
|
176
|
+
<Stack direction='row' alignItems='center' spacing={0.5} pl={5.25}>
|
177
|
+
{allowUserInput ? (
|
178
|
+
<CheckCircle
|
179
|
+
sx={{ fontSize: '12px', color: theme.palette.text.disabled }}
|
180
|
+
/>
|
181
|
+
) : (
|
182
|
+
<Close
|
183
|
+
sx={{ fontSize: '12px', color: theme.palette.text.disabled }}
|
184
|
+
/>
|
185
|
+
)}
|
186
|
+
<Typography
|
187
|
+
variant='body1'
|
188
|
+
color='text.disabled'
|
189
|
+
sx={{
|
190
|
+
fontSize: '12px',
|
191
|
+
fontWeight: '400',
|
192
|
+
alignSelf: 'flex-start',
|
193
|
+
textAlign: 'left!important',
|
194
|
+
}}
|
195
|
+
>
|
196
|
+
Allow User Input
|
197
|
+
</Typography>
|
198
|
+
</Stack>
|
199
|
+
);
|
200
|
+
};
|
201
|
+
|
202
|
+
useEffect(() => {
|
203
|
+
if (!isNew) return;
|
204
|
+
accordionRef.current?.scrollIntoView({ behavior: 'smooth' });
|
205
|
+
}, [isNew]);
|
206
|
+
|
207
|
+
return (
|
208
|
+
<Stack
|
209
|
+
ref={drop}
|
210
|
+
sx={{ position: 'relative', width: '100%', opacity: dropOpacity }}
|
211
|
+
>
|
212
|
+
<Paper
|
213
|
+
ref={(element) => preview(element)}
|
214
|
+
sx={{
|
215
|
+
p: '0!important',
|
216
|
+
width: `calc(100% - ${
|
217
|
+
(credentialRequestField?.level ?? 0) * 30
|
218
|
+
}px)!important`,
|
219
|
+
alignSelf: 'flex-end',
|
220
|
+
opacity,
|
221
|
+
}}
|
222
|
+
>
|
223
|
+
<Box>
|
224
|
+
<Accordion
|
225
|
+
defaultExpanded={isNew}
|
226
|
+
expanded={expanded}
|
227
|
+
sx={{
|
228
|
+
boxShadow: 'none',
|
229
|
+
'&::before': {
|
230
|
+
display: 'none',
|
231
|
+
},
|
232
|
+
my: '0px !important',
|
233
|
+
mt: 0,
|
234
|
+
p: '8px !important',
|
235
|
+
}}
|
236
|
+
data-testid='custom-demo-dialog-data-field-accordion'
|
237
|
+
>
|
238
|
+
<AccordionSummary
|
239
|
+
onClick={() => {
|
240
|
+
setOpen((prev) => !prev);
|
241
|
+
}}
|
242
|
+
expandIcon={
|
243
|
+
<>
|
244
|
+
<IconButton
|
245
|
+
size='small'
|
246
|
+
onClick={(e) => {
|
247
|
+
e.stopPropagation();
|
248
|
+
setModalOpen(true);
|
249
|
+
}}
|
250
|
+
data-testid='custom-demo-dialog-data-field-delete-button'
|
251
|
+
>
|
252
|
+
<Delete
|
253
|
+
fontSize='small'
|
254
|
+
sx={{
|
255
|
+
transform: 'rotate(0deg)',
|
256
|
+
}}
|
257
|
+
/>
|
258
|
+
</IconButton>
|
259
|
+
<Stack
|
260
|
+
className={chevronClassName}
|
261
|
+
sx={{ ml: 1, alignSelf: 'center' }}
|
262
|
+
>
|
263
|
+
<ChevronLeft
|
264
|
+
fontSize='small'
|
265
|
+
sx={{
|
266
|
+
color: '#0dbc3d',
|
267
|
+
transform: 'rotate(0deg)',
|
268
|
+
}}
|
269
|
+
/>
|
270
|
+
</Stack>
|
271
|
+
</>
|
272
|
+
}
|
273
|
+
sx={{
|
274
|
+
px: 0,
|
275
|
+
minHeight: 'auto!important',
|
276
|
+
'& .MuiAccordionSummary-content': {
|
277
|
+
my: '0px !important',
|
278
|
+
},
|
279
|
+
'& .MuiAccordionSummary-expandIconWrapper': {
|
280
|
+
alignSelf: 'flex-start',
|
281
|
+
transform: 'rotate(0deg) !important',
|
282
|
+
[`& .${chevronClassName}`]: {
|
283
|
+
transition: 'transform .3s',
|
284
|
+
},
|
285
|
+
'&.Mui-expanded': {
|
286
|
+
[`& .${chevronClassName}`]: {
|
287
|
+
transform: 'rotate(-90deg)',
|
288
|
+
},
|
289
|
+
},
|
290
|
+
},
|
291
|
+
}}
|
292
|
+
>
|
293
|
+
<Stack sx={{ alignItems: 'flex-start', mr: 0.5 }}>
|
294
|
+
<Stack direction='column' alignItems='flex-start' spacing={0}>
|
295
|
+
<Stack direction='row' alignItems='center' spacing={1}>
|
296
|
+
<IconButton
|
297
|
+
ref={drag}
|
298
|
+
size='small'
|
299
|
+
color='success'
|
300
|
+
onClick={(e) => {
|
301
|
+
e.preventDefault();
|
302
|
+
e.stopPropagation();
|
303
|
+
}}
|
304
|
+
sx={{ cursor: 'grab' }}
|
305
|
+
>
|
306
|
+
<Menu />
|
307
|
+
</IconButton>
|
308
|
+
{renderTitle()}
|
309
|
+
</Stack>
|
310
|
+
{renderUserInput()}
|
311
|
+
</Stack>
|
312
|
+
</Stack>
|
313
|
+
</AccordionSummary>
|
314
|
+
<AccordionDetails sx={{ pt: 3 }}>
|
315
|
+
{expanded && (
|
316
|
+
<Stack spacing={2}>
|
317
|
+
<DataFieldOptionType />
|
318
|
+
<DataFieldDescription />
|
319
|
+
<DataFieldMandatory />
|
320
|
+
<DataFieldUserInput />
|
321
|
+
<DataFieldMulti />
|
322
|
+
</Stack>
|
323
|
+
)}
|
324
|
+
</AccordionDetails>
|
325
|
+
</Accordion>
|
326
|
+
</Box>
|
327
|
+
</Paper>
|
328
|
+
<DataFieldDeleteModal
|
329
|
+
open={modalOpen}
|
330
|
+
onClose={() => {
|
331
|
+
setModalOpen(false);
|
332
|
+
}}
|
333
|
+
onConfirm={handleRemove}
|
334
|
+
/>
|
335
|
+
</Stack>
|
336
|
+
);
|
337
|
+
}
|