'use client'; import * as React from 'react'; import { mergeCallbacks, useId, slot } from '@fluentui/react-utilities'; import { getInitials } from '../../utils/index'; import { PersonRegular } from '@fluentui/react-icons'; import { PresenceBadge } from '@fluentui/react-badge'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { useAvatarContext } from '../../contexts/AvatarContext'; export const DEFAULT_STRINGS = { active: 'active', inactive: 'inactive' }; export const useAvatar_unstable = (props, ref)=>{ const { dir } = useFluent(); const { shape: contextShape, size: contextSize } = useAvatarContext(); const { size = contextSize !== null && contextSize !== void 0 ? contextSize : 32, shape = contextShape !== null && contextShape !== void 0 ? contextShape : 'circular', active = 'unset', activeAppearance = 'ring', idForColor, color: propColor = 'neutral', ...rest } = props; const state = useAvatarBase_unstable(rest, ref); var _ref; // Resolve 'colorful' to a specific color name const color = propColor === 'colorful' ? avatarColors[getHashCode((_ref = idForColor !== null && idForColor !== void 0 ? idForColor : props.name) !== null && _ref !== void 0 ? _ref : '') % avatarColors.length] : propColor; if (state.initials) { var _state_initials; state.initials = slot.optional(props.initials, { renderByDefault: true, defaultProps: { children: getInitials(props.name, dir === 'rtl', { firstInitialOnly: size <= 16 }), id: (_state_initials = state.initials) === null || _state_initials === void 0 ? void 0 : _state_initials.id }, elementType: 'span' }); } if (state.icon) { var _state_icon; var _children; (_children = (_state_icon = state.icon).children) !== null && _children !== void 0 ? _children : _state_icon.children = /*#__PURE__*/ React.createElement(PersonRegular, null); } const badge = slot.optional(props.badge, { defaultProps: { size: getBadgeSize(size), id: state.root.id + '__badge' }, elementType: PresenceBadge }); let activeAriaLabelElement = state.activeAriaLabelElement; // Enhance aria-label and/or aria-labelledby to include badge and active state // Only process if aria attributes were not explicitly provided by the user const userProvidedAriaLabel = props['aria-label'] !== undefined; const userProvidedAriaLabelledby = props['aria-labelledby'] !== undefined; if (!userProvidedAriaLabel && !userProvidedAriaLabelledby) { if (props.name) { if (badge) { state.root['aria-labelledby'] = state.root.id + ' ' + badge.id; } } else if (state.initials) { // root's aria-label should be the name, but fall back to being labelledby the initials if name is missing state.root['aria-labelledby'] = state.initials.id + (badge ? ' ' + badge.id : ''); delete state.root['aria-label']; } // Add the active state to the aria label if (active === 'active' || active === 'inactive') { const activeText = DEFAULT_STRINGS[active]; if (state.root['aria-labelledby']) { // If using aria-labelledby, render a hidden span and append it to the labelledby const activeId = state.root.id + '__active'; state.root['aria-labelledby'] += ' ' + activeId; activeAriaLabelElement = /*#__PURE__*/ React.createElement("span", { hidden: true, id: activeId }, activeText); } else if (state.root['aria-label']) { // Otherwise, just append it to the aria-label state.root['aria-label'] += ' ' + activeText; } } } return { ...state, size, shape, active, activeAppearance, activeAriaLabelElement, color, badge, // eslint-disable-next-line @typescript-eslint/no-deprecated components: { ...state.components, badge: PresenceBadge } }; }; /** * Base hook for Avatar component, manages state and structure common to all variants of Avatar */ export const useAvatarBase_unstable = (props, ref)=>{ const { dir } = useFluent(); const { name, ...rest } = props; const baseId = useId('avatar-'); const root = slot.always({ role: 'img', id: baseId, ref, ...rest }, { elementType: 'span' }); const [imageHidden, setImageHidden] = React.useState(undefined); let image = slot.optional(props.image, { defaultProps: { alt: '', role: 'presentation', 'aria-hidden': true, hidden: imageHidden }, elementType: 'img' }); // Image shouldn't be rendered if its src is not set if (!(image === null || image === void 0 ? void 0 : image.src)) { image = undefined; } // Hide the image if it fails to load and restore it on a successful load if (image) { image.onError = mergeCallbacks(image.onError, ()=>setImageHidden(true)); image.onLoad = mergeCallbacks(image.onLoad, ()=>setImageHidden(undefined)); } // Resolve the initials slot, defaulted to getInitials let initials = slot.optional(props.initials, { renderByDefault: true, defaultProps: { children: getInitials(name, dir === 'rtl'), id: baseId + '__initials' }, elementType: 'span' }); // Don't render the initials slot if it's empty if (!(initials === null || initials === void 0 ? void 0 : initials.children)) { initials = undefined; } // Render the icon slot *only if* there aren't any initials or image to display let icon = undefined; if (!initials && (!image || imageHidden)) { icon = slot.optional(props.icon, { renderByDefault: true, defaultProps: { 'aria-hidden': true }, elementType: 'span' }); } let activeAriaLabelElement; // Resolve aria-label and/or aria-labelledby if not provided by the user if (!root['aria-label'] && !root['aria-labelledby']) { if (name) { root['aria-label'] = name; } else if (initials) { // root's aria-label should be the name, but fall back to being labelledby the initials if name is missing root['aria-labelledby'] = initials.id; } } return { activeAriaLabelElement, components: { root: 'span', initials: 'span', icon: 'span', image: 'img' }, root, initials, icon, image }; }; const getBadgeSize = (size)=>{ if (size >= 96) { return 'extra-large'; } else if (size >= 64) { return 'large'; } else if (size >= 56) { return 'medium'; } else if (size >= 40) { return 'small'; } else if (size >= 28) { return 'extra-small'; } else { return 'tiny'; } }; const avatarColors = [ 'dark-red', 'cranberry', 'red', 'pumpkin', 'peach', 'marigold', 'gold', 'brass', 'brown', 'forest', 'seafoam', 'dark-green', 'light-teal', 'teal', 'steel', 'blue', 'royal-blue', 'cornflower', 'navy', 'lavender', 'purple', 'grape', 'lilac', 'pink', 'magenta', 'plum', 'beige', 'mink', 'platinum', 'anchor' ]; const getHashCode = (str)=>{ let hashCode = 0; for(let len = str.length - 1; len >= 0; len--){ const ch = str.charCodeAt(len); const shift = len % 8; hashCode ^= (ch << shift) + (ch >> 8 - shift); // eslint-disable-line no-bitwise } return hashCode; };