Skip to content

Commit

Permalink
feat: uselisttransition support shuffie
Browse files Browse the repository at this point in the history
  • Loading branch information
Daydreamer-riri committed Apr 5, 2024
1 parent 4122a63 commit e45907f
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 17 deletions.
7 changes: 5 additions & 2 deletions build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/index',
'src/index.ts',
'src/hooks/useTransition.ts',
'src/hooks/useSwitchTransition/index.tsx',
'src/hooks/useListTransition.tsx',
],
declaration: true,
clean: true,
rollup: {
emitCJS: true,
esbuild: {
minify: true,
// minify: true,
},
},
})
6 changes: 4 additions & 2 deletions docs/components/BasicUseListTransition/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { useRef, useState } from 'react'
import { getSimpleStatus, useListTransition } from 'transition-hooks'
import { Button } from '../Button'
import { shuffle } from '../utils'

const numbers = Array.from({ length: 5 }, (_, i) => i)
export function BasicUseListTransition() {
const [list, setList] = useState(numbers)
const idRef = useRef(numbers.length)
const transition = useListTransition(list, { entered: false, timeout: 300 })
const { transitionList } = useListTransition(list, { entered: false, timeout: 300, keyExtractor: i => i })

return (
<div>
<div style={{ display: 'flex', gap: 4 }}>
<Button onClick={insert}>insert</Button>
<Button onClick={remove}>remove</Button>
<Button onClick={() => setList(shuffle)}>shuffle</Button>
</div>
<ul>
{transition((item, { status }) => {
{transitionList((item, { status }) => {
const simpleStatus = getSimpleStatus(status)
return (
<li
Expand Down
6 changes: 6 additions & 0 deletions docs/components/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function shuffle<T>(array: readonly T[]): T[] {
return array
.map(a => ({ rand: Math.random(), value: a }))
.sort((a, b) => a.rand - b.rand)
.map(a => a.value)
}
45 changes: 32 additions & 13 deletions src/hooks/useListTransition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,35 @@ import { setAnimationFrameTimeout } from '../helpers/setAnimationFrameTimeout'
import { STATUS, type StatusState, getState } from '../status'
import type { Timeout } from '../helpers/getTimeout'
import { getTimeout } from '../helpers/getTimeout'
import useMemoizedFn from '../helpers/useMemorizeFn'

type RenderCallback<Item> = (item: Item, stage: StatusState) => React.ReactNode

type ItemWithState<Item> = {
item: Item
key: number
key: number | string
} & StatusState

interface ItemWithKey<Item> {
item: Item
index: number
}

export function useListTransition<Item>(list: Array<Item>, options?: { timeout: Timeout, entered?: boolean, getKey?: (item: Item) => string | number }) {
const { timeout = 300, entered = true } = options || {}
export function useListTransition<Item>(list: Array<Item>, options?: { timeout: Timeout, entered?: boolean, keyExtractor?: (item: Item) => string | number }) {
const { timeout = 300, entered = true, keyExtractor: _keyExtractor } = options || {}
const keyRef = useRef(0)
const hasCustomKeyExtractor = !!_keyExtractor
const keyExtractor = useMemoizedFn(_keyExtractor || (() => keyRef.current))
// change list to our list form with extra information.
const initialList: Array<ItemWithState<Item>> = list.map((item, _key) => ({
item,
key: keyRef.current++,
...getState(STATUS.entered),
}))
const { enterTimeout, exitTimeout } = getTimeout(timeout)

const [listState, setListState] = useState(initialList)
const [listState, setListState] = useState<Array<ItemWithState<Item>>>(
() => list.map((item, _key) => ({
item,
key: hasCustomKeyExtractor ? keyExtractor(item) : keyRef.current++,
...getState(STATUS.entered),
})),
)

useEffect(
() => {
Expand All @@ -46,7 +50,7 @@ export function useListTransition<Item>(list: Array<Item>, options?: { timeout:
(prev, { item, index }, _i) =>
insertArray(prev, index, {
item,
key: keyRef.current++,
key: hasCustomKeyExtractor ? keyExtractor(item) : keyRef.current++,
...getState(STATUS.from),
}),
prevListState,
Expand Down Expand Up @@ -104,17 +108,32 @@ export function useListTransition<Item>(list: Array<Item>, options?: { timeout:
)
}, exitTimeout)
}

if (
hasCustomKeyExtractor
&& list.length === listState.length
&& list.some((item, index) => keyExtractor(item) !== listState[index].key)
) {
setListState(
list.map(item => ({
item,
key: keyExtractor(item),
...getState(STATUS.entered),
})),
)
}
},
[list, listState, enterTimeout, exitTimeout, entered],
[list, listState, enterTimeout, exitTimeout, entered, keyExtractor, hasCustomKeyExtractor],
)

function transition(renderCallback: RenderCallback<Item>) {
function transitionList(renderCallback: RenderCallback<Item>) {
return listState.map(item => (
<Fragment key={item.key}>
{renderCallback(item.item, item)}
</Fragment>
))
}
const isResolved = listState.every(item => item.isResolved)

return transition
return { transitionList, isResolved }
}

0 comments on commit e45907f

Please sign in to comment.