diff --git a/packages/app/package.json b/packages/app/package.json
index 66ffc257..4efa4536 100755
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -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 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/app/src/components/SortableList/index.jsx b/packages/app/src/components/SortableList/index.jsx
new file mode 100644
index 00000000..5cc55782
--- /dev/null
+++ b/packages/app/src/components/SortableList/index.jsx
@@ -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 (
+
+ )
+}
+
+export function SortableOverlay({ children }) {
+ const dropAnimationConfig = {
+ sideEffects: defaultDropAnimationSideEffects({
+ styles: {
+ active: {
+ opacity: "0.4"
+ }
+ }
+ })
+ }
+
+ return (
+ {children}
+ )
+}
+
+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 (
+
+
+ {children}
+
+
+
+ )
+}
+
+export const DragActiveActions = ({
+ actions
+}) => {
+ const { activeDrag, setActiveDrag } = React.useContext(SortableItemContext)
+
+ return
+ }
+ onClick={() => setActiveDrag(false)}
+ />
+
+ {
+ actions?.map((action, index) => {
+ return
+ })
+ }
+
+}
+
+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
+
+
+
+ {
+ items.map((item, index) => (
+
+ {
+ renderItem(item, index)
+ }
+
+ ))
+ }
+
+
+ {
+ useActiveDragActions &&
+ }
+
+
+
+ {
+ useDragOverlay &&
+ {activeItem ? renderItem(activeItem) : null}
+
+ }
+
+}
\ No newline at end of file
diff --git a/packages/app/src/components/SortableList/index.less b/packages/app/src/components/SortableList/index.less
new file mode 100644
index 00000000..3e00612a
--- /dev/null
+++ b/packages/app/src/components/SortableList/index.less
@@ -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;
+ }
+}
\ No newline at end of file