Files
krow-demo/src/components/ds/ActivityCard.jsx
2026-08-07 23:53:15 +05:30

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>
);
}