UMBRA/frontend/src/components/calendar/CalendarMemberRow.tsx
Kyle Pope 1b36e6b6a7 Widen shared calendar dialogs + single-line member rows
- CalendarForm: max-w-3xl when sharing (was sm:max-w-2xl, overridden by base max-w-xl)
- SharedCalendarSettings: max-w-2xl (was sm:max-w-lg)
- CalendarMemberRow: back to single-line with PermissionToggle inline (less cramped)
- Use unprefixed max-w classes so twMerge properly overrides DialogContent base

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:54:25 +08:00

89 lines
3.3 KiB
TypeScript

import { X, UserPlus } from 'lucide-react';
import type { CalendarMemberInfo, CalendarPermission } from '@/types';
import { Checkbox } from '@/components/ui/checkbox';
import { useConfirmAction } from '@/hooks/useConfirmAction';
import PermissionBadge from './PermissionBadge';
import PermissionToggle from './PermissionToggle';
interface CalendarMemberRowProps {
member: CalendarMemberInfo;
isOwner: boolean;
readOnly?: boolean;
onUpdatePermission?: (memberId: number, permission: CalendarPermission) => void;
onUpdateCanAddOthers?: (memberId: number, canAddOthers: boolean) => void;
onRemove?: (memberId: number) => void;
}
export default function CalendarMemberRow({
member,
isOwner,
readOnly = false,
onUpdatePermission,
onUpdateCanAddOthers,
onRemove,
}: CalendarMemberRowProps) {
const { confirming, handleClick: handleRemoveClick } = useConfirmAction(
() => onRemove?.(member.id)
);
const displayName = member.preferred_name || member.umbral_name;
const initial = displayName.charAt(0).toUpperCase();
return (
<div className="flex items-center gap-3 rounded-lg border border-border p-3 transition-all duration-200 hover:border-border/80">
<div className="h-8 w-8 rounded-full bg-violet-500/15 flex items-center justify-center shrink-0">
<span className="text-sm text-violet-400 font-medium">{initial}</span>
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium truncate">{displayName}</span>
{member.status === 'pending' && (
<span className="text-[9px] px-1.5 py-0.5 rounded-full bg-orange-500/10 text-orange-400 font-medium">
Pending
</span>
)}
</div>
{member.preferred_name && (
<span className="text-xs text-muted-foreground">{member.umbral_name}</span>
)}
</div>
{readOnly ? (
<PermissionBadge permission={member.permission} />
) : isOwner ? (
<div className="flex items-center gap-2.5 shrink-0">
<PermissionToggle
value={member.permission}
onChange={(p) => onUpdatePermission?.(member.id, p)}
/>
{(member.permission === 'create_modify' || member.permission === 'full_access') && (
<label className="flex items-center gap-1.5 cursor-pointer shrink-0" title="Can add others">
<Checkbox
checked={member.can_add_others}
onChange={() => onUpdateCanAddOthers?.(member.id, !member.can_add_others)}
className="h-3.5 w-3.5"
/>
<UserPlus className="h-3.5 w-3.5 text-muted-foreground" />
</label>
)}
<button
type="button"
onClick={handleRemoveClick}
className="text-muted-foreground hover:text-destructive transition-colors"
title={confirming ? 'Click again to confirm' : 'Remove member'}
>
{confirming ? (
<span className="text-[10px] text-destructive font-medium px-1">Sure?</span>
) : (
<X className="h-4 w-4" />
)}
</button>
</div>
) : (
<PermissionBadge permission={member.permission} />
)}
</div>
);
}