I am working on an Angular 17 project with a Tailwind CSS-styled sidebar.
The expand/collapse functionality works as intended, but the animation is not as smooth as I would like. There is a noticeable “jump” in the animation, which disrupts the smooth transition I aim for. Ideally, the animation should smoothly transition from the top to bottom (and vice versa) without glitches.
Showcase of the problem: https://i.imgur.com/4RpEwYv.gif
My HTML Code:
<div class="flex h-screen bg-white">
<aside class="w-72 bg-gray-900 shadow-lg flex flex-col">
<!-- Navigation -->
<nav class="p-4 pl-0 overflow-y-auto">
@for (group of navigation; track $index) {
<div class="mb-2" [class.pb-3]="$index != navigation.length - 1">
<div class="flex flex-col">
<div class="flex items-center justify-between pl-2 pr-0 cursor-pointer gap-2 w-[104%]" (click)="toggleGroup(group.category)">
<div>
<h3 class="px-0 mb-0 text-xs font-semibold text-gray-500 uppercase tracking-wider text-{{group.color}}-gradient">
{{ group.category }}
</h3>
<p class="text-xs text-gray-500 mb-0">{{ group.description }}</p>
</div>
</div>
</div>
<div class="mt-2 space-y-1 overflow-hidden" [@expandCollapse]="expandedGroups[group.category] ? 'expanded' : 'collapsed'">
<div class="space-y-1">
<!-- Show only 3 pages if group is not expanded & be able to expand it -->
@for (page of (expandedGroups[group.category] ? group.pages : group.pages.slice(0, 3)); track $index) {
<a [routerLink]="page.redirect_url"
class="flex items-center p-2 !text-slate-400 hover:bg-gray-500 rounded-lg transition-none hover:!text-white">
<div class="w-6 flex-shrink-0">
<fa-icon [icon]="page.icon"></fa-icon>
</div>
<span class="ml-3 text-[0.9rem]">{{ page.title }}</span>
</a>
}
</div>
</div>
</div>
}
</nav>
</aside>
</div>
TypeScript Code with Angular animation part:
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
NgOptimizedImage,
RouterLink,
FaIconComponent,
],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss',
animations: [
trigger('expandCollapse', [
state('collapsed', style({
height: '86px',
overflow: 'hidden',
opacity: 1
})),
state('expanded', style({
height: '*',
overflow: 'hidden',
opacity: 1
})),
transition('expanded => collapsed', [
style({ height: '*' }),
animate('300ms cubic-bezier(0.4, 0.0, 0.2, 1)',
style({ height: '96px' })
)
]),
transition('collapsed => expanded', [
style({ height: '96px' }),
animate('300ms cubic-bezier(0.4, 0.0, 0.2, 1)',
style({ height: '*' })
)
])
])
]
})
export class DashboardComponent {
// it is filled, but commented out
navigation = []
expandedGroups: { [key: string]: boolean } = {};
toggleGroup(category: string) {
this.expandedGroups[category] = !this.expandedGroups[category];
}
constructor() {
this.navigation.forEach(group => {
this.expandedGroups[group.category] = false;
});
}