3

I was wondering if there is a way to add a ToolTip component to the bubble showing the additional avatars in the Avatar Group component. Here is an example of how the component renders: (Image from Material UI documentation (https://material-ui.com/components/avatars/#grouped)

I want to add a ToolTip to the last bubble displaying the name of the remaining users.

enter image description here

I am currently adding a ToolTip to each avatar. This is my code

<AvatarGroup max={4}>
     {users.people.map((person, index) => (
          <Tooltip title={person.name} key={index}>
               <Avatar
                    className={classes.smallAvatar}
                    onClick={e => {
                         handleUserClick(e),
                         handleAvatarClick(person);
                    }}
                    src={person.avatar}
               >
                    {person.initials}
               </Avatar>
          </Tooltip>
     ))}
</AvatarGroup>
Karla Lopez
  • 73
  • 1
  • 7

1 Answers1

2

AFAIK there is NO such a feature, but you can create your own AvatarGroup component (and support it in the future).

enter image description here

Sample usage:

import React from "react";
import Avatar from "@material-ui/core/Avatar";
import AvatarGroup from "./AvatarGroup"; // Custom AvatarGroup

export default function GroupAvatars() {
  return (
    <AvatarGroup
      max={4}
      extraAvatarsTooltipTitle="Extra Avatar Tooltip"
    >
      <Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
      <Avatar alt="Travis Howard" src="/static/images/avatar/2.jpg" />
      <Avatar alt="Cindy Baker" src="/static/images/avatar/3.jpg" />
      <Avatar alt="Agnes Walker" src="/static/images/avatar/4.jpg" />
      <Avatar alt="Trevor Henderson" src="/static/images/avatar/5.jpg" />
    </AvatarGroup>
  );
}

and the code of the custom AvatarGroup:

import * as React from 'react';
import PropTypes from 'prop-types';
import { isFragment } from 'react-is';
import clsx from 'clsx';
import { withStyles } from '@material-ui/core/styles';
import Avatar from '@material-ui/core/Avatar';
import Tooltip from '@material-ui/core/Tooltip';
import { chainPropTypes } from '@material-ui/utils';

const SPACINGS = {
  small: -16,
  medium: null,
};

export const styles = (theme) => ({
  /* Styles applied to the root element. */
  root: {
    display: 'flex',
  },
  /* Styles applied to the avatar elements. */
  avatar: {
    border: `2px solid ${theme.palette.background.default}`,
    marginLeft: -8,
    '&:first-child': {
      marginLeft: 0,
    },
  },
});

const AvatarGroup = React.forwardRef(function AvatarGroup(props, ref) {
  const {
    children: childrenProp,
    classes,
    className,
    max = 5,
    spacing = 'medium',
    ...other
  } = props;
  const clampedMax = max < 2 ? 2 : max;

  const children = React.Children.toArray(childrenProp).filter((child) => {
    if (process.env.NODE_ENV !== 'production') {
      if (isFragment(child)) {
        console.error(
          [
            "Material-UI: The AvatarGroup component doesn't accept a Fragment as a child.",
            'Consider providing an array instead.',
          ].join('\n'),
        );
      }
    }

    return React.isValidElement(child);
  });

  const extraAvatars = children.length > clampedMax ? children.length - clampedMax + 1 : 0;

  const marginLeft = spacing && SPACINGS[spacing] !== undefined ? SPACINGS[spacing] : -spacing;

  return (
    <div className={clsx(classes.root, className)} ref={ref} {...other}>
      {children.slice(0, children.length - extraAvatars).map((child, index) => {
        return React.cloneElement(child, {
          className: clsx(child.props.className, classes.avatar),
          style: {
            zIndex: children.length - index,
            marginLeft: index === 0 ? undefined : marginLeft,
            ...child.props.style,
          },
        });
      })}
      {extraAvatars ? (
        <Tooltip
          title={props.extraAvatarsTooltipTitle}
        >
        <Avatar
          className={classes.avatar}
          style={{
            zIndex: 0,
            marginLeft,
          }}
        >
          +{extraAvatars}
        </Avatar>
        </Tooltip>
      ) : null}
    </div>
  );
});

AvatarGroup.propTypes = {
  // ----------------------------- Warning --------------------------------
  // | These PropTypes are generated from the TypeScript type definitions |
  // |     To update them edit the d.ts file and run "yarn proptypes"     |
  // ----------------------------------------------------------------------
  /**
   * The avatars to stack.
   */
  children: PropTypes.node,
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css) below for more details.
   */
  classes: PropTypes.object,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * Max avatars to show before +x.
   */
  max: chainPropTypes(PropTypes.number, (props) => {
    if (props.max < 2) {
      throw new Error(
        [
          'Material-UI: The prop `max` should be equal to 2 or above.',
          'A value below is clamped to 2.',
        ].join('\n'),
      );
    }
  }),
  /**
   * Spacing between avatars.
   */
  spacing: PropTypes.oneOfType([PropTypes.oneOf(['medium', 'small']), PropTypes.number]),
};

export default withStyles(styles, { name: 'MuiAvatarGroup' })(AvatarGroup);
Arthur Rubens
  • 4,626
  • 1
  • 8
  • 11
  • 1
    Thank you! This is super helpful! Will definitely bring it up as an opportunity for the library. – Karla Lopez Jul 07 '20 at 20:08
  • You are welcome. Unfortunately it is not super solution. The functional programming (react functional components) has some forgotten limitations. That is why was created OOP and Prototype based programming (JS). That is why I had to copy-paste existing code and make there changes. – Arthur Rubens Jul 07 '20 at 20:33