cozy-ui 127.10.1 → 127.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +1 -1
  3. package/react/Contacts/GroupsSelect/GroupCreation.jsx +95 -0
  4. package/react/Contacts/GroupsSelect/GroupsSelect.jsx +159 -0
  5. package/react/Contacts/GroupsSelect/GroupsSelect.spec.jsx +248 -0
  6. package/react/Contacts/GroupsSelect/GroupsSelectProvider.jsx +39 -0
  7. package/react/Contacts/GroupsSelect/Readme.md +1 -0
  8. package/react/Contacts/GroupsSelect/SelectBox/Control.jsx +36 -0
  9. package/react/Contacts/GroupsSelect/SelectBox/EditGroupName.jsx +52 -0
  10. package/react/Contacts/GroupsSelect/SelectBox/Menu.jsx +26 -0
  11. package/react/Contacts/GroupsSelect/SelectBox/Option.jsx +67 -0
  12. package/react/Contacts/GroupsSelect/SelectBox/SelectContainer.jsx +15 -0
  13. package/react/Contacts/GroupsSelect/SelectBox/styles.styl +2 -0
  14. package/react/Contacts/GroupsSelect/helpers.js +25 -0
  15. package/react/Contacts/GroupsSelect/locales/en.json +21 -0
  16. package/react/Contacts/GroupsSelect/locales/fr.json +21 -0
  17. package/react/Contacts/GroupsSelect/locales/index.jsx +12 -0
  18. package/react/Contacts/GroupsSelect/styles.styl +47 -0
  19. package/react/Contacts/GroupsSelect/useGroupSelect.jsx +88 -0
  20. package/react/Contacts/Header/GroupsSelection.jsx +74 -0
  21. package/react/Contacts/Header/ImportDropdown.jsx +78 -0
  22. package/react/Contacts/Header/Readme.md +40 -0
  23. package/react/Contacts/Header/SearchInput.jsx +19 -0
  24. package/react/Contacts/Header/index.jsx +79 -0
  25. package/react/Contacts/Header/locales/en.json +15 -0
  26. package/react/Contacts/Header/locales/fr.json +15 -0
  27. package/react/Contacts/Header/locales/index.jsx +12 -0
  28. package/react/Contacts/Readme.md +1 -0
  29. package/react/ContactsList/ContactCell.jsx +58 -0
  30. package/react/ContactsList/helpers.js +50 -1
  31. package/react/ContactsList/helpers.spec.js +50 -1
  32. package/react/ContactsList/locales/en.json +3 -1
  33. package/react/ContactsList/locales/fr.json +3 -1
  34. package/react/providers/Selection/Readme.md +28 -0
  35. package/react/providers/Selection/index.jsx +85 -0
  36. package/transpiled/react/Contacts/GroupsSelect/GroupCreation.d.ts +3 -0
  37. package/transpiled/react/Contacts/GroupsSelect/GroupCreation.js +133 -0
  38. package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.d.ts +56 -0
  39. package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.js +181 -0
  40. package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.spec.d.ts +22 -0
  41. package/transpiled/react/Contacts/GroupsSelect/GroupsSelectProvider.d.ts +5 -0
  42. package/transpiled/react/Contacts/GroupsSelect/GroupsSelectProvider.js +38 -0
  43. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Control.d.ts +14 -0
  44. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Control.js +37 -0
  45. package/transpiled/react/Contacts/GroupsSelect/SelectBox/EditGroupName.d.ts +7 -0
  46. package/transpiled/react/Contacts/GroupsSelect/SelectBox/EditGroupName.js +71 -0
  47. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Menu.d.ts +7 -0
  48. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Menu.js +34 -0
  49. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Option.d.ts +18 -0
  50. package/transpiled/react/Contacts/GroupsSelect/SelectBox/Option.js +66 -0
  51. package/transpiled/react/Contacts/GroupsSelect/SelectBox/SelectContainer.d.ts +2 -0
  52. package/transpiled/react/Contacts/GroupsSelect/SelectBox/SelectContainer.js +12 -0
  53. package/transpiled/react/Contacts/GroupsSelect/helpers.d.ts +9 -0
  54. package/transpiled/react/Contacts/GroupsSelect/helpers.js +30 -0
  55. package/transpiled/react/Contacts/GroupsSelect/locales/index.d.ts +6 -0
  56. package/transpiled/react/Contacts/GroupsSelect/locales/index.js +49 -0
  57. package/transpiled/react/Contacts/GroupsSelect/useGroupSelect.d.ts +11 -0
  58. package/transpiled/react/Contacts/GroupsSelect/useGroupSelect.js +179 -0
  59. package/transpiled/react/Contacts/Header/GroupsSelection.d.ts +7 -0
  60. package/transpiled/react/Contacts/Header/GroupsSelection.js +84 -0
  61. package/transpiled/react/Contacts/Header/ImportDropdown.d.ts +4 -0
  62. package/transpiled/react/Contacts/Header/ImportDropdown.js +88 -0
  63. package/transpiled/react/Contacts/Header/SearchInput.d.ts +4 -0
  64. package/transpiled/react/Contacts/Header/SearchInput.js +24 -0
  65. package/transpiled/react/Contacts/Header/index.d.ts +26 -0
  66. package/transpiled/react/Contacts/Header/index.js +72 -0
  67. package/transpiled/react/Contacts/Header/locales/index.d.ts +6 -0
  68. package/transpiled/react/Contacts/Header/locales/index.js +37 -0
  69. package/transpiled/react/ContactsList/ContactCell.d.ts +8 -0
  70. package/transpiled/react/ContactsList/ContactCell.js +68 -0
  71. package/transpiled/react/ContactsList/helpers.d.ts +1 -0
  72. package/transpiled/react/ContactsList/helpers.js +51 -1
  73. package/transpiled/react/ContactsList/locales/withContactsListLocales.js +6 -2
  74. package/transpiled/react/providers/Selection/index.d.ts +6 -0
  75. package/transpiled/react/providers/Selection/index.js +99 -0
  76. package/transpiled/react/stylesheet.css +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # [127.12.0](https://github.com/cozy/cozy-ui/compare/v127.11.0...v127.12.0) (2025-08-20)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add `Contacts/Header` and `Contacts/GroupsSelect` components ([41892e8](https://github.com/cozy/cozy-ui/commit/41892e8))
7
+
8
+ # [127.11.0](https://github.com/cozy/cozy-ui/compare/v127.10.1...v127.11.0) (2025-08-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * **ContactsList:** Add ContactCell component to show a contact in table ([234bfe2](https://github.com/cozy/cozy-ui/commit/234bfe2))
14
+ * **ContactsList:** Add makeGroupLabelsAndCounts function ([b628571](https://github.com/cozy/cozy-ui/commit/b628571))
15
+ * **Providers:** Add Selection provider ([b9854b6](https://github.com/cozy/cozy-ui/commit/b9854b6))
16
+
1
17
  ## [127.10.1](https://github.com/cozy/cozy-ui/compare/v127.10.0...v127.10.1) (2025-08-18)
2
18
 
3
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "127.10.1",
3
+ "version": "127.12.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -0,0 +1,95 @@
1
+ import classNames from 'classnames'
2
+ import React, { Component } from 'react'
3
+
4
+ import styles from './styles.styl'
5
+ import Icon from '../../Icon'
6
+ import PlusIcon from '../../Icons/Plus'
7
+ import Input from '../../Input'
8
+ import { translate } from '../../providers/I18n'
9
+
10
+ const normalizeGroupData = name => {
11
+ return {
12
+ name: name,
13
+ metadata: {
14
+ version: 1
15
+ }
16
+ }
17
+ }
18
+ class GroupCreation extends Component {
19
+ state = {
20
+ isInputDisplayed: false,
21
+ groupName: ''
22
+ }
23
+ constructor(props) {
24
+ super(props)
25
+ this.textInput = React.createRef()
26
+ }
27
+ handleClick = () => {
28
+ this.setState({ isInputDisplayed: !this.state.isInputDisplayed })
29
+ }
30
+ onFocus = e => {
31
+ e.stopPropagation()
32
+ }
33
+
34
+ onClick = e => {
35
+ e.stopPropagation()
36
+ }
37
+ keyPress = async e => {
38
+ if (e.keyCode == 13) {
39
+ this.props.createGroup(normalizeGroupData(e.target.value))
40
+ this.textInput.current.value = ''
41
+ }
42
+ e.stopPropagation()
43
+ }
44
+
45
+ onMouseDown = e => {
46
+ e.stopPropagation()
47
+ }
48
+
49
+ render() {
50
+ const { isInputDisplayed } = this.state
51
+ const { t } = this.props
52
+ return (
53
+ <div>
54
+ <div className={styles['contact-group-creation-divider']} />
55
+ <div
56
+ className={classNames(
57
+ 'u-ml-half',
58
+ 'u-mr-half',
59
+ 'u-c-pointer',
60
+ styles['container']
61
+ )}
62
+ >
63
+ {!isInputDisplayed && (
64
+ <div
65
+ onClick={this.handleClick}
66
+ className={styles['contact-group-create-div-icon']}
67
+ >
68
+ <Icon icon={PlusIcon} />
69
+ <span className="u-pl-half">
70
+ {t('Contacts.GroupsSelect.create')}
71
+ </span>
72
+ </div>
73
+ )}
74
+ {isInputDisplayed && (
75
+ <Input
76
+ id="createGroupInput"
77
+ ref={this.textInput}
78
+ type="text"
79
+ placeholder={t('Contacts.GroupsSelect.name')}
80
+ onClick={this.onClick}
81
+ onFocus={this.onFocus}
82
+ onKeyDown={this.keyPress}
83
+ size="tiny"
84
+ autoComplete="off"
85
+ autoFocus={true}
86
+ onMouseDown={this.onMouseDown}
87
+ />
88
+ )}
89
+ </div>
90
+ </div>
91
+ )
92
+ }
93
+ }
94
+
95
+ export default translate()(GroupCreation)
@@ -0,0 +1,159 @@
1
+ import PropTypes from 'prop-types'
2
+ import React, { useState } from 'react'
3
+
4
+ import { useClient } from 'cozy-client'
5
+
6
+ import CustomMenu from './SelectBox/Menu'
7
+ import CustomOption from './SelectBox/Option'
8
+ import CustomSelectContainer from './SelectBox/SelectContainer'
9
+ import useGroupsSelect from './useGroupSelect'
10
+ import ClickAwayListener from '../../ClickAwayListener'
11
+ import SelectBox from '../../SelectBox'
12
+ import { useBreakpoints } from '../../providers/Breakpoints'
13
+
14
+ const captureEscapeEvent = e => {
15
+ if (e.key === 'Escape') {
16
+ e.stopPropagation()
17
+ e.target.blur()
18
+ }
19
+ }
20
+
21
+ export const GroupsSelect = ({
22
+ allGroups,
23
+ closeMenuOnSelect,
24
+ value,
25
+ styles,
26
+ isMulti,
27
+ noOptionsMessage,
28
+ withCheckbox,
29
+ components,
30
+ onGroupCreated,
31
+ onChange,
32
+ onGroupCreate,
33
+ onGroupUpdate,
34
+ onGroupDelete,
35
+ menuPosition
36
+ }) => {
37
+ const client = useClient()
38
+ const { isMobile } = useBreakpoints()
39
+ const [{ menuIsOpen, editedGroupId }, setState] = useState({
40
+ menuIsOpen: false,
41
+ editedGroupId: ''
42
+ })
43
+ const { createGroup, renameGroup } = useGroupsSelect({
44
+ allGroups,
45
+ onGroupCreated,
46
+ client,
47
+ onGroupCreate,
48
+ onGroupUpdate
49
+ })
50
+
51
+ const toggleMenu = () => {
52
+ setState(prev => ({ ...prev, menuIsOpen: !prev.menuIsOpen }))
53
+ }
54
+
55
+ const closeMenu = () => {
56
+ setState(prev => ({ ...prev, menuIsOpen: false }))
57
+ }
58
+
59
+ const setEditedGroupId = id => {
60
+ setState(prev => ({ ...prev, editedGroupId: id }))
61
+ }
62
+
63
+ const handleChange = props => {
64
+ if (closeMenuOnSelect) {
65
+ closeMenu()
66
+ }
67
+
68
+ onChange(props)
69
+ }
70
+
71
+ const handleDelete = group => {
72
+ closeMenu()
73
+ onGroupDelete(group)
74
+ }
75
+
76
+ const defaultComponents = {
77
+ Menu: CustomMenu,
78
+ Option: CustomOption,
79
+ SelectContainer: CustomSelectContainer
80
+ }
81
+
82
+ return (
83
+ <>
84
+ <ClickAwayListener onClickAway={menuIsOpen ? closeMenu : () => {}}>
85
+ <SelectBox
86
+ className={isMobile ? 'u-mb-half' : 'u-mr-half'}
87
+ classNamePrefix="react-select"
88
+ isMulti={isMulti}
89
+ withCheckbox={withCheckbox}
90
+ menuIsOpen={menuIsOpen}
91
+ blurInputOnSelect={true}
92
+ hideSelectedOptions={false}
93
+ isSearchable={false}
94
+ isClearable={false}
95
+ closeMenuOnSelect={closeMenuOnSelect}
96
+ tabSelectsValue={false}
97
+ onKeyDown={captureEscapeEvent}
98
+ noOptionsMessage={noOptionsMessage}
99
+ options={allGroups}
100
+ value={value}
101
+ onChange={handleChange}
102
+ getOptionLabel={group => group.name}
103
+ getOptionValue={group => group._id}
104
+ components={{ ...defaultComponents, ...components }}
105
+ createGroup={createGroup}
106
+ deleteGroup={handleDelete}
107
+ renameGroup={renameGroup}
108
+ styles={styles}
109
+ onControlClicked={toggleMenu}
110
+ setEditedGroupId={setEditedGroupId}
111
+ editedGroupId={editedGroupId}
112
+ menuPosition={menuPosition}
113
+ fullwidth
114
+ />
115
+ </ClickAwayListener>
116
+ </>
117
+ )
118
+ }
119
+
120
+ GroupsSelect.propTypes = {
121
+ allGroups: PropTypes.array.isRequired,
122
+ styles: PropTypes.object,
123
+ // for multiple selections, value can be an array
124
+ value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
125
+ // to customize react-select elements
126
+ components: PropTypes.object,
127
+ // to define if it is possible to select more than one option
128
+ isMulti: PropTypes.bool,
129
+ // noOptionsMessage is used to show a message when there is no options in the menu list
130
+ noOptionsMessage: PropTypes.func,
131
+ // hide/show checkbox besides menu list options
132
+ withCheckbox: PropTypes.bool,
133
+ onChange: PropTypes.func.isRequired,
134
+ // function to be triggered after creating a group
135
+ onGroupCreated: PropTypes.func,
136
+ // function to be triggered when creating a group
137
+ onGroupCreate: PropTypes.func,
138
+ // function to be triggered when updating a group
139
+ onGroupUpdate: PropTypes.func,
140
+ // function to be triggered when deleting a group
141
+ onGroupDelete: PropTypes.func,
142
+ closeMenuOnSelect: PropTypes.bool,
143
+ menuPosition: PropTypes.oneOf(['fixed', 'absolute'])
144
+ }
145
+
146
+ GroupsSelect.defaultProps = {
147
+ isMulti: false,
148
+ components: {},
149
+ closeMenuOnSelect: false
150
+ }
151
+
152
+ GroupsSelect.propTypes = {
153
+ allGroups: PropTypes.array.isRequired,
154
+ onGroupCreate: PropTypes.func.isRequired,
155
+ onGroupUpdate: PropTypes.func.isRequired,
156
+ onGroupDelete: PropTypes.func.isRequired
157
+ }
158
+
159
+ export default GroupsSelect
@@ -0,0 +1,248 @@
1
+ import { render, screen, fireEvent, act } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import GroupsSelect from './GroupsSelect'
5
+ import SelectedGroupProvider from './GroupsSelectProvider'
6
+ import Control from './SelectBox/Control'
7
+ import AlertProvider from '../../providers/Alert'
8
+ import DemoProvider from '../../providers/DemoProvider'
9
+
10
+ const createGroup = jest.fn()
11
+ const updateGroup = jest.fn()
12
+ const deleteGroup = jest.fn()
13
+
14
+ export const groups = [
15
+ {
16
+ id: '5b49553c5916cf7b5b2a5f48600078a8',
17
+ _id: '5b49553c5916cf7b5b2a5f48600078a8',
18
+ _type: 'io.cozy.contacts.groups',
19
+ _rev: '1-7862def64fb044932d3264e2f8477454',
20
+ cozyMetadata: {
21
+ createdAt: '2019-07-15T11:22:13.551Z',
22
+ createdByApp: 'Contacts',
23
+ createdByAppVersion: '0.8.5',
24
+ metadataVersion: 1,
25
+ updatedAt: '2019-07-15T11:22:13.551Z',
26
+ updatedByApps: [
27
+ { date: '2019-07-15T11:22:13.551Z', slug: 'Contacts', version: '0.8.5' }
28
+ ]
29
+ },
30
+ metadata: { version: 1 },
31
+ name: '2018-2019 Enseignants'
32
+ },
33
+ {
34
+ id: '8cb7ea7fe264260e997529439b0091c0',
35
+ _id: '8cb7ea7fe264260e997529439b0091c0',
36
+ _type: 'io.cozy.contacts.groups',
37
+ _rev: '1-8b92242f1ccaca20e7862306cddbe37f',
38
+ cozyMetadata: {
39
+ createdAt: '2020-04-15T06:46:20.128Z',
40
+ createdByApp: 'Contacts',
41
+ createdByAppVersion: '0.8.7',
42
+ metadataVersion: 1,
43
+ updatedAt: '2020-04-15T06:46:20.128Z',
44
+ updatedByApps: [
45
+ { date: '2020-04-15T06:46:20.128Z', slug: 'Contacts', version: '0.8.7' }
46
+ ]
47
+ },
48
+ metadata: { version: 1 },
49
+ name: 'ez'
50
+ },
51
+ {
52
+ id: '18c031b0ac9f47cdc48b275b1900726d',
53
+ _id: '18c031b0ac9f47cdc48b275b1900726d',
54
+ _type: 'io.cozy.contacts.groups',
55
+ _rev: '1-f438477a749fa0b90cde2e7c53a41bd6',
56
+ cozyMetadata: {
57
+ createdAt: '2019-11-08T07:45:55.424Z',
58
+ createdByApp: 'Contacts',
59
+ createdByAppVersion: '0.8.6',
60
+ metadataVersion: 1,
61
+ updatedAt: '2019-11-08T07:45:55.424Z',
62
+ updatedByApps: [
63
+ { date: '2019-11-08T07:45:55.424Z', slug: 'Contacts', version: '0.8.6' }
64
+ ]
65
+ },
66
+ metadata: { version: 1 },
67
+ name: 'groupe test'
68
+ }
69
+ ]
70
+
71
+ const setup = () => {
72
+ const root = render(
73
+ <DemoProvider>
74
+ <AlertProvider>
75
+ <SelectedGroupProvider>
76
+ <GroupsSelect
77
+ value={[groups[0]]}
78
+ allGroups={groups}
79
+ components={{ Control }}
80
+ onChange={() => {}}
81
+ onGroupCreate={createGroup}
82
+ onGroupUpdate={updateGroup}
83
+ onGroupDelete={deleteGroup}
84
+ />
85
+ </SelectedGroupProvider>
86
+ </AlertProvider>
87
+ </DemoProvider>
88
+ )
89
+ return { root }
90
+ }
91
+
92
+ describe('GroupsSelect', () => {
93
+ beforeEach(() => {
94
+ jest.clearAllMocks()
95
+ })
96
+
97
+ it('should display every groups and group creation button', () => {
98
+ setup()
99
+
100
+ act(() => {
101
+ fireEvent.click(screen.getByText('Groups'))
102
+ })
103
+
104
+ for (const group of groups) {
105
+ expect(screen.queryByText(group.name)).not.toBeNull()
106
+ }
107
+ expect(screen.queryByText('Create a group')).not.toBeNull()
108
+ })
109
+
110
+ it('should be able to create a new group', () => {
111
+ setup()
112
+
113
+ act(() => {
114
+ fireEvent.click(screen.getByText('Groups'))
115
+ })
116
+
117
+ act(() => {
118
+ fireEvent.click(screen.getByText('Create a group'))
119
+ })
120
+
121
+ // it should replace the button by an empty input
122
+ let createGroupInput = screen.queryByRole('textbox', {
123
+ id: 'createGroupInput'
124
+ })
125
+ expect(createGroupInput).not.toBeNull()
126
+ expect(createGroupInput.value).toBe('')
127
+
128
+ act(() => {
129
+ // it should trigger creation function by pressing Enter key, and clear the input
130
+ fireEvent.change(
131
+ screen.getByRole('textbox', {
132
+ id: 'createGroupInput'
133
+ }),
134
+ { target: { value: 'new group' } }
135
+ )
136
+ })
137
+
138
+ createGroupInput = screen.queryByRole('textbox', {
139
+ id: 'createGroupInput'
140
+ })
141
+ expect(createGroupInput).not.toBeNull()
142
+ expect(createGroupInput.value).toBe('new group')
143
+
144
+ act(() => {
145
+ fireEvent.keyDown(
146
+ screen.getByRole('textbox', {
147
+ id: 'createGroupInput'
148
+ }),
149
+ { key: 'Enter', keyCode: '13' }
150
+ )
151
+ })
152
+
153
+ expect(createGroup).toHaveBeenCalled()
154
+ createGroupInput = screen.queryByRole('textbox', {
155
+ id: 'createGroupInput'
156
+ })
157
+ expect(createGroupInput).not.toBeNull()
158
+ expect(createGroupInput.value).toBe('')
159
+ })
160
+
161
+ it("shouldn't be able to create a new group", () => {
162
+ setup()
163
+
164
+ act(() => {
165
+ fireEvent.click(screen.getByText('Groups'))
166
+ })
167
+
168
+ act(() => {
169
+ fireEvent.click(screen.getByText('Create a group'))
170
+ })
171
+
172
+ // it should replace the button by an empty input
173
+ let createGroupInput = screen.queryByRole('textbox', {
174
+ id: 'createGroupInput'
175
+ })
176
+ expect(createGroupInput).not.toBeNull()
177
+ expect(createGroupInput.value).toBe('')
178
+
179
+ act(() => {
180
+ // it should trigger creation function by pressing Enter key, and clear the input
181
+ fireEvent.change(
182
+ screen.getByRole('textbox', {
183
+ id: 'createGroupInput'
184
+ }),
185
+ { target: { value: '' } }
186
+ )
187
+ })
188
+
189
+ createGroupInput = screen.queryByRole('textbox', {
190
+ id: 'createGroupInput'
191
+ })
192
+ expect(createGroupInput).not.toBeNull()
193
+ expect(createGroupInput.value).toBe('')
194
+
195
+ act(() => {
196
+ fireEvent.keyDown(
197
+ screen.getByRole('textbox', {
198
+ id: 'createGroupInput'
199
+ }),
200
+ { key: 'Enter', keyCode: '13' }
201
+ )
202
+ })
203
+
204
+ expect(createGroup).not.toHaveBeenCalled()
205
+ createGroupInput = screen.queryByRole('textbox', {
206
+ id: 'createGroupInput'
207
+ })
208
+ expect(createGroupInput).not.toBeNull()
209
+ expect(createGroupInput.value).toBe('')
210
+ })
211
+
212
+ it('should be able to rename a group', () => {
213
+ setup()
214
+
215
+ act(() => {
216
+ // it should replace the field by input with group name as value
217
+ fireEvent.click(screen.getByText('Groups'))
218
+ })
219
+
220
+ act(() => {
221
+ fireEvent.click(
222
+ screen.getByTestId(`ActionsOption_${groups[0].name}-icon_pen`)
223
+ )
224
+ })
225
+
226
+ let editGroupInput = screen.queryByRole('textbox', {
227
+ id: 'editGroupInput'
228
+ })
229
+ expect(editGroupInput).not.toBeNull()
230
+ expect(editGroupInput.value).toBe(groups[0].name)
231
+
232
+ act(() => {
233
+ // it should trigger rename function by pressing Enter key, and remove input
234
+ fireEvent.keyDown(
235
+ screen.getByRole('textbox', {
236
+ id: 'editGroupInput'
237
+ }),
238
+ { key: 'Enter', keyCode: '13' }
239
+ )
240
+ })
241
+
242
+ expect(updateGroup).toHaveBeenCalled()
243
+ editGroupInput = screen.queryByRole('textbox', {
244
+ id: 'editGroupInput'
245
+ })
246
+ expect(editGroupInput).toBeNull()
247
+ })
248
+ })
@@ -0,0 +1,39 @@
1
+ import React, { useContext, useState } from 'react'
2
+
3
+ import { translatedDefaultSelectedGroup } from './helpers'
4
+ import { locales } from './locales'
5
+ import { useI18n, useExtendI18n } from '../../providers/I18n'
6
+
7
+ const SelectedGroupContext = React.createContext()
8
+
9
+ export const useSelectedGroup = () => {
10
+ const context = useContext(SelectedGroupContext)
11
+
12
+ if (!context) {
13
+ throw new Error(
14
+ 'useSelectedGroup must be used within a SelectedGroupProvider'
15
+ )
16
+ }
17
+ return context
18
+ }
19
+
20
+ const SelectedGroupProvider = ({ children }) => {
21
+ useExtendI18n(locales)
22
+ const { t } = useI18n()
23
+ const [selectedGroup, setSelectedGroup] = useState(
24
+ translatedDefaultSelectedGroup(t)
25
+ )
26
+
27
+ const contextValue = {
28
+ selectedGroup,
29
+ setSelectedGroup
30
+ }
31
+
32
+ return (
33
+ <SelectedGroupContext.Provider value={contextValue}>
34
+ {children}
35
+ </SelectedGroupContext.Provider>
36
+ )
37
+ }
38
+
39
+ export default SelectedGroupProvider
@@ -0,0 +1 @@
1
+ <!-- For styleguidist, a .md file is needed in directories -->
@@ -0,0 +1,36 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ import Button from '../../../Buttons'
5
+ import Icon from '../../../Icon'
6
+ import BottomSelectIcon from '../../../Icons/BottomSelect'
7
+ import { useI18n, useExtendI18n } from '../../../providers/I18n'
8
+ import { locales } from '../locales'
9
+
10
+ const Control = ({
11
+ innerRef,
12
+ innerProps,
13
+ selectProps: { onControlClicked }
14
+ }) => {
15
+ useExtendI18n(locales)
16
+ const { t } = useI18n()
17
+
18
+ return (
19
+ <div ref={innerRef} {...innerProps}>
20
+ <Button
21
+ variant="secondary"
22
+ size="small"
23
+ onClick={onControlClicked}
24
+ onTouchStart={onControlClicked}
25
+ label={t('Contacts.GroupsSelect.manage')}
26
+ startIcon={<Icon icon={BottomSelectIcon} width="12" />}
27
+ />
28
+ </div>
29
+ )
30
+ }
31
+
32
+ Control.propTypes = {
33
+ selectProps: PropTypes.object.isRequired
34
+ }
35
+
36
+ export default Control
@@ -0,0 +1,52 @@
1
+ import PropTypes from 'prop-types'
2
+ import React, { useRef } from 'react'
3
+
4
+ import Input from '../../../Input'
5
+
6
+ const EditGroupName = ({
7
+ groupId,
8
+ groupName,
9
+ setEditedGroupId,
10
+ renameGroup
11
+ }) => {
12
+ const inputRef = useRef()
13
+
14
+ const stopPropagation = e => {
15
+ e.stopPropagation()
16
+ }
17
+
18
+ const exitEditMode = () => setEditedGroupId('')
19
+
20
+ const handleKeyDown = async e => {
21
+ const ENTER_KEY_CODE = 13
22
+ if (e.keyCode == ENTER_KEY_CODE) {
23
+ exitEditMode()
24
+ renameGroup(groupId, inputRef.current.value)
25
+ }
26
+ stopPropagation(e)
27
+ }
28
+
29
+ return (
30
+ <Input
31
+ id="editGroupInput"
32
+ ref={inputRef}
33
+ type="text"
34
+ defaultValue={groupName}
35
+ size="tiny"
36
+ autoComplete="off"
37
+ autoFocus={true}
38
+ onKeyDown={handleKeyDown}
39
+ onClick={stopPropagation}
40
+ onFocus={stopPropagation}
41
+ onMouseDown={stopPropagation}
42
+ onBlur={exitEditMode}
43
+ />
44
+ )
45
+ }
46
+
47
+ Option.propTypes = {
48
+ groupName: PropTypes.string.isRequired,
49
+ setEditedGroupId: PropTypes.string.isRequired
50
+ }
51
+
52
+ export default EditGroupName
@@ -0,0 +1,26 @@
1
+ import cx from 'classnames'
2
+ import React from 'react'
3
+
4
+ import styles from './styles.styl'
5
+ import { components } from '../../../SelectBox'
6
+ import Typography from '../../../Typography'
7
+ import GroupCreation from '../GroupCreation'
8
+
9
+ const Menu = ({ children, selectProps, className, ...props }) => {
10
+ const { createGroup, ...restSelectProps } = selectProps
11
+
12
+ return (
13
+ <components.Menu
14
+ {...props}
15
+ className={cx(className, styles['menu'])}
16
+ selectProps={restSelectProps}
17
+ >
18
+ <Typography variant="body1" component="div">
19
+ {children}
20
+ <GroupCreation createGroup={createGroup} />
21
+ </Typography>
22
+ </components.Menu>
23
+ )
24
+ }
25
+
26
+ export default Menu