cozy-sharing 5.0.0 → 6.0.1

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 (117) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/SharedRecipients.js +1 -1
  3. package/dist/SharedStatus.js +1 -1
  4. package/dist/SharingBanner/hooks/useSharingInfos.js +1 -1
  5. package/dist/SharingDetailsModal.js +1 -1
  6. package/dist/SharingOwnerAvatar.js +1 -1
  7. package/dist/SharingProvider.js +21 -8
  8. package/dist/components/ConfirmTrustedRecipientsDialog.js +1 -1
  9. package/dist/components/ContactSuggestion.js +15 -27
  10. package/dist/components/EditLinkPermissionDialog.js +72 -0
  11. package/dist/components/EditLinkPermissionDialog.spec.js +48 -0
  12. package/dist/components/EditableSharingModal.js +15 -62
  13. package/dist/components/{AvatarPlusX.js → Recipient/AvatarPlusX.js} +1 -1
  14. package/dist/components/{Identity.js → Recipient/Identity.js} +17 -16
  15. package/dist/components/Recipient/LinkRecipient.js +72 -0
  16. package/dist/components/Recipient/LinkRecipientPermissions.js +159 -0
  17. package/dist/components/Recipient/OwnerIdentity.js +44 -0
  18. package/dist/components/Recipient/Recipient.js +72 -0
  19. package/dist/components/{Recipient.spec.js → Recipient/Recipient.spec.js} +4 -45
  20. package/dist/components/Recipient/RecipientAvatar.js +33 -0
  21. package/dist/components/Recipient/RecipientConfirm.js +63 -0
  22. package/dist/components/Recipient/RecipientPermissions.js +126 -0
  23. package/dist/components/Recipient/RecipientPlusX.js +45 -0
  24. package/dist/components/Recipient/RecipientStatus.js +64 -0
  25. package/dist/components/Recipient/RecipientWithoutStatus.js +45 -0
  26. package/dist/components/Recipient/RecipientsAvatars.js +100 -0
  27. package/dist/components/{RecipientsAvatars.spec.js → Recipient/RecipientsAvatars.spec.js} +43 -2
  28. package/dist/components/{recipient.styl → Recipient/recipient.styl} +6 -2
  29. package/dist/components/ShareAutosuggest.js +176 -252
  30. package/dist/components/ShareAutosuggest.spec.js +3 -2
  31. package/dist/components/ShareByEmail.js +142 -193
  32. package/dist/components/ShareByEmail.spec.js +42 -23
  33. package/dist/components/ShareByLink.js +251 -357
  34. package/dist/components/ShareDialogCozyToCozy.js +16 -4
  35. package/dist/components/ShareDialogCozyToCozy.spec.js +12 -1
  36. package/dist/components/ShareDialogOnlyByLink.js +23 -10
  37. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +7 -7
  38. package/dist/components/ShareModal.js +5 -1
  39. package/dist/components/ShareRecipientsInput.js +44 -128
  40. package/dist/components/WhoHasAccess.js +43 -5
  41. package/dist/components/WhoHasAccessLight.js +3 -19
  42. package/dist/components/autosuggest.styl +21 -36
  43. package/dist/helpers/documentType.js +6 -0
  44. package/dist/helpers/hooks.js +8 -1
  45. package/dist/helpers/link.js +5 -0
  46. package/dist/helpers/permissions.js +8 -0
  47. package/dist/helpers/recipients.js +24 -0
  48. package/dist/helpers/sharings.js +72 -53
  49. package/dist/helpers/synchronousJobQueue.js +67 -0
  50. package/dist/helpers/synchronousJobQueue.spec.js +97 -0
  51. package/dist/index.js +2 -1
  52. package/dist/queries/queries.js +72 -0
  53. package/dist/share.styl +0 -15
  54. package/dist/stylesheet.css +67 -92
  55. package/locales/en.json +29 -41
  56. package/locales/fr.json +26 -39
  57. package/package.json +10 -10
  58. package/src/SharedRecipients.jsx +1 -1
  59. package/src/SharedStatus.jsx +1 -1
  60. package/src/SharingBanner/hooks/useSharingInfos.jsx +1 -1
  61. package/src/SharingDetailsModal.jsx +1 -1
  62. package/src/SharingOwnerAvatar.jsx +1 -1
  63. package/src/SharingProvider.jsx +17 -4
  64. package/src/components/ConfirmTrustedRecipientsDialog.jsx +1 -1
  65. package/src/components/ContactSuggestion.jsx +25 -10
  66. package/src/components/EditLinkPermissionDialog.jsx +89 -0
  67. package/src/components/EditLinkPermissionDialog.spec.jsx +52 -0
  68. package/src/components/EditableSharingModal.jsx +58 -100
  69. package/src/components/{AvatarPlusX.jsx → Recipient/AvatarPlusX.jsx} +1 -1
  70. package/src/components/{Identity.jsx → Recipient/Identity.jsx} +0 -0
  71. package/src/components/Recipient/LinkRecipient.jsx +59 -0
  72. package/src/components/Recipient/LinkRecipientPermissions.jsx +157 -0
  73. package/src/components/Recipient/OwnerIdentity.jsx +20 -0
  74. package/src/components/Recipient/Recipient.jsx +79 -0
  75. package/src/components/{Recipient.spec.jsx → Recipient/Recipient.spec.jsx} +4 -42
  76. package/src/components/Recipient/RecipientAvatar.jsx +38 -0
  77. package/src/components/Recipient/RecipientConfirm.jsx +37 -0
  78. package/src/components/Recipient/RecipientPermissions.jsx +114 -0
  79. package/src/components/Recipient/RecipientPlusX.jsx +28 -0
  80. package/src/components/Recipient/RecipientStatus.jsx +48 -0
  81. package/src/components/Recipient/RecipientWithoutStatus.jsx +21 -0
  82. package/src/components/Recipient/RecipientsAvatars.jsx +108 -0
  83. package/src/components/{RecipientsAvatars.spec.jsx → Recipient/RecipientsAvatars.spec.jsx} +43 -2
  84. package/src/components/{recipient.styl → Recipient/recipient.styl} +6 -2
  85. package/src/components/ShareAutosuggest.jsx +136 -127
  86. package/src/components/ShareAutosuggest.spec.jsx +12 -8
  87. package/src/components/ShareByEmail.jsx +65 -90
  88. package/src/components/ShareByEmail.spec.jsx +39 -11
  89. package/src/components/ShareByLink.jsx +146 -238
  90. package/src/components/ShareDialogCozyToCozy.jsx +14 -2
  91. package/src/components/ShareDialogCozyToCozy.spec.jsx +8 -1
  92. package/src/components/ShareDialogOnlyByLink.jsx +30 -13
  93. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +5 -5
  94. package/src/components/ShareModal.jsx +5 -1
  95. package/src/components/ShareRecipientsInput.jsx +30 -44
  96. package/src/components/WhoHasAccess.jsx +85 -27
  97. package/src/components/WhoHasAccessLight.jsx +2 -1
  98. package/src/components/__snapshots__/ContactSuggestion.spec.jsx.snap +915 -132
  99. package/src/components/__snapshots__/ShareRecipientsInput.spec.jsx.snap +86 -102
  100. package/src/components/autosuggest.styl +21 -36
  101. package/src/helpers/documentType.js +6 -0
  102. package/src/helpers/hooks.js +11 -1
  103. package/src/helpers/link.js +4 -0
  104. package/src/helpers/permissions.js +15 -0
  105. package/src/helpers/recipients.js +21 -0
  106. package/src/helpers/sharings.js +15 -12
  107. package/src/helpers/synchronousJobQueue.js +22 -0
  108. package/src/helpers/synchronousJobQueue.spec.js +75 -0
  109. package/src/index.jsx +1 -0
  110. package/src/queries/queries.js +75 -0
  111. package/src/share.styl +0 -15
  112. package/dist/SharingBanner/helpers/queries.js +0 -12
  113. package/dist/components/ContactsAndGroupsDataLoader.js +0 -50
  114. package/dist/components/Recipient.js +0 -450
  115. package/src/SharingBanner/helpers/queries.js +0 -14
  116. package/src/components/ContactsAndGroupsDataLoader.jsx +0 -30
  117. package/src/components/Recipient.jsx +0 -415
@@ -0,0 +1,108 @@
1
+ import React from 'react'
2
+ import cx from 'classnames'
3
+ import { useClient } from 'cozy-client'
4
+
5
+ import Avatar from 'cozy-ui/transpiled/react/Avatar'
6
+ import LinkIcon from 'cozy-ui/transpiled/react/Icons/Link'
7
+
8
+ import RecipientAvatar from './RecipientAvatar'
9
+ import AvatarPlusX from './AvatarPlusX'
10
+ import styles from './recipient.styl'
11
+ import { getDisplayName } from '../../models'
12
+
13
+ export const MAX_DISPLAYED_RECIPIENTS = 3
14
+
15
+ /**
16
+ * Exclude me from the list of recipients.
17
+ * @typedef {object} Recipient
18
+ * @param {array<Recipient>} recipients - List of recipients
19
+ * @param {boolean} isOwner - Indicates if I'm the owner or not
20
+ * @param {object} client - CozyClient instance
21
+ * @returns {array<Recipient>} List of recipients without me if I'm the owner
22
+ */
23
+ export const excludeMeAsOwnerFromRecipients = ({
24
+ recipients,
25
+ isOwner,
26
+ client
27
+ }) => {
28
+ return recipients.filter(recipient => {
29
+ if (isOwner) {
30
+ return recipient.status !== 'owner'
31
+ }
32
+ return recipient.instance !== client.options.uri
33
+ })
34
+ }
35
+
36
+ const RecipientsAvatars = ({
37
+ recipients,
38
+ link,
39
+ size,
40
+ className,
41
+ onClick,
42
+ isOwner,
43
+ showMeAsOwner
44
+ }) => {
45
+ const client = useClient()
46
+ const filteredRecipients = showMeAsOwner
47
+ ? recipients.slice().reverse() // we slice first to clone the original array because reverser() mutates it
48
+ : excludeMeAsOwnerFromRecipients({
49
+ recipients,
50
+ isOwner,
51
+ client
52
+ }).reverse()
53
+ // we reverse the recipients array because we use `flex-direction: row-reverse` to display them correctly
54
+
55
+ const isAvatarPlusX = filteredRecipients.length > MAX_DISPLAYED_RECIPIENTS
56
+ const extraRecipients = filteredRecipients
57
+ .slice(MAX_DISPLAYED_RECIPIENTS)
58
+ .map(recipient => getDisplayName(recipient))
59
+ const shownRecipients = filteredRecipients.slice(0, MAX_DISPLAYED_RECIPIENTS)
60
+
61
+ return (
62
+ <div
63
+ className={cx(
64
+ styles['recipients-avatars'],
65
+ {
66
+ [styles['--interactive']]: onClick
67
+ },
68
+ className
69
+ )}
70
+ onClick={onClick}
71
+ >
72
+ {link && (
73
+ <span data-testid="recipientsAvatars-link">
74
+ <Avatar
75
+ className={styles['recipients-avatars--link']}
76
+ icon={LinkIcon}
77
+ size={size}
78
+ />
79
+ </span>
80
+ )}
81
+ {isAvatarPlusX && (
82
+ <span data-testid="recipientsAvatars-plusX">
83
+ <AvatarPlusX
84
+ className={styles['recipients-avatars--plusX']}
85
+ extraRecipients={extraRecipients}
86
+ size={size}
87
+ />
88
+ </span>
89
+ )}
90
+ {shownRecipients.map((recipient, idx) => (
91
+ <span
92
+ data-testid={`recipientsAvatars-avatar${
93
+ recipient.status === 'owner' ? '-owner' : ''
94
+ }`}
95
+ key={idx}
96
+ >
97
+ <RecipientAvatar
98
+ recipient={recipient}
99
+ size={size}
100
+ className={cx(styles['recipients-avatars--avatar'])}
101
+ />
102
+ </span>
103
+ ))}
104
+ </div>
105
+ )
106
+ }
107
+
108
+ export default RecipientsAvatars
@@ -2,8 +2,11 @@ import React from 'react'
2
2
  import { createMockClient } from 'cozy-client'
3
3
  import { render } from '@testing-library/react'
4
4
 
5
- import { RecipientsAvatars, MAX_DISPLAYED_RECIPIENTS } from './Recipient'
6
- import AppLike from '../../test/AppLike'
5
+ import RecipientsAvatars, {
6
+ MAX_DISPLAYED_RECIPIENTS,
7
+ excludeMeAsOwnerFromRecipients
8
+ } from './RecipientsAvatars'
9
+ import AppLike from '../../../test/AppLike'
7
10
 
8
11
  const mockRecipients = new Array(MAX_DISPLAYED_RECIPIENTS - 1)
9
12
  .fill(
@@ -113,3 +116,41 @@ describe('RecipientsAvatars', () => {
113
116
  expect(getByTestId('recipientsAvatars-link')).toBeTruthy()
114
117
  })
115
118
  })
119
+
120
+ describe('excludeMeAsOwnerFromRecipients', () => {
121
+ test('excludeMeAsOwnerFromRecipients behavior', () => {
122
+ const recipients = [
123
+ {
124
+ status: 'owner',
125
+ instance: 'http://foo1.cozy.bar'
126
+ },
127
+ { status: 'pending', instance: 'http://foo2.cozy.bar' },
128
+ {
129
+ status: 'pending',
130
+ instance: 'http://foo3.cozy.bar'
131
+ }
132
+ ]
133
+ const client = { options: { uri: 'http://foo2.cozy.bar' } }
134
+ expect(
135
+ excludeMeAsOwnerFromRecipients({ recipients, isOwner: true, client })
136
+ ).toEqual([
137
+ { status: 'pending', instance: 'http://foo2.cozy.bar' },
138
+ {
139
+ status: 'pending',
140
+ instance: 'http://foo3.cozy.bar'
141
+ }
142
+ ])
143
+ expect(
144
+ excludeMeAsOwnerFromRecipients({ recipients, isOwner: false, client })
145
+ ).toEqual([
146
+ {
147
+ status: 'owner',
148
+ instance: 'http://foo1.cozy.bar'
149
+ },
150
+ {
151
+ status: 'pending',
152
+ instance: 'http://foo3.cozy.bar'
153
+ }
154
+ ])
155
+ })
156
+ })
@@ -52,6 +52,11 @@
52
52
  .recipient-user
53
53
  font-size 1.125rem
54
54
 
55
+ .link-recipient-icon-circle
56
+ border 1px solid var(--borderMainColor)
57
+ background-color unset
58
+ color unset
59
+
55
60
  .recipients-avatars
56
61
  display inline-flex
57
62
  flex-direction row-reverse
@@ -74,5 +79,4 @@
74
79
 
75
80
  &--plusX,
76
81
  &--avatar
77
- margin-right -0.6rem
78
-
82
+ margin-right -0.6rem
@@ -1,14 +1,15 @@
1
- import React, { Component } from 'react'
1
+ import React, { useState, useRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import Autosuggest from 'react-autosuggest'
4
4
 
5
5
  import { Spinner } from 'cozy-ui/transpiled/react/Spinner'
6
6
  import palette from 'cozy-ui/transpiled/react/palette'
7
+ import Chip from 'cozy-ui/transpiled/react/Chips'
8
+ import Avatar from 'cozy-ui/transpiled/react/Avatar'
7
9
 
8
10
  import styles from './autosuggest.styl'
9
- import BoldCross from '../../assets/icons/icon-cross-bold.svg'
10
11
 
11
- import { getDisplayName, Contact } from '../models'
12
+ import { getDisplayName, getInitials, Contact } from '../models'
12
13
  import {
13
14
  cozyUrlMatch,
14
15
  emailMatch,
@@ -18,24 +19,22 @@ import {
18
19
  import ContactSuggestion from './ContactSuggestion'
19
20
  import { extractEmails, validateEmail } from '../helpers/email'
20
21
 
21
- export default class ShareAutocomplete extends Component {
22
- state = {
23
- inputValue: '',
24
- suggestions: []
25
- }
26
-
27
- componentDidUpdate(prevProps) {
28
- if (
29
- this.props.contactsAndGroups.length !== prevProps.contactsAndGroups.length
30
- ) {
31
- this.onSuggestionsFetchRequested({
32
- value: this.state.inputValue
33
- })
34
- }
35
- }
36
-
37
- computeSuggestions(value) {
38
- const { contactsAndGroups } = this.props
22
+ const ShareAutocomplete = ({
23
+ loading,
24
+ contactsAndGroups,
25
+ recipients,
26
+ onFocus,
27
+ onPick,
28
+ onRemove,
29
+ placeholder
30
+ }) => {
31
+ const [inputValue, setInputValue] = useState('')
32
+ const [suggestions, setSuggestions] = useState([])
33
+ const [isPasted, setIsPasted] = useState(false)
34
+
35
+ const autosuggestRef = useRef(null)
36
+
37
+ const computeSuggestions = value => {
39
38
  const inputValue = value.trim().toLowerCase()
40
39
 
41
40
  return inputValue.length === 0
@@ -49,154 +48,164 @@ export default class ShareAutocomplete extends Component {
49
48
  )
50
49
  }
51
50
 
52
- onSuggestionsFetchRequested = ({ value }) => {
53
- this.setState(state => ({
54
- ...state,
55
- suggestions: this.computeSuggestions(value)
56
- }))
51
+ const onSuggestionsFetchRequested = ({ value }) => {
52
+ const computedSuggestions = computeSuggestions(value)
53
+
54
+ const newSuggestions =
55
+ computedSuggestions.length === 0 && validateEmail(value)
56
+ ? [
57
+ {
58
+ email: value,
59
+ _type: Contact.doctype
60
+ }
61
+ ]
62
+ : computedSuggestions
63
+
64
+ setSuggestions(newSuggestions)
57
65
  }
58
66
 
59
- onSuggestionsClearRequested = () => {
60
- this.setState(state => ({ ...state, suggestions: [] }))
67
+ const onSuggestionsClearRequested = () => {
68
+ setSuggestions([])
61
69
  }
62
- onPaste = () => {
63
- this.isPasted = true
70
+
71
+ const onAutosuggestPaste = () => {
72
+ setIsPasted(true)
64
73
  }
65
74
 
66
- onChange = (event, { newValue, method }) => {
67
- if (this.isPasted) {
75
+ const onAutosuggestChange = (event, { newValue, method }) => {
76
+ if (isPasted) {
68
77
  const emails = extractEmails(newValue)
69
78
  if (emails) {
70
79
  emails.map(email => {
71
- this.onPick({ email })
80
+ onAutosuggestPick({ email })
72
81
  })
73
82
  }
74
- this.isPasted = false
83
+ setIsPasted(false)
75
84
  } else {
76
85
  if (typeof newValue !== 'object') {
77
- this.setState(state => ({ ...state, inputValue: newValue }))
86
+ setInputValue(newValue)
78
87
  } else if (method === 'click' || method === 'enter') {
79
88
  // A suggestion has been picked
80
- this.onPick(newValue)
89
+ onAutosuggestPick(newValue)
81
90
  }
82
91
  }
83
92
  }
84
93
 
85
- onKeyPress = event => {
94
+ const onKeyPress = event => {
86
95
  // The user wants to add an unknown email
87
96
  if (
88
97
  ((event.key === 'Enter' || event.keyCode === 13) &&
89
- validateEmail(this.state.inputValue)) ||
98
+ validateEmail(inputValue)) ||
90
99
  ((event.key === 'Space' || event.keyCode === 32) &&
91
- validateEmail(this.state.inputValue))
100
+ validateEmail(inputValue))
92
101
  ) {
93
- this.onPick({ email: this.state.inputValue })
102
+ onAutosuggestPick({ email: inputValue })
94
103
  }
95
104
  }
96
105
 
97
- onFocus = () => {
98
- this.props.onFocus()
99
- }
100
-
101
- onBlur = (event, { highlightedSuggestion }) => {
102
- if (highlightedSuggestion) {
103
- this.props.onPick(highlightedSuggestion)
104
- this.setState(state => ({ ...state, inputValue: '' }))
105
- } else if (
106
- this.state.inputValue !== '' &&
107
- this.state.inputValue.match(/\S+@\S+/)
106
+ const onKeyUp = ({ key, keyCode }) => {
107
+ if (
108
+ recipients.length > 0 &&
109
+ inputValue === '' &&
110
+ (key === 'Backspace' ||
111
+ keyCode === 8 ||
112
+ key === 'Delete' ||
113
+ keyCode === 46)
108
114
  ) {
109
- this.props.onPick({ email: this.state.inputValue })
110
- this.setState(state => ({ ...state, inputValue: '' }))
115
+ document.getElementById(`recipient_${recipients.length - 1}`).focus()
111
116
  }
112
117
  }
113
118
 
114
- onPick = value => {
115
- this.props.onPick(value)
116
- this.setState(state => ({ ...state, inputValue: '' }))
117
- requestAnimationFrame(() => this.input && this.input.focus(), 1) // don't ask...
119
+ const onAutosuggestFocus = () => {
120
+ onFocus()
118
121
  }
119
122
 
120
- onRemove = value => {
121
- this.props.onRemove(value)
122
- requestAnimationFrame(() => this.input && this.input.focus(), 1)
123
+ const onAutosuggestBlur = () => {
124
+ if (inputValue !== '' && inputValue.match(/\S+@\S+/)) {
125
+ onAutosuggestPick({ email: inputValue })
126
+ }
123
127
  }
124
128
 
125
- renderInput(inputProps) {
126
- const { loading, recipients } = this.props
127
- return (
128
- <div className={styles['recipientsContainer']}>
129
- {recipients.map((recipient, idx) => {
130
- const value = getDisplayName(recipient)
131
- return (
132
- <div
133
- className={styles['recipientChip']}
134
- key={`key_recipient_${idx}`}
135
- >
136
- <span>{value}</span>
137
- <button
138
- className={styles['removeRecipient']}
139
- onClick={() => this.onRemove(recipient)}
140
- >
141
- <BoldCross />
142
- </button>
143
- </div>
144
- )
145
- })}
146
- <input {...inputProps} onKeyPress={this.onKeyPress} />
147
- {loading && (
148
- <Spinner
149
- color={palette.dodgerBlue}
150
- className="u-flex u-flex-items-center"
151
- />
152
- )}
153
- </div>
154
- )
129
+ const onAutosuggestPick = value => {
130
+ onPick(value)
131
+ setInputValue('')
132
+ autosuggestRef?.current?.input?.focus()
155
133
  }
156
134
 
157
- render() {
158
- const { inputValue, suggestions } = this.state
159
- const { contactsAndGroups, placeholder } = this.props
160
- return (
161
- <Autosuggest
162
- ref={self => {
163
- this.input = self ? self.input : null
164
- }}
165
- theme={styles}
166
- suggestions={suggestions.slice(0, 20)}
167
- getSuggestionValue={contact => contact}
168
- onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
169
- onSuggestionsClearRequested={this.onSuggestionsClearRequested}
170
- renderSuggestion={contactOrGroup => (
171
- <ContactSuggestion
172
- contacts={contactsAndGroups.filter(
173
- item => item._type === Contact.doctype
174
- )}
175
- contactOrGroup={contactOrGroup}
176
- />
177
- )}
178
- renderInputComponent={props => this.renderInput(props)}
179
- highlightFirstSuggestion
180
- inputProps={{
181
- onFocus: this.onFocus,
182
- onChange: this.onChange,
183
- onPaste: this.onPaste,
184
- onBlur: this.onBlur,
185
- value: inputValue,
186
- type: 'email',
187
- placeholder
188
- }}
189
- />
190
- )
135
+ const onAutosuggestRemove = value => {
136
+ onRemove(value)
137
+ autosuggestRef?.current?.input?.focus()
191
138
  }
139
+
140
+ const renderInput = inputProps => (
141
+ <div className={styles['recipientsContainer']}>
142
+ {recipients.map((recipient, idx) => {
143
+ const avatarText = getInitials(recipient)
144
+ const name = getDisplayName(recipient)
145
+ return (
146
+ <Chip
147
+ id={`recipient_${idx}`}
148
+ key={`key_recipient_${idx}`}
149
+ avatar={<Avatar text={avatarText} size="xsmall" />}
150
+ label={name}
151
+ onDelete={() => {
152
+ onAutosuggestRemove(recipient)
153
+ }}
154
+ className={styles['recipientChip']}
155
+ />
156
+ )
157
+ })}
158
+ <input {...inputProps} onKeyPress={onKeyPress} onKeyUp={onKeyUp} />
159
+ {loading && (
160
+ <Spinner
161
+ color={palette.dodgerBlue}
162
+ className="u-flex u-flex-items-center"
163
+ />
164
+ )}
165
+ </div>
166
+ )
167
+
168
+ const renderSuggestion = contactOrGroup => (
169
+ <ContactSuggestion
170
+ contacts={contactsAndGroups.filter(
171
+ item => item._type === Contact.doctype
172
+ )}
173
+ contactOrGroup={contactOrGroup}
174
+ />
175
+ )
176
+
177
+ return (
178
+ <Autosuggest
179
+ ref={autosuggestRef}
180
+ theme={styles}
181
+ suggestions={suggestions.slice(0, 20)}
182
+ getSuggestionValue={contact => contact}
183
+ onSuggestionsFetchRequested={onSuggestionsFetchRequested}
184
+ onSuggestionsClearRequested={onSuggestionsClearRequested}
185
+ renderSuggestion={renderSuggestion}
186
+ renderInputComponent={renderInput}
187
+ highlightFirstSuggestion
188
+ inputProps={{
189
+ onFocus: onAutosuggestFocus,
190
+ onChange: onAutosuggestChange,
191
+ onPaste: onAutosuggestPaste,
192
+ onBlur: onAutosuggestBlur,
193
+ value: inputValue,
194
+ type: 'email',
195
+ placeholder
196
+ }}
197
+ />
198
+ )
192
199
  }
193
200
 
194
201
  ShareAutocomplete.propTypes = {
202
+ loading: PropTypes.bool,
195
203
  contactsAndGroups: PropTypes.array,
196
- placeholder: PropTypes.string,
204
+ recipients: PropTypes.array.isRequired,
197
205
  onFocus: PropTypes.func.isRequired,
198
206
  onPick: PropTypes.func.isRequired,
199
207
  onRemove: PropTypes.func.isRequired,
200
- loading: PropTypes.bool,
201
- recipients: PropTypes.array.isRequired
208
+ placeholder: PropTypes.string
202
209
  }
210
+
211
+ export default ShareAutocomplete
@@ -2,20 +2,24 @@ import React from 'react'
2
2
  import { render, fireEvent } from '@testing-library/react'
3
3
 
4
4
  import ShareAutosuggest from './ShareAutosuggest'
5
+ import AppLike from '../../test/AppLike'
6
+
5
7
  describe('ShareAutosuggest', () => {
6
8
  it('tests if ShareAutosuggest calls onFocus and onPick', () => {
7
9
  const onPick = jest.fn()
8
10
  const onFocus = jest.fn()
9
11
  const onRemove = jest.fn()
10
12
  const { getByPlaceholderText } = render(
11
- <ShareAutosuggest
12
- contactsAndGroups={[]}
13
- placeholder="myPlaceHolder"
14
- onPick={onPick}
15
- onFocus={onFocus}
16
- recipients={[]}
17
- onRemove={onRemove}
18
- />
13
+ <AppLike>
14
+ <ShareAutosuggest
15
+ contactsAndGroups={[]}
16
+ placeholder="myPlaceHolder"
17
+ onPick={onPick}
18
+ onFocus={onFocus}
19
+ recipients={[]}
20
+ onRemove={onRemove}
21
+ />
22
+ </AppLike>
19
23
  )
20
24
  const inputNode = getByPlaceholderText('myPlaceHolder')
21
25
  inputNode.focus()