AudienceAvatar.tsx1.7 KBView on GitHub
import type { LucideIcon } from 'lucide-react';

import { personColor } from '@/components/ui/person-avatar';
import { cn } from '@/lib/utils';

/**
 * A CROWD, as a round mark — the organisation, or everyone who holds one agent.
 *
 * The share panel's list is a list of subjects, and two of them are not people. Drawn
 * with `PersonAvatar` they get initials, so "Everyone with this agent" arrives as a disc
 * reading `EW` in a column of real faces — a person who does not exist, which is worse
 * than an unlabelled mark because the reader tries to place them.
 *
 * It takes the SAME tint as a person's disc (`personColor`, hashed off the subject's id)
 * and the same white foreground. A pale circle with a grey glyph beside a column of
 * saturated discs is a different kind of mark, and the eye changes gear at exactly the
 * row where nothing new is happening. The glyph is what says "this is not one person".
 *
 * `aria-hidden` because the row's own text already names the subject; the disc would
 * otherwise be read out twice.
 */
export function AudienceAvatar({
  /** What the tint is hashed off — the organisation's id, or the agent's. */
  seed,
  icon: Icon,
  className,
}: {
  seed: string;
  icon: LucideIcon;
  className?: string;
}) {
  return (
    <span
      aria-hidden
      className={cn(
        'flex size-5 shrink-0 items-center justify-center rounded-full text-white',
        className,
      )}
      // Inline because the tint is data, not a token — see `PersonAvatar`, which says
      // why a class per palette entry cannot survive a production build.
      style={{ backgroundColor: personColor(seed) }}
    >
      <Icon className="size-3" />
    </span>
  );
}