avatarList.tsx 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import UserAvatar from 'sentry/components/avatar/userAvatar';
  4. import Tooltip from 'sentry/components/tooltip';
  5. import {AvatarUser} from 'sentry/types';
  6. type UserAvatarProps = React.ComponentProps<typeof UserAvatar>;
  7. type Props = {
  8. users: AvatarUser[];
  9. avatarSize?: number;
  10. className?: string;
  11. maxVisibleAvatars?: number;
  12. renderTooltip?: UserAvatarProps['renderTooltip'];
  13. tooltipOptions?: UserAvatarProps['tooltipOptions'];
  14. typeMembers?: string;
  15. };
  16. function AvatarList({
  17. avatarSize = 28,
  18. maxVisibleAvatars = 5,
  19. typeMembers = 'users',
  20. tooltipOptions = {},
  21. className,
  22. users,
  23. renderTooltip,
  24. }: Props) {
  25. const visibleUsers = users.slice(0, maxVisibleAvatars);
  26. const numCollapsedUsers = users.length - visibleUsers.length;
  27. if (!tooltipOptions.position) {
  28. tooltipOptions.position = 'top';
  29. }
  30. return (
  31. <AvatarListWrapper className={className}>
  32. {!!numCollapsedUsers && (
  33. <Tooltip title={`${numCollapsedUsers} other ${typeMembers}`}>
  34. <CollapsedUsers size={avatarSize} data-test-id="avatarList-collapsedusers">
  35. {numCollapsedUsers < 99 && <Plus>+</Plus>}
  36. {numCollapsedUsers}
  37. </CollapsedUsers>
  38. </Tooltip>
  39. )}
  40. {visibleUsers.map(user => (
  41. <StyledAvatar
  42. key={`${user.id}-${user.email}`}
  43. user={user}
  44. size={avatarSize}
  45. renderTooltip={renderTooltip}
  46. tooltipOptions={tooltipOptions}
  47. hasTooltip
  48. />
  49. ))}
  50. </AvatarListWrapper>
  51. );
  52. }
  53. export default AvatarList;
  54. // used in releases list page to do some alignment
  55. export const AvatarListWrapper = styled('div')`
  56. display: flex;
  57. flex-direction: row-reverse;
  58. `;
  59. const Circle = p => css`
  60. border-radius: 50%;
  61. border: 2px solid ${p.theme.background};
  62. margin-left: -8px;
  63. cursor: default;
  64. &:hover {
  65. z-index: 1;
  66. }
  67. `;
  68. const StyledAvatar = styled(UserAvatar)`
  69. overflow: hidden;
  70. ${Circle};
  71. `;
  72. const CollapsedUsers = styled('div')<{size: number}>`
  73. display: flex;
  74. align-items: center;
  75. justify-content: center;
  76. position: relative;
  77. text-align: center;
  78. font-weight: 600;
  79. background-color: ${p => p.theme.gray200};
  80. color: ${p => p.theme.gray300};
  81. font-size: ${p => Math.floor(p.size / 2.3)}px;
  82. width: ${p => p.size}px;
  83. height: ${p => p.size}px;
  84. ${Circle};
  85. `;
  86. const Plus = styled('span')`
  87. font-size: 10px;
  88. margin-left: 1px;
  89. margin-right: -1px;
  90. `;