mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: set animation
This commit is contained in:
parent
731b31c8ce
commit
a89edff8fa
@ -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
24
pnpm-lock.yaml
generated
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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 />}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user