💄Added SortableList

This commit is contained in:
SrGooglo 2023-05-09 21:41:47 +00:00
parent 42578bb08a
commit 74c20d3b24
3 changed files with 381 additions and 1 deletions

View File

@ -34,11 +34,14 @@
"@capacitor/storage": "1.2.4",
"@capgo/capacitor-updater": "^4.12.8",
"@corenode/utils": "0.28.26",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/css": "11.0.0",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@loadable/component": "5.15.2",
"@mui/material": "^5.11.9",
"@paciolan/remote-component": "^2.13.0",
"@tsmx/human-readable": "^1.0.7",
"antd": "^5.2.1",
"antd-mobile": "^5.0.0-rc.17",
@ -171,4 +174,4 @@
]
}
}
}
}

View File

@ -0,0 +1,268 @@
import React from "react"
import { Button } from "antd"
import { Icons, createIconRender } from "components/Icons"
import classnames from "classnames"
import useLongPress from "hooks/useLongPress"
import {
DndContext,
TouchSensor,
MouseSensor,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragOverlay,
defaultDropAnimationSideEffects,
} from "@dnd-kit/core"
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
useSortable,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import "./index.less"
export const SortableItemChildrenContext = React.createContext({
attributes: {},
listeners: undefined,
ref() { },
activeDrag: true,
})
export const SortableItemContext = React.createContext({
activeDrag: true,
onLongPress() { },
setActiveDrag() { },
})
export function DragHandle() {
const { attributes, listeners, ref, activeDrag } = React.useContext(SortableItemChildrenContext)
return (
<button
className={classnames(
"drag-handle",
{
["active"]: activeDrag
}
)}
{...attributes}
{...listeners}
ref={ref}
>
<svg viewBox="0 0 20 20" width="12">
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"></path>
</svg>
</button>
)
}
export function SortableOverlay({ children }) {
const dropAnimationConfig = {
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: {
opacity: "0.4"
}
}
})
}
return (
<DragOverlay dropAnimation={dropAnimationConfig}>{children}</DragOverlay>
)
}
export function SortableItem({
children,
id,
}) {
const { activeDrag, onLongPress } = React.useContext(SortableItemContext)
const {
attributes,
isDragging,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
} = useSortable({ id })
const context = React.useMemo(
() => ({
attributes,
listeners,
ref: setActivatorNodeRef,
activeDrag: activeDrag
}),
[attributes, listeners, setActivatorNodeRef, activeDrag]
)
const style = {
opacity: isDragging ? 0.4 : undefined,
transform: CSS.Translate.toString(transform),
transition,
}
return (
<SortableItemChildrenContext.Provider value={context}>
<li
className="sortable-item"
ref={setNodeRef}
style={style}
{...useLongPress(onLongPress)}
>
{children}
<DragHandle />
</li>
</SortableItemChildrenContext.Provider>
)
}
export const DragActiveActions = ({
actions
}) => {
const { activeDrag, setActiveDrag } = React.useContext(SortableItemContext)
return <div
className={classnames(
"drag-actions",
{
["active"]: activeDrag
}
)}
>
<Button
type="primary"
size="small"
icon={<Icons.Check />}
onClick={() => setActiveDrag(false)}
/>
{
actions?.map((action, index) => {
return <Button
key={index}
type={action.type ?? "default"}
size="small"
icon={createIconRender(action.icon)}
onClick={action.onClick}
disabled={action.disabled}
danger={action.danger}
>
{action.label}
</Button>
})
}
</div>
}
export const SortableList = ({
items,
onChange,
renderItem,
activationConstraint,
useDragOverlay,
useActiveDragActions = true,
activeDragActions,
}) => {
const [active, setActive] = React.useState(null)
const [activeDrag, setActiveDrag] = React.useState(false)
const activeItem = React.useMemo(() => items.find((item) => item.id === active?.id), [active, items])
const context = React.useMemo(
() => ({
activeDrag,
onLongPress: () => setActiveDrag(true),
setActiveDrag: setActiveDrag,
}),
)
const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint,
}),
useSensor(TouchSensor, {
activationConstraint,
}),
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
)
const onDragStart = ({ active }) => {
if (!activeDrag) {
return
}
setActive(active)
}
const onDragEnd = ({ active, over }) => {
if (!activeDrag) {
return
}
if (over && active.id !== over?.id) {
const activeIndex = items.findIndex(({ id }) => id === active.id);
const overIndex = items.findIndex(({ id }) => id === over.id);
onChange(arrayMove(items, activeIndex, overIndex));
}
setActive(null);
}
const onDragCancel = () => {
setActive(null)
}
return <DndContext
sensors={sensors}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragCancel={onDragCancel}
>
<SortableContext items={items}>
<SortableItemContext.Provider value={context}>
<ul
className={classnames(
"shortable-list",
{
["active-drag"]: activeDrag
}
)}
role="application"
>
{
items.map((item, index) => (
<React.Fragment key={item.id}>
{
renderItem(item, index)
}
</React.Fragment>
))
}
</ul>
{
useActiveDragActions && <DragActiveActions
actions={activeDragActions}
/>
}
</SortableItemContext.Provider>
</SortableContext>
{
useDragOverlay && <SortableOverlay>
{activeItem ? renderItem(activeItem) : null}
</SortableOverlay>
}
</DndContext>
}

View File

@ -0,0 +1,109 @@
.shortable-list {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0;
list-style: none;
transition: all 0.2s ease-in-out;
&.active-drag {
//border: 1px dashed var(--border-color);
border-radius: 12px;
padding: 10px;
background-color: var(--background-color-accent);
}
}
.sortable-item {
display: flex;
justify-content: space-between;
flex-grow: 1;
align-items: center;
//box-shadow: 0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05), 0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15);
//border-radius: calc(4px / var(--scale-x, 1));
box-sizing: border-box;
list-style: none;
}
.drag-handle {
display: flex;
width: 0px;
padding: 0px;
opacity: 0;
align-items: center;
justify-content: center;
flex: 0 0 auto;
touch-action: none;
cursor: var(--cursor, pointer);
border-radius: 5px;
border: none;
outline: none;
appearance: none;
background-color: transparent;
-webkit-tap-highlight-color: transparent;
transition: all 0.2s ease-in-out;
&.active {
width: 12px;
padding: 15px;
opacity: 1;
}
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
svg {
flex: 0 0 auto;
margin: auto;
height: 100%;
overflow: visible;
fill: #919eab;
}
}
.drag-actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
width: 0px;
height: 0px;
opacity: 0;
transition: all 0.2s ease-in-out;
&.active {
width: 100%;
height: 30px;
opacity: 1;
}
}