-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fc1e67e
commit ef30750
Showing
8 changed files
with
344 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import React from 'react'; | ||
import { DragDropContext, DropResult } from 'react-beautiful-dnd'; | ||
import { useEvent } from '@/hooks/useEvent'; | ||
import { reorder } from '@/utils/reorder'; | ||
import { BaseSortableData } from './types'; | ||
|
||
interface SortableContextProps<T extends BaseSortableData = BaseSortableData> { | ||
list: T[]; | ||
onChange: (list: T[]) => void; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const SortableContext = <T extends BaseSortableData>( | ||
props: SortableContextProps<T> | ||
) => { | ||
const { list, onChange, children } = props; | ||
|
||
const handleDragEnd = useEvent((result: DropResult) => { | ||
// dropped outside the list | ||
if (!result.destination) { | ||
return; | ||
} | ||
|
||
if (result.type === 'root') { | ||
const final = reorder( | ||
list, | ||
result.source.index, | ||
result.destination.index | ||
); | ||
onChange(final); | ||
return; | ||
} | ||
|
||
if (result.type === 'group') { | ||
// move data from source to destination | ||
// NOTICE: now only support 1 level | ||
|
||
const final = [...list]; | ||
const sourceGroupIndex = final.findIndex( | ||
(group) => group.id === result.source.droppableId | ||
); | ||
if (sourceGroupIndex === -1) { | ||
return; | ||
} | ||
const destinationGroupIndex = final.findIndex( | ||
(group) => group.id === result.destination?.droppableId | ||
); | ||
if (destinationGroupIndex === -1) { | ||
return; | ||
} | ||
|
||
if (sourceGroupIndex === destinationGroupIndex) { | ||
if (!('items' in final[sourceGroupIndex])) { | ||
return; | ||
} | ||
|
||
// same group | ||
final[sourceGroupIndex].items = reorder( | ||
final[sourceGroupIndex].items!, | ||
result.source.index, | ||
result.destination.index | ||
); | ||
} else { | ||
// cross group | ||
if ( | ||
!('items' in final[sourceGroupIndex]) || | ||
!('items' in final[destinationGroupIndex]) | ||
) { | ||
return; | ||
} | ||
|
||
const sourceGroupItems = Array.from( | ||
final[sourceGroupIndex].items ?? [] | ||
); | ||
const [removed] = sourceGroupItems.splice(result.source.index, 1); | ||
|
||
const destinationGroupItems = Array.from( | ||
final[destinationGroupIndex].items ?? [] | ||
); | ||
destinationGroupItems.splice(result.destination.index, 0, removed); | ||
|
||
final[sourceGroupIndex].items = sourceGroupItems; | ||
final[destinationGroupIndex].items = destinationGroupItems; | ||
} | ||
|
||
onChange(final); | ||
} | ||
}); | ||
|
||
return ( | ||
<DragDropContext onDragEnd={handleDragEnd}>{children}</DragDropContext> | ||
); | ||
}; | ||
SortableContext.displayName = 'SortableGroup'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React from 'react'; | ||
import { SortableContext } from './SortableContext'; | ||
import { StrictModeDroppable } from './StrictModeDroppable'; | ||
import { useEvent } from '@/hooks/useEvent'; | ||
import { Draggable } from 'react-beautiful-dnd'; | ||
import { BaseSortableData, ExtractGroup, ExtractItem } from './types'; | ||
|
||
interface SortableGroupProps<T extends BaseSortableData> { | ||
list: T[]; | ||
onChange: (list: T[]) => void; | ||
renderGroup: ( | ||
group: ExtractGroup<T>, | ||
children: React.ReactNode | ||
) => React.ReactNode; | ||
renderItem: (item: ExtractItem<T>) => React.ReactNode; | ||
} | ||
|
||
export const SortableGroup = <T extends BaseSortableData>( | ||
props: SortableGroupProps<T> | ||
) => { | ||
const { list, onChange, renderGroup, renderItem } = props; | ||
|
||
const renderItemEl = useEvent((item: ExtractItem<T>, index: number) => { | ||
return ( | ||
<Draggable key={item.id} draggableId={item.id} index={index}> | ||
{(dragProvided) => ( | ||
<div | ||
ref={dragProvided.innerRef} | ||
{...dragProvided.draggableProps} | ||
{...dragProvided.dragHandleProps} | ||
> | ||
{renderItem(item)} | ||
</div> | ||
)} | ||
</Draggable> | ||
); | ||
}); | ||
|
||
const renderGroupEl = useEvent((group: ExtractGroup<T>, level = 0) => { | ||
return ( | ||
<StrictModeDroppable | ||
droppableId={group.id} | ||
type={group.type} | ||
key={group.id} | ||
> | ||
{(dropProvided) => ( | ||
<div ref={dropProvided.innerRef} {...dropProvided.droppableProps}> | ||
{renderGroup( | ||
group, | ||
<> | ||
{group.items.map((item, index) => | ||
item.type === 'item' ? ( | ||
renderItemEl(item as ExtractItem<T>, index) | ||
) : ( | ||
<Draggable | ||
draggableId={item.id} | ||
key={item.id} | ||
index={index} | ||
> | ||
{(dragProvided) => ( | ||
<div | ||
ref={dragProvided.innerRef} | ||
{...dragProvided.draggableProps} | ||
{...dragProvided.dragHandleProps} | ||
> | ||
{renderGroupEl(item as ExtractGroup<T>, level + 1)} | ||
</div> | ||
)} | ||
</Draggable> | ||
) | ||
)} | ||
</> | ||
)} | ||
|
||
{dropProvided.placeholder} | ||
</div> | ||
)} | ||
</StrictModeDroppable> | ||
); | ||
}); | ||
|
||
return ( | ||
<SortableContext<T> list={list} onChange={onChange}> | ||
{renderGroupEl( | ||
{ | ||
id: 'root', | ||
type: 'root' as const, | ||
items: list, | ||
} as any, | ||
0 | ||
)} | ||
</SortableContext> | ||
); | ||
}; | ||
SortableGroup.displayName = 'SortableGroup'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { Droppable, DroppableProps } from 'react-beautiful-dnd'; | ||
|
||
/** | ||
* https://github.com/atlassian/react-beautiful-dnd/issues/2350 | ||
*/ | ||
export const StrictModeDroppable: React.FC<DroppableProps> = React.memo( | ||
(props) => { | ||
const [enabled, setEnabled] = useState(false); | ||
|
||
useEffect(() => { | ||
const animation = requestAnimationFrame(() => setEnabled(true)); | ||
|
||
return () => { | ||
cancelAnimationFrame(animation); | ||
setEnabled(false); | ||
}; | ||
}, []); | ||
|
||
if (!enabled) { | ||
return null; | ||
} | ||
|
||
return <Droppable {...props}>{props.children}</Droppable>; | ||
} | ||
); | ||
StrictModeDroppable.displayName = 'StrictModeDroppable'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Id } from 'react-beautiful-dnd'; | ||
|
||
export type BaseSortableItem = { | ||
type: 'item'; | ||
id: Id; | ||
}; | ||
|
||
export type BaseSortableGroup<GroupProps = unknown, ItemProps = unknown> = { | ||
type: 'group'; | ||
id: Id; | ||
title?: string; | ||
items: ((BaseSortableGroup & GroupProps) | (BaseSortableItem & ItemProps))[]; | ||
}; | ||
|
||
export type BaseSortableRoot<GroupProps = unknown> = { | ||
type: 'root'; | ||
id: Id; | ||
title?: string; | ||
items: (BaseSortableItem & GroupProps)[]; | ||
}; | ||
|
||
export type BaseSortableData = | ||
| BaseSortableRoot | ||
| BaseSortableGroup | ||
| BaseSortableItem; | ||
|
||
export type SortableData<GroupProps = unknown, ItemProps = unknown> = | ||
| BaseSortableRoot<GroupProps> | ||
| (BaseSortableGroup<GroupProps, ItemProps> & GroupProps) | ||
| (BaseSortableItem & ItemProps); | ||
|
||
export type ExtractGroup<T> = T extends { type: 'group' } ? T : never; | ||
export type ExtractItem<T> = T extends { type: 'item' } ? T : never; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, { useState } from 'react'; | ||
import { SortableData } from '@/components/Sortable/types'; | ||
import { SortableGroup } from '@/components/Sortable/SortableGroup'; | ||
|
||
type MonitorStatusPageServiceItem = SortableData<{}, { title: string }>; | ||
|
||
export const MonitorStatusPageServiceList: React.FC = React.memo(() => { | ||
const [list, setList] = useState<MonitorStatusPageServiceItem[]>([ | ||
{ | ||
type: 'group', | ||
title: 'Group 1', | ||
id: 'group1', | ||
items: [ | ||
{ | ||
id: 'item1', | ||
type: 'item', | ||
title: 'Item 1', | ||
}, | ||
{ | ||
id: 'item2', | ||
type: 'item', | ||
title: 'Item 2', | ||
}, | ||
], | ||
}, | ||
{ | ||
type: 'group', | ||
title: 'Group 2', | ||
id: 'group2', | ||
items: [ | ||
{ | ||
id: 'item3', | ||
type: 'item', | ||
title: 'Item 3', | ||
}, | ||
{ | ||
id: 'item4', | ||
type: 'item', | ||
title: 'Item 4', | ||
}, | ||
], | ||
}, | ||
] as MonitorStatusPageServiceItem[]); | ||
|
||
return ( | ||
<SortableGroup | ||
list={list} | ||
onChange={(list) => setList(list)} | ||
renderGroup={(group, children) => ( | ||
<div> | ||
<div>{group.title}</div> | ||
<div className="p-2">{children}</div> | ||
</div> | ||
)} | ||
renderItem={(item) => <div>{item.id}</div>} | ||
/> | ||
); | ||
}); | ||
MonitorStatusPageServiceList.displayName = 'MonitorStatusPageServiceList'; |
Oops, something went wrong.