@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
172 lines (160 loc) • 5.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = require("react");
var _reactNative = require("react-native");
var _styles = require("../styles");
var _fonts = require("../styles/fonts");
var _jsxRuntime = require("react/jsx-runtime");
/**
* Extracts initials from a name string
* - Single word: first letter (e.g., "John" -> "J")
* - Multiple words: first letter of first and last word (e.g., "John Doe" -> "JD")
* - Username starting with @: first two letters after @ (e.g., "@johndoe" -> "JO")
*/
const getInitials = nameStr => {
if (!nameStr?.trim()) return '';
const trimmed = nameStr.trim();
const parts = trimmed.split(/\s+/).filter(part => part.length > 0);
if (parts.length === 0) return '';
if (parts.length === 1) {
const firstPart = parts[0];
// Handle username format (@username)
if (firstPart.length >= 2 && firstPart.startsWith('@')) {
return firstPart.substring(1, 3).toUpperCase();
}
return firstPart.charAt(0).toUpperCase();
}
// Multiple words: first letter of first and last word
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
};
/**
* Generates a consistent color from a string (name)
* Uses a simple hash function to create a deterministic color
*/
const generateColorFromString = str => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
// Generate a vibrant color with good contrast
const hue = Math.abs(hash) % 360;
// Use high saturation and medium lightness for vibrant, readable colors
return `hsl(${hue}, 65%, 55%)`;
};
/**
* Calculates font size based on avatar size for letter avatars
* Uses a larger ratio for better visibility
*/
const calculateFontSize = size => {
// Use larger font size for letter avatars (50-55% of size)
return size <= 40 ? Math.floor(size * 0.5) : Math.floor(size * 0.45);
};
/**
* Avatar component that displays either an image or text avatar
* Falls back to displaying initials (1-2 letters) derived from the name if no image is provided
* Supports flexible sizing via the size prop (default: 40px)
*
* @example
* <Avatar name="John Doe" size={100} theme="light" />
* <Avatar uri="https://example.com/avatar.jpg" size={50} />
*/
const Avatar = ({
uri,
text,
name,
size = 40,
theme = 'light',
backgroundColor,
textColor = '#FFFFFF',
style,
imageStyle,
textStyle,
isLoading = false
}) => {
const colors = (0, _styles.useThemeColors)(theme);
const displayText = (0, _react.useMemo)(() => text || (name ? getInitials(name) : ''), [text, name]);
// Generate a color from the name for letter avatars (only when no backgroundColor is provided and no uri)
const generatedBgColor = (0, _react.useMemo)(() => {
if (backgroundColor) return backgroundColor;
if (uri) return colors.primary; // Use primary for image avatars as fallback
// Generate color from name for letter avatars
const nameForColor = name || text || 'User';
return generateColorFromString(nameForColor);
}, [backgroundColor, uri, name, text, colors.primary]);
// Memoize computed values to avoid recalculation on every render
const bgColor = (0, _react.useMemo)(() => generatedBgColor, [generatedBgColor]);
const fontSize = (0, _react.useMemo)(() => calculateFontSize(size), [size]);
const containerStyle = (0, _react.useMemo)(() => ({
width: size,
height: size,
borderRadius: size / 2
}), [size]);
const textStyleComputed = (0, _react.useMemo)(() => [styles.text, {
fontSize,
fontFamily: _fonts.fontFamilies.phuduBold,
color: textColor,
textAlign: 'center',
lineHeight: size,
// Match line height to size for perfect vertical centering
...(_reactNative.Platform.OS === 'android' && {
includeFontPadding: false
}) // Remove extra padding for better centering (Android only)
}, textStyle], [fontSize, textColor, textStyle, size]);
// Early return for loading state
if (isLoading) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, containerStyle, {
backgroundColor: colors.inputBackground
}, style],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
color: colors.primary,
size: size > 50 ? 'large' : 'small'
})
});
}
// Image avatar
if (uri) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, containerStyle, {
backgroundColor: bgColor
}, style],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
source: {
uri
},
style: [styles.image, containerStyle, imageStyle],
resizeMode: "cover"
})
});
}
// Text avatar with initials
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, containerStyle, {
backgroundColor: bgColor
}, style],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: textStyleComputed,
children: displayText
})
});
};
const styles = _reactNative.StyleSheet.create({
container: {
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center'
},
image: {
width: '100%',
height: '100%'
},
text: {
fontWeight: _reactNative.Platform.OS === 'web' ? 'bold' : undefined
}
});
// Memoize component to prevent unnecessary re-renders when props haven't changed
var _default = exports.default = /*#__PURE__*/(0, _react.memo)(Avatar);
//# sourceMappingURL=Avatar.js.map