diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 7234534ff44..50d0572774b 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -6,6 +6,10 @@ "build" ], "devDependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", "@juggle/resize-observer": "^3.1.2", "@playwright/test": "^1.37.1", "@reach/listbox": "^0.18.0", diff --git a/packages/desktop-client/src/components/sidebar/Account.tsx b/packages/desktop-client/src/components/sidebar/Account.tsx index dc42b4e7a78..b458a069875 100644 --- a/packages/desktop-client/src/components/sidebar/Account.tsx +++ b/packages/desktop-client/src/components/sidebar/Account.tsx @@ -1,6 +1,8 @@ // @ts-strict-ignore import React from 'react'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import { css } from 'glamor'; import { type AccountEntity } from 'loot-core/src/types/models'; @@ -9,13 +11,6 @@ import { styles, theme, type CSSProperties } from '../../style'; import { AlignedText } from '../common/AlignedText'; import { AnchorLink } from '../common/AnchorLink'; import { View } from '../common/View'; -import { - useDraggable, - useDroppable, - DropHighlight, - type OnDragChangeCallback, - type OnDropCallback, -} from '../sort'; import { type Binding } from '../spreadsheet'; import { CellValue } from '../spreadsheet/CellValue'; @@ -42,8 +37,6 @@ type AccountProps = { updated?: boolean; style?: CSSProperties; outerStyle?: CSSProperties; - onDragChange?: OnDragChangeCallback<{ id: string }>; - onDrop?: OnDropCallback; }; export function Account({ @@ -56,92 +49,85 @@ export function Account({ query, style, outerStyle, - onDragChange, - onDrop, }: AccountProps) { - const type = account - ? account.closed - ? 'account-closed' - : account.offbudget - ? 'account-offbudget' - : 'account-onbudget' - : 'title'; + const { + isDragging, + attributes, + listeners, + setNodeRef, + transform, + transition, + } = useSortable({ id: account?.id || `sortable-account-${name}` }); - const { dragRef } = useDraggable({ - type, - onDragChange, - item: { id: account && account.id }, - canDrag: account != null, - }); - - const { dropRef, dropPos } = useDroppable({ - types: account ? [type] : [], - id: account && account.id, - onDrop, - }); + const dndStyle = { + opacity: isDragging ? 0.5 : undefined, + transform: CSS.Transform.toString(transform), + transition, + }; return ( - - - - - - -
- - - } - /> - + + + +
- + + } + /> + ); } diff --git a/packages/desktop-client/src/components/sidebar/Accounts.tsx b/packages/desktop-client/src/components/sidebar/Accounts.tsx index c77a9ae365c..81d8573ed5d 100644 --- a/packages/desktop-client/src/components/sidebar/Accounts.tsx +++ b/packages/desktop-client/src/components/sidebar/Accounts.tsx @@ -1,11 +1,25 @@ // @ts-strict-ignore -import React, { useState, useMemo } from 'react'; +import React, { useMemo } from 'react'; + +import { + DndContext, + KeyboardSensor, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; import { type AccountEntity } from 'loot-core/src/types/models'; import { SvgAdd } from '../../icons/v1'; import { View } from '../common/View'; -import { type OnDropCallback } from '../sort'; import { type Binding } from '../spreadsheet'; import { Account } from './Account'; @@ -34,7 +48,7 @@ type AccountsProps = { showClosedAccounts: boolean; onAddAccount: () => void; onToggleClosedAccounts: () => void; - onReorder: OnDropCallback; + onReorder: (id: string, dropPos: 'top' | 'bottom', targetId: string) => void; }; export function Accounts({ @@ -54,7 +68,6 @@ export function Accounts({ onToggleClosedAccounts, onReorder, }: AccountsProps) { - const [isDragging, setIsDragging] = useState(false); const offbudgetAccounts = useMemo( () => accounts.filter( @@ -74,100 +87,120 @@ export function Accounts({ [accounts], ); - function onDragChange(drag) { - setIsDragging(drag.state === 'start'); - } + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const onDragEnd = e => { + const { active, over } = e; + + if (active.id !== over.id) { + const dropPos = + active.data.current.sortable.index < over.data.current.sortable.index + ? 'bottom' + : 'top'; - const makeDropPadding = (i, length) => { - if (i === 0) { - return { - paddingTop: isDragging ? 15 : 0, - marginTop: isDragging ? -15 : 0, - }; + onReorder(active.id, dropPos, over.id); } - return null; }; return ( - - - {budgetedAccounts.length > 0 && ( - - )} - - {budgetedAccounts.map((account, i) => ( - - ))} - - {offbudgetAccounts.length > 0 && ( + - )} - {offbudgetAccounts.map((account, i) => ( - - ))} - - {closedAccounts.length > 0 && ( - - )} - - {showClosedAccounts && - closedAccounts.map((account, i) => ( + {budgetedAccounts.length > 0 && ( + + )} + + {budgetedAccounts.map((account, i) => ( + + ))} + + + {offbudgetAccounts.length > 0 && ( + )} + + + {offbudgetAccounts.map((account, i) => ( + + ))} + + + {closedAccounts.length > 0 && ( + - ))} + )} + + {showClosedAccounts && ( + + {closedAccounts.map((account, i) => ( + + ))} + + )} + =16.8.0" + checksum: 750a0537877d5dde3753e9ef59d19628b553567e90fc3e3b14a79bded08f47f4a7161bc0d003d7cd6b3bd9e10aa233628dca07d2aa5a2120cac84555ba1653d8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.1.0": + version: 6.1.0 + resolution: "@dnd-kit/core@npm:6.1.0" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.0" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: cf9e99763fbd9220cb6fdde2950c19fdf6248391234f5ee835601814124445fd8a6e4b3f5bc35543c802d359db8cc47f07d87046577adc41952ae981a03fbda0 + languageName: node + linkType: hard + +"@dnd-kit/modifiers@npm:^7.0.0": + version: 7.0.0 + resolution: "@dnd-kit/modifiers@npm:7.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.1.0 + react: ">=16.8.0" + checksum: 9ee0b7b86c23c15f6820d76ec398724597abc9d9e31cf58836e7f0b9935e33f9136a60ee9600eb27818447623f07786d4fed3f1d685d9cc6d860d8f6c5354ae3 + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^8.0.0": + version: 8.0.0 + resolution: "@dnd-kit/sortable@npm:8.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.1.0 + react: ">=16.8.0" + checksum: e2e0d37ace13db2e6aceb65a803195ef29e1a33a37e7722a988d7a9c1aacce77472a93b2adcd8e6780ac98b3d5640c5481892f530177c2eb966df235726942ad + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 6cfe46a5fcdaced943982e7ae66b08b89235493e106eb5bc833737c25905e13375c6ecc3aa0c357d136cb21dae3966213dba063f19b7a60b1235a29a7b05ff84 + languageName: node + linkType: hard + "@electron/asar@npm:^3.2.1": version: 3.2.4 resolution: "@electron/asar@npm:3.2.4" @@ -16107,6 +16173,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.0.0": + version: 2.6.2 + resolution: "tslib@npm:2.6.2" + checksum: bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca + languageName: node + linkType: hard + "tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0": version: 2.5.3 resolution: "tslib@npm:2.5.3"