UX Overhaul Part 2 (#3112)

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joe Milazzo 2024-08-16 19:37:12 -05:00 committed by GitHub
parent 0247bc5012
commit 3d8aa2ad24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
192 changed files with 14808 additions and 1874 deletions

View file

@ -25,7 +25,7 @@
[freeMode]="true">
@for(item of items; track item; let i = $index;) {
<ng-template swiperSlide>
<ng-container swiperSlide [ngTemplateOutlet]="carouselItemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
<ng-container [ngTemplateOutlet]="carouselItemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
</ng-template>
} @empty {
@if (alwaysShow) {

View file

@ -1,19 +1,31 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
EventEmitter,
inject,
Input,
Output,
TemplateRef
} from '@angular/core';
import { Swiper, SwiperEvents } from 'swiper/types';
import { SwiperModule } from 'swiper/angular';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-carousel-reel',
templateUrl: './carousel-reel.component.html',
styleUrls: ['./carousel-reel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
selector: 'app-carousel-reel',
templateUrl: './carousel-reel.component.html',
styleUrls: ['./carousel-reel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [NgClass, SwiperModule, NgTemplateOutlet, TranslocoDirective]
})
export class CarouselReelComponent {
private readonly cdRef = inject(ChangeDetectorRef);
@ContentChild('carouselItem') carouselItemTemplate!: TemplateRef<any>;
@ContentChild('promptToAdd') promptToAddTemplate!: TemplateRef<any>;
@Input() items: any[] = [];
@ -32,10 +44,6 @@ export class CarouselReelComponent {
swiper: Swiper | undefined;
constructor(private readonly cdRef: ChangeDetectorRef) {}
nextPage() {
if (this.swiper) {
if (this.swiper.isEnd) return;

View file

@ -0,0 +1 @@
<ng-content></ng-content>

View file

@ -0,0 +1,17 @@
import {ChangeDetectionStrategy, Component, ContentChild, Input, TemplateRef} from '@angular/core';
import {TabId} from "../carousel-tabs/carousel-tabs.component";
@Component({
selector: 'app-carousel-tab',
standalone: true,
imports: [],
templateUrl: './carousel-tab.component.html',
styleUrl: './carousel-tab.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CarouselTabComponent {
@Input({required: true}) id!: TabId;
@ContentChild(TemplateRef, {static: true}) implicitContent!: TemplateRef<any>;
}

View file

@ -0,0 +1,27 @@
<ng-container *transloco="let t;">
<div class="carousel-tabs-wrapper">
<button class="scroll-button left" (click)="scroll('left')" [class.visible]="showLeftArrow">
<i class="fas fa-chevron-left"></i>
</button>
<div class="carousel-tabs-container" #scrollContainer (scroll)="onScroll()">
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" (navChange)="onNavChange($event)">
@for (tab of tabComponents; track tab) {
<li [ngbNavItem]="tab.id">
<a ngbNavLink>{{t('tabs.' + tab.id)}}</a>
<ng-template ngbNavContent>
<!-- <ng-container [ngTemplateOutlet]="tab.contentTemplate"></ng-container>-->
<ng-content select="app-carousel-tab[id='{{tab.id}}']"></ng-content>
</ng-template>
</li>
}
</ul>
</div>
<button class="scroll-button right" (click)="scroll('right')" [class.visible]="showRightArrow">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div [ngbNavOutlet]="nav" style="min-height: 300px"></div>
</ng-container>

View file

@ -0,0 +1,45 @@
.carousel-tabs-wrapper {
position: relative;
display: flex;
align-items: center;
}
.carousel-tabs-container {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
scrollbar-width: none;
flex-grow: 1;
}
.carousel-tabs-container::-webkit-scrollbar {
display: none;
}
.nav-tabs {
flex-wrap: nowrap;
}
.scroll-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.7);
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 1;
}
.scroll-button.left {
left: 0;
}
.scroll-button.right {
right: 0;
}
.scroll-button.visible {
opacity: 1;
}

View file

@ -0,0 +1,127 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChildren, ElementRef, EventEmitter, HostListener,
inject, Input, OnInit, Output, QueryList,
TemplateRef, ViewChild
} from '@angular/core';
import {
NgbNav,
NgbNavChangeEvent,
NgbNavContent,
NgbNavItem,
NgbNavLink,
NgbNavOutlet
} from "@ng-bootstrap/ng-bootstrap";
import {CarouselTabComponent} from "../carousel-tab/carousel-tab.component";
import {TranslocoDirective} from "@jsverse/transloco";
import {NgTemplateOutlet} from "@angular/common";
/**
* Any Tabs that use this Carousel should use these
*/
export enum TabId {
Related = 'related-tab',
Reviews = 'review-tab', // Only applicable for books
Details = 'details-tab',
Chapters = 'chapters-tab',
}
@Component({
selector: 'app-carousel-tabs',
standalone: true,
imports: [
NgbNav,
TranslocoDirective,
NgbNavItem,
NgbNavLink,
NgTemplateOutlet,
NgbNavOutlet,
NgbNavContent
],
templateUrl: './carousel-tabs.component.html',
styleUrl: './carousel-tabs.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CarouselTabsComponent implements OnInit, AfterViewInit {
private readonly cdRef = inject(ChangeDetectorRef);
@ContentChildren(CarouselTabComponent) tabComponents!: QueryList<CarouselTabComponent>;
@Input({required: true}) activeTabId!: TabId;
@Output() activeTabIdChange = new EventEmitter<TabId>();
@Output() navChange = new EventEmitter<NgbNavChangeEvent>();
@ViewChild('scrollContainer') scrollContainer: ElementRef | undefined;
tabs: { id: TabId; contentTemplate: any }[] = [];
showLeftArrow = false;
showRightArrow = false;
ngOnInit() {
this.checkOverflow();
}
ngAfterViewInit() {
this.initializeTabs();
this.scrollToActiveTab();
this.checkOverflow();
}
initializeTabs() {
this.tabs = this.tabComponents.map(tabComponent => ({
id: tabComponent.id,
contentTemplate: tabComponent.implicitContent
}));
this.cdRef.markForCheck();
}
@HostListener('window:resize')
onResize() {
this.checkOverflow();
}
onNavChange(event: NgbNavChangeEvent) {
this.activeTabIdChange.emit(event.nextId);
this.navChange.emit(event);
this.scrollToActiveTab();
}
onScroll() {
this.checkOverflow();
}
scrollToActiveTab() {
setTimeout(() => {
const activeTab = this.scrollContainer?.nativeElement.querySelector('.active');
if (activeTab) {
activeTab.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
}
this.checkOverflow();
});
}
checkOverflow() {
const element = this.scrollContainer?.nativeElement;
if (!element) return;
this.showLeftArrow = element.scrollLeft > 0;
this.showRightArrow = element.scrollLeft < element.scrollWidth - element.clientWidth;
this.cdRef.markForCheck();
}
scroll(direction: 'left' | 'right') {
const element = this.scrollContainer?.nativeElement;
if (!element) return;
const scrollAmount = element.clientWidth / 2;
if (direction === 'left') {
element.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
} else {
element.scrollBy({ left: scrollAmount, behavior: 'smooth' });
}
}
}