letterAvatar.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import {imageStyle} from 'app/components/avatar/styles';
  4. import theme from 'app/utils/theme';
  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. identifier?: string;
  50. displayName?: string;
  51. round?: boolean;
  52. forwardedRef?: React.Ref<SVGSVGElement>;
  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. <svg ref={forwardedRef} viewBox="0 0 120 120" {...props}>
  70. <rect
  71. x="0"
  72. y="0"
  73. width="120"
  74. height="120"
  75. rx="15"
  76. ry="15"
  77. fill={suggested ? '#FFFFFF' : getColor(identifier)}
  78. />
  79. <text
  80. x="50%"
  81. y="50%"
  82. fontSize="65"
  83. style={{dominantBaseline: 'central'}}
  84. textAnchor="middle"
  85. fill={suggested ? theme.gray400 : '#FFFFFF'}
  86. >
  87. {getInitials(displayName)}
  88. </text>
  89. </svg>
  90. )
  91. )<Props>`
  92. ${imageStyle};
  93. `;
  94. LetterAvatar.defaultProps = {
  95. round: false,
  96. };
  97. export default React.forwardRef<SVGSVGElement, Props>((props, ref) => (
  98. <LetterAvatar forwardedRef={ref} {...props} />
  99. ));