cozy-ui 95.2.0 → 95.4.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 CHANGED
@@ -1,3 +1,31 @@
1
+ # [95.4.0](https://github.com/cozy/cozy-ui/compare/v95.3.0...v95.4.0) (2023-10-23)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ActionsMenu:** ActionsMenuMobileHeader was unnecessarily in DOM on desktop ([805a030](https://github.com/cozy/cozy-ui/commit/805a030))
7
+ * **Menu, ActionsMenu:** The margins between elements were not as expected ([e021fdc](https://github.com/cozy/cozy-ui/commit/e021fdc))
8
+
9
+
10
+ ### Features
11
+
12
+ * **ActionsMenuItem:** Add `cozyActionsMenuItem` class to be able to target it ([fcd6dfa](https://github.com/cozy/cozy-ui/commit/fcd6dfa))
13
+ * **ListItem:** Add min heights ([ceaba86](https://github.com/cozy/cozy-ui/commit/ceaba86))
14
+ * **ListItemIcon:** Add `cozyListItemIcon` class to be able to target it ([f0b218f](https://github.com/cozy/cozy-ui/commit/f0b218f))
15
+
16
+ # [95.3.0](https://github.com/cozy/cozy-ui/compare/v95.2.0...v95.3.0) (2023-10-18)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **BottomSheet:** IsTopPosition wasn't set if condition [secure] ([4ae9ac0](https://github.com/cozy/cozy-ui/commit/4ae9ac0))
22
+
23
+
24
+ ### Features
25
+
26
+ * **BottomSheet:** Add rerendering content compliancy ([c93efb2](https://github.com/cozy/cozy-ui/commit/c93efb2))
27
+ * **NestedSelect:** Rerender BottomSheet if content change ([b26e6bd](https://github.com/cozy/cozy-ui/commit/b26e6bd))
28
+
1
29
  # [95.2.0](https://github.com/cozy/cozy-ui/compare/v95.1.0...v95.2.0) (2023-10-11)
2
30
 
3
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "95.2.0",
3
+ "version": "95.4.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import omit from 'lodash/omit'
4
+ import cx from 'classnames'
4
5
 
5
6
  import MenuItem from '../MenuItem'
6
7
  import ListItem from '../ListItem'
@@ -11,16 +12,27 @@ const cleanPropsForDOM = props => {
11
12
  return omit(props, nonDOMProps)
12
13
  }
13
14
 
14
- const ActionsMenuItem = forwardRef(({ isListItem, ...props }, ref) => {
15
- const Component = isListItem ? ListItem : MenuItem
16
- const compProps = cleanPropsForDOM(props)
17
-
18
- return <Component {...compProps} ref={ref} button ellipsis={false} />
19
- })
15
+ const ActionsMenuItem = forwardRef(
16
+ ({ isListItem, className, ...props }, ref) => {
17
+ const Component = isListItem ? ListItem : MenuItem
18
+ const compProps = cleanPropsForDOM(props)
19
+
20
+ return (
21
+ <Component
22
+ {...compProps}
23
+ className={cx(className, { ['cozyActionsMenuItem']: !isListItem })}
24
+ ref={ref}
25
+ button
26
+ ellipsis={false}
27
+ />
28
+ )
29
+ }
30
+ )
20
31
 
21
32
  ActionsMenuItem.displayName = 'ActionsMenuItem'
22
33
 
23
34
  ActionsMenuItem.propTypes = {
35
+ className: PropTypes.string,
24
36
  /** Whether the ActionsMenuItem will return a ListItem or MenuItem */
25
37
  isListItem: PropTypes.bool
26
38
  }
@@ -8,12 +8,8 @@ import useBreakpoints from '../providers/Breakpoints'
8
8
  const ActionsMenuMobileHeader = forwardRef(({ children, ...props }, ref) => {
9
9
  const { isMobile } = useBreakpoints()
10
10
 
11
- // To make accessibility work, we need to return a displayed item.
12
- // The trick is to return an empty one with no padding to simulate a display none.
13
- // Otherwise it will be impossible to use keyboard navigation for example
14
- // probably due to the inner workings of Mui
15
11
  if (!isMobile) {
16
- return <ActionsMenuItem {...props} ref={ref} className="u-p-0" />
12
+ return null
17
13
  }
18
14
 
19
15
  return (
@@ -26,6 +22,8 @@ const ActionsMenuMobileHeader = forwardRef(({ children, ...props }, ref) => {
26
22
  )
27
23
  })
28
24
 
25
+ ActionsMenuMobileHeader.displayName = 'ActionsMenuMobileHeader'
26
+
29
27
  ActionsMenuMobileHeader.propTypes = {
30
28
  children: PropTypes.node
31
29
  }
@@ -44,13 +44,24 @@ const ActionsMenuWrapper = ({
44
44
 
45
45
  return (
46
46
  <Menu {...props} open={open} onClose={onClose}>
47
- {React.Children.map(children, child =>
48
- React.isValidElement(child)
47
+ {React.Children.map(children, (child, idx) => {
48
+ // To keep accessibility, we spread the autofocus on the second child
49
+ // if the first one is ActionsMenuMobileHeader
50
+ const firstChild = React.Children.toArray(children)[0]
51
+ const firstChildComponentName =
52
+ firstChild?.type?.name || firstChild?.type?.displayName
53
+ const isFirstChildActionsMenuMobileHeader =
54
+ firstChildComponentName === 'ActionsMenuMobileHeader'
55
+ const autoFocus =
56
+ isFirstChildActionsMenuMobileHeader && idx === 1 ? true : undefined
57
+
58
+ return React.isValidElement(child)
49
59
  ? React.cloneElement(child, {
60
+ autoFocus,
50
61
  onClick: overrideClick(child.props)
51
62
  })
52
63
  : null
53
- )}
64
+ })}
54
65
  </Menu>
55
66
  )
56
67
  }
@@ -209,6 +209,9 @@ const toggleMenu = () => setState(state => ({ showMenu: !state.showMenu }))
209
209
  </ListItemIcon>
210
210
  <ListItemText primary="Attachment" />
211
211
  </ActionsMenuItem>
212
+ <ActionsMenuItem>
213
+ <ListItemText primary="Item without icon" />
214
+ </ActionsMenuItem>
212
215
 
213
216
  <Divider className="u-mv-half" />
214
217
 
@@ -15,6 +15,7 @@ import Portal from '@material-ui/core/Portal'
15
15
 
16
16
  import { getFlagshipMetadata } from 'cozy-device-helper'
17
17
 
18
+ import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI'
18
19
  import CozyTheme, { useCozyTheme } from '../providers/CozyTheme'
19
20
  import Stack from '../Stack'
20
21
  import Paper from '../Paper'
@@ -27,10 +28,11 @@ import {
27
28
  setTopPosition,
28
29
  setBottomPosition,
29
30
  minimizeAndClose,
30
- computeBottomSpacer
31
+ computeBottomSpacer,
32
+ getCssValue
31
33
  } from './helpers'
32
34
  import { ANIMATION_DURATION } from './constants'
33
- import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI'
35
+ import stylescss from './styles.styl'
34
36
 
35
37
  const createContainerWrapperStyles = () => ({
36
38
  container: {
@@ -54,6 +56,7 @@ const createStyles = ({
54
56
  squared,
55
57
  hasToolbarProps,
56
58
  offset,
59
+ renderSaferHeight,
57
60
  isBottomPosition
58
61
  }) => ({
59
62
  root: {
@@ -108,6 +111,13 @@ const createStyles = ({
108
111
  backgroundColor: 'var(--paperBackgroundColor)',
109
112
  zIndex: 'calc(var(--zIndex-modal) + 10)',
110
113
  transition: `opacity ${ANIMATION_DURATION}ms`
114
+ },
115
+ renderSafer: {
116
+ height: renderSaferHeight,
117
+ width: '100%',
118
+ position: 'fixed',
119
+ bottom: 0,
120
+ pointerEvents: 'none'
111
121
  }
112
122
  })
113
123
 
@@ -148,17 +158,23 @@ const BottomSheet = memo(
148
158
  const [currentIndex, setCurrentIndex] = useState()
149
159
  const [bottomSpacerHeight, setBottomSpacerHeight] = useState(0)
150
160
  const [initPos, setInitPos] = useState(0)
161
+ const prevInitPos = useRef()
151
162
 
152
163
  const squared = backdrop
153
164
  ? isTopPosition && bottomSpacerHeight <= 0
154
165
  : isTopPosition
155
166
  const hasToolbarProps = !!Object.keys(toolbarProps).length
156
167
  const isClosable = !!onClose || backdrop
168
+ const renderSaferHeight =
169
+ initPos < prevInitPos.current ? prevInitPos.current - 16 : 0 // 16 because border radius
170
+ const showRenderSafer =
171
+ prevInitPos.current !== 0 && prevInitPos.current > initPos
157
172
 
158
173
  const styles = createStyles({
159
174
  squared,
160
175
  hasToolbarProps,
161
176
  offset,
177
+ renderSaferHeight,
162
178
  isBottomPosition
163
179
  })
164
180
  const overriddenChildren = makeOverridenChildren(children, headerContentRef)
@@ -207,14 +223,11 @@ const BottomSheet = memo(
207
223
  useEffect(() => {
208
224
  const headerContent = headerContentRef.current
209
225
  const innerContentHeight = innerContentRef.current.offsetHeight
210
- const actionButtonsHeight = headerContent
211
- ? parseFloat(getComputedStyle(headerContent).getPropertyValue('height'))
212
- : 0
213
- const actionButtonsBottomMargin = headerContent
214
- ? parseFloat(
215
- getComputedStyle(headerContent).getPropertyValue('padding-bottom')
216
- )
217
- : 0
226
+ const actionButtonsHeight = getCssValue(headerContent, 'height')
227
+ const actionButtonsBottomMargin = getCssValue(
228
+ headerContent,
229
+ 'padding-bottom'
230
+ )
218
231
 
219
232
  const maxHeight = computeMaxHeight(toolbarProps)
220
233
 
@@ -242,12 +255,29 @@ const BottomSheet = memo(
242
255
 
243
256
  if (computedMediumHeight >= maxHeight) {
244
257
  setIsTopPosition(true)
258
+ } else {
259
+ setIsTopPosition(false)
260
+ }
261
+
262
+ const newPeekHeights = [
263
+ ...new Set([minHeight, computedMediumHeight, maxHeight])
264
+ ]
265
+
266
+ const hasPeekHeightsChanged = peekHeights !== newPeekHeights
267
+
268
+ if (hasPeekHeightsChanged && isTopPosition) {
269
+ setCurrentIndex(v => v - 1)
245
270
  }
246
- setPeekHeights([...new Set([minHeight, computedMediumHeight, maxHeight])])
271
+
272
+ setPeekHeights(newPeekHeights)
273
+ prevInitPos.current = initPos
247
274
  setInitPos(computedMediumHeight)
248
275
  // Used so that the BottomSheet can be opened to the top without stopping at the content height
249
276
  setBottomSpacerHeight(bottomSpacerHeight)
277
+
278
+ // eslint-disable-next-line react-hooks/exhaustive-deps
250
279
  }, [
280
+ // initPos is missing, because we need the previous value
251
281
  innerContentRef,
252
282
  toolbarProps,
253
283
  mediumHeightRatio,
@@ -322,6 +352,15 @@ const BottomSheet = memo(
322
352
  </div>
323
353
  <div style={{ height: bottomSpacerHeight }} />
324
354
  </MuiBottomSheet>
355
+ {showRenderSafer && (
356
+ <div style={styles.renderSafer}>
357
+ <Paper
358
+ className={stylescss['renderSaferAnim']}
359
+ elevation={0}
360
+ square
361
+ />
362
+ </div>
363
+ )}
325
364
  {!isBottomPosition && (
326
365
  <>
327
366
  <Fade in timeout={ANIMATION_DURATION}>
@@ -136,3 +136,6 @@ export const computeBottomSpacer = ({
136
136
  // without backdrop, we want the bottomsheet to open to the top of the window
137
137
  return maxHeight - innerContentHeight
138
138
  }
139
+
140
+ export const getCssValue = (element, value) =>
141
+ element ? parseFloat(getComputedStyle(element).getPropertyValue(value)) : 0
@@ -0,0 +1,15 @@
1
+ .renderSaferAnim
2
+ position absolute
3
+ bottom 0
4
+ height 0
5
+ width 100%
6
+ animation slidein 1s
7
+
8
+ @keyframes slidein {
9
+ from {
10
+ height 100%
11
+ }
12
+ to {
13
+ height 0
14
+ }
15
+ }
@@ -1,9 +1,23 @@
1
- import ListItemIcon from '@material-ui/core/ListItemIcon'
1
+ import React, { forwardRef } from 'react'
2
+ import MuiListItemIcon from '@material-ui/core/ListItemIcon'
3
+
4
+ import cx from 'classnames'
2
5
 
3
6
  export const smallSize = 16
4
7
  export const mediumSize = 24
5
8
  export const largeSize = 32
6
9
 
10
+ // We add a specific class to be able to override the style in makeOverride when used in an other component
11
+ const ListItemIcon = forwardRef(({ className, ...props }, ref) => {
12
+ return (
13
+ <MuiListItemIcon
14
+ {...props}
15
+ ref={ref}
16
+ className={cx(className, 'cozyListItemIcon')}
17
+ />
18
+ )
19
+ })
20
+
7
21
  ListItemIcon.displayName = 'ListItemIcon'
8
22
 
9
23
  export default ListItemIcon
@@ -76,6 +76,9 @@ const hideMenu = () => setState({ showMenu: false })
76
76
  </ListItemIcon>
77
77
  <ListItemText primary="Attachment" />
78
78
  </MenuItem>
79
+ <MenuItem onClick={hideMenu}>
80
+ <ListItemText primary="Item without icon" />
81
+ </MenuItem>
79
82
 
80
83
  <Divider className="u-mv-half" />
81
84
 
@@ -293,25 +293,31 @@ export const makeOverrides = theme => ({
293
293
  gap: 16,
294
294
  paddingTop: 12,
295
295
  paddingBottom: 12,
296
+ minHeight: 56,
296
297
  '&.small': {
297
298
  paddingTop: 8,
298
- paddingBottom: 8
299
+ paddingBottom: 8,
300
+ minHeight: 48
299
301
  },
300
302
  '&.large': {
301
303
  paddingTop: 16,
302
- paddingBottom: 16
304
+ paddingBottom: 16,
305
+ minHeight: 64
303
306
  }
304
307
  },
305
308
  dense: {
306
309
  paddingTop: 8,
307
310
  paddingBottom: 8,
311
+ minHeight: 48,
308
312
  '&.small': {
309
313
  paddingTop: 4,
310
- paddingBottom: 4
314
+ paddingBottom: 4,
315
+ minHeight: 40
311
316
  },
312
317
  '&.large': {
313
318
  paddingTop: 12,
314
- paddingBottom: 12
319
+ paddingBottom: 12,
320
+ minHeight: 56
315
321
  }
316
322
  }
317
323
  },
@@ -347,16 +353,25 @@ export const makeOverrides = theme => ({
347
353
  },
348
354
  MuiMenuItem: {
349
355
  root: {
350
- gap: 8,
351
356
  maxWidth: 320,
352
357
  whiteSpace: 'normal',
353
358
  overflow: 'auto',
354
359
  paddingTop: 4,
355
- paddingBottom: 4
360
+ paddingBottom: 4,
361
+ [theme.breakpoints.up('sm')]: {
362
+ minHeight: 40
363
+ },
364
+ '&.cozyActionsMenuItem': {
365
+ minWidth: 256
366
+ },
367
+ '& .cozyListItemIcon': {
368
+ width: 16,
369
+ height: 16
370
+ }
356
371
  },
357
372
  gutters: {
358
- paddingLeft: 8,
359
- paddingRight: 8
373
+ paddingLeft: 16,
374
+ paddingRight: 16
360
375
  }
361
376
  },
362
377
  MuiFormLabel: {
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
2
  import cx from 'classnames'
3
3
 
4
4
  import Icon from '../Icon'
@@ -31,10 +31,16 @@ const HeaderComponent = ({ title, showBack, onClickBack }) => {
31
31
  }
32
32
 
33
33
  const SelfBottomSheet = props => {
34
+ const [, setInnerContentHeight] = useState(0) // tricks to rerender BottomSheet
35
+
34
36
  return (
35
37
  <BottomSheet backdrop onClose={props.onClose}>
36
38
  <BottomSheetItem disableGutters>
37
- <NestedSelect HeaderComponent={HeaderComponent} {...props} />
39
+ <NestedSelect
40
+ HeaderComponent={HeaderComponent}
41
+ setInnerContentHeight={setInnerContentHeight}
42
+ {...props}
43
+ />
38
44
  </BottomSheetItem>
39
45
  </BottomSheet>
40
46
  )
@@ -18,6 +18,7 @@ export { ItemRow }
18
18
  class NestedSelect extends Component {
19
19
  constructor(props) {
20
20
  super(props)
21
+ this.innerRef = React.createRef()
21
22
  this.state = {
22
23
  history: [props.options],
23
24
  searchValue: '',
@@ -29,6 +30,12 @@ class NestedSelect extends Component {
29
30
  this.unmounted = true
30
31
  }
31
32
 
33
+ componentDidUpdate() {
34
+ const { setInnerContentHeight } = this.props
35
+
36
+ setInnerContentHeight?.(this.innerRef?.current?.offsetHeight)
37
+ }
38
+
32
39
  resetHistory() {
33
40
  if (this.unmounted) {
34
41
  return
@@ -105,7 +112,7 @@ class NestedSelect extends Component {
105
112
  }
106
113
 
107
114
  return (
108
- <>
115
+ <span ref={this.innerRef}>
109
116
  {HeaderComponent ? (
110
117
  <HeaderComponent
111
118
  title={current.title || title}
@@ -171,7 +178,7 @@ class NestedSelect extends Component {
171
178
  ))
172
179
  )}
173
180
  </ContentComponent>
174
- </>
181
+ </span>
175
182
  )
176
183
  }
177
184
  }
@@ -170,7 +170,7 @@ const InteractiveExample = () => {
170
170
  radioPosition={variant.leftRadio ? 'left' : 'right'}
171
171
  title="Please select letter"
172
172
  transformParentItem={transformParentItem}
173
- searchOptions={variant.withSearch ? searchOpts : undefined}
173
+ searchOptions={variant.withSearch ? searchOptions : undefined}
174
174
  ellipsis={variant.withEllipsis}
175
175
  noDivider={variant.noDivider}
176
176
  />
@@ -1,9 +1,11 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- var _excluded = ["isListItem"];
4
+ var _excluded = ["isListItem", "className"];
4
5
  import React, { forwardRef } from 'react';
5
6
  import PropTypes from 'prop-types';
6
7
  import omit from 'lodash/omit';
8
+ import cx from 'classnames';
7
9
  import MenuItem from "cozy-ui/transpiled/react/MenuItem";
8
10
  import ListItem from "cozy-ui/transpiled/react/ListItem";
9
11
 
@@ -14,11 +16,13 @@ var cleanPropsForDOM = function cleanPropsForDOM(props) {
14
16
 
15
17
  var ActionsMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
16
18
  var isListItem = _ref.isListItem,
19
+ className = _ref.className,
17
20
  props = _objectWithoutProperties(_ref, _excluded);
18
21
 
19
22
  var Component = isListItem ? ListItem : MenuItem;
20
23
  var compProps = cleanPropsForDOM(props);
21
24
  return /*#__PURE__*/React.createElement(Component, _extends({}, compProps, {
25
+ className: cx(className, _defineProperty({}, 'cozyActionsMenuItem', !isListItem)),
22
26
  ref: ref,
23
27
  button: true,
24
28
  ellipsis: false
@@ -26,6 +30,8 @@ var ActionsMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
26
30
  });
27
31
  ActionsMenuItem.displayName = 'ActionsMenuItem';
28
32
  ActionsMenuItem.propTypes = {
33
+ className: PropTypes.string,
34
+
29
35
  /** Whether the ActionsMenuItem will return a ListItem or MenuItem */
30
36
  isListItem: PropTypes.bool
31
37
  };
@@ -11,17 +11,10 @@ var ActionsMenuMobileHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
11
11
  props = _objectWithoutProperties(_ref, _excluded);
12
12
 
13
13
  var _useBreakpoints = useBreakpoints(),
14
- isMobile = _useBreakpoints.isMobile; // To make accessibility work, we need to return a displayed item.
15
- // The trick is to return an empty one with no padding to simulate a display none.
16
- // Otherwise it will be impossible to use keyboard navigation for example
17
- // probably due to the inner workings of Mui
18
-
14
+ isMobile = _useBreakpoints.isMobile;
19
15
 
20
16
  if (!isMobile) {
21
- return /*#__PURE__*/React.createElement(ActionsMenuItem, _extends({}, props, {
22
- ref: ref,
23
- className: "u-p-0"
24
- }));
17
+ return null;
25
18
  }
26
19
 
27
20
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ActionsMenuItem, _extends({}, props, {
@@ -31,6 +24,7 @@ var ActionsMenuMobileHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
31
24
  className: "u-mv-half"
32
25
  }));
33
26
  });
27
+ ActionsMenuMobileHeader.displayName = 'ActionsMenuMobileHeader';
34
28
  ActionsMenuMobileHeader.propTypes = {
35
29
  children: PropTypes.node
36
30
  };
@@ -51,8 +51,17 @@ var ActionsMenuWrapper = function ActionsMenuWrapper(_ref) {
51
51
  return /*#__PURE__*/React.createElement(Menu, _extends({}, props, {
52
52
  open: open,
53
53
  onClose: onClose
54
- }), React.Children.map(children, function (child) {
54
+ }), React.Children.map(children, function (child, idx) {
55
+ var _firstChild$type, _firstChild$type2;
56
+
57
+ // To keep accessibility, we spread the autofocus on the second child
58
+ // if the first one is ActionsMenuMobileHeader
59
+ var firstChild = React.Children.toArray(children)[0];
60
+ var firstChildComponentName = (firstChild === null || firstChild === void 0 ? void 0 : (_firstChild$type = firstChild.type) === null || _firstChild$type === void 0 ? void 0 : _firstChild$type.name) || (firstChild === null || firstChild === void 0 ? void 0 : (_firstChild$type2 = firstChild.type) === null || _firstChild$type2 === void 0 ? void 0 : _firstChild$type2.displayName);
61
+ var isFirstChildActionsMenuMobileHeader = firstChildComponentName === 'ActionsMenuMobileHeader';
62
+ var autoFocus = isFirstChildActionsMenuMobileHeader && idx === 1 ? true : undefined;
55
63
  return /*#__PURE__*/React.isValidElement(child) ? /*#__PURE__*/React.cloneElement(child, {
64
+ autoFocus: autoFocus,
56
65
  onClick: overrideClick(child.props)
57
66
  }) : null;
58
67
  }));