0
votes

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

2
February of this year Prime added the ability to switch tabs programatically (Version 11.2.1) primefaces.org/primeng/showcase/#/tabview . Just wanted to check if you saw this already.canpan14
I am using an older version of angular which does not support Primeng11app

2 Answers

2
votes

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/

0
votes

I forked your example and added some example buttons to the top that can go to the previous/next tab. There are a few different ways you could do this of course.

https://stackblitz.com/edit/primeng-tabview-ngif-2e96sb?file=src/app/app.component.html

Let me know if CSS was more of the issue as this mainly shows off the functionality.