cozy-sharing 31.0.2 → 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 (81) hide show
  1. package/CHANGELOG.md +30 -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/GroupRecipientPermissions.js +15 -11
  6. package/dist/components/Recipient/LinkRecipient.js +15 -11
  7. package/dist/components/Recipient/LinkRecipientLite.js +1 -1
  8. package/dist/components/Recipient/LinkRecipientPermissions.js +2 -1
  9. package/dist/components/Recipient/MemberRecipient.spec.js +8 -10
  10. package/dist/components/Recipient/MemberRecipientLite.js +1 -1
  11. package/dist/components/Recipient/MemberRecipientPermissions.js +16 -12
  12. package/dist/components/Recipient/MemberRecipientPermissions.spec.js +62 -50
  13. package/dist/components/Recipient/RecipientConfirm.js +0 -5
  14. package/dist/components/ShareAutosuggest.js +9 -4
  15. package/dist/components/ShareByEmail.js +29 -274
  16. package/dist/components/ShareByEmail.spec.js +52 -230
  17. package/dist/components/ShareByLink.js +1 -3
  18. package/dist/components/ShareDialogCozyToCozy.js +169 -35
  19. package/dist/components/ShareDialogCozyToCozy.spec.js +123 -36
  20. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +13 -3
  21. package/dist/components/ShareModal/SharingDetailsModal.js +0 -5
  22. package/dist/components/ShareRecipientsInput.js +6 -3
  23. package/dist/components/SharedDrive/SharedDriveForm.js +17 -25
  24. package/dist/components/SharedDrive/SharedDriveModal.js +1 -1
  25. package/dist/components/SharedDrive/SharedDriveRecipientPermissions.js +15 -10
  26. package/dist/components/SharedDrive/useSharedDrive.js +33 -57
  27. package/dist/components/SharedDrive/useSharedDrive.spec.js +29 -93
  28. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +15 -26
  29. package/dist/components/Sharetypeselect.js +85 -90
  30. package/dist/hooks/usePendingRecipients.js +20 -0
  31. package/dist/hooks/usePendingRecipients.spec.js +30 -0
  32. package/dist/styles/autosuggest.styl +8 -2
  33. package/dist/styles/share.styl +0 -39
  34. package/dist/stylesheet.css +8 -36
  35. package/locales/en.json +14 -44
  36. package/locales/fr.json +14 -44
  37. package/locales/ru.json +14 -44
  38. package/locales/vi.json +14 -42
  39. package/package.json +2 -2
  40. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +14 -15
  41. package/src/components/FederatedFolder/FederatedFolderModal.jsx +50 -67
  42. package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +63 -186
  43. package/src/components/Recipient/GroupRecipientPermissions.jsx +15 -13
  44. package/src/components/Recipient/LinkRecipient.jsx +9 -9
  45. package/src/components/Recipient/LinkRecipientLite.jsx +1 -3
  46. package/src/components/Recipient/LinkRecipientPermissions.jsx +2 -1
  47. package/src/components/Recipient/MemberRecipient.spec.jsx +4 -6
  48. package/src/components/Recipient/MemberRecipientLite.jsx +2 -2
  49. package/src/components/Recipient/MemberRecipientPermissions.jsx +16 -21
  50. package/src/components/Recipient/MemberRecipientPermissions.spec.jsx +21 -23
  51. package/src/components/ShareAutosuggest.jsx +7 -2
  52. package/src/components/ShareByEmail.jsx +35 -186
  53. package/src/components/ShareByEmail.spec.jsx +22 -173
  54. package/src/components/ShareByLink.jsx +1 -7
  55. package/src/components/ShareDialogCozyToCozy.jsx +171 -38
  56. package/src/components/ShareDialogCozyToCozy.spec.jsx +67 -1
  57. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +11 -1
  58. package/src/components/ShareRecipientsInput.jsx +5 -2
  59. package/src/components/SharedDrive/SharedDriveForm.jsx +16 -25
  60. package/src/components/SharedDrive/SharedDriveModal.jsx +1 -1
  61. package/src/components/SharedDrive/SharedDriveRecipientPermissions.jsx +15 -17
  62. package/src/components/SharedDrive/useSharedDrive.js +24 -50
  63. package/src/components/SharedDrive/useSharedDrive.spec.js +19 -67
  64. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +15 -29
  65. package/src/components/Sharetypeselect.jsx +75 -58
  66. package/src/hooks/usePendingRecipients.js +13 -0
  67. package/src/hooks/usePendingRecipients.spec.js +21 -0
  68. package/src/styles/autosuggest.styl +8 -2
  69. package/src/styles/share.styl +0 -39
  70. package/dist/components/Recipient/actions/revokeGroup.js +0 -42
  71. package/dist/components/Recipient/actions/revokeMember.js +0 -42
  72. package/dist/components/Recipient/actions/revokeSharedDriveMember.js +0 -41
  73. package/dist/components/SharedDrive/helpers.js +0 -102
  74. package/dist/components/SharedDrive/helpers.spec.js +0 -227
  75. package/dist/components/Sharesubmit.js +0 -28
  76. package/src/components/Recipient/actions/revokeGroup.js +0 -42
  77. package/src/components/Recipient/actions/revokeMember.js +0 -42
  78. package/src/components/Recipient/actions/revokeSharedDriveMember.js +0 -41
  79. package/src/components/SharedDrive/helpers.js +0 -108
  80. package/src/components/SharedDrive/helpers.spec.js +0 -177
  81. 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,34 +38,27 @@ 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
- <div className="u-flex u-flex-justify-end u-ph-2 u-pv-1 u-column-gap-half">
49
+ <div className="u-flex u-ph-2 u-pv-1">
63
50
  <Button
64
- variant="secondary"
51
+ variant="text"
65
52
  label={t('SharedDrive.sharedDriveModal.cancel')}
53
+ className="u-w-100 u-m-1"
54
+ size="large"
66
55
  onClick={onCancel}
67
56
  />
68
57
  <Button
69
58
  variant="primary"
70
59
  label={t('SharedDrive.sharedDriveModal.create')}
60
+ className="u-w-100 u-m-1"
61
+ size="large"
71
62
  onClick={onCreate}
72
63
  />
73
64
  </div>
@@ -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()
@@ -1,15 +1,13 @@
1
1
  import React, { useState, useRef } from 'react'
2
2
 
3
- import { useClient } from 'cozy-client'
4
3
  import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
5
- import {
6
- makeActions,
7
- divider
8
- } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
4
+ import { makeActions } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
9
5
  import DropdownButton from 'cozy-ui/transpiled/react/DropdownButton'
6
+ import Icon from 'cozy-ui/transpiled/react/Icon'
7
+ import IconButton from 'cozy-ui/transpiled/react/IconButton'
8
+ import CrossCircleOutlineIcon from 'cozy-ui/transpiled/react/Icons/CrossCircleOutline'
10
9
  import { useI18n } from 'twake-i18n'
11
10
 
12
- import { revokeSharedDriveMember } from '../Recipient/actions/revokeSharedDriveMember'
13
11
  import { setReadOnlySharedPermission } from '../Recipient/actions/setReadOnlySharedPermission'
14
12
  import { setReadWriteSharedPermission } from '../Recipient/actions/setReadWriteSharedPermission'
15
13
 
@@ -21,7 +19,6 @@ const ShareDriveRecipientPermissions = ({
21
19
  }) => {
22
20
  const { t } = useI18n()
23
21
  const buttonRef = useRef()
24
- const client = useClient()
25
22
  const [isMenuDisplayed, setMenuDisplayed] = useState(false)
26
23
 
27
24
  const toggleMenu = () => setMenuDisplayed(!isMenuDisplayed)
@@ -40,18 +37,11 @@ const ShareDriveRecipientPermissions = ({
40
37
  }
41
38
 
42
39
  const actions = makeActions(
43
- [
44
- setReadOnlySharedPermission,
45
- setReadWriteSharedPermission,
46
- divider,
47
- revokeSharedDriveMember
48
- ],
40
+ [setReadOnlySharedPermission, setReadWriteSharedPermission],
49
41
  {
50
42
  t,
51
- client,
52
43
  type,
53
- setType,
54
- handleRevocation
44
+ setType
55
45
  }
56
46
  )
57
47
 
@@ -64,7 +54,7 @@ const ShareDriveRecipientPermissions = ({
64
54
  onClick={toggleMenu}
65
55
  textVariant="body2"
66
56
  >
67
- {t(`Share.type.${type}`).toLowerCase()}
57
+ {t(`Share.type.${type}`)}
68
58
  </DropdownButton>
69
59
  <ActionsMenu
70
60
  ref={buttonRef}
@@ -73,6 +63,14 @@ const ShareDriveRecipientPermissions = ({
73
63
  autoClose
74
64
  onClose={hideMenu}
75
65
  />
66
+ <IconButton
67
+ onClick={handleRevocation}
68
+ size="small"
69
+ className="u-ml-half"
70
+ aria-label={t('Share.members.revoke')}
71
+ >
72
+ <Icon icon={CrossCircleOutlineIcon} />
73
+ </IconButton>
76
74
  </div>
77
75
  )
78
76
  }
@@ -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
+ })