online-users-widget.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import React from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import {
  4. Dropdown,
  5. DropdownHeader,
  6. DropdownItem,
  7. DropdownMenu,
  8. DropdownToggle,
  9. } from '@/shared/components/dropdown/dropdown-menu'
  10. import { getBackgroundColorForUserId } from '@/shared/utils/colors'
  11. import OLTooltip from '@/shared/components/ol/ol-tooltip'
  12. import MaterialIcon from '@/shared/components/material-icon'
  13. import { OnlineUser } from '@/features/ide-react/context/online-users-context'
  14. import { Doc } from '@ol-types/doc'
  15. function OnlineUsersWidget({
  16. onlineUsers,
  17. goToUser,
  18. }: {
  19. onlineUsers: OnlineUser[]
  20. goToUser: (user: OnlineUser) => Promise<Doc | undefined>
  21. }) {
  22. const { t } = useTranslation()
  23. const shouldDisplayDropdown = onlineUsers.length >= 4
  24. if (shouldDisplayDropdown) {
  25. return (
  26. <Dropdown className="online-users" align="end">
  27. <DropdownToggle
  28. id="online-users"
  29. as={DropDownToggleButton}
  30. // @ts-ignore: fix type of DropdownToggle with "as" prop so that it can accept
  31. // custom props for that component
  32. onlineUserCount={onlineUsers.length}
  33. />
  34. <DropdownMenu>
  35. <DropdownHeader aria-hidden="true">
  36. {t('connected_users')}
  37. </DropdownHeader>
  38. {onlineUsers.map((user, index) => (
  39. <li role="none" key={`${user.user_id}_${index}`}>
  40. <DropdownItem
  41. as="button"
  42. tabIndex={-1}
  43. onClick={() => goToUser(user)}
  44. >
  45. <UserIcon user={user} showName />
  46. </DropdownItem>
  47. </li>
  48. ))}
  49. </DropdownMenu>
  50. </Dropdown>
  51. )
  52. } else {
  53. return (
  54. <div className="online-users">
  55. {onlineUsers.map((user, index) => (
  56. <OLTooltip
  57. key={`${user.user_id}_${index}`}
  58. id="online-user"
  59. description={user.name}
  60. overlayProps={{ placement: 'bottom', trigger: ['hover', 'focus'] }}
  61. tooltipProps={{ translate: 'no' }}
  62. >
  63. <span>
  64. {/* OverlayTrigger won't fire unless UserIcon is wrapped in a span */}
  65. <UserIcon user={user} onClick={goToUser} />
  66. </span>
  67. </OLTooltip>
  68. ))}
  69. </div>
  70. )
  71. }
  72. }
  73. function UserIcon({
  74. user,
  75. showName,
  76. onClick,
  77. }: {
  78. user: OnlineUser
  79. showName?: boolean
  80. onClick?: (user: OnlineUser) => void
  81. }) {
  82. const backgroundColor = getBackgroundColorForUserId(user.user_id)
  83. function handleOnClick() {
  84. onClick?.(user)
  85. }
  86. const [character] = [...user.name]
  87. return (
  88. // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
  89. <span onClick={handleOnClick}>
  90. <span className="online-user" style={{ backgroundColor }}>
  91. {character}
  92. </span>
  93. {showName && user.name}
  94. </span>
  95. )
  96. }
  97. const DropDownToggleButton = React.forwardRef<
  98. HTMLButtonElement,
  99. { onlineUserCount: number; onClick: React.MouseEventHandler }
  100. >((props, ref) => {
  101. const { t } = useTranslation()
  102. return (
  103. <OLTooltip
  104. id="connected-users"
  105. description={t('connected_users')}
  106. overlayProps={{ placement: 'left' }}
  107. >
  108. <button
  109. type="button"
  110. className="online-user online-user-multi"
  111. onClick={props.onClick} // required by Bootstrap Dropdown to trigger an opening
  112. ref={ref}
  113. >
  114. <strong>{props.onlineUserCount}</strong>&nbsp;
  115. <MaterialIcon type="groups" />
  116. </button>
  117. </OLTooltip>
  118. )
  119. })
  120. DropDownToggleButton.displayName = 'DropDownToggleButton'
  121. export default OnlineUsersWidget