Skip to content

Commit

Permalink
Merge pull request #11183 from cetincakiroglu/tabmenu-scroll
Browse files Browse the repository at this point in the history
Fixed #11154 - TabMenu | Scroll option for tabmenu
  • Loading branch information
yigitfindikli authored Feb 15, 2022
2 parents 84c7df5 + 6f1d3f9 commit 453d937
Show file tree
Hide file tree
Showing 46 changed files with 934 additions and 31 deletions.
39 changes: 37 additions & 2 deletions src/app/components/tabmenu/tabmenu.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
.p-tabmenu {
.p-tabmenu-nav-container {
position: relative;
}

.p-tabmenu-scrollable .p-tabmenu-nav-container {
overflow: hidden;
}

.p-tabmenu-nav-content {
overflow-x: auto;
overflow-y: hidden;
scroll-behavior: smooth;
scrollbar-width: none;
overscroll-behavior: contain auto;
}

.p-tabmenu-nav-btn {
position: absolute;
top: 0;
z-index: 2;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

.p-tabmenu-nav-prev {
left: 0;
}

.p-tabmenu-nav-next {
right: 0;
}

.p-tabview-nav-content::-webkit-scrollbar {
display: none;
}

.p-tabmenu-nav {
Expand Down Expand Up @@ -27,13 +61,14 @@

.p-tabmenu-nav .p-menuitem-text {
line-height: 1;
white-space: nowrap;
}

.p-tabmenu-ink-bar {
display: none;
z-index: 1;
}

.p-tabmenu::-webkit-scrollbar {
.p-tabmenu-nav-content::-webkit-scrollbar {
display: none;
}
117 changes: 90 additions & 27 deletions src/app/components/tabmenu/tabmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,43 @@ import {TooltipModule} from 'primeng/tooltip';
@Component({
selector: 'p-tabMenu',
template: `
<div [ngClass]="'p-tabmenu p-component'" [ngStyle]="style" [class]="styleClass">
<ul #navbar class="p-tabmenu-nav p-reset" role="tablist">
<li *ngFor="let item of model; let i = index" role="tab" [ngStyle]="item.style" [class]="item.styleClass" [attr.aria-selected]="isActive(item)" [attr.aria-expanded]="isActive(item)"
[ngClass]="{'p-tabmenuitem':true,'p-disabled':item.disabled,'p-highlight':isActive(item),'p-hidden': item.visible === false}" pTooltip [tooltipOptions]="item.tooltipOptions">
<a *ngIf="!item.routerLink" [attr.href]="item.url" class="p-menuitem-link" role="presentation" (click)="itemClick($event,item)" (keydown.enter)="itemClick($event,item)" [attr.tabindex]="item.disabled ? null : '0'"
[attr.target]="item.target" [attr.title]="item.title" [attr.id]="item.id" pRipple>
<ng-container *ngIf="!itemTemplate">
<span class="p-menuitem-icon" [ngClass]="item.icon" *ngIf="item.icon"></span>
<span class="p-menuitem-text" *ngIf="item.escape !== false; else htmlLabel">{{item.label}}</span>
<ng-template #htmlLabel><span class="p-menuitem-text" [innerHTML]="item.label"></span></ng-template>
</ng-container>
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item, index: i}"></ng-container>
</a>
<a *ngIf="item.routerLink" [routerLink]="item.routerLink" [queryParams]="item.queryParams" [routerLinkActive]="'p-menuitem-link-active'" [routerLinkActiveOptions]="item.routerLinkActiveOptions||{exact:false}"
role="presentation" class="p-menuitem-link" (click)="itemClick($event,item)" (keydown.enter)="itemClick($event,item)" [attr.tabindex]="item.disabled ? null : '0'"
[attr.target]="item.target" [attr.title]="item.title" [attr.id]="item.id"
[fragment]="item.fragment" [queryParamsHandling]="item.queryParamsHandling" [preserveFragment]="item.preserveFragment" [skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state" pRipple>
<ng-container *ngIf="!itemTemplate">
<span class="p-menuitem-icon" [ngClass]="item.icon" *ngIf="item.icon"></span>
<span class="p-menuitem-text" *ngIf="item.escape !== false; else htmlRouteLabel">{{item.label}}</span>
<ng-template #htmlRouteLabel><span class="p-menuitem-text" [innerHTML]="item.label"></span></ng-template>
</ng-container>
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item, index: i}"></ng-container>
</a>
</li>
<li #inkbar class="p-tabmenu-ink-bar"></li>
</ul>
<div [ngClass]="{'p-tabmenu p-component': true, 'p-tabmenu-scrollable': scrollable}" [ngStyle]="style" [class]="styleClass">
<div class="p-tabmenu-nav-container">
<button *ngIf="scrollable && !backwardIsDisabled" #prevBtn class="p-tabmenu-nav-prev p-tabmenu-nav-btn p-link" (click)="navBackward()" type="button" pRipple>
<span class="pi pi-chevron-left"></span>
</button>
<div #content class="p-tabmenu-nav-content" (scroll)="onScroll($event)">
<ul #navbar class="p-tabmenu-nav p-reset" role="tablist">
<li *ngFor="let item of model; let i = index" role="tab" [ngStyle]="item.style" [class]="item.styleClass" [attr.aria-selected]="isActive(item)" [attr.aria-expanded]="isActive(item)"
[ngClass]="{'p-tabmenuitem':true,'p-disabled':item.disabled,'p-highlight':isActive(item),'p-hidden': item.visible === false}" pTooltip [tooltipOptions]="item.tooltipOptions">
<a *ngIf="!item.routerLink" [attr.href]="item.url" class="p-menuitem-link" role="presentation" (click)="itemClick($event,item)" (keydown.enter)="itemClick($event,item)" [attr.tabindex]="item.disabled ? null : '0'"
[attr.target]="item.target" [attr.title]="item.title" [attr.id]="item.id" pRipple>
<ng-container *ngIf="!itemTemplate">
<span class="p-menuitem-icon" [ngClass]="item.icon" *ngIf="item.icon"></span>
<span class="p-menuitem-text" *ngIf="item.escape !== false; else htmlLabel">{{item.label}}</span>
<ng-template #htmlLabel><span class="p-menuitem-text" [innerHTML]="item.label"></span></ng-template>
</ng-container>
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item, index: i}"></ng-container>
</a>
<a *ngIf="item.routerLink" [routerLink]="item.routerLink" [queryParams]="item.queryParams" [routerLinkActive]="'p-menuitem-link-active'" [routerLinkActiveOptions]="item.routerLinkActiveOptions||{exact:false}"
role="presentation" class="p-menuitem-link" (click)="itemClick($event,item)" (keydown.enter)="itemClick($event,item)" [attr.tabindex]="item.disabled ? null : '0'"
[attr.target]="item.target" [attr.title]="item.title" [attr.id]="item.id"
[fragment]="item.fragment" [queryParamsHandling]="item.queryParamsHandling" [preserveFragment]="item.preserveFragment" [skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state" pRipple>
<ng-container *ngIf="!itemTemplate">
<span class="p-menuitem-icon" [ngClass]="item.icon" *ngIf="item.icon"></span>
<span class="p-menuitem-text" *ngIf="item.escape !== false; else htmlRouteLabel">{{item.label}}</span>
<ng-template #htmlRouteLabel><span class="p-menuitem-text" [innerHTML]="item.label"></span></ng-template>
</ng-container>
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item, index: i}"></ng-container>
</a>
</li>
<li #inkbar class="p-tabmenu-ink-bar"></li>
</ul>
</div>
<button *ngIf="scrollable && !forwardIsDisabled" #nextBtn class="p-tabmenu-nav-next p-tabmenu-nav-btn p-link" (click)="navForward()" type="button" pRipple>
<span class="pi pi-chevron-right"></span>
</button>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -52,22 +62,34 @@ export class TabMenu implements AfterContentInit,AfterViewInit,AfterViewChecked

@Input() activeItem: MenuItem;

@Input() scrollable: boolean;

@Input() popup: boolean;

@Input() style: any;

@Input() styleClass: string;

@ViewChild('content') content: ElementRef;

@ViewChild('navbar') navbar: ElementRef;

@ViewChild('inkbar') inkbar: ElementRef;

@ViewChild('prevBtn') prevBtn: ElementRef;

@ViewChild('nextBtn') nextBtn: ElementRef;

@ContentChildren(PrimeTemplate) templates: QueryList<any>;

itemTemplate: TemplateRef<any>;

tabChanged: boolean;

backwardIsDisabled: boolean = true;

forwardIsDisabled: boolean = false;

constructor(private router: Router, private route:ActivatedRoute, private cd: ChangeDetectorRef) { }

ngAfterContentInit() {
Expand Down Expand Up @@ -133,6 +155,47 @@ export class TabMenu implements AfterContentInit,AfterViewInit,AfterViewChecked
this.inkbar.nativeElement.style.left = DomHandler.getOffset(tabHeader).left - DomHandler.getOffset(this.navbar.nativeElement).left + 'px';
}
}

getVisibleButtonWidths() {
return [this.prevBtn?.nativeElement, this.nextBtn?.nativeElement].reduce((acc, el) => el ? acc + DomHandler.getWidth(el) : acc, 0);
}

updateButtonState() {
const content = this.content.nativeElement;
const { scrollLeft, scrollWidth } = content;
const width = DomHandler.getWidth(content);

this.backwardIsDisabled = scrollLeft === 0;
this.forwardIsDisabled = scrollLeft === scrollWidth - width;
}


updateScrollBar(index) {
let tabHeader = this.navbar.nativeElement.children[index];
tabHeader.scrollIntoView({ block: 'nearest' })
}

onScroll(event) {
this.scrollable && this.updateButtonState();

event.preventDefault();
}

navBackward() {
const content = this.content.nativeElement;
const width = DomHandler.getWidth(content) - this.getVisibleButtonWidths();
const pos = content.scrollLeft - width;
content.scrollLeft = pos <= 0 ? 0 : pos;
}

navForward() {
const content = this.content.nativeElement;
const width = DomHandler.getWidth(content) - this.getVisibleButtonWidths();
const pos = content.scrollLeft + width;
const lastPos = content.scrollWidth - width;

content.scrollLeft = pos >= lastPos ? lastPos : pos;
}
}

@NgModule({
Expand Down
45 changes: 43 additions & 2 deletions src/app/showcase/components/tabmenu/tabmenudemo.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ <h1>TabMenu</h1>

<div class="content-section implementation">
<div class="card">
<h5>Default</h5>
<p-tabMenu [model]="items" [activeItem]="activeItem"></p-tabMenu>
</div>

<div class="card">
<h5>Scrollable</h5>
<p-tabMenu [scrollable]="true" [model]="scrollableItems" [activeItem]="activeItem2"></p-tabMenu>
</div>
</div>

<div class="content-section documentation">
Expand Down Expand Up @@ -50,8 +56,8 @@ <h5>Getting Started</h5>
<h5>ActiveItem</h5>
<p>By default item that matches the active route is highlighted, alternatively <i>activeItem</i> property can be used choose the initial active item.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-tabMenu [model]="items" [activeItem]="items[0]"&gt;&lt;/p-tabMenu&gt;</app-code>

&lt;p-tabMenu [model]="items" [activeItem]="items[0]"&gt;&lt;/p-tabMenu&gt;
</app-code>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TabMenuDemo &#123;

Expand Down Expand Up @@ -81,6 +87,25 @@ <h5>Templating</h5>
//item content
&lt;/ng-template&gt;
&lt;/p-tabMenu&gt;
</app-code>

<h5>Scrollable</h5>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-tabMenu [model]="scrollableItems" [activeItem]="activeItem2"&gt;&lt;/p-tabMenu&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TabMenuDemo &#123;

scrollableItems: MenuItem[];

activeItem2: MenuItem;

ngOnInit() &#123;
this.scrollableItems = Array.from(&#123; length: 50 &#125;, (_, i) => (&#123; label: `Tab $&#123;i + 1&#125;`, icon: `pi pi-fw pi-display` &#125;));
this.activeItem2 = this.scrollableItems[0];
&#125;
&#125;
</app-code>

<h5>Properties</h5>
Expand Down Expand Up @@ -119,6 +144,12 @@ <h5>Properties</h5>
<td>null</td>
<td>Style class of the component.</td>
</tr>
<tr>
<td>scrollable</td>
<td>boolean</td>
<td>false</td>
<td>When enabled displays buttons at each side of the tab headers to scroll the tab list.</td>
</tr>
</tbody>
</table>
</div>
Expand Down Expand Up @@ -195,15 +226,21 @@ <h5>Dependencies</h5>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-tabMenu [model]="items" [activeItem]="activeItem"&gt;&lt;/p-tabMenu&gt;

&lt;p-tabMenu [model]="scrollableItems" [activeItem]="activeItem2"&gt;&lt;/p-tabMenu&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TabMenuDemo &#123;

items: MenuItem[];

scrollableItems: MenuItem[];

activeItem: MenuItem;

activeItem2: MenuItem;

ngOnInit() &#123;
this.items = [
&#123;label: 'Home', icon: 'pi pi-fw pi-home'&#125;,
Expand All @@ -213,7 +250,11 @@ <h5>Dependencies</h5>
&#123;label: 'Settings', icon: 'pi pi-fw pi-cog'&#125;
];

this.scrollableItems = Array.from(&#123; length: 50 &#125;, (_, i) => (&#123; label: `Tab $&#123;i + 1&#125;`, icon: `pi pi-fw pi-display` &#125;));

this.activeItem = this.items[0];

this.activeItem2 = this.scrollableItems[0];
&#125;

&#125;
Expand Down
8 changes: 8 additions & 0 deletions src/app/showcase/components/tabmenu/tabmenudemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ export class TabMenuDemo {

items: MenuItem[];

scrollableItems: MenuItem[];

activeItem: MenuItem;

activeItem2: MenuItem;

ngOnInit() {
this.items = [
{label: 'Home', icon: 'pi pi-fw pi-home'},
Expand All @@ -19,6 +23,10 @@ export class TabMenuDemo {
{label: 'Settings', icon: 'pi pi-fw pi-cog'}
];

this.scrollableItems = Array.from({ length: 50 }, (_, i) => ({ label: `Tab ${i + 1}`, icon: `pi pi-fw pi-display` }));

this.activeItem = this.items[0];

this.activeItem2 = this.scrollableItems[0];
}
}
18 changes: 18 additions & 0 deletions src/assets/components/themes/arya-blue/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -5047,6 +5047,24 @@ p-treeselect.ng-invalid.ng-dirty > .p-treeselect {
border-color: #64B5F6;
color: #64B5F6;
}
.p-tabmenu .p-tabmenu-left-icon {
margin-right: 0.5rem;
}
.p-tabmenu .p-tabmenu-right-icon {
margin-left: 0.5rem;
}
.p-tabmenu .p-tabmenu-nav-btn.p-link {
background: #1e1e1e;
color: #64B5F6;
width: 2.357rem;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
border-radius: 0;
}
.p-tabmenu .p-tabmenu-nav-btn.p-link:focus {
outline: 0 none;
outline-offset: 0;
box-shadow: inset 0 0 0 1px #93cbf9;
}

.p-tieredmenu {
padding: 0.25rem 0;
Expand Down
18 changes: 18 additions & 0 deletions src/assets/components/themes/arya-green/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -5047,6 +5047,24 @@ p-treeselect.ng-invalid.ng-dirty > .p-treeselect {
border-color: #81C784;
color: #81C784;
}
.p-tabmenu .p-tabmenu-left-icon {
margin-right: 0.5rem;
}
.p-tabmenu .p-tabmenu-right-icon {
margin-left: 0.5rem;
}
.p-tabmenu .p-tabmenu-nav-btn.p-link {
background: #1e1e1e;
color: #81C784;
width: 2.357rem;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
border-radius: 0;
}
.p-tabmenu .p-tabmenu-nav-btn.p-link:focus {
outline: 0 none;
outline-offset: 0;
box-shadow: inset 0 0 0 1px #a7d8a9;
}

.p-tieredmenu {
padding: 0.25rem 0;
Expand Down
Loading

0 comments on commit 453d937

Please sign in to comment.