123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import {Fragment, useState} from 'react';
- import styled from '@emotion/styled';
- import {AnimatePresence, motion} from 'framer-motion';
- import Avatar from 'sentry/components/avatar';
- import TeamAvatar from 'sentry/components/avatar/teamAvatar';
- import {Button} from 'sentry/components/button';
- import {IconChevron} from 'sentry/icons';
- import {t, tn} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Team} from 'sentry/types/organization';
- import type {User} from 'sentry/types/user';
- interface ParticipantScrollboxProps {
- teams: Team[];
- users: User[];
- }
- function ParticipantScrollbox({users, teams}: ParticipantScrollboxProps) {
- if (!users.length && !teams.length) {
- return null;
- }
- const showHeaders = users.length > 0 && teams.length > 0;
- return (
- <ParticipantListWrapper>
- {showHeaders && <ListTitle>{t('Teams (%s)', teams.length)}</ListTitle>}
- {teams.map(team => (
- <UserRow key={team.id}>
- <TeamAvatar team={team} size={28} />
- <div>
- {`#${team.slug}`}
- <SubText>{tn('%s member', '%s members', team.memberCount)}</SubText>
- </div>
- </UserRow>
- ))}
- {showHeaders && <ListTitle>{t('Individuals (%s)', users.length)}</ListTitle>}
- {users.map(user => (
- <UserRow key={user.id}>
- <Avatar user={user} size={28} />
- <div>
- {user.name}
- <SubText>{user.email}</SubText>
- </div>
- </UserRow>
- ))}
- </ParticipantListWrapper>
- );
- }
- interface ParticipantListProps {
- children: React.ReactNode;
- description: string;
- users: User[];
- teams?: Team[];
- }
- export function ParticipantList({teams = [], users, children}: ParticipantListProps) {
- const [isExpanded, setIsExpanded] = useState(false);
- return (
- <Fragment>
- <ParticipantWrapper onClick={() => setIsExpanded(!isExpanded)} role="button">
- {children}
- <Button
- borderless
- size="zero"
- icon={
- <IconChevron
- direction={isExpanded ? 'up' : 'down'}
- size="xs"
- color="gray300"
- />
- }
- aria-label={t('%s Participants', isExpanded ? t('Collapse') : t('Expand'))}
- onClick={() => setIsExpanded(!isExpanded)}
- />
- </ParticipantWrapper>
- <AnimatePresence>
- {isExpanded && (
- <motion.div
- variants={{
- open: {height: '100%', opacity: 1, overflow: 'initial'},
- closed: {height: '0', opacity: 0, overflow: 'hidden'},
- }}
- initial="closed"
- animate="open"
- exit="closed"
- >
- <ParticipantScrollbox users={users} teams={teams} />
- </motion.div>
- )}
- </AnimatePresence>
- </Fragment>
- );
- }
- const ParticipantWrapper = styled('div')`
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- padding-bottom: ${space(1)};
- & > span {
- cursor: pointer;
- }
- `;
- const ParticipantListWrapper = styled('div')`
- max-height: 325px;
- overflow-y: auto;
- border: 1px solid ${p => p.theme.border};
- border-radius: ${p => p.theme.borderRadius};
- & > div:not(:last-child) {
- border-bottom: 1px solid ${p => p.theme.border};
- }
- & > div:first-child {
- border-top-left-radius: ${p => p.theme.borderRadius};
- border-top-right-radius: ${p => p.theme.borderRadius};
- }
- & > div:last-child {
- border-bottom-left-radius: ${p => p.theme.borderRadius};
- border-bottom-right-radius: ${p => p.theme.borderRadius};
- }
- `;
- const ListTitle = styled('div')`
- display: flex;
- align-items: center;
- padding: ${space(1)} ${space(1.5)};
- background-color: ${p => p.theme.backgroundSecondary};
- color: ${p => p.theme.gray300};
- text-transform: uppercase;
- font-weight: ${p => p.theme.fontWeightBold};
- font-size: ${p => p.theme.fontSizeSmall};
- `;
- const UserRow = styled('div')`
- display: flex;
- align-items: center;
- padding: ${space(1)} ${space(1.5)};
- gap: ${space(1)};
- line-height: 1.2;
- font-size: ${p => p.theme.fontSizeSmall};
- `;
- const SubText = styled('div')`
- color: ${p => p.theme.subText};
- font-size: ${p => p.theme.fontSizeExtraSmall};
- `;
|