Files
bakery-ia/frontend/src/components/ui/ListItem/ListItem.tsx
2025-08-28 10:41:04 +02:00

138 lines
3.6 KiB
TypeScript

import React, { forwardRef } from 'react';
import { clsx } from 'clsx';
export interface ListItemProps extends React.HTMLAttributes<HTMLDivElement> {
title: string;
subtitle?: string;
description?: string;
leadingContent?: React.ReactNode;
trailingContent?: React.ReactNode;
variant?: 'default' | 'compact' | 'detailed';
interactive?: boolean;
active?: boolean;
disabled?: boolean;
divider?: boolean;
}
const ListItem = forwardRef<HTMLDivElement, ListItemProps>(({
title,
subtitle,
description,
leadingContent,
trailingContent,
variant = 'default',
interactive = false,
active = false,
disabled = false,
divider = false,
className,
children,
onClick,
...props
}, ref) => {
const baseClasses = [
'flex items-center w-full',
'transition-colors duration-200',
];
const variantClasses = {
default: 'py-3 px-4',
compact: 'py-2 px-3',
detailed: 'py-4 px-4',
};
const stateClasses = {
interactive: interactive ? [
'cursor-pointer',
'hover:bg-[var(--bg-secondary)]',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
] : [],
active: active ? 'bg-[var(--color-primary)]/10 border-r-2 border-[var(--color-primary)]' : '',
disabled: disabled ? 'opacity-50 cursor-not-allowed' : '',
};
const Component = interactive ? 'button' : 'div';
return (
<>
<Component
ref={ref}
className={clsx(
baseClasses,
variantClasses[variant],
stateClasses.interactive,
stateClasses.active,
stateClasses.disabled,
className
)}
onClick={disabled ? undefined : onClick}
disabled={disabled}
{...props}
>
{/* Leading content */}
{leadingContent && (
<div className="flex-shrink-0 mr-3">
{leadingContent}
</div>
)}
{/* Main content */}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<p className={clsx(
'font-medium text-[var(--text-primary)] truncate',
{
'text-sm': variant === 'compact',
'text-base': variant === 'default',
'text-lg': variant === 'detailed',
}
)}>
{title}
</p>
{subtitle && (
<p className={clsx(
'text-[var(--text-secondary)] truncate mt-1',
{
'text-xs': variant === 'compact',
'text-sm': variant === 'default' || variant === 'detailed',
}
)}>
{subtitle}
</p>
)}
{description && variant === 'detailed' && (
<p className="text-sm text-[var(--text-tertiary)] mt-1 line-clamp-2">
{description}
</p>
)}
{children && (
<div className="mt-2">
{children}
</div>
)}
</div>
{/* Trailing content */}
{trailingContent && (
<div className="flex-shrink-0 ml-3">
{trailingContent}
</div>
)}
</div>
</div>
</Component>
{divider && (
<div className="border-b border-[var(--border-primary)] mx-4" />
)}
</>
);
});
ListItem.displayName = 'ListItem';
export default ListItem;