feat: tree grouping for list and cards views (matching table view)
This commit is contained in:
@@ -475,10 +475,48 @@ export function ContactList({
|
||||
</li>
|
||||
);
|
||||
|
||||
// Group header renderer for list view
|
||||
const renderListGroupHeader = (group: GroupedContacts) => {
|
||||
const isExpanded = expandedGroups.has(group.key);
|
||||
return (
|
||||
<div
|
||||
key={`group-header-${group.key}`}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-secondary-50 border-b border-secondary-200"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleGroup(group.key)}
|
||||
className="flex items-center justify-center w-5 h-5 hover:bg-secondary-200 rounded transition-colors flex-shrink-0"
|
||||
aria-label={isExpanded ? 'Collapse group' : 'Expand group'}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</button>
|
||||
<span className="font-medium text-sm text-secondary-700 truncate">{group.label}</span>
|
||||
<span className="text-secondary-400 text-xs flex-shrink-0">({group.contacts.length})</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full" data-testid="contact-list-view">
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto">
|
||||
{shouldVirtualize ? (
|
||||
{isGrouped && groupedContacts ? (
|
||||
<div>
|
||||
{groupedContacts.map((group) => (
|
||||
<div key={group.key}>
|
||||
{renderListGroupHeader(group)}
|
||||
{expandedGroups.has(group.key) && (
|
||||
<ul className="divide-y divide-secondary-100" role="list">
|
||||
{group.contacts.map((contact) => renderContactItem(contact))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : shouldVirtualize ? (
|
||||
<ul className="divide-y divide-secondary-100" role="list" style={{ height: rowVirtualizer.getTotalSize(), position: 'relative' }}>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const contact = contacts[virtualRow.index];
|
||||
@@ -826,10 +864,48 @@ export function ContactList({
|
||||
</div>
|
||||
);
|
||||
|
||||
// Group header renderer for cards view
|
||||
const renderCardsGroupHeader = (group: GroupedContacts) => {
|
||||
const isExpanded = expandedGroups.has(group.key);
|
||||
return (
|
||||
<div
|
||||
key={`group-header-${group.key}`}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-secondary-50 border-b border-secondary-200 mb-3 sticky top-0 z-10"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleGroup(group.key)}
|
||||
className="flex items-center justify-center w-5 h-5 hover:bg-secondary-200 rounded transition-colors flex-shrink-0"
|
||||
aria-label={isExpanded ? 'Collapse group' : 'Expand group'}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</button>
|
||||
<span className="font-medium text-sm text-secondary-700 truncate">{group.label}</span>
|
||||
<span className="text-secondary-400 text-xs flex-shrink-0">({group.contacts.length})</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full" data-testid="contact-cards-view">
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto p-3">
|
||||
{shouldVirtualize ? (
|
||||
{isGrouped && groupedContacts ? (
|
||||
<div className="space-y-4">
|
||||
{groupedContacts.map((group) => (
|
||||
<div key={group.key}>
|
||||
{renderCardsGroupHeader(group)}
|
||||
{expandedGroups.has(group.key) && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{group.contacts.map((contact) => renderContactCard(contact))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : shouldVirtualize ? (
|
||||
<div style={{ height: rowVirtualizer.getTotalSize(), position: 'relative' }}>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3" style={{ position: 'absolute', top: 0, left: 0, right: 0 }}>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
|
||||
Reference in New Issue
Block a user