cozy-ui 63.0.0 → 64.0.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 (29) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +1 -1
  3. package/react/ContactsListModal/AddContact/AddContactActions.jsx +15 -0
  4. package/react/ContactsListModal/AddContact/AddContactContent.jsx +52 -0
  5. package/react/ContactsListModal/AddContact/AddContactDialog.jsx +38 -0
  6. package/react/ContactsListModal/AddContact/AddContactTitle.jsx +9 -0
  7. package/react/ContactsListModal/AddContact/helpers.js +18 -0
  8. package/react/ContactsListModal/AddContact/helpers.spec.js +74 -0
  9. package/react/ContactsListModal/AddContact/styles.styl +2 -0
  10. package/react/ContactsListModal/ContactsListContent.jsx +69 -0
  11. package/react/ContactsListModal/DemoProvider.jsx +2 -0
  12. package/react/ContactsListModal/MobileHeader.jsx +40 -0
  13. package/react/ContactsListModal/index.jsx +60 -124
  14. package/react/ContactsListModal/locales/en.json +8 -0
  15. package/react/ContactsListModal/locales/fr.json +8 -0
  16. package/react/ContactsListModal/withContactsListLocales.jsx +11 -0
  17. package/transpiled/react/ContactsListModal/AddContact/AddContactActions.js +19 -0
  18. package/transpiled/react/ContactsListModal/AddContact/AddContactContent.js +58 -0
  19. package/transpiled/react/ContactsListModal/AddContact/AddContactDialog.js +42 -0
  20. package/transpiled/react/ContactsListModal/AddContact/AddContactTitle.js +9 -0
  21. package/transpiled/react/ContactsListModal/AddContact/helpers.js +48 -0
  22. package/transpiled/react/ContactsListModal/ContactsListContent.js +64 -0
  23. package/transpiled/react/ContactsListModal/DemoProvider.js +6 -0
  24. package/transpiled/react/ContactsListModal/MobileHeader.js +40 -0
  25. package/transpiled/react/ContactsListModal/index.js +65 -109
  26. package/transpiled/react/ContactsListModal/withContactsListLocales.js +22 -0
  27. package/transpiled/react/stylesheet.css +1 -1
  28. package/react/ContactsListModal/AddContactButton.jsx +0 -72
  29. package/transpiled/react/ContactsListModal/AddContactButton.js +0 -67
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [64.0.0](https://github.com/cozy/cozy-ui/compare/v63.0.0...v64.0.0) (2022-04-26)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add AddContactDialog to add a contact from the contact list ([7333bf9](https://github.com/cozy/cozy-ui/commit/7333bf9))
7
+ * Add ContactsList locales and HOC to get them ([e315f40](https://github.com/cozy/cozy-ui/commit/e315f40))
8
+
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * the app must now have `POST` permission on `io.cozy.contacts` doctype to use `ContactsListModal`
13
+
1
14
  # [63.0.0](https://github.com/cozy/cozy-ui/compare/v62.12.0...v63.0.0) (2022-04-26)
2
15
 
3
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "63.0.0",
3
+ "version": "64.0.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -0,0 +1,15 @@
1
+ import React from 'react'
2
+
3
+ import Button from '../../Buttons'
4
+ import { withContactsListLocales } from '../withContactsListLocales'
5
+
6
+ const AddContactActions = ({ t, onCancel, onSave }) => {
7
+ return (
8
+ <>
9
+ <Button variant="secondary" label={t('cancel')} onClick={onCancel} />
10
+ <Button label={t('save')} onClick={onSave} />
11
+ </>
12
+ )
13
+ }
14
+
15
+ export default withContactsListLocales(AddContactActions)
@@ -0,0 +1,52 @@
1
+ import { Typography } from '@material-ui/core'
2
+ import React from 'react'
3
+
4
+ import { Media, Img, Bd } from '../../Media'
5
+ import Icon from '../../Icon'
6
+ import PeopleIcon from 'cozy-ui/transpiled/react/Icons/People'
7
+ import TextField from '../../MuiCozyTheme/TextField'
8
+
9
+ import { withContactsListLocales } from '../withContactsListLocales'
10
+ import styles from './styles.styl'
11
+
12
+ const AddContactContent = ({ t, setContactValues }) => {
13
+ const handleChange = ev => {
14
+ const { name, value } = ev.target
15
+ setContactValues(v => ({ ...v, [name]: value }))
16
+ }
17
+
18
+ return (
19
+ <>
20
+ <Typography variant="h5">{t('coordinates')}</Typography>
21
+ <Media>
22
+ <Img className={styles.icon}>
23
+ <Icon icon={PeopleIcon} />
24
+ </Img>
25
+ <Bd className="u-mr-1">
26
+ <TextField
27
+ className="u-mt-1"
28
+ variant="outlined"
29
+ fullWidth={true}
30
+ name="givenName"
31
+ label={t('givenName')}
32
+ onChange={handleChange}
33
+ />
34
+ </Bd>
35
+ </Media>
36
+ <Media>
37
+ <Bd className="u-ml-3 u-mr-1">
38
+ <TextField
39
+ className="u-mt-1"
40
+ variant="outlined"
41
+ fullWidth={true}
42
+ name="familyName"
43
+ label={t('familyName')}
44
+ onChange={handleChange}
45
+ />
46
+ </Bd>
47
+ </Media>
48
+ </>
49
+ )
50
+ }
51
+
52
+ export default withContactsListLocales(AddContactContent)
@@ -0,0 +1,38 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { useClient } from 'cozy-client'
4
+
5
+ import { FixedDialog } from '../../CozyDialogs'
6
+ import AddContactTitle from './AddContactTitle'
7
+ import AddContactContent from './AddContactContent'
8
+ import AddContactActions from './AddContactActions'
9
+ import { handleSubmit } from './helpers'
10
+
11
+ const AddContactDialog = ({ onListClose, onCreate, onClose }) => {
12
+ const [contactValues, setContactValues] = useState({})
13
+ const client = useClient()
14
+
15
+ return (
16
+ <FixedDialog
17
+ open={true}
18
+ onClose={onClose}
19
+ title={<AddContactTitle />}
20
+ content={<AddContactContent setContactValues={setContactValues} />}
21
+ actions={
22
+ <AddContactActions
23
+ onCancel={onClose}
24
+ onSave={() =>
25
+ handleSubmit({
26
+ client,
27
+ contactValues,
28
+ onCreate,
29
+ onListClose
30
+ })
31
+ }
32
+ />
33
+ }
34
+ />
35
+ )
36
+ }
37
+
38
+ export default AddContactDialog
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+
3
+ import { withContactsListLocales } from '../withContactsListLocales'
4
+
5
+ const AddContactTitle = ({ t }) => {
6
+ return <>{t('newContact')}</>
7
+ }
8
+
9
+ export default withContactsListLocales(AddContactTitle)
@@ -0,0 +1,18 @@
1
+ export const handleSubmit = async ({
2
+ client,
3
+ contactValues,
4
+ onCreate,
5
+ onListClose
6
+ }) => {
7
+ const { givenName, familyName } = contactValues
8
+
9
+ if (!givenName && !familyName) return
10
+
11
+ const { data: contact } = await client.save({
12
+ _type: 'io.cozy.contacts',
13
+ name: { familyName, givenName }
14
+ })
15
+
16
+ onCreate(contact)
17
+ onListClose()
18
+ }
@@ -0,0 +1,74 @@
1
+ import { createMockClient } from 'cozy-client'
2
+
3
+ import { handleSubmit } from './helpers'
4
+
5
+ const client = createMockClient({})
6
+ client.save = jest.fn(x => ({ data: x }))
7
+ const onCreate = jest.fn()
8
+ const onListClose = jest.fn()
9
+
10
+ describe('handleSubmit', () => {
11
+ it('should not create the contact if no givenName of familyName', async () => {
12
+ await handleSubmit({ contactValues: {}, client, onCreate, onListClose })
13
+
14
+ expect(onCreate).not.toHaveBeenCalled()
15
+ expect(onListClose).not.toHaveBeenCalled()
16
+ })
17
+
18
+ it('should create the contact if there is only the givenName', async () => {
19
+ await handleSubmit({
20
+ contactValues: { givenName: 'John' },
21
+ client,
22
+ onCreate,
23
+ onListClose
24
+ })
25
+
26
+ expect(client.save).toHaveBeenCalled()
27
+ expect(onCreate).toHaveBeenCalledWith({
28
+ _type: 'io.cozy.contacts',
29
+ name: {
30
+ familyName: undefined,
31
+ givenName: 'John'
32
+ }
33
+ })
34
+ expect(onListClose).toHaveBeenCalled()
35
+ })
36
+
37
+ it('should create the contact if there is only the familyName', async () => {
38
+ await handleSubmit({
39
+ contactValues: { familyName: 'Connor' },
40
+ client,
41
+ onCreate,
42
+ onListClose
43
+ })
44
+
45
+ expect(client.save).toHaveBeenCalled()
46
+ expect(onCreate).toHaveBeenCalledWith({
47
+ _type: 'io.cozy.contacts',
48
+ name: {
49
+ familyName: 'Connor',
50
+ givenName: undefined
51
+ }
52
+ })
53
+ expect(onListClose).toHaveBeenCalled()
54
+ })
55
+
56
+ it('should create the contact if there is the givenName and familyName', async () => {
57
+ await handleSubmit({
58
+ contactValues: { givenName: 'John', familyName: 'Connor' },
59
+ client,
60
+ onCreate,
61
+ onListClose
62
+ })
63
+
64
+ expect(client.save).toHaveBeenCalled()
65
+ expect(onCreate).toHaveBeenCalledWith({
66
+ _type: 'io.cozy.contacts',
67
+ name: {
68
+ familyName: 'Connor',
69
+ givenName: 'John'
70
+ }
71
+ })
72
+ expect(onListClose).toHaveBeenCalled()
73
+ })
74
+ })
@@ -0,0 +1,2 @@
1
+ .icon
2
+ margin 1rem 1.5rem 0 0.5rem
@@ -0,0 +1,69 @@
1
+ import React from 'react'
2
+
3
+ import { Contact } from 'cozy-doctypes'
4
+
5
+ import ContactsList from '../ContactsList'
6
+ import Spinner from '../Spinner'
7
+ import EmptyMessage from './EmptyMessage'
8
+
9
+ const mkFilter = filterStr => contacts => {
10
+ if (!filterStr) {
11
+ return contacts
12
+ }
13
+
14
+ const f = filterStr.toLowerCase()
15
+
16
+ // TODO better filtering methods can be extracted from drive. See https://github.com/cozy/cozy-ui/pull/1273#discussion_r351845385
17
+ return contacts.filter(contact => {
18
+ const displayName = Contact.getDisplayName(contact)
19
+
20
+ if (!displayName) {
21
+ return false
22
+ }
23
+
24
+ return displayName.toLowerCase().includes(f)
25
+ })
26
+ }
27
+
28
+ const ContactsListContent = ({
29
+ filter,
30
+ contacts,
31
+ emptyMessage,
32
+ onItemClick,
33
+ dismissAction
34
+ }) => {
35
+ const loading =
36
+ (contacts.fetchStatus === 'loading' ||
37
+ contacts.fetchStatus === 'pending') &&
38
+ !contacts.lastFetch
39
+
40
+ const filterContacts = mkFilter(filter)
41
+ const filteredContacts = filterContacts(contacts.data)
42
+
43
+ const handleItemClick = contact => {
44
+ if (!onItemClick) {
45
+ return
46
+ }
47
+
48
+ onItemClick(contact)
49
+ dismissAction()
50
+ }
51
+
52
+ if (loading) {
53
+ return (
54
+ <div className="u-mv-2">
55
+ <Spinner size="xxlarge" />
56
+ </div>
57
+ )
58
+ }
59
+
60
+ if (filteredContacts.length === 0) {
61
+ return <EmptyMessage>{emptyMessage}</EmptyMessage>
62
+ }
63
+
64
+ return (
65
+ <ContactsList contacts={filteredContacts} onItemClick={handleItemClick} />
66
+ )
67
+ }
68
+
69
+ export default ContactsListContent
@@ -42,6 +42,8 @@ mockClient.plugins = {
42
42
  }
43
43
  }
44
44
 
45
+ mockClient.save = () => ({ data: contacts[0] })
46
+
45
47
  const Wrapper = ({ children }) => {
46
48
  return <CozyProvider client={mockClient}>{children}</CozyProvider>
47
49
  }
@@ -0,0 +1,40 @@
1
+ import React from 'react'
2
+
3
+ import CozyTheme from '../CozyTheme'
4
+ import Icon from '../Icon'
5
+ import PreviousIcon from '../Icons/Previous'
6
+ import Paper from '../Paper'
7
+ import IconButton from '@material-ui/core/IconButton'
8
+ import Input from '@material-ui/core/Input'
9
+
10
+ const barStyle = {
11
+ height: 48
12
+ }
13
+
14
+ const MobileHeader = ({ filter, placeholder, onChange, onDismiss }) => {
15
+ return (
16
+ <CozyTheme variant="inverted">
17
+ <Paper
18
+ square
19
+ elevation={0}
20
+ className="u-flex u-flex-items-center u-pr-3 u-pl-half"
21
+ style={barStyle}
22
+ >
23
+ <IconButton className="u-mr-half" onClick={onDismiss}>
24
+ <Icon icon={PreviousIcon} />
25
+ </IconButton>
26
+ <Input
27
+ type="text"
28
+ placeholder={placeholder}
29
+ value={filter}
30
+ onChange={onChange}
31
+ autoFocus
32
+ fullWidth
33
+ disableUnderline
34
+ />
35
+ </Paper>
36
+ </CozyTheme>
37
+ )
38
+ }
39
+
40
+ export default MobileHeader
@@ -1,14 +1,11 @@
1
1
  import React, { useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import compose from 'lodash/flowRight'
4
- import IconButton from '@material-ui/core/IconButton'
5
- import Input from '@material-ui/core/Input'
3
+ import cx from 'classnames'
6
4
  import TextField from '@material-ui/core/TextField'
7
5
 
8
- import { Contact } from 'cozy-doctypes'
9
- import { Q, withClient, fetchPolicies, queryConnect } from 'cozy-client'
6
+ import { Q, fetchPolicies, useClient, useQuery } from 'cozy-client'
7
+
10
8
  import { DialogTitle, DialogContent } from '../Dialog'
11
- import Paper from '../Paper'
12
9
  import CozyTheme from '../CozyTheme'
13
10
  import {
14
11
  TopAnchoredDialog,
@@ -17,80 +14,44 @@ import {
17
14
  } from '../CozyDialogs'
18
15
  import useRealtime from '../hooks/useRealtime'
19
16
  import useEventListener from '../hooks/useEventListener.js'
20
- import PreviousIcon from '../Icons/Previous'
21
17
  import useBreakpoints from '../hooks/useBreakpoints'
22
-
23
- import ContactsList from '../ContactsList'
24
- import Spinner from '../Spinner'
25
- import styles from './styles.styl'
18
+ import Button from '../Buttons'
19
+ import PlusIcon from '../Icons/Plus'
26
20
  import Icon from '../Icon'
27
- import AddContactButton from './AddContactButton'
28
- import EmptyMessage from './EmptyMessage'
21
+ import MobileHeader from './MobileHeader'
22
+ import ContactsListContent from './ContactsListContent'
23
+ import AddContactDialog from './AddContact/AddContactDialog'
29
24
 
30
- import cx from 'classnames'
25
+ import styles from './styles.styl'
31
26
 
32
27
  const thirtySeconds = 30000
33
28
  const olderThan30s = fetchPolicies.olderThan(thirtySeconds)
34
29
 
35
- const mkFilter = filterStr => contacts => {
36
- if (!filterStr) {
37
- return contacts
30
+ const contactsQuery = {
31
+ definition: Q('io.cozy.contacts').UNSAFE_noLimit(),
32
+ options: {
33
+ as: 'contacts',
34
+ fetchPolicy: olderThan30s
38
35
  }
39
-
40
- const f = filterStr.toLowerCase()
41
-
42
- // TODO better filtering methods can be extracted from drive. See https://github.com/cozy/cozy-ui/pull/1273#discussion_r351845385
43
- return contacts.filter(contact => {
44
- const displayName = Contact.getDisplayName(contact)
45
-
46
- if (!displayName) {
47
- return false
48
- }
49
-
50
- return displayName.toLowerCase().includes(f)
51
- })
52
36
  }
53
37
 
54
- const barStyle = {
55
- height: 48
56
- }
57
-
58
- const ContactsListModal = props => {
59
- const { isMobile } = useBreakpoints()
60
- const {
61
- onItemClick,
62
- placeholder,
63
- addContactLabel,
64
- emptyMessage,
65
- contacts,
66
- client,
67
- ...rest
68
- } = props
69
-
38
+ const ContactsListModal = ({
39
+ onItemClick,
40
+ placeholder,
41
+ addContactLabel,
42
+ emptyMessage,
43
+ dismissAction
44
+ }) => {
70
45
  const [filter, setFilter] = useState('')
71
-
72
- const handleFilterChange = e => {
73
- setFilter(e.target.value)
74
- }
75
-
76
- const filterContacts = mkFilter(filter)
77
-
78
- const handleItemClick = contact => {
79
- if (!onItemClick) {
80
- return
81
- }
82
-
83
- onItemClick(contact)
84
- rest.dismissAction()
85
- }
86
-
87
- const loading =
88
- (contacts.fetchStatus === 'loading' ||
89
- contacts.fetchStatus === 'pending') &&
90
- !contacts.lastFetch
91
-
92
- const filteredContacts = filterContacts(contacts.data)
93
-
46
+ const [showAddDialog, setShowAddDialog] = useState(false)
47
+ const { isMobile } = useBreakpoints()
48
+ const { dialogProps, dialogTitleProps } = useCozyDialog({
49
+ size: 'large',
50
+ open: true,
51
+ onClose: dismissAction
52
+ })
53
+ const client = useClient()
54
+ const contacts = useQuery(contactsQuery.definition, contactsQuery.options)
94
55
  useRealtime(
95
56
  client,
96
57
  {
@@ -101,19 +62,16 @@ const ContactsListModal = props => {
101
62
  },
102
63
  []
103
64
  )
104
-
105
65
  useEventListener(document, 'resume', contacts.fetch)
106
66
 
107
- const { dialogProps, dialogTitleProps } = useCozyDialog({
108
- size: 'large',
109
- open: true,
110
- onClose: props.dismissAction
111
- })
67
+ const handleFilterChange = e => {
68
+ setFilter(e.target.value)
69
+ }
112
70
 
113
71
  return (
114
72
  <TopAnchoredDialog {...dialogProps}>
115
73
  <CozyTheme variant={isMobile ? 'inverted' : 'normal'}>
116
- <DialogCloseButton onClick={props.dismissAction} />
74
+ <DialogCloseButton onClick={dismissAction} />
117
75
  </CozyTheme>
118
76
  <DialogTitle
119
77
  {...dialogTitleProps}
@@ -122,29 +80,12 @@ const ContactsListModal = props => {
122
80
  })}
123
81
  >
124
82
  {isMobile ? (
125
- <>
126
- <CozyTheme variant="inverted">
127
- <Paper
128
- square
129
- elevation={0}
130
- className="u-flex u-flex-items-center u-pr-3 u-pl-half"
131
- style={barStyle}
132
- >
133
- <IconButton className="u-mr-half" onClick={rest.dismissAction}>
134
- <Icon icon={PreviousIcon} />
135
- </IconButton>
136
- <Input
137
- type="text"
138
- placeholder={placeholder}
139
- value={filter}
140
- onChange={handleFilterChange}
141
- autoFocus
142
- fullWidth
143
- disableUnderline
144
- />
145
- </Paper>
146
- </CozyTheme>
147
- </>
83
+ <MobileHeader
84
+ filter={filter}
85
+ placeholder={placeholder}
86
+ onChange={handleFilterChange}
87
+ onDismiss={dismissAction}
88
+ />
148
89
  ) : (
149
90
  <TextField
150
91
  variant="outlined"
@@ -160,27 +101,31 @@ const ContactsListModal = props => {
160
101
  <DialogContent className="u-p-0">
161
102
  <div className="dialogContentInner">
162
103
  <div className={styles.ContactsListModal__addContactContainer}>
163
- <AddContactButton
104
+ <Button
164
105
  className={isMobile && 'u-mt-1'}
106
+ variant="secondary"
107
+ theme="secondary"
165
108
  label={addContactLabel}
109
+ startIcon={<Icon icon={PlusIcon} />}
110
+ onClick={setShowAddDialog}
166
111
  />
167
112
  </div>
168
- {loading && (
169
- <div className="u-mv-2">
170
- <Spinner size="xxlarge" />
171
- </div>
172
- )}
173
- {!loading && filteredContacts.length === 0 && (
174
- <EmptyMessage>{emptyMessage}</EmptyMessage>
175
- )}
176
- {!loading && filteredContacts.length > 0 && (
177
- <ContactsList
178
- contacts={filteredContacts}
179
- onItemClick={handleItemClick}
180
- />
181
- )}
113
+ <ContactsListContent
114
+ filter={filter}
115
+ contacts={contacts}
116
+ onItemClick={onItemClick}
117
+ emptyMessage={emptyMessage}
118
+ dismissAction={dismissAction}
119
+ />
182
120
  </div>
183
121
  </DialogContent>
122
+ {showAddDialog && (
123
+ <AddContactDialog
124
+ onListClose={dismissAction}
125
+ onCreate={onItemClick}
126
+ onClose={() => setShowAddDialog(false)}
127
+ />
128
+ )}
184
129
  </TopAnchoredDialog>
185
130
  )
186
131
  }
@@ -189,13 +134,4 @@ ContactsListModal.propTypes = {
189
134
  onItemClick: PropTypes.func
190
135
  }
191
136
 
192
- export default compose(
193
- withClient,
194
- queryConnect({
195
- contacts: {
196
- query: () => Q('io.cozy.contacts').UNSAFE_noLimit(),
197
- as: 'contacts',
198
- fetchPolicy: olderThan30s
199
- }
200
- })
201
- )(ContactsListModal)
137
+ export default ContactsListModal
@@ -0,0 +1,8 @@
1
+ {
2
+ "cancel": "Cancel",
3
+ "save": "Save",
4
+ "newContact": "New contact",
5
+ "coordinates": "Coordinates",
6
+ "givenName": "Firstname",
7
+ "familyName": "Lastname"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "cancel": "Annuler",
3
+ "save": "Enregistrer",
4
+ "newContact": "Nouveau contact",
5
+ "coordinates": "Coordonnées",
6
+ "givenName": "Prénom",
7
+ "familyName": "Nom"
8
+ }
@@ -0,0 +1,11 @@
1
+ import withLocales from '../I18n/withLocales'
2
+
3
+ import en from './locales/en.json'
4
+ import fr from './locales/fr.json'
5
+
6
+ export const locales = {
7
+ en,
8
+ fr
9
+ }
10
+
11
+ export const withContactsListLocales = withLocales(locales)
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import Button from "cozy-ui/transpiled/react/Buttons";
3
+ import { withContactsListLocales } from "cozy-ui/transpiled/react/ContactsListModal/withContactsListLocales";
4
+
5
+ var AddContactActions = function AddContactActions(_ref) {
6
+ var t = _ref.t,
7
+ onCancel = _ref.onCancel,
8
+ onSave = _ref.onSave;
9
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
10
+ variant: "secondary",
11
+ label: t('cancel'),
12
+ onClick: onCancel
13
+ }), /*#__PURE__*/React.createElement(Button, {
14
+ label: t('save'),
15
+ onClick: onSave
16
+ }));
17
+ };
18
+
19
+ export default withContactsListLocales(AddContactActions);