cozy-ui 123.2.1 → 124.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +11 -6
  3. package/react/Filename/Readme.md +10 -8
  4. package/react/Filename/index.jsx +56 -10
  5. package/react/Filename/styles.styl +3 -0
  6. package/react/MuiCozyTheme/overrides/makeLightNormalOverrides.js +60 -0
  7. package/react/Table/Readme.md +80 -0
  8. package/react/Table/Virtualized/Cell.jsx +41 -0
  9. package/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.jsx +45 -0
  10. package/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.jsx +43 -0
  11. package/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.jsx +52 -0
  12. package/react/Table/Virtualized/Dnd/DnDConfigWrapper.jsx +48 -0
  13. package/react/Table/Virtualized/Dnd/TableRow.jsx +86 -0
  14. package/react/Table/Virtualized/FixedHeaderContent.jsx +58 -0
  15. package/react/Table/Virtualized/HeadCell.jsx +45 -0
  16. package/react/Table/Virtualized/RowContent.jsx +35 -0
  17. package/react/Table/Virtualized/helpers.js +41 -0
  18. package/react/Table/Virtualized/helpers.spec.js +108 -0
  19. package/react/Table/Virtualized/index.jsx +104 -0
  20. package/react/Table/Virtualized/virtuosoComponents.jsx +61 -0
  21. package/react/TableRow/index.js +16 -1
  22. package/transpiled/react/Filename/index.d.ts +2 -1
  23. package/transpiled/react/Filename/index.js +49 -16
  24. package/transpiled/react/MuiCozyTheme/overrides/makeDarkInvertedOverrides.d.ts +56 -0
  25. package/transpiled/react/MuiCozyTheme/overrides/makeDarkNormalOverrides.d.ts +56 -0
  26. package/transpiled/react/MuiCozyTheme/overrides/makeLightInvertedOverrides.d.ts +56 -0
  27. package/transpiled/react/MuiCozyTheme/overrides/makeLightNormalOverrides.d.ts +56 -0
  28. package/transpiled/react/MuiCozyTheme/overrides/makeLightNormalOverrides.js +59 -0
  29. package/transpiled/react/Table/Virtualized/Cell.d.ts +8 -0
  30. package/transpiled/react/Table/Virtualized/Cell.js +46 -0
  31. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.d.ts +4 -0
  32. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.js +47 -0
  33. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.d.ts +6 -0
  34. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.js +34 -0
  35. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.d.ts +8 -0
  36. package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.js +63 -0
  37. package/transpiled/react/Table/Virtualized/Dnd/DnDConfigWrapper.d.ts +2 -0
  38. package/transpiled/react/Table/Virtualized/Dnd/DnDConfigWrapper.js +55 -0
  39. package/transpiled/react/Table/Virtualized/Dnd/TableRow.d.ts +8 -0
  40. package/transpiled/react/Table/Virtualized/Dnd/TableRow.js +130 -0
  41. package/transpiled/react/Table/Virtualized/FixedHeaderContent.d.ts +10 -0
  42. package/transpiled/react/Table/Virtualized/FixedHeaderContent.js +54 -0
  43. package/transpiled/react/Table/Virtualized/HeadCell.d.ts +8 -0
  44. package/transpiled/react/Table/Virtualized/HeadCell.js +44 -0
  45. package/transpiled/react/Table/Virtualized/RowContent.d.ts +10 -0
  46. package/transpiled/react/Table/Virtualized/RowContent.js +34 -0
  47. package/transpiled/react/Table/Virtualized/helpers.d.ts +2 -0
  48. package/transpiled/react/Table/Virtualized/helpers.js +64 -0
  49. package/transpiled/react/Table/Virtualized/helpers.spec.d.ts +1 -0
  50. package/transpiled/react/Table/Virtualized/index.d.ts +2 -0
  51. package/transpiled/react/Table/Virtualized/index.js +115 -0
  52. package/transpiled/react/Table/Virtualized/virtuosoComponents.d.ts +10 -0
  53. package/transpiled/react/Table/Virtualized/virtuosoComponents.js +100 -0
  54. package/transpiled/react/TableRow/index.d.ts +2 -1
  55. package/transpiled/react/TableRow/index.js +20 -1
  56. package/transpiled/react/stylesheet.css +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ # [124.0.0](https://github.com/cozy/cozy-ui/compare/v123.2.1...v124.0.0) (2025-05-22)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add dragndrop on virtualized table ([519e610](https://github.com/cozy/cozy-ui/commit/519e610))
7
+ * Add react-dnd ([4f2805e](https://github.com/cozy/cozy-ui/commit/4f2805e))
8
+ * Add react-virtuoso ([e121afe](https://github.com/cozy/cozy-ui/commit/e121afe))
9
+ * Add Virtual table example and adjust style ([9cc9506](https://github.com/cozy/cozy-ui/commit/9cc9506))
10
+ * **Filename:** Add `path` and rework `icon` ([35a8459](https://github.com/cozy/cozy-ui/commit/35a8459))
11
+ * Memo rows and cells ([bae4212](https://github.com/cozy/cozy-ui/commit/bae4212))
12
+ * **TableRow:** Add disabled style ([2a0d626](https://github.com/cozy/cozy-ui/commit/2a0d626))
13
+ * Upgrade react ([fe320be](https://github.com/cozy/cozy-ui/commit/fe320be))
14
+ * **VirtualizedTable:** Add selection props ([f819531](https://github.com/cozy/cozy-ui/commit/f819531))
15
+
16
+
17
+ ### BREAKING CHANGES
18
+
19
+ * You have to add `react-dnd ^16.0.1` and `react-dnd-html5-backend ^16.0.1` to use dragndrop on virtualized table. Typically if you use `dragProps: {{ enabled: true }}` on `react/Table/Virtualized` component.
20
+ You also need to wrap your table into the DnD Provider like so:
21
+ ```
22
+ import { DndProvider } from 'react-dnd'
23
+ import { HTML5Backend } from 'react-dnd-html5-backend'
24
+
25
+ <DndProvider backend={HTML5Backend}>...</DndProvider>
26
+ ```
27
+ * **Filename:** You must use `<Icon />` component to use `icon` prop. So replace `<Filename icon={something} />` by `<Filename icon={<Icon icon={something} />} />`
28
+ * You must have `react ^16.14.0` and `react-dom ^16.14.0`.
29
+
1
30
  ## [123.2.1](https://github.com/cozy/cozy-ui/compare/v123.2.0...v123.2.1) (2025-05-20)
2
31
 
3
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "123.2.1",
3
+ "version": "124.0.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -130,12 +130,14 @@
130
130
  "postcss-loader": "2.1.6",
131
131
  "prettier": "2.6.0",
132
132
  "prop-types": "15.7.2",
133
- "react": "16.12.0",
134
- "react-dom": "16.12.0",
133
+ "react": "16.14.0",
134
+ "react-dnd": "16.0.1",
135
+ "react-dnd-html5-backend": "16.0.1",
136
+ "react-dom": "16.14.0",
135
137
  "react-hot-loader": "^4.3.11",
136
138
  "react-redux": "7.2.7",
137
139
  "react-styleguidist": "10.6.2",
138
- "react-test-renderer": "16.12.0",
140
+ "react-test-renderer": "16.14.0",
139
141
  "redux": "3.7.2",
140
142
  "redux-mock-store": "^1.5.4",
141
143
  "remark-cli": "^8.0.1",
@@ -179,6 +181,7 @@
179
181
  "react-remove-scroll": "^2.4.0",
180
182
  "react-select": "^4.3.0",
181
183
  "react-swipeable-views": "^0.13.3",
184
+ "react-virtuoso": "4.12.7",
182
185
  "rooks": "^5.11.2"
183
186
  },
184
187
  "peerDependencies": {
@@ -186,8 +189,10 @@
186
189
  "cozy-device-helper": ">=2.0.0",
187
190
  "cozy-flags": ">=2.10.1",
188
191
  "cozy-intent": ">=2.29.1",
189
- "react": "^16.8.6",
190
- "react-dom": "^16.8.6"
192
+ "react": "^16.14.0",
193
+ "react-dnd": "^16.0.1",
194
+ "react-dnd-html5-backend": "^16.0.1",
195
+ "react-dom": "^16.14.0"
191
196
  },
192
197
  "eslintConfig": {
193
198
  "extends": [
@@ -3,23 +3,25 @@
3
3
  ```jsx
4
4
  import Filename from 'cozy-ui/transpiled/react/Filename'
5
5
  import FileIcon from 'cozy-ui/transpiled/react/Icons/File'
6
+ import Icon from 'cozy-ui/transpiled/react/Icon'
6
7
  import Variants from 'cozy-ui/docs/components/Variants'
7
8
 
8
9
  const initialVariants = [
9
- { midEllipsis: false, icon: false, body1Variant: false, extension: true, short: false }
10
+ { midEllipsis: false, icon: true, body1Variant: false, extension: true, short: false, withPath: true }
10
11
  ]
11
12
 
12
13
  ;
13
14
 
14
15
  <Variants initialVariants={initialVariants} screenshotAllVariants>
15
16
  {variant => (
16
- <Filename
17
- icon={variant.icon ? FileIcon : undefined}
18
- variant={variant.body1Variant ? 'body1' : undefined}
19
- midEllipsis={variant.midEllipsis}
20
- filename={variant.short ? "Lacinia condimentum this is the end" : "Lacinia condimentum potenti id est tortor dictumst lectus tincidunt hac ultricies, curae mattis nisi neque sodales sagittis dui nulla aliquam turpis eros, finibus ac iaculis dictum et orci elit posuere ex and this is the end"}
21
- extension={variant.extension ? ".pdf" : undefined}
22
- />
17
+ <Filename
18
+ icon={variant.icon ? <Icon icon={FileIcon} size={32} /> : undefined}
19
+ variant={variant.body1Variant ? 'body1' : undefined}
20
+ midEllipsis={variant.midEllipsis}
21
+ filename={variant.short ? "Lacinia condimentum this is the end" : "Lacinia condimentum potenti id est tortor dictumst lectus tincidunt hac ultricies, curae mattis nisi neque sodales sagittis dui nulla aliquam turpis eros, finibus ac iaculis dictum et orci elit posuere ex and this is the end"}
22
+ extension={variant.extension ? ".pdf" : undefined}
23
+ path={variant.withPath ? '/some/folder/and/subfolder' : undefined}
24
+ />
23
25
  )}
24
26
  </Variants>
25
27
  ```
@@ -1,18 +1,15 @@
1
+ import cx from 'classnames'
1
2
  import PropTypes from 'prop-types'
2
- import React from 'react'
3
+ import React, { Fragment } from 'react'
3
4
 
4
- import Icon, { iconPropType } from '../Icon'
5
+ import styles from './styles.styl'
6
+ import { iconPropType } from '../Icon'
5
7
  import MidEllipsis from '../MidEllipsis'
6
8
  import Typography from '../Typography'
7
9
 
8
- const Filename = ({ icon, filename, extension, midEllipsis, variant }) => {
10
+ const NameAndExtension = ({ filename, extension, variant, midEllipsis }) => {
9
11
  return (
10
- <div className="u-flex u-flex-items-center">
11
- {icon && (
12
- <div className="u-mr-1">
13
- <Icon icon={icon} width={30} height={30} />
14
- </div>
15
- )}
12
+ <>
16
13
  {filename && (
17
14
  <Typography variant={variant} component="span" noWrap>
18
15
  {midEllipsis ? <MidEllipsis text={filename} /> : filename}
@@ -23,7 +20,56 @@ const Filename = ({ icon, filename, extension, midEllipsis, variant }) => {
23
20
  {extension}
24
21
  </Typography>
25
22
  )}
26
- </div>
23
+ </>
24
+ )
25
+ }
26
+
27
+ const Filename = ({
28
+ icon,
29
+ filename,
30
+ extension,
31
+ midEllipsis,
32
+ variant,
33
+ path
34
+ }) => {
35
+ const [Wrapper, wrapperProps] = path
36
+ ? [Fragment, {}]
37
+ : ['div', { className: cx('u-flex u-flex-items-center') }]
38
+
39
+ return (
40
+ <Wrapper {...wrapperProps}>
41
+ {icon && (
42
+ <div
43
+ className={cx('u-flex u-pos-relative u-mr-1', {
44
+ [styles['icon-withPath']]: !!path
45
+ })}
46
+ >
47
+ {icon}
48
+ </div>
49
+ )}
50
+ {path ? (
51
+ <>
52
+ <div className="u-flex">
53
+ <NameAndExtension
54
+ filename={filename}
55
+ extension={extension}
56
+ variant={variant}
57
+ midEllipsis={midEllipsis}
58
+ />
59
+ </div>
60
+ <Typography variant="body2" component="div" noWrap>
61
+ {path}
62
+ </Typography>
63
+ </>
64
+ ) : (
65
+ <NameAndExtension
66
+ filename={filename}
67
+ extension={extension}
68
+ variant={variant}
69
+ midEllipsis={midEllipsis}
70
+ />
71
+ )}
72
+ </Wrapper>
27
73
  )
28
74
  }
29
75
 
@@ -0,0 +1,3 @@
1
+ .icon-withPath
2
+ float left
3
+ top 4px
@@ -506,6 +506,66 @@ export const makeLightNormalOverrides = theme => ({
506
506
  paddingRight: 16
507
507
  }
508
508
  },
509
+ MuiTableHead: {
510
+ root: {
511
+ backgroundColor: theme.palette.background.paper
512
+ }
513
+ },
514
+ MuiTableRow: {
515
+ root: {
516
+ '&.disabled': {
517
+ cursor: 'pointer',
518
+ pointerEvents: 'none',
519
+ opacity: 0.5
520
+ }
521
+ }
522
+ },
523
+ MuiTableCell: {
524
+ root: {
525
+ padding: '8px 4px'
526
+ },
527
+ head: {
528
+ ...theme.typography.subtitle2,
529
+ color: theme.palette.text.secondary,
530
+ lineHeight: 1.292
531
+ },
532
+ body: {
533
+ color: theme.palette.text.secondary,
534
+ '&.sortable': {
535
+ '&$paddingNone': {
536
+ '&$alignLeft': {
537
+ paddingLeft: '12px'
538
+ },
539
+ '&$alignRight': {
540
+ paddingRight: '12px'
541
+ }
542
+ },
543
+ '&$alignLeft': {
544
+ paddingLeft: '16px'
545
+ },
546
+ '&$alignRight': {
547
+ paddingRight: '16px'
548
+ }
549
+ }
550
+ },
551
+ stickyHeader: {
552
+ backgroundColor: theme.palette.background.paper
553
+ }
554
+ },
555
+ MuiTableSortLabel: {
556
+ root: {
557
+ padding: '8px 12px',
558
+ color: theme.palette.text.secondary,
559
+ '&:hover': {
560
+ color: theme.palette.text.primary,
561
+ borderRadius: 999,
562
+ backgroundColor: theme.palette.action.hover
563
+ }
564
+ },
565
+ icon: {
566
+ fontSize: 14
567
+ }
568
+ },
509
569
  MuiFormLabel: {
510
570
  root: {
511
571
  color: theme.palette.text.secondary,
@@ -0,0 +1,80 @@
1
+ ### React-Virtuoso
2
+
3
+ ```jsx
4
+ import VirtualizedTable from 'cozy-ui/transpiled/react/Table/Virtualized'
5
+
6
+ const createData = (id, name, calories, fat, carbs, protein) => {
7
+ return { id, name, calories, fat, carbs, protein }
8
+ }
9
+
10
+ const rows = [
11
+ createData(0, 'Cupcake', 305, 3.7, 67, 4.3),
12
+ createData(1, 'Donut', 452, 25.0, 51, 4.9),
13
+ createData(2, 'Eclair', 262, 16.0, 24, 6.0),
14
+ createData(3, 'Frozen yoghurt', 159, 6.0, 24, 4.0),
15
+ createData(4, 'Gingerbread', 356, 16.0, 49, 3.9),
16
+ createData(5, 'Honeycomb', 408, 3.2, 87, 6.5),
17
+ createData(6, 'Ice cream sandwich', 237, 9.0, 37, 4.3),
18
+ createData(7, 'Jelly Bean', 375, 0.0, 94, 0.0),
19
+ createData(8, 'KitKat', 518, 26.0, 65, 7.0),
20
+ createData(9, 'Lollipop', 392, 0.2, 98, 0.0),
21
+ createData(10, 'Marshmallow', 318, 0, 81, 2.0),
22
+ createData(11, 'Nougat', 360, 19.0, 9, 37.0),
23
+ createData(12, 'Oreo', 437, 18.0, 63, 4.0),
24
+ createData(
25
+ 6,
26
+ 'Ice cream with a very long list of ingredient to see how the table can handle this kind of item, and this is the end',
27
+ 237,
28
+ 9.0,
29
+ 37,
30
+ 4.3
31
+ )
32
+ ]
33
+
34
+ const columns = [
35
+ {
36
+ id: 'name',
37
+ disablePadding: true,
38
+ label: 'Dessert'
39
+ },
40
+ {
41
+ id: 'calories',
42
+ disablePadding: false,
43
+ width: 80,
44
+ label: 'Calories',
45
+ textAlign: 'left',
46
+ sortable: false
47
+ },
48
+ {
49
+ id: 'fat',
50
+ disablePadding: false,
51
+ width: 85,
52
+ label: 'Fat (g)',
53
+ textAlign: 'right'
54
+ },
55
+ {
56
+ id: 'carbs',
57
+ disablePadding: false,
58
+ width: 115,
59
+ label: 'Carbs (g)',
60
+ textAlign: 'right'
61
+ },
62
+ {
63
+ id: 'protein',
64
+ disablePadding: false,
65
+ width: 115,
66
+ label: 'Protein (g)',
67
+ textAlign: 'right'
68
+ }
69
+ ]
70
+
71
+ ;
72
+
73
+ <div style={{ border: "1px solid var(--borderMainColor)", height: 400, width: "100%" }}>
74
+ <VirtualizedTable
75
+ rows={rows}
76
+ columns={columns}
77
+ defaultOrder={columns[0].id}
78
+ />
79
+ </div>
80
+ ```
@@ -0,0 +1,41 @@
1
+ import cx from 'classnames'
2
+ import React from 'react'
3
+
4
+ import TableCell from '../../TableCell'
5
+ import { makeStyles } from '../../styles'
6
+
7
+ const useStyles = makeStyles({
8
+ root: {
9
+ width: ({ column }) => column.width,
10
+ maxWidth: ({ column }) => column.maxWidth
11
+ }
12
+ })
13
+
14
+ const Cell = ({ row, columns, column, children }) => {
15
+ const classes = useStyles({ column })
16
+
17
+ return (
18
+ <TableCell
19
+ key={column.id}
20
+ classes={classes}
21
+ className={cx({ sortable: column.sortable !== false })}
22
+ align={column.textAlign ?? 'left'}
23
+ padding={column.disablePadding ? 'none' : 'normal'}
24
+ >
25
+ {children
26
+ ? React.Children.map(children, child =>
27
+ React.isValidElement(child)
28
+ ? React.cloneElement(child, {
29
+ row,
30
+ columns,
31
+ column,
32
+ cell: row[column.id]
33
+ })
34
+ : null
35
+ )
36
+ : row[column.id]}
37
+ </TableCell>
38
+ )
39
+ }
40
+
41
+ export default React.memo(Cell)
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import { useDragLayer } from 'react-dnd'
3
+
4
+ import DragPreviewWrapper from './DragPreviewWrapper'
5
+
6
+ const layerStyles = {
7
+ position: 'fixed',
8
+ pointerEvents: 'none',
9
+ zIndex: 100,
10
+ left: 0,
11
+ top: 0,
12
+ width: '100%',
13
+ height: '100%'
14
+ }
15
+
16
+ // Example find in the official documentation
17
+ // https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer
18
+ export const CustomDragLayer = ({ dragId }) => {
19
+ const { itemType, isDragging, item, initialOffset, currentOffset } =
20
+ useDragLayer(monitor => ({
21
+ item: monitor.getItem(),
22
+ itemType: monitor.getItemType(),
23
+ initialOffset: monitor.getInitialSourceClientOffset(),
24
+ currentOffset: monitor.getSourceClientOffset(),
25
+ isDragging: monitor.isDragging()
26
+ }))
27
+
28
+ if (!isDragging) {
29
+ return null
30
+ }
31
+
32
+ return (
33
+ <div style={layerStyles}>
34
+ <DragPreviewWrapper
35
+ item={item}
36
+ itemType={itemType}
37
+ dragId={dragId}
38
+ initialOffset={initialOffset}
39
+ currentOffset={currentOffset}
40
+ />
41
+ </div>
42
+ )
43
+ }
44
+
45
+ export default CustomDragLayer
@@ -0,0 +1,43 @@
1
+ import React from 'react'
2
+
3
+ import Badge from '../../../../Badge'
4
+ import Paper from '../../../../Paper'
5
+ import Typography from '../../../../Typography'
6
+ import { makeStyles } from '../../../../styles'
7
+
8
+ const useStyles = makeStyles({
9
+ root: {
10
+ width: 'fit-content'
11
+ }
12
+ })
13
+
14
+ const DragPreview = ({ fileName, selectedCount }) => {
15
+ const classes = useStyles()
16
+
17
+ return (
18
+ <>
19
+ {selectedCount > 1 ? (
20
+ <Badge
21
+ badgeContent={selectedCount}
22
+ size="large"
23
+ color="primary"
24
+ anchorOrigin={{
25
+ vertical: 'top',
26
+ horizontal: 'right'
27
+ }}
28
+ overlap="rectangular"
29
+ >
30
+ <Paper classes={classes} className="u-p-half u-maw-5">
31
+ <Typography>{fileName}</Typography>
32
+ </Paper>
33
+ </Badge>
34
+ ) : (
35
+ <Paper classes={classes} className="u-p-half u-maw-5">
36
+ <Typography>{fileName}</Typography>
37
+ </Paper>
38
+ )}
39
+ </>
40
+ )
41
+ }
42
+
43
+ export default React.memo(DragPreview)
@@ -0,0 +1,52 @@
1
+ import React, { useEffect, useState } from 'react'
2
+
3
+ import DragPreview from './DragPreview'
4
+
5
+ const makeStyles = ({ x, y }) => {
6
+ if (!x || !y) {
7
+ return { display: 'none' }
8
+ }
9
+
10
+ const transform = `translate(${x}px, ${y}px)`
11
+
12
+ return {
13
+ transform,
14
+ WebkitTransform: transform
15
+ }
16
+ }
17
+
18
+ const DragPreviewWrapper = ({
19
+ item,
20
+ itemType,
21
+ dragId,
22
+ initialOffset,
23
+ currentOffset
24
+ }) => {
25
+ const [mousePosition, setMousePosition] = useState({ x: null, y: null })
26
+
27
+ useEffect(() => {
28
+ const handleMouseMove = e => {
29
+ setMousePosition({ x: e.clientX, y: e.clientY })
30
+ }
31
+
32
+ window.addEventListener('dragover', handleMouseMove)
33
+ return () => {
34
+ window.removeEventListener('dragover', handleMouseMove)
35
+ }
36
+ }, [])
37
+
38
+ if (!initialOffset || !currentOffset || itemType !== dragId) {
39
+ return null
40
+ }
41
+
42
+ return (
43
+ <div style={makeStyles(mousePosition)}>
44
+ <DragPreview
45
+ fileName={item.draggedItems[0].name}
46
+ selectedCount={item.draggedItems.length}
47
+ />
48
+ </div>
49
+ )
50
+ }
51
+
52
+ export default DragPreviewWrapper
@@ -0,0 +1,48 @@
1
+ import { forwardRef, useEffect, useState } from 'react'
2
+ import { useDragDropManager } from 'react-dnd'
3
+
4
+ const DnDConfigWrapper = forwardRef(({ children }, ref) => {
5
+ const dragDropManager = useDragDropManager()
6
+ const monitor = dragDropManager.getMonitor()
7
+ const [isDragging, setIsDragging] = useState(false)
8
+
9
+ useEffect(() => {
10
+ const unsubscribe = monitor.subscribeToStateChange(() => {
11
+ setIsDragging(monitor.isDragging())
12
+ })
13
+ return () => unsubscribe()
14
+ }, [monitor])
15
+
16
+ useEffect(() => {
17
+ if (!isDragging) return
18
+
19
+ const scrollThreshold = 100
20
+ const scrollMaxSpeed = 75
21
+
22
+ const intervalId = setInterval(() => {
23
+ const offset = monitor.getClientOffset()
24
+ const container = ref.current
25
+ if (!offset || !container) return
26
+
27
+ const { top, bottom } = container.getBoundingClientRect()
28
+ const distanceToTop = offset.y - top
29
+ const distanceToBottom = bottom - offset.y
30
+
31
+ if (distanceToTop < scrollThreshold) {
32
+ const speed = scrollMaxSpeed * (1 - distanceToTop / scrollThreshold)
33
+ container.scrollBy(0, -speed)
34
+ } else if (distanceToBottom < scrollThreshold) {
35
+ const speed = scrollMaxSpeed * (1 - distanceToBottom / scrollThreshold)
36
+ container.scrollBy(0, speed)
37
+ }
38
+ }, 16) // ~60fps
39
+
40
+ return () => clearInterval(intervalId)
41
+ }, [isDragging, monitor, ref])
42
+
43
+ return children
44
+ })
45
+
46
+ DnDConfigWrapper.displayName = 'DnDConfigWrapper'
47
+
48
+ export default DnDConfigWrapper
@@ -0,0 +1,86 @@
1
+ import React, { useEffect } from 'react'
2
+ import { useDrag, useDrop } from 'react-dnd'
3
+ import { getEmptyImage } from 'react-dnd-html5-backend'
4
+
5
+ import TableRowClassic from '../../../TableRow'
6
+
7
+ const TableRow = ({ item, context, selected, disabled, ...props }) => {
8
+ const { selectedItems, setItemsInDropProcess, dragProps } = context
9
+ const {
10
+ onDrop,
11
+ canDrop: canDropProps,
12
+ canDrag: canDragProps,
13
+ dragId
14
+ } = dragProps
15
+
16
+ const [dragCollect, dragRef, dragRefPreview] = useDrag(
17
+ () => ({
18
+ type: dragId,
19
+ isDragging: monitor => {
20
+ // put all selected items in isDragging state
21
+ if (selectedItems.length > 0) {
22
+ return selectedItems.some(
23
+ selectedItem => selectedItem._id === item._id
24
+ )
25
+ }
26
+
27
+ return item._id === monitor.getItem().draggedItems[0]._id
28
+ },
29
+ item: {
30
+ draggedItems: selectedItems.length > 0 ? selectedItems : [item]
31
+ },
32
+ canDrag: () => {
33
+ const defaultCanDrag = canDragProps?.(item) || true
34
+ // if selectedItems is not empty, only the selected items can be dragged
35
+ if (selectedItems.length > 0) {
36
+ return defaultCanDrag && selected
37
+ }
38
+ return defaultCanDrag
39
+ },
40
+ collect: monitor => {
41
+ return {
42
+ isDragging: monitor.isDragging()
43
+ }
44
+ }
45
+ }),
46
+ [item, selectedItems] // used to pass args inside returned object attributes
47
+ )
48
+
49
+ const [dropCollect, dropRef] = useDrop(
50
+ () => ({
51
+ accept: dragId,
52
+ canDrop: () => (canDropProps ? canDropProps(item) : true),
53
+ drop: async draggedItem => {
54
+ setItemsInDropProcess(
55
+ draggedItem.draggedItems.map(draggedItem => draggedItem._id)
56
+ )
57
+ await onDrop(draggedItem.draggedItems, item, selectedItems)
58
+ setItemsInDropProcess([])
59
+ },
60
+ collect: monitor => {
61
+ return {
62
+ isOver: monitor.isOver()
63
+ }
64
+ }
65
+ }),
66
+ [item._id, selectedItems] // used to pass args inside returned object attributes
67
+ )
68
+
69
+ // Tricks for the preview image to be empty
70
+ // https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer
71
+ useEffect(() => {
72
+ dragRefPreview(getEmptyImage(), { captureDraggingState: true })
73
+ }, [dragRefPreview])
74
+
75
+ return (
76
+ <TableRowClassic
77
+ {...props}
78
+ ref={node => dragRef(dropRef(node))}
79
+ selected={selected || dropCollect.isOver}
80
+ className={dragCollect.isDragging ? 'u-o-50' : ''}
81
+ disabled={disabled}
82
+ />
83
+ )
84
+ }
85
+
86
+ export default TableRow