mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-31 19:08:05 +08:00
feat: set animation
This commit is contained in:
parent
731b31c8ce
commit
a89edff8fa
@ -32,6 +32,7 @@
|
|||||||
"db0": "npm:@ourongxing/db0@latest",
|
"db0": "npm:@ourongxing/db0@latest",
|
||||||
"fast-xml-parser": "^4.5.0",
|
"fast-xml-parser": "^4.5.0",
|
||||||
"favicons-scraper": "^1.3.2",
|
"favicons-scraper": "^1.3.2",
|
||||||
|
"framer-motion": "^11.11.5",
|
||||||
"h3": "^1.13.0",
|
"h3": "^1.13.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"jotai": "^2.10.0",
|
"jotai": "^2.10.0",
|
||||||
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -62,6 +62,9 @@ importers:
|
|||||||
favicons-scraper:
|
favicons-scraper:
|
||||||
specifier: ^1.3.2
|
specifier: ^1.3.2
|
||||||
version: 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:
|
h3:
|
||||||
specifier: ^1.13.0
|
specifier: ^1.13.0
|
||||||
version: 1.13.0
|
version: 1.13.0
|
||||||
@ -3000,6 +3003,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||||
engines: {node: '>=14'}
|
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:
|
fresh@0.5.2:
|
||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -7743,6 +7760,13 @@ snapshots:
|
|||||||
cross-spawn: 7.0.3
|
cross-spawn: 7.0.3
|
||||||
signal-exit: 4.1.0
|
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: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
front-matter@4.0.2:
|
front-matter@4.0.2:
|
||||||
|
@ -22,7 +22,7 @@ export const localSourcesAtom = atomWithLocalStorage<Record<SectionID, SourceID[
|
|||||||
|
|
||||||
export const focusSourcesAtom = atom((get) => {
|
export const focusSourcesAtom = atom((get) => {
|
||||||
return get(localSourcesAtom).focus
|
return get(localSourcesAtom).focus
|
||||||
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
|
}, (get, set, update: Update<SourceID[]>) => {
|
||||||
const _ = update instanceof Function ? update(get(focusSourcesAtom)) : update
|
const _ = update instanceof Function ? update(get(focusSourcesAtom)) : update
|
||||||
set(localSourcesAtom, {
|
set(localSourcesAtom, {
|
||||||
...get(localSourcesAtom),
|
...get(localSourcesAtom),
|
||||||
@ -50,10 +50,12 @@ export const currentSectionIDAtom = atom<SectionID>("focus")
|
|||||||
export const currentSectionAtom = atom((get) => {
|
export const currentSectionAtom = atom((get) => {
|
||||||
const id = get(currentSectionIDAtom)
|
const id = get(currentSectionIDAtom)
|
||||||
return get(localSourcesAtom)[id]
|
return get(localSourcesAtom)[id]
|
||||||
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
|
}, (get, set, update: Update<SourceID[]>) => {
|
||||||
const _ = update instanceof Function ? update(get(currentSectionAtom)) : update
|
const _ = update instanceof Function ? update(get(currentSectionAtom)) : update
|
||||||
set(localSourcesAtom, {
|
set(localSourcesAtom, {
|
||||||
...get(localSourcesAtom),
|
...get(localSourcesAtom),
|
||||||
[get(currentSectionIDAtom)]: _,
|
[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 { useCallback, useState } from "react"
|
||||||
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core"
|
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core"
|
||||||
import {
|
import {
|
||||||
@ -13,12 +14,56 @@ import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from "@d
|
|||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { CSS } from "@dnd-kit/utilities"
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
|
import { motion } from "framer-motion"
|
||||||
import type { ItemsProps } from "./card"
|
import type { ItemsProps } from "./card"
|
||||||
import { CardWrapper } from "./card"
|
import { CardWrapper } from "./card"
|
||||||
import { currentSectionAtom } from "~/atoms"
|
import { currentSectionAtom } from "~/atoms"
|
||||||
|
|
||||||
export function Dnd() {
|
export function Dnd() {
|
||||||
const [items, setItems] = useAtom(currentSectionAtom)
|
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 [activeId, setActiveId] = useState<string | null>(null)
|
||||||
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
|
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
|
||||||
|
|
||||||
@ -29,16 +74,14 @@ export function Dnd() {
|
|||||||
const { active, over } = event
|
const { active, over } = event
|
||||||
|
|
||||||
if (active.id !== over?.id) {
|
if (active.id !== over?.id) {
|
||||||
setItems((items) => {
|
|
||||||
const oldIndex = items.indexOf(active.id as any)
|
const oldIndex = items.indexOf(active.id as any)
|
||||||
const newIndex = items.indexOf(over!.id as any)
|
const newIndex = items.indexOf(over!.id as any)
|
||||||
|
setItems(arrayMove(items, oldIndex, newIndex))
|
||||||
return arrayMove(items, oldIndex, newIndex)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveId(null)
|
setActiveId(null)
|
||||||
}, [setItems])
|
}, [setItems, items])
|
||||||
|
|
||||||
const handleDragCancel = useCallback(() => {
|
const handleDragCancel = useCallback(() => {
|
||||||
setActiveId(null)
|
setActiveId(null)
|
||||||
}, [])
|
}, [])
|
||||||
@ -52,9 +95,7 @@ export function Dnd() {
|
|||||||
onDragCancel={handleDragCancel}
|
onDragCancel={handleDragCancel}
|
||||||
>
|
>
|
||||||
<SortableContext items={items} strategy={rectSortingStrategy}>
|
<SortableContext items={items} strategy={rectSortingStrategy}>
|
||||||
{items.map(id => (
|
{children}
|
||||||
<SortableCardWrapper key={id} id={id} />
|
|
||||||
))}
|
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
<DragOverlay adjustScale style={{ transformOrigin: "0 0 " }}>
|
<DragOverlay adjustScale style={{ transformOrigin: "0 0 " }}>
|
||||||
{!!activeId && <CardWrapper id={activeId as SourceID} isOverlay />}
|
{!!activeId && <CardWrapper id={activeId as SourceID} isOverlay />}
|
||||||
|
@ -2,13 +2,13 @@ import { metadata, sectionIds } from "@shared/data"
|
|||||||
import type { SectionID } from "@shared/types"
|
import type { SectionID } from "@shared/types"
|
||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useSetAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { Dnd } from "./dnd"
|
import { Dnd } from "./dnd"
|
||||||
import { currentSectionIDAtom } from "~/atoms"
|
import { currentSectionIDAtom } from "~/atoms"
|
||||||
|
|
||||||
export function Section({ id }: { id: SectionID }) {
|
export function Section({ id }: { id: SectionID }) {
|
||||||
const setCurrentSectionID = useSetAtom(currentSectionIDAtom)
|
const [currentSectionID, setCurrentSectionID] = useAtom(currentSectionIDAtom)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentSectionID(id)
|
setCurrentSectionID(id)
|
||||||
}, [id, setCurrentSectionID])
|
}, [id, setCurrentSectionID])
|
||||||
@ -30,15 +30,7 @@ export function Section({ id }: { id: SectionID }) {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
{ currentSectionID === id && <Dnd />}
|
||||||
<div
|
|
||||||
className="grid w-full gap-5"
|
|
||||||
style={{
|
|
||||||
gridTemplateColumns: "repeat(auto-fill, minmax(350px, 1fr))",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Dnd />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user