cozy-ui-plus 4.4.1 → 4.5.1

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 CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.5.1](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@4.5.0...cozy-ui-plus@4.5.1) (2026-02-03)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-ui-plus:** Fix ContactName not bolding multi-word family names ([e8015c0](https://github.com/cozy/cozy-libs/commit/e8015c05bda2272b85bf96cb1d560251097fef07))
11
+
12
+ # [4.5.0](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@4.4.1...cozy-ui-plus@4.5.0) (2026-02-02)
13
+
14
+ ### Features
15
+
16
+ - **cozy-ui-plus:** Add `disable` prop on ContactCell ([2249927](https://github.com/cozy/cozy-libs/commit/22499270ab1b110cb4e01cf83297fd69d33f5d3b))
17
+
6
18
  ## [4.4.1](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@4.4.0...cozy-ui-plus@4.4.1) (2026-01-29)
7
19
 
8
20
  **Note:** Version bump only for package cozy-ui-plus
@@ -17,11 +17,13 @@ var Cell = function Cell(_ref) {
17
17
  var row = _ref.row,
18
18
  column = _ref.column,
19
19
  cell = _ref.cell,
20
- actions = _ref.actions;
20
+ actions = _ref.actions,
21
+ disable = _ref.disable;
21
22
 
22
23
  if (column.id === 'fullname') {
23
24
  return /*#__PURE__*/_react.default.createElement(_ContactIdentity.default, {
24
25
  contact: row,
26
+ disable: disable,
25
27
  noWrapper: true
26
28
  });
27
29
  }
@@ -9,6 +9,8 @@ exports.default = void 0;
9
9
 
10
10
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
11
11
 
12
+ var _classnames = _interopRequireDefault(require("classnames"));
13
+
12
14
  var _propTypes = _interopRequireDefault(require("prop-types"));
13
15
 
14
16
  var _react = _interopRequireDefault(require("react"));
@@ -50,18 +52,24 @@ var MyselfMarker = function MyselfMarker() {
50
52
  };
51
53
 
52
54
  var ContactIdentity = function ContactIdentity(_ref) {
53
- var contact = _ref.contact;
55
+ var contact = _ref.contact,
56
+ disable = _ref.disable;
54
57
  var isMyself = !!contact.me;
55
58
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_Avatar.default, {
59
+ className: (0, _classnames.default)({
60
+ 'u-o-60': disable
61
+ }),
56
62
  display: "inline",
57
63
  size: "s"
58
64
  }, (0, _contact.getInitials)(contact)), /*#__PURE__*/_react.default.createElement(_ContactName.default, {
59
- contact: contact
65
+ contact: contact,
66
+ disable: disable
60
67
  }), isMyself && /*#__PURE__*/_react.default.createElement(MyselfMarker, null));
61
68
  };
62
69
 
63
70
  ContactIdentity.propTypes = {
64
- contact: _propTypes.default.object.isRequired
71
+ contact: _propTypes.default.object.isRequired,
72
+ disable: _propTypes.default.bool
65
73
  };
66
74
 
67
75
  var ContactIdentityWrapper = function ContactIdentityWrapper(_ref2) {
@@ -7,8 +7,6 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports.default = void 0;
9
9
 
10
- var _classnames = _interopRequireDefault(require("classnames"));
11
-
12
10
  var _propTypes = _interopRequireDefault(require("prop-types"));
13
11
 
14
12
  var _react = _interopRequireDefault(require("react"));
@@ -18,31 +16,32 @@ var _contact = require("cozy-client/dist/models/contact");
18
16
  var _Typography = _interopRequireDefault(require("cozy-ui/transpiled/react/Typography"));
19
17
 
20
18
  var ContactName = function ContactName(_ref) {
21
- var _contact$name;
19
+ var _getDisplayName;
20
+
21
+ var contact = _ref.contact,
22
+ disable = _ref.disable;
23
+
24
+ var _ref2 = contact.name || {},
25
+ familyName = _ref2.familyName;
22
26
 
23
- var contact = _ref.contact;
24
- var familyName = (_contact$name = contact.name) === null || _contact$name === void 0 ? void 0 : _contact$name.familyName;
25
- var displayName = (0, _contact.getDisplayName)(contact);
26
- var namesToDisplay = displayName === null || displayName === void 0 ? void 0 : displayName.split(' ');
27
+ var displayName = (_getDisplayName = (0, _contact.getDisplayName)(contact)) === null || _getDisplayName === void 0 ? void 0 : _getDisplayName.trim();
28
+ var hasFamilyName = familyName && (displayName === null || displayName === void 0 ? void 0 : displayName.endsWith(familyName));
29
+ var namePrefix = hasFamilyName ? displayName.slice(0, -familyName.length).trimEnd() : null;
27
30
  return /*#__PURE__*/_react.default.createElement(_Typography.default, {
28
31
  "data-testid": "ContactName" // used by a test in cozy-contacts
29
32
  ,
30
33
  className: "u-ml-1",
31
34
  noWrap: true,
32
- display: "inline"
33
- }, namesToDisplay === null || namesToDisplay === void 0 ? void 0 : namesToDisplay.map(function (name, index) {
34
- var isLastItem = index === namesToDisplay.length - 1;
35
- return /*#__PURE__*/_react.default.createElement("span", {
36
- key: "display-".concat(index),
37
- className: (0, _classnames.default)({
38
- 'u-fw-bold': name === familyName
39
- })
40
- }, name, !isLastItem && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "\xA0"));
41
- }));
35
+ display: "inline",
36
+ color: disable ? 'textSecondary' : 'textPrimary'
37
+ }, hasFamilyName ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, namePrefix && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, namePrefix, "\xA0"), /*#__PURE__*/_react.default.createElement("span", {
38
+ className: "u-fw-bold"
39
+ }, familyName)) : displayName);
42
40
  };
43
41
 
44
42
  ContactName.propTypes = {
45
- contact: _propTypes.default.object.isRequired
43
+ contact: _propTypes.default.object.isRequired,
44
+ disable: _propTypes.default.bool
46
45
  };
47
46
  var _default = ContactName;
48
47
  exports.default = _default;
@@ -1,8 +1,9 @@
1
- declare const _default: React.MemoExoticComponent<({ row, column, cell, actions }: {
1
+ declare const _default: React.MemoExoticComponent<({ row, column, cell, actions, disable }: {
2
2
  row: any;
3
3
  column: any;
4
4
  cell: any;
5
5
  actions: any;
6
+ disable: any;
6
7
  }) => any>;
7
8
  export default _default;
8
9
  import React from 'react';
@@ -1,10 +1,12 @@
1
1
  export default ContactName;
2
- declare function ContactName({ contact }: {
2
+ declare function ContactName({ contact, disable }: {
3
3
  contact: any;
4
+ disable: any;
4
5
  }): JSX.Element;
5
6
  declare namespace ContactName {
6
7
  namespace propTypes {
7
8
  let contact: PropTypes.Validator<object>;
9
+ let disable: PropTypes.Requireable<boolean>;
8
10
  }
9
11
  }
10
12
  import PropTypes from 'prop-types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui-plus",
3
- "version": "4.4.1",
3
+ "version": "4.5.1",
4
4
  "description": "Cozy-ui-plus is an extension of cozy-ui and provides components based on cozy-client",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -74,5 +74,5 @@
74
74
  "react-dom": "^16 || ^17 || ^18",
75
75
  "twake-i18n": ">=0.3.0"
76
76
  },
77
- "gitHead": "80ec928bcefb30b6ea6ed778fbd71e3417c2efa6"
77
+ "gitHead": "549503df8a2071ff837e672ad5f7019aa122cb1f"
78
78
  }
@@ -4,9 +4,9 @@ import ActionsMenuButton from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuB
4
4
 
5
5
  import ContactIdentity from './Contacts/ContactIdentity'
6
6
 
7
- const Cell = ({ row, column, cell, actions }) => {
7
+ const Cell = ({ row, column, cell, actions, disable }) => {
8
8
  if (column.id === 'fullname') {
9
- return <ContactIdentity contact={row} noWrapper />
9
+ return <ContactIdentity contact={row} disable={disable} noWrapper />
10
10
  }
11
11
 
12
12
  if (column.id === 'actions') {
@@ -1,3 +1,4 @@
1
+ import cx from 'classnames'
1
2
  import PropTypes from 'prop-types'
2
3
  import React from 'react'
3
4
  import { useI18n, useExtendI18n } from 'twake-i18n'
@@ -17,22 +18,23 @@ const MyselfMarker = () => {
17
18
  return <span className={`${styles['contact-myself']}`}>({t('me')})</span>
18
19
  }
19
20
 
20
- const ContactIdentity = ({ contact }) => {
21
+ const ContactIdentity = ({ contact, disable }) => {
21
22
  const isMyself = !!contact.me
22
23
 
23
24
  return (
24
25
  <>
25
- <Avatar display="inline" size="s">
26
+ <Avatar className={cx({ 'u-o-60': disable })} display="inline" size="s">
26
27
  {getInitials(contact)}
27
28
  </Avatar>
28
- <ContactName contact={contact} />
29
+ <ContactName contact={contact} disable={disable} />
29
30
  {isMyself && <MyselfMarker />}
30
31
  </>
31
32
  )
32
33
  }
33
34
 
34
35
  ContactIdentity.propTypes = {
35
- contact: PropTypes.object.isRequired
36
+ contact: PropTypes.object.isRequired,
37
+ disable: PropTypes.bool
36
38
  }
37
39
 
38
40
  const ContactIdentityWrapper = ({ noWrapper, ...props }) => {
@@ -1,14 +1,16 @@
1
- import cx from 'classnames'
2
1
  import PropTypes from 'prop-types'
3
2
  import React from 'react'
4
3
 
5
4
  import { getDisplayName } from 'cozy-client/dist/models/contact'
6
5
  import Typography from 'cozy-ui/transpiled/react/Typography'
7
6
 
8
- const ContactName = ({ contact }) => {
9
- const familyName = contact.name?.familyName
10
- const displayName = getDisplayName(contact)
11
- const namesToDisplay = displayName?.split(' ')
7
+ const ContactName = ({ contact, disable }) => {
8
+ const { familyName } = contact.name || {}
9
+ const displayName = getDisplayName(contact)?.trim()
10
+ const hasFamilyName = familyName && displayName?.endsWith(familyName)
11
+ const namePrefix = hasFamilyName
12
+ ? displayName.slice(0, -familyName.length).trimEnd()
13
+ : null
12
14
 
13
15
  return (
14
16
  <Typography
@@ -16,28 +18,23 @@ const ContactName = ({ contact }) => {
16
18
  className="u-ml-1"
17
19
  noWrap
18
20
  display="inline"
21
+ color={disable ? 'textSecondary' : 'textPrimary'}
19
22
  >
20
- {namesToDisplay?.map((name, index) => {
21
- const isLastItem = index === namesToDisplay.length - 1
22
-
23
- return (
24
- <span
25
- key={`display-${index}`}
26
- className={cx({
27
- 'u-fw-bold': name === familyName
28
- })}
29
- >
30
- {name}
31
- {!isLastItem && <>&nbsp;</>}
32
- </span>
33
- )
34
- })}
23
+ {hasFamilyName ? (
24
+ <>
25
+ {namePrefix && <>{namePrefix}&nbsp;</>}
26
+ <span className="u-fw-bold">{familyName}</span>
27
+ </>
28
+ ) : (
29
+ displayName
30
+ )}
35
31
  </Typography>
36
32
  )
37
33
  }
38
34
 
39
35
  ContactName.propTypes = {
40
- contact: PropTypes.object.isRequired
36
+ contact: PropTypes.object.isRequired,
37
+ disable: PropTypes.bool
41
38
  }
42
39
 
43
40
  export default ContactName
@@ -0,0 +1,99 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { getDisplayName } from 'cozy-client/dist/models/contact'
5
+
6
+ import ContactName from './ContactName'
7
+
8
+ jest.mock('cozy-client/dist/models/contact', () => ({
9
+ getDisplayName: jest.fn()
10
+ }))
11
+
12
+ const makeContact = attrs => ({
13
+ _id: 'contactid',
14
+ _type: 'io.cozy.contacts',
15
+ ...attrs
16
+ })
17
+
18
+ describe('ContactName', () => {
19
+ it('should bold single-word family name', () => {
20
+ getDisplayName.mockReturnValue('John Doe')
21
+ const contact = makeContact({
22
+ name: { familyName: 'Doe', givenName: 'John' }
23
+ })
24
+
25
+ render(<ContactName contact={contact} />)
26
+
27
+ const boldElement = document.querySelector('.u-fw-bold')
28
+ expect(boldElement).toHaveTextContent('Doe')
29
+ })
30
+
31
+ it('should bold multi-word family name', () => {
32
+ getDisplayName.mockReturnValue('khaled Van Beethoven')
33
+ const contact = makeContact({
34
+ name: { familyName: 'Van Beethoven', givenName: 'khaled' }
35
+ })
36
+
37
+ render(<ContactName contact={contact} />)
38
+
39
+ const boldElement = document.querySelector('.u-fw-bold')
40
+ expect(boldElement).toHaveTextContent('Van Beethoven')
41
+ })
42
+
43
+ it('should not bold anything when familyName is missing', () => {
44
+ getDisplayName.mockReturnValue('johndoe@localhost')
45
+ const contact = makeContact()
46
+
47
+ render(<ContactName contact={contact} />)
48
+
49
+ const boldElement = document.querySelector('.u-fw-bold')
50
+ expect(boldElement).toBeNull()
51
+ expect(screen.getByText('johndoe@localhost')).toBeInTheDocument()
52
+ })
53
+
54
+ it('should not bold when displayName does not end with familyName', () => {
55
+ getDisplayName.mockReturnValue('johndoe@localhost')
56
+ const contact = makeContact({
57
+ name: { familyName: 'Doe' }
58
+ })
59
+
60
+ render(<ContactName contact={contact} />)
61
+
62
+ const boldElement = document.querySelector('.u-fw-bold')
63
+ expect(boldElement).toBeNull()
64
+ })
65
+
66
+ it('should handle contact without name property', () => {
67
+ getDisplayName.mockReturnValue('johndoe@localhost')
68
+ const contact = makeContact()
69
+
70
+ render(<ContactName contact={contact} />)
71
+
72
+ expect(screen.getByText('johndoe@localhost')).toBeInTheDocument()
73
+ })
74
+
75
+ it('should display name prefix before bolded family name', () => {
76
+ getDisplayName.mockReturnValue('khaled Van Beethoven')
77
+ const contact = makeContact({
78
+ name: { familyName: 'Van Beethoven', givenName: 'khaled' }
79
+ })
80
+
81
+ render(<ContactName contact={contact} />)
82
+
83
+ const nameElement = screen.getByTestId('ContactName')
84
+ expect(nameElement).toHaveTextContent('khaled')
85
+ expect(nameElement).toHaveTextContent('Van Beethoven')
86
+ })
87
+
88
+ it('should bold family name even with trailing whitespace in displayName', () => {
89
+ getDisplayName.mockReturnValue('John Doe ')
90
+ const contact = makeContact({
91
+ name: { familyName: 'Doe', givenName: 'John' }
92
+ })
93
+
94
+ render(<ContactName contact={contact} />)
95
+
96
+ const boldElement = document.querySelector('.u-fw-bold')
97
+ expect(boldElement).toHaveTextContent('Doe')
98
+ })
99
+ })