@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.
Files changed (90) hide show
  1. package/README.md +41 -0
  2. package/package.json +6 -24
  3. package/src/components/Alert/Alert.tsx +8 -0
  4. package/src/components/Alert/FullWidthAlert.tsx +27 -0
  5. package/src/components/Alert/index.ts +2 -0
  6. package/src/components/Button/index.tsx +8 -0
  7. package/src/components/CredentialRequestsEditor/CredentialRequestsEditor.context.tsx +98 -0
  8. package/src/components/CredentialRequestsEditor/components/CredentialRequestsField.tsx +103 -0
  9. package/src/components/CredentialRequestsEditor/components/DataFieldAccordion.tsx +337 -0
  10. package/src/components/CredentialRequestsEditor/components/DataFieldDeleteModal.tsx +64 -0
  11. package/src/components/CredentialRequestsEditor/components/DataFieldDescription.tsx +68 -0
  12. package/src/components/CredentialRequestsEditor/components/DataFieldMandatory.tsx +84 -0
  13. package/src/components/CredentialRequestsEditor/components/DataFieldMulti.tsx +74 -0
  14. package/src/components/CredentialRequestsEditor/components/DataFieldOptionType.tsx +84 -0
  15. package/src/components/CredentialRequestsEditor/components/DataFieldSection.tsx +48 -0
  16. package/src/components/CredentialRequestsEditor/components/DataFieldUserInput.tsx +71 -0
  17. package/src/components/CredentialRequestsEditor/components/RadioOption.tsx +89 -0
  18. package/src/components/CredentialRequestsEditor/contexts/CredentialRequestFieldContext.tsx +36 -0
  19. package/src/components/CredentialRequestsEditor/index.tsx +15 -0
  20. package/src/components/CredentialRequestsEditor/types/compositeCredentialSchema.ts +1 -0
  21. package/src/components/CredentialRequestsEditor/types/credentialSchemasDto.ts +3 -0
  22. package/src/components/CredentialRequestsEditor/types/form.ts +28 -0
  23. package/src/components/CredentialRequestsEditor/types/mandatoryEnum.ts +5 -0
  24. package/src/components/CredentialRequestsEditor/utils/buildDataFieldValue.ts +65 -0
  25. package/src/components/CredentialRequestsEditor/utils/prettyField.ts +16 -0
  26. package/src/components/Image.tsx +10 -0
  27. package/src/components/QRCodeDisplay/index.tsx +50 -0
  28. package/src/components/RequiredLabel/index.tsx +15 -0
  29. package/src/components/TextField/index.tsx +8 -0
  30. package/src/components/Tip/index.tsx +18 -0
  31. package/src/components/Typography/index.tsx +8 -0
  32. package/src/components/When.tsx +28 -0
  33. package/src/components/form/CountrySelector.tsx +96 -0
  34. package/src/components/form/DataFieldClearAdornment.tsx +28 -0
  35. package/src/components/form/DateInput.tsx +117 -0
  36. package/src/components/form/DefaultInput.tsx +28 -0
  37. package/src/components/form/InputMask.tsx +41 -0
  38. package/src/components/form/OTPInput.tsx +246 -0
  39. package/src/components/form/PhoneInput.tsx +153 -0
  40. package/src/components/form/SSNInput.tsx +100 -0
  41. package/src/components/form/SelectInput.tsx +89 -0
  42. package/src/components/form/TextMaskCustom.tsx +48 -0
  43. package/src/components/form/index.ts +5 -0
  44. package/src/components/form/styles/input.ts +24 -0
  45. package/src/components/index.ts +10 -0
  46. package/src/components/terms/AcceptTermsNotice.tsx +27 -0
  47. package/src/components/terms/LegalLink.tsx +22 -0
  48. package/src/components/verified/VerifiedImage.tsx +272 -0
  49. package/src/components/verified/VerifiedIncLogo.tsx +11 -0
  50. package/src/components/verified/index.ts +2 -0
  51. package/src/hooks/index.ts +5 -0
  52. package/src/hooks/useCallbackRef.ts +22 -0
  53. package/src/hooks/useCopyToClipboard.ts +76 -0
  54. package/src/hooks/useDisclosure.ts +96 -0
  55. package/src/hooks/useLocalStorage.ts +24 -0
  56. package/src/hooks/usePrevious.ts +17 -0
  57. package/src/hooks/useQRCode.ts +62 -0
  58. package/src/index.ts +5 -0
  59. package/src/stories/components/Alert.stories.tsx +41 -0
  60. package/src/stories/components/Button.stories.ts +49 -0
  61. package/src/stories/components/CredentialRequestsEditor.stories.tsx +98 -0
  62. package/src/stories/components/QRCodeDisplay.stories.tsx +60 -0
  63. package/src/stories/components/TextField.stories.ts +59 -0
  64. package/src/stories/components/Typography.stories.ts +140 -0
  65. package/src/stories/components/VerifiedImage.stories.tsx +32 -0
  66. package/src/stories/components/form/DateInput.stories.ts +39 -0
  67. package/src/stories/components/form/OTPInput.stories.tsx +90 -0
  68. package/src/stories/components/form/PhoneInput.stories.tsx +34 -0
  69. package/src/stories/components/form/SSNInput.stories.ts +30 -0
  70. package/src/stories/components/form/SelectInput.stories.ts +39 -0
  71. package/src/stories/hooks/useCopyToClipboard.stories.tsx +45 -0
  72. package/src/styles/colors.ts +34 -0
  73. package/src/styles/index.ts +2 -0
  74. package/src/styles/theme.ts +209 -0
  75. package/src/utils/date.ts +41 -0
  76. package/src/utils/index.ts +5 -0
  77. package/src/utils/masks/index.ts +6 -0
  78. package/src/utils/omitProperty.ts +19 -0
  79. package/src/utils/phone.ts +76 -0
  80. package/src/utils/wrapPromise.ts +19 -0
  81. package/src/validations/birthDate.schema.ts +54 -0
  82. package/src/validations/date.schema.ts +10 -0
  83. package/src/validations/description.schema.ts +5 -0
  84. package/src/validations/email.schema.ts +3 -0
  85. package/src/validations/field.schema.ts +3 -0
  86. package/src/validations/index.ts +9 -0
  87. package/src/validations/phone.schema.ts +6 -0
  88. package/src/validations/ssn.schema.ts +6 -0
  89. package/src/validations/state.schema.ts +3 -0
  90. 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.11.6",
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
- "types": "./dist/index.d.ts"
22
+ "require": "./dist/shared-ui-elements.mjs"
21
23
  },
22
- "./components": {
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,8 @@
1
+ import MUIAlert from '@mui/material/Alert';
2
+ import type { AlertProps as MUIAlertProps } from '@mui/material/Alert';
3
+
4
+ export type AlertProps = MUIAlertProps;
5
+
6
+ export function Alert(props: AlertProps): React.JSX.Element {
7
+ return <MUIAlert {...props} />;
8
+ }
@@ -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,2 @@
1
+ export * from './Alert';
2
+ export * from './FullWidthAlert';
@@ -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
+ }