diff --git a/package-lock.json b/package-lock.json index fd89a98d..00a6547a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -357,47 +357,6 @@ "webpack": "^5.1.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/eslint-scope": { "version": "5.1.1", "dev": true, diff --git a/src/app/_samples/mediaco/components/banner/banner.component.scss b/src/app/_samples/mediaco/components/banner/banner.component.scss index b22f90e2..55efd4f7 100644 --- a/src/app/_samples/mediaco/components/banner/banner.component.scss +++ b/src/app/_samples/mediaco/components/banner/banner.component.scss @@ -2,7 +2,7 @@ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: calc(2 * 0.5rem); - row-gap: 1.5rem; + gap: 1.5rem; align-items: start; } diff --git a/src/app/_samples/mediaco/components/quick-create/quick-create.component.html b/src/app/_samples/mediaco/components/quick-create/quick-create.component.html index 7b15284e..a3289b52 100644 --- a/src/app/_samples/mediaco/components/quick-create/quick-create.component.html +++ b/src/app/_samples/mediaco/components/quick-create/quick-create.component.html @@ -6,24 +6,10 @@

Get started

-
- -
- - - - - -
- - -
- - - - - -
+
+ + +
diff --git a/src/app/_samples/mediaco/components/quick-create/quick-create.component.scss b/src/app/_samples/mediaco/components/quick-create/quick-create.component.scss index 76a35394..6dda097b 100644 --- a/src/app/_samples/mediaco/components/quick-create/quick-create.component.scss +++ b/src/app/_samples/mediaco/components/quick-create/quick-create.component.scss @@ -43,26 +43,20 @@ $text-dark: #2c2c2c; } .masonry-wrapper { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: 1fr; + grid-auto-rows: 1px; gap: $gap-size; width: 100%; max-width: 1100px; + align-items: start; + grid-auto-flow: row dense; @media (min-width: 768px) { - flex-direction: row; - align-items: flex-start; + grid-template-columns: repeat(2, 1fr); } } -.masonry-column { - display: flex; - flex-direction: column; - gap: $gap-size; - flex: 1; - width: 100%; -} - // --- Card Styles --- .card { background: #fff; @@ -74,6 +68,10 @@ $text-dark: #2c2c2c; overflow: hidden; display: flex; flex-direction: column; + height: fit-content; + + // Dynamic grid-row-end will be set via JavaScript for true masonry effect + grid-row-end: span var(--row-span, auto); &:hover { .icon-box { diff --git a/src/app/_samples/mediaco/components/quick-create/quick-create.component.ts b/src/app/_samples/mediaco/components/quick-create/quick-create.component.ts index 36be0e77..75ae29fc 100644 --- a/src/app/_samples/mediaco/components/quick-create/quick-create.component.ts +++ b/src/app/_samples/mediaco/components/quick-create/quick-create.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { MatIcon } from '@angular/material/icon'; import { Utils } from '@pega/angular-sdk-components'; import { QUICK_LINKS_DATA } from './quick-create.utils'; +import { MasonryDirective } from '../../directives/masonry.directive'; interface QuickCreateProps { // If any, enter additional props that only exist on this component @@ -15,7 +16,7 @@ interface QuickCreateProps { selector: 'app-quick-create', templateUrl: './quick-create.component.html', styleUrls: ['./quick-create.component.scss'], - imports: [CommonModule, MatIcon] + imports: [CommonModule, MatIcon, MasonryDirective] }) export class QuickCreateComponent implements OnInit, OnChanges { @Input() pConn$: typeof PConnect; @@ -27,6 +28,7 @@ export class QuickCreateComponent implements OnInit, OnChanges { showCaseIcons$?: boolean; classFilter$: any; cases$: any = []; + constructor(private utils: Utils) {} ngOnInit() { diff --git a/src/app/_samples/mediaco/directives/masonry.directive.ts b/src/app/_samples/mediaco/directives/masonry.directive.ts new file mode 100644 index 00000000..253ca130 --- /dev/null +++ b/src/app/_samples/mediaco/directives/masonry.directive.ts @@ -0,0 +1,92 @@ +import { Directive, ElementRef, AfterViewInit, OnDestroy, Input, HostListener } from '@angular/core'; + +@Directive({ + selector: '[appMasonry]', + standalone: true +}) +export class MasonryDirective implements AfterViewInit, OnDestroy { + @Input() masonryGap: number = 16; + @Input() masonryRowHeight: number = 1; + @Input() masonryItemSelector: string = '.card'; + + private resizeTimeout: any; + private mutationObserver: MutationObserver | null = null; + + constructor(private elementRef: ElementRef) {} + + ngAfterViewInit() { + // Apply masonry layout after view is initialized + setTimeout(() => { + this.applyMasonryLayout(); + }, 100); + + // Watch for DOM changes (new items added/removed) + this.setupMutationObserver(); + } + + ngOnDestroy() { + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + } + } + + @HostListener('window:resize', ['$event']) + onWindowResize() { + // Throttle resize events to avoid excessive recalculations + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + + this.resizeTimeout = setTimeout(() => { + this.applyMasonryLayout(); + }, 150); + } + + private setupMutationObserver() { + this.mutationObserver = new MutationObserver(() => { + // Debounce DOM changes + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + + this.resizeTimeout = setTimeout(() => { + this.applyMasonryLayout(); + }, 100); + }); + + this.mutationObserver.observe(this.elementRef.nativeElement, { + childList: true, + subtree: true + }); + } + + private applyMasonryLayout() { + const grid = this.elementRef.nativeElement; + if (!grid) return; + + const items = grid.querySelectorAll(this.masonryItemSelector); + if (items.length === 0) return; + + // Reset any existing row spans first + items.forEach((item: HTMLElement) => { + item.style.removeProperty('--row-span'); + }); + + // Wait for layout to settle after reset, then recalculate + requestAnimationFrame(() => { + items.forEach((item: HTMLElement) => { + const itemHeight = item.getBoundingClientRect().height; + const rowSpan = Math.ceil((itemHeight + this.masonryGap) / (this.masonryRowHeight + this.masonryGap)); + item.style.setProperty('--row-span', rowSpan.toString()); + }); + }); + } + + // Public method to manually trigger layout recalculation + public recalculateLayout() { + this.applyMasonryLayout(); + } +}