Skip to content

Commit

Permalink
feat: Rework flyout component
Browse files Browse the repository at this point in the history
* use Angular Animations to animate flyout
* support additional placement options
* fix alignment issues with nav-bar
  • Loading branch information
jrassa committed Oct 10, 2024
1 parent 5117bba commit 0d4d7ef
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 86 deletions.
14 changes: 11 additions & 3 deletions src/app/common/flyout/flyout.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
<span class="fa-solid fa-lg fa-angle-down ms-2" data-inline="true"></span>
</button>

<div class="flyout-content" #flyoutContentContainer>
<ng-content />
</div>
@if (placement().startsWith('top') || placement().startsWith('bottom')) {
<div class="flyout-content" [@flyInOutVertical]="isOpen()">
<ng-container *ngTemplateOutlet="content" />
</div>
} @else {
<div class="flyout-content" [@flyInOutHorizontal]="isOpen()">
<ng-container *ngTemplateOutlet="content" />
</div>
}
</section>

<ng-template #content><ng-content /></ng-template>
120 changes: 86 additions & 34 deletions src/app/common/flyout/flyout.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,87 @@
$flyout-border-top: 8px !default;
$flyout-box-shadow: 0 0.25rem 0.5rem rgba(var(--bs-body-bg-rgb), 0.2) !default;

$flyout-right-top: 24px !default;
$flyout-top-top: 20px !default;
$flyout-bottom-bottom: 20px * 2 !default;
$flyout-top: 24px !default;
$flyout-bottom: 24px * 2 !default;

:host {
position: fixed;
right: 0;
top: $flyout-right-top;
top: $flyout-top;
@include transition(left 0.15s ease-in-out, right 0.15s ease-in-out);
z-index: $zindex-popover;

&[placement='left'] {
&[placement='right-bottom'] {
right: 0;
top: auto;
bottom: $flyout-bottom;
left: var(--nav-open-width);
}

&[placement='left-top']
{
right: auto;
left: $nav-open-width;
left: var(--nav-open-width);
}

&[placement='left-bottom'] {
right: auto;
left: var(--nav-open-width);
top: auto;
bottom: $flyout-bottom;
}

&[placement='top'] {
&[placement='top-left'] {
right: auto;
top: $flyout-top-top;
left: $nav-open-width;
top: $flyout-top;
left: var(--nav-open-width);
}

&[placement='bottom'] {
&[placement='top-right'] {
top: $flyout-top;
left: var(--nav-open-width);
}

&[placement='bottom-left'] {
right: auto;
top: auto;
bottom: $flyout-bottom-bottom;
left: $nav-open-width;
bottom: $flyout-bottom;
left: var(--nav-open-width);
}

&[placement='bottom-right'] {
right: 0;
top: auto;
bottom: $flyout-bottom;
left: var(--nav-open-width);
}
}

.navbar-close {
:host[placement='left'],
:host[placement='top'],
:host[placement='bottom'] {
left: $nav-closed-width;
:host[placement='left-top'],
:host[placement='left-bottom'],
:host[placement='top-left'],
:host[placement='bottom-left'] {
left: var(--nav-closed-width);
}
}

.flyout {
background: var(--bs-body-bg);

&.flyout-left {
&.flyout-left-top,
&.flyout-left-bottom, {
flex-direction: row-reverse;
}

&.flyout-top {

&.flyout-top-left,
&.flyout-top-right {
flex-direction: column-reverse;
}

&.flyout-bottom {
&.flyout-bottom-left,
&.flyout-bottom-right {
flex-direction: column;

.flyout-btn {
Expand All @@ -79,41 +110,62 @@ $flyout-bottom-bottom: 20px * 2 !default;

.flyout-btn {
position: absolute;
transform: rotate(90deg);
transform-origin: 0 0;

box-shadow: none;
border-radius: 0 0 var(--bs-border-radius) var(--bs-border-radius);

.flyout-left & {
.flyout-left-top & {
transform: rotate(-90deg);
transform-origin: 100% 0;
}

.flyout-top & {
.flyout-left-bottom & {
bottom: 0;
transform: translate(100%, 100%) rotate(-90deg);
transform-origin: 0 0;
}

.flyout-right-top & {
transform: rotate(90deg);
transform-origin: 0 0;
}

.flyout-right-bottom & {
bottom: 0;
transform: translate(-100%, 100%) rotate(90deg);
transform-origin: top right;
}

.flyout-top-left & {
transform: translateY(100%);
}

.flyout-bottom & {
.flyout-top-right & {
right: 0;
transform: translateY(100%);
}

.flyout-bottom-left & {
transform: translateY(-100%);
}

.flyout-bottom-right & {
right: 0;
transform: translateY(-100%)
}

}

.flyout-content {
@include box-shadow(var(--bs-box-shadow));

overflow: auto;
border-top: $flyout-border-top solid var(--bs-primary);
max-height: calc(100vh - 80px);
width: 0;

.flyout-top &,
.flyout-bottom & {
border-top: none;
border-left: $flyout-border-top solid var(--bs-primary);
.flyout-top-left &,
.flyout-top-right &,
.flyout-bottom-left &,
.flyout-bottom-right & {
width: auto;
height: 0;
max-height: calc(100vh - 100px);
}

@include transition(width 1s ease-in-out, height 1s ease-in-out);
}
72 changes: 29 additions & 43 deletions src/app/common/flyout/flyout.component.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,44 @@
import { NgClass } from '@angular/common';
import {
Component,
ElementRef,
Renderer2,
contentChild,
inject,
input,
signal,
viewChild
} from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { Component, input, signal } from '@angular/core';

@Component({
selector: 'app-flyout',
templateUrl: './flyout.component.html',
styleUrls: ['./flyout.component.scss'],
standalone: true,
imports: [NgClass]
imports: [NgClass, NgTemplateOutlet],
animations: [
trigger('flyInOutVertical', [
state('false', style({ height: '0' })),
transition('false => true', [animate('500ms ease-in', style({ height: '*' }))]),
transition('true => false', [animate('500ms ease-in', style({ height: '0' }))])
]),
trigger('flyInOutHorizontal', [
state('false', style({ width: '0' })),
transition('false => true', [animate('500ms ease-in', style({ width: '*' }))]),
transition('true => false', [animate('500ms ease-in', style({ width: '0' }))])
])
]
})
export class FlyoutComponent {
readonly #renderer = inject(Renderer2);

readonly container = viewChild.required<ElementRef>('flyoutContentContainer');
readonly content = contentChild.required<ElementRef>('flyoutContent');

readonly label = input('');
readonly placement = input<'left' | 'right' | 'top' | 'bottom'>('right');
readonly placement = input<
| 'top'
| 'bottom'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
>('right-top');

readonly isOpen = signal(false);

toggle() {
if (this.content() && this.container()) {
if (this.placement() === 'top' || this.placement() === 'bottom') {
if (this.isOpen()) {
this.#renderer.setStyle(this.container().nativeElement, 'height', 0);
} else {
this.#renderer.setStyle(
this.container().nativeElement,
'height',
`${this.content().nativeElement.clientHeight}px`
);
}
} else {
if (this.isOpen()) {
this.#renderer.setStyle(this.container().nativeElement, 'width', 0);
} else {
this.#renderer.setStyle(
this.container().nativeElement,
'width',
`${this.content().nativeElement.clientWidth}px`
);
}
}

this.isOpen.set(!this.isOpen());
}
this.isOpen.set(!this.isOpen());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DebugElement, signal } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Router } from '@angular/router';

import { APP_CONFIG } from '../../tokens';
Expand All @@ -27,7 +28,7 @@ describe('FeedbackFlyoutComponent', () => {

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [FeedbackFlyoutComponent],
imports: [FeedbackFlyoutComponent, NoopAnimationsModule],
providers: [
{ provide: Router, useValue: { url: 'test-url' } },
{ provide: APP_CONFIG, useValue: signal(mockConfigObject) },
Expand Down
7 changes: 3 additions & 4 deletions src/app/core/site-container/site-container.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<main class="site-content h-100 overflow-auto" id="app-content" cdkScrollable tabindex="-1">
<ng-content />
</main>
@if (isAuthenticated() && showFeedbackFlyout()) {
<app-feedback-flyout />
}
</site-navbar>
</section>

Expand All @@ -25,7 +28,3 @@
<div class="site-banner footer-banner" [innerHtml]="bannerHtml()"></div>
}
</footer>

@if (isAuthenticated() && showFeedbackFlyout()) {
<app-feedback-flyout />
}
3 changes: 2 additions & 1 deletion src/app/core/site-navbar/site-navbar.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ $nav-logo-text-font-size: 1.75rem !default;
$nav-popover-width: 192px !default;

:host {
--nav-open-width: $nav-open-width;
--nav-closed-width: $nav-closed-width;
display: flex;
height: 100%;

}

.navbar {
Expand Down

0 comments on commit 0d4d7ef

Please sign in to comment.