cozy-ui 128.1.0 → 128.3.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 (101) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +5 -1
  3. package/react/ActionsMenu/Actions/viewInDrive.js +2 -1
  4. package/react/Contacts/AddModal/ContactAddressDialog/helpers.js +22 -0
  5. package/react/Contacts/AddModal/ContactAddressDialog/helpers.spec.js +64 -0
  6. package/react/Contacts/AddModal/ContactAddressDialog/index.jsx +84 -0
  7. package/react/Contacts/AddModal/ContactAddressDialog/locales/en.json +25 -0
  8. package/react/Contacts/AddModal/ContactAddressDialog/locales/fr.json +25 -0
  9. package/react/Contacts/AddModal/ContactAddressDialog/locales/index.jsx +7 -0
  10. package/react/Contacts/AddModal/ContactForm/FieldInput.jsx +117 -0
  11. package/react/Contacts/AddModal/ContactForm/FieldInputArray.jsx +80 -0
  12. package/react/Contacts/AddModal/ContactForm/FieldInputLayout.jsx +65 -0
  13. package/react/Contacts/AddModal/ContactForm/FieldInputWrapper.jsx +41 -0
  14. package/react/Contacts/AddModal/ContactForm/HasValueCondition.jsx +31 -0
  15. package/react/Contacts/AddModal/ContactForm/HasValueCondition.spec.jsx +79 -0
  16. package/react/Contacts/AddModal/ContactForm/RelatedContactList.jsx +37 -0
  17. package/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.jsx +78 -0
  18. package/react/Contacts/AddModal/ContactForm/TextFieldSelect.jsx +39 -0
  19. package/react/Contacts/AddModal/ContactForm/__snapshots__/HasValueCondition.spec.jsx.snap +33 -0
  20. package/react/Contacts/AddModal/ContactForm/contactToFormValues.js +99 -0
  21. package/react/Contacts/AddModal/ContactForm/contactToFormValues.spec.js +128 -0
  22. package/react/Contacts/AddModal/ContactForm/fieldsConfig.jsx +341 -0
  23. package/react/Contacts/AddModal/ContactForm/formValuesToContact.js +100 -0
  24. package/react/Contacts/AddModal/ContactForm/formValuesToContact.spec.js +494 -0
  25. package/react/Contacts/AddModal/ContactForm/helpers.js +324 -0
  26. package/react/Contacts/AddModal/ContactForm/helpers.spec.js +152 -0
  27. package/react/Contacts/AddModal/ContactForm/index.jsx +104 -0
  28. package/react/Contacts/AddModal/ContactForm/index.spec.jsx +289 -0
  29. package/react/Contacts/AddModal/ContactForm/locales/en.json +73 -0
  30. package/react/Contacts/AddModal/ContactForm/locales/fr.json +73 -0
  31. package/react/Contacts/AddModal/ContactForm/locales/index.jsx +7 -0
  32. package/react/Contacts/AddModal/ContactForm/styles.styl +2 -0
  33. package/react/Contacts/AddModal/CustomLabelDialog/index.jsx +108 -0
  34. package/react/Contacts/AddModal/CustomLabelDialog/locales/en.json +15 -0
  35. package/react/Contacts/AddModal/CustomLabelDialog/locales/fr.json +15 -0
  36. package/react/Contacts/AddModal/CustomLabelDialog/locales/index.jsx +7 -0
  37. package/react/Contacts/AddModal/Readme.md +46 -0
  38. package/react/Contacts/AddModal/index.jsx +78 -0
  39. package/react/Contacts/AddModal/locales/en.json +13 -0
  40. package/react/Contacts/AddModal/locales/fr.json +13 -0
  41. package/react/Contacts/AddModal/locales/index.jsx +7 -0
  42. package/react/Contacts/AddModal/mocks.js +249 -0
  43. package/react/Contacts/AddModal/types.js +57 -0
  44. package/react/Contacts/Header/Readme.md +0 -2
  45. package/react/providers/DemoProvider.jsx +3 -2
  46. package/transpiled/react/ActionsMenu/Actions/viewInDrive.js +2 -1
  47. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.d.ts +4 -0
  48. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.js +20 -0
  49. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/helpers.spec.d.ts +1 -0
  50. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/index.d.ts +39 -0
  51. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/index.js +87 -0
  52. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/locales/index.d.ts +6 -0
  53. package/transpiled/react/Contacts/AddModal/ContactAddressDialog/locales/index.js +54 -0
  54. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInput.d.ts +35 -0
  55. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInput.js +126 -0
  56. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputArray.d.ts +14 -0
  57. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputArray.js +82 -0
  58. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputLayout.d.ts +20 -0
  59. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputLayout.js +70 -0
  60. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputWrapper.d.ts +16 -0
  61. package/transpiled/react/Contacts/AddModal/ContactForm/FieldInputWrapper.js +31 -0
  62. package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.d.ts +18 -0
  63. package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.js +32 -0
  64. package/transpiled/react/Contacts/AddModal/ContactForm/HasValueCondition.spec.d.ts +1 -0
  65. package/transpiled/react/Contacts/AddModal/ContactForm/RelatedContactList.d.ts +15 -0
  66. package/transpiled/react/Contacts/AddModal/ContactForm/RelatedContactList.js +39 -0
  67. package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.d.ts +9 -0
  68. package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldCustomLabelSelect.js +81 -0
  69. package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldSelect.d.ts +5 -0
  70. package/transpiled/react/Contacts/AddModal/ContactForm/TextFieldSelect.js +42 -0
  71. package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.d.ts +2 -0
  72. package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.js +88 -0
  73. package/transpiled/react/Contacts/AddModal/ContactForm/contactToFormValues.spec.d.ts +1 -0
  74. package/transpiled/react/Contacts/AddModal/ContactForm/fieldsConfig.d.ts +4 -0
  75. package/transpiled/react/Contacts/AddModal/ContactForm/fieldsConfig.js +278 -0
  76. package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.d.ts +6 -0
  77. package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.js +94 -0
  78. package/transpiled/react/Contacts/AddModal/ContactForm/formValuesToContact.spec.d.ts +1 -0
  79. package/transpiled/react/Contacts/AddModal/ContactForm/helpers.d.ts +28 -0
  80. package/transpiled/react/Contacts/AddModal/ContactForm/helpers.js +335 -0
  81. package/transpiled/react/Contacts/AddModal/ContactForm/helpers.spec.d.ts +1 -0
  82. package/transpiled/react/Contacts/AddModal/ContactForm/index.d.ts +11 -0
  83. package/transpiled/react/Contacts/AddModal/ContactForm/index.js +114 -0
  84. package/transpiled/react/Contacts/AddModal/ContactForm/index.spec.d.ts +1 -0
  85. package/transpiled/react/Contacts/AddModal/ContactForm/locales/index.d.ts +6 -0
  86. package/transpiled/react/Contacts/AddModal/ContactForm/locales/index.js +150 -0
  87. package/transpiled/react/Contacts/AddModal/CustomLabelDialog/index.d.ts +22 -0
  88. package/transpiled/react/Contacts/AddModal/CustomLabelDialog/index.js +113 -0
  89. package/transpiled/react/Contacts/AddModal/CustomLabelDialog/locales/index.d.ts +6 -0
  90. package/transpiled/react/Contacts/AddModal/CustomLabelDialog/locales/index.js +34 -0
  91. package/transpiled/react/Contacts/AddModal/index.d.ts +7 -0
  92. package/transpiled/react/Contacts/AddModal/index.js +109 -0
  93. package/transpiled/react/Contacts/AddModal/locales/index.d.ts +6 -0
  94. package/transpiled/react/Contacts/AddModal/locales/index.js +30 -0
  95. package/transpiled/react/Contacts/AddModal/mocks.d.ts +270 -0
  96. package/transpiled/react/Contacts/AddModal/mocks.js +214 -0
  97. package/transpiled/react/Contacts/AddModal/types.d.ts +54 -0
  98. package/transpiled/react/Contacts/AddModal/types.js +49 -0
  99. package/transpiled/react/providers/DemoProvider.d.ts +2 -1
  100. package/transpiled/react/providers/DemoProvider.js +7 -3
  101. package/transpiled/react/stylesheet.css +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [128.3.0](https://github.com/cozy/cozy-ui/compare/v128.2.0...v128.3.0) (2025-09-03)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add ContactsAddModal component ([5e21445](https://github.com/cozy/cozy-ui/commit/5e21445))
7
+ * **DemoProvider:** Add ability to pass locales ([f6815a3](https://github.com/cozy/cozy-ui/commit/f6815a3))
8
+
9
+ # [128.2.0](https://github.com/cozy/cozy-ui/compare/v128.1.0...v128.2.0) (2025-09-03)
10
+
11
+
12
+ ### Features
13
+
14
+ * Adjust view in drive to adapt shared drive :sparkles: ([dcf0204](https://github.com/cozy/cozy-ui/commit/dcf0204))
15
+
1
16
  # [128.1.0](https://github.com/cozy/cozy-ui/compare/v128.0.0...v128.1.0) (2025-09-02)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "128.1.0",
3
+ "version": "128.3.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -171,6 +171,8 @@
171
171
  "cozy-interapp": "^0.5.4",
172
172
  "date-fns": "2.30.0",
173
173
  "filesize": "8.0.7",
174
+ "final-form": "4.20.9",
175
+ "final-form-arrays": "3.1.0",
174
176
  "hammerjs": "^2.0.8",
175
177
  "intersection-observer": "0.11.0",
176
178
  "mime-types": "2.1.35",
@@ -179,6 +181,8 @@
179
181
  "normalize.css": "^8.0.0",
180
182
  "pdf-lib": "1.17.1",
181
183
  "react-chartjs-2": "4.1.0",
184
+ "react-final-form": "6.5.9",
185
+ "react-final-form-arrays": "3.1.4",
182
186
  "react-markdown": "^4.0.8",
183
187
  "react-popper": "^2.2.3",
184
188
  "react-remove-scroll": "^2.4.0",
@@ -38,12 +38,13 @@ export const viewInDrive = () => {
38
38
  Component: makeComponent(label, icon),
39
39
  action: (docs, { client }) => {
40
40
  const dirId = docs[0].dir_id
41
+ const driveId = docs[0].driveId
41
42
  const webLink = generateWebLink({
42
43
  slug: 'drive',
43
44
  cozyUrl: client.getStackClient().uri,
44
45
  subDomainType: client.getInstanceOptions().subdomain,
45
46
  pathname: '/',
46
- hash: `folder/${dirId}`
47
+ hash: driveId ? `shareddrive/${driveId}/${dirId}` : `folder/${dirId}`
47
48
  })
48
49
 
49
50
  window.open(webLink, '_blank')
@@ -0,0 +1,22 @@
1
+ import { cleanFormattedAddress } from 'cozy-client/dist/models/contact'
2
+
3
+ /**
4
+ * Make formatted address
5
+ * @param {{ name: string, value: string }[]} subFieldsState - State of address sub fields
6
+ * @returns {string} - Formatted address
7
+ */
8
+ export const makeFormattedAddressWithSubFields = (subFieldsState, t) => {
9
+ const normalizedAddress = subFieldsState.reduce((acc, curr) => {
10
+ const key = curr.name
11
+ .split('.')
12
+ .pop()
13
+ .replace(/address/, '')
14
+
15
+ return {
16
+ ...acc,
17
+ [key]: curr.value || ''
18
+ }
19
+ }, {})
20
+
21
+ return cleanFormattedAddress(t('formatted.address', normalizedAddress))
22
+ }
@@ -0,0 +1,64 @@
1
+ import { makeFormattedAddressWithSubFields } from './helpers'
2
+
3
+ describe('makeFormattedAddressWithSubFields', () => {
4
+ it('should return full formatted address', () => {
5
+ const subFieldsStateMock = [
6
+ { name: 'address[0].addressnumber', value: '10' },
7
+ { name: 'address[0].addressstreet', value: 'rue du test' },
8
+ { name: 'address[0].addresscode', value: '75056' },
9
+ { name: 'address[0].addresscity', value: 'Paris' },
10
+ { name: 'address[0].addresscountry', value: 'France' }
11
+ ]
12
+ const res = makeFormattedAddressWithSubFields(
13
+ subFieldsStateMock,
14
+ jest.fn(() => '10 rue du test, 75056 Paris, France')
15
+ )
16
+
17
+ expect(res).toBe('10 rue du test, 75056 Paris, France')
18
+ })
19
+ it('should return formatted address if only "code" & "city" values are defined', () => {
20
+ const subFieldsStateMock = [
21
+ { name: 'address[0].addressnumber', value: undefined },
22
+ { name: 'address[0].addressstreet', value: undefined },
23
+ { name: 'address[0].addresscode', value: '75056' },
24
+ { name: 'address[0].addresscity', value: 'Paris' },
25
+ { name: 'address[0].addresscountry', value: undefined }
26
+ ]
27
+ const res = makeFormattedAddressWithSubFields(
28
+ subFieldsStateMock,
29
+ jest.fn(() => ' , 75056 Paris, ')
30
+ )
31
+
32
+ expect(res).toBe('75056 Paris')
33
+ })
34
+ it('should return formatted address if "code" & "city" values are undefined', () => {
35
+ const subFieldsStateMock = [
36
+ { name: 'address[0].addressnumber', value: '10' },
37
+ { name: 'address[0].addressstreet', value: 'rue du test' },
38
+ { name: 'address[0].addresscode', value: undefined },
39
+ { name: 'address[0].addresscity', value: undefined },
40
+ { name: 'address[0].addresscountry', value: 'France' }
41
+ ]
42
+ const res = makeFormattedAddressWithSubFields(
43
+ subFieldsStateMock,
44
+ jest.fn(() => '10 rue du test, , France')
45
+ )
46
+
47
+ expect(res).toBe('10 rue du test, France')
48
+ })
49
+ it('should return formatted address if all values are undefined', () => {
50
+ const subFieldsStateMock = [
51
+ { name: 'address[0].addressnumber', value: undefined },
52
+ { name: 'address[0].addressstreet', value: undefined },
53
+ { name: 'address[0].addresscode', value: undefined },
54
+ { name: 'address[0].addresscity', value: undefined },
55
+ { name: 'address[0].addresscountry', value: undefined }
56
+ ]
57
+ const res = makeFormattedAddressWithSubFields(
58
+ subFieldsStateMock,
59
+ jest.fn(() => ' , , ')
60
+ )
61
+
62
+ expect(res).toBe('')
63
+ })
64
+ })
@@ -0,0 +1,84 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import { Field, useForm } from 'react-final-form'
4
+
5
+ import { makeFormattedAddressWithSubFields } from './helpers'
6
+ import { locales } from './locales'
7
+ import Button from '../../../Buttons'
8
+ import { FixedDialog } from '../../../CozyDialogs'
9
+ import { useI18n, useExtendI18n } from '../../../providers/I18n'
10
+ import FieldInputWrapper from '../ContactForm/FieldInputWrapper'
11
+ import { fieldInputAttributesTypes } from '../types'
12
+
13
+ const ContactAddressModal = ({ onClose, name, subFields }) => {
14
+ useExtendI18n(locales)
15
+ const { t } = useI18n()
16
+ const { getFieldState, change } = useForm()
17
+
18
+ const subFieldsState = subFields.map(subField =>
19
+ getFieldState(`${name}${subField.name}`)
20
+ )
21
+
22
+ const onConfirm = () => {
23
+ const hasBeenModified = subFieldsState.some(state => !state.pristine)
24
+ if (!hasBeenModified) {
25
+ return onClose()
26
+ }
27
+
28
+ const formattedAddress = makeFormattedAddressWithSubFields(
29
+ subFieldsState,
30
+ t
31
+ )
32
+ change(name, formattedAddress)
33
+
34
+ onClose()
35
+ }
36
+
37
+ const onCancel = () => {
38
+ subFieldsState.forEach(({ name, initial }) => change(name, initial))
39
+ onClose()
40
+ }
41
+
42
+ return (
43
+ <FixedDialog
44
+ open
45
+ onClose={onClose}
46
+ size="small"
47
+ title={t('Contacts.AddModal.ContactAddressDialog.fields.address')}
48
+ content={subFields.map(subField => (
49
+ <div key={subField.name} className="u-mt-1">
50
+ <Field
51
+ label={t(
52
+ `Contacts.AddModal.ContactAddressDialog.fields.${subField.name}`
53
+ )}
54
+ attributes={{ type: subField.type }}
55
+ name={`${name}${subField.name}`}
56
+ component={FieldInputWrapper}
57
+ />
58
+ </div>
59
+ ))}
60
+ actions={
61
+ <>
62
+ <Button
63
+ variant="secondary"
64
+ label={t('Contacts.AddModal.ContactAddressDialog.cancel')}
65
+ onClick={onCancel}
66
+ />
67
+ <Button
68
+ className="u-ml-half"
69
+ label={t('Contacts.AddModal.ContactAddressDialog.ok')}
70
+ onClick={onConfirm}
71
+ />
72
+ </>
73
+ }
74
+ />
75
+ )
76
+ }
77
+
78
+ ContactAddressModal.propTypes = {
79
+ onClose: PropTypes.func.isRequired,
80
+ name: PropTypes.string.isRequired,
81
+ subFields: PropTypes.arrayOf(fieldInputAttributesTypes).isRequired
82
+ }
83
+
84
+ export default ContactAddressModal
@@ -0,0 +1,25 @@
1
+ {
2
+ "Contacts": {
3
+ "AddModal": {
4
+ "ContactAddressDialog": {
5
+ "fields": {
6
+ "address": "Address",
7
+ "number": "Lane number",
8
+ "street": "Postal address",
9
+ "code": "Postal code",
10
+ "city": "City",
11
+ "country": "Country",
12
+ "locality": "Locality",
13
+ "building": "Building",
14
+ "stairs": "Stairs",
15
+ "floor": "Floor",
16
+ "apartment": "Apartment",
17
+ "region": "Region",
18
+ "entrycode": "Entry code"
19
+ },
20
+ "cancel": "Cancel",
21
+ "ok": "Ok"
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "Contacts": {
3
+ "AddModal": {
4
+ "ContactAddressDialog": {
5
+ "fields": {
6
+ "address": "Adresse",
7
+ "number": "Numéro de voie",
8
+ "street": "Adresse postal",
9
+ "code": "Code postal",
10
+ "city": "Ville",
11
+ "country": "Pays",
12
+ "locality": "Lieu-dit",
13
+ "building": "Bâtiment",
14
+ "stairs": "Escalier",
15
+ "floor": "Etage",
16
+ "apartment": "Appartement",
17
+ "region": "Région",
18
+ "entrycode": "Code d'entrée"
19
+ },
20
+ "cancel": "Annuler",
21
+ "ok": "Ok"
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ import en from './en.json'
2
+ import fr from './fr.json'
3
+
4
+ export const locales = {
5
+ en,
6
+ fr
7
+ }
@@ -0,0 +1,117 @@
1
+ import cx from 'classnames'
2
+ import uniqueId from 'lodash/uniqueId'
3
+ import PropTypes from 'prop-types'
4
+ import React, { useState } from 'react'
5
+ import { Field } from 'react-final-form'
6
+
7
+ import FieldInputWrapper from './FieldInputWrapper'
8
+ import HasValueCondition from './HasValueCondition'
9
+ import { RelatedContactList } from './RelatedContactList'
10
+ import { locales } from './locales'
11
+ import styles from './styles.styl'
12
+ import { useI18n, useExtendI18n } from '../../../providers/I18n'
13
+ import ContactAddressDialog from '../ContactAddressDialog'
14
+ import { fieldInputAttributesTypes, labelPropTypes } from '../types'
15
+
16
+ const FieldInput = ({
17
+ name,
18
+ labelProps,
19
+ attributes: { subFields, ...restAttributes },
20
+ contacts,
21
+ error,
22
+ helperText,
23
+ label,
24
+ required
25
+ }) => {
26
+ const [id] = useState(uniqueId('field_')) // state only use to generate id once and not at each render
27
+ const [hasBeenFocused, setHasBeenFocused] = useState(false)
28
+ const [isAddressDialogOpen, setIsAddressDialogOpen] = useState(false)
29
+ const [isRelatedContactDialogOpen, setIsRelatedContactDialogOpen] =
30
+ useState(false)
31
+ useExtendI18n(locales)
32
+ const { t } = useI18n()
33
+
34
+ const handleClick = () => {
35
+ if (name.includes('address')) {
36
+ setIsAddressDialogOpen(true)
37
+ }
38
+
39
+ if (name.includes('relatedContact')) {
40
+ setIsRelatedContactDialogOpen(true)
41
+ }
42
+ }
43
+
44
+ const onFocus = () => {
45
+ setHasBeenFocused(true)
46
+ }
47
+
48
+ return (
49
+ <div
50
+ className={cx(
51
+ styles['contact-form-field__wrapper'],
52
+ 'u-flex',
53
+ 'u-flex-column-s'
54
+ )}
55
+ >
56
+ <Field
57
+ required={required}
58
+ error={error}
59
+ helperText={helperText}
60
+ label={label}
61
+ id={id}
62
+ attributes={restAttributes}
63
+ name={name}
64
+ component={FieldInputWrapper}
65
+ onFocus={onFocus}
66
+ onClick={handleClick}
67
+ />
68
+ {isAddressDialogOpen && (
69
+ <ContactAddressDialog
70
+ onClose={() => setIsAddressDialogOpen(false)}
71
+ name={name}
72
+ subFields={subFields}
73
+ />
74
+ )}
75
+ {isRelatedContactDialogOpen && (
76
+ <RelatedContactList
77
+ onClose={() => setIsRelatedContactDialogOpen(false)}
78
+ name={name}
79
+ contacts={contacts}
80
+ />
81
+ )}
82
+ {labelProps && (
83
+ <HasValueCondition name={name} otherCondition={hasBeenFocused}>
84
+ <div className="u-mt-half-s u-ml-half u-ml-0-s u-flex-shrink-0 u-w-auto u-miw-4">
85
+ <Field
86
+ attributes={labelProps}
87
+ name={`${name}Label`}
88
+ label={t('Contacts.AddModal.ContactForm.fields.label')}
89
+ component={FieldInputWrapper}
90
+ onFocus={onFocus}
91
+ />
92
+ </div>
93
+ </HasValueCondition>
94
+ )}
95
+ </div>
96
+ )
97
+ }
98
+
99
+ FieldInput.propTypes = {
100
+ name: PropTypes.string.isRequired,
101
+ labelProps: labelPropTypes,
102
+ attributes: fieldInputAttributesTypes,
103
+ contacts: PropTypes.shape({
104
+ data: PropTypes.arrayOf(PropTypes.object)
105
+ }),
106
+ // Destructuring props
107
+ id: PropTypes.string,
108
+ label: PropTypes.string,
109
+ required: PropTypes.bool
110
+ }
111
+
112
+ FieldInput.defaultProps = {
113
+ labelProps: null,
114
+ required: false
115
+ }
116
+
117
+ export default FieldInput
@@ -0,0 +1,80 @@
1
+ import cx from 'classnames'
2
+ import React from 'react'
3
+ import { FieldArray } from 'react-final-form-arrays'
4
+
5
+ import FieldInput from './FieldInput'
6
+ import { fieldsRequired, addField, removeField } from './helpers'
7
+ import { locales } from './locales'
8
+ import Button from '../../../Buttons'
9
+ import Icon from '../../../Icon'
10
+ import IconButton from '../../../IconButton'
11
+ import CrossCircleIcon from '../../../Icons/CrossCircle'
12
+ import PlusIcon from '../../../Icons/Plus'
13
+ import ListItemIcon from '../../../ListItemIcon'
14
+ import { useI18n, useExtendI18n } from '../../../providers/I18n'
15
+
16
+ const FieldInputArray = ({
17
+ attributes: { name, label, ...restAttributes },
18
+ contacts,
19
+ formProps: { valid, submitFailed, errors }
20
+ }) => {
21
+ useExtendI18n(locales)
22
+ const { t } = useI18n()
23
+
24
+ return (
25
+ <FieldArray name={name}>
26
+ {({ fields }) => {
27
+ return (
28
+ <>
29
+ {fields.map((nameWithIndex, index) => {
30
+ const key = fields.value[index]?.fieldId || nameWithIndex
31
+ const showRemove = fields.value[index]?.[name]
32
+ const inputName = `${nameWithIndex}.${name}`
33
+ const isError =
34
+ fieldsRequired.includes(inputName) && !valid && submitFailed
35
+
36
+ return (
37
+ <div
38
+ key={key}
39
+ className={cx('u-flex u-flex-items-center', {
40
+ 'u-mt-1': index !== 0
41
+ })}
42
+ >
43
+ <FieldInput
44
+ attributes={restAttributes}
45
+ contacts={contacts}
46
+ error={isError}
47
+ helperText={isError ? errors[inputName] : null}
48
+ name={inputName}
49
+ label={t(`Contacts.AddModal.ContactForm.fields.${name}`)}
50
+ labelProps={label}
51
+ />
52
+ {showRemove && (
53
+ <ListItemIcon className="u-ml-half">
54
+ <IconButton
55
+ aria-label="delete"
56
+ color="error"
57
+ size="medium"
58
+ onClick={() => removeField(fields, index)}
59
+ >
60
+ <Icon icon={CrossCircleIcon} />
61
+ </IconButton>
62
+ </ListItemIcon>
63
+ )}
64
+ </div>
65
+ )
66
+ })}
67
+ <Button
68
+ variant="text"
69
+ startIcon={<Icon icon={PlusIcon} />}
70
+ onClick={() => addField(fields)}
71
+ label={t(`Contacts.AddModal.ContactForm.addLabel.${name}`)}
72
+ />
73
+ </>
74
+ )
75
+ }}
76
+ </FieldArray>
77
+ )
78
+ }
79
+
80
+ export default FieldInputArray
@@ -0,0 +1,65 @@
1
+ import cx from 'classnames'
2
+ import PropTypes from 'prop-types'
3
+ import React from 'react'
4
+
5
+ import FieldInput from './FieldInput'
6
+ import FieldInputArray from './FieldInputArray'
7
+ import { fieldsRequired } from './helpers'
8
+ import { locales } from './locales'
9
+ import Icon from '../../../Icon'
10
+ import { useI18n, useExtendI18n } from '../../../providers/I18n'
11
+
12
+ const FieldInputLayout = ({
13
+ attributes: { isArray, icon, ...attributes }, // ⚠️ isArray and icon here are removed from attributes to avoid DOM propagration
14
+ contacts,
15
+ formProps
16
+ }) => {
17
+ useExtendI18n(locales)
18
+ const { t } = useI18n()
19
+ const { valid, submitFailed, errors } = formProps
20
+ const { name, label, ...restAttributes } = attributes
21
+
22
+ const isError = fieldsRequired.includes(name) && !valid && submitFailed
23
+
24
+ return (
25
+ <div
26
+ className={cx('u-flex u-mt-1', {
27
+ 'u-flex-items-center': !isArray,
28
+ 'u-flex-items-baseline': isArray
29
+ })}
30
+ >
31
+ <div className="u-w-2-half">
32
+ {icon && <Icon icon={icon} color="var(--iconTextColor)" />}
33
+ </div>
34
+ <div className="u-w-100">
35
+ {isArray ? (
36
+ <FieldInputArray
37
+ attributes={attributes}
38
+ contacts={contacts}
39
+ formProps={formProps}
40
+ />
41
+ ) : (
42
+ <FieldInput
43
+ attributes={restAttributes}
44
+ contacts={contacts}
45
+ error={isError}
46
+ helperText={isError ? errors[name] : null}
47
+ name={name}
48
+ label={t(`Contacts.AddModal.ContactForm.fields.${name}`)}
49
+ labelProps={label}
50
+ />
51
+ )}
52
+ </div>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ FieldInputLayout.propTypes = {
58
+ attributes: PropTypes.object,
59
+ contacts: PropTypes.shape({
60
+ data: PropTypes.arrayOf(PropTypes.object)
61
+ }),
62
+ formProps: PropTypes.object
63
+ }
64
+
65
+ export default FieldInputLayout
@@ -0,0 +1,41 @@
1
+ import React from 'react'
2
+
3
+ import TextFieldCustomLabelSelect from './TextFieldCustomLabelSelect'
4
+ import TextFieldSelect from './TextFieldSelect'
5
+ import TextField from '../../../TextField'
6
+ import { FieldInputWrapperPropTypes } from '../types'
7
+
8
+ // component used to flatten props to ensure compatibility
9
+ // between Field from react-final-form and TextField from Mui
10
+ const FieldInputWrapper = ({
11
+ input,
12
+ attributes,
13
+ variant,
14
+ fullWidth,
15
+ ...props
16
+ }) => {
17
+ const Component = attributes.customLabelOptions
18
+ ? TextFieldCustomLabelSelect
19
+ : attributes?.select
20
+ ? TextFieldSelect
21
+ : TextField
22
+
23
+ return (
24
+ <Component
25
+ {...attributes}
26
+ {...input}
27
+ {...props}
28
+ variant={variant}
29
+ fullWidth={fullWidth}
30
+ minRows="2"
31
+ />
32
+ )
33
+ }
34
+
35
+ FieldInputWrapper.propTypes = FieldInputWrapperPropTypes
36
+ FieldInputWrapper.defaultProps = {
37
+ variant: 'outlined',
38
+ fullWidth: true
39
+ }
40
+
41
+ export default FieldInputWrapper
@@ -0,0 +1,31 @@
1
+ /*
2
+ Wrapper for react-final-form field that renders it children only if the field
3
+ with the given name has a truthy value or if the other condition is fulfilled
4
+ */
5
+ import PropTypes from 'prop-types'
6
+ import React from 'react'
7
+ import { Field } from 'react-final-form'
8
+
9
+ const HasValueCondition = ({ children, otherCondition, name }) => {
10
+ return (
11
+ <Field name={name} subscription={{ value: true }}>
12
+ {({ input: { value } }) =>
13
+ (otherCondition !== undefined && otherCondition) || value
14
+ ? children
15
+ : null
16
+ }
17
+ </Field>
18
+ )
19
+ }
20
+
21
+ HasValueCondition.defaultProps = {
22
+ otherCondition: undefined
23
+ }
24
+
25
+ HasValueCondition.propTypes = {
26
+ children: PropTypes.element.isRequired,
27
+ name: PropTypes.string.isRequired,
28
+ otherCondition: PropTypes.bool
29
+ }
30
+
31
+ export default HasValueCondition