geraete/components/ui/Feed.tsx
2025-11-14 20:16:24 +01:00

226 lines
8.3 KiB
TypeScript

// components/ui/Feed.tsx
'use client';
import * as React from 'react';
import { Fragment } from 'react';
import {
ChatBubbleLeftEllipsisIcon,
TagIcon,
UserCircleIcon,
} from '@heroicons/react/20/solid';
import clsx from 'clsx';
/* ───────── Types ───────── */
export type FeedPerson = {
name: string;
href?: string;
};
export type FeedTag = {
name: string;
href?: string;
/** z.B. 'fill-red-500' */
color?: string;
};
export type FeedItem =
| {
id: string | number;
type: 'comment';
person: FeedPerson;
imageUrl?: string;
comment: string;
date: string;
}
| {
id: string | number;
type: 'assignment';
person: FeedPerson;
assigned: FeedPerson;
date: string;
}
| {
id: string | number;
type: 'tags';
person: FeedPerson;
tags: FeedTag[];
date: string;
};
export interface FeedProps {
items: FeedItem[];
className?: string;
}
/* ───────── Helper ───────── */
function classNames(...classes: Array<string | false | null | undefined>) {
return classes.filter(Boolean).join(' ');
}
/* ───────── Component ───────── */
export default function Feed({ items, className }: FeedProps) {
if (!items.length) {
return (
<p className={clsx('text-sm text-gray-500 dark:text-gray-400', className)}>
Keine Aktivitäten vorhanden.
</p>
);
}
return (
<div className={clsx('flow-root', className)}>
<ul role="list" className="-mb-8">
{items.map((activityItem, idx) => (
<li key={activityItem.id}>
<div className="relative pb-8">
{idx !== items.length - 1 ? (
<span
aria-hidden="true"
className="absolute left-5 top-5 -ml-px h-full w-0.5 bg-gray-200 dark:bg-white/10"
/>
) : null}
<div className="relative flex items-start space-x-3">
{activityItem.type === 'comment' ? (
<>
<div className="relative">
{activityItem.imageUrl ? (
<img
alt=""
src={activityItem.imageUrl}
className="flex size-10 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white outline -outline-offset-1 outline-black/5 dark:ring-gray-900 dark:outline-white/10"
/>
) : (
<div className="flex size-10 items-center justify-center rounded-full bg-gray-200 ring-8 ring-white dark:bg-gray-800 dark:ring-gray-900">
<ChatBubbleLeftEllipsisIcon
aria-hidden="true"
className="size-5 text-gray-400"
/>
</div>
)}
<span className="absolute -right-1 -bottom-0.5 rounded-tl bg-white px-0.5 py-px dark:bg-gray-900">
<ChatBubbleLeftEllipsisIcon
aria-hidden="true"
className="size-5 text-gray-400"
/>
</span>
</div>
<div className="min-w-0 flex-1">
<div>
<div className="text-sm">
<a
href={activityItem.person.href ?? '#'}
className="font-medium text-gray-900 dark:text-white"
>
{activityItem.person.name}
</a>
</div>
<p className="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
Kommentiert {activityItem.date}
</p>
</div>
<div className="mt-2 text-sm text-gray-700 dark:text-gray-200">
<p>{activityItem.comment}</p>
</div>
</div>
</>
) : activityItem.type === 'assignment' ? (
<>
<div>
<div className="relative px-1">
<div className="flex size-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white dark:bg-gray-800 dark:ring-gray-900">
<UserCircleIcon
aria-hidden="true"
className="size-5 text-gray-500 dark:text-gray-400"
/>
</div>
</div>
</div>
<div className="min-w-0 flex-1 py-1.5">
<div className="text-sm text-gray-500 dark:text-gray-400">
<a
href={activityItem.person.href ?? '#'}
className="font-medium text-gray-900 dark:text-white"
>
{activityItem.person.name}
</a>{' '}
hat{' '}
<a
href={activityItem.assigned.href ?? '#'}
className="font-medium text-gray-900 dark:text-white"
>
{activityItem.assigned.name}
</a>{' '}
zugewiesen{' '}
<span className="whitespace-nowrap">
{activityItem.date}
</span>
</div>
</div>
</>
) : activityItem.type === 'tags' ? (
<>
<div>
<div className="relative px-1">
<div className="flex size-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white dark:bg-gray-800 dark:ring-gray-900">
<TagIcon
aria-hidden="true"
className="size-5 text-gray-500 dark:text-gray-400"
/>
</div>
</div>
</div>
<div className="min-w-0 flex-1 py-0">
<div className="text-sm/8 text-gray-500 dark:text-gray-400">
<span className="mr-0.5">
<a
href={activityItem.person.href ?? '#'}
className="font-medium text-gray-900 dark:text-white"
>
{activityItem.person.name}
</a>{' '}
hat Tags hinzugefügt
</span>{' '}
<span className="mr-0.5">
{activityItem.tags.map((tag) => (
<Fragment key={tag.name}>
<a
href={tag.href ?? '#'}
className="inline-flex items-center gap-x-1.5 rounded-full px-2 py-1 text-xs font-medium text-gray-900 inset-ring inset-ring-gray-200 dark:bg-white/5 dark:text-gray-100 dark:inset-ring-white/10"
>
<svg
viewBox="0 0 6 6"
aria-hidden="true"
className={classNames(
tag.color ?? 'fill-gray-400',
'size-1.5',
)}
>
<circle r={3} cx={3} cy={3} />
</svg>
{tag.name}
</a>{' '}
</Fragment>
))}
</span>
<span className="whitespace-nowrap">
{activityItem.date}
</span>
</div>
</div>
</>
) : null}
</div>
</div>
</li>
))}
</ul>
</div>
);
}