UNPKG

mediasfu-reactnative-expo

Version:

mediasfu-reactnative-expo – Expo-managed React Native WebRTC SDK for video conferencing, webinars, live streaming, broadcast, screen sharing, whiteboard, chat, recording, live subtitles, translation, and AI agent rooms on iOS, Android, and web. Prebuilt r

228 lines (225 loc) 8.34 kB
import React, { useCallback, useMemo, useState } from 'react'; import { Image, StyleSheet, Text, View, } from 'react-native'; import { FontAwesome5 } from '@expo/vector-icons'; import { getModernColors, modernShadow, resolveIsDarkMode } from '../core/modernTheme'; const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); export const ModernMiniCard = ({ initials, fontSize = 14, textColor, customStyle, imageSource, roundedImage = true, imageStyle, showVideoIcon = false, showAudioIcon = false, name, customMiniCard, parameters, style, renderContent, renderContainer, isDarkMode, backgroundColor, showGradientBackground = true, showBorder = true, size, }) => { const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const darkMode = resolveIsDarkMode({ isDarkMode, parameters }); const colors = getModernColors(darkMode); const resolvedLabel = useMemo(() => { const rawAvatarLabel = initials?.trim() || name?.trim() || '?'; return rawAvatarLabel.slice(0, 10); }, [initials, name]); const resolvedTextColor = useMemo(() => textColor ?? colors.invertedText, [colors.invertedText, textColor]); const avatarSize = useMemo(() => { if (typeof size === 'number' && size > 0) { return clamp(size, 32, 140); } if (!dimensions.width || !dimensions.height) { return 72; } return clamp(Math.min(dimensions.width, dimensions.height) * 0.72, 32, 140); }, [dimensions.height, dimensions.width, size]); const resolvedFontSize = useMemo(() => { const baseSize = typeof size === 'number' && size > 0 ? size : avatarSize; const widthFit = baseSize / Math.max(resolvedLabel.length * 0.58, 1); const heightFit = baseSize * 0.28; return Math.max(10, Math.min(fontSize, Math.floor(widthFit), Math.floor(heightFit))); }, [avatarSize, fontSize, resolvedLabel.length, size]); const badgeIconSize = useMemo(() => clamp(Math.round(avatarSize * 0.15), 10, 14), [avatarSize]); const haloSize = avatarSize + 18; const circleBackgroundColor = backgroundColor ?? (darkMode ? colors.surfaceMuted : colors.surfaceStrong); const handleLayout = useCallback((event) => { const { width, height } = event.nativeEvent.layout; setDimensions((current) => { if (current.width === width && current.height === height) { return current; } return { width, height }; }); }, []); const defaultContent = customMiniCard ? (customMiniCard({ initials: resolvedLabel, fontSize: resolvedFontSize, textColor: resolvedTextColor, customStyle, imageSource, roundedImage, imageStyle, showVideoIcon, showAudioIcon, name, parameters, style, })) : (<View style={styles.contentShell}> {roundedImage && (<View pointerEvents="none" style={[ styles.avatarHalo, { width: haloSize, height: haloSize, borderRadius: haloSize / 2, backgroundColor: darkMode ? colors.accentSoft : 'rgba(37, 99, 235, 0.16)', }, ]}/>)} <View style={[ styles.avatarFrame, roundedImage ? styles.roundedFrame : styles.squareFrame, roundedImage ? { width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2, } : styles.fillFrame, { backgroundColor: circleBackgroundColor, borderColor: showBorder ? darkMode ? 'rgba(255, 255, 255, 0.16)' : colors.borderStrong : 'transparent', }, ]}> {!imageSource && showGradientBackground && (<View pointerEvents="none" style={[ styles.gradientUnderlay, { backgroundColor: darkMode ? colors.accent : colors.accentAlt, opacity: 0.22, }, ]}/>)} <View pointerEvents="none" style={[ styles.glassSheen, { backgroundColor: darkMode ? 'rgba(255, 255, 255, 0.12)' : 'rgba(255, 255, 255, 0.42)', }, ]}/> <View pointerEvents="none" style={[ styles.bottomTint, { backgroundColor: darkMode ? 'rgba(2, 6, 23, 0.22)' : 'rgba(37, 99, 235, 0.10)', }, ]}/> {imageSource ? (<Image source={{ uri: imageSource }} style={[ styles.avatarImage, roundedImage ? { borderRadius: avatarSize / 2 } : null, imageStyle, ]} resizeMode="cover"/>) : (<Text style={[ styles.avatarLabel, { fontSize: resolvedFontSize, color: resolvedTextColor, textShadowColor: darkMode ? 'rgba(15, 23, 42, 0.45)' : 'rgba(255, 255, 255, 0.55)', }, ]} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.55}> {resolvedLabel} </Text>)} </View> {(showVideoIcon || showAudioIcon) && (<View style={[ styles.statusBadge, { backgroundColor: darkMode ? 'rgba(2, 6, 23, 0.72)' : 'rgba(255, 255, 255, 0.94)', borderColor: colors.border, }, ]}> {showVideoIcon ? (<FontAwesome5 name="video" size={badgeIconSize} color={darkMode ? colors.invertedText : colors.text}/>) : null} {showAudioIcon ? (<FontAwesome5 name="microphone" size={badgeIconSize} color={darkMode ? colors.invertedText : colors.text} style={showVideoIcon ? styles.statusIconGap : null}/>) : null} </View>)} </View>); const content = renderContent ? renderContent({ defaultContent, dimensions }) : defaultContent; const defaultContainer = (<View style={[styles.container, customStyle, style]} onLayout={handleLayout} accessibilityRole="image" accessibilityLabel={name || resolvedLabel}> {content} </View>); return renderContainer ? renderContainer({ defaultContainer, dimensions }) : defaultContainer; }; const styles = StyleSheet.create({ container: { width: '100%', height: '100%', minWidth: 0, minHeight: 0, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', position: 'relative', }, contentShell: { width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', position: 'relative', }, avatarHalo: { position: 'absolute', ...modernShadow, }, avatarFrame: { alignItems: 'center', justifyContent: 'center', overflow: 'hidden', borderWidth: 1, position: 'relative', ...modernShadow, }, roundedFrame: { maxWidth: '82%', maxHeight: '82%', }, squareFrame: { width: '100%', height: '100%', borderRadius: 18, }, fillFrame: { width: '100%', height: '100%', }, gradientUnderlay: { position: 'absolute', inset: 0, }, glassSheen: { position: 'absolute', top: 0, left: 0, right: 0, height: '46%', }, bottomTint: { position: 'absolute', left: 0, right: 0, bottom: 0, height: '52%', }, avatarImage: { width: '100%', height: '100%', }, avatarLabel: { fontWeight: '800', maxWidth: '84%', textAlign: 'center', }, statusBadge: { position: 'absolute', right: 8, bottom: 8, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 8, paddingVertical: 5, borderRadius: 999, borderWidth: 1, }, statusIconGap: { marginLeft: 6, }, }); export default ModernMiniCard; //# sourceMappingURL=ModernMiniCard.js.map