@dt-workspace/react-native-heatmap
Version:
A modern, highly customizable React Native heatmap component library inspired by GitHub's contribution calendar
186 lines (173 loc) • 4.92 kB
JavaScript
"use strict";
/**
* Animated Cell component for React Native Heatmap
* Provides smooth animations and enhanced touch handling
*/
import React, { useEffect, useRef, useCallback } from 'react';
import { Animated, TouchableOpacity } from 'react-native';
import { Rect } from 'react-native-svg';
import { getEntryAnimationStyle, calculateStaggerDelay } from "../utils/animation.js";
import { triggerHapticFeedback, DoubleTapDetector } from "../utils/gestures.js";
import { jsx as _jsx } from "react/jsx-runtime";
/**
* Animated Cell Component
*/
const AnimatedCell = ({
data,
index,
totalCells,
cellSize,
cellSpacing,
cellShape,
animationConfig,
borderColor,
borderWidth,
cellStyle,
onPress,
onLongPress,
onPressIn,
onPressOut,
onDoublePress,
hapticFeedback = false,
useSvg = true
}) => {
const animatedValue = useRef(new Animated.Value(0)).current;
const scaleValue = useRef(new Animated.Value(1)).current;
const doubleTapDetector = useRef(new DoubleTapDetector()).current;
// Calculate position
const x = data.x * (cellSize + cellSpacing);
const y = data.y * (cellSize + cellSpacing);
// Entry animation
useEffect(() => {
if (!animationConfig?.enabled) {
animatedValue.setValue(1);
return;
}
const delay = calculateStaggerDelay(index, totalCells, animationConfig);
Animated.timing(animatedValue, {
toValue: 1,
duration: animationConfig.duration,
delay,
useNativeDriver: animationConfig.useNativeDriver ?? true
}).start();
}, [animatedValue, index, totalCells, animationConfig]);
// Handle press with haptic feedback
const handlePress = useCallback(() => {
if (hapticFeedback) {
triggerHapticFeedback('light');
}
// Check for double tap
if (onDoublePress) {
const isDoubleTap = doubleTapDetector.detectDoubleTap(() => {
onDoublePress(data, index);
});
if (isDoubleTap) {
return;
}
}
// Single tap after delay
setTimeout(() => {
onPress?.(data, index);
}, onDoublePress ? 150 : 0);
}, [data, index, onPress, onDoublePress, hapticFeedback, doubleTapDetector]);
// Handle long press
const handleLongPress = useCallback(() => {
if (hapticFeedback) {
triggerHapticFeedback('medium');
}
onLongPress?.(data, index);
}, [data, index, onLongPress, hapticFeedback]);
// Handle press in
const handlePressIn = useCallback(() => {
// Scale animation
Animated.spring(scaleValue, {
toValue: 0.95,
useNativeDriver: true
}).start();
onPressIn?.(data, index);
}, [data, index, onPressIn, scaleValue]);
// Handle press out
const handlePressOut = useCallback(() => {
// Scale animation
Animated.spring(scaleValue, {
toValue: 1,
useNativeDriver: true
}).start();
onPressOut?.(data, index);
}, [data, index, onPressOut, scaleValue]);
// Get cell border radius
const getBorderRadius = useCallback(() => {
switch (cellShape) {
case 'circle':
return cellSize / 2;
case 'rounded':
return 2;
default:
return 0;
}
}, [cellShape, cellSize]);
// Animation styles
const animationStyles = animationConfig?.enabled ? getEntryAnimationStyle(animationConfig.entryAnimation, animatedValue) : {};
if (useSvg) {
// SVG rendering (for integration with existing SVG-based heatmap)
const cellProps = {
x,
y,
width: cellSize,
height: cellSize,
fill: data.color,
stroke: borderColor,
strokeWidth: borderWidth,
onPress: handlePress,
onLongPress: handleLongPress,
...cellStyle
};
if (cellShape === 'circle') {
return /*#__PURE__*/_jsx(Rect, {
...cellProps,
rx: cellSize / 2,
ry: cellSize / 2
}, `cell-${index}`);
}
if (cellShape === 'rounded') {
return /*#__PURE__*/_jsx(Rect, {
...cellProps,
rx: 2,
ry: 2
}, `cell-${index}`);
}
return /*#__PURE__*/_jsx(Rect, {
...cellProps
}, `cell-${index}`);
}
// React Native View rendering (for enhanced animations)
return /*#__PURE__*/_jsx(Animated.View, {
style: [{
position: 'absolute',
left: x,
top: y,
width: cellSize,
height: cellSize,
transform: [{
scale: scaleValue
}]
}, animationStyles],
children: /*#__PURE__*/_jsx(TouchableOpacity, {
style: [{
flex: 1,
backgroundColor: data.color,
borderColor: borderColor,
borderWidth: borderWidth,
borderRadius: getBorderRadius()
}, cellStyle],
onPress: handlePress,
onLongPress: handleLongPress,
onPressIn: handlePressIn,
onPressOut: handlePressOut,
delayLongPress: 500,
activeOpacity: 0.8
})
});
};
export default AnimatedCell;
//# sourceMappingURL=AnimatedCell.js.map