avatarList.tsx 2.7 KB

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