feat: set animation

This commit is contained in:
Ou 2024-10-10 00:29:59 +08:00
parent 731b31c8ce
commit a89edff8fa
5 changed files with 83 additions and 23 deletions

View File

@ -32,6 +32,7 @@
"db0": "npm:@ourongxing/db0@latest",
"fast-xml-parser": "^4.5.0",
"favicons-scraper": "^1.3.2",
"framer-motion": "^11.11.5",
"h3": "^1.13.0",
"iconv-lite": "^0.6.3",
"jotai": "^2.10.0",

24
pnpm-lock.yaml generated
View File

@ -62,6 +62,9 @@ importers:
favicons-scraper:
specifier: ^1.3.2
version: 1.3.2
framer-motion:
specifier: ^11.11.5
version: 11.11.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
h3:
specifier: ^1.13.0
version: 1.13.0
@ -3000,6 +3003,20 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
framer-motion@11.11.5:
resolution: {integrity: sha512-kwnnxRe3fTQyTr90VMBlrjAvKxZhUWQnR2lXrpX98dPBBlOY+JiTqHhlHoyCJskMpyGpiXS84TL0moI/MsS/5Q==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
@ -7743,6 +7760,13 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
framer-motion@11.11.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
tslib: 2.7.0
optionalDependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
fresh@0.5.2: {}
front-matter@4.0.2:

View File

@ -22,7 +22,7 @@ export const localSourcesAtom = atomWithLocalStorage<Record<SectionID, SourceID[
export const focusSourcesAtom = atom((get) => {
return get(localSourcesAtom).focus
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
}, (get, set, update: Update<SourceID[]>) => {
const _ = update instanceof Function ? update(get(focusSourcesAtom)) : update
set(localSourcesAtom, {
...get(localSourcesAtom),
@ -50,10 +50,12 @@ export const currentSectionIDAtom = atom<SectionID>("focus")
export const currentSectionAtom = atom((get) => {
const id = get(currentSectionIDAtom)
return get(localSourcesAtom)[id]
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
}, (get, set, update: Update<SourceID[]>) => {
const _ = update instanceof Function ? update(get(currentSectionAtom)) : update
set(localSourcesAtom, {
...get(localSourcesAtom),
[get(currentSectionIDAtom)]: _,
})
})
export type Update<T> = T | ((prev: T) => T)

View File

@ -1,3 +1,4 @@
import type { PropsWithChildren } from "react"
import { useCallback, useState } from "react"
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core"
import {
@ -13,12 +14,56 @@ import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from "@d
import { useAtom } from "jotai"
import type { SourceID } from "@shared/types"
import { CSS } from "@dnd-kit/utilities"
import { motion } from "framer-motion"
import type { ItemsProps } from "./card"
import { CardWrapper } from "./card"
import { currentSectionAtom } from "~/atoms"
export function Dnd() {
const [items, setItems] = useAtom(currentSectionAtom)
return (
<DndWrapper items={items} setItems={setItems}>
<motion.div
className="grid w-full gap-5"
style={{
gridTemplateColumns: "repeat(auto-fill, minmax(350px, 1fr))",
}}
initial="hidden"
animate="visible"
variants={{
visible: {
transition: {
delayChildren: 0.5,
staggerChildren: 0.3,
},
},
}}
>
{items.map(id => (
<motion.div
key={id}
variants={{
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
},
}}
>
<SortableCardWrapper id={id} />
</motion.div>
))}
</motion.div>
</DndWrapper>
)
}
interface DndProps {
items: SourceID[]
setItems: (update: SourceID[]) => void
}
export function DndWrapper({ items, setItems, children }: PropsWithChildren<DndProps>) {
const [activeId, setActiveId] = useState<string | null>(null)
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
@ -29,16 +74,14 @@ export function Dnd() {
const { active, over } = event
if (active.id !== over?.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id as any)
const newIndex = items.indexOf(over!.id as any)
return arrayMove(items, oldIndex, newIndex)
})
const oldIndex = items.indexOf(active.id as any)
const newIndex = items.indexOf(over!.id as any)
setItems(arrayMove(items, oldIndex, newIndex))
}
setActiveId(null)
}, [setItems])
}, [setItems, items])
const handleDragCancel = useCallback(() => {
setActiveId(null)
}, [])
@ -52,9 +95,7 @@ export function Dnd() {
onDragCancel={handleDragCancel}
>
<SortableContext items={items} strategy={rectSortingStrategy}>
{items.map(id => (
<SortableCardWrapper key={id} id={id} />
))}
{children}
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: "0 0 " }}>
{!!activeId && <CardWrapper id={activeId as SourceID} isOverlay />}

View File

@ -2,13 +2,13 @@ import { metadata, sectionIds } from "@shared/data"
import type { SectionID } from "@shared/types"
import { Link } from "@tanstack/react-router"
import clsx from "clsx"
import { useSetAtom } from "jotai"
import { useAtom } from "jotai"
import { useEffect } from "react"
import { Dnd } from "./dnd"
import { currentSectionIDAtom } from "~/atoms"
export function Section({ id }: { id: SectionID }) {
const setCurrentSectionID = useSetAtom(currentSectionIDAtom)
const [currentSectionID, setCurrentSectionID] = useAtom(currentSectionIDAtom)
useEffect(() => {
setCurrentSectionID(id)
}, [id, setCurrentSectionID])
@ -30,15 +30,7 @@ export function Section({ id }: { id: SectionID }) {
</Link>
))}
</section>
<div
className="grid w-full gap-5"
style={{
gridTemplateColumns: "repeat(auto-fill, minmax(350px, 1fr))",
}}
>
<Dnd />
</div>
{ currentSectionID === id && <Dnd />}
</div>
)
}