Skip to content

Commit

Permalink
Adding drag to reorder
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgperry committed Sep 9, 2021
1 parent 7840886 commit be5c949
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 149 deletions.
49 changes: 26 additions & 23 deletions cypress/integration/drag-to-reorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,48 @@ describe("Drag to reorder", () => {
it("Works correctly with other layout animations", () => {
cy.visit("?test=drag-to-reorder")
.wait(50)
.get("#h60")
.get("#Tomato")
.should(([$item]: any) => {
expectBbox($item, {
top: 0,
left: 0,
width: 300,
height: 60,
height: 68,
left: 350,
top: 174,
width: 340,
})
})
.get("#h40")
.get("#Cucumber")
.should(([$item]: any) => {
expectBbox($item, {
top: 160,
left: 0,
width: 300,
height: 40,
height: 68,
left: 350,
top: 252,
width: 340,
})
})
.trigger("pointerdown", 300, 300, { force: true })
.get("#Tomato")
.trigger("pointerdown", 360, 175, { force: true })
.wait(50)
.trigger("pointermove", 295, 295, { force: true })
.wait(50)
.trigger("pointermove", 200, 200, { force: true })
.trigger("pointermove", 360, 180, { force: true })
.wait(20)
.trigger("pointermove", 360, 200, { force: true })
.wait(20)
.trigger("pointermove", 360, 220, { force: true })
.wait(50)
.should(([$item]: any) => {
expectBbox($item, {
top: 55,
left: 0,
width: 300,
height: 40,
height: 68,
left: 350,
top: 204,
width: 340,
})
})
.get("#h60")
.get("#Cucumber")
.should(([$item]: any) => {
expectBbox($item, {
top: 0,
left: 0,
width: 300,
height: 60,
height: 68,
left: 350,
top: 174,
width: 340,
})
})
})
Expand Down
26 changes: 19 additions & 7 deletions dev/tests/drag-to-reorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const inactiveShadow = "0px 0px 0px rgba(0,0,0,0.8)"
const Item = ({ item }) => {
const y = useMotionValue(0)
const boxShadow = useMotionValue(inactiveShadow)
// const dragControls = useDragControls()

useEffect(() => {
let isActive = false
Expand All @@ -25,13 +26,21 @@ const Item = ({ item }) => {
}
})
}, [y, boxShadow])
// box shadow
// automatic zindex

return (
<Reorder.Item as="li" y={y} id={item} style={{ boxShadow }}>
<Reorder.Item
value={item}
id={item}
drag
// dragListener={false}
// dragControls={dragControls}
style={{ boxShadow, y }}
transition={{ duration: 0.1 }}
>
<span>{item}</span>
<ReorderIcon />
<ReorderIcon
// dragControls={dragControls}
/>
</Reorder.Item>
)
}
Expand All @@ -40,7 +49,7 @@ export const App = () => {
const [items, setItems] = useState(initialItems)

return (
<Reorder.Group as="ul" axis="y" onReorder={setItems}>
<Reorder.Group onReorder={setItems}>
{items.map((item) => (
<Item key={item} item={item} />
))}
Expand All @@ -55,13 +64,16 @@ export interface Position {
height: number
}

function ReorderIcon() {
function ReorderIcon({ dragControls }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 39 39"
width="39"
height="39"
// onPointerDown={(e) => {
// dragControls.start(e)
// }}
>
<path
d="M 5 0 C 7.761 0 10 2.239 10 5 C 10 7.761 7.761 10 5 10 C 2.239 10 0 7.761 0 5 C 0 2.239 2.239 0 5 0 Z"
Expand Down Expand Up @@ -141,7 +153,6 @@ ul {
li {
border-radius: 10px;
margin-bottom: 10px;
cursor: pointer;
width: 100%;
padding: 20px;
position: relative;
Expand All @@ -155,6 +166,7 @@ li {
li svg {
width: 18px;
height: 18px;
cursor: grab;
}
.background {
Expand Down
104 changes: 68 additions & 36 deletions src/components/Reorder/Group.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,68 @@
export const Group = () => {}

// ({ children, axis, onReorder, as = "ul" }) => {
// const Component = useConstant(() => motion<typeof as>(as))
// const order: ItemData<T>[] = []
// const isReordering = useRef(false)

// const context: ReorderContextProps<any> = {
// axis,
// registerItem: (id, layout) => {
// order.push({ id, layout: layout[axis] })
// },
// updateOrder: (id, offset, velocity) => {
// if (isReordering.current) return

// const newOrder = checkReorder(order, id, offset, velocity)

// if (order !== newOrder) {
// isReordering.current = true
// onReorder(newOrder.map((item) => item.id))
// }
// },
// }

// useEffect(() => {
// isReordering.current = false
// })

// return (
// <Component>
// <ReorderContext.Provider value={context}>
// {children}
// </ReorderContext.Provider>
// </Component>
// )
// },
import * as React from "react"
import {
FunctionComponent,
PropsWithChildren,
ReactHTML,
useEffect,
useRef,
} from "react"
import { ReorderContext } from "../../context/ReorderContext"
import { motion } from "../../render/dom/motion"
import { HTMLMotionProps } from "../../render/html/types"
import { useConstant } from "../../utils/use-constant"
import { ItemData, ReorderContextProps } from "./types"
import { checkReorder } from "./utils/check-reorder"

interface Props<V> {
as?: keyof ReactHTML
axis?: "x" | "y"
onReorder: (newOrder: V[]) => void
}

export function Group<V>({
children,
as = "ul",
axis = "y",
onReorder,
...props
}: Props<V> & HTMLMotionProps<any> & PropsWithChildren<{}>) {
const Component = useConstant(() => motion(as)) as FunctionComponent<
HTMLMotionProps<any>
>

const order: ItemData<V>[] = []
const isReordering = useRef(false)

const context: ReorderContextProps<any> = {
axis,
registerItem: (value, layout) => {
order.push({ value, layout: layout[axis] })
},
updateOrder: (id, offset, velocity) => {
if (isReordering.current) return

const newOrder = checkReorder(order, id, offset, velocity)

if (order !== newOrder) {
isReordering.current = true
onReorder(newOrder.map(getValue))
}
},
}

useEffect(() => {
isReordering.current = false
})

return (
<Component {...props}>
<ReorderContext.Provider value={context}>
{children}
</ReorderContext.Provider>
</Component>
)
}

function getValue<V>(item: ItemData<V>) {
return item.value
}
110 changes: 58 additions & 52 deletions src/components/Reorder/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,79 @@
import { ReactHTML } from "hoist-non-react-statics/node_modules/@types/react"
import { invariant } from "hey-listen"
import * as React from "react"
import { motion } from "../.."
import { MotionStyle } from "../../motion/types"
import {
PropsWithChildren,
ReactHTML,
FunctionComponent,
useContext,
useEffect,
useRef,
} from "react"
import { Box, motion, useMotionValue, useTransform } from "../.."
import { ReorderContext } from "../../context/ReorderContext"
import { HTMLMotionProps } from "../../render/html/types"
import { useConstant } from "../../utils/use-constant"
import { MotionValue } from "../../value"
import { isMotionValue } from "../../value/utils/is-motion-value"

interface Props<V> {
as?: keyof ReactHTML
x?: MotionValue<number>
y?: MotionValue<number>
value: V
style?: MotionStyle
}

function useDefaultMotionValue(value: any, defaultValue: number = 0) {
return isMotionValue(value) ? value : useMotionValue(defaultValue)
}

/**
* TODO: Fix slippage when dragging
*/
export function Item<V>({
children,
value,
style,
x,
y,
as,
value,
as = "li",
...props
}: Props<V> & HTMLMotionProps<typeof as>) {
const Component = useConstant(() => motion(as))
}: Props<V> & HTMLMotionProps<any> & PropsWithChildren<{}>) {
const Component = useConstant(() => motion(as)) as FunctionComponent<
HTMLMotionProps<any>
>

return <Component {...props}></Component>
}
const context = useContext(ReorderContext)
const point = {
x: useDefaultMotionValue(style?.x),
y: useDefaultMotionValue(style?.y),
}

const zIndex = useTransform([point.x, point.y], ([latestX, latestY]) =>
latestX || latestY ? 1 : 0
)

// ({ children, value, style, x, y, as = "li", ...props }) => {
// const Component = useConstant(() => motion<typeof as>(as))
// const context = useContext(ReorderContext)
// const point = {
// x: x || useMotionValue(0),
// y: y || useMotionValue(0),
// }
// const zIndex = useTransform([point.x, point.y], ([latestX, latestY]) =>
// latestX || latestY ? 1 : 0
// )
// const layout = useRef<Box | null>(null)
const layout = useRef<Box | null>(null)

// invariant(
// Boolean(context),
// "Reorder.Item must be a child of Reorder.Group"
// )
invariant(Boolean(context), "Reorder.Item must be a child of Reorder.Group")

// const { axis, registerItem, updateOrder } = context!
const { axis, registerItem, updateOrder } = context!

// useEffect(() => {
// registerItem(value, layout.current!)
// })
useEffect(() => {
registerItem(value, layout.current!)
})

// return (
// <Component
// {...props}
// style={{ ...style, x: point.x, y: point.y, zIndex }}
// layout
// drag
// dragConstraints={originConstraints}
// dragElastic={1}
// onDrag={(_event, { velocity }) => {
// velocity[axis] &&
// updateOrder(value, point[axis].get(), velocity[axis])
// }}
// onLayoutMeasure={(measured) => (layout.current = measured)}
// >
// {children}
// </Component>
// )
// }
return (
<Component
drag={axis}
{...props}
style={{ ...style, x: point.x, y: point.y, zIndex }}
layout
dragConstraints={originConstraints}
dragElastic={1}
onDrag={(_event, { velocity }) => {
velocity[axis] &&
updateOrder(value, point[axis].get(), velocity[axis])
}}
onLayoutMeasure={(measured) => (layout.current = measured)}
>
{children}
</Component>
)
}

const originConstraints = { top: 0, left: 0, right: 0, bottom: 0 }
7 changes: 0 additions & 7 deletions src/components/Reorder/Trigger.tsx

This file was deleted.

0 comments on commit be5c949

Please sign in to comment.