cozy-ui 133.0.0-beta.1 → 133.0.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,38 @@
1
+ # [133.0.0](https://github.com/cozy/cozy-ui/compare/v132.1.0...v133.0.0) (2025-10-30)
2
+
3
+
4
+ ### Features
5
+
6
+ * **ActionsMenu/ActionsItems:** No longer pass `client` in actions implicitly ([aeaa262](https://github.com/cozy/cozy-ui/commit/aeaa262))
7
+ * **Actions:** Some actions doesn't import `downloadFile` anymore ([df7a36f](https://github.com/cozy/cozy-ui/commit/df7a36f))
8
+ * **Actions:** Some actions doesn't import `fetchBlobFileById, isFile` anymore ([98cf97f](https://github.com/cozy/cozy-ui/commit/98cf97f))
9
+ * **Actions:** Some actions doesn't import `generateWebLink` anymore ([809011e](https://github.com/cozy/cozy-ui/commit/809011e))
10
+ * **Actions:** Some actions doesn't import `splitFilename` anymore ([c13841c](https://github.com/cozy/cozy-ui/commit/c13841c))
11
+ * **BarContextProvider:** Removed ([c080cd2](https://github.com/cozy/cozy-ui/commit/c080cd2))
12
+ * Remove all components related to cozy-client ([840168a](https://github.com/cozy/cozy-ui/commit/840168a))
13
+ * Remove deprecated `modify` and `viewInContacts` actions ([17dc240](https://github.com/cozy/cozy-ui/commit/17dc240))
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * If you use a component in that list, you must import it from `cozy-ui-plus` instead: `AppIcon, AppLinker, AppSections, AppTitle, CipherIcon, ContactPicker, Contacts/AddModal, Contacts/GroupsSelect, Contacts/Header, ContactsList, ContactsListModal, AuthenticationDialogs, Field, FileImageLoader, FilePicker, hooks/useClientErrors, IntentDialogOpener, IntentIframe, Labs/CollectionField, ListItem/ListItemByDoc, ListItem/ListItemBase, ListItem/ListItemContact, ListItem/ListItemFile, Paywall, providers/CozyTheme, providers/Intent, QualificationGrid, QualificationIcon, QualificationItem, QualificationModal, ShortcutTile, SquareAppIcon, Storage, UploadQueue, Wizard`.
19
+
20
+ Please note that these components have been completely removed: `deprecated/IntentModal, deprecated/IntentOpener, deprecated/QuotaAlert`
21
+ * If you used `deprecated/ActionMenu/Actions/modif` and `deprecated/ActionMenu/Actions/viewInContacts` please use those in `/ActionsMenu/Actions` instead
22
+ * **BarContextProvider:** If you use `BarContextProvider` you must create it yourself. Take a look to the code to copy/paste
23
+ * **Actions:** You must `import { fetchBlobFileById, isFile } from 'cozy-client/dist/models/file'` and pass it as action's option `makeActions([], { fetchBlobFileById, isFile })` when using `print` action.
24
+ * **Actions:** You must `import { downloadFile } from 'cozy-client/dist/models/file'` and pass it as action's option `makeActions([], { downloadFile })` when using `download` action.
25
+ * **Actions:** You must `import { splitFilename } from 'cozy-client/dist/models/file'` and pass it as action's option `makeActions([], { splitFilename })` when using `addToFavorites`, `removeFromFavorites` actions.
26
+ * **Actions:** You must `import { generateWebLink } from 'cozy-client'` and pass it as action's option `makeActions([], { generateWebLink })` when using `modify`, `viewInContacts`, `viewInDrive` actions.
27
+ * **ActionsMenu/ActionsItems:** You have to pass `client` yourself as option when creating an action, for actions that used `client` as arg `action: (docs, { client }) => {}` . The simplest way to do that: `const actions = makeActions([ ...someActions ], { client })`
28
+
29
+ # [132.1.0](https://github.com/cozy/cozy-ui/compare/v132.0.0...v132.1.0) (2025-10-30)
30
+
31
+
32
+ ### Features
33
+
34
+ * Add shared drive icons :sparkles: ([2eaee77](https://github.com/cozy/cozy-ui/commit/2eaee77))
35
+
1
36
  # [132.0.0](https://github.com/cozy/cozy-ui/compare/v131.2.0...v132.0.0) (2025-10-27)
2
37
 
3
38
 
@@ -0,0 +1 @@
1
+ <svg width="16" height="13" viewBox="0 0 16 13" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_13717_2258)"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.4 1.62H8L6.4 0H1.6C.72 0 0 .73 0 1.62v9.75c0 .89.71 1.62 1.59 1.62h12.8c.88 0 1.6-.73 1.6-1.62V3.25c0-.89-.72-1.62-1.6-1.62l.01-.01ZM11.09 8.9c0 .93-.77 1.7-1.7 1.7H6c-.93 0-1.7-.77-1.7-1.7V5.51c0-.93.77-1.7 1.7-1.7h.77c.19 0 .31.12.31.31s-.12.31-.31.31H6c-.59 0-1.08.49-1.08 1.08V8.9c0 .59.49 1.08 1.08 1.08h3.39c.59 0 1.08-.49 1.08-1.08v-.46c0-.19.12-.31.31-.31s.31.12.31.31v.46Zm.52-2.71L9.76 8.04c-.09.09-.22.12-.34.06-.12-.03-.19-.15-.19-.28v-.93h-.62c-.83 0-1.54.52-1.79 1.33a.3.3 0 0 1-.28.22c-.13 0-.25-.09-.31-.22-.06-.22-.09-.46-.09-.71 0-1.36 1.11-2.47 2.47-2.47h.62v-.93c0-.12.06-.25.19-.28.12-.06.25-.03.34.06l1.85 1.85c.12.12.12.31 0 .43v.02Z" fill="#A6A6A6"/></g><defs><clipPath id="clip0_13717_2258"><path fill="#fff" d="M0 0h16v13H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg width="96" height="78" viewBox="0 0 96 78" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48 9.75h38.4c5.28 0 9.6 4.386 9.6 9.75v48.75c0 5.364-4.32 9.75-9.6 9.75H9.6C4.32 78 0 73.614 0 68.25l.048-58.5C.048 4.386 4.32 0 9.6 0h28.8L48 9.75Z" fill="url(#paint0_linear_13714_2236)"/><path d="M56.673 62.55H39.176c-4.772 0-8.749-3.976-8.749-8.748V36.305c0-4.772 3.977-8.748 8.749-8.748h3.976c.955 0 1.591.636 1.591 1.59 0 .955-.636 1.59-1.59 1.59h-3.977c-3.022 0-5.567 2.546-5.567 5.568v17.497c0 3.022 2.545 5.567 5.567 5.567h17.497c3.022 0 5.567-2.545 5.567-5.567v-2.386c0-.955.636-1.59 1.59-1.59.955 0 1.591.635 1.591 1.59v2.386c0 4.772-3.977 8.748-8.748 8.748ZM42.039 51.416c-.636 0-1.273-.477-1.59-1.114-.319-1.113-.478-2.386-.478-3.658 0-6.999 5.726-12.725 12.725-12.725h3.181v-4.772c0-.636.319-1.272.955-1.431.636-.319 1.272-.16 1.75.318l9.543 9.543c.636.637.636 1.591 0 2.227l-9.544 9.544c-.477.477-1.113.636-1.75.318-.636-.159-.954-.795-.954-1.431v-4.772h-3.18c-4.296 0-7.954 2.704-9.227 6.84-.158.636-.795 1.113-1.431 1.113Z" fill="#8DBCFF"/><path d="M56.673 61.55H39.176c-4.772 0-8.749-3.976-8.749-8.748V35.305c0-4.772 3.977-8.748 8.749-8.748h3.976c.955 0 1.591.636 1.591 1.59 0 .955-.636 1.59-1.59 1.59h-3.977c-3.022 0-5.567 2.546-5.567 5.568v17.497c0 3.022 2.545 5.567 5.567 5.567h17.497c3.022 0 5.567-2.545 5.567-5.567v-2.386c0-.955.636-1.59 1.59-1.59.955 0 1.591.635 1.591 1.59v2.386c0 4.772-3.977 8.748-8.748 8.748ZM42.039 50.416c-.636 0-1.273-.477-1.59-1.114-.319-1.113-.478-2.386-.478-3.658 0-6.999 5.726-12.725 12.725-12.725h3.181v-4.772c0-.636.319-1.272.955-1.431.636-.319 1.272-.16 1.75.318l9.543 9.543c.636.637.636 1.591 0 2.227l-9.544 9.544c-.477.477-1.113.636-1.75.318-.636-.159-.954-.795-.954-1.431v-4.772h-3.18c-4.296 0-7.954 2.704-9.227 6.84-.158.636-.795 1.113-1.431 1.113Z" fill="url(#paint1_linear_13714_2236)"/><defs><linearGradient id="paint0_linear_13714_2236" x1="48" y1="0" x2="48" y2="94.5" gradientUnits="userSpaceOnUse"><stop offset=".044" stop-color="#1D7AFF"/><stop offset=".13" stop-color="#7CB2FF"/><stop offset=".617" stop-color="#76AFFF"/><stop offset="1" stop-color="#4290FF"/></linearGradient><linearGradient id="paint1_linear_13714_2236" x1="49.092" y1="11.943" x2="49.447" y2="68.954" gradientUnits="userSpaceOnUse"><stop stop-color="#76AFFF"/><stop offset=".781" stop-color="#1D7AFF"/></linearGradient></defs></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "133.0.0-beta.1",
3
+ "version": "133.0.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -201,5 +201,6 @@
201
201
  },
202
202
  "browserslist": [
203
203
  "extends browserslist-config-cozy"
204
- ]
204
+ ],
205
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
205
206
  }
@@ -730,6 +730,8 @@ import FileTypeServerIcon from 'cozy-ui/transpiled/react/Icons/FileTypeServer'
730
730
  import FileTypeImageIcon from 'cozy-ui/transpiled/react/Icons/FileTypeImage'
731
731
  import FileTypeNoteIcon from 'cozy-ui/transpiled/react/Icons/FileTypeNote'
732
732
  import FileTypePdfIcon from 'cozy-ui/transpiled/react/Icons/FileTypePdf'
733
+ import FileTypeSharedDriveIcon from 'cozy-ui/transpiled/react/Icons/FileTypeSharedDrive'
734
+ import FileTypeSharedDriveGreyIcon from 'cozy-ui/transpiled/react/Icons/FileTypeSharedDriveGrey'
733
735
  import FileTypeSheetIcon from 'cozy-ui/transpiled/react/Icons/FileTypeSheet'
734
736
  import FileTypeSlideIcon from 'cozy-ui/transpiled/react/Icons/FileTypeSlide'
735
737
  import FileTypeTextIcon from 'cozy-ui/transpiled/react/Icons/FileTypeText'
@@ -782,6 +784,8 @@ const icons = [
782
784
  FileTypeImageIcon,
783
785
  FileTypeNoteIcon,
784
786
  FileTypePdfIcon,
787
+ FileTypeSharedDriveIcon,
788
+ FileTypeSharedDriveGreyIcon,
785
789
  FileTypeSheetIcon,
786
790
  FileTypeSlideIcon,
787
791
  FileTypeTextIcon,
@@ -801,7 +805,7 @@ const icons = [
801
805
  PhotosIcon,
802
806
  TopSelectIcon,
803
807
  TrashDuotoneIcon,
804
- TwakeWorkplace,
808
+ TwakeWorkplace
805
809
  ]
806
810
 
807
811
  initialState = { size: 16 }
@@ -1026,9 +1030,7 @@ import Typography from 'cozy-ui/transpiled/react/Typography'
1026
1030
  import Grid from 'cozy-ui/transpiled/react/Grid'
1027
1031
  import Sprite from 'cozy-ui/transpiled/react/Icon/Sprite'
1028
1032
 
1029
- const availableIcons = ['account', 'bottom-select', 'check-white', 'cloud-broken', 'contacts', 'cozy-authentification', 'cozy-logo', 'cozy-upgrade', 'credit-card-large', 'dash-white', 'device-browser', 'device-laptop', 'device-phone', 'device-tablet', 'file-type-audio', 'file-type-banking-account' , 'file-type-bin', 'file-type-code', 'file-type-files', 'file-type-folder', 'file-type-server', 'file-type-image', 'file-type-note', 'file-type-pdf', 'file-type-sheet', 'file-type-slide', 'file-type-text', 'file-type-video', 'file-type-zip', 'forbidden-sign', 'google', 'keychain', 'logout-large', 'papers', 'only-office', 'store', 'top-select', 'trash-duotone', 'cozy']
1030
-
1031
- ;
1033
+ const availableIcons = ['account', 'bottom-select', 'check-white', 'cloud-broken', 'contacts', 'cozy-authentification', 'cozy-logo', 'cozy-upgrade', 'credit-card-large', 'dash-white', 'device-browser', 'device-laptop', 'device-phone', 'device-tablet', 'file-type-audio', 'file-type-banking-account' , 'file-type-bin', 'file-type-code', 'file-type-files', 'file-type-folder', 'file-type-server', 'file-type-shared-drive', 'file-type-shared-drive-grey', 'file-type-image', 'file-type-note', 'file-type-pdf', 'file-type-sheet', 'file-type-slide', 'file-type-text', 'file-type-video', 'file-type-zip', 'forbidden-sign', 'google', 'keychain', 'logout-large', 'papers', 'only-office', 'store', 'top-select', 'trash-duotone', 'cozy'];
1032
1034
 
1033
1035
  <Grid container spacing={2}>
1034
1036
  <Sprite />
@@ -0,0 +1,51 @@
1
+ // Automatically created, please run `scripts/generate-svgr-icon.sh assets/icons/ui/file-type-shared-drive.svg` to regenerate;
2
+ import React from 'react'
3
+
4
+ function SvgFileTypeSharedDrive(props) {
5
+ return (
6
+ <svg viewBox="0 0 96 78" fill="none" {...props}>
7
+ <path
8
+ fillRule="evenodd"
9
+ clipRule="evenodd"
10
+ d="M48 9.75h38.4c5.28 0 9.6 4.386 9.6 9.75v48.75c0 5.364-4.32 9.75-9.6 9.75H9.6C4.32 78 0 73.614 0 68.25l.048-58.5C.048 4.386 4.32 0 9.6 0h28.8L48 9.75z"
11
+ fill="url(#file-type-shared-drive_svg__paint0_linear_13714_2236)"
12
+ />
13
+ <path
14
+ d="M56.673 62.55H39.176c-4.772 0-8.749-3.976-8.749-8.748V36.305c0-4.772 3.977-8.748 8.749-8.748h3.976c.955 0 1.591.636 1.591 1.59 0 .955-.636 1.59-1.59 1.59h-3.977c-3.022 0-5.567 2.546-5.567 5.568v17.497c0 3.022 2.545 5.567 5.567 5.567h17.497c3.022 0 5.567-2.545 5.567-5.567v-2.386c0-.955.636-1.59 1.59-1.59.955 0 1.591.635 1.591 1.59v2.386c0 4.772-3.977 8.748-8.748 8.748zM42.039 51.416c-.636 0-1.273-.477-1.59-1.114-.319-1.113-.478-2.386-.478-3.658 0-6.999 5.726-12.725 12.725-12.725h3.181v-4.772c0-.636.319-1.272.955-1.431.636-.319 1.272-.16 1.75.318l9.543 9.543c.636.637.636 1.591 0 2.227l-9.544 9.544c-.477.477-1.113.636-1.75.318-.636-.159-.954-.795-.954-1.431v-4.772h-3.18c-4.296 0-7.954 2.704-9.227 6.84-.158.636-.795 1.113-1.431 1.113z"
15
+ fill="#8DBCFF"
16
+ />
17
+ <path
18
+ d="M56.673 61.55H39.176c-4.772 0-8.749-3.976-8.749-8.748V35.305c0-4.772 3.977-8.748 8.749-8.748h3.976c.955 0 1.591.636 1.591 1.59 0 .955-.636 1.59-1.59 1.59h-3.977c-3.022 0-5.567 2.546-5.567 5.568v17.497c0 3.022 2.545 5.567 5.567 5.567h17.497c3.022 0 5.567-2.545 5.567-5.567v-2.386c0-.955.636-1.59 1.59-1.59.955 0 1.591.635 1.591 1.59v2.386c0 4.772-3.977 8.748-8.748 8.748zM42.039 50.416c-.636 0-1.273-.477-1.59-1.114-.319-1.113-.478-2.386-.478-3.658 0-6.999 5.726-12.725 12.725-12.725h3.181v-4.772c0-.636.319-1.272.955-1.431.636-.319 1.272-.16 1.75.318l9.543 9.543c.636.637.636 1.591 0 2.227l-9.544 9.544c-.477.477-1.113.636-1.75.318-.636-.159-.954-.795-.954-1.431v-4.772h-3.18c-4.296 0-7.954 2.704-9.227 6.84-.158.636-.795 1.113-1.431 1.113z"
19
+ fill="url(#file-type-shared-drive_svg__paint1_linear_13714_2236)"
20
+ />
21
+ <defs>
22
+ <linearGradient
23
+ id="file-type-shared-drive_svg__paint0_linear_13714_2236"
24
+ x1={48}
25
+ y1={0}
26
+ x2={48}
27
+ y2={94.5}
28
+ gradientUnits="userSpaceOnUse"
29
+ >
30
+ <stop offset={0.044} stopColor="#1D7AFF" />
31
+ <stop offset={0.13} stopColor="#7CB2FF" />
32
+ <stop offset={0.617} stopColor="#76AFFF" />
33
+ <stop offset={1} stopColor="#4290FF" />
34
+ </linearGradient>
35
+ <linearGradient
36
+ id="file-type-shared-drive_svg__paint1_linear_13714_2236"
37
+ x1={49.092}
38
+ y1={11.943}
39
+ x2={49.447}
40
+ y2={68.954}
41
+ gradientUnits="userSpaceOnUse"
42
+ >
43
+ <stop stopColor="#76AFFF" />
44
+ <stop offset={0.781} stopColor="#1D7AFF" />
45
+ </linearGradient>
46
+ </defs>
47
+ </svg>
48
+ )
49
+ }
50
+
51
+ export default SvgFileTypeSharedDrive
@@ -0,0 +1,24 @@
1
+ // Automatically created, please run `scripts/generate-svgr-icon.sh assets/icons/ui/file-type-shared-drive-grey.svg` to regenerate;
2
+ import React from 'react'
3
+
4
+ function SvgFileTypeSharedDriveGrey(props) {
5
+ return (
6
+ <svg viewBox="0 0 16 13" fill="none" {...props}>
7
+ <g clipPath="url(#file-type-shared-drive-grey_svg__clip0_13717_2258)">
8
+ <path
9
+ fillRule="evenodd"
10
+ clipRule="evenodd"
11
+ d="M14.4 1.62H8L6.4 0H1.6C.72 0 0 .73 0 1.62v9.75c0 .89.71 1.62 1.59 1.62h12.8c.88 0 1.6-.73 1.6-1.62V3.25c0-.89-.72-1.62-1.6-1.62l.01-.01zM11.09 8.9c0 .93-.77 1.7-1.7 1.7H6c-.93 0-1.7-.77-1.7-1.7V5.51c0-.93.77-1.7 1.7-1.7h.77c.19 0 .31.12.31.31s-.12.31-.31.31H6c-.59 0-1.08.49-1.08 1.08V8.9c0 .59.49 1.08 1.08 1.08h3.39c.59 0 1.08-.49 1.08-1.08v-.46c0-.19.12-.31.31-.31s.31.12.31.31v.46zm.52-2.71L9.76 8.04c-.09.09-.22.12-.34.06-.12-.03-.19-.15-.19-.28v-.93h-.62c-.83 0-1.54.52-1.79 1.33a.3.3 0 01-.28.22c-.13 0-.25-.09-.31-.22-.06-.22-.09-.46-.09-.71 0-1.36 1.11-2.47 2.47-2.47h.62v-.93c0-.12.06-.25.19-.28.12-.06.25-.03.34.06l1.85 1.85c.12.12.12.31 0 .43v.02z"
12
+ fill="#A6A6A6"
13
+ />
14
+ </g>
15
+ <defs>
16
+ <clipPath id="file-type-shared-drive-grey_svg__clip0_13717_2258">
17
+ <path fill="#fff" d="M0 0h16v13H0z" />
18
+ </clipPath>
19
+ </defs>
20
+ </svg>
21
+ )
22
+ }
23
+
24
+ export default SvgFileTypeSharedDriveGrey
@@ -0,0 +1,114 @@
1
+ const sortBy = require('lodash/sortBy')
2
+ const flattenDeep = require('lodash/flattenDeep')
3
+ const { sleep } = require('./helpers')
4
+
5
+ const pushAll = (arr, items) => arr.push.apply(arr, items)
6
+
7
+ const formatLink = (parsedStyleguideURL, relativeLink) => {
8
+ if (parsedStyleguideURL.protocol === 'file:') {
9
+ return `file://${relativeLink}`
10
+ } else {
11
+ return `${parsedStyleguideURL.toString()}${relativeLink}`
12
+ }
13
+ }
14
+
15
+ const getComponentNameFromTestId = testId => {
16
+ return testId.split('-example-')[0]
17
+ }
18
+
19
+ /**
20
+ * Fetch all available components on the styleguide and returns an array
21
+ * of { name, link } describing each component.
22
+ * Components are sorted by name.
23
+ */
24
+ const fetchAllComponents = async (page, args, config) => {
25
+ const styleguideIndexURL = `${args.styleguideUrl}/index.html`
26
+
27
+ console.log(`➡️ Opening styleguide ${styleguideIndexURL}`)
28
+ await page.goto(styleguideIndexURL, {
29
+ waitUntil: 'load',
30
+ timeout: 0
31
+ })
32
+
33
+ console.log('➡️ Extracting links')
34
+
35
+ // We want to take screenshot for individual example, so we :
36
+ // - extract categories (link from the side menu with no ?id=)
37
+ // - go to category's page
38
+ // - look for `.rsg--controls-40 a` which is the open isolated for examples
39
+ // - look for its closest data-testid to get the name
40
+ const categoriesName = await page.evaluate(() => {
41
+ const sidebarSelector = '.rsg--sidebar-4'
42
+ return Array.from(document.querySelectorAll(`${sidebarSelector} a`))
43
+ .filter(v => !v.href.includes('?id='))
44
+ .map(x => x.text)
45
+ .filter(x => x !== 'Cozy-ui documentation') // see section's name in styleguide.config.js
46
+ })
47
+
48
+ const sortedCategoriesNames = sortBy(
49
+ categoriesName.map(catName => ({
50
+ link: styleguideIndexURL + '#/' + catName,
51
+ name: catName
52
+ })),
53
+ x => x.name
54
+ )
55
+
56
+ const allLinks = []
57
+ const parsedStyleguideURL = new URL(args.styleguideUrl)
58
+
59
+ for (const cate of sortedCategoriesNames) {
60
+ await page.goto(cate.link, { waitUntil: 'load', timeout: 0 })
61
+ await sleep(100)
62
+
63
+ const componentLinks = flattenDeep(
64
+ await page.evaluate(config => {
65
+ const componentSectionSelector = '.rsg--root-23'
66
+ const exampleToolbarSelector = '.rsg--toolbar-41'
67
+ const componentToolbarSelector = '.rsg--toolbar-11'
68
+ const openIsolatedButtonSelector = '.rsg--button-21'
69
+ const componentContainers = document.querySelectorAll(
70
+ componentSectionSelector
71
+ )
72
+
73
+ return Array.from(componentContainers, componentContainer => {
74
+ const componentId = componentContainer.dataset.testid.replace(
75
+ '-container',
76
+ ''
77
+ )
78
+ const perExampleScreenshot =
79
+ config[componentId] && config[componentId].perExampleScreenshot
80
+
81
+ const isolateButtons = componentContainer.querySelectorAll(
82
+ `${
83
+ perExampleScreenshot
84
+ ? exampleToolbarSelector
85
+ : componentToolbarSelector
86
+ } ${openIsolatedButtonSelector}`
87
+ )
88
+
89
+ return Array.from(isolateButtons).map(btn => {
90
+ const testId = btn.dataset.testid.replace('-isolate-button', '')
91
+
92
+ return {
93
+ testId,
94
+ link: btn.getAttribute('href')
95
+ }
96
+ })
97
+ })
98
+ }, config)
99
+ )
100
+
101
+ pushAll(
102
+ allLinks,
103
+ componentLinks.map(link => ({
104
+ ...link,
105
+ link: formatLink(parsedStyleguideURL, link.link),
106
+ name: getComponentNameFromTestId(link.testId)
107
+ }))
108
+ )
109
+ }
110
+
111
+ return allLinks
112
+ }
113
+
114
+ module.exports = fetchAllComponents
@@ -0,0 +1,39 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+
4
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
5
+
6
+ const readConfig = async () => {
7
+ const configPath = path.join(process.cwd(), 'rsgscreenshots.json')
8
+ if (fs.existsSync(configPath)) {
9
+ return JSON.parse(fs.readFileSync(configPath))
10
+ } else {
11
+ return {}
12
+ }
13
+ }
14
+
15
+ const builtinViewports = {
16
+ mobile: '320x480',
17
+ desktop: '800x600'
18
+ }
19
+
20
+ const parseViewportArgument = viewportStr => {
21
+ viewportStr = builtinViewports[viewportStr] || viewportStr
22
+ const splitted = viewportStr.split('x')
23
+ if (!splitted[1]) {
24
+ console.warn(
25
+ `Viewport format unsupported (${viewportStr}), supported format example: 800x600. You can also use built-in viewport names: ${Object.keys(
26
+ builtinViewports
27
+ ).join(', ')}.`
28
+ )
29
+ throw new Error('Bad viewport format')
30
+ }
31
+ return { width: parseInt(splitted[0]), height: parseInt(splitted[1]) }
32
+ }
33
+
34
+ module.exports = {
35
+ sleep,
36
+ readConfig,
37
+ builtinViewports,
38
+ parseViewportArgument
39
+ }
@@ -0,0 +1,61 @@
1
+ const path = require('path')
2
+ const { ArgumentParser } = require('argparse')
3
+ const { builtinViewports } = require('./helpers')
4
+
5
+ const pathArgument = p => {
6
+ if (p.startsWith('/')) {
7
+ return p
8
+ } else {
9
+ return path.join(process.cwd(), p)
10
+ }
11
+ }
12
+
13
+ const urlArgument = rawURL => {
14
+ const c = new URL(rawURL) //eslint-disable-line no-unused-vars
15
+ return rawURL
16
+ }
17
+
18
+ const makeParser = () => {
19
+ const parser = new ArgumentParser()
20
+
21
+ parser.addArgument('--mode', {
22
+ choices: ['react', 'kss'],
23
+ defaultValue: 'react'
24
+ })
25
+ parser.addArgument('--screenshot-dir', {
26
+ required: true,
27
+ dest: 'screenshotDir',
28
+ type: pathArgument
29
+ })
30
+ parser.addArgument('--styleguide-url', {
31
+ required: true,
32
+ dest: 'styleguideUrl',
33
+ type: urlArgument
34
+ })
35
+ parser.addArgument('--kss-dir', {
36
+ required: true,
37
+ dest: 'kssDir',
38
+ type: pathArgument
39
+ })
40
+ parser.addArgument('--viewport', {
41
+ defaultValue: builtinViewports.desktop
42
+ })
43
+ parser.addArgument('--no-empty-screenshot-dir', {
44
+ action: 'storeFalse',
45
+ defaultValue: true,
46
+ dest: 'emptyScreenshotDir'
47
+ })
48
+ parser.addArgument('--component')
49
+ parser.addArgument('--cache-file', {
50
+ defaultValue: '/tmp/cozy-ui-e2e-screenshots-cache.json',
51
+ dest: 'cacheFile'
52
+ })
53
+ parser.addArgument('--no-cache', {
54
+ dest: 'cacheFile',
55
+ action: 'storeFalse'
56
+ })
57
+
58
+ return parser
59
+ }
60
+
61
+ module.exports = makeParser
@@ -0,0 +1,58 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+
4
+ const emptyDirectory = directory => {
5
+ for (const filename of fs.readdirSync(directory)) {
6
+ fs.unlinkSync(path.join(directory, filename))
7
+ }
8
+ }
9
+
10
+ /**
11
+ * Ensure directories are ready for taking screenshots.
12
+ *
13
+ * - Throws if styleguide has not been built
14
+ * - Creates the screenshot dir if it does not exist
15
+ *
16
+ * @param {string} options.screenshotDir - Where to store screenshots
17
+ * @param {boolean} options.emptyScreenshotDir - Whether to empty the screenshot dir
18
+ *
19
+ */
20
+ const prepareFS = async options => {
21
+ const { screenshotDir, emptyScreenshotDir } = options
22
+ if (!fs.existsSync(screenshotDir)) {
23
+ console.log(`Creating screenshot directory ${screenshotDir}`)
24
+ fs.mkdirSync(screenshotDir)
25
+ } else if (emptyScreenshotDir) {
26
+ console.log(`Emptying screenshot directory ${screenshotDir}`)
27
+ emptyDirectory(screenshotDir)
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Opens and configure browser and page.
33
+ * Returns { browser, page }
34
+ */
35
+ const prepareBrowser = async (puppeteer, options) => {
36
+ const browser = await puppeteer.launch({
37
+ headless: 'new',
38
+ executablePath: `/usr/bin/google-chrome` // could be `/Applications/Chrome.app/Contents/MacOS/Chrome` on MacOS
39
+ })
40
+ const page = await browser.newPage()
41
+ // Put Argos in user agent
42
+ await page.setUserAgent(
43
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36; Argos'
44
+ )
45
+ await page.emulateMediaFeatures([
46
+ { name: 'prefers-reduced-motion', value: 'reduce' }
47
+ ])
48
+ page.setViewport(options.viewport)
49
+ await page.setDefaultNavigationTimeout(0)
50
+ await page.evaluateOnNewDocument(({ type, variant }) => {
51
+ localStorage.clear()
52
+ localStorage.setItem('ui-theme-type', type)
53
+ localStorage.setItem('ui-theme-variant', variant)
54
+ }, options.theme)
55
+ return { browser, page }
56
+ }
57
+
58
+ module.exports = { prepareFS, prepareBrowser }
@@ -0,0 +1,68 @@
1
+ const path = require('path')
2
+
3
+ const { sleep } = require('./helpers')
4
+
5
+ const rootDirectory = path.join(__dirname, '../')
6
+
7
+ const formatViewport = viewport => `${viewport.width}x${viewport.height}`
8
+
9
+ const getDefaultScreenshotName = ({
10
+ component,
11
+ viewport,
12
+ suffix,
13
+ type,
14
+ variant
15
+ }) =>
16
+ `${component.testId}-${
17
+ suffix ? `${suffix}-` : ''
18
+ }${type}-${variant}-${formatViewport(viewport)}.png`
19
+
20
+ /**
21
+ * Screenshot a component to the screenshot directory, taking care of
22
+ * resizing the viewport before-hand so that the viewport fits the
23
+ * component.
24
+ */
25
+ const screenshotComponent = async (page, options) => {
26
+ const { component, screenshotDir, viewport, type, variant, componentConfig } =
27
+ options
28
+ const { link, name } = component
29
+
30
+ await page.goto(link)
31
+ await page.addStyleTag({ content: 'body {height: auto;}' }) // to resize viewport according to its content
32
+ await sleep(200) // to be sure the page is entirely loaded
33
+
34
+ const getScreenshotName =
35
+ options.getScreenshotName || getDefaultScreenshotName
36
+
37
+ const screenshot = async suffix => {
38
+ await page.screenshot({
39
+ path: path.join(
40
+ screenshotDir,
41
+ getScreenshotName({ component, viewport, suffix, type, variant })
42
+ ),
43
+ fullPage: componentConfig?.fullPage ?? true,
44
+ optimizeForSpeed: true,
45
+ captureBeyondViewport: false
46
+ })
47
+ }
48
+
49
+ await page.bringToFront()
50
+
51
+ if (options.componentConfig && options.componentConfig.script) {
52
+ const componentScript = require(path.join(
53
+ rootDirectory,
54
+ options.componentConfig.script
55
+ ))
56
+ console.log(`Executing custom script for ${name}`)
57
+ await componentScript(page, screenshot)
58
+ } else {
59
+ console.log(
60
+ `Screenshotting ${name} for ${type}-${variant} theme at ${formatViewport(
61
+ viewport
62
+ )}`
63
+ )
64
+ await screenshot()
65
+ }
66
+ }
67
+
68
+ module.exports = screenshotComponent
@@ -0,0 +1,60 @@
1
+ const path = require('path')
2
+
3
+ const LINK_BASE = 'file://'
4
+
5
+ const screenshotKSSStyleguide = async (page, args) => {
6
+ const resolveLink = relativePage =>
7
+ `${LINK_BASE}${path.join(args.kssDir, relativePage)}`
8
+ const kssPage = resolveLink('index.html')
9
+ await page.goto(kssPage)
10
+
11
+ // We do not screenshot utilities because
12
+ // - their screenshots are huge
13
+ // - they do not change often and the probability of mistake is low
14
+ const ignore = [/^utilities/]
15
+
16
+ const WIDTH = 800
17
+ await page.setViewport({ width: WIDTH, height: 800 })
18
+ const links = await page.evaluate(() => {
19
+ const navLinks = Array.from(
20
+ document.querySelectorAll('.kss-nav > .kss-nav__item > a[href]')
21
+ )
22
+ return navLinks.map(node => node.getAttribute('href'))
23
+ })
24
+
25
+ for (const link of links) {
26
+ await page
27
+ await page.goto(resolveLink(link))
28
+
29
+ const sections = Array.from(await page.$$('.kss-section--depth-2')).concat(
30
+ Array.from(await page.$$('.kss-section--depth-3'))
31
+ )
32
+ for (let section of sections) {
33
+ const idProp = await section.getProperty('id')
34
+ const idValue = await idProp.jsonValue()
35
+ const id = idValue.replace('kssref-', '')
36
+ const ref = await section.$eval('.kss-section__ref', el => el.textContent)
37
+
38
+ if (ignore.filter(rx => rx.exec(id)).length > 0) {
39
+ console.log('Ignoring', id)
40
+ continue
41
+ }
42
+
43
+ // Need to resize the viewport otherwise screenshots are blank
44
+ const body = await page.$('body')
45
+ const bodySize = await body.boundingBox()
46
+ await page.setViewport({
47
+ height: Math.ceil(bodySize.height),
48
+ width: WIDTH
49
+ })
50
+
51
+ console.log('Screenshotting section', idValue)
52
+ await page.screenshot({
53
+ clip: await section.boundingBox(),
54
+ path: path.join(args.screenshotDir, `kss-${ref}-${id}.png`)
55
+ })
56
+ }
57
+ }
58
+ }
59
+
60
+ module.exports = screenshotKSSStyleguide
@@ -0,0 +1,74 @@
1
+ const fs = require('fs')
2
+
3
+ const { parseViewportArgument } = require('./helpers')
4
+ const fetchAllComponents = require('./fetchAllComponents')
5
+ const screenshotComponent = require('./screenshotComponent')
6
+
7
+ const loadJSON = filename => {
8
+ try {
9
+ return JSON.parse(fs.readFileSync(filename).toString())
10
+ } catch {
11
+ return null
12
+ }
13
+ }
14
+
15
+ const cacheToDisk = (fnToCache, options) =>
16
+ async function() {
17
+ const { cacheFile } = options
18
+ let res
19
+ if (cacheFile) {
20
+ res = loadJSON(cacheFile)
21
+ }
22
+ if (res) {
23
+ options.onLoadCache && options.onLoadCache()
24
+ } else {
25
+ res = await fnToCache.apply(this, arguments)
26
+ if (cacheFile) {
27
+ fs.writeFileSync(cacheFile, JSON.stringify(res, null, 2))
28
+ options.onSaveCache && options.onSaveCache()
29
+ }
30
+ }
31
+ return res
32
+ }
33
+
34
+ const screenshotReactStyleguide = async (page, args, config, theme) => {
35
+ const cachedFetchAllComponents = cacheToDisk(fetchAllComponents, {
36
+ cacheFile: args.cacheFile,
37
+ onLoadCache: () =>
38
+ console.log(`Using cached component list from ${args.cacheFile}`),
39
+ onSaveCache: () => console.log(`Saved component list to ${args.cacheFile}`)
40
+ })
41
+
42
+ let components = await cachedFetchAllComponents(page, args, config)
43
+ if (args.component) {
44
+ components = components.filter(component =>
45
+ component.name.includes(args.component)
46
+ )
47
+ }
48
+
49
+ console.log('⌛ Screenshotting components...')
50
+
51
+ for (const component of components) {
52
+ // Skip components in Deprecated folder
53
+ if (component.link.includes('Deprecated')) continue
54
+
55
+ const componentConfig = config[component.name] || {}
56
+ const componentViewportSpec =
57
+ (componentConfig.viewports && componentConfig.viewports[args.viewport]) ||
58
+ null
59
+ const componentViewport = componentViewportSpec
60
+ ? parseViewportArgument(componentViewportSpec)
61
+ : parseViewportArgument(args.viewport)
62
+ await page.setViewport(componentViewport)
63
+ await screenshotComponent(page, {
64
+ component,
65
+ componentConfig,
66
+ screenshotDir: args.screenshotDir,
67
+ viewport: componentViewport,
68
+ type: theme.type,
69
+ variant: theme.variant
70
+ })
71
+ }
72
+ }
73
+
74
+ module.exports = screenshotReactStyleguide