@props([ 'groupBy' => 'priority', 'groupId' => null, 'label' => '', 'totalCount' => 0, 'statusCounts' => [], 'statusColumns' => [], 'expanded' => true, 'moreInfo' => null, 'timeAlert' => null ]) @php use Leantime\Domain\Tickets\Models\TicketDesignTokens; // Determine which icon component to use $iconComponent = match($groupBy) { 'priority' => 'thermometer-icon', 'storypoints' => 'tshirt-icon', 'effort' => 'tshirt-icon', 'editorId' => 'user-avatar', 'milestoneid' => 'milestone-icon', 'type' => 'type-icon', 'sprint' => 'sprint-icon', 'dueDate' => null, // No icon for due date buckets - label is sufficient default => null // Status and other groupings use FontAwesome icon below }; // For groupBy types without a component, use FontAwesome icon $faIcon = match($groupBy) { 'status' => 'fa-circle-dot', 'milestoneid' => null, // No icon for milestones 'dueDate' => null, // No icon for due date buckets - label is sufficient default => 'fa-layer-group' }; $iconProps = match($groupBy) { 'priority' => ['priority' => (int)$groupId], 'storypoints' => ['effort' => (float)$groupId], 'effort' => ['effort' => (float)$groupId], 'editorId' => ['userId' => $groupId, 'username' => $label], 'type' => ['type' => $groupId], default => ['label' => $label] }; // Effort groupby shows size label next to icon $effortLabel = ''; if (in_array($groupBy, ['storypoints', 'effort'])) { $effortLabel = TicketDesignTokens::getEffort((float)$groupId)['tshirtLabel'] ?? ''; } // Strip existing profileImage HTML from label for editorId (we use user-avatar component instead) if ($groupBy === 'editorId') { $label = preg_replace('/
.*?<\/div>\s*/i', '', $label); } // Transform statusColumns for micro-progress-bar $statusLabels = []; foreach ($statusColumns as $statusId => $statusData) { if (is_array($statusData)) { $statusLabels[$statusId] = $statusData['name'] ?? $statusData['label'] ?? "Status $statusId"; } else { $statusLabels[$statusId] = $statusData; } } @endphp {{-- PRD v2 Compliant: 150px horizontal layout with two rows --}} {{-- Outer container stretches full height, inner content scrolls/sticks --}}
merge(['class' => 'kanban-swimlane-sidebar']) }} data-swimlane-id="{{ $groupId }}" tabindex="0" role="button" aria-expanded="{{ $expanded ? 'true' : 'false' }}" aria-controls="swimlane-content-{{ $groupId }}" aria-label="{{ strip_tags($label) }} - {{ $totalCount }} tasks - {{ $expanded ? 'Expanded' : 'Collapsed' }}" onclick="leantime.kanbanController.toggleSwimlane('{{ $groupId }}')" onkeydown="if(event.key === 'Enter' || event.key === ' ') { event.preventDefault(); leantime.kanbanController.toggleSwimlane('{{ $groupId }}'); }"> {{-- Inner content wrapper - this receives the transform for sticky behavior --}}
{{-- Row 1: Chevron + Icon + Label + Time Indicator --}}
{{-- Chevron (▼ expanded, ▶ collapsed) --}} {{-- Visual indicator (icon/avatar) --}} @if($iconComponent)
@else {{-- Default FontAwesome icon for status and other groupings --}} @endif {{-- Label - truncates with ellipsis --}} {!! $label !!} {{-- Time indicator (⏳ ⏰ 💤) - hidden when collapsed --}} @if($timeAlert) @endif {{-- Count Badge (inline) - only visible when collapsed --}} {{ $totalCount }}
{{-- Row 2: Progress Bar + Count Badge --}}
{{-- Micro Progress Bar (status breakdown) - always shown, gray when empty --}}
{{-- Count Badge --}} {{ $totalCount }}
{{-- .kanban-swimlane-sidebar-inner --}}
{{-- Tooltip shown on hover for long labels --}} @if(strlen(strip_tags($label)) > 12 || $moreInfo)
{!! $label !!}
@if($moreInfo)
{!! $moreInfo !!}
@endif
@endif