-
-
Notifications
You must be signed in to change notification settings - Fork 277
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
Showing
7 changed files
with
454 additions
and
32 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
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,282 @@ | ||
import React, { useEffect, useRef, useState } from 'react'; | ||
import { createPortal } from 'react-dom'; | ||
|
||
import { | ||
Active, | ||
Announcements, | ||
closestCenter, | ||
CollisionDetection, | ||
DragOverlay, | ||
DndContext, | ||
DropAnimation, | ||
KeyboardSensor, | ||
KeyboardCoordinateGetter, | ||
Modifiers, | ||
MouseSensor, | ||
MeasuringConfiguration, | ||
PointerActivationConstraint, | ||
ScreenReaderInstructions, | ||
TouchSensor, | ||
UniqueIdentifier, | ||
useSensor, | ||
useSensors, | ||
defaultDropAnimationSideEffects, | ||
} from '@dnd-kit/core'; | ||
import { | ||
arrayMove, | ||
SortableContext, | ||
sortableKeyboardCoordinates, | ||
SortingStrategy, | ||
rectSortingStrategy, | ||
AnimateLayoutChanges, | ||
NewIndexGetter, | ||
useSortable, | ||
} from '@dnd-kit/sortable'; | ||
|
||
import { useApps } from '@os/apps/hooks/useApps'; | ||
import { IApp } from '@os/apps/config/apps'; | ||
import { CSS } from '@dnd-kit/utilities'; | ||
import { AppIcon } from '@ui/components'; | ||
|
||
const defaultInitializer = (index: number) => index; | ||
|
||
export function createRange<T = number>( | ||
length: number, | ||
initializer: (index: number) => any = defaultInitializer, | ||
): T[] { | ||
return [...new Array(length)].map((_, index) => initializer(index)); | ||
} | ||
|
||
export interface Props { | ||
activationConstraint?: PointerActivationConstraint; | ||
animateLayoutChanges?: AnimateLayoutChanges; | ||
adjustScale?: boolean; | ||
collisionDetection?: CollisionDetection; | ||
coordinateGetter?: KeyboardCoordinateGetter; | ||
Container?: any; // To-do: Fix me | ||
dropAnimation?: DropAnimation | null; | ||
getNewIndex?: NewIndexGetter; | ||
handle?: boolean; | ||
itemCount?: number; | ||
items?: UniqueIdentifier[]; | ||
measuring?: MeasuringConfiguration; | ||
modifiers?: Modifiers; | ||
renderItem?: any; | ||
removable?: boolean; | ||
reorderItems?: typeof arrayMove; | ||
strategy?: SortingStrategy; | ||
style?: React.CSSProperties; | ||
useDragOverlay?: boolean; | ||
|
||
getItemStyles?(args: { | ||
id: UniqueIdentifier; | ||
index: number; | ||
isSorting: boolean; | ||
isDragOverlay: boolean; | ||
overIndex: number; | ||
isDragging: boolean; | ||
}): React.CSSProperties; | ||
|
||
wrapperStyle?(args: { | ||
active: Pick<Active, 'id'> | null; | ||
index: number; | ||
isDragging: boolean; | ||
id: UniqueIdentifier; | ||
}): React.CSSProperties; | ||
|
||
isDisabled?(id: UniqueIdentifier): boolean; | ||
} | ||
|
||
const dropAnimationConfig: DropAnimation = { | ||
sideEffects: defaultDropAnimationSideEffects({ | ||
styles: { | ||
active: { | ||
opacity: '0.5', | ||
}, | ||
}, | ||
}), | ||
}; | ||
|
||
const screenReaderInstructions: ScreenReaderInstructions = { | ||
draggable: ` | ||
To pick up a sortable item, press the space bar. | ||
While sorting, use the arrow keys to move the item. | ||
Press space again to drop the item in its new position, or press escape to cancel. | ||
`, | ||
}; | ||
|
||
export function SortableApps({ | ||
activationConstraint, | ||
collisionDetection = closestCenter, | ||
coordinateGetter = sortableKeyboardCoordinates, | ||
itemCount = 16, | ||
items: initialItems, | ||
measuring, | ||
modifiers, | ||
removable, | ||
reorderItems = arrayMove, | ||
strategy = rectSortingStrategy, | ||
}: Props) { | ||
const [items, setItems] = useState<UniqueIdentifier[]>( | ||
() => initialItems ?? createRange<UniqueIdentifier>(itemCount, (index) => index + 1), | ||
); | ||
|
||
const { apps, setApps } = useApps(); | ||
|
||
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null); | ||
const sensors = useSensors( | ||
useSensor(MouseSensor, { | ||
activationConstraint, | ||
}), | ||
useSensor(TouchSensor, { | ||
activationConstraint, | ||
}), | ||
useSensor(KeyboardSensor, { | ||
// Disable smooth scrolling in Cypress automated tests | ||
scrollBehavior: 'Cypress' in window ? 'auto' : undefined, | ||
coordinateGetter, | ||
}), | ||
); | ||
const isFirstAnnouncement = useRef(true); | ||
const getIndex = (id: UniqueIdentifier) => items.indexOf(id); | ||
const getPosition = (id: UniqueIdentifier) => getIndex(id) + 1; | ||
const activeIndex = activeId ? getIndex(activeId) : -1; | ||
const handleRemove = removable | ||
? (id: UniqueIdentifier) => setItems((items) => items.filter((item) => item !== id)) | ||
: undefined; | ||
const announcements: Announcements = { | ||
onDragStart({ active: { id } }) { | ||
return `Picked up sortable item ${String( | ||
id, | ||
)}. Sortable item ${id} is in position ${getPosition(id)} of ${items.length}`; | ||
}, | ||
onDragOver({ active, over }) { | ||
// In this specific use-case, the picked up item's `id` is always the same as the first `over` id. | ||
// The first `onDragOver` event therefore doesn't need to be announced, because it is called | ||
// immediately after the `onDragStart` announcement and is redundant. | ||
if (isFirstAnnouncement.current === true) { | ||
isFirstAnnouncement.current = false; | ||
return; | ||
} | ||
|
||
if (over) { | ||
return `Sortable item ${active.id} was moved into position ${getPosition(over.id)} of ${ | ||
items.length | ||
}`; | ||
} | ||
|
||
return; | ||
}, | ||
onDragEnd({ active, over }) { | ||
if (over) { | ||
return `Sortable item ${active.id} was dropped at position ${getPosition(over.id)} of ${ | ||
items.length | ||
}`; | ||
} | ||
|
||
return; | ||
}, | ||
onDragCancel({ active: { id } }) { | ||
return `Sorting was cancelled. Sortable item ${id} was dropped and returned to position ${getPosition( | ||
id, | ||
)} of ${items.length}.`; | ||
}, | ||
}; | ||
|
||
useEffect(() => { | ||
if (!activeId) { | ||
isFirstAnnouncement.current = true; | ||
} | ||
}, [activeId]); | ||
|
||
return ( | ||
<DndContext | ||
accessibility={{ | ||
announcements, | ||
screenReaderInstructions, | ||
}} | ||
sensors={sensors} | ||
collisionDetection={collisionDetection} | ||
onDragStart={({ active }) => { | ||
if (!active) { | ||
return; | ||
} | ||
|
||
setActiveId(active.id); | ||
}} | ||
onDragEnd={({ over }) => { | ||
setActiveId(null); | ||
|
||
if (over) { | ||
const overIndex = getIndex(over.id); | ||
if (activeIndex !== overIndex) { | ||
setItems((items) => reorderItems(items, activeIndex, overIndex)); | ||
} | ||
} | ||
}} | ||
onDragCancel={() => setActiveId(null)} | ||
measuring={measuring} | ||
modifiers={modifiers} | ||
> | ||
<div> | ||
<SortableContext items={items} strategy={strategy}> | ||
<div> | ||
{apps.map((value, index) => ( | ||
<> | ||
<SortableAppItem {...value} /> | ||
</> | ||
))} | ||
</div> | ||
</SortableContext> | ||
</div> | ||
</DndContext> | ||
); | ||
} | ||
|
||
const SortableAppItem = ({ | ||
app: IApp, | ||
disabled, | ||
animateLayoutChanges, | ||
getNewIndex, | ||
handle, | ||
id, | ||
index, | ||
onRemove, | ||
style, | ||
renderItem, | ||
useDragOverlay, | ||
wrapperStyle, | ||
}) => { | ||
/*const { transition, transform, attributes, setNodeRef, listeners } = useSortable({ | ||
id: app.id, | ||
});*/ | ||
|
||
const { | ||
active, | ||
attributes, | ||
isDragging, | ||
isSorting, | ||
listeners, | ||
overIndex, | ||
setNodeRef, | ||
setActivatorNodeRef, | ||
transform, | ||
transition, | ||
} = useSortable({ | ||
id, | ||
animateLayoutChanges, | ||
disabled, | ||
getNewIndex, | ||
}); | ||
|
||
const style = { | ||
transform: CSS.Transform.toString(transform), | ||
transition, | ||
}; | ||
|
||
return ( | ||
<div ref={setNodeRef} {...attributes} {...listeners} style={style}> | ||
<AppIcon {...app} /> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.