101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
import * as React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import { Avatar } from '@/components/ds/Avatar';
|
|
import { Surface } from '@/components/ds/Surface';
|
|
import { StatusBadge } from '@/components/ds/StatusBadge';
|
|
|
|
/**
|
|
* ActivityCard — one event in a feed: who did what, when.
|
|
*
|
|
* Renders as a row by default (feeds are dense) and as a bordered card when
|
|
* `variant="card"`. Either way the anatomy is fixed — actor, sentence, meta,
|
|
* trailing slot — so a feed built from mixed event types stays aligned.
|
|
*/
|
|
export function ActivityCard({
|
|
/** Person or system that acted. */
|
|
actor,
|
|
actorSrc,
|
|
/** Icon shown instead of an avatar, for system events. */
|
|
icon: Icon,
|
|
iconTone = 'soft',
|
|
/** What happened — "Screened Sofia Mendez". */
|
|
title,
|
|
/** Supporting detail. */
|
|
description,
|
|
timestamp,
|
|
status,
|
|
/** Right-aligned slot — a score, a button. */
|
|
trailing,
|
|
variant = 'row',
|
|
onClick,
|
|
className,
|
|
}) {
|
|
const iconTones = {
|
|
soft: 'bg-krow-blue-tint text-krow-blue',
|
|
brand: 'bg-krow-blue text-white',
|
|
success: 'bg-success-muted text-success',
|
|
warning: 'bg-warning-muted text-warning',
|
|
destructive: 'bg-destructive-muted text-destructive',
|
|
neutral: 'bg-surface-sunken text-ink-3',
|
|
};
|
|
|
|
const body = (
|
|
<>
|
|
{Icon ? (
|
|
<span className={cn('grid place-items-center w-9 h-9 rounded-xl shrink-0', iconTones[iconTone])}>
|
|
<Icon className="w-4 h-4" aria-hidden="true" />
|
|
</span>
|
|
) : (
|
|
<Avatar name={actor} src={actorSrc} size="default" />
|
|
)}
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-body-sm text-ink-1">
|
|
{actor && <span className="font-semibold">{actor}</span>}
|
|
{actor && title && ' '}
|
|
{title && <span className={actor ? 'text-ink-2' : 'font-medium'}>{title}</span>}
|
|
</p>
|
|
{description && <p className="text-caption text-ink-3 mt-0.5 truncate-2">{description}</p>}
|
|
{timestamp && <p className="text-caption text-ink-4 mt-1">{timestamp}</p>}
|
|
</div>
|
|
|
|
{(status || trailing) && (
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
{status && <StatusBadge status={status} size="sm" />}
|
|
{trailing}
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
if (variant === 'card') {
|
|
return (
|
|
<Surface
|
|
as={onClick ? 'button' : 'div'}
|
|
onClick={onClick}
|
|
interactive={onClick ? true : undefined}
|
|
variant="solid"
|
|
radius="lg"
|
|
padding="sm"
|
|
className={cn('flex items-start gap-3 w-full text-left', className)}
|
|
>
|
|
{body}
|
|
</Surface>
|
|
);
|
|
}
|
|
|
|
const Comp = onClick ? 'button' : 'div';
|
|
return (
|
|
<Comp
|
|
onClick={onClick}
|
|
className={cn(
|
|
'flex items-start gap-3 w-full text-left rounded-xl p-2.5 transition-colors duration-fast',
|
|
onClick && 'hover:bg-white/60 cursor-pointer focus-ring',
|
|
className
|
|
)}
|
|
>
|
|
{body}
|
|
</Comp>
|
|
);
|
|
}
|