Skip to content

Commit fc0906d

Browse files
committed
v5.2.0
1 parent d3f458c commit fc0906d

13 files changed

Lines changed: 361 additions & 9 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netdata/netdata-ui",
3-
"version": "5.1.16",
3+
"version": "5.2.0",
44
"description": "netdata UI kit",
55
"main": "dist/index.js",
66
"module": "dist/es6/index.js",
@@ -39,6 +39,7 @@
3939
],
4040
"dependencies": {
4141
"@dnd-kit/core": "^6.3.1",
42+
"@dnd-kit/modifiers": "^9.0.0",
4243
"@dnd-kit/sortable": "^10.0.0",
4344
"@elastic/react-search-ui": "^1.23.0",
4445
"@elastic/search-ui-site-search-connector": "^1.23.0",

src/components/table/body/header/cell.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import ResizeHandler from "./resizeHandler"
88
import Sorting, { SortIconContainer } from "./sorting"
99
import Info from "./info"
1010
import Filter from "./filter"
11+
import useSortableHeader from "./sortableHeader"
12+
import DragHandle from "./dragHandle"
1113

1214
const Label = styled(Text)`
1315
width: 100%;
@@ -40,10 +42,16 @@ const BodyHeaderCell = ({
4042
children,
4143
isSubheader,
4244
hasSubheaders,
45+
enableColumnReordering,
4346
}) => {
4447
useTableState(rerenderSelector)
4548

4649
const { column } = header
50+
const { sortableRef, sortableStyle, dragHandleProps, isDragging } = useSortableHeader(
51+
column.id,
52+
enableColumnReordering
53+
)
54+
4755
const tableMeta =
4856
typeof column.columnDef.tableMeta === "function"
4957
? column.columnDef.tableMeta({}, column, index)
@@ -63,6 +71,8 @@ const BodyHeaderCell = ({
6371

6472
return (
6573
<Flex
74+
ref={sortableRef}
75+
style={enableColumnReordering ? sortableStyle : undefined}
6676
flex={
6777
!column.columnDef.fullWidth && (column.columnDef.notFlex || column.getCanResize())
6878
? false
@@ -98,6 +108,7 @@ const BodyHeaderCell = ({
98108
overflow="hidden"
99109
width="100%"
100110
>
111+
<DragHandle dragHandleProps={dragHandleProps} visible={isDragging} />
101112
{column.isPlaceholder ? null : (
102113
<Label
103114
as={column.columnDef.labelAs}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from "react"
2+
import styled from "styled-components"
3+
import Flex from "@/components/templates/flex"
4+
import { Icon } from "@/components/icon"
5+
6+
const HandleContainer = styled(Flex)`
7+
cursor: grab;
8+
opacity: 0;
9+
transition: opacity 0.15s ease-in-out;
10+
flex-shrink: 0;
11+
12+
&:active {
13+
cursor: grabbing;
14+
}
15+
16+
svg {
17+
width: 10px;
18+
height: 10px;
19+
}
20+
`
21+
22+
export const DragHandle = ({ dragHandleProps, visible }) => {
23+
if (!dragHandleProps || Object.keys(dragHandleProps).length === 0) {
24+
return null
25+
}
26+
27+
return (
28+
<HandleContainer
29+
{...dragHandleProps}
30+
alignItems="center"
31+
justifyContent="center"
32+
padding={[0, 1, 0, 0]}
33+
style={{ opacity: visible ? 1 : undefined }}
34+
className="drag-handle"
35+
>
36+
<Icon name="hamburger" color="textLite" />
37+
</HandleContainer>
38+
)
39+
}
40+
41+
export default DragHandle
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from "react"
2+
import { createPortal } from "react-dom"
3+
import { DragOverlay as DndDragOverlay } from "@dnd-kit/core"
4+
import styled from "styled-components"
5+
import Flex from "@/components/templates/flex"
6+
import { Text } from "@/components/typography"
7+
8+
const OverlayContainer = styled(Flex)`
9+
background: ${({ theme }) => theme.colors.mainBackground};
10+
border: 1px solid ${({ theme }) => theme.colors.border};
11+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
12+
padding: 8px 16px;
13+
border-radius: 4px;
14+
opacity: 0.9;
15+
`
16+
17+
const DragOverlay = ({ activeColumn }) => {
18+
if (!activeColumn) return null
19+
20+
return createPortal(
21+
<DndDragOverlay>
22+
<OverlayContainer alignItems="center" gap={2}>
23+
<Text strong>{activeColumn.columnDef.header || activeColumn.id}</Text>
24+
</OverlayContainer>
25+
</DndDragOverlay>,
26+
document.body
27+
)
28+
}
29+
30+
export default DragOverlay

src/components/table/body/header/index.js

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
import React, { memo } from "react"
1+
import React, { memo, useState, useMemo, useCallback } from "react"
22
import styled from "styled-components"
3+
import {
4+
DndContext,
5+
closestCenter,
6+
KeyboardSensor,
7+
PointerSensor,
8+
useSensor,
9+
useSensors,
10+
} from "@dnd-kit/core"
11+
import { SortableContext, horizontalListSortingStrategy, arrayMove } from "@dnd-kit/sortable"
12+
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers"
313
import Flex from "@/components/templates/flex"
414
import { useTableState } from "../../provider"
515
import Cell from "./cell"
616
import { Handler } from "./resizeHandler"
17+
import DragOverlay from "./dragOverlay"
718

819
const rerenderSelector = state => {
920
const columns = state.allColumns || []
@@ -19,8 +30,10 @@ const rerenderSelector = state => {
1930
}
2031
}
2132

22-
const HeaderGroup = ({ id, headers, testPrefix, rowReverse, ...rest }) => {
23-
return (
33+
const HeaderGroup = ({ id, headers, testPrefix, rowReverse, enableColumnReordering, ...rest }) => {
34+
const columnIds = useMemo(() => headers.map(h => h.column.id), [headers])
35+
36+
const content = (
2437
<Flex
2538
id={id}
2639
data-testid={`netdata-table-headRow${testPrefix}`}
@@ -36,23 +49,37 @@ const HeaderGroup = ({ id, headers, testPrefix, rowReverse, ...rest }) => {
3649
header={header}
3750
testPrefix={testPrefix}
3851
hasSubheaders={!!header.subHeaders.length}
52+
enableColumnReordering={
53+
enableColumnReordering && header.column.columnDef.enableReordering !== false
54+
}
3955
>
4056
{!!header.subHeaders.length && (
4157
<HeaderGroup
4258
headers={header.subHeaders}
4359
id={header.id}
4460
key={header.id}
4561
{...rest}
62+
enableColumnReordering={enableColumnReordering}
4663
isSubheader
4764
/>
4865
)}
4966
</Cell>
5067
))}
5168
</Flex>
5269
)
70+
71+
if (!enableColumnReordering) {
72+
return content
73+
}
74+
75+
return (
76+
<SortableContext items={columnIds} strategy={horizontalListSortingStrategy}>
77+
{content}
78+
</SortableContext>
79+
)
5380
}
5481

55-
const HeaderGroups = ({ groups, size, side, flex = "grow", ...rest }) => {
82+
const HeaderGroups = ({ groups, size, side, flex = "grow", enableColumnReordering, ...rest }) => {
5683
if (!groups[0].headers.length) return null
5784

5885
return (
@@ -69,7 +96,13 @@ const HeaderGroups = ({ groups, size, side, flex = "grow", ...rest }) => {
6996
flex={flex}
7097
column
7198
>
72-
<HeaderGroup headers={groups[0].headers} id={groups[0].id} key={groups[0].id} {...rest} />
99+
<HeaderGroup
100+
headers={groups[0].headers}
101+
id={groups[0].id}
102+
key={groups[0].id}
103+
{...rest}
104+
enableColumnReordering={enableColumnReordering}
105+
/>
73106
</Flex>
74107
)
75108
}
@@ -82,12 +115,67 @@ const HeaderRow = styled(Flex)`
82115
&:hover ${Handler} {
83116
display: block;
84117
}
118+
119+
&:hover .drag-handle {
120+
opacity: 1;
121+
}
85122
`
86123

87-
const BodyHeader = memo(({ table, testPrefix, ...rest }) => {
124+
const BodyHeader = memo(({ table, testPrefix, enableColumnReordering, ...rest }) => {
88125
useTableState(rerenderSelector)
89126

90-
return (
127+
const [activeId, setActiveId] = useState(null)
128+
129+
const sensors = useSensors(
130+
useSensor(PointerSensor, {
131+
activationConstraint: {
132+
distance: 5,
133+
},
134+
}),
135+
useSensor(KeyboardSensor)
136+
)
137+
138+
const activeColumn = useMemo(() => {
139+
if (!activeId) return null
140+
return table.getColumn(activeId)
141+
}, [activeId, table])
142+
143+
const handleDragStart = useCallback(event => {
144+
setActiveId(event.active.id)
145+
}, [])
146+
147+
const handleDragEnd = useCallback(
148+
event => {
149+
const { active, over } = event
150+
setActiveId(null)
151+
152+
if (!active || !over || active.id === over.id) return
153+
154+
const currentOrder = table.getState().columnOrder
155+
const allColumns = table.getAllLeafColumns()
156+
157+
let columnOrder = currentOrder.length > 0 ? currentOrder : allColumns.map(c => c.id)
158+
159+
const oldIndex = columnOrder.indexOf(active.id)
160+
const newIndex = columnOrder.indexOf(over.id)
161+
162+
if (oldIndex === -1 || newIndex === -1) return
163+
164+
const activeColumn = table.getColumn(active.id)
165+
const overColumn = table.getColumn(over.id)
166+
if (activeColumn?.getIsPinned() !== overColumn?.getIsPinned()) return
167+
168+
const newOrder = arrayMove(columnOrder, oldIndex, newIndex)
169+
table.setColumnOrder(newOrder)
170+
},
171+
[table]
172+
)
173+
174+
const handleDragCancel = useCallback(() => {
175+
setActiveId(null)
176+
}, [])
177+
178+
const headerContent = (
91179
<HeaderRow
92180
data-testid={`netdata-table-head${testPrefix}`}
93181
flex
@@ -106,13 +194,15 @@ const BodyHeader = memo(({ table, testPrefix, ...rest }) => {
106194
{...rest}
107195
flex={false}
108196
table={table}
197+
enableColumnReordering={enableColumnReordering}
109198
/>
110199
<HeaderGroups
111200
groups={table.getCenterHeaderGroups()}
112201
size={table.getCenterTotalSize()}
113202
testPrefix={testPrefix}
114203
{...rest}
115204
table={table}
205+
enableColumnReordering={enableColumnReordering}
116206
/>
117207
<HeaderGroups
118208
groups={table.getRightHeaderGroups()}
@@ -122,10 +212,29 @@ const BodyHeader = memo(({ table, testPrefix, ...rest }) => {
122212
{...rest}
123213
flex={false}
124214
table={table}
215+
enableColumnReordering={enableColumnReordering}
125216
rowReverse
126217
/>
127218
</HeaderRow>
128219
)
220+
221+
if (!enableColumnReordering) {
222+
return headerContent
223+
}
224+
225+
return (
226+
<DndContext
227+
sensors={sensors}
228+
collisionDetection={closestCenter}
229+
modifiers={[restrictToHorizontalAxis]}
230+
onDragStart={handleDragStart}
231+
onDragEnd={handleDragEnd}
232+
onDragCancel={handleDragCancel}
233+
>
234+
{headerContent}
235+
<DragOverlay activeColumn={activeColumn} />
236+
</DndContext>
237+
)
129238
})
130239

131240
export default BodyHeader
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useSortable } from "@dnd-kit/sortable"
2+
import { CSS } from "@dnd-kit/utilities"
3+
4+
const useSortableHeader = (columnId, enabled) => {
5+
const {
6+
attributes,
7+
listeners,
8+
setNodeRef,
9+
transform,
10+
transition,
11+
isDragging,
12+
} = useSortable({
13+
id: columnId,
14+
disabled: !enabled,
15+
})
16+
17+
const style = {
18+
transform: CSS.Transform.toString(transform),
19+
transition,
20+
opacity: isDragging ? 0.5 : 1,
21+
position: "relative",
22+
zIndex: isDragging ? 1 : undefined,
23+
}
24+
25+
return {
26+
sortableRef: setNodeRef,
27+
sortableStyle: style,
28+
dragHandleProps: enabled ? { ...attributes, ...listeners } : {},
29+
isDragging,
30+
}
31+
}
32+
33+
export default useSortableHeader

src/components/table/body/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const Body = memo(
3434
virtualRef,
3535
initialOffset = 0,
3636
onScroll,
37+
enableColumnReordering,
3738
...rest
3839
}) => {
3940
useTableState(rerenderSelector)
@@ -134,6 +135,7 @@ const Body = memo(
134135
testPrefix={testPrefix}
135136
coloredSortedColumn={coloredSortedColumn}
136137
index={virtualRow.index}
138+
enableColumnReordering={enableColumnReordering}
137139
{...rest}
138140
/>
139141
) : (

0 commit comments

Comments
 (0)