diff --git a/demos/grid-list.html b/demos/grid-list.html new file mode 100644 index 00000000000..02941baae4a --- /dev/null +++ b/demos/grid-list.html @@ -0,0 +1,656 @@ + + + + + + MDC Grid List Demo + + + + + + + +

MDC Grid List

+ +
+

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

+
+ +
+

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

+
+ +
+

Grid List: tile aspect ratio 1x1 image only

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+
+ +
+
+ + + + \ 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 3245cc5a387..b9801b64814 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.2", "@material/form-field": "^0.2.0", + "@material/grid-list": "^0.0.0", "@material/icon-toggle": "^0.1.4", "@material/layout-grid": "^0.1.1", "@material/list": "^0.2.2", diff --git a/packages/mdc-grid-list/README.md b/packages/mdc-grid-list/README.md new file mode 100644 index 00000000000..c61d59949a5 --- /dev/null +++ b/packages/mdc-grid-list/README.md @@ -0,0 +1,195 @@ +# MDC Grid List + +MDC Grid List provides a RTL-aware Material Design Grid Lists component adhering to the +[Material Design Grid List spec](https://material.io/guidelines/components/grid-lists.html) +Grid List is best suited to presenting homogenous data, typically images. Each data in +grid list is called a tile and tile maintains fixed width, height and padding between each +other as window resizes. Meanwhile, margin changes to keep grid list remain centered. + + +## 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: + +- Has with 4px padding in between +- With 1x1 aspect ratio +- Has 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 +[Use div in replace of img](#use-div-in-replace-of-img) section. + +Besides default setting, we provide the following modifiers. You can mix and +match them to modify your grid list. + + +### Change tile padding + +Grid list tiles can also have 1px padding instead of 4px by adding +`mdc-grid-list--tile-gutter-1` modifier. + +```html +
    + +
    +``` + +### Change aspect ratio of tile + +Grid list tiles support all material guideline recommended aspect ratio: + +- 1x1 +- 16x9 +- 2x3 +- 3x2 +- 4x3 +- 3x4 + +```html +
    + + +
    +``` + +As pointed out in the previous section as well, 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 +[Use div in replace of img](#use-div-in-replace-of-img) section. + + +### Add support text to secondary content (caption) + +Grid list support one line caption by default. You can also add a line of support +text if needed by adding `mdc-grid-list--twoline-caption` modifier and additional +markup + +```html +
    + + +
    +``` + +### Add icon to secondary content (caption) + +You can add icon to caption by adding `mdc-grid-list--with-icon-align-start` or +`mdc-grid-list--with-icon-align-end` and changing the markup. + +```html +
    + + +
    +``` + +### Use div in replace of 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 +
    + +
    +``` + +In this case, you should set `background-image:url(...)` instead of `src`. 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, has a `dir="rtl"` attribute. + + +### Theme + +`mdc-grid-list` support theme. Tile primary uses background and text on +background, secondary uses the primary color and text on primary. + + +### 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 offsetWidth. | +| `getTileOffsetWidthAtIndex(index: number) => number` | Get offsetWidth of tile at specified index. | +| `setTilesWidth(value: number) => void` | Set tiles container width to 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..6d8f12cd8ab --- /dev/null +++ b/packages/mdc-grid-list/foundation.js @@ -0,0 +1,50 @@ +/** + * 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, + getTileOffsetWidthAtIndex: (/* index: number | null */) => /* number */ 0, + setTilesWidth: (/* value: number | null */) => {}, + registerResizeHandler: (/* handler: EventListener */) => {}, + deregisterResizeHandler: (/* handler: EventListener */) => {}, + }; + } + constructor(adapter) { + super(Object.assign(MDCGridListFoundation.defaultAdapter, adapter)); + this.resizeHandler_ = () => this.alignCenter(); + } + init() { + this.alignCenter(); + this.adapter_.registerResizeHandler(this.resizeHandler_); + } + destroy() { + this.adapter_.deregisterResizeHandler(this.resizeHandler_); + } + alignCenter() { + const gridWidth = this.adapter_.getOffsetWidth(); + const itemWidth = this.adapter_.getTileOffsetWidthAtIndex(0); + this.adapter_.setTilesWidth(itemWidth * Math.floor(gridWidth / itemWidth)); + } +} diff --git a/packages/mdc-grid-list/index.js b/packages/mdc-grid-list/index.js new file mode 100644 index 00000000000..a136e95dca5 --- /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, + getTileOffsetWidthAtIndex: (index) => { + return this.root_.querySelectorAll(MDCGridListFoundation.strings.TILE_SELECTOR)[index].offsetWidth; + }, + setTilesWidth: (value) => { + this.root_.querySelector(MDCGridListFoundation.strings.TILES_SELECTOR).style.width = value + 'px'; + }, + 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..278ff8642fa --- /dev/null +++ b/packages/mdc-grid-list/mdc-grid-list.scss @@ -0,0 +1,237 @@ +/** + * 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"; + +$mdc-grid-list-tile-width: 200px; +$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; + +// postcss-bem-linter: define grid-list +// stylelint-disable plugin/selector-bem-pattern +@mixin mdc-grid-list--tile-aspect($width-height-ratio) { + .mdc-grid-tile .mdc-grid-tile__primary { + padding-bottom: calc(100% / #{$width-height-ratio}); + } +} + +@mixin mdc-grid-list--tile-gutter($gutter-width) { + .mdc-grid-tile { + padding: 0 $gutter-width/2; + margin: $gutter-width/2 0; + + &__secondary { + left: $gutter-width/2; + width: calc(100% - #{$gutter-width}); + } + } + + .mdc-grid-list__tiles { + margin: $gutter-width/2 auto; + } +} +// stylelint-enable plugin/selector-bem-pattern + +.mdc-grid-list { + /* + * Default grid list style: + * - mdc-grid-list--tile-gutter-4 + * - mdc-grid-list--tile-aspect-1x1 + * - mdc-grid-list--oneline-caption + * - mdc-grid-list--footer-caption + * - mdc-grid-list--no-icon + * + */ + @include mdc-grid-list--tile-aspect(1); + @include mdc-grid-list--tile-gutter(4px); + + // stylelint-disable plugin/selector-bem-pattern + .mdc-grid-tile { + &__secondary { + bottom: 0; + height: $mdc-grid-list-oneline-cap-secondary-height; + padding: $mdc-grid-list-tile-secondary-padding; + } + + &__icon { + font-size: 0; + } + } + // stylelint-enable plugin/selector-bem-pattern + + &__tiles { + margin: 0; + padding: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + } +} + +// stylelint-disable plugin/selector-bem-pattern +.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); +} + +.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 { + padding-right: $mdc-grid-list-tile-secondary-padding-narrow; + padding-left: $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size; + + .mdc-grid-tile__icon { + left: $mdc-grid-list-tile-secondary-padding; + font-size: $mdc-grid-list-tile-secondary-icon-size; + } + } + + @include mdc-rtl { + .mdc-grid-tile__secondary { + padding-right: $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size; + padding-left: $mdc-grid-list-tile-secondary-padding-narrow; + + .mdc-grid-tile__icon { + right: $mdc-grid-list-tile-secondary-padding; + } + } + } +} + +.mdc-grid-list--with-icon-align-end { + .mdc-grid-tile__secondary { + padding-right: $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size; + + .mdc-grid-tile__icon { + right: $mdc-grid-list-tile-secondary-padding; + font-size: $mdc-grid-list-tile-secondary-icon-size; + } + } + + @include mdc-rtl { + .mdc-grid-tile__secondary { + padding-left: $mdc-grid-list-tile-secondary-padding * 2 + $mdc-grid-list-tile-secondary-icon-size; + + .mdc-grid-tile__icon { + left: $mdc-grid-list-tile-secondary-padding; + } + } + } +} +// stylelint-enable plugin/selector-bem-pattern +// postcss-bem-linter: end + +// postcss-bem-linter: define grid-tile +.mdc-grid-tile { + // stylelint-disable plugin/selector-bem-pattern + --mdc-grid-tile-width: 200px; + // stylelint-enable plugin/selector-bem-pattern + + display: block; + position: relative; + width: var(--mdc-grid-list-tile-width); + 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; + + @include mdc-theme-prop(background-color, primary); + @include mdc-theme-prop(color, text-primary-on-primary); + + .mdc-grid-tile__title { + display: block; + margin: 0; + padding: 0; + font-size: 16px; + font-weight: 500; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .mdc-grid-tile__support-text { + display: block; + margin: 0; + margin-top: 4px; + padding: 0; + font-size: 14px; + line-height: 20px; + } + + .mdc-grid-tile__icon { + position: absolute; + top: calc(50% - #{$mdc-grid-list-tile-secondary-icon-size} / 2); + } + } +} +// 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..b6a8cef5eb2 --- /dev/null +++ b/packages/mdc-grid-list/package.json @@ -0,0 +1,23 @@ +{ + "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" + }, + "publishConfig": { + "access": "public" + } +} 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'),