Files
breakpilot-compliance/admin-compliance/components/sdk/SDKPipelineSidebar/SDKPipelineSidebar.tsx
Sharang Parnerkar b00fe6cb73 refactor(admin): split remaining components
Split 4 oversized component files (all >500 LOC) into sibling modules:
- SDKPipelineSidebar → Icons + Parts siblings (193/264/35 LOC)
- SourcesTab → SourceModals sibling (311/243 LOC)
- ScopeDecisionTab → ScopeDecisionSections sibling (127/444 LOC)
- ComplianceAdvisorWidget → ComplianceAdvisorParts sibling (265/131 LOC)

Zero behavior changes; all logic relocated verbatim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:41:29 +02:00

194 lines
7.5 KiB
TypeScript

'use client'
/**
* SDK Pipeline Sidebar
*
* Floating Action Button mit Drawer zur Visualisierung der SDK-Pipeline.
* Zeigt die 5 Pakete mit Fortschritt und ermoeglicht schnelle Navigation.
*
* Features:
* - Desktop (xl+): Fixierte Sidebar rechts
* - Mobile/Tablet: Floating Action Button mit Slide-In Drawer
*/
import { useState, useEffect } from 'react'
import { useSDK } from '@/lib/sdk'
import { CloseIcon, PipelineIcon } from './SDKPipelineSidebarIcons'
import { SidebarContent } from './SDKPipelineSidebarParts'
// =============================================================================
// MAIN COMPONENT - RESPONSIVE
// =============================================================================
export interface SDKPipelineSidebarProps {
/** Position des FAB auf Mobile */
fabPosition?: 'bottom-right' | 'bottom-left'
}
export function SDKPipelineSidebar({ fabPosition = 'bottom-right' }: SDKPipelineSidebarProps) {
const [isMobileOpen, setIsMobileOpen] = useState(false)
const [isDesktopCollapsed, setIsDesktopCollapsed] = useState(true) // Start collapsed
const { completionPercentage } = useSDK()
// Load collapsed state from localStorage
useEffect(() => {
const stored = localStorage.getItem('sdk-pipeline-sidebar-collapsed')
if (stored !== null) {
setIsDesktopCollapsed(stored === 'true')
}
}, [])
// Save collapsed state to localStorage
const toggleDesktopSidebar = () => {
const newState = !isDesktopCollapsed
setIsDesktopCollapsed(newState)
localStorage.setItem('sdk-pipeline-sidebar-collapsed', String(newState))
}
// Close drawer on escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsMobileOpen(false)
setIsDesktopCollapsed(true)
}
}
window.addEventListener('keydown', handleEscape)
return () => window.removeEventListener('keydown', handleEscape)
}, [])
// Prevent body scroll when drawer is open
useEffect(() => {
if (isMobileOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.body.style.overflow = ''
}
}, [isMobileOpen])
const fabPositionClasses = fabPosition === 'bottom-right'
? 'right-4 bottom-6'
: 'left-4 bottom-6'
const progressCircle = (
<svg className="absolute inset-0 w-full h-full -rotate-90" viewBox="0 0 56 56">
<circle cx="28" cy="28" r="26" fill="none" stroke="rgba(255,255,255,0.3)" strokeWidth="2" />
<circle
cx="28" cy="28" r="26" fill="none" stroke="white" strokeWidth="2"
strokeDasharray={`${(completionPercentage / 100) * 163.36} 163.36`}
strokeLinecap="round"
/>
</svg>
)
return (
<>
{/* Desktop: Fixed Sidebar (when expanded) */}
{!isDesktopCollapsed && (
<div className="hidden xl:block fixed right-6 top-24 w-72 z-10">
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-slate-200 dark:border-gray-700 overflow-hidden">
{/* Header with close button */}
<div className="px-4 py-3 bg-gradient-to-r from-purple-50 to-indigo-50 dark:from-purple-900/20 dark:to-indigo-900/20 border-b border-slate-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-purple-600 dark:text-purple-400"><PipelineIcon /></span>
<div>
<span className="font-semibold text-slate-700 dark:text-slate-200 text-sm">SDK Pipeline</span>
<span className="ml-2 text-xs text-purple-600 dark:text-purple-400">{completionPercentage}%</span>
</div>
</div>
<button
onClick={toggleDesktopSidebar}
className="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 rounded-lg hover:bg-slate-100 dark:hover:bg-gray-700 transition-colors"
aria-label="Sidebar einklappen"
title="Einklappen"
>
<CloseIcon />
</button>
</div>
</div>
{/* Content */}
<div className="p-3 max-h-[calc(100vh-200px)] overflow-y-auto">
<SidebarContent onNavigate={() => {}} />
</div>
</div>
</div>
)}
{/* Desktop: FAB (when collapsed) */}
{isDesktopCollapsed && (
<button
onClick={toggleDesktopSidebar}
className="hidden xl:flex fixed right-6 bottom-6 z-40 w-14 h-14 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-full shadow-lg hover:shadow-xl transition-all items-center justify-center group"
aria-label="SDK Pipeline Navigation oeffnen"
title="Pipeline anzeigen"
>
<PipelineIcon />
{progressCircle}
</button>
)}
{/* Mobile/Tablet: FAB */}
<button
onClick={() => setIsMobileOpen(true)}
className={`xl:hidden fixed ${fabPositionClasses} z-40 w-14 h-14 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-full shadow-lg hover:shadow-xl transition-all flex items-center justify-center group`}
aria-label="SDK Pipeline Navigation oeffnen"
>
<PipelineIcon />
{progressCircle}
</button>
{/* Mobile/Tablet: Drawer Overlay */}
{isMobileOpen && (
<div className="xl:hidden fixed inset-0 z-50">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity"
onClick={() => setIsMobileOpen(false)}
/>
{/* Drawer */}
<div className="absolute right-0 top-0 bottom-0 w-80 max-w-[85vw] bg-white dark:bg-gray-900 shadow-2xl transform transition-transform animate-slide-in-right">
{/* Drawer Header */}
<div className="flex items-center justify-between px-4 py-4 border-b border-slate-200 dark:border-gray-700 bg-gradient-to-r from-purple-50 to-indigo-50 dark:from-purple-900/20 dark:to-indigo-900/20">
<div className="flex items-center gap-2">
<span className="text-purple-600 dark:text-purple-400"><PipelineIcon /></span>
<div>
<span className="font-semibold text-slate-700 dark:text-slate-200">SDK Pipeline</span>
<span className="ml-2 text-sm text-purple-600 dark:text-purple-400">{completionPercentage}%</span>
</div>
</div>
<button
onClick={() => setIsMobileOpen(false)}
className="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 rounded-lg hover:bg-slate-100 dark:hover:bg-gray-800 transition-colors"
aria-label="Schliessen"
>
<CloseIcon />
</button>
</div>
{/* Drawer Content */}
<div className="p-4 overflow-y-auto max-h-[calc(100vh-80px)]">
<SidebarContent onNavigate={() => setIsMobileOpen(false)} />
</div>
</div>
</div>
)}
{/* CSS for slide-in animation */}
<style jsx>{`
@keyframes slide-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
.animate-slide-in-right {
animation: slide-in-right 0.2s ease-out;
}
`}</style>
</>
)
}
export default SDKPipelineSidebar