Skip to content

Commit

Permalink
feat(design): add a media-gallery component (#1206)
Browse files Browse the repository at this point in the history
Co-authored-by: xelaint <xelaint@gmail.com>
Co-authored-by: lderrickable <lderrickable@gmail.com>
Co-authored-by: damienwebdev <damien@graycore.io>
  • Loading branch information
4 people committed Apr 2, 2021
1 parent f81c7ef commit a131764
Show file tree
Hide file tree
Showing 42 changed files with 1,239 additions and 4 deletions.
4 changes: 3 additions & 1 deletion .stylelintrc
Expand Up @@ -82,7 +82,9 @@
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-pseudo-element-no-unknown": [true, {
"ignorePseudoElements": ["ng-deep"]
}],
"selector-type-case": "lower",
"selector-max-id": 0,
"no-missing-end-of-source-newline": true,
Expand Down
1 change: 1 addition & 0 deletions apps/design-land/src/app/app-routing.module.ts
Expand Up @@ -26,6 +26,7 @@ export const appRoutes: Routes = [
{ path: 'image', loadChildren: () => import('./image/image.module').then(m => m.DesignLandImageModule) },
{ path: 'image-gallery', loadChildren: () => import('./image-gallery/image-gallery.module').then(m => m.ImageGalleryModule) },
{ path: 'navbar', loadChildren: () => import('./navbar/navbar.module').then(m => m.NavbarModule) },
{ path: 'media-gallery', loadChildren: () => import('./media-gallery/media-gallery.module').then(m => m.DesignLandMediaGalleryModule) },
{ path: 'modal', loadChildren: () => import('./modal/modal.module').then(m => m.ModalModule) },
{ path: 'paginator', loadChildren: () => import('./paginator/paginator.module').then(m => m.PaginatorModule) },
{ path: 'progress-indicator', loadChildren: () => import('./progress-indicator/progress-indicator.module').then(m => m.ProgressIndicatorModule) },
Expand Down
1 change: 1 addition & 0 deletions apps/design-land/src/app/app.component.html
Expand Up @@ -28,6 +28,7 @@
<a daff-link-set-item routerLink="link-set">Link Set</a>
<a daff-link-set-item routerLink="list">List</a>
<a daff-link-set-item routerLink="navbar">Navigation Bar</a>
<a daff-link-set-item routerLink="media-gallery">Media Gallery</a>
<a daff-link-set-item routerLink="modal">Modal</a>
<a daff-link-set-item routerLink="paginator">Paginator</a>
<a daff-link-set-item routerLink="qty-dropdown">Quantity Dropdown</a>
Expand Down
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import {
Routes,
RouterModule,
} from '@angular/router';

import { DesignLandMediaGalleryComponent } from './media-gallery.component';

export const mediaGalleryRoutes: Routes = [
{ path: '', component: DesignLandMediaGalleryComponent },
];

@NgModule({
imports: [
RouterModule.forChild(mediaGalleryRoutes),
],
exports: [
RouterModule,
],
})
export class DesignLandMediaGalleryRoutingModule {}
@@ -0,0 +1,19 @@
<daff-article>
<h1 daffArticleTitle>Media Gallery</h1>
<p daffArticleLead><code>&lt;daff-media-gallery&gt;</code> is used to display a group of <code>[daffThumbnail]</code>s in a gallery format. Media galleries are useful to showcase multiple images related to a single product or topic.</p>

<h2>Thumbnail</h2>
<p><code>[daffThumbnail]</code>should be used as a directive with <code>&lt;daff-image&gt;</code>. (View <a href="/image">Image Documentation</a>)</p>
<p>It should never be used as a standalone component. The first thumbnail is selected by default and dynamically rendered as the primary image by utilizing the <code>&lt;daff-media-renderer&gt;</code> component. The selected thumbnail can be controlled by the enduser, and the position of the list of thumbnails is dependent on the screen size.</p>

<design-land-example-viewer-container example="basic-media-gallery"></design-land-example-viewer-container>

<h2>Image Aspect Ratio</h2>
<p>It's recommended to utilize the same aspect ratio for all images in the same media gallery. Otherwise, the height and width of the media gallery may change with every different aspect ratio presented by the selected thumbnail as show in the example.</p>

<p>The thumbnail dimension is set to <code>80x80</code> pixels, so the recommended aspect ratio is <code>1:1</code>. However, it is not required since the thumbnail will horizontally and vertically center align images within a thumbnail.</p>
<design-land-example-viewer-container example="mismatched-sizes-media-gallery"></design-land-example-viewer-container>

<h2>Accessibility</h2>
<p>Accessibility considerations for media gallery is handled by the <code>DaffImageComponent</code>. The <code>alt</code> attribute must be defined in <code>&lt;daff-image&gt;</code>. It specifies an alternate text for an image. An error will appear if it's not defined. This is important because it allows screen readers to describe what's in the image for visually impaired people. (View <a href="/image">Image Documentation</a>)</p>
</daff-article>
Empty file.
@@ -0,0 +1,29 @@
import {
async,
ComponentFixture,
TestBed,
} from '@angular/core/testing';

import { DesignLandMediaGalleryComponent } from './media-gallery.component';

describe('DesignLandMediaGalleryComponent', () => {
let component: DesignLandMediaGalleryComponent;
let fixture: ComponentFixture<DesignLandMediaGalleryComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DesignLandMediaGalleryComponent ],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(DesignLandMediaGalleryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
11 changes: 11 additions & 0 deletions apps/design-land/src/app/media-gallery/media-gallery.component.ts
@@ -0,0 +1,11 @@
import {
Component,
OnInit,
} from '@angular/core';

@Component({
selector: 'design-land-media-gallery',
templateUrl: './media-gallery.component.html',
styleUrls: ['./media-gallery.component.scss'],
})
export class DesignLandMediaGalleryComponent {}
53 changes: 53 additions & 0 deletions apps/design-land/src/app/media-gallery/media-gallery.module.ts
@@ -0,0 +1,53 @@
import { CommonModule } from '@angular/common';
import {
NgModule,
Injector,
ComponentFactoryResolver,
} from '@angular/core';
import { createCustomElement } from '@angular/elements';


import {
DaffArticleModule,
DaffMediaGalleryModule,
DaffImageModule,
} from '@daffodil/design';
import { MEDIA_GALLERY_EXAMPLES } from '@daffodil/design/media-gallery/examples';

import { DesignLandExampleViewerModule } from '../core/code-preview/container/example-viewer.module';
import { DesignLandMediaGalleryRoutingModule } from './media-gallery-routing-module';
import { DesignLandMediaGalleryComponent } from './media-gallery.component';


@NgModule({
declarations: [
DesignLandMediaGalleryComponent,
],
imports: [
CommonModule,
DesignLandMediaGalleryRoutingModule,
DesignLandExampleViewerModule,

DaffArticleModule,
DaffMediaGalleryModule,
DaffImageModule,
],
})
export class DesignLandMediaGalleryModule {
constructor(
injector: Injector,
private componentFactoryResolver: ComponentFactoryResolver,
) {
MEDIA_GALLERY_EXAMPLES.map((classConstructor) => ({
element: createCustomElement(classConstructor, { injector }),
class: classConstructor,
}))
.map((customElement) => {
// Register the custom element with the browser.
customElements.define(
this.componentFactoryResolver.resolveComponentFactory(customElement.class).selector + '-example',
customElement.element,
);
});
}
}
8 changes: 8 additions & 0 deletions libs/design/media-gallery/examples/ng-package.json
@@ -0,0 +1,8 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/design/examples",
"deleteDestPath": false,
"lib": {
"entryFile": "src/index.ts"
}
}
3 changes: 3 additions & 0 deletions libs/design/media-gallery/examples/package.json
@@ -0,0 +1,3 @@
{
"name": "@daffodil/design/media-gallery/examples"
}
@@ -0,0 +1,7 @@
<daff-media-gallery>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1556804335-2fa563e93aae?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=3763&q=80" alt="Drink" width="946" height="946"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1607344635159-59930e3330b1?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1600&q=80" alt="Drink" width="946" height="946"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1584559582213-787a2953dcbe?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1600&q=80" alt="Fruits" width="946" height="946"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1587324438673-56c78a866b15?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1600&q=80" alt="Lemons" width="946" height="946"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1543363136-7fbfcd3b240d?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1600&q=80" alt="Avocado" width="946" height="946"></daff-image>
</daff-media-gallery>
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'basic-media-gallery',
templateUrl: './basic-media-gallery.component.html',
})
export class BasicMediaGalleryComponent {


}
@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';

import {
DaffMediaGalleryModule,
DaffImageModule,
} from '@daffodil/design';

import { BasicMediaGalleryComponent } from './basic-media-gallery.component';

@NgModule({
declarations: [
BasicMediaGalleryComponent,
],
exports: [
BasicMediaGalleryComponent,
],
imports: [
DaffImageModule,
DaffMediaGalleryModule,
],
providers: [],
})
export class BasicMediaGalleryModule { }
7 changes: 7 additions & 0 deletions libs/design/media-gallery/examples/src/examples.ts
@@ -0,0 +1,7 @@
import { BasicMediaGalleryComponent } from './basic-media-gallery/basic-media-gallery.component';
import { MismatchedSizesMediaGalleryComponent } from './mismatched-sizes-media-gallery/mismatched-sizes-media-gallery.component';

export const MEDIA_GALLERY_EXAMPLES = [
BasicMediaGalleryComponent,
MismatchedSizesMediaGalleryComponent,
];
1 change: 1 addition & 0 deletions libs/design/media-gallery/examples/src/index.ts
@@ -0,0 +1 @@
export * from './public_api';
@@ -0,0 +1,7 @@
<daff-media-gallery>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1556804335-2fa563e93aae?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=3763&q=80" alt="Drink" width="946" height="946"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1551410224-699683e15636?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1300&q=80" alt="Drink" width="640" height="799"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1586788224331-947f68671cf1?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=934&q=80" alt="Fruits" width="640" height="960"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1559181567-c3190ca9959b?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1300&q=80" alt="Cherries" width="640" height="800"></daff-image>
<daff-image daffThumbnail src="https://images.unsplash.com/photo-1606926167690-07a1edafd407?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=2250&q=80" alt="Lemons" width="640" height="426"></daff-image>
</daff-media-gallery>
@@ -0,0 +1,8 @@
import { Component } from '@angular/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'mismatched-sizes-media-gallery',
templateUrl: './mismatched-sizes-media-gallery.component.html',
})
export class MismatchedSizesMediaGalleryComponent {}
@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';

import {
DaffMediaGalleryModule,
DaffImageModule,
} from '@daffodil/design';

import { MismatchedSizesMediaGalleryComponent } from './mismatched-sizes-media-gallery.component';

@NgModule({
declarations: [
MismatchedSizesMediaGalleryComponent,
],
exports: [
MismatchedSizesMediaGalleryComponent,
],
imports: [
DaffImageModule,
DaffMediaGalleryModule,
],
providers: [],
})
export class MismatchedSizesMediaGalleryModule { }
4 changes: 4 additions & 0 deletions libs/design/media-gallery/examples/src/public_api.ts
@@ -0,0 +1,4 @@
export { MEDIA_GALLERY_EXAMPLES } from './examples';

export { BasicMediaGalleryModule } from './basic-media-gallery/basic-media-gallery.module';
export { MismatchedSizesMediaGalleryModule } from './mismatched-sizes-media-gallery/mismatched-sizes-media-gallery.module';
2 changes: 1 addition & 1 deletion libs/design/src/atoms/image/image.component.html
@@ -1,3 +1,3 @@
<div class="daff-image__wrapper" [style.paddingTop]="paddingTop">
<div class="daff-image__wrapper" [style.paddingTop]="_paddingTop">
<img [src]="src" [alt]="alt" (load)="load.emit" />
</div>
5 changes: 4 additions & 1 deletion libs/design/src/atoms/image/image.component.scss
Expand Up @@ -2,20 +2,23 @@

:host {
display: inline-block;
position: relative;
width: 100%;

img {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
height: auto;
margin: auto;
max-width: 100%;
}
}

.daff-image {
&__wrapper {
position: relative;
height: 0;
}
}
10 changes: 9 additions & 1 deletion libs/design/src/atoms/image/image.component.ts
Expand Up @@ -9,6 +9,8 @@ import {
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

import { daffThumbnailCompatToken } from '../../molecules/media-gallery/public_api';

const validateProperty = (object: Record<string, any>, prop: string) => {
if (object[prop] === null || object[prop] === undefined || object[prop] === '') {
throw new Error(`DaffImageComponent must have a defined ${prop} attribute.`);
Expand All @@ -35,6 +37,12 @@ const validateProperties = (object: Record<string, any>, props: string[]) => {
templateUrl: './image.component.html',
styleUrls: ['./image.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
// eslint-disable-next-line @typescript-eslint/no-use-before-define
provide: daffThumbnailCompatToken, useExisting: DaffImageComponent,
},
],
})
export class DaffImageComponent implements OnInit {

Expand Down Expand Up @@ -98,7 +106,7 @@ export class DaffImageComponent implements OnInit {
/**
* @docs-private
*/
get paddingTop(): any {
get _paddingTop(): any {
if (!this.height || !this.width ) {
return undefined;
}
Expand Down
3 changes: 3 additions & 0 deletions libs/design/src/atoms/image/image.module.ts
Expand Up @@ -13,5 +13,8 @@ import { DaffImageComponent } from './image.component';
exports: [
DaffImageComponent,
],
entryComponents: [
DaffImageComponent,
],
})
export class DaffImageModule { }
19 changes: 19 additions & 0 deletions libs/design/src/molecules/media-gallery/README.md
@@ -0,0 +1,19 @@
# Media Gallery
`<daff-media-gallery>` is used to display a group of `[daffThumbnail]`s in a gallery format. Media galleries are useful to showcase multiple images related to a single product or topic.

## Thumbnail
`[daffThumbnail]` should be used as a directive with `<daff-image>`. [View Image Documentation](/libs/design/src/atoms/image/README.md)

It should never be used as a standalone component. The first thumbnail is selected by default and dynamically rendered as the primary image by utilizing the `<daff-media-renderer>` component. The selected thumbnail can be controlled by the enduser, and the position of the list of thumbnails is dependent on the screen size.

<design-land-example-viewer-container example="basic-media-gallery"></design-land-example-viewer-container>

## Image Aspect Ratio
It's recommended to utilize the same aspect ratio for all images in the same media gallery. Otherwise, the height and width of the media gallery may change with every different aspect ratio presented by the selected thumbnail as show in the example.

The thumbnail dimension is set to `80x80` pixels, so the recommended aspect ratio is `1:1`. However, it is not required since the thumbnail will horizontally and vertically center align images within a thumbnail.

<design-land-example-viewer-container example="mismatched-sizes-media-gallery"></design-land-example-viewer-container>

## Accessibility
Accessibility considerations for media gallery is handled by the `DaffImageComponent`. The `alt` attribute must be defined in `<daff-image>`. It specifies an alternate text for an image. An error will appear if it's not defined. This is important because it allows screen readers to describe what's in the image for visually impaired people. [View Image Documentation](/libs/design/src/atoms/image/README.md)
22 changes: 22 additions & 0 deletions libs/design/src/molecules/media-gallery/media-gallery-theme.scss
@@ -0,0 +1,22 @@
@mixin daff-media-gallery-theme($theme) {
$primary: map-get($theme, primary);
$secondary: map-get($theme, secondary);
$tertiary: map-get($theme, tertiary);
$base: daff-map-deep-get($theme, 'core.base');
$white: daff-map-deep-get($theme, 'core.white');
$black: daff-map-deep-get($theme, 'core.black');
$gray: daff-configure-palette($daff-gray, 60);

.daff-media-gallery {
$root: &;

.daff-thumbnail {
border: 1px solid transparent;
transition: border 150ms;

&--selected {
border: 1px solid daff-color($gray);
}
}
}
}

0 comments on commit a131764

Please sign in to comment.