-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d6d2cb3
commit 000d900
Showing
40 changed files
with
2,146 additions
and
24 deletions.
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
angular/demo/daisyui/src/app/samples/carousel/carousel.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<div class="overflow-hidden relative cursor-grab active:cursor-grabbing" [auUse]="widget.directives.emblaDirective"> | ||
<div class="flex"> | ||
@for (item of items(); track item) { | ||
<div class="basis-full min-w-0 shrink-0 grow-0 flex justify-center"> | ||
<ng-container *ngTemplateOutlet="slideRef().templateRef; context: {$implicit: item}"></ng-container> | ||
</div> | ||
} | ||
</div> | ||
@if (withNavArrows()) { | ||
<div class="absolute left-5 right-5 top-1/2 flex -translate-y-1/2 transform justify-between"> | ||
<button | ||
class="btn btn-sm md:btn-md btn-circle opacity-75 hover:opacity-100" | ||
(pointerdown)="$event.preventDefault()" | ||
[disabled]="!state().canScrollPrev" | ||
(click)="widget.api.scrollPrev()" | ||
aria-label="Go to previous slide" | ||
> | ||
❮ | ||
</button> | ||
<button | ||
class="btn btn-sm md:btn-md btn-circle opacity-75 hover:opacity-100" | ||
(pointerdown)="$event.preventDefault()" | ||
[disabled]="!state().canScrollNext" | ||
(click)="widget.api.scrollNext()" | ||
aria-label="Go to next slide" | ||
> | ||
❯ | ||
</button> | ||
</div> | ||
} | ||
@if (withNavIndicators()) { | ||
<div class="flex w-full justify-center gap-2 py-2 cursor-auto"> | ||
@for (item of items(); track item; let index = $index) { | ||
<button | ||
class="btn btn-xs md:btn-sm" | ||
[class.btn-active]="state().selectedScrollSnap === index" | ||
(click)="widget.api.scrollTo(index)" | ||
attr.aria-label="Go to slide {{ index + 1 }}" | ||
> | ||
{{ index + 1 }} | ||
</button> | ||
} | ||
</div> | ||
} | ||
</div> |
54 changes: 54 additions & 0 deletions
54
angular/demo/daisyui/src/app/samples/carousel/carousel.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { | ||
auBooleanAttribute, | ||
BaseWidgetDirective, | ||
callWidgetFactory, | ||
createCarousel, | ||
UseDirective, | ||
type CarouselWidget, | ||
} from '@agnos-ui/angular-headless'; | ||
import {NgTemplateOutlet} from '@angular/common'; | ||
import {Component, contentChild, Directive, effect, inject, input, TemplateRef} from '@angular/core'; | ||
import AutoPlay from 'embla-carousel-autoplay'; | ||
|
||
export interface CarouselSlideContext { | ||
$implicit: string; | ||
} | ||
|
||
@Directive({standalone: true, selector: 'ng-template[appCarouselSlide]'}) | ||
export class CarouselSlideDirective { | ||
public templateRef = inject(TemplateRef<CarouselSlideContext>); | ||
static ngTemplateContextGuard(_dir: CarouselSlideDirective, context: unknown): context is CarouselSlideContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
selector: 'app-carousel', | ||
standalone: true, | ||
templateUrl: 'carousel.component.html', | ||
imports: [UseDirective, NgTemplateOutlet], | ||
}) | ||
export class CarouselComponent<Item> extends BaseWidgetDirective<CarouselWidget> { | ||
readonly dragFree = input(false, {transform: auBooleanAttribute}); | ||
readonly loop = input(true, {transform: auBooleanAttribute}); | ||
readonly autoplay = input(true, {transform: auBooleanAttribute}); | ||
readonly withNavIndicators = input(true, {transform: auBooleanAttribute}); | ||
readonly withNavArrows = input(true, {transform: auBooleanAttribute}); | ||
readonly items = input.required<Item[]>(); | ||
|
||
readonly slideRef = contentChild.required(CarouselSlideDirective); | ||
|
||
readonly _widget = callWidgetFactory({ | ||
factory: createCarousel, | ||
widgetName: 'carousel', | ||
}); | ||
|
||
constructor() { | ||
super(); | ||
effect(() => { | ||
this._widget.patch({ | ||
plugins: this.autoplay() ? [AutoPlay({playOnInit: true, stopOnInteraction: false, stopOnMouseEnter: true, stopOnFocusIn: true})] : [], | ||
}); | ||
}); | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
angular/demo/daisyui/src/app/samples/carousel/default.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import {Component} from '@angular/core'; | ||
import {CarouselComponent, CarouselSlideDirective} from './carousel.component'; | ||
import {FormsModule} from '@angular/forms'; | ||
|
||
@Component({ | ||
standalone: true, | ||
template: ` | ||
<div class="w-full flex justify-center"> | ||
<div class="max-w-[600px] self-center"> | ||
<app-carousel | ||
[items]="photos" | ||
[withNavArrows]="withNavArrows" | ||
[withNavIndicators]="withNavIndicators" | ||
[loop]="loop" | ||
[dragFree]="dragFree" | ||
[autoplay]="autoplay && loop" | ||
> | ||
<img | ||
*appCarouselSlide="let photo" | ||
[src]="photo" | ||
class="select-none object-contain aspect-[4/3] w-full" | ||
alt="random picsum" | ||
loading="lazy" | ||
/> | ||
</app-carousel> | ||
<div class="form-control items-start"> | ||
<label class="label cursor-pointer gap-3"> | ||
<span class="label-text">Loop</span> | ||
<input type="checkbox" class="toggle toggle-primary" [(ngModel)]="loop" /> | ||
</label> | ||
<label class="label gap-3" [class.cursor-pointer]="loop"> | ||
<span class="label-text">Autoplay</span> | ||
<input type="checkbox" class="toggle toggle-primary" [(ngModel)]="autoplay" [disabled]="!loop" /> | ||
</label> | ||
<label class="label cursor-pointer gap-3"> | ||
<span class="label-text">Drag free</span> | ||
<input type="checkbox" class="toggle toggle-primary" [(ngModel)]="dragFree" /> | ||
</label> | ||
<label class="label cursor-pointer gap-3"> | ||
<span class="label-text">Navigation Indicators</span> | ||
<input type="checkbox" class="toggle toggle-primary" [(ngModel)]="withNavIndicators" /> | ||
</label> | ||
<label class="label cursor-pointer gap-3"> | ||
<span class="label-text">Navigation Arrows</span> | ||
<input type="checkbox" class="toggle toggle-primary" [(ngModel)]="withNavArrows" /> | ||
</label> | ||
</div> | ||
</div> | ||
</div> | ||
`, | ||
imports: [CarouselComponent, CarouselSlideDirective, FormsModule], | ||
}) | ||
export default class DemoCarouselComponent { | ||
readonly photos = [ | ||
'https://picsum.photos/600/450.webp?random=1', | ||
'https://picsum.photos/600/450.webp?random=2', | ||
'https://picsum.photos/450/600.webp?random=3', | ||
'https://picsum.photos/600/450.webp?random=4', | ||
'https://picsum.photos/600/450.webp?random=5', | ||
'https://picsum.photos/600/450.webp?random=6', | ||
]; | ||
loop = true; | ||
dragFree = false; | ||
withNavArrows = true; | ||
withNavIndicators = true; | ||
autoplay = true; | ||
} |
18 changes: 18 additions & 0 deletions
18
angular/demo/daisyui/src/app/samples/carousel/demoGallery.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {Component} from '@angular/core'; | ||
import {GalleryComponent} from './gallery.component'; | ||
import {photos} from '@agnos-ui/common/samples/carousel/photo'; | ||
|
||
@Component({ | ||
standalone: true, | ||
template: ` | ||
<div class="w-full flex justify-center"> | ||
<div class="max-w-[600px] lg:max-w-[1000px]"> | ||
<app-gallery [photos]="photos" withNavArrows withShowFullscreen /> | ||
</div> | ||
</div> | ||
`, | ||
imports: [GalleryComponent], | ||
}) | ||
export default class DemoGalleryComponent { | ||
readonly photos = photos; | ||
} |
42 changes: 42 additions & 0 deletions
42
angular/demo/daisyui/src/app/samples/carousel/gallery-image.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type {Source} from '@agnos-ui/common/samples/carousel/photo'; | ||
import {Component, input, signal} from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'app-gallery-image', | ||
standalone: true, | ||
template: ` | ||
@if (loadRequested()) { | ||
@if (!imageLoaded()) { | ||
<span class="absolute top-1/2 -translate-y-1/2 left-1/2 loading loading-spinner loading-lg text-primary"></span> | ||
} | ||
<picture class="flex justify-center"> | ||
@for (source of sources(); track source) { | ||
<source [media]="source.media" [srcset]="source.srcset" /> | ||
} | ||
<img | ||
class="select-none object-contain transition-opacity ease-in-out duration-300 opacity-0" | ||
[class.opacity-100]="toShow()" | ||
[alt]="alt()" | ||
[src]="src()" | ||
loading="lazy" | ||
[style.aspect-ratio]="aspectRatio()" | ||
(load)="imageLoaded.set(true)" | ||
/> | ||
</picture> | ||
} @else { | ||
<div class="skeleton w-full h-full"></div> | ||
} | ||
`, | ||
host: { | ||
style: 'display: contents;', | ||
}, | ||
}) | ||
export class GalleryImageComponent { | ||
readonly src = input.required<string>(); | ||
readonly alt = input.required<string>(); | ||
readonly sources = input.required<Source[]>(); | ||
readonly loadRequested = input.required<boolean>(); | ||
readonly aspectRatio = input.required<number>(); | ||
readonly toShow = input.required<boolean>(); | ||
readonly imageLoaded = signal(false); | ||
} |
64 changes: 64 additions & 0 deletions
64
angular/demo/daisyui/src/app/samples/carousel/gallery.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<div #mainContainer class="grid grid-flow-row max-h-dvh"> | ||
<div class="overflow-hidden relative cursor-grab active:cursor-grabbing" [auUse]="mainCarouselWidget.directives.emblaDirective"> | ||
<div class="flex max-h-full"> | ||
@for (photoWithLoadState of photosWithLoadState(); track photoWithLoadState; let index = $index) { | ||
<div class="relative basis-full min-w-0 shrink-0 grow-0 flex justify-center"> | ||
<app-gallery-image | ||
[src]="photoWithLoadState.src" | ||
[alt]="photoWithLoadState.alt" | ||
[aspectRatio]="aspectRatio()" | ||
[loadRequested]="photoWithLoadState.loadRequested()" | ||
[sources]="photoWithLoadState.sources" | ||
[toShow]="showImage(index)" | ||
/> | ||
</div> | ||
} | ||
</div> | ||
@if (withNavArrows()) { | ||
<div class="absolute left-5 right-5 top-1/2 flex -translate-y-1/2 transform justify-between"> | ||
<button | ||
class="btn btn-sm md:btn-md btn-circle opacity-75 hover:opacity-100" | ||
(pointerdown)="$event.preventDefault()" | ||
[disabled]="!mainCarouselState().canScrollPrev" | ||
(click)="mainCarouselWidget.api.scrollPrev()" | ||
aria-label="Go to previous photo" | ||
> | ||
❮ | ||
</button> | ||
<button | ||
class="btn btn-sm md:btn-md btn-circle opacity-75 hover:opacity-100" | ||
(pointerdown)="$event.preventDefault()" | ||
[disabled]="!mainCarouselState().canScrollNext" | ||
(click)="mainCarouselWidget.api.scrollNext()" | ||
aria-label="Go to next photo" | ||
> | ||
❯ | ||
</button> | ||
</div> | ||
} | ||
@if (canFullScreen()) { | ||
<div class="absolute right-5 bottom-5 flex"> | ||
<button | ||
class="btn btn-sm md:btn-md opacity-75 hover:opacity-100" | ||
(click)="toggleFullScreen()" | ||
[attr.aria-label]="isFullScreen() ? 'leave fullscreen' : 'open photo in fullscreen'" | ||
[innerHTML]="isFullScreen() ? compressSvg : expandSvg" | ||
></button> | ||
</div> | ||
} | ||
</div> | ||
<div class="overflow-hidden relative mt-1 mb-2" [auUse]="thumbCarouselWidget.directives.emblaDirective"> | ||
<div class="grid grid-flow-col auto-cols-max gap-2 mx-1 my-1"> | ||
@for (photo of photos(); track photo; let index = $index) { | ||
<button | ||
class="shadow-primary" | ||
(click)="scrollToSlide(index)" | ||
[class.ring]="mainCarouselState().selectedScrollSnap === index" | ||
attr.aria-label="Go to photo {{ index + 1 }}" | ||
> | ||
<img class="select-none" alt="random picsum" [src]="photo.thumbnail" loading="lazy" [style.aspect-ratio]="aspectRatio()" /> | ||
</button> | ||
} | ||
</div> | ||
</div> | ||
</div> |
94 changes: 94 additions & 0 deletions
94
angular/demo/daisyui/src/app/samples/carousel/gallery.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import {Component, computed, type ElementRef, inject, input, type OnDestroy, type OnInit, signal, viewChild} from '@angular/core'; | ||
import type {Photo} from '@agnos-ui/common/samples/carousel/photo'; | ||
import {auBooleanAttribute, callWidgetFactory, createCarousel, UseDirective} from '@agnos-ui/angular-headless'; | ||
import {GalleryImageComponent} from './gallery-image.component'; | ||
import type {UnsubscribeFunction, UnsubscribeObject} from '@amadeus-it-group/tansu'; | ||
import expandSvg from '@agnos-ui/common/samples/carousel/expand.svg'; | ||
import compressSvg from '@agnos-ui/common/samples/carousel/compress.svg'; | ||
import {DomSanitizer} from '@angular/platform-browser'; | ||
|
||
@Component({ | ||
selector: 'app-gallery', | ||
standalone: true, | ||
templateUrl: 'gallery.component.html', | ||
imports: [UseDirective, GalleryImageComponent], | ||
}) | ||
export class GalleryComponent implements OnInit, OnDestroy { | ||
readonly photos = input.required<Photo[]>(); | ||
readonly withNavArrows = input(false, {transform: auBooleanAttribute}); | ||
readonly withShowFullscreen = input(false, {transform: auBooleanAttribute}); | ||
readonly aspectRatio = input(4 / 3); | ||
|
||
private readonly domSanitizer = inject(DomSanitizer); | ||
readonly expandSvg = this.domSanitizer.bypassSecurityTrustHtml(expandSvg); | ||
readonly compressSvg = this.domSanitizer.bypassSecurityTrustHtml(compressSvg); | ||
|
||
private readonly _mainCarousel = callWidgetFactory({ | ||
factory: createCarousel, | ||
widgetName: 'carousel', | ||
}); | ||
get mainCarouselWidget() { | ||
return this._mainCarousel.widget; | ||
} | ||
get mainCarouselState() { | ||
return this._mainCarousel.ngState; | ||
} | ||
|
||
private readonly _thumbCarousel = callWidgetFactory({ | ||
factory: createCarousel, | ||
widgetName: 'carousel', | ||
defaultConfig: { | ||
dragFree: true, | ||
containScroll: 'keepSnaps', | ||
}, | ||
}); | ||
get thumbCarouselWidget() { | ||
return this._thumbCarousel.widget; | ||
} | ||
get thumbCarouselState() { | ||
return this._thumbCarousel.ngState; | ||
} | ||
|
||
readonly photosWithLoadState = computed(() => this.photos().map((photo, index) => ({...photo, loadRequested: signal(index === 0)}))); | ||
readonly canFullScreen = computed(() => this.withShowFullscreen() && document?.fullscreenEnabled); | ||
readonly isFullScreen = signal(false); | ||
readonly mainContainer = viewChild.required<ElementRef>('mainContainer'); | ||
private selectedScrollSnapSubscription?: UnsubscribeFunction & UnsubscribeObject; | ||
|
||
ngOnInit() { | ||
this._mainCarousel.ngInit(); | ||
this._thumbCarousel.ngInit(); | ||
this.selectedScrollSnapSubscription = this._mainCarousel.stores.selectedScrollSnap$.subscribe((selectedSnap: number) => { | ||
this.thumbCarouselWidget.api.scrollTo(selectedSnap); | ||
const photosWithLoadState = this.photosWithLoadState(); | ||
photosWithLoadState[selectedSnap].loadRequested.set(true); | ||
if (selectedSnap > 0) { | ||
photosWithLoadState[selectedSnap - 1].loadRequested.set(true); | ||
} | ||
if (selectedSnap < photosWithLoadState.length - 1) { | ||
photosWithLoadState[selectedSnap + 1].loadRequested.set(true); | ||
} | ||
}); | ||
} | ||
|
||
ngOnDestroy() { | ||
this.selectedScrollSnapSubscription?.(); | ||
} | ||
|
||
toggleFullScreen() { | ||
if (!this.isFullScreen()) { | ||
this.mainContainer().nativeElement.requestFullscreen(); | ||
} else { | ||
void document.exitFullscreen(); | ||
} | ||
this.isFullScreen.update((val) => !val); | ||
} | ||
|
||
scrollToSlide(index: number) { | ||
this.mainCarouselWidget.api.scrollTo(index, Math.abs(this.mainCarouselState().selectedScrollSnap - index) > 2); | ||
} | ||
|
||
showImage(index: number) { | ||
return Math.abs(this.mainCarouselState().selectedScrollSnap - index) <= 2; | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.