feat: animated view selector
This commit is contained in:
parent
db5bd2641c
commit
3b1db30456
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue