(fuse/masonry) Added a new component (and its docs) for creating Masonry layouts

This commit is contained in:
sercan 2021-05-03 18:47:57 +03:00
parent b05763135e
commit e7a1d386a6
11 changed files with 551 additions and 0 deletions

View File

@ -0,0 +1 @@
export * from '@fuse/components/card/public-api';

View File

@ -0,0 +1,3 @@
<div class="flex">
<ng-container *ngTemplateOutlet="columnsTemplate; context: { $implicit: distributedColumns }"></ng-container>
</div>

View File

@ -0,0 +1,97 @@
import { AfterViewInit, Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core';
import { FuseAnimations } from '@fuse/animations';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
@Component({
selector : 'fuse-masonry',
templateUrl : './masonry.component.html',
styleUrls : ['./masonry.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : FuseAnimations,
exportAs : 'fuseMasonry'
})
export class FuseMasonryComponent implements OnChanges, AfterViewInit
{
@Input() columnsTemplate: TemplateRef<any>;
@Input() columns: number;
@Input() items: any[] = [];
distributedColumns: any[] = [];
/**
* Constructor
*/
constructor(private _fuseMediaWatcherService: FuseMediaWatcherService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On changes
*
* @param changes
*/
ngOnChanges(changes: SimpleChanges): void
{
// Columns
if ( 'columns' in changes )
{
if ( changes.columns.isFirstChange() )
{
return;
}
// Distribute the items
this._distributeItems();
}
// Items
if ( 'items' in changes )
{
if ( changes.columns.isFirstChange() )
{
return;
}
// Distribute the items
this._distributeItems();
}
}
/**
* After view init
*/
ngAfterViewInit(): void
{
// Distribute the items for the first time
this._distributeItems();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Distribute items into columns
*/
private _distributeItems(): void
{
// Return an empty array if there are no items
if ( this.items.length === 0 )
{
this.distributedColumns = [];
return;
}
// Prepare the distributed columns array
this.distributedColumns = Array.from(Array(this.columns), item => ({items: []}));
// Distribute the items to columns
for ( let i = 0; i < this.items.length; i++ )
{
this.distributedColumns[i % this.columns].items.push(this.items[i]);
}
}
}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FuseMasonryComponent } from '@fuse/components/masonry/masonry.component';
@NgModule({
declarations: [
FuseMasonryComponent
],
imports : [
CommonModule
],
exports : [
FuseMasonryComponent
]
})
export class FuseMasonryModule
{
}

View File

@ -0,0 +1,2 @@
export * from '@fuse/components/masonry/masonry.component';
export * from '@fuse/components/masonry/masonry.module';

View File

@ -0,0 +1,334 @@
<div class="flex flex-col flex-auto min-w-0">
<!-- Header -->
<div class="flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between p-6 sm:py-8 sm:px-10 border-b bg-card dark:bg-transparent">
<div class="flex-1 min-w-0">
<!-- Breadcrumbs -->
<div class="flex flex-wrap items-center font-medium">
<div>
<a class="whitespace-nowrap text-primary-500">Documentation</a>
</div>
<div class="flex items-center ml-1 whitespace-nowrap">
<mat-icon
class="icon-size-5 text-secondary"
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
<a class="ml-1 text-primary-500">Core Features</a>
</div>
<div class="flex items-center ml-1 whitespace-nowrap">
<mat-icon
class="icon-size-5 text-secondary"
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
<span class="ml-1 text-secondary">Components</span>
</div>
</div>
<!-- Title -->
<div class="mt-2">
<h2 class="text-3xl md:text-4xl font-extrabold tracking-tight leading-7 sm:leading-10 truncate">
Masonry
</h2>
</div>
</div>
<button
class="-ml-3 sm:ml-0 mb-2 sm:mb-0 order-first sm:order-last"
mat-icon-button
(click)="toggleDrawer()">
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
</button>
</div>
<div class="flex-auto max-w-3xl p-6 sm:p-10 prose prose-sm">
<p>
<strong>fuse-masonry</strong> is a basic Masonry layout component to show items in Masonry grid layout. Since it doesn't use any other third party library or complex calculations to
keep everything smooth and fast;
</p>
<ul>
<li>It does NOT work with elements with different widths
<li>It does NOT sort items based on their heights (This here is the real performance killer)</li>
</ul>
<p>
If you don't need to mix and match different width items and don't need to sort items based on their heights, <strong>fuse-masonry</strong> will do the job for you and it will do it very smoothly.
</p>
<p>
<strong>Exported as: </strong><code>fuseMasonry</code>
</p>
<h2>Module</h2>
<textarea
fuse-highlight
lang="typescript">
import { FuseMasonry } from '@fuse/components/masonry';
</textarea>
<h2>Properties</h2>
<div class="bg-card py-3 px-6 rounded shadow">
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-mono text-md text-secondary">
<div>@Input()</div>
<div>columnsTemplate: TemplateRef</div>
</td>
<td>
Column template for masonry component to lay out.
</td>
<td>
<code class="whitespace-nowrap">undefined</code>
</td>
</tr>
<tr>
<td class="font-mono text-md text-secondary">
<div>@Input()</div>
<div>columns: number</div>
</td>
<td>
Number of columns to create
</td>
<td>
<code class="whitespace-nowrap">undefined</code>
</td>
</tr>
<tr>
<td class="font-mono text-md text-secondary">
<div>@Input()</div>
<div>items: any[]</div>
</td>
<td>
Items to distribute into columns
</td>
<td>
<code class="whitespace-nowrap">[]</code>
</td>
</tr>
</tbody>
</table>
</div>
<h2>Methods</h2>
<p>This component doesn't have any public methods.</p>
<h2>Usage</h2>
<p>
<strong>fuse-masonry</strong> doesn't provide a default template or styling to the items. You can think of it as a helper function but in a component form:
</p>
<div class="example-viewer">
<div class="title">
<h6>Example</h6>
</div>
<mat-tab-group [animationDuration]="'0ms'">
<mat-tab label="Preview">
<ng-template matTabContent>
<div class="bg-gray-100 p-4">
<div class="mx-auto max-w-80">
<fuse-masonry
class="-mx-2"
[items]="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
[columns]="4"
[columnsTemplate]="columnsTemplate">
</fuse-masonry>
<ng-template
#columnsTemplate
let-columns>
<ng-container *ngFor="let column of columns">
<!-- Column -->
<div class="flex-1 mx-2 p-2 border rounded space-y-2">
<ng-container *ngFor="let item of column.items">
<!-- Item -->
<div class="p-2 text-center rounded bg-primary text-on-primary">
{{item}}
</div>
</ng-container>
</div>
</ng-container>
</ng-template>
</div>
</div>
</ng-template>
</mat-tab>
<mat-tab label="HTML">
<!-- @formatter:off -->
<textarea
fuse-highlight
[lang]="'html'">
<fuse-masonry
class="-mx-2"
[items]="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
[columns]="4"
[columnsTemplate]="columnsTemplate">
</fuse-masonry>
<ng-template
#columnsTemplate
let-columns>
<ng-container *ngFor="let column of columns">
<!-- Column -->
<div class="flex-1 mx-2 p-2 border rounded space-y-2">
<ng-container *ngFor="let item of column.items">
<!-- Item -->
<div class="p-2 text-center rounded bg-primary text-on-primary">
ITEM CONTENT
</div>
</ng-container>
</div>
</ng-container>
</ng-template>
</textarea>
<!-- @formatter:on -->
</mat-tab>
</mat-tab-group>
</div>
<h3>Responsive</h3>
<p>
<strong>fuse-masonry</strong> doesn't provide a way of changing the column count depending on breakpoints but this can easily be handled by
combining <code>fuse-masonry</code> with <code>FuseMediaWatcherService</code>:
</p>
<div class="example-viewer">
<div class="title">
<h6>Example</h6>
</div>
<mat-tab-group [animationDuration]="'0ms'">
<mat-tab label="Preview">
<ng-template matTabContent>
<div class="bg-gray-100 p-4">
<div class="mx-auto max-w-80">
<fuse-masonry
class="-mx-2"
[items]="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
[columns]="columns"
[columnsTemplate]="columnsTemplate">
</fuse-masonry>
<ng-template
#columnsTemplate
let-columns>
<ng-container *ngFor="let column of columns">
<!-- Column -->
<div class="flex-1 mx-2 p-2 border rounded space-y-2">
<ng-container *ngFor="let item of column.items">
<!-- Item -->
<div class="p-2 text-center rounded bg-primary text-on-primary">
{{item}}
</div>
</ng-container>
</div>
</ng-container>
</ng-template>
</div>
</div>
</ng-template>
</mat-tab>
<mat-tab label="HTML">
<!-- @formatter:off -->
<textarea
fuse-highlight
[lang]="'html'">
<fuse-masonry
class="-mx-2"
[items]="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
[columns]="columns"
[columnsTemplate]="columnsTemplate">
</fuse-masonry>
<ng-template
#columnsTemplate
let-columns>
<ng-container *ngFor="let column of columns">
<!-- Column -->
<div class="flex-1 mx-2 p-2 border rounded space-y-2">
<ng-container *ngFor="let item of column.items">
<!-- Item -->
<div class="p-2 text-center rounded bg-primary text-on-primary">
ITEM CONTENT
</div>
</ng-container>
</div>
</ng-container>
</ng-template>
</textarea>
<!-- @formatter:on -->
</mat-tab>
<mat-tab label="TypeScript">
<!-- @formatter:off -->
<textarea
fuse-highlight
[lang]="'ts'">
columns: number = 4;
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
// Set the masonry columns
//
// This if block structured in a way so that only the
// biggest matching alias will be used to set the column
// count.
if ( matchingAliases.includes('xl') )
{
this.columns = 5;
}
else if ( matchingAliases.includes('lg') )
{
this.columns = 4;
}
else if ( matchingAliases.includes('md') )
{
this.columns = 3;
}
else if ( matchingAliases.includes('sm') )
{
this.columns = 2;
}
else
{
this.columns = 1;
}
});
</textarea>
<!-- @formatter:on -->
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
import { Component, OnInit } from '@angular/core';
import { CoreFeaturesComponent } from 'app/modules/admin/docs/core-features/core-features.component';
import { Subject } from 'rxjs';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { takeUntil } from 'rxjs/operators';
@Component({
selector : 'masonry',
templateUrl: './masonry.component.html',
styles : ['']
})
export class MasonryComponent implements OnInit
{
columns: number = 4;
private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _coreFeaturesComponent: CoreFeaturesComponent,
private _fuseMediaWatcherService: FuseMediaWatcherService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
// Set the masonry columns
//
// This if block structured in a way so that only the
// biggest matching alias will be used to set the column
// count.
if ( matchingAliases.includes('xl') )
{
this.columns = 5;
}
else if ( matchingAliases.includes('lg') )
{
this.columns = 4;
}
else if ( matchingAliases.includes('md') )
{
this.columns = 3;
}
else if ( matchingAliases.includes('sm') )
{
this.columns = 2;
}
else
{
this.columns = 1;
}
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Toggle the drawer
*/
toggleDrawer(): void
{
// Toggle the drawer
this._coreFeaturesComponent.matDrawer.toggle();
}
}

View File

@ -77,6 +77,12 @@ export class CoreFeaturesComponent implements OnInit, OnDestroy
type : 'basic',
link : '/docs/core-features/components/highlight'
},
{
id : 'core-features.components.masonry',
title: 'Masonry',
type : 'basic',
link : '/docs/core-features/components/masonry'
},
{
id : 'core-features.components.navigation',
title: 'Navigation',

View File

@ -10,6 +10,7 @@ import { FuseDateRangeModule } from '@fuse/components/date-range';
import { FuseDrawerModule } from '@fuse/components/drawer';
import { FuseHighlightModule } from '@fuse/components/highlight';
import { FuseAlertModule } from '@fuse/components/alert';
import { FuseMasonryModule } from '@fuse/components/masonry/masonry.module';
import { FuseNavigationModule } from '@fuse/components/navigation';
import { FuseScrollResetModule } from '@fuse/directives/scroll-reset';
import { SharedModule } from 'app/shared/shared.module';
@ -21,6 +22,7 @@ import { DateRangeComponent } from 'app/modules/admin/docs/core-features/compone
import { DrawerComponent } from 'app/modules/admin/docs/core-features/components/drawer/drawer.component';
import { HighlightComponent } from 'app/modules/admin/docs/core-features/components/highlight/highlight.component';
import { NavigationComponent } from 'app/modules/admin/docs/core-features/components/navigation/navigation.component';
import { MasonryComponent } from 'app/modules/admin/docs/core-features/components/masonry/masonry.component';
import { AutogrowComponent } from 'app/modules/admin/docs/core-features/directives/autogrow/autogrow.component';
import { ScrollbarComponent } from 'app/modules/admin/docs/core-features/directives/scrollbar/scrollbar.component';
import { ScrollResetComponent } from 'app/modules/admin/docs/core-features/directives/scroll-reset/scroll-reset.component';
@ -40,6 +42,7 @@ import { coreFeaturesRoutes } from 'app/modules/admin/docs/core-features/core-fe
DateRangeComponent,
DrawerComponent,
HighlightComponent,
MasonryComponent,
NavigationComponent,
AutogrowComponent,
ScrollbarComponent,
@ -62,6 +65,7 @@ import { coreFeaturesRoutes } from 'app/modules/admin/docs/core-features/core-fe
FuseDateRangeModule,
FuseDrawerModule,
FuseHighlightModule,
FuseMasonryModule,
FuseNavigationModule,
FuseScrollResetModule,
SharedModule

View File

@ -6,6 +6,7 @@ import { CardComponent } from 'app/modules/admin/docs/core-features/components/c
import { DateRangeComponent } from 'app/modules/admin/docs/core-features/components/date-range/date-range.component';
import { DrawerComponent } from 'app/modules/admin/docs/core-features/components/drawer/drawer.component';
import { HighlightComponent } from 'app/modules/admin/docs/core-features/components/highlight/highlight.component';
import { MasonryComponent } from 'app/modules/admin/docs/core-features/components/masonry/masonry.component';
import { NavigationComponent } from 'app/modules/admin/docs/core-features/components/navigation/navigation.component';
import { AutogrowComponent } from 'app/modules/admin/docs/core-features/directives/autogrow/autogrow.component';
import { ScrollbarComponent } from 'app/modules/admin/docs/core-features/directives/scrollbar/scrollbar.component';
@ -63,6 +64,10 @@ export const coreFeaturesRoutes: Route[] = [
path : 'highlight',
component: HighlightComponent
},
{
path : 'masonry',
component: MasonryComponent
},
{
path : 'navigation',
component: NavigationComponent