Search code examples
angulartypescriptprimengnav

How to add arrows for left and right navigations in primeng tab view in Angular 8?


I am looking to add left and right navigation arrows to support larger tabs size in nav.

I tried to use primeng as my application already has this library with Angular 8.

Is there any other library that supports this feature other than Angular Material?

enter image description here

I have created my example here.

https://stackblitz.com/edit/primeng-tabview-ngif-hluezs

Angular material example.

https://stackblitz.com/edit/angular-selecting-mattab-bihcd1

enter image description here


Solution

  • You can create your own directive to achieve this...

    Have the below css in your styles.css file

    .nav-wrapper {
      position: relative;
      overflow: hidden;
      padding: 0;
    }
    .nav-wrapper > ul {
      display: flex;
    }
    
    .nav-wrapper--scrollable {
      padding: 0 32px;
    }
    
    .nav-arrow {
      position: absolute;
      display: none;
      top: 0;
      bottom: 0;
      z-index: 1;
      border: 1px solid #2399e5;
      background: #2399e5;
      font: 14px/1 FontAwesome;
      color: #fff;
      cursor: pointer;
    }
    .nav-wrapper--scrollable .nav-arrow {
      display: block;
    }
    .nav-arrow:hover {
      border: 1px solid #1f89ce;
      background: #1f89ce;
    }
    
    .nav-arrow:before {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
    .nav-arrow--left {
      left: 0;
    }
    .nav-arrow--left:before {
      content: "\f053";
    }
    
    .nav-arrow--right {
      right: 0;
    }
    .nav-arrow--right:before {
      content: "\f054";
    }
    .nav-arrow--disabled,
    .nav-arrow--disabled:hover {
      background: #ccc;
      border: #ddd;
      cursor: default;
    }
    
    

    Create a directive

    import {
      ContentChildren,
      Directive,
      ElementRef,
      Input,
      NgZone,
      QueryList
    } from "@angular/core";
    import { TabPanel } from "primeng/tabview";
    import { fromEvent, interval, Subject } from "rxjs";
    import { delay, mergeMap, take, takeUntil } from "rxjs/operators";
    
    @Directive({
      selector: "[appTabScroller]"
    })
    export class TabScrollerDirective {
      @Input() arrowWidth = 30;
      @Input() shiftWidth = 25;
    
      @ContentChildren(TabPanel) tabPanels: QueryList<TabPanel>;
    
      private _container: HTMLElement;
      private _nav: HTMLElement;
    
      private _shift = 0;
      private _scrollable: boolean;
    
      private _leftArrow: HTMLElement;
      private _rightArrow: HTMLElement;
    
      private readonly _destroyed$ = new Subject<void>();
    
      constructor(private elRef: ElementRef, private zone: NgZone) {}
    
      get rightBorder() {
        return -(this._nav.scrollWidth - this._nav.offsetWidth);
      }
    
      ngAfterContentInit() {
        this.tabPanels.changes.pipe(takeUntil(this._destroyed$)).subscribe(() => {
          this.zone.onStable
            .asObservable()
            .pipe(take(1))
            .subscribe(() => this._refreshScroller());
        });
      }
    
      ngAfterViewInit() {
        this.zone.runOutsideAngular(() => this.init());
      }
    
      init() {
        this._nav = this.elRef.nativeElement.querySelector("[role=tablist]");
        this._container = wrap(this._nav, "nav-wrapper");
    
        this._initEvents();
    
        this._leftArrow = this._createArrow("left");
        this._rightArrow = this._createArrow("right");
    
        this._refreshScroller();
      }
    
      scroll(shift: number) {
        this._shift += shift;
    
        const rightBorder = this.rightBorder;
        if (this._shift < rightBorder) {
          this._shift = rightBorder;
        }
        if (this._shift >= 0) {
          this._shift = 0;
        }
    
        this._leftArrow.classList.toggle("nav-arrow--disabled", this._shift >= 0);
        this._rightArrow.classList.toggle(
          "nav-arrow--disabled",
          this._shift <= rightBorder
        );
    
        this._nav.style.transform = `translateX(${this._shift}px)`;
      }
    
      ngOnDestroy() {
        this._destroyed$.next();
        this._destroyed$.complete();
      }
    
      private _initEvents() {
        fromEvent(this._container, "mousewheel")
          .pipe(takeUntil(this._destroyed$))
          .subscribe((e: any) => this._onMouseWheel(e));
        // Firefox
        fromEvent(this._container, "DOMMouseScroll")
          .pipe(takeUntil(this._destroyed$))
          .subscribe((e: any) => this._onMouseWheel(e));
        fromEvent(window, "resize")
          .pipe(takeUntil(this._destroyed$))
          .subscribe(() => {
            this._refreshScroller();
          });
      }
    
      private _onMouseWheel(e: any) {
        const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
        this.scroll(delta * 25);
      }
    
      private _createArrow(direction: string) {
        const arrow = el(`nav-arrow nav-arrow--${direction}`);
        this._container.insertBefore(arrow, this._nav);
        arrow.style.width = this.arrowWidth + "px";
        fromEvent(arrow, "click")
          .pipe(takeUntil(this._destroyed$))
          .subscribe(() => {
            this.scroll(direction === "left" ? this.shiftWidth : -this.shiftWidth);
          });
    
        const upStream$ = fromEvent(arrow, "mouseup");
        // handle long press
        fromEvent(arrow, "mousedown")
          .pipe(
            takeUntil(this._destroyed$),
            mergeMap(event =>
              interval(100).pipe(
                delay(100),
                takeUntil(upStream$)
              )
            )
          )
          .subscribe(() => {
            this.scroll(direction === "left" ? this.shiftWidth : -this.shiftWidth);
          });
    
        return arrow;
      }
    
      private _refreshScroller() {
        const compareWith = this._scrollable ? -this.arrowWidth * 2 : 0;
        this._container.classList.toggle(
          "nav-wrapper--scrollable",
          this.rightBorder < compareWith
        );
        this._scrollable = this.rightBorder < compareWith;
        this.scroll(0);
      }
    }
    
    function wrap(elem, wrapperClass: string) {
      const wrapper = el("nav-wrapper");
      elem.parentNode.insertBefore(wrapper, elem);
      wrapper.appendChild(elem);
      return wrapper;
    }
    
    function el(className: string): HTMLElement {
      const div = document.createElement("div");
      div.className = className;
      return div;
    }
    
    

    Now everything is set, In your html

    <p-tabView appTabScroller>
      <p-tabPanel [header]="item.content" *ngFor="let item of items; let i = index" [selected]="i == 0">
      </p-tabPanel>
    </p-tabView>
    

    See Demo

    Source: https://embed.plnkr.co/BX6UTrG7XTBXS2TNChAs/