cozy-ui 127.10.1 → 127.11.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 +9 -0
- package/package.json +1 -1
- 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/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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# [127.11.0](https://github.com/cozy/cozy-ui/compare/v127.10.1...v127.11.0) (2025-08-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **ContactsList:** Add ContactCell component to show a contact in table ([234bfe2](https://github.com/cozy/cozy-ui/commit/234bfe2))
|
|
7
|
+
* **ContactsList:** Add makeGroupLabelsAndCounts function ([b628571](https://github.com/cozy/cozy-ui/commit/b628571))
|
|
8
|
+
* **Providers:** Add Selection provider ([b9854b6](https://github.com/cozy/cozy-ui/commit/b9854b6))
|
|
9
|
+
|
|
1
10
|
## [127.10.1](https://github.com/cozy/cozy-ui/compare/v127.10.0...v127.10.1) (2025-08-18)
|
|
2
11
|
|
|
3
12
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import ContactIdentity from './Contacts/ContactIdentity'
|
|
4
|
+
import { locales } from './locales/withContactsListLocales'
|
|
5
|
+
import ActionsMenu from '../ActionsMenu'
|
|
6
|
+
import Icon from '../Icon'
|
|
7
|
+
import IconButton from '../IconButton'
|
|
8
|
+
import DotsIcon from '../Icons/Dots'
|
|
9
|
+
import ListItemIcon from '../ListItemIcon'
|
|
10
|
+
import { useI18n, useExtendI18n } from '../providers/I18n'
|
|
11
|
+
|
|
12
|
+
const Cell = ({ row, column, cell, actions }) => {
|
|
13
|
+
const [showActions, setShowActions] = useState(false)
|
|
14
|
+
useExtendI18n(locales)
|
|
15
|
+
const { t } = useI18n()
|
|
16
|
+
const actionsRef = useRef()
|
|
17
|
+
|
|
18
|
+
if (column.id === 'fullname') {
|
|
19
|
+
return <ContactIdentity contact={row} noWrapper />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (column.id === 'actions') {
|
|
23
|
+
if (!actions) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<ListItemIcon>
|
|
30
|
+
<IconButton
|
|
31
|
+
ref={actionsRef}
|
|
32
|
+
arial-label={t('menu')}
|
|
33
|
+
onClick={() => setShowActions(true)}
|
|
34
|
+
>
|
|
35
|
+
<Icon icon={DotsIcon} />
|
|
36
|
+
</IconButton>
|
|
37
|
+
</ListItemIcon>
|
|
38
|
+
{showActions && (
|
|
39
|
+
<ActionsMenu
|
|
40
|
+
open
|
|
41
|
+
ref={actionsRef}
|
|
42
|
+
docs={[row]}
|
|
43
|
+
actions={actions}
|
|
44
|
+
anchorOrigin={{
|
|
45
|
+
vertical: 'bottom',
|
|
46
|
+
horizontal: 'right'
|
|
47
|
+
}}
|
|
48
|
+
onClose={() => setShowActions(false)}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cell
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default React.memo(Cell)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import get from 'lodash/get'
|
|
2
|
+
|
|
1
3
|
export function buildLastNameFirst(contact) {
|
|
2
4
|
const givenName =
|
|
3
5
|
contact.name && contact.name.givenName
|
|
@@ -36,15 +38,39 @@ const makeHeader = (contact, t) => {
|
|
|
36
38
|
return name[0] || t('empty')
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Build header for a contact (first letter of indexes.byFamilyNameGivenNameEmailCozyUrl)
|
|
43
|
+
* @param {object} contact
|
|
44
|
+
* @param {function} t translation function
|
|
45
|
+
* @returns {string} header
|
|
46
|
+
*/
|
|
47
|
+
const makeHeaderForIndexedContacts = (contact, t) => {
|
|
48
|
+
if (contact.me) return t('me')
|
|
49
|
+
if (contact.cozyMetadata?.favorite) return t('favorite')
|
|
50
|
+
|
|
51
|
+
const index = get(contact, 'indexes.byFamilyNameGivenNameEmailCozyUrl', '')
|
|
52
|
+
const hasIndex = index !== null && index.length > 0
|
|
53
|
+
|
|
54
|
+
if (hasIndex) {
|
|
55
|
+
const firstLetterWithoutAccent = index[0]
|
|
56
|
+
.normalize('NFD')
|
|
57
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
58
|
+
return firstLetterWithoutAccent
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return t('empty')
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
/**
|
|
40
65
|
* @typedef {Object.<string, Object>} CategorizedContactsResult
|
|
41
66
|
*/
|
|
42
67
|
|
|
43
68
|
/**
|
|
44
69
|
* Categorize contacts by first letter of last name
|
|
70
|
+
* Expl.: all contacts with A as first letter will be in A category
|
|
45
71
|
* @param {object[]} contacts io.cozy.contacts documents
|
|
46
72
|
* @param {function} t translation function
|
|
47
|
-
* @returns {CategorizedContactsResult}
|
|
73
|
+
* @returns {CategorizedContactsResult} Categorized contacts
|
|
48
74
|
*/
|
|
49
75
|
export const categorizeContacts = (contacts, t) =>
|
|
50
76
|
contacts.reduce((acc, contact) => {
|
|
@@ -81,3 +107,26 @@ export const sortHeaders = (categorized, t) => {
|
|
|
81
107
|
|
|
82
108
|
return headersSorted.concat(notEmptyAndMyselfSorted)
|
|
83
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Counts how many contacts are categorized by first letter and store it in `groupCounts`
|
|
113
|
+
* Expl.: if there is 3 contacts in A and 2 in B, it will return [3,2]
|
|
114
|
+
* Also store first letters and store them in `groupLabels`
|
|
115
|
+
* @param {array} contacts - Array of io.cozy.contact documents
|
|
116
|
+
* @returns {object}
|
|
117
|
+
*/
|
|
118
|
+
export const makeGroupLabelsAndCounts = (contacts, t) => {
|
|
119
|
+
return contacts.reduce(
|
|
120
|
+
(acc, contact) => {
|
|
121
|
+
const header = makeHeaderForIndexedContacts(contact, t)
|
|
122
|
+
if (!acc.groupLabels.includes(header)) {
|
|
123
|
+
acc.groupLabels.push(header)
|
|
124
|
+
}
|
|
125
|
+
const idx = acc.groupLabels.indexOf(header)
|
|
126
|
+
const val = acc.groupCounts[idx] || 0
|
|
127
|
+
acc.groupCounts[idx] = val + 1
|
|
128
|
+
return acc
|
|
129
|
+
},
|
|
130
|
+
{ groupLabels: [], groupCounts: [] }
|
|
131
|
+
)
|
|
132
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
sortLastNameFirst,
|
|
3
|
+
sortHeaders,
|
|
4
|
+
makeGroupLabelsAndCounts
|
|
5
|
+
} from './helpers'
|
|
6
|
+
|
|
7
|
+
const t = x => x
|
|
8
|
+
|
|
2
9
|
describe('Sort contacts', () => {
|
|
3
10
|
describe('By Last Name', () => {
|
|
4
11
|
it('should sort contact by last name', () => {
|
|
@@ -99,3 +106,45 @@ describe('sortHeaders', () => {
|
|
|
99
106
|
expect(sortedHeaders).toEqual(['me', 'empty', 'B', 'F', 'H'])
|
|
100
107
|
})
|
|
101
108
|
})
|
|
109
|
+
|
|
110
|
+
describe('makeGroupLabelsAndCounts', () => {
|
|
111
|
+
it('should returns labels and counts', () => {
|
|
112
|
+
const contacts = [
|
|
113
|
+
{ name: 'Alex', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'A' } },
|
|
114
|
+
{ name: 'Alan', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'A' } },
|
|
115
|
+
{ name: 'Cleo', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'C' } },
|
|
116
|
+
{ name: 'Cloé', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'C' } },
|
|
117
|
+
{ name: 'Clotilde', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'C' } },
|
|
118
|
+
{ name: 'Constant', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'C' } },
|
|
119
|
+
{
|
|
120
|
+
name: 'Christophe',
|
|
121
|
+
indexes: { byFamilyNameGivenNameEmailCozyUrl: 'C' }
|
|
122
|
+
},
|
|
123
|
+
{ name: 'Bernard', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'B' } },
|
|
124
|
+
{ name: 'Baptiste', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'B' } },
|
|
125
|
+
{ name: 'Xavier', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'X' } },
|
|
126
|
+
{ name: 'Zorro', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'Z' } },
|
|
127
|
+
{ name: '', indexes: { byFamilyNameGivenNameEmailCozyUrl: '' } },
|
|
128
|
+
{ name: {}, indexes: { byFamilyNameGivenNameEmailCozyUrl: {} } },
|
|
129
|
+
{ name: 'John', indexes: { byFamilyNameGivenNameEmailCozyUrl: null } },
|
|
130
|
+
{
|
|
131
|
+
name: 'Connor',
|
|
132
|
+
indexes: { byFamilyNameGivenNameEmailCozyUrl: undefined }
|
|
133
|
+
},
|
|
134
|
+
{ name: 'Àlbert', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'À' } },
|
|
135
|
+
{
|
|
136
|
+
name: 'Alice',
|
|
137
|
+
me: true,
|
|
138
|
+
indexes: { byFamilyNameGivenNameEmailCozyUrl: 'Alice' }
|
|
139
|
+
},
|
|
140
|
+
{ name: 'Èllen', indexes: { byFamilyNameGivenNameEmailCozyUrl: 'È' } }
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
const res = makeGroupLabelsAndCounts(contacts, t)
|
|
144
|
+
|
|
145
|
+
expect(res).toStrictEqual({
|
|
146
|
+
groupLabels: ['A', 'C', 'B', 'X', 'Z', 'empty', 'me', 'E'],
|
|
147
|
+
groupCounts: [3, 5, 2, 1, 1, 4, 1, 1]
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
```jsx
|
|
2
|
+
import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
|
|
3
|
+
import SelectionProvider, { useSelection } from 'cozy-ui/transpiled/react/providers/Selection'
|
|
4
|
+
import Button from 'cozy-ui/transpiled/react/Buttons'
|
|
5
|
+
import Icon from 'cozy-ui/transpiled/react/Icon'
|
|
6
|
+
import Typography from 'cozy-ui/transpiled/react/Typography'
|
|
7
|
+
import DeviceLaptopIcon from 'cozy-ui/transpiled/react/Icons/DeviceLaptop'
|
|
8
|
+
|
|
9
|
+
const Component = () => {
|
|
10
|
+
const { selectedItemsId, addSelectedItem, removeSelectedItem } = useSelection()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Typography>selectedItemsId : {JSON.stringify(selectedItemsId)}</Typography>
|
|
15
|
+
<Button label="Add Item" onClick={() => addSelectedItem({ _id: '01' })} />
|
|
16
|
+
<Button label="Remove Item" onClick={() => removeSelectedItem({ _id: '01' })} />
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
;
|
|
22
|
+
|
|
23
|
+
<DemoProvider>
|
|
24
|
+
<SelectionProvider>
|
|
25
|
+
<Component />
|
|
26
|
+
</SelectionProvider>
|
|
27
|
+
</DemoProvider>
|
|
28
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual'
|
|
2
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
const SelectionContext = createContext()
|
|
5
|
+
|
|
6
|
+
export const useSelection = () => {
|
|
7
|
+
const context = useContext(SelectionContext)
|
|
8
|
+
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error('useSelection must be used within a SelectionProvider')
|
|
11
|
+
}
|
|
12
|
+
return context
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* This provider allows you to manage item selection
|
|
17
|
+
*/
|
|
18
|
+
const SelectionProvider = ({ children }) => {
|
|
19
|
+
const [selectedItemsId, setSelectedItemsId] = useState([])
|
|
20
|
+
|
|
21
|
+
const isSelectedItem = item => {
|
|
22
|
+
return selectedItemsId.some(selectedItemId => selectedItemId === item._id)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isSelectionEnabled = selectedItemsId.length > 0
|
|
26
|
+
|
|
27
|
+
const addSelectedItem = item => {
|
|
28
|
+
setSelectedItemsId(prev => [...prev, item._id])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const removeSelectedItem = item => {
|
|
32
|
+
setSelectedItemsId(prev => prev.filter(el => el !== item._id))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const toggleSelectedItem = item => {
|
|
36
|
+
if (isSelectedItem(item)) {
|
|
37
|
+
removeSelectedItem(item)
|
|
38
|
+
} else {
|
|
39
|
+
addSelectedItem(item)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const selectAll = items => {
|
|
44
|
+
const ids = items.map(item => item._id)
|
|
45
|
+
setSelectedItemsId(ids)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const unselectAll = () => {
|
|
49
|
+
setSelectedItemsId([])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isSelectedAllItems = items => {
|
|
53
|
+
const a = selectedItemsId.sort()
|
|
54
|
+
const b = items.map(item => item._id).sort()
|
|
55
|
+
return isEqual(a, b)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const toggleSelectAllItems = items => {
|
|
59
|
+
if (isSelectedAllItems(items)) {
|
|
60
|
+
unselectAll()
|
|
61
|
+
} else {
|
|
62
|
+
selectAll(items)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<SelectionContext.Provider
|
|
68
|
+
value={{
|
|
69
|
+
selectedItemsId,
|
|
70
|
+
addSelectedItem,
|
|
71
|
+
removeSelectedItem,
|
|
72
|
+
toggleSelectedItem,
|
|
73
|
+
selectAll,
|
|
74
|
+
toggleSelectAllItems,
|
|
75
|
+
isSelectedItem,
|
|
76
|
+
isSelectionEnabled,
|
|
77
|
+
isSelectedAllItems
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</SelectionContext.Provider>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default React.memo(SelectionProvider)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import React, { useRef, useState } from 'react';
|
|
3
|
+
import ContactIdentity from "cozy-ui/transpiled/react/ContactsList/Contacts/ContactIdentity";
|
|
4
|
+
import { locales } from "cozy-ui/transpiled/react/ContactsList/locales/withContactsListLocales";
|
|
5
|
+
import ActionsMenu from "cozy-ui/transpiled/react/ActionsMenu";
|
|
6
|
+
import Icon from "cozy-ui/transpiled/react/Icon";
|
|
7
|
+
import IconButton from "cozy-ui/transpiled/react/IconButton";
|
|
8
|
+
import DotsIcon from "cozy-ui/transpiled/react/Icons/Dots";
|
|
9
|
+
import ListItemIcon from "cozy-ui/transpiled/react/ListItemIcon";
|
|
10
|
+
import { useI18n, useExtendI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
|
11
|
+
|
|
12
|
+
var Cell = function Cell(_ref) {
|
|
13
|
+
var row = _ref.row,
|
|
14
|
+
column = _ref.column,
|
|
15
|
+
cell = _ref.cell,
|
|
16
|
+
actions = _ref.actions;
|
|
17
|
+
|
|
18
|
+
var _useState = useState(false),
|
|
19
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
20
|
+
showActions = _useState2[0],
|
|
21
|
+
setShowActions = _useState2[1];
|
|
22
|
+
|
|
23
|
+
useExtendI18n(locales);
|
|
24
|
+
|
|
25
|
+
var _useI18n = useI18n(),
|
|
26
|
+
t = _useI18n.t;
|
|
27
|
+
|
|
28
|
+
var actionsRef = useRef();
|
|
29
|
+
|
|
30
|
+
if (column.id === 'fullname') {
|
|
31
|
+
return /*#__PURE__*/React.createElement(ContactIdentity, {
|
|
32
|
+
contact: row,
|
|
33
|
+
noWrapper: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (column.id === 'actions') {
|
|
38
|
+
if (!actions) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(IconButton, {
|
|
43
|
+
ref: actionsRef,
|
|
44
|
+
"arial-label": t('menu'),
|
|
45
|
+
onClick: function onClick() {
|
|
46
|
+
return setShowActions(true);
|
|
47
|
+
}
|
|
48
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
49
|
+
icon: DotsIcon
|
|
50
|
+
}))), showActions && /*#__PURE__*/React.createElement(ActionsMenu, {
|
|
51
|
+
open: true,
|
|
52
|
+
ref: actionsRef,
|
|
53
|
+
docs: [row],
|
|
54
|
+
actions: actions,
|
|
55
|
+
anchorOrigin: {
|
|
56
|
+
vertical: 'bottom',
|
|
57
|
+
horizontal: 'right'
|
|
58
|
+
},
|
|
59
|
+
onClose: function onClose() {
|
|
60
|
+
return setShowActions(false);
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return cell;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default /*#__PURE__*/React.memo(Cell);
|
|
@@ -3,6 +3,7 @@ export function sortLastNameFirst(contact: any, comparedContact: any): any;
|
|
|
3
3
|
export function sortContacts(contacts: any): any;
|
|
4
4
|
export function categorizeContacts(contacts: object[], t: Function): CategorizedContactsResult;
|
|
5
5
|
export function sortHeaders(categorized: CategorizedContactsResult, t: Function): string[];
|
|
6
|
+
export function makeGroupLabelsAndCounts(contacts: array, t: any): object;
|
|
6
7
|
export type CategorizedContactsResult = {
|
|
7
8
|
[x: string]: Object;
|
|
8
9
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
1
2
|
export function buildLastNameFirst(contact) {
|
|
2
3
|
var givenName = contact.name && contact.name.givenName ? contact.name.givenName.toUpperCase() : '';
|
|
3
4
|
var familyName = contact.name && contact.name.familyName ? contact.name.familyName.toUpperCase() : '';
|
|
@@ -28,15 +29,39 @@ var makeHeader = function makeHeader(contact, t) {
|
|
|
28
29
|
var name = buildLastNameFirst(contact);
|
|
29
30
|
return name[0] || t('empty');
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Build header for a contact (first letter of indexes.byFamilyNameGivenNameEmailCozyUrl)
|
|
34
|
+
* @param {object} contact
|
|
35
|
+
* @param {function} t translation function
|
|
36
|
+
* @returns {string} header
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
var makeHeaderForIndexedContacts = function makeHeaderForIndexedContacts(contact, t) {
|
|
41
|
+
var _contact$cozyMetadata;
|
|
42
|
+
|
|
43
|
+
if (contact.me) return t('me');
|
|
44
|
+
if ((_contact$cozyMetadata = contact.cozyMetadata) !== null && _contact$cozyMetadata !== void 0 && _contact$cozyMetadata.favorite) return t('favorite');
|
|
45
|
+
var index = get(contact, 'indexes.byFamilyNameGivenNameEmailCozyUrl', '');
|
|
46
|
+
var hasIndex = index !== null && index.length > 0;
|
|
47
|
+
|
|
48
|
+
if (hasIndex) {
|
|
49
|
+
var firstLetterWithoutAccent = index[0].normalize('NFD').replace(/(?:[\^`\xA8\xAF\xB4\xB7\xB8\u02B0-\u034E\u0350-\u0357\u035D-\u0362\u0374\u0375\u037A\u0384\u0385\u0483-\u0487\u0559\u0591-\u05A1\u05A3-\u05BD\u05BF\u05C1\u05C2\u05C4\u064B-\u0652\u0657\u0658\u06DF\u06E0\u06E5\u06E6\u06EA-\u06EC\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F5\u0818\u0819\u0898-\u089F\u08C9-\u08D2\u08E3-\u08FE\u093C\u094D\u0951-\u0954\u0971\u09BC\u09CD\u0A3C\u0A4D\u0ABC\u0ACD\u0AFD-\u0AFF\u0B3C\u0B4D\u0B55\u0BCD\u0C3C\u0C4D\u0CBC\u0CCD\u0D3B\u0D3C\u0D4D\u0DCA\u0E47-\u0E4C\u0E4E\u0EBA\u0EC8-\u0ECC\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F82-\u0F84\u0F86\u0F87\u0FC6\u1037\u1039\u103A\u1063\u1064\u1069-\u106D\u1087-\u108D\u108F\u109A\u109B\u135D-\u135F\u1714\u1715\u17C9-\u17D3\u17DD\u1939-\u193B\u1A75-\u1A7C\u1A7F\u1AB0-\u1ABE\u1AC1-\u1ACB\u1B34\u1B44\u1B6B-\u1B73\u1BAA\u1BAB\u1C36\u1C37\u1C78-\u1C7D\u1CD0-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1D2C-\u1D6A\u1DC4-\u1DCF\u1DF5-\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2CEF-\u2CF1\u2E2F\u302A-\u302F\u3099-\u309C\u30FC\uA66F\uA67C\uA67D\uA67F\uA69C\uA69D\uA6F0\uA6F1\uA700-\uA721\uA788-\uA78A\uA7F8\uA7F9\uA8C4\uA8E0-\uA8F1\uA92B-\uA92E\uA953\uA9B3\uA9C0\uA9E5\uAA7B-\uAA7D\uAABF-\uAAC2\uAAF6\uAB5B-\uAB5F\uAB69-\uAB6B\uABEC\uABED\uFB1E\uFE20-\uFE2F\uFF3E\uFF40\uFF70\uFF9E\uFF9F\uFFE3]|\uD800\uDEE0|\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDEE5\uDEE6]|\uD803[\uDD22-\uDD27\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC46\uDC70\uDCB9\uDCBA\uDD33\uDD34\uDD73\uDDC0\uDDCA-\uDDCC\uDE35\uDE36\uDEE9\uDEEA\uDF3C\uDF4D\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC42\uDC46\uDCC2\uDCC3\uDDBF\uDDC0\uDE3F\uDEB6\uDEB7\uDF2B]|\uD806[\uDC39\uDC3A\uDD3D\uDD3E\uDD43\uDDE0\uDE34\uDE47\uDE99]|\uD807[\uDC3F\uDD42\uDD44\uDD45\uDD97]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF8F-\uDF9F\uDFF0\uDFF1]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uD838[\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD46\uDD48-\uDD4A])/g, '');
|
|
50
|
+
return firstLetterWithoutAccent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return t('empty');
|
|
54
|
+
};
|
|
31
55
|
/**
|
|
32
56
|
* @typedef {Object.<string, Object>} CategorizedContactsResult
|
|
33
57
|
*/
|
|
34
58
|
|
|
35
59
|
/**
|
|
36
60
|
* Categorize contacts by first letter of last name
|
|
61
|
+
* Expl.: all contacts with A as first letter will be in A category
|
|
37
62
|
* @param {object[]} contacts io.cozy.contacts documents
|
|
38
63
|
* @param {function} t translation function
|
|
39
|
-
* @returns {CategorizedContactsResult}
|
|
64
|
+
* @returns {CategorizedContactsResult} Categorized contacts
|
|
40
65
|
*/
|
|
41
66
|
|
|
42
67
|
|
|
@@ -77,4 +102,29 @@ export var sortHeaders = function sortHeaders(categorized, t) {
|
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
return headersSorted.concat(notEmptyAndMyselfSorted);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Counts how many contacts are categorized by first letter and store it in `groupCounts`
|
|
108
|
+
* Expl.: if there is 3 contacts in A and 2 in B, it will return [3,2]
|
|
109
|
+
* Also store first letters and store them in `groupLabels`
|
|
110
|
+
* @param {array} contacts - Array of io.cozy.contact documents
|
|
111
|
+
* @returns {object}
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
export var makeGroupLabelsAndCounts = function makeGroupLabelsAndCounts(contacts, t) {
|
|
115
|
+
return contacts.reduce(function (acc, contact) {
|
|
116
|
+
var header = makeHeaderForIndexedContacts(contact, t);
|
|
117
|
+
|
|
118
|
+
if (!acc.groupLabels.includes(header)) {
|
|
119
|
+
acc.groupLabels.push(header);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var idx = acc.groupLabels.indexOf(header);
|
|
123
|
+
var val = acc.groupCounts[idx] || 0;
|
|
124
|
+
acc.groupCounts[idx] = val + 1;
|
|
125
|
+
return acc;
|
|
126
|
+
}, {
|
|
127
|
+
groupLabels: [],
|
|
128
|
+
groupCounts: []
|
|
129
|
+
});
|
|
80
130
|
};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
var en = {
|
|
2
2
|
empty: "EMPTY",
|
|
3
|
-
me: "
|
|
3
|
+
me: "me",
|
|
4
|
+
favorite: "favorites",
|
|
5
|
+
menu: "Menu"
|
|
4
6
|
};
|
|
5
7
|
var fr = {
|
|
6
8
|
empty: "VIDE",
|
|
7
|
-
me: "
|
|
9
|
+
me: "moi",
|
|
10
|
+
favorite: "favoris",
|
|
11
|
+
menu: "Menu"
|
|
8
12
|
};
|
|
9
13
|
import withOnlyLocales from "cozy-ui/transpiled/react/providers/I18n/withOnlyLocales";
|
|
10
14
|
export var locales = {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
import isEqual from 'lodash/isEqual';
|
|
4
|
+
import React, { createContext, useContext, useState } from 'react';
|
|
5
|
+
var SelectionContext = /*#__PURE__*/createContext();
|
|
6
|
+
export var useSelection = function useSelection() {
|
|
7
|
+
var context = useContext(SelectionContext);
|
|
8
|
+
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error('useSelection must be used within a SelectionProvider');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return context;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* This provider allows you to manage item selection
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
var SelectionProvider = function SelectionProvider(_ref) {
|
|
20
|
+
var children = _ref.children;
|
|
21
|
+
|
|
22
|
+
var _useState = useState([]),
|
|
23
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
24
|
+
selectedItemsId = _useState2[0],
|
|
25
|
+
setSelectedItemsId = _useState2[1];
|
|
26
|
+
|
|
27
|
+
var isSelectedItem = function isSelectedItem(item) {
|
|
28
|
+
return selectedItemsId.some(function (selectedItemId) {
|
|
29
|
+
return selectedItemId === item._id;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var isSelectionEnabled = selectedItemsId.length > 0;
|
|
34
|
+
|
|
35
|
+
var addSelectedItem = function addSelectedItem(item) {
|
|
36
|
+
setSelectedItemsId(function (prev) {
|
|
37
|
+
return [].concat(_toConsumableArray(prev), [item._id]);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
var removeSelectedItem = function removeSelectedItem(item) {
|
|
42
|
+
setSelectedItemsId(function (prev) {
|
|
43
|
+
return prev.filter(function (el) {
|
|
44
|
+
return el !== item._id;
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
var toggleSelectedItem = function toggleSelectedItem(item) {
|
|
50
|
+
if (isSelectedItem(item)) {
|
|
51
|
+
removeSelectedItem(item);
|
|
52
|
+
} else {
|
|
53
|
+
addSelectedItem(item);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
var selectAll = function selectAll(items) {
|
|
58
|
+
var ids = items.map(function (item) {
|
|
59
|
+
return item._id;
|
|
60
|
+
});
|
|
61
|
+
setSelectedItemsId(ids);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
var unselectAll = function unselectAll() {
|
|
65
|
+
setSelectedItemsId([]);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
var isSelectedAllItems = function isSelectedAllItems(items) {
|
|
69
|
+
var a = selectedItemsId.sort();
|
|
70
|
+
var b = items.map(function (item) {
|
|
71
|
+
return item._id;
|
|
72
|
+
}).sort();
|
|
73
|
+
return isEqual(a, b);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
var toggleSelectAllItems = function toggleSelectAllItems(items) {
|
|
77
|
+
if (isSelectedAllItems(items)) {
|
|
78
|
+
unselectAll();
|
|
79
|
+
} else {
|
|
80
|
+
selectAll(items);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return /*#__PURE__*/React.createElement(SelectionContext.Provider, {
|
|
85
|
+
value: {
|
|
86
|
+
selectedItemsId: selectedItemsId,
|
|
87
|
+
addSelectedItem: addSelectedItem,
|
|
88
|
+
removeSelectedItem: removeSelectedItem,
|
|
89
|
+
toggleSelectedItem: toggleSelectedItem,
|
|
90
|
+
selectAll: selectAll,
|
|
91
|
+
toggleSelectAllItems: toggleSelectAllItems,
|
|
92
|
+
isSelectedItem: isSelectedItem,
|
|
93
|
+
isSelectionEnabled: isSelectionEnabled,
|
|
94
|
+
isSelectedAllItems: isSelectedAllItems
|
|
95
|
+
}
|
|
96
|
+
}, children);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default /*#__PURE__*/React.memo(SelectionProvider);
|