Files
jrshikoku/components/Menu/Carousel/SortGridCard.tsx

116 lines
3.1 KiB
TypeScript

import React, { useEffect } from "react";
import { useItemContext } from "react-native-sortables";
import Animated, {
Easing,
interpolate,
useAnimatedStyle,
useSharedValue,
withDelay,
withTiming,
} from "react-native-reanimated";
import { GridMiniSign } from "./GridMiniSign";
type Props = {
item: any;
cellW: number;
cellH: number;
startX: number;
startY: number;
exitX: number;
exitY: number;
startScale: number;
isCurrentCard: boolean;
isExiting: boolean;
exitDelay: number;
onPress?: () => void;
};
const EASE_OUT = Easing.out(Easing.cubic);
const DURATION = 320;
/** グリッドセルへのスライド+スケールアニメーション付きカード */
export const SortGridCard = React.memo(function SortGridCard({
item,
cellW,
cellH,
startX,
startY,
exitX,
exitY,
startScale,
isCurrentCard,
isExiting,
exitDelay,
onPress,
}: Props) {
const { activationAnimationProgress } = useItemContext();
// 現在選択中のカードはカルーセル位置から、それ以外は近距離からスライド
const initX = isCurrentCard ? startX : startX * 0.35;
const initY = isCurrentCard ? startY : startY * 0.35;
const initScale = isCurrentCard ? Math.min(startScale, 1.5) : Math.min(startScale, 1.15);
const tx = useSharedValue(initX);
const ty = useSharedValue(initY);
const sc = useSharedValue(initScale);
const opacity = useSharedValue(0);
// 入場
useEffect(() => {
const cfg = { duration: DURATION, easing: EASE_OUT };
tx.value = withTiming(0, cfg);
ty.value = withTiming(0, cfg);
sc.value = withTiming(1, cfg);
opacity.value = withTiming(1, { duration: 180, easing: EASE_OUT });
}, []);
// 退場
useEffect(() => {
if (!isExiting) return;
const cfg = { duration: DURATION, easing: EASE_OUT };
const toX = isCurrentCard ? exitX : exitX * 0.35;
const toY = isCurrentCard ? exitY : exitY * 0.35;
tx.value = withDelay(exitDelay, withTiming(toX, cfg));
ty.value = withDelay(exitDelay, withTiming(toY, cfg));
sc.value = withDelay(exitDelay, withTiming(initScale, cfg));
opacity.value = withDelay(exitDelay, withTiming(0, { duration: 150, easing: EASE_OUT }));
}, [isExiting]);
const animStyle = useAnimatedStyle(() => {
const p = activationAnimationProgress.value;
return {
opacity: opacity.value * interpolate(p, [0, 1], [1, 0.85]),
shadowOpacity: interpolate(p, [0, 1], [0, 0.4]),
shadowRadius: interpolate(p, [0, 1], [0, 10]),
elevation: interpolate(p, [0, 1], [1, 12]),
transform: [
{ translateX: tx.value },
{ translateY: ty.value },
{ scale: sc.value * interpolate(p, [0, 1], [1, 1.06]) },
] as any,
};
});
return (
<Animated.View
style={[
{
width: cellW,
height: cellH,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
},
animStyle,
]}
>
<GridMiniSign
item={item}
width={cellW}
height={cellH}
onPress={onPress}
/>
</Animated.View>
);
});