Files
PaperClipAI/ui/src/components/Identity.tsx
T
DottaandPaperclip b315580640 feat: add private task creation and sharing controls (#14718)
Add private task creation and sharing controls, effective access explanations, private project management, and locked task references.

Constrain the mobile composer plus menu to the viewport and explain named inherited privacy on hover, focus, or tap. Include 125 production-component Storybook stories and thirteen interactive user journeys.

Final head passes all CI checks and Greptile Apex 5/5 with no findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-07 07:27:52 -05:00

45 lines
1.3 KiB
TypeScript

import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
type IdentitySize = "xs" | "sm" | "default" | "lg";
type IdentityShape = "circle" | "square";
export interface IdentityProps {
name: string;
avatarUrl?: string | null;
initials?: string;
size?: IdentitySize;
shape?: IdentityShape;
className?: string;
}
export function deriveInitials(name: string): string {
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
return name.slice(0, 2).toUpperCase();
}
const textSize: Record<IdentitySize, string> = {
xs: "text-sm",
sm: "text-xs",
default: "text-sm",
lg: "text-sm",
};
export function Identity({ name, avatarUrl, initials, size = "default", shape = "circle", className }: IdentityProps) {
const displayInitials = initials ?? deriveInitials(name);
return (
<span
className={cn("inline-flex min-w-0 gap-1.5 items-center", size === "xs" && "gap-1", size === "lg" && "gap-2", className)}
title={name}
>
<Avatar size={size} shape={shape} aria-hidden="true">
{avatarUrl && <AvatarImage src={avatarUrl} alt="" />}
<AvatarFallback>{displayInitials}</AvatarFallback>
</Avatar>
<span className={cn("truncate", textSize[size])}>{name}</span>
</span>
);
}