cozy-sharing 31.1.0 → 31.2.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 (57) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +15 -16
  3. package/dist/components/FederatedFolder/FederatedFolderModal.js +78 -103
  4. package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +102 -292
  5. package/dist/components/Recipient/RecipientConfirm.js +0 -5
  6. package/dist/components/ShareAutosuggest.js +9 -4
  7. package/dist/components/ShareByEmail.js +29 -274
  8. package/dist/components/ShareByEmail.spec.js +52 -230
  9. package/dist/components/ShareByLink.js +1 -3
  10. package/dist/components/ShareDialogCozyToCozy.js +169 -35
  11. package/dist/components/ShareDialogCozyToCozy.spec.js +122 -35
  12. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +13 -3
  13. package/dist/components/ShareModal/SharingDetailsModal.js +0 -5
  14. package/dist/components/ShareRecipientsInput.js +6 -3
  15. package/dist/components/SharedDrive/SharedDriveForm.js +11 -23
  16. package/dist/components/SharedDrive/SharedDriveModal.js +1 -1
  17. package/dist/components/SharedDrive/useSharedDrive.js +33 -57
  18. package/dist/components/SharedDrive/useSharedDrive.spec.js +29 -93
  19. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +15 -26
  20. package/dist/components/Sharetypeselect.js +85 -90
  21. package/dist/hooks/usePendingRecipients.js +20 -0
  22. package/dist/hooks/usePendingRecipients.spec.js +30 -0
  23. package/dist/styles/autosuggest.styl +8 -2
  24. package/dist/styles/share.styl +0 -39
  25. package/dist/stylesheet.css +8 -36
  26. package/locales/en.json +5 -9
  27. package/locales/fr.json +5 -9
  28. package/locales/ru.json +5 -9
  29. package/locales/vi.json +5 -9
  30. package/package.json +2 -2
  31. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +14 -15
  32. package/src/components/FederatedFolder/FederatedFolderModal.jsx +50 -67
  33. package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +63 -186
  34. package/src/components/ShareAutosuggest.jsx +7 -2
  35. package/src/components/ShareByEmail.jsx +35 -186
  36. package/src/components/ShareByEmail.spec.jsx +22 -173
  37. package/src/components/ShareByLink.jsx +1 -7
  38. package/src/components/ShareDialogCozyToCozy.jsx +171 -38
  39. package/src/components/ShareDialogCozyToCozy.spec.jsx +66 -0
  40. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +11 -1
  41. package/src/components/ShareRecipientsInput.jsx +5 -2
  42. package/src/components/SharedDrive/SharedDriveForm.jsx +10 -23
  43. package/src/components/SharedDrive/SharedDriveModal.jsx +1 -1
  44. package/src/components/SharedDrive/useSharedDrive.js +24 -50
  45. package/src/components/SharedDrive/useSharedDrive.spec.js +19 -67
  46. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +15 -29
  47. package/src/components/Sharetypeselect.jsx +75 -58
  48. package/src/hooks/usePendingRecipients.js +13 -0
  49. package/src/hooks/usePendingRecipients.spec.js +21 -0
  50. package/src/styles/autosuggest.styl +8 -2
  51. package/src/styles/share.styl +0 -39
  52. package/dist/components/SharedDrive/helpers.js +0 -102
  53. package/dist/components/SharedDrive/helpers.spec.js +0 -227
  54. package/dist/components/Sharesubmit.js +0 -28
  55. package/src/components/SharedDrive/helpers.js +0 -108
  56. package/src/components/SharedDrive/helpers.spec.js +0 -177
  57. package/src/components/Sharesubmit.jsx +0 -31
@@ -8,20 +8,18 @@ import { useI18n } from 'twake-i18n'
8
8
 
9
9
  import withLocales from '../../hoc/withLocales'
10
10
  import { default as DumbShareByEmail } from '../ShareByEmail'
11
- import WhoHasAccess from '../WhoHasAccess'
12
11
  import { useSharedDrive } from './useSharedDrive'
13
12
 
14
13
  export const SharedDriveForm = withLocales(({ onSuccess, onCancel }) => {
15
14
  const { t } = useI18n()
16
15
  const {
17
16
  sharedDriveName,
18
- recipients,
19
17
  handleSharedDriveNameChange,
20
- onShare,
21
- onCreate,
22
- onSetType,
23
- onRevoke,
24
- createContact
18
+ pendingRecipients,
19
+ setPendingRecipients,
20
+ selectedOption,
21
+ setSelectedOption,
22
+ onCreate
25
23
  } = useSharedDrive({ onSuccess })
26
24
 
27
25
  return (
@@ -40,25 +38,14 @@ export const SharedDriveForm = withLocales(({ onSuccess, onCancel }) => {
40
38
  {t('SharedDrive.sharedDriveModal.addPeople')}
41
39
  </Typography>
42
40
  <DumbShareByEmail
43
- createContact={createContact}
44
- currentRecipients={recipients}
41
+ currentRecipients={[]}
45
42
  documentType="Files"
46
- sharedDrive
47
- onShare={onShare}
48
- submitLabel={t('SharedDrive.sharedDriveModal.add')}
49
- showNotifications={false}
50
- sharingDesc=""
43
+ pendingRecipients={pendingRecipients}
44
+ onPendingRecipientsChange={setPendingRecipients}
45
+ selectedOption={selectedOption}
46
+ onSelectedOptionChange={setSelectedOption}
51
47
  />
52
48
  </div>
53
- <WhoHasAccess
54
- isOwner
55
- isSharedDrive
56
- recipients={recipients}
57
- documentType="Files"
58
- className="u-w-100"
59
- onRevoke={onRevoke}
60
- onSetType={onSetType}
61
- />
62
49
  <div className="u-flex u-ph-2 u-pv-1">
63
50
  <Button
64
51
  variant="text"
@@ -4,8 +4,8 @@ import React from 'react'
4
4
  import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
5
5
  import { useI18n } from 'twake-i18n'
6
6
 
7
- import withLocales from '../../hoc/withLocales'
8
7
  import { SharedDriveForm } from './SharedDriveForm'
8
+ import withLocales from '../../hoc/withLocales'
9
9
 
10
10
  export const SharedDriveModal = withLocales(({ onClose }) => {
11
11
  const { t } = useI18n()
@@ -4,13 +4,8 @@ import { useClient } from 'cozy-client'
4
4
  import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
5
5
  import { useI18n } from 'twake-i18n'
6
6
 
7
- import {
8
- RECIPIENT_INDEX_PREFIX,
9
- formatRecipients,
10
- mergeAndDeduplicateRecipients,
11
- moveRecipientToReadOnly,
12
- moveRecipientToReadWrite
13
- } from './helpers'
7
+ import { getOrCreateFromArray } from '../../helpers/contacts'
8
+ import { usePendingRecipients } from '../../hooks/usePendingRecipients'
14
9
  import { Contact } from '../../models'
15
10
 
16
11
  export const useSharedDrive = ({ onSuccess }) => {
@@ -18,36 +13,35 @@ export const useSharedDrive = ({ onSuccess }) => {
18
13
  const { t } = useI18n()
19
14
  const { showAlert } = useAlert()
20
15
 
21
- const [sharedDriveRecipients, setSharedDriveRecipients] = useState({
22
- recipients: [],
23
- readOnlyRecipients: []
24
- })
25
16
  const [sharedDriveName, setSharedDriveName] = useState('')
17
+ const {
18
+ pendingRecipients,
19
+ setPendingRecipients,
20
+ selectedOption,
21
+ setSelectedOption
22
+ } = usePendingRecipients()
26
23
 
27
24
  const handleSharedDriveNameChange = event => {
28
25
  setSharedDriveName(event.target.value)
29
26
  }
30
27
 
31
- const onShare = params => {
32
- setSharedDriveRecipients(prev => ({
33
- recipients: mergeAndDeduplicateRecipients([
34
- prev.recipients,
35
- params.recipients
36
- ]),
37
- readOnlyRecipients: mergeAndDeduplicateRecipients([
38
- prev.readOnlyRecipients,
39
- params.readOnlyRecipients
40
- ])
41
- }))
42
- }
28
+ const createContact = contact => client.create(Contact.doctype, contact)
43
29
 
44
30
  const onCreate = async () => {
45
31
  try {
32
+ const contacts = await getOrCreateFromArray(
33
+ client,
34
+ pendingRecipients,
35
+ createContact
36
+ )
37
+ const readWriteRecipients = selectedOption === 'readOnly' ? [] : contacts
38
+ const readOnlyRecipients = selectedOption === 'readOnly' ? contacts : []
39
+
46
40
  await client.collection('io.cozy.sharings').createSharedDrive({
47
41
  name: sharedDriveName,
48
42
  description: sharedDriveName,
49
- recipients: sharedDriveRecipients.recipients,
50
- readOnlyRecipients: sharedDriveRecipients.readOnlyRecipients
43
+ recipients: readWriteRecipients,
44
+ readOnlyRecipients
51
45
  })
52
46
 
53
47
  showAlert({
@@ -66,33 +60,13 @@ export const useSharedDrive = ({ onSuccess }) => {
66
60
  }
67
61
  }
68
62
 
69
- const onSetType = (index, newType) => {
70
- const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
71
- setSharedDriveRecipients(prev =>
72
- newType === 'two-way'
73
- ? moveRecipientToReadWrite(prev, _id)
74
- : moveRecipientToReadOnly(prev, _id)
75
- )
76
- }
77
-
78
- const onRevoke = index => {
79
- const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
80
- setSharedDriveRecipients(prev => ({
81
- recipients: prev.recipients.filter(r => r._id !== _id),
82
- readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
83
- }))
84
- }
85
-
86
- const createContact = contact => client.create(Contact.doctype, contact)
87
-
88
63
  return {
89
64
  sharedDriveName,
90
- recipients: formatRecipients(sharedDriveRecipients),
91
65
  handleSharedDriveNameChange,
92
- onShare,
93
- onCreate,
94
- onSetType,
95
- onRevoke,
96
- createContact
66
+ pendingRecipients,
67
+ setPendingRecipients,
68
+ selectedOption,
69
+ setSelectedOption,
70
+ onCreate
97
71
  }
98
72
  }
@@ -11,16 +11,9 @@ const mockT = jest.fn(key => key)
11
11
 
12
12
  jest.mock('cozy-client', () => ({
13
13
  useClient: jest.fn(() => ({
14
- collection: mockCollection
15
- })),
16
- models: {
17
- contact: {
18
- getPrimaryEmail: jest.fn(r =>
19
- Array.isArray(r.email) && r.email[0] ? r.email[0].address : ''
20
- ),
21
- getDisplayName: jest.fn(r => r.displayName || r.name || '')
22
- }
23
- }
14
+ collection: mockCollection,
15
+ create: jest.fn()
16
+ }))
24
17
  }))
25
18
 
26
19
  jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
@@ -35,6 +28,12 @@ jest.mock('../../models', () => ({
35
28
  Contact: { doctype: 'io.cozy.contacts' }
36
29
  }))
37
30
 
31
+ jest.mock('../../helpers/contacts', () => ({
32
+ getOrCreateFromArray: jest.fn((client, recipients) =>
33
+ Promise.resolve(recipients)
34
+ )
35
+ }))
36
+
38
37
  describe('useSharedDrive', () => {
39
38
  beforeEach(() => {
40
39
  jest.clearAllMocks()
@@ -45,7 +44,8 @@ describe('useSharedDrive', () => {
45
44
  useSharedDrive({ onSuccess: jest.fn() })
46
45
  )
47
46
  expect(result.current.sharedDriveName).toBe('')
48
- expect(result.current.recipients).toEqual([])
47
+ expect(result.current.pendingRecipients).toEqual([])
48
+ expect(result.current.selectedOption).toBe('readWrite')
49
49
  })
50
50
 
51
51
  it('handleSharedDriveNameChange updates the name', () => {
@@ -60,56 +60,7 @@ describe('useSharedDrive', () => {
60
60
  expect(result.current.sharedDriveName).toBe('My Drive')
61
61
  })
62
62
 
63
- it('onRevoke removes a recipient from both lists', () => {
64
- const { result } = renderHook(() =>
65
- useSharedDrive({ onSuccess: jest.fn() })
66
- )
67
- act(() => {
68
- result.current.onShare({
69
- recipients: [{ _id: 'abc', displayName: 'Alice', email: [] }],
70
- readOnlyRecipients: []
71
- })
72
- })
73
- act(() => {
74
- result.current.onRevoke(`virtual-shared-drive-sharing-abc`)
75
- })
76
- expect(result.current.recipients).toHaveLength(0)
77
- })
78
-
79
- it('onSetType moves a recipient to read-only', () => {
80
- const { result } = renderHook(() =>
81
- useSharedDrive({ onSuccess: jest.fn() })
82
- )
83
- act(() => {
84
- result.current.onShare({
85
- recipients: [{ _id: 'abc', displayName: 'Alice', email: [] }],
86
- readOnlyRecipients: []
87
- })
88
- })
89
- act(() => {
90
- result.current.onSetType(`virtual-shared-drive-sharing-abc`, 'one-way')
91
- })
92
- expect(result.current.recipients[0].type).toBe('one-way')
93
- })
94
-
95
- it('onSetType moves a recipient to read-write', () => {
96
- const { result } = renderHook(() =>
97
- useSharedDrive({ onSuccess: jest.fn() })
98
- )
99
- act(() => {
100
- result.current.onShare({
101
- recipients: [],
102
- readOnlyRecipients: [{ _id: 'xyz', displayName: 'Bob', email: [] }]
103
- })
104
- })
105
- act(() => {
106
- result.current.onSetType(`virtual-shared-drive-sharing-xyz`, 'two-way')
107
- })
108
- expect(result.current.recipients).toHaveLength(1)
109
- expect(result.current.recipients[0].type).toBe('two-way')
110
- })
111
-
112
- it('onCreate calls createSharedDrive, showAlert with success, and onSuccess', async () => {
63
+ it('onCreate calls createSharedDrive and onSuccess', async () => {
113
64
  const onSuccess = jest.fn()
114
65
  mockCreateSharedDrive.mockResolvedValue({})
115
66
 
@@ -119,18 +70,19 @@ describe('useSharedDrive', () => {
119
70
  result.current.handleSharedDriveNameChange({
120
71
  target: { value: 'My Drive' }
121
72
  })
73
+ result.current.setPendingRecipients([{ email: 'a@b.c' }])
122
74
  })
123
75
 
124
76
  await act(async () => {
125
77
  await result.current.onCreate()
126
78
  })
127
79
 
128
- expect(mockCreateSharedDrive).toHaveBeenCalledWith(
129
- expect.objectContaining({
130
- name: 'My Drive',
131
- description: 'My Drive'
132
- })
133
- )
80
+ expect(mockCreateSharedDrive).toHaveBeenCalledWith({
81
+ name: 'My Drive',
82
+ description: 'My Drive',
83
+ recipients: [{ email: 'a@b.c' }],
84
+ readOnlyRecipients: []
85
+ })
134
86
  expect(mockShowAlert).toHaveBeenCalledWith(
135
87
  expect.objectContaining({ severity: 'success' })
136
88
  )
@@ -18,33 +18,29 @@ export const DumbBatchSharedFolderModal = withLocales(
18
18
  document,
19
19
  folderName,
20
20
  handleFolderNameChange,
21
- createContact,
22
21
  recipients,
23
22
  currentRecipients,
24
23
  onRevoke,
25
- onSetType,
26
24
  onCreate,
27
25
  onSend,
28
26
  onClose,
29
- onShare,
30
27
  onRename,
31
28
  showNameField = false,
32
29
  sharingLink,
33
30
  nameLabel,
34
31
  addPeopleLabel,
35
- addButtonLabel,
36
32
  cancelLabel,
37
33
  createLabel,
38
34
  shareLabel,
39
35
  saveLabel,
40
- sharingDesc,
41
36
  showShareByEmail = true,
42
37
  autoOpenShareRestriction,
43
- showGenerateLinkButton
38
+ showGenerateLinkButton,
39
+ pendingRecipients,
40
+ onPendingRecipientsChange,
41
+ selectedOption,
42
+ onSelectedOptionChange
44
43
  }) => {
45
- const hasRecipients = Boolean(recipients?.length)
46
-
47
- // Determine action buttons based on modal mode
48
44
  const actionButtons = (() => {
49
45
  if (showNameField && onCreate) {
50
46
  return (
@@ -80,7 +76,7 @@ export const DumbBatchSharedFolderModal = withLocales(
80
76
  <Button
81
77
  variant="primary"
82
78
  label={shareLabel}
83
- disabled={!hasRecipients || !onSend}
79
+ disabled={!onSend}
84
80
  onClick={onSend}
85
81
  />
86
82
  </>
@@ -130,17 +126,13 @@ export const DumbBatchSharedFolderModal = withLocales(
130
126
  </Typography>
131
127
  {showShareByEmail && (
132
128
  <DumbShareByEmail
133
- createContact={createContact}
134
129
  currentRecipients={currentRecipients}
135
130
  document={document}
136
131
  documentType="Files"
137
- sharedDrive
138
- sharingDesc={sharingDesc}
139
- onShare={params => {
140
- onShare(params)
141
- }}
142
- submitLabel={addButtonLabel}
143
- showNotifications={false}
132
+ pendingRecipients={pendingRecipients}
133
+ onPendingRecipientsChange={onPendingRecipientsChange}
134
+ selectedOption={selectedOption}
135
+ onSelectedOptionChange={onSelectedOptionChange}
144
136
  />
145
137
  )}
146
138
  </div>
@@ -152,7 +144,6 @@ export const DumbBatchSharedFolderModal = withLocales(
152
144
  documentType="Files"
153
145
  className="u-w-100"
154
146
  onRevoke={onRevoke}
155
- onSetType={onSetType}
156
147
  link={sharingLink}
157
148
  />
158
149
  </div>
@@ -168,31 +159,26 @@ DumbBatchSharedFolderModal.propTypes = {
168
159
  document: PropTypes.object,
169
160
  folderName: PropTypes.string,
170
161
  handleFolderNameChange: PropTypes.func,
171
- createContact: PropTypes.func.isRequired,
172
162
  recipients: PropTypes.array,
173
163
  currentRecipients: PropTypes.array,
174
164
  onRevoke: PropTypes.func.isRequired,
175
- onSetType: PropTypes.func.isRequired,
176
165
  onCreate: PropTypes.func,
177
166
  onSend: PropTypes.func,
178
167
  onClose: PropTypes.func.isRequired,
179
- onShare: PropTypes.func.isRequired,
180
168
  onRename: PropTypes.func,
181
169
  showNameField: PropTypes.bool,
182
170
  sharingLink: PropTypes.string,
183
171
  nameLabel: PropTypes.string,
184
172
  addPeopleLabel: PropTypes.string,
185
- addButtonLabel: PropTypes.string,
186
173
  cancelLabel: PropTypes.string,
187
174
  createLabel: PropTypes.string,
188
175
  shareLabel: PropTypes.string,
189
176
  saveLabel: PropTypes.string,
190
- sharingDesc: PropTypes.string,
191
177
  showShareByEmail: PropTypes.bool,
192
178
  autoOpenShareRestriction: PropTypes.bool,
193
- showGenerateLinkButton: PropTypes.bool
194
- }
195
-
196
- DumbBatchSharedFolderModal.defaultProps = {
197
- sharingDesc: ''
179
+ showGenerateLinkButton: PropTypes.bool,
180
+ pendingRecipients: PropTypes.array,
181
+ onPendingRecipientsChange: PropTypes.func,
182
+ selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']),
183
+ onSelectedOptionChange: PropTypes.func
198
184
  }
@@ -1,72 +1,89 @@
1
- import cx from 'classnames'
2
1
  import PropTypes from 'prop-types'
3
- import React from 'react'
2
+ import React, { forwardRef, useRef, useState } from 'react'
4
3
 
5
- import Icon from 'cozy-ui/transpiled/react/Icon'
6
- import BottomIcon from 'cozy-ui/transpiled/react/Icons/Bottom'
7
- import CheckIcon from 'cozy-ui/transpiled/react/Icons/Check'
8
- import SelectBox, { components } from 'cozy-ui/transpiled/react/SelectBox'
4
+ import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
5
+ import { makeActions } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
6
+ import ActionsMenuItem from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuItem'
7
+ import DropdownButton from 'cozy-ui/transpiled/react/DropdownButton'
8
+ import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
9
+ import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
10
+ import Radios from 'cozy-ui/transpiled/react/Radios'
9
11
 
10
12
  import logger from '../logger'
11
- import styles from '../styles/share.styl'
12
13
 
13
- const DropdownIndicator = props => (
14
- <components.DropdownIndicator {...props}>
15
- <Icon icon={BottomIcon} color="var(--iconTextColor)" />
16
- </components.DropdownIndicator>
17
- )
14
+ const makeShareTypeAction = ({ option, isSelected, onSelect }) => {
15
+ const Component = forwardRef((props, ref) => (
16
+ <ActionsMenuItem
17
+ {...props}
18
+ ref={ref}
19
+ onClick={() => onSelect(option.value)}
20
+ disabled={option.disabled}
21
+ >
22
+ <ListItemIcon>
23
+ <Radios checked={isSelected} />
24
+ </ListItemIcon>
25
+ <ListItemText primary={option.label} />
26
+ </ActionsMenuItem>
27
+ ))
28
+ Component.displayName = `ShareTypeAction-${option.value}`
18
29
 
19
- const Option = props => (
20
- <components.Option {...props}>
21
- <div className={cx(styles['select-option'])}>
22
- {props.isSelected && (
23
- <Icon icon={CheckIcon} color="var(--primaryColor)" />
24
- )}
25
- <div>
26
- <div className={styles['select-option-label']}>{props.label}</div>
27
- <div className={styles['select-option-desc']}>{props.data.desc}</div>
28
- </div>
29
- </div>
30
- </components.Option>
31
- )
32
-
33
- const customStyles = {
34
- option: (base, state) => ({
35
- ...base,
36
- color: 'var(--primaryTextColor)',
37
- backgroundColor: state.isFocused ? 'var(--actionColorHover)' : null,
38
- padding: 0,
39
- borderBottom:
40
- state.options.findIndex(o => o.value === state.value) === 0
41
- ? '1px solid var(--borderMainColor)'
42
- : null
43
- }),
44
- menu: base => ({
45
- ...base,
46
- backgroundColor: 'var(--paperBackgroundColor)',
47
- width: '204%'
30
+ return () => ({
31
+ name: `shareType-${option.value}`,
32
+ label: option.label,
33
+ icon: null,
34
+ action: () => onSelect(option.value),
35
+ Component
48
36
  })
49
37
  }
50
38
 
51
- const ShareTypeSelect = ({ options, onChange }) => (
52
- <div className={styles['select-wrapper']}>
53
- <SelectBox
54
- name="select"
55
- classNamePrefix="needsclick react-select"
56
- components={{ DropdownIndicator, Option }}
57
- styles={customStyles}
58
- defaultValue={options[0]}
59
- isSearchable={false}
60
- onChange={option => {
61
- onChange(option.value)
62
- }}
63
- options={options}
64
- menuPosition="fixed"
65
- />
66
- </div>
67
- )
39
+ const ShareTypeSelect = ({ value, options, onChange }) => {
40
+ const buttonRef = useRef()
41
+ const [isOpen, setOpen] = useState(false)
42
+
43
+ const open = () => setOpen(true)
44
+ const close = () => setOpen(false)
45
+
46
+ const onSelect = nextValue => {
47
+ onChange(nextValue)
48
+ setOpen(false)
49
+ }
50
+
51
+ const selectedOption =
52
+ options.find(option => option.value === value) ?? options[0]
53
+
54
+ const actions = makeActions(
55
+ options.map(option =>
56
+ makeShareTypeAction({
57
+ option,
58
+ isSelected: option.value === selectedOption.value,
59
+ onSelect
60
+ })
61
+ )
62
+ )
63
+
64
+ return (
65
+ <>
66
+ <DropdownButton
67
+ ref={buttonRef}
68
+ onClick={open}
69
+ textVariant="body2"
70
+ size="small"
71
+ >
72
+ {selectedOption.label}
73
+ </DropdownButton>
74
+ <ActionsMenu
75
+ ref={buttonRef}
76
+ open={isOpen}
77
+ actions={actions}
78
+ autoClose
79
+ onClose={close}
80
+ />
81
+ </>
82
+ )
83
+ }
68
84
 
69
85
  ShareTypeSelect.propTypes = {
86
+ value: PropTypes.string,
70
87
  onChange: PropTypes.func,
71
88
  options: PropTypes.array.isRequired
72
89
  }
@@ -0,0 +1,13 @@
1
+ import { useState } from 'react'
2
+
3
+ export const usePendingRecipients = () => {
4
+ const [pendingRecipients, setPendingRecipients] = useState([])
5
+ const [selectedOption, setSelectedOption] = useState('readWrite')
6
+
7
+ return {
8
+ pendingRecipients,
9
+ setPendingRecipients,
10
+ selectedOption,
11
+ setSelectedOption
12
+ }
13
+ }
@@ -0,0 +1,21 @@
1
+ import { renderHook, act } from '@testing-library/react-hooks'
2
+
3
+ import { usePendingRecipients } from './usePendingRecipients'
4
+
5
+ describe('usePendingRecipients', () => {
6
+ it('starts with empty recipients and readWrite option', () => {
7
+ const { result } = renderHook(() => usePendingRecipients())
8
+ expect(result.current.pendingRecipients).toEqual([])
9
+ expect(result.current.selectedOption).toBe('readWrite')
10
+ })
11
+
12
+ it('exposes setters that update state', () => {
13
+ const { result } = renderHook(() => usePendingRecipients())
14
+ act(() => {
15
+ result.current.setPendingRecipients([{ email: 'a@b.c' }])
16
+ result.current.setSelectedOption('readOnly')
17
+ })
18
+ expect(result.current.pendingRecipients).toEqual([{ email: 'a@b.c' }])
19
+ expect(result.current.selectedOption).toBe('readOnly')
20
+ })
21
+ })
@@ -5,7 +5,7 @@
5
5
  box-sizing border-box
6
6
  position absolute
7
7
  top 100%
8
- margin-top .25rem
8
+ margin-top .5rem
9
9
  z-index var(--zIndex-alertMobile)
10
10
  width 100%
11
11
  overflow hidden
@@ -37,12 +37,13 @@
37
37
  display flex
38
38
  flex-wrap wrap
39
39
  align-items center
40
- padding .25rem .5rem
40
+ padding .25rem 3.5rem .25rem .5rem
41
41
  border-radius .25rem
42
42
  border 1px solid var(--borderMainColor)
43
43
  min-height 2.5rem
44
44
  max-height 6.75rem
45
45
  overflow auto
46
+ position relative
46
47
 
47
48
  &:hover
48
49
  border 1px solid var(--borderMainColor)
@@ -69,3 +70,8 @@
69
70
  font-size 1rem
70
71
  .recipientChip
71
72
  margin .25rem .5rem .25rem 0
73
+
74
+ .endAdornment
75
+ position absolute
76
+ top .75rem
77
+ right 1rem
@@ -38,45 +38,6 @@
38
38
  .coz-form-desc
39
39
  color var(--secondaryTextColor)
40
40
 
41
- .share-type-control
42
- display flex
43
- flex-direction row
44
- justify-content space-between
45
- padding .5rem 0 0
46
-
47
- .select-wrapper
48
- flex 0 0 49%
49
- position relative
50
-
51
- .select-option
52
- padding .5rem 1.5rem .5rem 3rem
53
- position relative
54
-
55
- svg
56
- position absolute
57
- left 1rem
58
- top 1rem
59
-
60
-
61
- .select-option-label
62
- line-height 2
63
-
64
- .select-option-desc
65
- line-height 1.5
66
- font-size .8125rem
67
- color var(--secondaryTextColor)
68
-
69
-
70
-
71
- button
72
- flex 0 0 49%
73
- margin 0
74
-
75
- :global
76
- // dirty hack for react-select: its API doesn't allow to make change to active styles
77
- .react-select__option:active
78
- background-color var(--defaultBackgroundColor) !important
79
-
80
41
  input[type=text]
81
42
  width 100%
82
43