UserIcon.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { css, cx } from '@emotion/css';
  2. import React, { FC } from 'react';
  3. import { GrafanaTheme, dateTime } from '@grafana/data';
  4. import { Button, stylesFactory, Tooltip, useTheme } from '@grafana/ui';
  5. import { UserViewDTO } from './api';
  6. const getIconBorder = (color: string): string => {
  7. return `0 0 0 1px ${color}`;
  8. };
  9. export const getUserIconStyles = stylesFactory((theme: GrafanaTheme, isActive: boolean, showBorder: boolean) => {
  10. const shadowColor = isActive ? theme.palette.blue80 : theme.colors.border2;
  11. const shadowHoverColor = isActive ? theme.palette.blue95 : theme.colors.border3;
  12. const borderColor = showBorder ? theme.colors.dashboardBg : 'transparent';
  13. return {
  14. icon: css`
  15. border-radius: 50%;
  16. width: 30px;
  17. height: 30px;
  18. margin-left: -6px;
  19. border: 3px ${borderColor} solid;
  20. box-shadow: ${getIconBorder(shadowColor)};
  21. background-clip: padding-box;
  22. &:hover {
  23. background-clip: padding-box;
  24. box-shadow: ${getIconBorder(shadowHoverColor)};
  25. }
  26. `,
  27. textIcon: css`
  28. padding: 0px;
  29. text-align: center;
  30. line-height: 22px;
  31. justify-content: center;
  32. color: ${theme.colors.textSemiWeak};
  33. cursor: auto;
  34. font-size: ${theme.typography.size.sm};
  35. background: ${theme.isDark ? theme.palette.dark9 : theme.palette.gray90};
  36. &:focus {
  37. box-shadow: ${getIconBorder(shadowColor)};
  38. }
  39. &:hover {
  40. background: ${theme.isDark ? theme.palette.dark10 : theme.palette.gray95};
  41. }
  42. `,
  43. tooltipContainer: css`
  44. text-align: center;
  45. padding: 0px ${theme.spacing.sm};
  46. `,
  47. tooltipName: css`
  48. font-weight: ${theme.typography.weight.bold};
  49. `,
  50. tooltipDate: css`
  51. font-weight: ${theme.typography.weight.regular};
  52. `,
  53. dot: css`
  54. height: 6px;
  55. width: 6px;
  56. background-color: ${theme.palette.blue80};
  57. border-radius: 50%;
  58. display: inline-block;
  59. margin-left: 6px;
  60. margin-bottom: 1px;
  61. `,
  62. };
  63. });
  64. export interface UserIconProps {
  65. userView: UserViewDTO;
  66. showTooltip?: boolean;
  67. showBorder?: boolean;
  68. className?: string;
  69. }
  70. const formatViewed = (dateString: string): string => {
  71. const date = dateTime(dateString);
  72. const diffHours = date.diff(dateTime(), 'hours', false);
  73. return `Active last ${(Math.floor(-diffHours / 24) + 1) * 24}h`;
  74. };
  75. export const UserIcon: FC<UserIconProps> = ({ userView, showTooltip = true, showBorder = false, className }) => {
  76. const { user, viewed } = userView;
  77. const isActive = dateTime(viewed).diff(dateTime(), 'minutes', true) >= -15;
  78. const theme = useTheme();
  79. const styles = getUserIconStyles(theme, isActive, showBorder);
  80. const userDisplayName = user.name || user.login;
  81. const initialsArray = userDisplayName.split(' ');
  82. const initials = (
  83. (initialsArray.shift()?.slice(0, 1) || '') + (initialsArray.pop()?.slice(0, 1) || '')
  84. ).toUpperCase();
  85. const content =
  86. user.avatarUrl && user.hasCustomAvatar ? (
  87. <img
  88. className={cx(styles.icon, className)}
  89. src={user.avatarUrl}
  90. aria-label="Avatar icon"
  91. alt={`${initials} avatar`}
  92. />
  93. ) : (
  94. <Button variant="secondary" className={cx(styles.textIcon, styles.icon, className)} aria-label="Initials icon">
  95. {initials}
  96. </Button>
  97. );
  98. if (showTooltip) {
  99. const tooltip = (
  100. <div className={styles.tooltipContainer}>
  101. <div className={styles.tooltipName}>{userDisplayName}</div>
  102. <div className={styles.tooltipDate}>
  103. {isActive ? (
  104. <>
  105. <span>Active last 15m</span>
  106. <span className={styles.dot}></span>
  107. </>
  108. ) : (
  109. formatViewed(viewed)
  110. )}
  111. </div>
  112. </div>
  113. );
  114. return (
  115. <Tooltip content={tooltip} key={`recent-user-${user.id}`}>
  116. {content}
  117. </Tooltip>
  118. );
  119. } else {
  120. return content;
  121. }
  122. };