cozy-ui 74.4.1 → 74.6.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 (43) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/assets/icons/ui/swap.svg +1 -0
  3. package/package.json +1 -1
  4. package/react/Icon/Readme.md +4 -2
  5. package/react/Icon/icons-sprite.js +1 -1
  6. package/react/Icons/Swap.jsx +16 -0
  7. package/react/Snackbar/Readme.md +10 -4
  8. package/react/Snackbar/index.js +12 -5
  9. package/react/Viewer/Footer/BottomSheetContent.jsx +2 -2
  10. package/react/Viewer/Footer/FooterContent.jsx +6 -7
  11. package/react/Viewer/Panel/ActionMenuWrapper.jsx +69 -0
  12. package/react/Viewer/Panel/Qualification.jsx +86 -63
  13. package/react/Viewer/Panel/QualificationListItemContact.jsx +76 -0
  14. package/react/Viewer/Panel/QualificationListItemDate.jsx +47 -0
  15. package/react/Viewer/Panel/QualificationListItemNumber.jsx +42 -0
  16. package/react/Viewer/Panel/QualificationListItemOther.jsx +64 -0
  17. package/react/Viewer/Readme.md +8 -9
  18. package/react/Viewer/ViewerContainer.jsx +23 -18
  19. package/react/Viewer/helpers.js +64 -0
  20. package/react/Viewer/helpers.spec.js +50 -1
  21. package/react/Viewer/{Footer → hooks}/useReferencedContactName.jsx +5 -2
  22. package/react/Viewer/locales/en.json +27 -8
  23. package/react/Viewer/locales/fr.json +27 -8
  24. package/react/Viewer/snackbar/ViewerSnackbar.jsx +30 -0
  25. package/react/Viewer/snackbar/ViewerSnackbarProvider.jsx +64 -0
  26. package/react/__snapshots__/examples.spec.jsx.snap +40 -24
  27. package/transpiled/react/Icon/icons-sprite.js +1 -1
  28. package/transpiled/react/Icons/Swap.js +15 -0
  29. package/transpiled/react/Snackbar/index.js +10 -5
  30. package/transpiled/react/Viewer/Footer/BottomSheetContent.js +2 -4
  31. package/transpiled/react/Viewer/Footer/FooterContent.js +7 -11
  32. package/transpiled/react/Viewer/Panel/ActionMenuWrapper.js +72 -0
  33. package/transpiled/react/Viewer/Panel/Qualification.js +104 -47
  34. package/transpiled/react/Viewer/Panel/QualificationListItemContact.js +89 -0
  35. package/transpiled/react/Viewer/Panel/QualificationListItemDate.js +49 -0
  36. package/transpiled/react/Viewer/Panel/QualificationListItemNumber.js +41 -0
  37. package/transpiled/react/Viewer/Panel/QualificationListItemOther.js +50 -0
  38. package/transpiled/react/Viewer/ViewerContainer.js +4 -2
  39. package/transpiled/react/Viewer/helpers.js +57 -0
  40. package/transpiled/react/Viewer/{Footer → hooks}/useReferencedContactName.js +3 -2
  41. package/transpiled/react/Viewer/snackbar/ViewerSnackbar.js +25 -0
  42. package/transpiled/react/Viewer/snackbar/ViewerSnackbarProvider.js +73 -0
  43. package/transpiled/react/Viewer/withViewerLocales.js +54 -16
package/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ # [74.6.0](https://github.com/cozy/cozy-ui/compare/v74.5.0...v74.6.0) (2022-09-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add Swap icon ([cb3dc06](https://github.com/cozy/cozy-ui/commit/cb3dc06))
7
+
8
+ # [74.5.0](https://github.com/cozy/cozy-ui/compare/v74.4.2...v74.5.0) (2022-09-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Change defaultProps to Snackbar component ([c749957](https://github.com/cozy/cozy-ui/commit/c749957))
14
+ * Disabling the Portal on the BottomSheet component in the Viewer ([35ede40](https://github.com/cozy/cozy-ui/commit/35ede40))
15
+ * Translations of the Qualification Viewer ([489f27a](https://github.com/cozy/cozy-ui/commit/489f27a))
16
+
17
+
18
+ ### Features
19
+
20
+ * Add ActionMenuWrapper component ([653e7d5](https://github.com/cozy/cozy-ui/commit/653e7d5))
21
+ * Add arrays of known metadata names ([ab84458](https://github.com/cozy/cozy-ui/commit/ab84458))
22
+ * Add helper for formatting all known metadata ([2fa5906](https://github.com/cozy/cozy-ui/commit/2fa5906))
23
+ * Add QualificationListItem's components ([93f2dd1](https://github.com/cozy/cozy-ui/commit/93f2dd1))
24
+ * Add QualificationListItem's components to Qualification ([c987346](https://github.com/cozy/cozy-ui/commit/c987346))
25
+ * Add ViewerSnackbar component to display alerts in Viewer ([1aef68c](https://github.com/cozy/cozy-ui/commit/1aef68c))
26
+ * Add ViewerSnackbarContext to manage internal Snackbar component ([ce4d2f1](https://github.com/cozy/cozy-ui/commit/ce4d2f1))
27
+ * Create an alert when the attribute is copied ([ccbd467](https://github.com/cozy/cozy-ui/commit/ccbd467))
28
+ * Implement ViewerSnackbar in ViewerContainer component ([3c5eeae](https://github.com/cozy/cozy-ui/commit/3c5eeae))
29
+ * Move `useReferencedContactName` call to Qualification component ([39ccd6e](https://github.com/cozy/cozy-ui/commit/39ccd6e))
30
+ * Update Viewer locales files ([f418f95](https://github.com/cozy/cozy-ui/commit/f418f95))
31
+
32
+ ## [74.4.2](https://github.com/cozy/cozy-ui/compare/v74.4.1...v74.4.2) (2022-09-08)
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * isLoadingContacts on useReferencedContactName hook ([dab3e09](https://github.com/cozy/cozy-ui/commit/dab3e09))
38
+
1
39
  ## [74.4.1](https://github.com/cozy/cozy-ui/compare/v74.4.0...v74.4.1) (2022-09-08)
2
40
 
3
41
 
@@ -0,0 +1 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11 0a2 2 0 0 1 2 2h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h1a2 2 0 0 1 2-2h6Zm2 12h1V4h-1v8ZM3 4v8H2V4h1Zm2-2v12h6V2H5Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "74.4.1",
3
+ "version": "74.6.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -218,6 +218,7 @@ import Stack from 'cozy-ui/transpiled/react/Icons/Stack'
218
218
  import Star from 'cozy-ui/transpiled/react/Icons/Star'
219
219
  import Stats from 'cozy-ui/transpiled/react/Icons/Stats'
220
220
  import Subway from 'cozy-ui/transpiled/react/Icons/Subway'
221
+ import Swap from 'cozy-ui/transpiled/react/Icons/Swap'
221
222
  import Sync from 'cozy-ui/transpiled/react/Icons/Sync'
222
223
  import SyncCozy from 'cozy-ui/transpiled/react/Icons/SyncCozy'
223
224
  import Tag from 'cozy-ui/transpiled/react/Icons/Tag'
@@ -437,6 +438,7 @@ const icons = [
437
438
  Star,
438
439
  Stats,
439
440
  Subway,
441
+ Swap,
440
442
  Sync,
441
443
  SyncCozy,
442
444
  Tag,
@@ -599,7 +601,7 @@ const icons = [
599
601
  BottomSelectIcon,
600
602
  CheckWhiteIcon,
601
603
  DashWhiteIcon,
602
- KeychainIcon
604
+ KeychainIcon,
603
605
  ]
604
606
 
605
607
  const wrapperStyle = {
@@ -850,7 +852,7 @@ import Typography from 'cozy-ui/transpiled/react/Typography'
850
852
 
851
853
  const colors = ['#297EF2', '#08b442', '#B449E7', '#F52D2D', '#FF962F']
852
854
  let i = 0
853
- const availableIcons = ['album-add','album-remove','album','answer','apple','archive','attachment','attention','bank','banking-add','banking','bell','bike','bill','bottom','browser-brave','browser-chrome','browser-duckduckgo','browser-edge','browser-edge-chromium','browser-firefox','browser-ie','browser-opera','browser-safari','burger','bus','calendar','camera','car','carbonCopy','categories','certified','check-circle','check-list','check-square','check', 'checkbox','circle-filled','clock','cloud-happy','cloud','collect','comment','company','compass','connector','contract','contrast','copy','cozy-circle','cozy-laugh','cozy-text','credit-card-add','credit-card','credit','crop','cross-circle','cross-medium','cross-small','cross','cube','dash','dashboard','data-control','debit','devices','dots','down','download','drawing-arrow-up','dropdown-close','dropdown-open','dropdown','dropup','email-notification','email','eu','euro','exchange','eye-closed','eye','file-add','file-duotone','file-new','file-none','file-outline','file','filter','fingerprint','fitness','flag-outlined','flag','flash-auto','flashlight','folder-add','folder-moveto','folder','forbidden','from-user','gear','globe','graph-circle','grid','group-list','groups','hand','heart','help','help-outlined','history','home','hourglass','image','info-outlined','info','key','laptop','left','lightbulb','link-out','link','list','location','lock', 'lock-screen', 'logout','magic-trick','magnet','magnifier','merge','mountain','movement-in','movement-out','mouvement','moveto','multi-files','music','new','next','note','notification-email','offline','online', 'openapp', 'openwith','palette','paper','paperplane','password','pen','people','percent-circle','percent','personal-data','phone-download','phone-upload','phone','pie-chart','pin','plane','plus-small','plus','previous','printer','qualify','radio-checked','radio-unchecked','refresh','repare','reply','restaurant','restore-straight','restore','right','rise','rotate-left','rotate-right','sad-cozy','safe','school','select-all','setting','share-circle','share','shield','shop','sound','spinner','stack','star','stats','subway','sync-cozy','sync','tag','target','team','telephone','to-the-cloud','top','train','trash','trophy','unknow','unlink','unlock','up','upload','videos','walk','wallet-add','wallet-new','wallet','warn','warning-circle','warning','wrench-circle']
855
+ const availableIcons = ['album-add','album-remove','album','answer','apple','archive','attachment','attention','bank','banking-add','banking','bell','bike','bill','bottom','browser-brave','browser-chrome','browser-duckduckgo','browser-edge','browser-edge-chromium','browser-firefox','browser-ie','browser-opera','browser-safari','burger','bus','calendar','camera','car','carbonCopy','categories','certified','check-circle','check-list','check-square','check', 'checkbox','circle-filled','clock','cloud-happy','cloud','collect','comment','company','compass','connector','contract','contrast','copy','cozy-circle','cozy-laugh','cozy-text','credit-card-add','credit-card','credit','crop','cross-circle','cross-medium','cross-small','cross','cube','dash','dashboard','data-control','debit','devices','dots','down','download','drawing-arrow-up','dropdown-close','dropdown-open','dropdown','dropup','email-notification','email','eu','euro','exchange','eye-closed','eye','file-add','file-duotone','file-new','file-none','file-outline','file','filter','fingerprint','fitness','flag-outlined','flag','flash-auto','flashlight','folder-add','folder-moveto','folder','forbidden','from-user','gear','globe','graph-circle','grid','group-list','groups','hand','heart','help','help-outlined','history','home','hourglass','image','info-outlined','info','key','laptop','left','lightbulb','link-out','link','list','location','lock', 'lock-screen', 'logout','magic-trick','magnet','magnifier','merge','mountain','movement-in','movement-out','mouvement','moveto','multi-files','music','new','next','note','notification-email','offline','online', 'openapp', 'openwith','palette','paper','paperplane','password','pen','people','percent-circle','percent','personal-data','phone-download','phone-upload','phone','pie-chart','pin','plane','plus-small','plus','previous','printer','qualify','radio-checked','radio-unchecked','refresh','repare','reply','restaurant','restore-straight','restore','right','rise','rotate-left','rotate-right','sad-cozy','safe','school','select-all','setting','share-circle','share','shield','shop','sound','spinner','stack','star','stats','subway', 'swap', 'sync-cozy','sync','tag','target','team','telephone','to-the-cloud','top','train','trash','trophy','unknow','unlink','unlock','up','upload','videos','walk','wallet-add','wallet-new','wallet','warn','warning-circle','warning','wrench-circle']
854
856
  ;
855
857
  <div style={{ fontSize: '2rem', display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)' }}>
856
858
  <Sprite />
@@ -520,7 +520,7 @@ module.exports = `<svg><defs>
520
520
  <path fill-rule="evenodd" d="M8 12l-4.702 2.472.898-5.236L.392 5.528l5.257-.764L8 0l2.351 4.764 5.257.764-3.804 3.708.898 5.236z"/>
521
521
  </symbol><symbol id="stats" viewBox="0 0 16 16">
522
522
  <path fill-rule="evenodd" d="M13 4h2a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1M7 0h2a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1M1 8h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1"/>
523
- </symbol><symbol id="subway" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.64.64C11.2.072 9.488 0 8 0 6.512 0 4.8.072 3.36.64 1.224 1.472 0 3.24 0 5.488V16h16V5.488C16 3.24 14.776 1.472 12.64.64ZM13 5.857v5c0 1.07-.785 1.958-1.811 2.118l1.097 1.096v.358h-1.429L9.43 13H6.57l-1.428 1.429H3.714v-.358l1.097-1.096A2.143 2.143 0 0 1 3 10.857v-5C3 3.714 5.143 3 8 3s5 .714 5 2.857Zm-8.571-.714V8h7.142V5.143H4.43ZM6.57 10.5a1.071 1.071 0 1 1-2.142 0 1.071 1.071 0 0 1 2.142 0Zm3.929 1.071a1.071 1.071 0 1 0 0-2.142 1.071 1.071 0 0 0 0 2.142Z"/></symbol><symbol id="sync-cozy" viewBox="0 0 16 16">
523
+ </symbol><symbol id="subway" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.64.64C11.2.072 9.488 0 8 0 6.512 0 4.8.072 3.36.64 1.224 1.472 0 3.24 0 5.488V16h16V5.488C16 3.24 14.776 1.472 12.64.64ZM13 5.857v5c0 1.07-.785 1.958-1.811 2.118l1.097 1.096v.358h-1.429L9.43 13H6.57l-1.428 1.429H3.714v-.358l1.097-1.096A2.143 2.143 0 0 1 3 10.857v-5C3 3.714 5.143 3 8 3s5 .714 5 2.857Zm-8.571-.714V8h7.142V5.143H4.43ZM6.57 10.5a1.071 1.071 0 1 1-2.142 0 1.071 1.071 0 0 1 2.142 0Zm3.929 1.071a1.071 1.071 0 1 0 0-2.142 1.071 1.071 0 0 0 0 2.142Z"/></symbol><symbol id="swap" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M11 0a2 2 0 0 1 2 2h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h1a2 2 0 0 1 2-2h6Zm2 12h1V4h-1v8ZM3 4v8H2V4h1Zm2-2v12h6V2H5Z"/></symbol><symbol id="sync-cozy" viewBox="0 0 16 16">
524
524
  <g fill="none" fill-rule="evenodd">
525
525
  <g fill="#297EF1">
526
526
  <path d="M8 2c2.21 0 4 1.79 4 4 2.21 0 4 1.79 4 4s-1.79 4-4 4H4c-2.21 0-4-1.79-4-4s1.79-4 4-4c0-2.21 1.79-4 4-4zM4.6 9.014c-.12-.025-.242-.004-.345.055l-.038.024c-.042.029-.08.063-.112.104l-.012.014c-.034.047-.059.1-.074.16l-.006.04C4.008 9.441 4 9.469 4 9.5v2c0 .276.224.5.5.5.237 0 .426-.168.478-.39C5.728 12.459 6.818 13 8 13c1.547 0 2.956-.899 3.632-2.268.122-.247.02-.547-.228-.67-.248-.121-.547-.02-.67.228C10.227 11.322 9.162 12 8 12c-.74 0-1.436-.286-1.976-.754.135-.007.267-.064.36-.176.177-.212.148-.527-.064-.704l-1.5-1.25c-.005-.005-.012-.006-.017-.011l-.04-.024c-.04-.025-.08-.045-.124-.057zM8 5c-1.57 0-2.992.906-3.653 2.291-.119.25-.013.548.236.667.25.118.548.013.667-.236C5.746 6.682 6.817 6 8 6c.769 0 1.477.287 2.017.758-.149-.005-.298.048-.401.172-.177.212-.148.527.064.704l1.5 1.25.011.007c.01.008.023.013.034.02.04.028.083.05.127.063l.04.01c.054.012.11.017.163.01.005 0 .009.002.013.001.063-.01.118-.031.17-.059.014-.007.025-.016.038-.024.04-.027.076-.059.107-.097l.02-.021c.034-.047.06-.1.077-.158.003-.01.002-.02.004-.03.008-.034.016-.068.016-.106v-2c0-.276-.224-.5-.5-.5-.226 0-.41.152-.471.357C10.284 5.523 9.196 5 7.999 5z"/>
@@ -0,0 +1,16 @@
1
+ // Automatically created, please run `scripts/generate-svgr-icon.sh assets/icons/ui/swap.svg` to regenerate;
2
+ import React from 'react'
3
+
4
+ function SvgSwap(props) {
5
+ return (
6
+ <svg viewBox="0 0 16 16" {...props}>
7
+ <path
8
+ fillRule="evenodd"
9
+ clipRule="evenodd"
10
+ d="M11 0a2 2 0 012 2h1a2 2 0 012 2v8a2 2 0 01-2 2h-1a2 2 0 01-2 2H5a2 2 0 01-2-2H2a2 2 0 01-2-2V4a2 2 0 012-2h1a2 2 0 012-2h6zm2 12h1V4h-1v8zM3 4v8H2V4h1zm2-2v12h6V2H5z"
11
+ />
12
+ </svg>
13
+ )
14
+ }
15
+
16
+ export default SvgSwap
@@ -7,6 +7,9 @@ import IconButton from 'cozy-ui/transpiled/react/IconButton'
7
7
  import Typography from 'cozy-ui/transpiled/react/Typography'
8
8
  import Icon from 'cozy-ui/transpiled/react/Icon'
9
9
  import InfoIcon from 'cozy-ui/transpiled/react/Icons/Info'
10
+ import {
11
+ BreakpointsProvider
12
+ } from 'cozy-ui/transpiled/react/hooks/useBreakpoints'
10
13
 
11
14
  initialState = { open: false }
12
15
 
@@ -14,7 +17,7 @@ const handleToggle = () => {setState(state => ({ open: !state.open }))}
14
17
 
15
18
  ;
16
19
 
17
- <>
20
+ <BreakpointsProvider>
18
21
  <Button
19
22
  variant="ghost"
20
23
  size="small"
@@ -34,7 +37,7 @@ const handleToggle = () => {setState(state => ({ open: !state.open }))}
34
37
  }
35
38
  onClose={handleToggle}
36
39
  />
37
- </>
40
+ </BreakpointsProvider>
38
41
  ```
39
42
 
40
43
  ### With `Alert` inside the `Snackbar`
@@ -44,6 +47,9 @@ import Snackbar from 'cozy-ui/transpiled/react/Snackbar'
44
47
  import Alert from 'cozy-ui/transpiled/react/Alert'
45
48
  import Button from 'cozy-ui/transpiled/react/Buttons'
46
49
  import Variants from 'cozy-ui/docs/components/Variants'
50
+ import {
51
+ BreakpointsProvider
52
+ } from 'cozy-ui/transpiled/react/hooks/useBreakpoints'
47
53
 
48
54
  initialState = { open: isTesting() }
49
55
 
@@ -54,7 +60,7 @@ const initialVariants = [{ primary: true, secondary: true, success: false, error
54
60
 
55
61
  ;
56
62
 
57
- <>
63
+ <BreakpointsProvider>
58
64
  <Variants initialVariants={initialVariants} radio>
59
65
  {variant => (
60
66
  <>
@@ -77,5 +83,5 @@ const initialVariants = [{ primary: true, secondary: true, success: false, error
77
83
  label="Open snackbar"
78
84
  onClick={handleToggle}
79
85
  />
80
- </>
86
+ </BreakpointsProvider>
81
87
  ```
@@ -1,9 +1,19 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import MuiSnackbar from '@material-ui/core/Snackbar'
3
+ import useBreakpoints from '../hooks/useBreakpoints'
3
4
 
4
5
  const Snackbar = forwardRef(({ children, ...props }, ref) => {
6
+ const { isDesktop } = useBreakpoints()
7
+
5
8
  return (
6
- <MuiSnackbar ref={ref} {...props}>
9
+ <MuiSnackbar
10
+ ref={ref}
11
+ anchorOrigin={{
12
+ vertical: !isDesktop ? 'bottom' : 'top',
13
+ horizontal: 'center'
14
+ }}
15
+ {...props}
16
+ >
7
17
  {children}
8
18
  </MuiSnackbar>
9
19
  )
@@ -12,10 +22,7 @@ const Snackbar = forwardRef(({ children, ...props }, ref) => {
12
22
  Snackbar.displayName = 'Snackbar'
13
23
 
14
24
  Snackbar.defaultProps = {
15
- anchorOrigin: {
16
- vertical: 'top',
17
- horizontal: 'center'
18
- }
25
+ autoHideDuration: 2000
19
26
  }
20
27
 
21
28
  export default Snackbar
@@ -5,7 +5,7 @@ import { BottomSheetItem } from '../../BottomSheet'
5
5
 
6
6
  import getPanelBlocks, { panelBlocksSpecs } from '../Panel/getPanelBlocks'
7
7
 
8
- const BottomSheetContent = ({ file, contactsFullname }) => {
8
+ const BottomSheetContent = ({ file }) => {
9
9
  const panelBlocks = getPanelBlocks({ panelBlocksSpecs, file })
10
10
 
11
11
  return panelBlocks.map((PanelBlock, index) => (
@@ -14,7 +14,7 @@ const BottomSheetContent = ({ file, contactsFullname }) => {
14
14
  disableGutters
15
15
  disableElevation={index === panelBlocks.length - 1}
16
16
  >
17
- <PanelBlock file={file} contactsFullname={contactsFullname} />
17
+ <PanelBlock file={file} />
18
18
  </BottomSheetItem>
19
19
  ))
20
20
  }
@@ -7,7 +7,6 @@ import BottomSheet, { BottomSheetHeader } from '../../BottomSheet'
7
7
 
8
8
  import { isValidForPanel } from '../helpers'
9
9
  import BottomSheetContent from './BottomSheetContent'
10
- import useReferencedContactName from './useReferencedContactName'
11
10
 
12
11
  const useStyles = makeStyles(theme => ({
13
12
  footer: {
@@ -30,8 +29,6 @@ const FooterContent = ({ file, toolbarRef, children }) => {
30
29
 
31
30
  const toolbarProps = useMemo(() => ({ ref: toolbarRef }), [toolbarRef])
32
31
 
33
- const { contactName, isLoadingContacts } = useReferencedContactName(file)
34
-
35
32
  const FooterActionButtons =
36
33
  Children.toArray(children).find(child => {
37
34
  return (
@@ -46,16 +43,18 @@ const FooterContent = ({ file, toolbarRef, children }) => {
46
43
  })
47
44
  : null
48
45
 
49
- // We have to wait for the Contact request to finish before rendering `BottomSheet`, because it doesn't handle async well.
50
- if (isValidForPanel({ file }) && !isLoadingContacts) {
46
+ if (isValidForPanel({ file })) {
51
47
  return (
52
- <BottomSheet toolbarProps={toolbarProps}>
48
+ <BottomSheet
49
+ toolbarProps={toolbarProps}
50
+ portalProps={{ disablePortal: true }}
51
+ >
53
52
  <BottomSheetHeader
54
53
  className={cx('u-ph-1 u-pb-1', styles.bottomSheetHeader)}
55
54
  >
56
55
  {FooterActionButtonsWithFile}
57
56
  </BottomSheetHeader>
58
- <BottomSheetContent file={file} contactsFullname={contactName} />
57
+ <BottomSheetContent file={file} />
59
58
  </BottomSheet>
60
59
  )
61
60
  }
@@ -0,0 +1,69 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import List from '../../MuiCozyTheme/List'
5
+ import ListItem from '../../MuiCozyTheme/ListItem'
6
+ import ListItemIcon from '../../MuiCozyTheme/ListItemIcon'
7
+ import ListItemText from '../../ListItemText'
8
+ import Icon from '../../Icon'
9
+ import Copy from '../../Icons/Copy'
10
+ import BottomSheet, { BottomSheetItem } from '../../BottomSheet'
11
+ import ActionMenu, { ActionMenuItem } from '../../ActionMenu'
12
+ import Typography from '../../Typography'
13
+ import useBreakpoints from '../../hooks/useBreakpoints'
14
+ import { useI18n } from '../../I18n'
15
+ import useViewerSnackbar from '../snackbar/ViewerSnackbarProvider'
16
+
17
+ const ActionMenuWrapper = forwardRef(({ onClose, value }, ref) => {
18
+ const { isMobile } = useBreakpoints()
19
+ const { t } = useI18n()
20
+ const { showViewerSnackbar } = useViewerSnackbar()
21
+
22
+ const handleCopy = () => {
23
+ if (navigator?.clipboard) {
24
+ navigator.clipboard.writeText(value)
25
+ showViewerSnackbar(
26
+ 'secondary',
27
+ t(`Viewer.snackbar.copiedToClipboard.success`)
28
+ )
29
+ } else {
30
+ showViewerSnackbar('error', t(`Viewer.snackbar.copiedToClipboard.error`))
31
+ }
32
+ onClose()
33
+ }
34
+
35
+ if (isMobile) {
36
+ return (
37
+ <BottomSheet backdrop onClose={onClose}>
38
+ <BottomSheetItem disableGutters>
39
+ <List>
40
+ <ListItem button onClick={handleCopy}>
41
+ <ListItemIcon>
42
+ <Icon icon={Copy} />
43
+ </ListItemIcon>
44
+ <ListItemText
45
+ primary={t(`Viewer.panel.qualification.actions.copyClipboard`)}
46
+ />
47
+ </ListItem>
48
+ </List>
49
+ </BottomSheetItem>
50
+ </BottomSheet>
51
+ )
52
+ }
53
+
54
+ return (
55
+ <ActionMenu onClose={onClose} anchorElRef={ref}>
56
+ <ActionMenuItem onClick={handleCopy} left={<Icon icon={Copy} />}>
57
+ <Typography>{t(`Viewer.panel.qualification.actions.copy`)}</Typography>
58
+ </ActionMenuItem>
59
+ </ActionMenu>
60
+ )
61
+ })
62
+ ActionMenuWrapper.displayName = 'ActionMenuWrapper'
63
+
64
+ ActionMenuWrapper.propTypes = {
65
+ onClose: PropTypes.func,
66
+ value: PropTypes.string
67
+ }
68
+
69
+ export default ActionMenuWrapper
@@ -1,84 +1,107 @@
1
- import React from 'react'
1
+ import React, { useRef, useState, createRef, useMemo, useEffect } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
- import { models } from 'cozy-client'
5
-
6
4
  import List from '../../MuiCozyTheme/List'
7
- import ListItem from '../../MuiCozyTheme/ListItem'
8
- import QualificationListItemText from './QualificationListItemText'
9
5
  import { withViewerLocales } from '../withViewerLocales'
10
- import MidEllipsis from '../../MidEllipsis'
6
+ import {
7
+ formatMetadataQualification,
8
+ knownDateMetadataNames,
9
+ knowNumberMetadataNames,
10
+ knowOtherMetadataNames
11
+ } from '../helpers'
12
+ import QualificationListItemContact from './QualificationListItemContact'
13
+ import ActionMenuWrapper from './ActionMenuWrapper'
14
+ import QualificationListItemDate from './QualificationListItemDate'
15
+ import QualificationListItemNumber from './QualificationListItemNumber'
16
+ import QualificationListItemOther from './QualificationListItemOther'
17
+
18
+ const Qualification = ({ file = {} }) => {
19
+ const { metadata = {} } = file
20
+ const actionBtnRef = useRef([])
21
+ const [optionFile, setOptionFile] = useState({
22
+ isOpen: false,
23
+ id: '',
24
+ value: ''
25
+ })
26
+
27
+ const hideActionsMenu = () => {
28
+ setOptionFile({ isOpen: false, id: '', value: '' })
29
+ }
11
30
 
12
- const {
13
- document: {
14
- locales: { getBoundT }
31
+ const toggleActionsMenu = (id, value) => {
32
+ setOptionFile(prev => {
33
+ if (prev.isOpen) return { isOpen: false, id: '', value: '' }
34
+ return { isOpen: true, id, value }
35
+ })
15
36
  }
16
- } = models
17
37
 
18
- const Qualification = ({ file = {}, contactsFullname, t, f, lang }) => {
19
- const scannerT = getBoundT(lang)
38
+ const metadataComputed = useMemo(() => {
39
+ return formatMetadataQualification(metadata)
40
+ }, [metadata])
20
41
 
21
- const { name: filename, metadata = {} } = file
22
- const {
23
- qualification = {},
24
- page: pageLabel,
25
- datetime,
26
- datetimeLabel
27
- } = metadata
42
+ useEffect(() => {
43
+ actionBtnRef.current = metadataComputed.map(
44
+ (_, idx) => actionBtnRef.current[idx] ?? createRef()
45
+ )
46
+ }, [metadataComputed])
28
47
 
29
48
  return (
30
49
  <List className={'u-pv-1'}>
31
- {datetime && (
32
- <ListItem className={'u-ph-2'}>
33
- <QualificationListItemText
34
- primary={t(
35
- `Viewer.panel.qualification.date.title.${
36
- datetimeLabel === 'datetime' || datetimeLabel === undefined
37
- ? 'addedOn'
38
- : datetimeLabel
39
- }`
40
- )}
41
- secondary={f(datetime, 'DD/MM/YYYY')}
42
- />
43
- </ListItem>
44
- )}
45
- {contactsFullname && (
46
- <ListItem className={'u-ph-2'}>
47
- <QualificationListItemText
48
- primary={t('Viewer.panel.qualification.identity')}
49
- secondary={contactsFullname}
50
- />
51
- </ListItem>
52
- )}
53
- <ListItem className={'u-ph-2'}>
54
- <QualificationListItemText
55
- primary={t('Viewer.panel.qualification.label.title')}
56
- secondary={
57
- <MidEllipsis
58
- text={
59
- pageLabel
60
- ? t(`Viewer.panel.qualification.label.${pageLabel}`)
61
- : filename
62
- }
50
+ {metadataComputed.map((meta, idx) => {
51
+ const { name } = meta
52
+
53
+ if (knownDateMetadataNames.includes(name)) {
54
+ return (
55
+ <QualificationListItemDate
56
+ key={idx}
57
+ ref={actionBtnRef.current[idx]}
58
+ metadataComputed={meta}
59
+ toggleActionsMenu={val => toggleActionsMenu(idx, val)}
63
60
  />
61
+ )
62
+ }
63
+
64
+ if (knowNumberMetadataNames.includes(name)) {
65
+ return (
66
+ <QualificationListItemNumber
67
+ key={idx}
68
+ ref={actionBtnRef.current[idx]}
69
+ metadataComputed={meta}
70
+ toggleActionsMenu={val => toggleActionsMenu(idx, val)}
71
+ />
72
+ )
73
+ }
74
+
75
+ if (knowOtherMetadataNames.includes(name)) {
76
+ if (name === 'owner') {
77
+ return <QualificationListItemContact key={idx} file={file} />
64
78
  }
79
+
80
+ return (
81
+ <QualificationListItemOther
82
+ key={idx}
83
+ ref={actionBtnRef.current[idx]}
84
+ filename={file.name}
85
+ metadataComputed={meta}
86
+ toggleActionsMenu={val => toggleActionsMenu(idx, val)}
87
+ />
88
+ )
89
+ }
90
+ })}
91
+
92
+ {optionFile.isOpen && (
93
+ <ActionMenuWrapper
94
+ onClose={hideActionsMenu}
95
+ value={optionFile.value}
96
+ ref={actionBtnRef.current[optionFile.id]}
65
97
  />
66
- </ListItem>
67
- <ListItem className={'u-ph-2'}>
68
- <QualificationListItemText
69
- primary={t('Viewer.panel.qualification.qualification')}
70
- secondary={scannerT(`Scan.items.${qualification.label}`)}
71
- />
72
- </ListItem>
98
+ )}
73
99
  </List>
74
100
  )
75
101
  }
76
102
 
77
103
  Qualification.propTypes = {
78
- file: PropTypes.object.isRequired,
79
- t: PropTypes.func.isRequired,
80
- f: PropTypes.func.isRequired,
81
- lang: PropTypes.string.isRequired
104
+ file: PropTypes.object
82
105
  }
83
106
 
84
- export default withViewerLocales(Qualification)
107
+ export default withViewerLocales(React.memo(Qualification))
@@ -0,0 +1,76 @@
1
+ import React, { useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import ListItem from '../../MuiCozyTheme/ListItem'
5
+ import ListItemSecondaryAction from '../../MuiCozyTheme/ListItemSecondaryAction'
6
+ import IconButton from '../../IconButton'
7
+ import Icon from '../../Icon'
8
+ import Dots from '../../Icons/Dots'
9
+ import QualificationListItemText from './QualificationListItemText'
10
+ import Spinner from '../../Spinner'
11
+ import useReferencedContactName from '../hooks/useReferencedContactName'
12
+ import { useI18n } from '../../I18n'
13
+ import ActionMenuWrapper from './ActionMenuWrapper'
14
+
15
+ const QualificationListItemContact = ({ file }) => {
16
+ const { t } = useI18n()
17
+ const actionBtnRef = useRef()
18
+ const [optionFile, setOptionFile] = useState({
19
+ isOpen: false,
20
+ value: ''
21
+ })
22
+
23
+ const hideActionsMenu = () => setOptionFile({ isOpen: false, value: '' })
24
+ const toggleActionsMenu = value =>
25
+ setOptionFile(prev => {
26
+ if (prev.isOpen) return { isOpen: false, value: '' }
27
+ return { isOpen: true, value }
28
+ })
29
+
30
+ const { contactName, isLoadingContacts } = useReferencedContactName(file)
31
+
32
+ if (isLoadingContacts) {
33
+ return (
34
+ <ListItem className={'u-pl-2 u-pr-3'}>
35
+ <Spinner color="var(--secondaryTextColor)" />
36
+ </ListItem>
37
+ )
38
+ }
39
+
40
+ if (!isLoadingContacts && !contactName) {
41
+ return null
42
+ }
43
+
44
+ return (
45
+ <>
46
+ <ListItem className={'u-ph-2'}>
47
+ <QualificationListItemText
48
+ primary={t('Viewer.panel.qualification.owner')}
49
+ secondary={contactName}
50
+ />
51
+ <ListItemSecondaryAction>
52
+ <IconButton
53
+ ref={actionBtnRef}
54
+ onClick={() => toggleActionsMenu(contactName)}
55
+ >
56
+ <Icon icon={Dots} />
57
+ </IconButton>
58
+ </ListItemSecondaryAction>
59
+ </ListItem>
60
+
61
+ {optionFile.isOpen && (
62
+ <ActionMenuWrapper
63
+ onClose={hideActionsMenu}
64
+ value={optionFile.value}
65
+ ref={actionBtnRef}
66
+ />
67
+ )}
68
+ </>
69
+ )
70
+ }
71
+
72
+ QualificationListItemContact.propTypes = {
73
+ file: PropTypes.object.isRequired
74
+ }
75
+
76
+ export default QualificationListItemContact
@@ -0,0 +1,47 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import ListItem from '../../MuiCozyTheme/ListItem'
5
+ import ListItemSecondaryAction from '../../MuiCozyTheme/ListItemSecondaryAction'
6
+ import IconButton from '../../IconButton'
7
+ import Icon from '../../Icon'
8
+ import Dots from '../../Icons/Dots'
9
+ import QualificationListItemText from './QualificationListItemText'
10
+ import { useI18n } from '../../I18n'
11
+ import { formatDate } from '../helpers'
12
+
13
+ const QualificationListItemDate = forwardRef(
14
+ ({ metadataComputed, toggleActionsMenu }, ref) => {
15
+ const { t, f, lang } = useI18n()
16
+ const { name, value } = metadataComputed
17
+ const formattedDate = formatDate({ f, lang, date: value })
18
+
19
+ return (
20
+ <ListItem className={'u-pl-2 u-pr-3'}>
21
+ <QualificationListItemText
22
+ primary={t(`Viewer.panel.qualification.date.title.${name}`)}
23
+ secondary={formattedDate}
24
+ />
25
+ <ListItemSecondaryAction>
26
+ <IconButton
27
+ ref={ref}
28
+ onClick={() => toggleActionsMenu(formattedDate)}
29
+ >
30
+ <Icon icon={Dots} />
31
+ </IconButton>
32
+ </ListItemSecondaryAction>
33
+ </ListItem>
34
+ )
35
+ }
36
+ )
37
+ QualificationListItemDate.displayName = 'QualificationListItemDate'
38
+
39
+ QualificationListItemDate.propTypes = {
40
+ metadataComputed: PropTypes.shape({
41
+ name: PropTypes.string,
42
+ value: PropTypes.string
43
+ }).isRequired,
44
+ toggleActionsMenu: PropTypes.func.isRequired
45
+ }
46
+
47
+ export default QualificationListItemDate