All files / src/features/Settings/Team/components GiveMemberNameCell.tsx

100% Statements 8/8
85.71% Branches 12/14
100% Functions 1/1
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80                      11x                                                     11x               609x 609x   609x 609x       609x   609x                                              
import { Stack } from "@mui/material";
import { addSizeToImage } from "@components/UploadAvatar/UploadAvatar";
import GiveTruncateText from "@shared/Text/GiveTruncateText";
import GiveAvatar from "@shared/Avatar/GiveAvatar";
 
/**
 * The two pairings the designs use. `md` is the table row (Settings → Team, the
 * event Host tab); `sm` is the Assign Host modal's 54px row, whose 24px avatar
 * comes with a wider gutter (frame 8181-61190). Paired rather than exposed as
 * two props because the two values move together in the mockups.
 */
const SIZES = {
  md: { avatar: "32px", gap: "8px" },
  sm: { avatar: "24px", gap: "12px" },
} as const;
 
type GiveMemberNameCellProps = {
  imageURL?: string;
  firstName: string;
  lastName?: string;
  email: string;
  size?: keyof typeof SIZES;
  /**
   * Lead with the email when the member has no name on file, and drop the
   * second line rather than repeating it. Off by default: a table row wants the
   * blank name line so the email stays in its own column position, while a
   * standalone row (the Assign Host modal) wants the collapse.
   */
  fallbackToEmail?: boolean;
};
 
/**
 * One member's identity: avatar, name, email.
 *
 * Shared by the Team table, the event Host tab and the Assign Host modal — the
 * three drew the same person from three copies of this markup before, which is
 * how they drift.
 */
export const GiveMemberNameCell = ({
  imageURL,
  firstName,
  lastName,
  email,
  size = "md",
  fallbackToEmail = false,
}: GiveMemberNameCellProps) => {
  const image = imageURL ? addSizeToImage(imageURL, "small") : undefined;
  const { avatar, gap } = SIZES[size];
 
  const name = [firstName, lastName].filter(Boolean).join(" ").trim();
  const leadWithEmail = fallbackToEmail && !name;
  // The default keeps the literal two-slot template rather than the joined
  // name: a member with no name on file renders a whitespace-only line, and
  // that line is what holds a table row at its full height.
  const nameLine = fallbackToEmail ? name : `${firstName} ${lastName ?? ""}`;
 
  return (
    <Stack direction="row" alignItems="center" gap={gap} flex={1} minWidth={0}>
      <GiveAvatar size={avatar} imageUrl={image} shape="rounded" />
      {/* minWidth:0 is what lets the two texts truncate instead of pushing a
          sibling control off the row's right edge. */}
      <Stack minWidth={0} flex={1}>
        <GiveTruncateText lineClamp={1} variant="bodyS">
          {leadWithEmail ? email : nameLine}
        </GiveTruncateText>
        {!leadWithEmail && (
          <GiveTruncateText
            sx={{ wordBreak: "break-all" }}
            color="secondary"
            variant="bodyXS"
            lineClamp={1}
          >
            {email}
          </GiveTruncateText>
        )}
      </Stack>
    </Stack>
  );
};