新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import { useEffect, useRef } from "react";
|
||
import { Animated, Easing, type StyleProp, type ViewStyle } from "react-native";
|
||
import { useReduceMotion } from "../lib/reduce-motion";
|
||
|
||
export interface HeartbeatDotProps {
|
||
size?: number;
|
||
color?: string;
|
||
style?: StyleProp<ViewStyle>;
|
||
}
|
||
|
||
// Q-4:未讀/推播提示紅點=小心跳,對應 apps/web 的 `.heartbeat-dot`(1.2s 循環的 opacity+scale 脈動)。
|
||
export function HeartbeatDot({ size = 10, color = "#FF7E9D", style }: HeartbeatDotProps) {
|
||
const reduceMotion = useReduceMotion();
|
||
const t = useRef(new Animated.Value(0)).current;
|
||
|
||
useEffect(() => {
|
||
if (reduceMotion) {
|
||
t.setValue(0);
|
||
return;
|
||
}
|
||
const loop = Animated.loop(
|
||
Animated.sequence([
|
||
Animated.timing(t, { toValue: 1, duration: 600, easing: Easing.inOut(Easing.ease), useNativeDriver: true }),
|
||
Animated.timing(t, { toValue: 0, duration: 600, easing: Easing.inOut(Easing.ease), useNativeDriver: true }),
|
||
]),
|
||
);
|
||
loop.start();
|
||
return () => loop.stop();
|
||
}, [reduceMotion, t]);
|
||
|
||
const scale = t.interpolate({ inputRange: [0, 1], outputRange: [1, 1.3] });
|
||
const opacity = t.interpolate({ inputRange: [0, 1], outputRange: [1, 0.7] });
|
||
|
||
return (
|
||
<Animated.View
|
||
style={[
|
||
{ width: size, height: size, borderRadius: size / 2, backgroundColor: color, opacity, transform: [{ scale }] },
|
||
style,
|
||
]}
|
||
/>
|
||
);
|
||
}
|