feat: animated view selector

This commit is contained in:
DIYgod 2025-02-12 14:51:24 +08:00
parent db5bd2641c
commit 3b1db30456
No known key found for this signature in database
1 changed files with 73 additions and 18 deletions

View File

@ -1,6 +1,9 @@
import { Image } from "expo-image"
import { ScrollView, TouchableOpacity, View } from "react-native"
import { useEffect } from "react"
import type { StyleProp, ViewStyle } from "react-native"
import { ScrollView, Text, TouchableOpacity, View } from "react-native"
import { Grayscale } from "react-native-color-matrix-image-filters"
import Animated, { useSharedValue, withSpring } from "react-native-reanimated"
import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
import type { ViewDefinition } from "@/src/constants/views"
@ -32,49 +35,101 @@ export function ViewSelector() {
)
}
function ViewItem({ view }: { view: ViewDefinition }) {
const bgColor = useColor("gray2")
const selectedFeed = useSelectedFeed()
if (!selectedFeed) return null
const isActive = selectedFeed.type === "view" && selectedFeed.viewId === view.view
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity)
function ItemWrapper({
children,
isActive,
onPress,
style,
}: {
children: React.ReactNode
isActive: boolean
onPress: () => void
style?: Exclude<StyleProp<ViewStyle>, number>
}) {
const width = useSharedValue(isActive ? 130 : 48)
const bgColor = useColor("gray5")
useEffect(() => {
width.value = withSpring(isActive ? 130 : 48)
}, [isActive, width])
return (
<TouchableOpacity
className="relative flex size-12 items-center justify-center overflow-hidden rounded-full"
onPress={() => selectTimeline({ type: "view", viewId: view.view })}
<AnimatedTouchableOpacity
className="relative flex h-12 flex-row items-center justify-center gap-2 overflow-hidden rounded-[1.2rem]"
onPress={onPress}
style={{
backgroundColor: isActive ? view.activeColor : bgColor,
backgroundColor: bgColor,
width,
...style,
}}
>
<view.icon color={"#fff"} height={21} width={21} />
</TouchableOpacity>
{children}
</AnimatedTouchableOpacity>
)
}
function ViewItem({ view }: { view: ViewDefinition }) {
const textColor = useColor("gray")
const selectedFeed = useSelectedFeed()
const isActive = selectedFeed?.type === "view" && selectedFeed.viewId === view.view
return (
<ItemWrapper
isActive={isActive}
onPress={() => selectTimeline({ type: "view", viewId: view.view })}
style={isActive ? { backgroundColor: view.activeColor } : undefined}
>
<view.icon color={isActive ? "#fff" : textColor} height={21} width={21} />
{isActive && (
<Text className="text-sm font-semibold text-white" numberOfLines={1}>
{view.name}
</Text>
)}
</ItemWrapper>
)
}
function ListItem({ listId }: { listId: string }) {
const list = useList(listId)
const selectedFeed = useSelectedFeed()
const bgColor = useColor("orange")
if (!selectedFeed) return null
const isActive = selectedFeed.type === "list" && selectedFeed.listId === listId
if (!list) return null
return (
<TouchableOpacity
className="relative flex size-12 items-center justify-center overflow-hidden rounded-full"
<ItemWrapper
isActive={isActive}
onPress={() => selectTimeline({ type: "list", listId })}
style={isActive ? { backgroundColor: bgColor } : undefined}
>
{list.image ? (
isActive ? (
<Image source={list.image} contentFit="cover" className="size-12" />
<Image source={list.image} contentFit="cover" className="size-7 rounded-lg" />
) : (
<Grayscale>
<Image source={list.image} contentFit="cover" className="size-12" />
<Image source={list.image} contentFit="cover" className="size-7 rounded-lg" />
</Grayscale>
)
) : (
<FallbackIcon title={list.title} size="100%" gray={!isActive} />
<FallbackIcon
title={list.title}
size={28}
gray={!isActive}
style={{
borderRadius: 8,
}}
/>
)}
</TouchableOpacity>
{isActive && (
<Text className="max-w-24 text-sm font-semibold text-white" numberOfLines={1}>
{list.title}
</Text>
)}
</ItemWrapper>
)
}