ADD new frontend
This commit is contained in:
138
frontend/src/components/ui/ListItem/ListItem.tsx
Normal file
138
frontend/src/components/ui/ListItem/ListItem.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user