From 5b84e733a1707617ceb2d677c600e8fded719728 Mon Sep 17 00:00:00 2001 From: Yiran Mao Date: Fri, 10 Mar 2017 15:21:12 -0500 Subject: [PATCH] feat(grid-list): Implement mdc-grid-list (#47) (#359) --- demos/grid-list.html | 666 ++++++++++++++++++ demos/index.html | 1 + packages/material-components-web/index.js | 3 + .../material-components-web.scss | 1 + packages/material-components-web/package.json | 1 + packages/mdc-grid-list/README.md | 264 +++++++ packages/mdc-grid-list/constants.js | 19 + packages/mdc-grid-list/foundation.js | 61 ++ packages/mdc-grid-list/index.js | 41 ++ packages/mdc-grid-list/mdc-grid-list.scss | 194 +++++ packages/mdc-grid-list/package.json | 24 + test/unit/mdc-grid-list/foundation.test.js | 96 +++ test/unit/mdc-grid-list/mdc-grid-list.test.js | 69 ++ webpack.config.js | 2 + 14 files changed, 1442 insertions(+) create mode 100644 demos/grid-list.html create mode 100644 packages/mdc-grid-list/README.md create mode 100644 packages/mdc-grid-list/constants.js create mode 100644 packages/mdc-grid-list/foundation.js create mode 100644 packages/mdc-grid-list/index.js create mode 100644 packages/mdc-grid-list/mdc-grid-list.scss create mode 100644 packages/mdc-grid-list/package.json create mode 100644 test/unit/mdc-grid-list/foundation.test.js create mode 100644 test/unit/mdc-grid-list/mdc-grid-list.test.js diff --git a/demos/grid-list.html b/demos/grid-list.html new file mode 100644 index 00000000000..e8cd6c3cdf1 --- /dev/null +++ b/demos/grid-list.html @@ -0,0 +1,666 @@ + + + + + + MDC Grid List Demo + + + + + + + +

MDC Grid List

+ +
+

Grid List (Default): tile aspect ratio 1x1 with oneline footer caption

+
+
    +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with 1px gutter

+
+
    +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: tile aspect ratio 1x1 image only

+
+
    +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
+
+

Grid List: tile aspect ratio 1x1 with oneline header caption

+
+
    +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with twoline footer caption

+
+
    +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + Support text + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with oneline footer caption and icon at start of the caption

+
+
    +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with twoline footer caption and icon at start of the caption

+
+
    +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with oneline footer caption and icon at end of the caption

+
+
    +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: tile aspect ratio 1x1 with twoline footer caption and icon at end of the caption

+
+
    +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
  • +
    + +
    + + star_border + Single Very Long Grid Title Line + Support text + +
  • +
+
+

Grid List: tile aspect ratio 16x9 with oneline footer caption (Support: 16:9, 4:3, 3:4, 2:3, 3:2 as well)

+
+
    +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    + +
    + + Single Very Long Grid Title Line + +
  • +
+
+

Grid List: use div's background instead of img tag (useful when image ratio cannot be ensured)

+
+
    +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
  • +
    +
    +
    + + Single Very Long Grid Title Line + +
  • +
+
+
+ + + + \ No newline at end of file diff --git a/demos/index.html b/demos/index.html index c3c88d4e528..1be751b8989 100644 --- a/demos/index.html +++ b/demos/index.html @@ -33,6 +33,7 @@
  • Drawer (permanent, below toolbar)
  • Elevation
  • FAB
  • +
  • Grid List
  • Icon Toggle
  • Layout grid
  • List
  • diff --git a/packages/material-components-web/index.js b/packages/material-components-web/index.js index 883f1c20042..21e14e68bf4 100644 --- a/packages/material-components-web/index.js +++ b/packages/material-components-web/index.js @@ -17,6 +17,7 @@ import * as base from '@material/base'; import * as checkbox from '@material/checkbox'; import * as formField from '@material/form-field'; +import * as gridList from '@material/grid-list'; import * as iconToggle from '@material/icon-toggle'; import * as radio from '@material/radio'; import * as ripple from '@material/ripple'; @@ -31,6 +32,7 @@ import autoInit from '@material/auto-init'; autoInit.register('MDCCheckbox', checkbox.MDCCheckbox); autoInit.register('MDCTemporaryDrawer', drawer.MDCTemporaryDrawer); autoInit.register('MDCRipple', ripple.MDCRipple); +autoInit.register('MDCGridList', gridList.MDCGridList); autoInit.register('MDCIconToggle', iconToggle.MDCIconToggle); autoInit.register('MDCRadio', radio.MDCRadio); autoInit.register('MDCSnackbar', snackbar.MDCSnackbar); @@ -43,6 +45,7 @@ export { base, checkbox, formField, + gridList, iconToggle, radio, ripple, diff --git a/packages/material-components-web/material-components-web.scss b/packages/material-components-web/material-components-web.scss index 06a2ecd1d6a..4d5c691d6bf 100644 --- a/packages/material-components-web/material-components-web.scss +++ b/packages/material-components-web/material-components-web.scss @@ -22,6 +22,7 @@ @import "@material/elevation/mdc-elevation"; @import "@material/fab/mdc-fab"; @import "@material/form-field/mdc-form-field"; +@import "@material/grid-list/mdc-grid-list"; @import "@material/icon-toggle/mdc-icon-toggle"; @import "@material/layout-grid/mdc-layout-grid"; @import "@material/list/mdc-list"; diff --git a/packages/material-components-web/package.json b/packages/material-components-web/package.json index 9d179f6a4e1..150f5719fda 100644 --- a/packages/material-components-web/package.json +++ b/packages/material-components-web/package.json @@ -23,6 +23,7 @@ "@material/elevation": "^0.1.3", "@material/fab": "^0.3.3", "@material/form-field": "^0.2.1", + "@material/grid-list": "^0.0.0", "@material/icon-toggle": "^0.1.5", "@material/layout-grid": "^0.1.1", "@material/list": "^0.2.3", diff --git a/packages/mdc-grid-list/README.md b/packages/mdc-grid-list/README.md new file mode 100644 index 00000000000..6c655620702 --- /dev/null +++ b/packages/mdc-grid-list/README.md @@ -0,0 +1,264 @@ +# MDC Grid list + +MDC Grid list provides a RTL-aware Material Design Grid list component adhering to the +[Material Design Grid list spec](https://material.io/guidelines/components/grid-lists.html). +Grid Lists are best suited for presenting homogeneous data, typically images. +Each item in a grid list is called a **tile**. Tiles maintain consistent width, height, and padding +across screen sizes. + + +## Installation + +``` +npm install --save @material/grid-list +``` + + +## Usage + +Basic Grid list has the following structure: + +```html +
    + +
    +``` + +The above markup will give you a Grid list of tiles that: + +- Have 4px padding in between themselves +- Have a 1x1 aspect ratio +- Have a one-line footer caption with no icon + +You just need to put the content you want to load in `src` of +``. However, if your +assets don't have the same aspect ratio you as specified in the tile, it will +distort those assets. We provide a solution of that case in +[Using a div in place of an img](#using-a-div-in-place-of-an-img) section. + + +### Setting the tile width + +The tile width is set to 200px by default. There are three ways that you can +overwrite the default value for your grid list: + +1. Using CSS variables + + ```css + .mdc-grid-tile { + --mdc-grid-list-tile-width: 300px; + } + ``` + +2. Overwriting SCSS variable + + You can overwrite the scss variable by + + ```scss + @import "@material/grid-list/mdc-grid-list"; + $mdc-grid-list-tile-width: 300px; + ``` + +3. Add own style to tile + + ```html + +
    + +
    + ``` + +### Change tile padding + +Grid list tiles can have 1px padding instead of 4px by adding +`mdc-grid-list--tile-gutter-1` modifier. + +```html +
    + +
    +``` + +### Image only tile + +Grid lists support image only tile. You can remove `mdc-grid-tile__secondary` +and create a image only grid list. + +```html +
    + +
    +``` + +### Header caption + +Grid lists support header caption. You can change the footer caption to be a +header caption by adding `mdc-grid-list--header-caption` modifier. + +```html +
    + +
    +``` + +### Add support text to secondary content (caption) + +Grid lists support a one-line caption by default. You can add an additional line of support +text if needed by adding the `mdc-grid-list--twoline-caption` modifier and additional +markup + +```html +
    + +
    +``` + +### Add icon to secondary content (caption) + +You can add an icon to a caption by adding `mdc-grid-list--with-icon-align-start` or +`mdc-grid-list--with-icon-align-end` and changing the markup. + +```html +
    + +
    +``` + +### Change aspect ratio of tile + +Grid list tiles support all material guideline recommended aspect ratio: + +- 1x1 +- 16x9 +- 2x3 +- 3x2 +- 4x3 +- 3x4 + +You can use the modifier class `mdc-grid-list--tile-aspect-$ASPECT_RATIO` to apply these aspect +ratios to your grid list. Simply replace `$ASPECT_RATIO` with any of the predefined ratios. + +```html + +
    + +
    +``` + +As pointed out in the previous section, if your +assets don't have the same aspect ratio you as specified in the tile, it will +distort those assets. We provide a solution of that case in +[Using a div in place of an img](#using-a-div-in-place-of-an-img) section. + +### Using a div in place of an img + +In case you cannot ensure all your assets will have the same aspect ratio, you +can use `div` instead of `img` markup. It will resize the assets to cover the tile +and crop the assets to display the center part. + +```html + +
    + +
    +``` + +However, the method results in a less semantic markup, so we don't use this method by +default. + + +### RTL Support + +`mdc-grid-list` is automatically RTL-aware, and will re-position elements whenever +it, or its ancestors, have a `dir="rtl"` attribute. + + +### Theme + +`mdc-grid-list` supports theming. `mdc-grid-tile__primary` uses the theme's background +color for its background color. `mdc-grid-tile__secondary` uses the theme's primary +color for it's background color, and the theme's `text-primary-on-primary` color for its text color. + +### Using the Foundation Class + +MDCGridList ships with an `MDCGridListFoundation` class that external frameworks and libraries +can use to build their own MDCGridList components with minimal effort. As with all foundation +classes, an adapter object must be provided. The adapter for Grid list must provide the following +functions, with correct signatures: + +| Method Signature | Description | +| --- | --- | +| `getOffsetWidth() => number` | Get root element `mdc-grid-list` offsetWidth. | +| `getOffsetWidthForTileAtIndex(index: number) => number` | Get offsetWidth of `mdc-grid-tile` at specified index. | +| `setStyleForTilesElement(property: string, value: number) => void` | Set `mdc-grid-list__tiles` style property to provided value. | +| `registerResizeHandler(handler: Function) => void` | Registers a handler to be called when the surface (or its viewport) resizes. Our default implementation adds the handler as a listener to the window's `resize()` event. | +| `deregisterResizeHandler(handler: Function) => void` | Unregisters a handler to be called when the surface (or its viewport) resizes. Our default implementation removes the handler as a listener to the window's `resize()` event. | \ No newline at end of file diff --git a/packages/mdc-grid-list/constants.js b/packages/mdc-grid-list/constants.js new file mode 100644 index 00000000000..e766f0b624c --- /dev/null +++ b/packages/mdc-grid-list/constants.js @@ -0,0 +1,19 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const strings = { + TILES_SELECTOR: '.mdc-grid-list__tiles', + TILE_SELECTOR: '.mdc-grid-tile', +}; diff --git a/packages/mdc-grid-list/foundation.js b/packages/mdc-grid-list/foundation.js new file mode 100644 index 00000000000..5748c652b4b --- /dev/null +++ b/packages/mdc-grid-list/foundation.js @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {MDCFoundation} from '@material/base'; +import {strings} from './constants'; + +export default class MDCGridListFoundation extends MDCFoundation { + static get strings() { + return strings; + } + + static get defaultAdapter() { + return { + getOffsetWidth: () => /* number */ 0, + getOffsetWidthForTileAtIndex: (/* index: number */) => /* number */ 0, + setStyleForTilesElement: (/* property: string, value: string */) => {}, + registerResizeHandler: (/* handler: EventListener */) => {}, + deregisterResizeHandler: (/* handler: EventListener */) => {}, + }; + } + constructor(adapter) { + super(Object.assign(MDCGridListFoundation.defaultAdapter, adapter)); + this.resizeHandler_ = () => this.alignCenter(); + this.resizeFrame_ = 0; + } + init() { + this.alignCenter(); + this.adapter_.registerResizeHandler(this.resizeHandler_); + } + destroy() { + this.adapter_.deregisterResizeHandler(this.resizeHandler_); + } + alignCenter() { + if (this.resizeFrame_ !== 0) { + cancelAnimationFrame(this.resizeFrame_); + } + this.resizeFrame_ = requestAnimationFrame(() => { + this.alignCenter_(); + this.resizeFrame_ = 0; + }); + } + alignCenter_() { + const gridWidth = this.adapter_.getOffsetWidth(); + const itemWidth = this.adapter_.getOffsetWidthForTileAtIndex(0); + const tilesWidth = itemWidth * Math.floor(gridWidth / itemWidth); + this.adapter_.setStyleForTilesElement('width', `${tilesWidth}px`); + } +} diff --git a/packages/mdc-grid-list/index.js b/packages/mdc-grid-list/index.js new file mode 100644 index 00000000000..5716d005e33 --- /dev/null +++ b/packages/mdc-grid-list/index.js @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {MDCComponent} from '@material/base'; + +import MDCGridListFoundation from './foundation'; + +export {MDCGridListFoundation}; + +export class MDCGridList extends MDCComponent { + static attachTo(root) { + return new MDCGridList(root); + } + + getDefaultFoundation() { + return new MDCGridListFoundation({ + getOffsetWidth: () => this.root_.offsetWidth, + getOffsetWidthForTileAtIndex: (index) => { + return this.root_.querySelectorAll(MDCGridListFoundation.strings.TILE_SELECTOR)[index].offsetWidth; + }, + setStyleForTilesElement: (property, value) => { + this.root_.querySelector(MDCGridListFoundation.strings.TILES_SELECTOR).style[property] = value; + }, + registerResizeHandler: (handler) => window.addEventListener('resize', handler), + deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler), + }); + } +} diff --git a/packages/mdc-grid-list/mdc-grid-list.scss b/packages/mdc-grid-list/mdc-grid-list.scss new file mode 100644 index 00000000000..fb46345291c --- /dev/null +++ b/packages/mdc-grid-list/mdc-grid-list.scss @@ -0,0 +1,194 @@ +// +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@import "@material/rtl/mixins"; +@import "@material/theme/mixins"; +@import "@material/typography/mixins"; + +$mdc-grid-list-tile-width: 200px !default; +$mdc-grid-list-tile-secondary-padding: 16px; +$mdc-grid-list-tile-secondary-padding-narrow: 8px; +$mdc-grid-list-oneline-cap-secondary-height: 48px; +$mdc-grid-list-twoline-cap-secondary-height: 68px; +$mdc-grid-list-tile-secondary-icon-size: 24px; + +@mixin mdc-grid-list-tile-aspect($width-height-ratio) { + .mdc-grid-tile__primary { + padding-bottom: calc(100% / #{$width-height-ratio}); + } +} + +@mixin mdc-grid-list-tile-gutter($gutter-width) { + .mdc-grid-tile { + margin: $gutter-width/2 0; + padding: 0 $gutter-width/2; + + &__secondary { + left: $gutter-width/2; + width: calc(100% - #{$gutter-width}); + } + } + + .mdc-grid-list__tiles { + margin: $gutter-width/2 auto; + } +} + +// postcss-bem-linter: define grid-list +.mdc-grid-list { + @include mdc-grid-list-tile-aspect(1); + @include mdc-grid-list-tile-gutter(4px); + + &__tiles { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + padding: 0; + } +} + +.mdc-grid-list--tile-gutter-1 { + @include mdc-grid-list-tile-gutter(1px); +} + +.mdc-grid-list--tile-aspect-16x9 { + @include mdc-grid-list-tile-aspect(16 / 9); +} + +.mdc-grid-list--tile-aspect-3x2 { + @include mdc-grid-list-tile-aspect(3 / 2); +} + +.mdc-grid-list--tile-aspect-2x3 { + @include mdc-grid-list-tile-aspect(2 / 3); +} + +.mdc-grid-list--tile-aspect-4x3 { + @include mdc-grid-list-tile-aspect(4 / 3); +} + +.mdc-grid-list--tile-aspect-3x4 { + @include mdc-grid-list-tile-aspect(3 / 4); +} + +// stylelint-disable plugin/selector-bem-pattern +// We need to disable linter here because we are nesting grid-tile under grid list here. +.mdc-grid-list--twoline-caption { + .mdc-grid-tile__secondary { + height: $mdc-grid-list-twoline-cap-secondary-height; + } +} + +.mdc-grid-list--header-caption { + .mdc-grid-tile__secondary { + top: 0; + bottom: auto; + } +} + +.mdc-grid-list--with-icon-align-start { + .mdc-grid-tile__secondary { + @include mdc-rtl-reflexive-property(padding, $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size, $mdc-grid-list-tile-secondary-padding-narrow, ".mdc-grid-list"); + } + + .mdc-grid-tile__icon { + @include mdc-rtl-reflexive-position(left, $mdc-grid-list-tile-secondary-padding, ".mdc-grid-list"); + + font-size: $mdc-grid-list-tile-secondary-icon-size; + } +} + +.mdc-grid-list--with-icon-align-end { + .mdc-grid-tile__secondary { + @include mdc-rtl-reflexive-property(padding, $mdc-grid-list-tile-secondary-padding, $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size, ".mdc-grid-list"); + } + + .mdc-grid-tile__icon { + @include mdc-rtl-reflexive-position(right, $mdc-grid-list-tile-secondary-padding, ".mdc-grid-list"); + + font-size: $mdc-grid-list-tile-secondary-icon-size; + } +} +// stylelint-enable plugin/selector-bem-pattern +// postcss-bem-linter: end + +// postcss-bem-linter: define grid-tile +.mdc-grid-tile { + display: block; + position: relative; + width: var(--mdc-grid-list-tile-width, $mdc-grid-list-tile-width); + + &__primary { + position: relative; + height: 0; + + @include mdc-theme-prop(background-color, background); + @include mdc-theme-prop(color, text-primary-on-background); + + &-content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + } + } + + &__secondary { + position: absolute; + box-sizing: border-box; + bottom: 0; + height: $mdc-grid-list-oneline-cap-secondary-height; + padding: $mdc-grid-list-tile-secondary-padding; + + @include mdc-theme-prop(background-color, primary); + @include mdc-theme-prop(color, text-primary-on-primary); + } + + &__title { + display: block; + margin: 0; + padding: 0; + font-size: 1rem; + font-weight: #{map-get($mdc-typography-font-weight-values, medium)}; + line-height: 1rem; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + &__support-text { + @include mdc-typography(body1); + + display: block; + margin: 0; + margin-top: 4px; + padding: 0; + } + + &__icon { + position: absolute; + top: calc(50% - #{$mdc-grid-list-tile-secondary-icon-size} / 2); + font-size: 0; + } +} +// postcss-bem-linter: end diff --git a/packages/mdc-grid-list/package.json b/packages/mdc-grid-list/package.json new file mode 100644 index 00000000000..923e8bd7789 --- /dev/null +++ b/packages/mdc-grid-list/package.json @@ -0,0 +1,24 @@ +{ + "name": "@material/grid-list", + "version": "0.0.0", + "description": "The Material Components for the web grid list component", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/material-components/material-components-web.git" + }, + "keywords": [ + "material components", + "material design", + "grid list" + ], + "dependencies": { + "@material/base": "^0.1.2", + "@material/rtl": "^0.1.2", + "@material/theme": "^0.1.2", + "@material/typography": "^0.2.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/test/unit/mdc-grid-list/foundation.test.js b/test/unit/mdc-grid-list/foundation.test.js new file mode 100644 index 00000000000..8414bb192c4 --- /dev/null +++ b/test/unit/mdc-grid-list/foundation.test.js @@ -0,0 +1,96 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import td from 'testdouble'; + +import {createMockRaf} from '../helpers/raf'; +import {setupFoundationTest} from '../helpers/setup'; +import {verifyDefaultAdapter} from '../helpers/foundation'; +import MDCGridListFoundation from '../../../packages/mdc-grid-list/foundation'; + +suite('MDCGridListFoundation'); + +test('exports strings', () => { + assert.isOk('strings' in MDCGridListFoundation); +}); + +test('defaultAdapter returns a complete adapter implementation', () => { + verifyDefaultAdapter(MDCGridListFoundation, [ + 'getOffsetWidth', 'getOffsetWidthForTileAtIndex', 'setStyleForTilesElement', + 'registerResizeHandler', 'deregisterResizeHandler', + ]); +}); + +const setupTest = () => setupFoundationTest(MDCGridListFoundation); + +test('#init calls component event registrations and align center function', () => { + const {foundation, mockAdapter} = setupTest(); + + foundation.init(); + td.verify(mockAdapter.registerResizeHandler(td.matchers.isA(Function))); +}); + +test('#destroy calls component event deregistrations', () => { + const {foundation, mockAdapter} = setupTest(); + + let resizeHandler; + td.when(mockAdapter.registerResizeHandler(td.matchers.isA(Function))).thenDo((handler) => { + resizeHandler = handler; + }); + + foundation.init(); + foundation.destroy(); + td.verify(mockAdapter.deregisterResizeHandler(resizeHandler)); +}); + +test('#align center sets the container width to fit tiles inside', () => { + const {foundation, mockAdapter} = setupTest(); + const mockRaf = createMockRaf(); + const listOffsetWidth = 1005; + const tileOffsetWidth = 200; + const tilesWidth = Math.floor(listOffsetWidth / tileOffsetWidth) * tileOffsetWidth; + td.when(mockAdapter.getOffsetWidth()).thenReturn(listOffsetWidth); + td.when(mockAdapter.getOffsetWidthForTileAtIndex(0)).thenReturn(tileOffsetWidth); + foundation.init(); + + foundation.alignCenter(); + mockRaf.flush(); + + td.verify(mockAdapter.setStyleForTilesElement('width', `${tilesWidth}px`)); + mockRaf.restore(); +}); + +test('#alignCenter debounces calls within the same frame', () => { + const {foundation} = setupTest(); + const mockRaf = createMockRaf(); + foundation.alignCenter(); + foundation.alignCenter(); + foundation.alignCenter(); + assert.equal(mockRaf.pendingFrames.length, 1); + mockRaf.restore(); +}); + +test('#alignCenter resets debounce latch when alignCenter frame is run', () => { + const {foundation} = setupTest(); + const mockRaf = createMockRaf(); + + foundation.alignCenter(); + mockRaf.flush(); + foundation.alignCenter(); + assert.equal(mockRaf.pendingFrames.length, 1); + mockRaf.restore(); +}); diff --git a/test/unit/mdc-grid-list/mdc-grid-list.test.js b/test/unit/mdc-grid-list/mdc-grid-list.test.js new file mode 100644 index 00000000000..2bbe4e08ad9 --- /dev/null +++ b/test/unit/mdc-grid-list/mdc-grid-list.test.js @@ -0,0 +1,69 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import bel from 'bel'; +import domEvents from 'dom-events'; +import td from 'testdouble'; + +import {MDCGridList} from '../../../packages/mdc-grid-list'; + +function getFixture() { + return bel` +
    + +
    + `; +} + +function setupTest() { + const root = getFixture(); + const component = new MDCGridList(root); + return {root, component}; +} + +test('attachTo initializes and returns an MDCGridList instance', () => { + assert.isOk(MDCGridList.attachTo(getFixture()) instanceof MDCGridList); +}); + +test('adapter#registerResizeHandler uses the handler as a window resize listener', () => { + const {component} = setupTest(); + const handler = td.func('resizeHandler'); + component.getDefaultFoundation().adapter_.registerResizeHandler(handler); + domEvents.emit(window, 'resize'); + td.verify(handler(td.matchers.anything())); + window.removeEventListener('resize', handler); +}); + +test('adapter#registerResizeHandler unlistens the handler for window resize', () => { + const {component} = setupTest(); + const handler = td.func('resizeHandler'); + window.addEventListener('resize', handler); + component.getDefaultFoundation().adapter_.deregisterResizeHandler(handler); + domEvents.emit(window, 'resize'); + td.verify(handler(td.matchers.anything()), {times: 0}); + // Just to be safe + window.removeEventListener('resize', handler); +}); diff --git a/webpack.config.js b/webpack.config.js index 52c15b10c36..319b025648c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -77,6 +77,7 @@ module.exports = [{ checkbox: [path.resolve('./packages/mdc-checkbox/index.js')], drawer: [path.resolve('./packages/mdc-drawer/index.js')], formField: [path.resolve('./packages/mdc-form-field/index.js')], + gridList: [path.resolve('./packages/mdc-grid-list/index.js')], iconToggle: [path.resolve('./packages/mdc-icon-toggle/index.js')], menu: [path.resolve('./packages/mdc-menu/index.js')], radio: [path.resolve('./packages/mdc-radio/index.js')], @@ -143,6 +144,7 @@ module.exports = [{ 'mdc.elevation': path.resolve('./packages/mdc-elevation/mdc-elevation.scss'), 'mdc.fab': path.resolve('./packages/mdc-fab/mdc-fab.scss'), 'mdc.form-field': path.resolve('./packages/mdc-form-field/mdc-form-field.scss'), + 'mdc.grid-list': path.resolve('./packages/mdc-grid-list/mdc-grid-list.scss'), 'mdc.icon-toggle': path.resolve('./packages/mdc-icon-toggle/mdc-icon-toggle.scss'), 'mdc.layout-grid': path.resolve('./packages/mdc-layout-grid/mdc-layout-grid.scss'), 'mdc.list': path.resolve('./packages/mdc-list/mdc-list.scss'),