feat: Phase 5 — Sidebar pinned section dual collapse + show more/less
- Header collapse hides entire section, resets to 5 items on re-expand - List truncation: show first 5, "Show more (N)" expands to all - Clicking a flow auto-collapses back to 5 - Smooth max-height CSS transition (250ms ease-out) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,51 +10,89 @@ interface PinnedFlowsSectionProps {
|
|||||||
onUnpin: (treeId: string) => void
|
onUnpin: (treeId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TRUNCATE_COUNT = 5
|
||||||
|
|
||||||
export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps) {
|
export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
const [showAll, setShowAll] = useState(false)
|
||||||
|
|
||||||
|
const handleToggleCollapse = () => {
|
||||||
|
if (collapsed) {
|
||||||
|
setShowAll(false) // Reset to truncated on re-expand
|
||||||
|
}
|
||||||
|
setCollapsed(!collapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleFlows = showAll ? flows : flows.slice(0, TRUNCATE_COUNT)
|
||||||
|
const hasMore = flows.length > TRUNCATE_COUNT
|
||||||
|
|
||||||
|
const handleFlowClick = (flow: PinnedFlow) => {
|
||||||
|
setShowAll(false) // Collapse back to 5 on navigation
|
||||||
|
navigate(getTreeNavigatePath(flow.tree_id, flow.tree_type))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
onClick={handleToggleCollapse}
|
||||||
className="flex w-full items-center gap-1 px-3 mb-1 font-heading text-[0.6875rem] font-bold uppercase tracking-[0.04em] text-muted-foreground hover:text-foreground transition-colors"
|
className="flex w-full items-center gap-1 px-3 mb-1 font-heading text-[0.6875rem] font-bold uppercase tracking-[0.04em] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{collapsed ? <ChevronRight size={12} /> : <ChevronDown size={12} />}
|
{collapsed ? <ChevronRight size={12} /> : <ChevronDown size={12} />}
|
||||||
Pinned
|
Pinned
|
||||||
|
{flows.length > 0 && (
|
||||||
|
<span className="ml-auto text-[0.625rem] font-normal">{flows.length}</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{!collapsed && (
|
<div
|
||||||
<div className="space-y-0.5 max-h-[280px] overflow-y-auto">
|
className="overflow-hidden transition-[max-height] duration-[250ms] ease-out"
|
||||||
|
style={{
|
||||||
|
maxHeight: collapsed ? 0 : showAll
|
||||||
|
? `${flows.length * 36 + 40}px`
|
||||||
|
: `${Math.min(flows.length, TRUNCATE_COUNT) * 36 + 40}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="space-y-0.5">
|
||||||
{flows.length === 0 ? (
|
{flows.length === 0 ? (
|
||||||
<p className="px-3 py-2 text-xs text-muted-foreground">
|
<p className="px-3 py-2 text-xs text-muted-foreground">
|
||||||
Pin your most-used flows here
|
Pin your most-used flows here
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
flows.map(flow => (
|
<>
|
||||||
<button
|
{visibleFlows.map(flow => (
|
||||||
key={flow.tree_id}
|
<button
|
||||||
onClick={() => navigate(getTreeNavigatePath(flow.tree_id, flow.tree_type))}
|
key={flow.tree_id}
|
||||||
onContextMenu={(e) => {
|
onClick={() => handleFlowClick(flow)}
|
||||||
e.preventDefault()
|
onContextMenu={(e) => {
|
||||||
onUnpin(flow.tree_id)
|
e.preventDefault()
|
||||||
}}
|
onUnpin(flow.tree_id)
|
||||||
className={cn(
|
}}
|
||||||
'group flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
className={cn(
|
||||||
'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
|
'group flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
||||||
)}
|
'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
|
||||||
title={`${flow.tree_name} (right-click to unpin)`}
|
)}
|
||||||
>
|
title={`${flow.tree_name} (right-click to unpin)`}
|
||||||
<span className="text-sm shrink-0">
|
>
|
||||||
{flow.tree_type === 'procedural' ? '📋' : flow.tree_type === 'maintenance' ? '🛠️' : '🔧'}
|
<span className="text-sm shrink-0">
|
||||||
</span>
|
{flow.tree_type === 'procedural' ? '📋' : flow.tree_type === 'maintenance' ? '🛠️' : '🔧'}
|
||||||
<span className="truncate flex-1 text-left">{flow.tree_name}</span>
|
</span>
|
||||||
<Pin size={12} className="shrink-0 opacity-0 group-hover:opacity-40 transition-opacity" />
|
<span className="truncate flex-1 text-left">{flow.tree_name}</span>
|
||||||
</button>
|
<Pin size={12} className="shrink-0 opacity-0 group-hover:opacity-40 transition-opacity" />
|
||||||
))
|
</button>
|
||||||
|
))}
|
||||||
|
{hasMore && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
className="w-full px-3 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors text-left"
|
||||||
|
>
|
||||||
|
{showAll ? 'Show less' : `Show more (${flows.length})`}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user