letterAvatar.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import {forwardRef} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {imageStyle} from 'sentry/components/avatar/styles';
  5. const COLORS = [
  6. '#4674ca', // blue
  7. '#315cac', // blue_dark
  8. '#57be8c', // green
  9. '#3fa372', // green_dark
  10. '#f9a66d', // yellow_orange
  11. '#ec5e44', // red
  12. '#e63717', // red_dark
  13. '#f868bc', // pink
  14. '#6c5fc7', // purple
  15. '#4e3fb4', // purple_dark
  16. '#57b1be', // teal
  17. '#847a8c', // gray
  18. ] as const;
  19. type Color = typeof COLORS[number];
  20. function hashIdentifier(identifier: string) {
  21. identifier += '';
  22. let hash = 0;
  23. for (let i = 0; i < identifier.length; i++) {
  24. hash += identifier.charCodeAt(i);
  25. }
  26. return hash;
  27. }
  28. function getColor(identifier: string | undefined): Color {
  29. // Gray if the identifier is not set
  30. if (identifier === undefined) {
  31. return '#847a8c';
  32. }
  33. const id = hashIdentifier(identifier);
  34. return COLORS[id % COLORS.length];
  35. }
  36. function getInitials(displayName: string | undefined) {
  37. const names = ((typeof displayName === 'string' && displayName.trim()) || '?').split(
  38. ' '
  39. );
  40. // Use Array.from as slicing and substring() work on ucs2 segments which
  41. // results in only getting half of any 4+ byte character.
  42. let initials = Array.from(names[0])[0];
  43. if (names.length > 1) {
  44. initials += Array.from(names[names.length - 1])[0];
  45. }
  46. return initials.toUpperCase();
  47. }
  48. type Props = {
  49. displayName?: string;
  50. forwardedRef?: React.Ref<SVGSVGElement>;
  51. identifier?: string;
  52. round?: boolean;
  53. suggested?: boolean;
  54. };
  55. type LetterAvatarProps = React.ComponentProps<'svg'> & Props;
  56. /**
  57. * Also see avatar.py. Anything changed in this file (how colors are selected,
  58. * the svg, etc) will also need to be changed there.
  59. */
  60. const LetterAvatar = styled(
  61. ({
  62. identifier,
  63. displayName,
  64. round: _round,
  65. forwardedRef,
  66. suggested,
  67. ...props
  68. }: LetterAvatarProps) => {
  69. const theme = useTheme();
  70. return (
  71. <svg ref={forwardedRef} viewBox="0 0 120 120" {...props}>
  72. <rect
  73. x="0"
  74. y="0"
  75. width="120"
  76. height="120"
  77. rx="15"
  78. ry="15"
  79. opacity={suggested ? '50%' : '100%'}
  80. fill={getColor(identifier)}
  81. />
  82. <text
  83. x="50%"
  84. y="50%"
  85. fontSize="65"
  86. style={{dominantBaseline: 'central'}}
  87. textAnchor="middle"
  88. fill={theme.white}
  89. >
  90. {getInitials(displayName)}
  91. </text>
  92. </svg>
  93. );
  94. }
  95. )<Props>`
  96. ${imageStyle};
  97. `;
  98. LetterAvatar.defaultProps = {
  99. round: false,
  100. };
  101. export default forwardRef<SVGSVGElement, Props>((props, ref) => (
  102. <LetterAvatar forwardedRef={ref} {...props} />
  103. ));