File

src/components/sq-overlay/sq-overlay.component.ts

Deprecated

Use `SqOverlayBaseComponent` or `SqModalService.openOverlay()` instead. This component will be removed in a future version.

Description

Represents an overlay component, an abstraction with differente style but still a modal.

This component will be removed in a future version.

Example :
<sq-overlay [open]="isOverlayOpen" overlayDirection="right" (overlayClose)="onOverlayClose()">
  <ng-template #headerTemplate>
    <h2>Overlay Header</h2>
  </ng-template>
  <ng-template #footerTemplate>
    Footer
  </ng-template>
  <div>
    <!-- Your content here -->
  </div>
</sq-overlay>
<button (click)='isOverlayOpen = true'>Open Modal</button>

Implements

OnChanges OnDestroy

Metadata

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(documentImported: Document, router: Router, getWindow: GetWindow)

Constructs an instance of SqOverlayComponent.

Parameters :
Name Type Optional Description
documentImported Document No
  • The injected Document object for DOM manipulation.
router Router No
  • The Router service for programmatic navigation.
getWindow GetWindow No
  • The GetWindow service for safely accessing the window object.

Inputs

backdrop
Type : string
Default value : 'static'

Specifies the behavior of the backdrop when clicked.

bodyColor
Type : string
Default value : 'var(--background_secondary)'

The background color of the overlay body.

bodyPadding
Type : string
Default value : '2rem'

The padding applied to the overlay body.

borderless
Type : boolean
Default value : false

Determines whether the overlay has a border.

footerColor
Type : string
Default value : 'var(--background_secondary)'

The background color of the overlay footer.

headerColor
Type : string
Default value : 'var(--background_secondary)'

The background color of the overlay header.

headerItemsColor
Type : string
Default value : ''

The text color of items within the overlay header.

id
Type : string
Default value : `overlay-random-id-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`

A unique identifier for the overlay.

open
Type : boolean

Indicates whether the overlay is open or closed.

overlayDirection
Type : "right" | "left"
Default value : 'right'

The direction in which the overlay slides in when opened.

showClose
Type : boolean
Default value : true

Determines whether the close button is shown.

width
Type : string
Default value : '475px'

The width of the overlay.

Outputs

leftPress
Type : EventEmitter<void>

Emits an event when the left arrow key is pressed.

overlayClose
Type : EventEmitter<void>

Emits an event when the overlay is closed.

rightPress
Type : EventEmitter<void>

Emits an event when the right arrow key is pressed.

Methods

doCssWidth
doCssWidth()

Applies CSS styles to set the width of the overlay.

Returns : void
events
events(key: string)

Handles specific keyboard events.

Parameters :
Name Type Optional Description
key string No
  • The key code of the pressed key.
Returns : void
Async ngOnChanges
ngOnChanges(changes: SimpleChanges)

Lifecycle hook that detects changes to the 'open' input property and handles modal behavior accordingly.

Parameters :
Name Type Optional Description
changes SimpleChanges No
  • The changes detected in the component's input properties.
Returns : any
ngOnDestroy
ngOnDestroy()

Performs actions before the component is destroyed.

Returns : void
observeRouter
observeRouter()

Function that init the routerObservable.

Returns : void
onKeydown
onKeydown(event: KeyboardEvent)

Handles keyboard events for the modal component.

Parameters :
Name Type Optional Description
event KeyboardEvent No
  • The keyboard event object.
Returns : void
removeOverlayFromBody
removeOverlayFromBody()

Removes the overlay element from document body.

Returns : void
toCloseOverlay
toCloseOverlay()

Closes the overlay logic.

Returns : void
undoCssWidth
undoCssWidth()

Removes the CSS styles that set the width of the overlay.

Returns : void

Properties

document
Type : Document

A reference to the Document object.

Public documentImported
Type : Document
Decorators :
@Inject(DOCUMENT)
- The injected Document object for DOM manipulation.
finishOpening
Default value : false

Indicates whether the overlay has finished opening.

Optional footerTemplate
Type : TemplateRef<ElementRef> | null
Default value : null
Decorators :
@ContentChild('footerTemplate')

A reference to the footer template.

Public getWindow
Type : GetWindow
- The GetWindow service for safely accessing the window object.
hasFooter
Default value : false

Indicates whether the overlay has a footer.

hasHeader
Default value : false

Indicates whether the overlay has a header.

Optional headerTemplate
Type : TemplateRef<ElementRef> | null
Default value : null
Decorators :
@ContentChild('headerTemplate')

A reference to the header template.

localized
Type : URL

Indicates the origin path from overlay.

modalNumber
Type : number
Default value : 0

The number of modal elements.

modals
Type : HTMLCollectionOf<Element> | undefined

A collection of modal elements.

overlay
Type : ElementRef | null
Default value : null
Decorators :
@ViewChild('overlay')

A reference to the overlay element.

Public router
Type : Router
- The Router service for programmatic navigation.
routerObservable
Type : Subscription

A subscription to the router change url.

scrollY
Default value : this.getWindow?.window()?.scrollY

Indicates the scroll position of the window.

styleId
Default value : `overlay-style-random-id-${new Date().getTime()}-${Math.random().toString(36).substring(7)}`

A unique style identifier.

import { DOCUMENT, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
import {
  Component,
  ContentChild,
  ElementRef,
  EventEmitter,
  Inject,
  Input,
  OnChanges,
  OnDestroy,
  Output,
  SimpleChanges,
  TemplateRef,
  ViewChild,
} from '@angular/core';
import { sleep } from '../../helpers/sleep.helper';
import { NavigationStart, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { GetWindow } from '../../helpers/window.helper';
import { SqClickOutsideDirective } from '../../directives/sq-click-outside/sq-click-outside.directive';

/**
 * Represents an overlay component, an abstraction with differente style but still a modal.
 *
 * @deprecated Use `SqOverlayBaseComponent` or `SqModalService.openOverlay()` instead.
 * This component will be removed in a future version.
 *
 * @example
 * <sq-overlay [open]="isOverlayOpen" overlayDirection="right" (overlayClose)="onOverlayClose()">
 *   <ng-template #headerTemplate>
 *     <h2>Overlay Header</h2>
 *   </ng-template>
 *   <ng-template #footerTemplate>
 *     Footer
 *   </ng-template>
 *   <div>
 *     <!-- Your content here -->
 *   </div>
 * </sq-overlay>
 * <button (click)='isOverlayOpen = true'>Open Modal</button>
 *
 * @implements {OnChanges}
 * @implements {OnDestroy}
 */
@Component({
  selector: 'sq-overlay',
  templateUrl: './sq-overlay.component.html',
  styleUrls: ['./sq-overlay.component.scss'],
  standalone: true,
  imports: [NgClass, NgStyle, NgTemplateOutlet, SqClickOutsideDirective],
})
export class SqOverlayComponent implements OnChanges, OnDestroy {
  /**
   * A unique identifier for the overlay.
   */
  @Input() id = `overlay-random-id-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`;

  /**
   * Indicates whether the overlay is open or closed.
   *
   */
  @Input() open?: boolean;

  /**
   * The direction in which the overlay slides in when opened.
   *
   */
  @Input() overlayDirection: 'right' | 'left' = 'right';

  /**
   * The width of the overlay.
   *
   */
  @Input() width = '475px';

  /**
   * Determines whether the overlay has a border.
   *
   */
  @Input() borderless = false;

  /**
   * The background color of the overlay header.
   *
   */
  @Input() headerColor = 'var(--background_secondary)';

  /**
   * The text color of items within the overlay header.
   *
   */
  @Input() headerItemsColor = '';

  /**
   * The background color of the overlay footer.
   *
   */
  @Input() footerColor = 'var(--background_secondary)';

  /**
   * The background color of the overlay body.
   *
   */
  @Input() bodyColor = 'var(--background_secondary)';

  /**
   * Determines whether the close button is shown.
   *
   */
  @Input() showClose = true;

  /**
   * Specifies the behavior of the backdrop when clicked.
   *
   */
  @Input() backdrop = 'static';

  /**
   * The padding applied to the overlay body.
   *
   */
  @Input() bodyPadding = '2rem';

  /**
   * Emits an event when the overlay is closed.
   *
   */
  @Output() overlayClose: EventEmitter<void> = new EventEmitter();

  /**
   * Emits an event when the left arrow key is pressed.
   *
   */
  @Output() leftPress: EventEmitter<void> = new EventEmitter();

  /**
   * Emits an event when the right arrow key is pressed.
   *
   */
  @Output() rightPress: EventEmitter<void> = new EventEmitter();

  /**
   * A reference to the overlay element.
   *
   */
  @ViewChild('overlay') overlay: ElementRef | null = null;

  /**
   * A reference to the header template.
   *
   */
  @ContentChild('headerTemplate') headerTemplate?: TemplateRef<ElementRef> | null = null;

  /**
   * A reference to the footer template.
   *
   */
  @ContentChild('footerTemplate') footerTemplate?: TemplateRef<ElementRef> | null = null;

  /**
   * A collection of modal elements.
   *
   */
  modals: HTMLCollectionOf<Element> | undefined;

  /**
   * The number of modal elements.
   *
   */
  modalNumber = 0;

  /**
   * Indicates whether the overlay has a header.
   *
   */
  hasHeader = false;

  /**
   * Indicates whether the overlay has a footer.
   *
   */
  hasFooter = false;

  /**
   * A reference to the Document object.
   *
   */
  document: Document;

  /**
   * A unique style identifier.
   *
   */
  styleId = `overlay-style-random-id-${new Date().getTime()}-${Math.random().toString(36).substring(7)}`;

  /**
   * Indicates whether the overlay has finished opening.
   *
   */
  finishOpening = false;

  /**
   * Indicates the origin path from overlay.
   *
   */
  localized: URL;

  /**
   * A subscription to the router change url.
   */
  routerObservable!: Subscription;

  /**
   * Indicates the scroll position of the window.
   */
  scrollY = this.getWindow?.window()?.scrollY;

  /**
   * Constructs an instance of SqOverlayComponent.
   * @constructor
   * @param {Document} documentImported - The injected Document object for DOM manipulation.
   * @param {Router} router - The Router service for programmatic navigation.
   * @param {GetWindow} getWindow - The GetWindow service for safely accessing the window object.
   */
  constructor(
    @Inject(DOCUMENT) public documentImported: Document,
    public router: Router,
    public getWindow: GetWindow
  ) {
    this.onKeydown = this.onKeydown.bind(this);
    this.document = documentImported || document;
    this.localized = new URL(this.getWindow.href());
  }

  /**
   * Lifecycle hook that detects changes to the 'open' input property and handles modal behavior accordingly.
   *
   * @param changes - The changes detected in the component's input properties.
   */
  async ngOnChanges(changes: SimpleChanges) {
    if (changes.hasOwnProperty('width') && this.open) {
      this.doCssWidth();
    }
    if (changes.hasOwnProperty('open')) {
      const overlay = this.overlay;
      if (overlay) {
        const body = this.document.getElementsByTagName('body')[0];
        const backdrop = this.document.getElementById('modal-backdrop') || this.document.createElement('div');
        if (this.open) {
          this.scrollY = this.getWindow?.window()?.scrollY;
          body.appendChild(overlay.nativeElement);
          this.observeRouter();
          this.doCssWidth();
          this.hasFooter = !!this.footerTemplate;
          this.hasHeader = !!this.headerTemplate;
          body.classList.add('block');
          overlay.nativeElement.style.display = 'flex';
          this.getWindow?.window()?.addEventListener('keydown', this.onKeydown);
          this.modals = this.document.getElementsByClassName('modal open');
          await sleep(10);
          this.modalNumber = this.modals?.length || 0;
          if (this.modalNumber <= 1) {
            backdrop.setAttribute('id', 'modal-backdrop');
            backdrop.setAttribute('class', 'modal-backdrop show');
            body.appendChild(backdrop);
          } else if (this.modalNumber > 1) {
            overlay.nativeElement.style.zIndex = 1060 + this.modalNumber + 1;
            backdrop.setAttribute('style', `z-index: ${1060 + this.modalNumber};`);
          }
          this.finishOpening = true;
        } else {
          this.removeOverlayFromBody();
        }
      }
    }
  }

  /**
   * Performs actions before the component is destroyed.
   */
  ngOnDestroy(): void {
    this.routerObservable?.unsubscribe();
  }

  /**
   * Function that init the routerObservable.
   */
  observeRouter() {
    this.routerObservable = this.router.events.subscribe(async event => {
      if (event instanceof NavigationStart) {
        const destinationRoute = new URL(event.url, this.localized.origin);
        if (this.localized.origin + this.localized.pathname !== destinationRoute.origin + destinationRoute.pathname) {
          this.removeOverlayFromBody();
          await sleep(1000);
        }
      }
    });
  }

  /**
   * Removes the overlay element from document body.
   */
  removeOverlayFromBody() {
    const body = this.document.getElementsByTagName('body')[0];
    if (this.modalNumber <= 1) {
      body?.classList?.remove('block');
      if (window.scrollY !== this.scrollY) {
        if (this.scrollY) this.getWindow?.window()?.scrollTo(0, this.scrollY);
      }
    }
    const backdrop = this.document.getElementById('modal-backdrop');
    const overlay: any = this.document.getElementById(this.id);
    this.overlayClose.emit();
    this.finishOpening = false;
    this.undoCssWidth();
    overlay?.parentNode?.removeChild(overlay);
    if (this.modalNumber === 2) {
      backdrop?.removeAttribute('style');
    } else if (this.modalNumber <= 1) {
      backdrop?.parentNode?.removeChild(backdrop);
    }
    window.removeEventListener('keydown', this.onKeydown);
  }

  /**
   * Applies CSS styles to set the width of the overlay.
   */
  doCssWidth() {
    const css = `
      .overlay.open .modal-dialog.opened {
        width: ${this.width};
      }
    `;
    const head = this.document.getElementsByTagName('head')[0];
    let style = this.document.getElementById(this.styleId);
    if (!style) {
      style = this.document.createElement('style');
      style.setAttribute('id', this.styleId);
      style.appendChild(this.document.createTextNode(css));
      head.appendChild(style);
    } else {
      style.innerHTML = '';
      style.appendChild(this.document.createTextNode(css));
    }
  }

  /**
   * Removes the CSS styles that set the width of the overlay.
   */
  undoCssWidth() {
    const style = this.document.getElementById(this.styleId);
    if (style?.parentNode) {
      style.parentNode.removeChild(style);
    }
  }

  /**
   * Handles keyboard events for the modal component.
   *
   * @param event - The keyboard event object.
   */
  onKeydown(event: KeyboardEvent) {
    if (this.open) {
      this.modals = this.document.getElementsByClassName('modal open');
      if (this.modals?.length && this.modals[this.modals.length - 1]?.id === this.id) {
        this.events(event.key);
      }
    }
  }

  /**
   * Handles specific keyboard events.
   *
   * @param key - The key code of the pressed key.
   */
  events(key: string) {
    switch (key) {
      case 'Escape':
        this.overlayClose.emit();
        break;
      case 'ArrowLeft':
        this.leftPress.emit();
        break;
      case 'ArrowRight':
        this.rightPress.emit();
        break;
    }
  }

  /**
   * Closes the overlay logic.
   */
  toCloseOverlay() {
    if (this.overlay && this.open) {
      const body = this.document.getElementsByTagName('body')[0];
      const backdrop = this.document.getElementById('modal-backdrop') || this.document.createElement('div');
      this.overlayClose.emit();
      this.overlay.nativeElement.style.display = 'none';
      if (backdrop.parentNode && this.modalNumber <= 1) {
        backdrop.parentNode.removeChild(backdrop);
        body.classList.remove('block');
      }
      window.removeEventListener('keydown', this.onKeydown);
    }
  }
}
<div [id]="id" class="modal overlay {{ overlayDirection }}" [ngClass]="{ open: open }" #overlay>
  <div
    class="modal-dialog {{ overlayDirection }}"
    [ngStyle]="{ 'background-color': headerColor }"
    [ngClass]="{ opened: finishOpening }"
    [clickOutsideEnabled]="!!(open && backdrop !== 'static' && overlay)"
    (clickOutside)="toCloseOverlay()"
  >
    <div class="modal-content scrollbar">
      <div
        class="modal-header"
        [ngClass]="{
          'without-header': !hasHeader,
          borderless: borderless,
        }"
        [ngStyle]="{
          'background-color': headerColor,
        }"
      >
        @if (headerTemplate) {
          <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
        }

        @if (showClose) {
          <button
            id="close-button"
            type="button"
            class="close button-close"
            aria-label="Close"
            (click)="overlayClose.emit()"
          >
            <i class="fa-solid fa-xmark" [ngStyle]="{ color: headerItemsColor }"></i>
          </button>
        }
      </div>

      <div
        class="modal-body scrollbar"
        [ngClass]="{
          'without-footer': !hasFooter,
        }"
        [ngStyle]="{
          'background-color': bodyColor,
          padding: bodyPadding,
        }"
      >
        <ng-content></ng-content>
      </div>

      @if (hasFooter) {
        <div
          class="modal-footer"
          [ngClass]="{
            borderless: borderless,
          }"
          [ngStyle]="{
            'background-color': footerColor,
          }"
        >
          @if (footerTemplate) {
            <ng-container *ngTemplateOutlet="footerTemplate"></ng-container>
          }
        </div>
      }
    </div>
  </div>
</div>

./sq-overlay.component.scss

::ng-deep {
  .overlay {
    padding: 0;
    border-radius: 0px;
    &.left {
      justify-content: flex-start;
    }
    &.right {
      justify-content: flex-end;
    }
    .modal-dialog {
      width: 300px;
      max-width: 100vw;
      height: 100%;
      margin: 0;
      position: fixed;
      transition:
        opacity 0.3s linear,
        left 0.3s ease-out,
        right 0.3s ease-out,
        width 0.3s ease-out,
        height 0.3s ease-out !important;
      transform: translate3d(0%, 0, 0) !important;
      .modal-content {
        height: 100%;
        border-radius: 0;
        overflow-y: auto;
        border: none;
        background-color: transparent;
        .modal-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          height: 60px;
          border-radius: 0;
          overflow: hidden;
          padding: 1rem 2rem;
          &.without-header {
            padding: 0 0.08rem;
            border: none;
            justify-content: flex-end;
            .button-close {
              margin: 0;
              padding: 0;
            }
          }
          .button-close {
            max-height: 30px;
            line-height: 30px;
            opacity: 0.7 !important;
            transition: var(--transition);
            z-index: 1;
            span {
              font-size: 2.1rem;
              font-weight: 300;
              transition: var(--transition);
            }
            &:hover,
            &:focus {
              outline: none;

              span {
                font-weight: 500;
              }
            }
          }
        }
        .modal-body {
          height: calc(100vh - 112px);
          overflow-y: auto;
          overflow-x: hidden;
          &.without-footer {
            height: calc(100vh - 52px);
          }
        }
        .modal-footer {
          height: 60px;
          border-radius: 0;
          overflow: hidden;
          padding: 0.5rem 2rem;
        }
        .modal-footer > * {
          margin: 0;
        }
        .borderless {
          border-color: transparent;
        }
      }
    }
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""