cozy-ui 123.2.1 → 124.1.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 +36 -0
- package/assets/icons/ui/cloud2.svg +1 -0
- package/assets/icons/ui/discuss.svg +1 -0
- package/assets/icons/ui/email-open.svg +1 -0
- package/assets/icons/ui/key2.svg +1 -0
- package/assets/icons/ui/peoples.svg +1 -0
- package/assets/icons/ui/security.svg +1 -0
- package/package.json +11 -6
- package/react/Filename/Readme.md +10 -8
- package/react/Filename/index.jsx +56 -10
- package/react/Filename/styles.styl +3 -0
- package/react/Icon/Readme.md +13 -1
- package/react/Icons/Cloud2.jsx +29 -0
- package/react/Icons/Discuss.jsx +16 -0
- package/react/Icons/EmailOpen.jsx +55 -0
- package/react/Icons/Key2.jsx +23 -0
- package/react/Icons/Peoples.jsx +12 -0
- package/react/Icons/Security.jsx +12 -0
- package/react/MuiCozyTheme/overrides/makeLightNormalOverrides.js +60 -0
- package/react/Table/Readme.md +80 -0
- package/react/Table/Virtualized/Cell.jsx +41 -0
- package/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.jsx +45 -0
- package/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.jsx +43 -0
- package/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.jsx +52 -0
- package/react/Table/Virtualized/Dnd/DnDConfigWrapper.jsx +48 -0
- package/react/Table/Virtualized/Dnd/TableRow.jsx +86 -0
- package/react/Table/Virtualized/FixedHeaderContent.jsx +58 -0
- package/react/Table/Virtualized/HeadCell.jsx +45 -0
- package/react/Table/Virtualized/RowContent.jsx +35 -0
- package/react/Table/Virtualized/helpers.js +41 -0
- package/react/Table/Virtualized/helpers.spec.js +108 -0
- package/react/Table/Virtualized/index.jsx +104 -0
- package/react/Table/Virtualized/virtuosoComponents.jsx +61 -0
- package/react/TableRow/index.js +16 -1
- package/transpiled/react/Filename/index.d.ts +2 -1
- package/transpiled/react/Filename/index.js +49 -16
- package/transpiled/react/Icon/icons-sprite.d.ts +1 -1
- package/transpiled/react/Icon/icons-sprite.js +1 -1
- package/transpiled/react/Icons/Cloud2.d.ts +2 -0
- package/transpiled/react/Icons/Cloud2.js +26 -0
- package/transpiled/react/Icons/Discuss.d.ts +2 -0
- package/transpiled/react/Icons/Discuss.js +15 -0
- package/transpiled/react/Icons/EmailOpen.d.ts +2 -0
- package/transpiled/react/Icons/EmailOpen.js +53 -0
- package/transpiled/react/Icons/Key2.d.ts +2 -0
- package/transpiled/react/Icons/Key2.js +21 -0
- package/transpiled/react/Icons/Peoples.d.ts +2 -0
- package/transpiled/react/Icons/Peoples.js +13 -0
- package/transpiled/react/Icons/Security.d.ts +2 -0
- package/transpiled/react/Icons/Security.js +13 -0
- package/transpiled/react/MuiCozyTheme/overrides/makeDarkInvertedOverrides.d.ts +56 -0
- package/transpiled/react/MuiCozyTheme/overrides/makeDarkNormalOverrides.d.ts +56 -0
- package/transpiled/react/MuiCozyTheme/overrides/makeLightInvertedOverrides.d.ts +56 -0
- package/transpiled/react/MuiCozyTheme/overrides/makeLightNormalOverrides.d.ts +56 -0
- package/transpiled/react/MuiCozyTheme/overrides/makeLightNormalOverrides.js +59 -0
- package/transpiled/react/Table/Virtualized/Cell.d.ts +8 -0
- package/transpiled/react/Table/Virtualized/Cell.js +46 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.d.ts +4 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/CustomDragLayer.js +47 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.d.ts +6 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreview.js +34 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.d.ts +8 -0
- package/transpiled/react/Table/Virtualized/Dnd/CustomDrag/DragPreviewWrapper.js +63 -0
- package/transpiled/react/Table/Virtualized/Dnd/DnDConfigWrapper.d.ts +2 -0
- package/transpiled/react/Table/Virtualized/Dnd/DnDConfigWrapper.js +55 -0
- package/transpiled/react/Table/Virtualized/Dnd/TableRow.d.ts +8 -0
- package/transpiled/react/Table/Virtualized/Dnd/TableRow.js +130 -0
- package/transpiled/react/Table/Virtualized/FixedHeaderContent.d.ts +10 -0
- package/transpiled/react/Table/Virtualized/FixedHeaderContent.js +54 -0
- package/transpiled/react/Table/Virtualized/HeadCell.d.ts +8 -0
- package/transpiled/react/Table/Virtualized/HeadCell.js +44 -0
- package/transpiled/react/Table/Virtualized/RowContent.d.ts +10 -0
- package/transpiled/react/Table/Virtualized/RowContent.js +34 -0
- package/transpiled/react/Table/Virtualized/helpers.d.ts +2 -0
- package/transpiled/react/Table/Virtualized/helpers.js +64 -0
- package/transpiled/react/Table/Virtualized/helpers.spec.d.ts +1 -0
- package/transpiled/react/Table/Virtualized/index.d.ts +2 -0
- package/transpiled/react/Table/Virtualized/index.js +115 -0
- package/transpiled/react/Table/Virtualized/virtuosoComponents.d.ts +10 -0
- package/transpiled/react/Table/Virtualized/virtuosoComponents.js +100 -0
- package/transpiled/react/TableRow/index.d.ts +2 -1
- package/transpiled/react/TableRow/index.js +20 -1
- package/transpiled/react/stylesheet.css +1 -1
|
@@ -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
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import TableHeadCell from './HeadCell'
|
|
4
|
+
import Checkbox from '../../Checkbox'
|
|
5
|
+
import TableCell from '../../TableCell'
|
|
6
|
+
import TableRow from '../../TableRow'
|
|
7
|
+
import { makeStyles } from '../../styles'
|
|
8
|
+
|
|
9
|
+
const useStyles = makeStyles({
|
|
10
|
+
visuallyHidden: {
|
|
11
|
+
border: 0,
|
|
12
|
+
clip: 'rect(0 0 0 0)',
|
|
13
|
+
height: 1,
|
|
14
|
+
margin: -1,
|
|
15
|
+
overflow: 'hidden',
|
|
16
|
+
padding: 0,
|
|
17
|
+
position: 'absolute',
|
|
18
|
+
top: 20,
|
|
19
|
+
width: 1
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const FixedHeaderContent = ({
|
|
24
|
+
columns,
|
|
25
|
+
order,
|
|
26
|
+
orderBy,
|
|
27
|
+
rowCount,
|
|
28
|
+
selectedCount,
|
|
29
|
+
onClick,
|
|
30
|
+
onSelectAllClick
|
|
31
|
+
}) => {
|
|
32
|
+
const classes = useStyles()
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableCell align="center" padding="checkbox">
|
|
37
|
+
<Checkbox
|
|
38
|
+
indeterminate={selectedCount > 0 && selectedCount < rowCount}
|
|
39
|
+
checked={rowCount > 0 && selectedCount === rowCount}
|
|
40
|
+
inputProps={{ 'aria-label': 'select all' }}
|
|
41
|
+
onChange={onSelectAllClick}
|
|
42
|
+
/>
|
|
43
|
+
</TableCell>
|
|
44
|
+
{columns.map(column => (
|
|
45
|
+
<TableHeadCell
|
|
46
|
+
key={column.id}
|
|
47
|
+
className={classes.visuallyHidden}
|
|
48
|
+
column={column}
|
|
49
|
+
order={order}
|
|
50
|
+
orderBy={orderBy}
|
|
51
|
+
onClick={() => onClick(column.id)}
|
|
52
|
+
/>
|
|
53
|
+
))}
|
|
54
|
+
</TableRow>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default FixedHeaderContent
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import TableCell from '../../TableCell'
|
|
4
|
+
import TableSortLabel from '../../TableSortLabel'
|
|
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 TableHeadCell = ({ className, column, orderBy, order, onClick }) => {
|
|
15
|
+
const classes = useStyles({ column })
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<TableCell
|
|
19
|
+
key={column.id}
|
|
20
|
+
classes={classes}
|
|
21
|
+
align={column.textAlign ?? 'left'}
|
|
22
|
+
padding={column.disablePadding ? 'none' : 'normal'}
|
|
23
|
+
sortDirection={orderBy === column.id ? order : false}
|
|
24
|
+
>
|
|
25
|
+
{column.sortable !== false ? (
|
|
26
|
+
<TableSortLabel
|
|
27
|
+
active={orderBy === column.id}
|
|
28
|
+
direction={orderBy === column.id ? order : 'asc'}
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
>
|
|
31
|
+
{column.label}
|
|
32
|
+
{orderBy === column.id && (
|
|
33
|
+
<span className={className}>
|
|
34
|
+
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
|
35
|
+
</span>
|
|
36
|
+
)}
|
|
37
|
+
</TableSortLabel>
|
|
38
|
+
) : (
|
|
39
|
+
column.label
|
|
40
|
+
)}
|
|
41
|
+
</TableCell>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default TableHeadCell
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import Cell from './Cell'
|
|
4
|
+
import Checkbox from '../../Checkbox'
|
|
5
|
+
import TableCell from '../../TableCell'
|
|
6
|
+
|
|
7
|
+
const RowContent = ({
|
|
8
|
+
index,
|
|
9
|
+
row,
|
|
10
|
+
columns,
|
|
11
|
+
isSelectedItem,
|
|
12
|
+
children,
|
|
13
|
+
onSelectClick
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<TableCell align="center" padding="checkbox">
|
|
18
|
+
<Checkbox
|
|
19
|
+
checked={isSelectedItem(row)}
|
|
20
|
+
inputProps={{
|
|
21
|
+
'aria-labelledby': `enhanced-table-checkbox-${index}`
|
|
22
|
+
}}
|
|
23
|
+
onChange={() => onSelectClick(row)}
|
|
24
|
+
/>
|
|
25
|
+
</TableCell>
|
|
26
|
+
{columns.map(column => (
|
|
27
|
+
<Cell key={column.id} row={row} columns={columns} column={column}>
|
|
28
|
+
{children}
|
|
29
|
+
</Cell>
|
|
30
|
+
))}
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default React.memo(RowContent)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const descendingComparator = ({ a, b, order, orderBy, lang }) => {
|
|
2
|
+
const aValue = a[orderBy]
|
|
3
|
+
const bValue = b[orderBy]
|
|
4
|
+
|
|
5
|
+
if (typeof aValue === 'string') {
|
|
6
|
+
const isDate = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}/.test(aValue)
|
|
7
|
+
const isNumber = !isNaN(parseInt(aValue))
|
|
8
|
+
|
|
9
|
+
if (isDate) {
|
|
10
|
+
return new Date(bValue) - new Date(aValue)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (isNumber) {
|
|
14
|
+
return parseInt(bValue) - parseInt(aValue)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { compare } = Intl.Collator(lang || 'en', {
|
|
18
|
+
caseFirst: order === 'asc' ? 'upper' : 'lower'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return compare(bValue, aValue)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return bValue - aValue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getComparator = (order, orderBy, lang) => {
|
|
28
|
+
return order === 'desc'
|
|
29
|
+
? (a, b) => descendingComparator({ a, b, order, orderBy, lang })
|
|
30
|
+
: (a, b) => -descendingComparator({ a, b, order, orderBy, lang })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const stableSort = (array, comparator) => {
|
|
34
|
+
const stabilizedThis = array.map((el, index) => [el, index])
|
|
35
|
+
stabilizedThis.sort((a, b) => {
|
|
36
|
+
const order = comparator(a[0], b[0])
|
|
37
|
+
if (order !== 0) return order
|
|
38
|
+
return a[1] - b[1]
|
|
39
|
+
})
|
|
40
|
+
return stabilizedThis.map(el => el[0])
|
|
41
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { stableSort, getComparator } from './helpers'
|
|
2
|
+
|
|
3
|
+
describe('stableSort', () => {
|
|
4
|
+
describe('it should sort string correctly even with number', () => {
|
|
5
|
+
const rows = [
|
|
6
|
+
{ name: 'b' },
|
|
7
|
+
{ name: 'A' },
|
|
8
|
+
{ name: 'B' },
|
|
9
|
+
{ name: 'a' },
|
|
10
|
+
{ name: '10' }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
it('for asc', () => {
|
|
14
|
+
const sortedData = stableSort(rows, getComparator('asc', 'name', 'fr'))
|
|
15
|
+
|
|
16
|
+
const onlyValues = sortedData.map(el => el.name)
|
|
17
|
+
expect(onlyValues).toStrictEqual(['A', 'a', 'B', 'b', '10'])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('for desc', () => {
|
|
21
|
+
const sortedData = stableSort(rows, getComparator('desc', 'name', 'fr'))
|
|
22
|
+
|
|
23
|
+
const onlyValues = sortedData.map(el => el.name)
|
|
24
|
+
expect(onlyValues).toStrictEqual(['B', 'b', 'A', 'a', '10'])
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('it should sort number correctly', () => {
|
|
29
|
+
const rows = [
|
|
30
|
+
{ number: 40 },
|
|
31
|
+
{ number: 1 },
|
|
32
|
+
{ number: 8 },
|
|
33
|
+
{ number: 30 },
|
|
34
|
+
{ number: 40 }
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
it('for asc', () => {
|
|
38
|
+
const sortedData = stableSort(rows, getComparator('asc', 'number', 'fr'))
|
|
39
|
+
|
|
40
|
+
const onlyValues = sortedData.map(el => el.number)
|
|
41
|
+
expect(onlyValues).toStrictEqual([1, 8, 30, 40, 40])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('for desc', () => {
|
|
45
|
+
const sortedData = stableSort(rows, getComparator('desc', 'number', 'fr'))
|
|
46
|
+
|
|
47
|
+
const onlyValues = sortedData.map(el => el.number)
|
|
48
|
+
expect(onlyValues).toStrictEqual([40, 40, 30, 8, 1])
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('it should sort string-number correctly', () => {
|
|
53
|
+
const rows = [
|
|
54
|
+
{ number: '40' },
|
|
55
|
+
{ number: '1' },
|
|
56
|
+
{ number: '8' },
|
|
57
|
+
{ number: '30' },
|
|
58
|
+
{ number: '40' }
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
it('for asc', () => {
|
|
62
|
+
const sortedData = stableSort(rows, getComparator('asc', 'number', 'fr'))
|
|
63
|
+
|
|
64
|
+
const onlyValues = sortedData.map(el => el.number)
|
|
65
|
+
expect(onlyValues).toStrictEqual(['1', '8', '30', '40', '40'])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('for desc', () => {
|
|
69
|
+
const sortedData = stableSort(rows, getComparator('desc', 'number', 'fr'))
|
|
70
|
+
|
|
71
|
+
const onlyValues = sortedData.map(el => el.number)
|
|
72
|
+
expect(onlyValues).toStrictEqual(['40', '40', '30', '8', '1'])
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('it should sort date correctly', () => {
|
|
77
|
+
const rows = [
|
|
78
|
+
{ date: '2025-05-01T12:00:00.0000+01:00' },
|
|
79
|
+
{ date: '2025-08-01T12:00:00.0000+01:00' },
|
|
80
|
+
{ date: '2025-01-01T12:00:00.0000+01:00' },
|
|
81
|
+
{ date: '2025-04-01T12:00:00.0000+01:00' }
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
it('for asc', () => {
|
|
85
|
+
const sortedData = stableSort(rows, getComparator('asc', 'date', 'fr'))
|
|
86
|
+
|
|
87
|
+
const onlyValues = sortedData.map(el => el.date)
|
|
88
|
+
expect(onlyValues).toStrictEqual([
|
|
89
|
+
'2025-01-01T12:00:00.0000+01:00',
|
|
90
|
+
'2025-04-01T12:00:00.0000+01:00',
|
|
91
|
+
'2025-05-01T12:00:00.0000+01:00',
|
|
92
|
+
'2025-08-01T12:00:00.0000+01:00'
|
|
93
|
+
])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('for desc', () => {
|
|
97
|
+
const sortedData = stableSort(rows, getComparator('desc', 'date', 'fr'))
|
|
98
|
+
|
|
99
|
+
const onlyValues = sortedData.map(el => el.date)
|
|
100
|
+
expect(onlyValues).toStrictEqual([
|
|
101
|
+
'2025-08-01T12:00:00.0000+01:00',
|
|
102
|
+
'2025-05-01T12:00:00.0000+01:00',
|
|
103
|
+
'2025-04-01T12:00:00.0000+01:00',
|
|
104
|
+
'2025-01-01T12:00:00.0000+01:00'
|
|
105
|
+
])
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|