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.
- package/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/react/Contacts/GroupsSelect/GroupCreation.jsx +95 -0
- package/react/Contacts/GroupsSelect/GroupsSelect.jsx +159 -0
- package/react/Contacts/GroupsSelect/GroupsSelect.spec.jsx +248 -0
- package/react/Contacts/GroupsSelect/GroupsSelectProvider.jsx +39 -0
- package/react/Contacts/GroupsSelect/Readme.md +1 -0
- package/react/Contacts/GroupsSelect/SelectBox/Control.jsx +36 -0
- package/react/Contacts/GroupsSelect/SelectBox/EditGroupName.jsx +52 -0
- package/react/Contacts/GroupsSelect/SelectBox/Menu.jsx +26 -0
- package/react/Contacts/GroupsSelect/SelectBox/Option.jsx +67 -0
- package/react/Contacts/GroupsSelect/SelectBox/SelectContainer.jsx +15 -0
- package/react/Contacts/GroupsSelect/SelectBox/styles.styl +2 -0
- package/react/Contacts/GroupsSelect/helpers.js +25 -0
- package/react/Contacts/GroupsSelect/locales/en.json +21 -0
- package/react/Contacts/GroupsSelect/locales/fr.json +21 -0
- package/react/Contacts/GroupsSelect/locales/index.jsx +12 -0
- package/react/Contacts/GroupsSelect/styles.styl +47 -0
- package/react/Contacts/GroupsSelect/useGroupSelect.jsx +88 -0
- package/react/Contacts/Header/GroupsSelection.jsx +74 -0
- package/react/Contacts/Header/ImportDropdown.jsx +78 -0
- package/react/Contacts/Header/Readme.md +40 -0
- package/react/Contacts/Header/SearchInput.jsx +19 -0
- package/react/Contacts/Header/index.jsx +79 -0
- package/react/Contacts/Header/locales/en.json +15 -0
- package/react/Contacts/Header/locales/fr.json +15 -0
- package/react/Contacts/Header/locales/index.jsx +12 -0
- package/react/Contacts/Readme.md +1 -0
- package/react/ContactsList/ContactCell.jsx +58 -0
- package/react/ContactsList/helpers.js +50 -1
- package/react/ContactsList/helpers.spec.js +50 -1
- package/react/ContactsList/locales/en.json +3 -1
- package/react/ContactsList/locales/fr.json +3 -1
- package/react/providers/Selection/Readme.md +28 -0
- package/react/providers/Selection/index.jsx +85 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupCreation.d.ts +3 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupCreation.js +133 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.d.ts +56 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.js +181 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupsSelect.spec.d.ts +22 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupsSelectProvider.d.ts +5 -0
- package/transpiled/react/Contacts/GroupsSelect/GroupsSelectProvider.js +38 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Control.d.ts +14 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Control.js +37 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/EditGroupName.d.ts +7 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/EditGroupName.js +71 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Menu.d.ts +7 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Menu.js +34 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Option.d.ts +18 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/Option.js +66 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/SelectContainer.d.ts +2 -0
- package/transpiled/react/Contacts/GroupsSelect/SelectBox/SelectContainer.js +12 -0
- package/transpiled/react/Contacts/GroupsSelect/helpers.d.ts +9 -0
- package/transpiled/react/Contacts/GroupsSelect/helpers.js +30 -0
- package/transpiled/react/Contacts/GroupsSelect/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/GroupsSelect/locales/index.js +49 -0
- package/transpiled/react/Contacts/GroupsSelect/useGroupSelect.d.ts +11 -0
- package/transpiled/react/Contacts/GroupsSelect/useGroupSelect.js +179 -0
- package/transpiled/react/Contacts/Header/GroupsSelection.d.ts +7 -0
- package/transpiled/react/Contacts/Header/GroupsSelection.js +84 -0
- package/transpiled/react/Contacts/Header/ImportDropdown.d.ts +4 -0
- package/transpiled/react/Contacts/Header/ImportDropdown.js +88 -0
- package/transpiled/react/Contacts/Header/SearchInput.d.ts +4 -0
- package/transpiled/react/Contacts/Header/SearchInput.js +24 -0
- package/transpiled/react/Contacts/Header/index.d.ts +26 -0
- package/transpiled/react/Contacts/Header/index.js +72 -0
- package/transpiled/react/Contacts/Header/locales/index.d.ts +6 -0
- package/transpiled/react/Contacts/Header/locales/index.js +37 -0
- package/transpiled/react/ContactsList/ContactCell.d.ts +8 -0
- package/transpiled/react/ContactsList/ContactCell.js +68 -0
- package/transpiled/react/ContactsList/helpers.d.ts +1 -0
- package/transpiled/react/ContactsList/helpers.js +51 -1
- package/transpiled/react/ContactsList/locales/withContactsListLocales.js +6 -2
- package/transpiled/react/providers/Selection/index.d.ts +6 -0
- package/transpiled/react/providers/Selection/index.js +99 -0
- 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
|
@@ -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
|