@dt-workspace/react-native-heatmap
Version:
A modern, highly customizable React Native heatmap component library inspired by GitHub's contribution calendar
414 lines (393 loc) • 10.7 kB
JavaScript
"use strict";
/**
* CardLayout component for flexible card-based layouts
*/
import React, { useMemo } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Animated, Platform } from 'react-native';
import { DEFAULT_CARD_LAYOUT, DEFAULT_CARD_SECTION } from "../types.js";
/**
* Badge component for displaying status, tags, or indicators
*/
import { jsx as _jsx } from "react/jsx-runtime";
const Badge = ({
text,
color = '#ffffff',
backgroundColor = '#007AFF',
size = 'medium',
style,
textStyle,
onPress
}) => {
const badgeStyle = useMemo(() => {
const sizeStyles = {
small: {
paddingHorizontal: 6,
paddingVertical: 2,
fontSize: 10
},
medium: {
paddingHorizontal: 8,
paddingVertical: 4,
fontSize: 12
},
large: {
paddingHorizontal: 10,
paddingVertical: 6,
fontSize: 14
}
};
return [styles.badge, {
backgroundColor,
...sizeStyles[size]
}, style];
}, [backgroundColor, size, style]);
const badgeTextStyle = useMemo(() => [styles.badgeText, {
color,
fontSize: size === 'small' ? 10 : size === 'medium' ? 12 : 14
}, textStyle], [color, size, textStyle]);
const BadgeComponent = /*#__PURE__*/_jsx(View, {
style: badgeStyle,
children: /*#__PURE__*/_jsx(Text, {
style: badgeTextStyle,
children: text
})
});
if (onPress) {
return /*#__PURE__*/_jsx(TouchableOpacity, {
onPress: onPress,
style: styles.badgeContainer,
children: BadgeComponent
});
}
return BadgeComponent;
};
/**
* Default badges renderer
*/
const DefaultBadges = ({
badges
}) => {
return /*#__PURE__*/_jsx(View, {
style: styles.badgesContainer,
children: badges.map((badge, index) => /*#__PURE__*/_jsx(Badge, {
...badge
}, index))
});
};
/**
* Card section wrapper component
*/
const CardSection = ({
config,
children
}) => {
const {
visible = true,
style,
padding,
margin
} = config;
const sectionStyle = useMemo(() => [{
padding: padding || DEFAULT_CARD_SECTION.padding,
margin: margin || DEFAULT_CARD_SECTION.margin
}, style], [padding, margin, style]);
if (!visible) return null;
return /*#__PURE__*/_jsx(View, {
style: sectionStyle,
children: children
});
};
/**
* Main CardLayout component
*/
const CardLayout = ({
// Title props
title,
titleStyle,
titleContainerStyle,
titleSection = DEFAULT_CARD_SECTION,
// Description props
description,
descriptionStyle,
descriptionContainerStyle,
descriptionSection = DEFAULT_CARD_SECTION,
// Badges props
badges,
badgesArray,
badgesContainerStyle,
badgesSection = DEFAULT_CARD_SECTION,
// Custom component props
customComponent,
customComponentContainerStyle,
customComponentSection = DEFAULT_CARD_SECTION,
// Hitman props
hitman,
hitmanContainerStyle,
hitmanSection = DEFAULT_CARD_SECTION,
// Card layout configuration
cardLayout = DEFAULT_CARD_LAYOUT,
// Accessibility
accessibilityLabel,
accessibilityHint,
accessibilityRole,
// Interaction handlers
onPress,
onLongPress,
// Animation
animated = false,
animationDuration = 300,
animationType = 'fade',
// Theme
theme = 'light',
themeColors,
// Children (alternative approach)
children,
// Container style
style,
// Test ID
testID
}) => {
// Merge card layout with defaults
const mergedCardLayout = useMemo(() => ({
...DEFAULT_CARD_LAYOUT,
...cardLayout
}), [cardLayout]);
// Theme colors
const resolvedThemeColors = useMemo(() => {
const defaultColors = theme === 'dark' ? {
background: '#1c1c1e',
text: '#ffffff',
border: '#38383a',
shadow: '#000000'
} : {
background: '#ffffff',
text: '#000000',
border: '#e1e4e8',
shadow: '#000000'
};
return {
...defaultColors,
...themeColors
};
}, [theme, themeColors]);
// Card container style
const cardContainerStyle = useMemo(() => [styles.cardContainer, {
backgroundColor: resolvedThemeColors.background,
borderColor: resolvedThemeColors.border,
borderWidth: mergedCardLayout.borderWidth,
borderRadius: mergedCardLayout.borderRadius,
padding: mergedCardLayout.padding,
margin: mergedCardLayout.margin,
width: mergedCardLayout.width,
height: mergedCardLayout.height
}, mergedCardLayout.shadow && {
...Platform.select({
ios: {
shadowColor: resolvedThemeColors.shadow,
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.1,
shadowRadius: 4
},
android: {
elevation: mergedCardLayout.elevation || 2
}
})
}, mergedCardLayout.style, style], [resolvedThemeColors, mergedCardLayout, style]);
// Collect all sections and sort by order
const sections = useMemo(() => {
const sectionArray = [];
// Title section
if (title) {
sectionArray.push({
key: 'title',
order: titleSection.order || 0,
component: /*#__PURE__*/_jsx(CardSection, {
config: titleSection,
children: /*#__PURE__*/_jsx(View, {
style: titleContainerStyle,
children: typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
style: [styles.title, {
color: resolvedThemeColors.text
}, titleStyle],
children: title
}) : title
})
}, "title-section")
});
}
// Description section
if (description) {
sectionArray.push({
key: 'description',
order: descriptionSection.order || 1,
component: /*#__PURE__*/_jsx(CardSection, {
config: descriptionSection,
children: /*#__PURE__*/_jsx(View, {
style: descriptionContainerStyle,
children: typeof description === 'string' ? /*#__PURE__*/_jsx(Text, {
style: [styles.description, {
color: resolvedThemeColors.text
}, descriptionStyle],
children: description
}) : description
})
}, "description-section")
});
}
// Badges section
if (badges || badgesArray) {
sectionArray.push({
key: 'badges',
order: badgesSection.order || 2,
component: /*#__PURE__*/_jsx(CardSection, {
config: badgesSection,
children: /*#__PURE__*/_jsx(View, {
style: badgesContainerStyle,
children: badges || badgesArray && /*#__PURE__*/_jsx(DefaultBadges, {
badges: badgesArray
})
})
}, "badges-section")
});
}
// Custom component section
if (customComponent) {
sectionArray.push({
key: 'custom',
order: customComponentSection.order || 3,
component: /*#__PURE__*/_jsx(CardSection, {
config: customComponentSection,
children: /*#__PURE__*/_jsx(View, {
style: customComponentContainerStyle,
children: customComponent
})
}, "custom-section")
});
}
// Hitman section (always last)
if (hitman) {
sectionArray.push({
key: 'hitman',
order: hitmanSection.order || 999,
component: /*#__PURE__*/_jsx(CardSection, {
config: hitmanSection,
children: /*#__PURE__*/_jsx(View, {
style: hitmanContainerStyle,
children: hitman
})
}, "hitman-section")
});
}
// Sort by order
return sectionArray.sort((a, b) => a.order - b.order);
}, [title, description, badges, badgesArray, customComponent, hitman, titleSection, descriptionSection, badgesSection, customComponentSection, hitmanSection, titleContainerStyle, descriptionContainerStyle, badgesContainerStyle, customComponentContainerStyle, hitmanContainerStyle, titleStyle, descriptionStyle, resolvedThemeColors]);
// Animation value
const animationValue = useMemo(() => new Animated.Value(0), []);
// Animate on mount
React.useEffect(() => {
if (animated) {
Animated.timing(animationValue, {
toValue: 1,
duration: animationDuration,
useNativeDriver: animationType === 'fade' || animationType === 'scale'
}).start();
}
}, [animated, animationValue, animationDuration, animationType]);
// Animation style
const animationStyle = useMemo(() => {
if (!animated) return {};
switch (animationType) {
case 'fade':
return {
opacity: animationValue
};
case 'scale':
return {
transform: [{
scale: animationValue
}]
};
case 'slide':
return {
transform: [{
translateY: animationValue.interpolate({
inputRange: [0, 1],
outputRange: [50, 0]
})
}]
};
default:
return {};
}
}, [animated, animationType, animationValue]);
// Main content
const content = children || sections.map(section => section.component);
// Wrap in TouchableOpacity if onPress or onLongPress is provided
if (onPress || onLongPress) {
return /*#__PURE__*/_jsx(TouchableOpacity, {
onPress: onPress,
onLongPress: onLongPress,
accessibilityLabel: accessibilityLabel,
accessibilityHint: accessibilityHint,
accessibilityRole: accessibilityRole,
testID: testID,
style: animated ? animationStyle : undefined,
children: /*#__PURE__*/_jsx(Animated.View, {
style: cardContainerStyle,
children: content
})
});
}
// Static card
return /*#__PURE__*/_jsx(Animated.View, {
style: [cardContainerStyle, animationStyle],
accessible: true,
accessibilityLabel: accessibilityLabel,
accessibilityHint: accessibilityHint,
accessibilityRole: accessibilityRole,
testID: testID,
children: content
});
};
const styles = StyleSheet.create({
cardContainer: {
backgroundColor: '#ffffff',
borderRadius: 8,
borderWidth: 1,
borderColor: '#e1e4e8',
padding: 16,
margin: 8
},
title: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4
},
description: {
fontSize: 14,
lineHeight: 20,
marginBottom: 8
},
badge: {
borderRadius: 12,
marginRight: 8,
marginBottom: 4
},
badgeContainer: {
// No additional styles needed
},
badgeText: {
fontSize: 12,
fontWeight: '500'
},
badgesContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center'
}
});
export default CardLayout;
//# sourceMappingURL=CardLayout.js.map