Skip to content

Commit 41892e8

Browse files
committed
feat: Add Contacts/Header and Contacts/GroupsSelect components
from cozy-contacts, to be used in admin-panel
1 parent ed31828 commit 41892e8

27 files changed

+1296
-3
lines changed

docs/styleguide.config.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,22 @@ module.exports = {
7272
'../react/Markdown'
7373
]
7474
},
75+
{
76+
name: 'Contacts',
77+
components: () => [
78+
'../react/ContactsList',
79+
'../react/ContactsListModal',
80+
'../react/ContactPicker',
81+
'../react/Contacts/Header'
82+
]
83+
},
7584
{
7685
name: 'Extra',
7786
components: () => [
7887
'../react/ActionsBar',
7988
'../react/ActionsMenu',
8089
'../react/AppSections',
8190
'../react/BottomSheet',
82-
'../react/ContactsList',
83-
'../react/ContactsListModal',
84-
'../react/ContactPicker',
8591
'../react/CozyDialogs',
8692
'../react/CozyDialogs/SpecificDialogs',
8793
'../react/DatePicker',
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)