cozy-ui-plus 12.2.1 → 12.3.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,16 @@
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
+ ## [12.3.1](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@12.3.0...cozy-ui-plus@12.3.1) (2026-07-29)
7
+
8
+ **Note:** Version bump only for package cozy-ui-plus
9
+
10
+ # [12.3.0](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@12.2.1...cozy-ui-plus@12.3.0) (2026-07-28)
11
+
12
+ ### Features
13
+
14
+ - **intent:** Support forced themes ([b207c1c](https://github.com/cozy/cozy-libs/commit/b207c1c488d26cfc60b1408ad356983a8aa2cd66))
15
+
6
16
  ## [12.2.1](https://github.com/cozy/cozy-libs/compare/cozy-ui-plus@12.2.0...cozy-ui-plus@12.2.1) (2026-07-28)
7
17
 
8
18
  **Note:** Version bump only for package cozy-ui-plus
@@ -21,6 +21,8 @@ var _CozyDialogs = require("cozy-ui/transpiled/react/CozyDialogs");
21
21
 
22
22
  var _Dialog = _interopRequireDefault(require("cozy-ui/transpiled/react/Dialog"));
23
23
 
24
+ var _CozyTheme = _interopRequireDefault(require("cozy-ui/transpiled/react/providers/CozyTheme"));
25
+
24
26
  var _IntentIframe = _interopRequireWildcard(require("../IntentIframe"));
25
27
 
26
28
  var _excluded = ["options", "action", "doctype", "children", "closable", "showCloseButton", "create", "tag", "onComplete", "onDismiss", "onReadyToUse", "waitForReadyToUse", "iframeProps", "Component"];
@@ -34,6 +36,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
34
36
  * for the specified intent.
35
37
  */
36
38
  var IntentDialogOpener = function IntentDialogOpener(props) {
39
+ var _options$theme;
40
+
37
41
  var options = props.options,
38
42
  action = props.action,
39
43
  doctype = props.doctype,
@@ -77,14 +81,18 @@ var IntentDialogOpener = function IntentDialogOpener(props) {
77
81
 
78
82
  var Tag = tag; // React needs uppercase element names
79
83
 
84
+ var themeType = options === null || options === void 0 ? void 0 : (_options$theme = options.theme) === null || _options$theme === void 0 ? void 0 : _options$theme.type;
85
+ var forcedTheme = ['light', 'dark'].includes(themeType) ? themeType : undefined;
80
86
  var elements = [/*#__PURE__*/_react.default.cloneElement(children, {
81
87
  key: 'opener',
82
88
  onClick: openModal
83
89
  })];
84
90
 
85
91
  if (modalOpened) {
86
- elements.push( /*#__PURE__*/_react.default.createElement(Component, (0, _extends2.default)({
92
+ elements.push( /*#__PURE__*/_react.default.createElement(_CozyTheme.default, {
87
93
  key: "intent-modal",
94
+ type: forcedTheme
95
+ }, /*#__PURE__*/_react.default.createElement(Component, (0, _extends2.default)({
88
96
  open: modalOpened,
89
97
  onClose: closable && handleDismiss
90
98
  }, componentProps), closable && showCloseButton && /*#__PURE__*/_react.default.createElement(_CozyDialogs.DialogCloseButton, {
@@ -99,7 +107,7 @@ var IntentDialogOpener = function IntentDialogOpener(props) {
99
107
  onReadyToUse: onReadyToUse,
100
108
  waitForReadyToUse: waitForReadyToUse,
101
109
  iframeProps: iframeProps
102
- })));
110
+ }))));
103
111
  }
104
112
 
105
113
  return /*#__PURE__*/_react.default.createElement(Tag, null, elements);
@@ -124,6 +132,13 @@ IntentDialogOpener.propTypes = {
124
132
  /** Action you want to execute */
125
133
  action: _propTypes.default.string.isRequired,
126
134
 
135
+ /** Options passed to the intent */
136
+ options: _propTypes.default.shape({
137
+ theme: _propTypes.default.shape({
138
+ type: _propTypes.default.oneOf(['light', 'dark'])
139
+ })
140
+ }),
141
+
127
142
  /** Doctype on which you want to execute the action */
128
143
  doctype: _propTypes.default.string.isRequired,
129
144
 
@@ -35,6 +35,8 @@ var _cozyInterapp = require("cozy-interapp");
35
35
 
36
36
  var _Spinner = _interopRequireDefault(require("cozy-ui/transpiled/react/Spinner"));
37
37
 
38
+ var _CozyTheme = _interopRequireDefault(require("cozy-ui/transpiled/react/providers/CozyTheme"));
39
+
38
40
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
39
41
 
40
42
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
@@ -152,9 +154,11 @@ var IntentIframe = /*#__PURE__*/function (_React$Component) {
152
154
  }, {
153
155
  key: "render",
154
156
  value: function render() {
155
- var _this3 = this;
157
+ var _data$theme,
158
+ _this3 = this;
156
159
 
157
160
  var _this$props3 = this.props,
161
+ data = _this$props3.data,
158
162
  iframeProps = _this$props3.iframeProps,
159
163
  waitForReadyToUse = _this$props3.waitForReadyToUse;
160
164
  var _this$state = this.state,
@@ -162,7 +166,13 @@ var IntentIframe = /*#__PURE__*/function (_React$Component) {
162
166
  frameLoaded = _this$state.frameLoaded,
163
167
  readyToUse = _this$state.readyToUse;
164
168
  var loading = error === null && (!frameLoaded || waitForReadyToUse && !readyToUse);
165
- return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({
169
+ var themeType = data === null || data === void 0 ? void 0 : (_data$theme = data.theme) === null || _data$theme === void 0 ? void 0 : _data$theme.type;
170
+ var forcedTheme = ['light', 'dark'].includes(themeType) ? themeType : null;
171
+ return /*#__PURE__*/_react.default.createElement(_CozyTheme.default, {
172
+ type: forcedTheme,
173
+ ignoreItself: false,
174
+ className: styles.intentContainer
175
+ }, /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({
166
176
  ref: function ref(intentViewer) {
167
177
  return _this3.intentViewer = intentViewer;
168
178
  },
@@ -176,7 +186,7 @@ var IntentIframe = /*#__PURE__*/function (_React$Component) {
176
186
  size: "xxlarge"
177
187
  }, (0, _get.default)(iframeProps, 'spinnerProps')))), error && /*#__PURE__*/_react.default.createElement("div", {
178
188
  className: styles.intentContainer__error
179
- }, error.message));
189
+ }, error.message)));
180
190
  }
181
191
  }]);
182
192
  return IntentIframe;
@@ -12,6 +12,11 @@ declare namespace IntentDialogOpener {
12
12
  export let onReadyToUse: PropTypes.Requireable<(...args: any[]) => any>;
13
13
  export let waitForReadyToUse: PropTypes.Requireable<boolean>;
14
14
  export let action: PropTypes.Validator<string>;
15
+ export let options: PropTypes.Requireable<PropTypes.InferProps<{
16
+ theme: PropTypes.Requireable<PropTypes.InferProps<{
17
+ type: PropTypes.Requireable<string>;
18
+ }>>;
19
+ }>>;
15
20
  export let doctype: PropTypes.Validator<string>;
16
21
  export let closable: PropTypes.Validator<boolean>;
17
22
  export let showCloseButton: PropTypes.Validator<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui-plus",
3
- "version": "12.2.1",
3
+ "version": "12.3.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",
@@ -38,7 +38,7 @@
38
38
  "cozy-intent": "^2.31.1",
39
39
  "cozy-interapp": "^0.18.2",
40
40
  "cozy-logger": "^1.18.1",
41
- "cozy-sharing": "^37.2.5",
41
+ "cozy-sharing": "^37.2.7",
42
42
  "cozy-stack-client": "^60.21.1",
43
43
  "cozy-ui": "^138.1.0",
44
44
  "jest": "30.3.0",
@@ -78,5 +78,5 @@
78
78
  "react-dom": "^16 || ^17 || ^18",
79
79
  "twake-i18n": ">=0.3.0"
80
80
  },
81
- "gitHead": "663ae9860187ebdc6d6a2cfa2e7919d8a39c824f"
81
+ "gitHead": "d1b71486a45b2a87c7d93311d09b7bb4bd58a8af"
82
82
  }
@@ -16,6 +16,7 @@ import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
16
16
  fullWidth // extra props will be passed to the dialog
17
17
  maxWidth="md" // extra props will be passed to the dialog
18
18
  iframeProps={{ spinnerProps: { middle: true } }}
19
+ options={{ theme: { type: 'dark' } }}
19
20
  waitForReadyToUse
20
21
  onComplete={res => alert('intent has completed ! ' + JSON.stringify(res))}
21
22
  onDismiss={() => alert('intent has been dismissed !')}
@@ -28,3 +29,7 @@ import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
28
29
  Set `waitForReadyToUse` to keep the intent hidden behind the loading spinner
29
30
  until its service sends the `readyToUse` signal. This option is disabled by
30
31
  default because older intent services do not send this signal.
32
+
33
+ Set `options.theme.type` to `light` or `dark` to force that theme on the
34
+ dialog, close button and loading surface as well as passing it to the intent
35
+ service. Any other value or no value keeps the caller's theme.
@@ -3,6 +3,7 @@ import React, { useState } from 'react'
3
3
 
4
4
  import { DialogCloseButton } from 'cozy-ui/transpiled/react/CozyDialogs'
5
5
  import Dialog from 'cozy-ui/transpiled/react/Dialog'
6
+ import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme'
6
7
 
7
8
  import IntentIframe, { iframeProps } from '../IntentIframe'
8
9
 
@@ -48,33 +49,38 @@ const IntentDialogOpener = props => {
48
49
  }
49
50
 
50
51
  const Tag = tag // React needs uppercase element names
52
+ const themeType = options?.theme?.type
53
+ const forcedTheme = ['light', 'dark'].includes(themeType)
54
+ ? themeType
55
+ : undefined
51
56
  const elements = [
52
57
  React.cloneElement(children, { key: 'opener', onClick: openModal })
53
58
  ]
54
59
 
55
60
  if (modalOpened) {
56
61
  elements.push(
57
- <Component
58
- key="intent-modal"
59
- open={modalOpened}
60
- onClose={closable && handleDismiss}
61
- {...componentProps}
62
- >
63
- {closable && showCloseButton && (
64
- <DialogCloseButton onClick={handleDismiss} />
65
- )}
66
- <IntentIframe
67
- action={action}
68
- type={doctype}
69
- data={options}
70
- create={create}
71
- onCancel={handleDismiss}
72
- onTerminate={handleComplete}
73
- onReadyToUse={onReadyToUse}
74
- waitForReadyToUse={waitForReadyToUse}
75
- iframeProps={iframeProps}
76
- />
77
- </Component>
62
+ <CozyTheme key="intent-modal" type={forcedTheme}>
63
+ <Component
64
+ open={modalOpened}
65
+ onClose={closable && handleDismiss}
66
+ {...componentProps}
67
+ >
68
+ {closable && showCloseButton && (
69
+ <DialogCloseButton onClick={handleDismiss} />
70
+ )}
71
+ <IntentIframe
72
+ action={action}
73
+ type={doctype}
74
+ data={options}
75
+ create={create}
76
+ onCancel={handleDismiss}
77
+ onTerminate={handleComplete}
78
+ onReadyToUse={onReadyToUse}
79
+ waitForReadyToUse={waitForReadyToUse}
80
+ iframeProps={iframeProps}
81
+ />
82
+ </Component>
83
+ </CozyTheme>
78
84
  )
79
85
  }
80
86
 
@@ -94,6 +100,12 @@ IntentDialogOpener.propTypes = {
94
100
  waitForReadyToUse: PropTypes.bool,
95
101
  /** Action you want to execute */
96
102
  action: PropTypes.string.isRequired,
103
+ /** Options passed to the intent */
104
+ options: PropTypes.shape({
105
+ theme: PropTypes.shape({
106
+ type: PropTypes.oneOf(['light', 'dark'])
107
+ })
108
+ }),
97
109
  /** Doctype on which you want to execute the action */
98
110
  doctype: PropTypes.string.isRequired,
99
111
  /** Whether the dialog is closable by itself (not by the Intent) with a button or by clicking outside of it */
@@ -14,7 +14,7 @@ import utils from '../../docs/utils'
14
14
  <IntentIframe
15
15
  action="PICK"
16
16
  create={utils.fakeIntentCreate}
17
- data={{folder: '840af7e7be6c41bed3c5b76d03f66328'}}
17
+ data={{folder: '840af7e7be6c41bed3c5b76d03f66328', theme: { type: 'dark' }}}
18
18
  type="io.cozy.files"
19
19
  onCancel={() => alert('intent cancelled')}
20
20
  onError={(error) => alert('intent has failed with error: ' + error.message)}
@@ -27,6 +27,10 @@ Set `waitForReadyToUse` to keep the intent hidden behind the loading spinner
27
27
  until its service sends the `readyToUse` signal. This option is disabled by
28
28
  default because older intent services do not send this signal.
29
29
 
30
+ Set `data.theme.type` to `light` or `dark` to force that theme on the
31
+ loading surface and pass it to the intent service. Any other value or no value
32
+ keeps the caller's theme.
33
+
30
34
  ### Within an application side controled Dialog
31
35
 
32
36
  Sometimes you have to render an Intent inside a modal, and handle the modal opening state on the application side. You can then use IntentIframe wrapped in a Dialog for that use case, instead of using IntentDialogOpener component (that is useful to start a new intent modal from a click on a button).
@@ -7,6 +7,7 @@ import React from 'react'
7
7
  import { withClient } from 'cozy-client'
8
8
  import { Intents } from 'cozy-interapp'
9
9
  import Spinner from 'cozy-ui/transpiled/react/Spinner'
10
+ import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme'
10
11
 
11
12
  import styles from './styles.styl'
12
13
 
@@ -79,30 +80,37 @@ class IntentIframe extends React.Component {
79
80
  }
80
81
 
81
82
  render() {
82
- const { iframeProps, waitForReadyToUse } = this.props
83
+ const { data, iframeProps, waitForReadyToUse } = this.props
83
84
  const { error, frameLoaded, readyToUse } = this.state
84
85
  const loading =
85
86
  error === null && (!frameLoaded || (waitForReadyToUse && !readyToUse))
86
-
87
+ const themeType = data?.theme?.type
88
+ const forcedTheme = ['light', 'dark'].includes(themeType) ? themeType : null
87
89
  return (
88
- <div
89
- ref={intentViewer => (this.intentViewer = intentViewer)}
90
+ <CozyTheme
91
+ type={forcedTheme}
92
+ ignoreItself={false}
90
93
  className={styles.intentContainer}
91
- aria-busy={loading}
92
- data-iframe-loaded={frameLoaded}
93
- data-waiting-for-ready-to-use={waitForReadyToUse && !readyToUse}
94
- {...get(iframeProps, 'wrapperProps')}
95
94
  >
96
- {loading && (
97
- <div className={styles.intentContainer__loader}>
98
- <Spinner size="xxlarge" {...get(iframeProps, 'spinnerProps')} />
99
- </div>
100
- )}
101
- {error && (
102
- <div className={styles.intentContainer__error}>{error.message}</div>
103
- )}
104
- {/* intent iframe will be appended here */}
105
- </div>
95
+ <div
96
+ ref={intentViewer => (this.intentViewer = intentViewer)}
97
+ className={styles.intentContainer}
98
+ aria-busy={loading}
99
+ data-iframe-loaded={frameLoaded}
100
+ data-waiting-for-ready-to-use={waitForReadyToUse && !readyToUse}
101
+ {...get(iframeProps, 'wrapperProps')}
102
+ >
103
+ {loading && (
104
+ <div className={styles.intentContainer__loader}>
105
+ <Spinner size="xxlarge" {...get(iframeProps, 'spinnerProps')} />
106
+ </div>
107
+ )}
108
+ {error && (
109
+ <div className={styles.intentContainer__error}>{error.message}</div>
110
+ )}
111
+ {/* intent iframe will be appended here */}
112
+ </div>
113
+ </CozyTheme>
106
114
  )
107
115
  }
108
116
  }
@@ -7,7 +7,7 @@ jest.mock('cozy-ui/transpiled/react/Spinner', () => () => (
7
7
  <div data-testid="intent-spinner" />
8
8
  ))
9
9
 
10
- function setup({ startError = null, waitForReadyToUse = false } = {}) {
10
+ function setup({ data, startError = null, waitForReadyToUse = false } = {}) {
11
11
  let startOptions
12
12
  const start = jest.fn((element, options) => {
13
13
  startOptions = options
@@ -24,6 +24,7 @@ function setup({ startError = null, waitForReadyToUse = false } = {}) {
24
24
  action="PICK"
25
25
  client={{}}
26
26
  create={create}
27
+ data={data}
27
28
  iframeProps={{ setIsLoading }}
28
29
  onCancel={jest.fn()}
29
30
  onError={onError}