diff --git a/src/dev-app/mdc-card/BUILD.bazel b/src/dev-app/mdc-card/BUILD.bazel index 66bbde1e3611..f6e4270157ed 100644 --- a/src/dev-app/mdc-card/BUILD.bazel +++ b/src/dev-app/mdc-card/BUILD.bazel @@ -11,6 +11,7 @@ ng_module( ":mdc_card_demo_scss", ], deps = [ + "//src/material-experimental/mdc-button", "//src/material-experimental/mdc-card", "@npm//@angular/router", ], diff --git a/src/dev-app/mdc-card/mdc-card-demo-module.ts b/src/dev-app/mdc-card/mdc-card-demo-module.ts index 6cede24df455..0fc09b717b53 100644 --- a/src/dev-app/mdc-card/mdc-card-demo-module.ts +++ b/src/dev-app/mdc-card/mdc-card-demo-module.ts @@ -8,12 +8,14 @@ import {NgModule} from '@angular/core'; import {MatCardModule} from '@angular/material-experimental/mdc-card'; +import {MatButtonModule} from '@angular/material-experimental/mdc-button'; import {RouterModule} from '@angular/router'; import {MdcCardDemo} from './mdc-card-demo'; @NgModule({ imports: [ MatCardModule, + MatButtonModule, RouterModule.forChild([{path: '', component: MdcCardDemo}]), ], declarations: [MdcCardDemo], diff --git a/src/dev-app/mdc-card/mdc-card-demo.html b/src/dev-app/mdc-card/mdc-card-demo.html index 6e664cb89902..b946295fc32d 100644 --- a/src/dev-app/mdc-card/mdc-card-demo.html +++ b/src/dev-app/mdc-card/mdc-card-demo.html @@ -1,2 +1,122 @@ - -Not yet implemented. +
+ + + + + Card with only text content + + + + + Card with only <mat-card-content> and text content. + + + + + Subtitle + Card with title and footer + +

This is supporting text.

+

{{longText}}

+
+ + + + +
+ + + Subtitle + Card with title, footer, and inset-divider + +

This is supporting text.

+

{{longText}}

+
+ + + + + +
+ + + + Content Title + +

Here is some content

+
+ + + + +
+ + + + + Header title + Header subtitle + + + +

Here is some content

+
+
+ + + Easily customizable + + + + + + +
+

Cards with media area

+ + + + Card + Small + + + + {{longText}} + + + + + + Card + Medium + + + + {{longText}} + + + + + + Card + Large + + + + {{longText}} + + + + + + Card + Extra large + + + + {{longText}} + + + + +
diff --git a/src/dev-app/mdc-card/mdc-card-demo.scss b/src/dev-app/mdc-card/mdc-card-demo.scss index f5ff0fdbee98..6038ba0385ae 100644 --- a/src/dev-app/mdc-card/mdc-card-demo.scss +++ b/src/dev-app/mdc-card/mdc-card-demo.scss @@ -1 +1,18 @@ -// TODO: copy in demo styles from existing mat-card demo. +.demo-card-container { + display: flex; + flex-flow: column nowrap; + + .mat-mdc-card { + margin: 0 16px 16px 0; + width: 350px; + } + + // Use a gray background instead of real images for that "mock" feel. + img { + background-color: gray; + } + + .mdc-button { + text-transform: uppercase; + } +} diff --git a/src/dev-app/mdc-card/mdc-card-demo.ts b/src/dev-app/mdc-card/mdc-card-demo.ts index 7771d73cec90..f1a23af1c895 100644 --- a/src/dev-app/mdc-card/mdc-card-demo.ts +++ b/src/dev-app/mdc-card/mdc-card-demo.ts @@ -6,13 +6,20 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; @Component({ moduleId: module.id, selector: 'mdc-card-demo', templateUrl: 'mdc-card-demo.html', styleUrls: ['mdc-card-demo.css'], + encapsulation: ViewEncapsulation.None, }) export class MdcCardDemo { + longText = `Once upon a midnight dreary, while I pondered, weak and weary, + Over many a quaint and curious volume of forgotten lore— + While I nodded, nearly napping, suddenly there came a tapping, + As of some one gently rapping, rapping at my chamber door. + “’Tis some visitor,” I muttered, “tapping at my chamber door— + Only this and nothing more.”`; } diff --git a/src/material-experimental/mdc-card/BUILD.bazel b/src/material-experimental/mdc-card/BUILD.bazel index 2188e04a5fc6..5472d60d14bc 100644 --- a/src/material-experimental/mdc-card/BUILD.bazel +++ b/src/material-experimental/mdc-card/BUILD.bazel @@ -22,12 +22,21 @@ sass_library( srcs = glob(["**/_*.scss"]), deps = [ "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + "//src/material/core:core_scss_lib", ], ) sass_binary( name = "card_scss", src = "card.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], ) ng_e2e_test_library( diff --git a/src/material-experimental/mdc-card/_mdc-card.scss b/src/material-experimental/mdc-card/_mdc-card.scss index ed41c765eee5..37c329db2945 100644 --- a/src/material-experimental/mdc-card/_mdc-card.scss +++ b/src/material-experimental/mdc-card/_mdc-card.scss @@ -1,13 +1,34 @@ +@import '@material/card/mixins'; +@import '@material/typography/mixins'; @import '../mdc-helpers/mdc-helpers'; @mixin mat-card-theme-mdc($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + @include mat-using-mdc-theme($theme) { - // TODO: MDC theme styles here. + @include mdc-card-without-ripple($query: $mat-theme-styles-query); + + // Card subtitles are an Angular Material construct (not MDC), so we explicitly set their + // color to secondary text here. + .mat-mdc-card-subtitle { + color: mat-color($foreground, secondary-text); + } } } @mixin mat-card-typography-mdc($config) { @include mat-using-mdc-typography($config) { - // TODO: MDC typography styles here. + @include mdc-card-without-ripple($query: $mat-typography-styles-query); + + // Card subtitles and titles are an Angular Material construct (not MDC), so we explicitly + // set their typographic styles here. + .mat-mdc-card-title { + @include mdc-typography(headline6); + } + + .mat-mdc-card-subtitle { + @include mdc-typography(subtitle2); + } } } diff --git a/src/material-experimental/mdc-card/card-header.html b/src/material-experimental/mdc-card/card-header.html new file mode 100644 index 000000000000..b476f9327c98 --- /dev/null +++ b/src/material-experimental/mdc-card/card-header.html @@ -0,0 +1,8 @@ + +
+ +
+ diff --git a/src/material-experimental/mdc-card/card-title-group.html b/src/material-experimental/mdc-card/card-title-group.html new file mode 100644 index 000000000000..67c0eba6f6e9 --- /dev/null +++ b/src/material-experimental/mdc-card/card-title-group.html @@ -0,0 +1,12 @@ +
+ +
+ + diff --git a/src/material-experimental/mdc-card/card.html b/src/material-experimental/mdc-card/card.html index 9c75ddb6f477..6dbc74306383 100644 --- a/src/material-experimental/mdc-card/card.html +++ b/src/material-experimental/mdc-card/card.html @@ -1 +1 @@ - + diff --git a/src/material-experimental/mdc-card/card.scss b/src/material-experimental/mdc-card/card.scss index f61450904c9a..cf9922f1e0a9 100644 --- a/src/material-experimental/mdc-card/card.scss +++ b/src/material-experimental/mdc-card/card.scss @@ -1 +1,149 @@ -// TODO: MDC core styles here. +@import '@material/card/mixins'; +@import '../mdc-helpers/mdc-helpers'; +@import '../../cdk/a11y/a11y'; + +// TODO(jelbourn): move header and title-group styles to their own files. + +// Size of the `mat-card-header` region custom to Angular Material. +$mat-card-header-size: 40px !default; + +// Default padding for text content within a card. +$mat-card-default-padding: 16px !default; + +// Include all MDC card styles except for color and typography. +@include mdc-card-without-ripple($query: $mat-base-styles-query); + +// Add styles to the root card to have an outline in high-contrast mode. +// TODO(jelbourn): file bug for MDC supporting high-contrast mode on `.mdc-card`. +.mat-mdc-card { + @include cdk-high-contrast { + outline: solid 1px; + } +} + +// Title text and subtitles text within a card. MDC doesn't have pre-made title sections for cards. +// Maintained here for backwards compatibility with the previous generation MatCard. +.mat-mdc-card-title, +.mat-mdc-card-subtitle { + // Custom elements default to `display: inline`. Reset to 'block'. + display: block; + + // Apply default padding for a text content region. Omit any bottom padding because we assume + // this region will be followed by another region that includes top padding. + padding: $mat-card-default-padding $mat-card-default-padding 0; +} + +// Header section at the top of a card. MDC does not have a pre-made header section for cards. +// Maintained here for backwards compatibility with the previous generation MatCard. +.mat-mdc-card-header { + // The primary purpose of the card header is to lay out the title, subtitle, and image in the + // correct order. The image will come first, followed by a single div that will contain (via + // content projection) both the title and the subtitle. + display: flex; + + // Apply default padding for the header region. Omit any bottom padding because we assume + // this region will be followed by another region that includes top padding. + padding: $mat-card-default-padding $mat-card-default-padding 0; + + // When a subtitle is inside of a header, we want to move it up slightly to reduce the space with + // the title, and add a margin bottom to create space underneath the header. + .mat-mdc-card-subtitle { + margin-top: -($mat-card-default-padding / 2); + margin-bottom: $mat-card-default-padding; + } +} + +// Primary card content. MDC does not have a pre-made section for primary content. +// Adds the default 16px padding to the content. Maintained here for backwards compatibility +// with the previous generation MatCard. +.mat-mdc-card-content { + // Custom elements default to `display: inline`. Reset to 'block'. + display: block; + + // Apply default horizontal padding for a content region. + padding: 0 $mat-card-default-padding; + + // Only add vertical padding to the main content area if it's not preceeded/followed by another + // element within the card. + &:first-child { + padding-top: $mat-card-default-padding; + } + + &:last-child { + padding-bottom: $mat-card-default-padding; + } +} + +// Title group within a card. MDC does not have a pre-made section for anything title-related. +// Maintained here for backwards compatibility with the previous generation MatCard. +.mat-mdc-card-title-group { + // This element exists primary to lay out its children (title, subtitle, media). The title + // and subtitle go first (projected into a single div), followed by the media. + display: flex; + justify-content: space-between; + + // Apply default padding for the title-group region. Omit any bottom padding because we assume + // this region will be followed by another region that includes top padding. + padding: $mat-card-default-padding $mat-card-default-padding 0; +} + +// Avatar image for a card. MDC does not specifically have a card avatar or a "common" avatar. +// They *do* have a list avatar, but it uses a different size than we do here, which we preserve +// to reduce breaking changes. Ultimately, the avatar styles just consist of a size and a +// border-radius. +.mat-mdc-card-avatar { + height: $mat-card-header-size; + width: $mat-card-header-size; + border-radius: 50%; + flex-shrink: 0; + + // Makes `` tags behave like `background-size: cover`. Not supported + // in IE, but we're using it as a progressive enhancement. + object-fit: cover; +} + +// Specifically sized small image, specific to Angular Material. +.mat-mdc-card-sm-image { + width: 80px; + height: 80px; +} + +// Specifically sized medium image, specific to Angular Material. +.mat-mdc-card-md-image { + width: 112px; + height: 112px; +} + +// Specifically sized large image, specific to Angular Material. +.mat-mdc-card-lg-image { + width: 152px; + height: 152px; +} + +// Specifically sized extra-large image, specific to Angular Material. +.mat-mdc-card-xl-image { + width: 240px; + height: 240px; +} + +// When both a title and a subtitle are used, eliminate the top padding of whichever comes second +// because the first already covers the padding. +// +// Additionally, reset the padding for title and subtitle when used within `mat-card-header` or +// `mat-card-title-group` since the padding is captured by parent container already. +.mat-mdc-card-subtitle ~ .mat-mdc-card-title, +.mat-mdc-card-title ~ .mat-mdc-card-subtitle, +.mat-mdc-card-header .mat-mdc-card-title, +.mat-mdc-card-header .mat-mdc-card-subtitle, +.mat-mdc-card-title-group .mat-mdc-card-title, +.mat-mdc-card-title-group .mat-mdc-card-subtitle { + padding-top: 0; +} + +// Remove the bottom margin from the last child of the card content. We intended to remove +// margin from elements that have it built-in, such as `

`. We do this to avoid having too much +// space between card content regions, as the space is already captured in the content region +// element. +.mat-mdc-card-content > :last-child:not(.mat-mdc-card-footer) { + margin-bottom: 0; +} diff --git a/src/material-experimental/mdc-card/card.ts b/src/material-experimental/mdc-card/card.ts index 729f21ae3211..999297b87485 100644 --- a/src/material-experimental/mdc-card/card.ts +++ b/src/material-experimental/mdc-card/card.ts @@ -6,20 +6,226 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Directive, + Input, + ViewEncapsulation, +} from '@angular/core'; + +/** + * Material Design card component. Cards contain content and actions about a single subject. + * See https://material.io/design/components/cards.html + * + * MatCard provides no behaviors, instead serving as a purely visual treatment. + */ @Component({ moduleId: module.id, selector: 'mat-card', templateUrl: 'card.html', styleUrls: ['card.css'], - host: { - 'class': 'mat-mdc-card', - }, + host: {'class': 'mat-mdc-card mdc-card'}, exportAs: 'matCard', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatCard { - // TODO: set up MDC foundation class. + // TODO(jelbourn): add `outline` option to card (supported by MDC) +} + +// TODO(jelbourn): add `MatActionCard`, which is a card that acts like a button (and has a ripple). +// Supported in MDC with `.mdc-card__primary-action`. Will require additional a11y docs for users. + + +/** + * Title of a card, intended for use within ``. This component is an optional + * convenience for one variety of card title; any custom title element may be used in its place. + * + * MatCardTitle provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: `mat-card-title, [mat-card-title], [matCardTitle]`, + host: {'class': 'mat-mdc-card-title'} +}) +export class MatCardTitle {} + + +/** + * Container intended to be used within the `` component. Can contain exactly one + * ``, one `` and one content image of any size + * (e.g. ``). + */ +@Component({ + moduleId: module.id, + selector: 'mat-card-title-group', + templateUrl: 'card-title-group.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: {'class': 'mat-mdc-card-title-group'} +}) +export class MatCardTitleGroup {} + + +/** + * Content of a card, intended for use within ``. This component is an optional + * convenience for use with other convenience elements, such as ``; any custom + * content block element may be used in its place. + * + * MatCardContent provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: 'mat-card-content', + host: {'class': 'mat-mdc-card-content'} +}) +export class MatCardContent {} + + +/** + * Sub-title of a card, intended for use within `` beneath a ``. This + * component is an optional convenience for use with other convenience elements, such as + * ``. + * + * MatCardSubtitle provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: `mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]`, + host: {'class': 'mat-mdc-card-subtitle'} +}) +export class MatCardSubtitle {} + + +/** + * Bottom area of a card that contains action buttons, intended for use within ``. + * This component is an optional convenience for use with other convenience elements, such as + * ``; any custom action block element may be used in its place. + * + * MatCardActions provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: 'mat-card-actions', + exportAs: 'matCardActions', + host: { + 'class': 'mat-mdc-card-actions mdc-card__actions', + '[class.mat-mdc-card-actions-align-end]': 'align === "end"', + } +}) +export class MatCardActions { + // TODO(jelbourn): deprecate `align` in favor of `actionPositon` or `actionAlignment` + // as to not conflict with the native `align` attribute. + + /** Position of the actions inside the card. */ + @Input() align: 'start' | 'end' = 'start'; + + // TODO(jelbourn): support `.mdc-card__actions--full-bleed`. + + // TODO(jelbourn): support `.mdc-card__action-buttons` and `.mdc-card__action-icons`. + + // TODO(jelbourn): figure out how to use `.mdc-card__action`, `.mdc-card__action--button`, and + // `mdc-card__action--icon`. They're used primarily for positioning, which we might be able to + // do implicitly. } + + +/** + * Header region of a card, intended for use within ``. This header captures + * a card title, subtitle, and avatar. This component is an optional convenience for use with + * other convenience elements, such as ``; any custom header block element may be + * used in its place. + * + * MatCardHeader provides no behaviors, instead serving as a purely visual treatment. + */ +@Component({ + moduleId: module.id, + selector: 'mat-card-header', + templateUrl: 'card-header.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: {'class': 'mat-mdc-card-header'} +}) +export class MatCardHeader {} + + +/** + * Footer area a card, intended for use within ``. + * This component is an optional convenience for use with other convenience elements, such as + * ``; any custom footer block element may be used in its place. + * + * MatCardFooter provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: 'mat-card-footer', + host: {'class': 'mat-mdc-card-footer'} +}) +export class MatCardFooter {} + +// TODO(jelbourn): deprecate the "image" selectors to replace with "media". + +// TODO(jelbourn): support `.mdc-card__media-content`. + +/** + * Primary image content for a card, intended for use within ``. Can be applied to + * any media element, such as `` or ``. + * + * This component is an optional convenience for use with other convenience elements, such as + * ``; any custom media element may be used in its place. + * + * MatCardImage provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: '[mat-card-image], [matCardImage]', + host: {'class': 'mat-mdc-card-image mdc-card__media'} +}) +export class MatCardImage { + // TODO(jelbourn): support `.mdc-card__media--square` and `.mdc-card__media--16-9`. +} + + +/** Same as `MatCardImage`, but small. */ +@Directive({ + selector: '[mat-card-sm-image], [matCardImageSmall]', + host: {'class': 'mat-mdc-card-sm-image mdc-card__media'} +}) +export class MatCardSmImage {} + + +/** Same as `MatCardImage`, but medium. */ +@Directive({ + selector: '[mat-card-md-image], [matCardImageMedium]', + host: {'class': 'mat-mdc-card-md-image mdc-card__media'} +}) +export class MatCardMdImage {} + + +/** Same as `MatCardImage`, but large. */ +@Directive({ + selector: '[mat-card-lg-image], [matCardImageLarge]', + host: {'class': 'mat-mdc-card-lg-image mdc-card__media'} +}) +export class MatCardLgImage {} + + +/** Same as `MatCardImage`, but extra-large. */ +@Directive({ + selector: '[mat-card-xl-image], [matCardImageXLarge]', + host: {'class': 'mat-mdc-card-xl-image mdc-card__media'} +}) +export class MatCardXlImage {} + + +/** + * Avatar image content for a card, intended for use within ``. Can be applied to + * any media element, such as `` or ``. + * + * This component is an optional convenience for use with other convenience elements, such as + * ``; any custom media element may be used in its place. + * + * MatCardAvatar provides no behaviors, instead serving as a purely visual treatment. + */ +@Directive({ + selector: '[mat-card-avatar], [matCardAvatar]', + host: {'class': 'mat-mdc-card-avatar'} +}) +export class MatCardAvatar {} + diff --git a/src/material-experimental/mdc-card/migration.md b/src/material-experimental/mdc-card/migration.md new file mode 100644 index 000000000000..c1d7c311f15b --- /dev/null +++ b/src/material-experimental/mdc-card/migration.md @@ -0,0 +1,2 @@ +# Migration notes for MDC-based `MatCard` +* Previous `MatCard` always set 16px padding, which the MDC card sets no padding. diff --git a/src/material-experimental/mdc-card/module.ts b/src/material-experimental/mdc-card/module.ts index 70f61b5a7b84..09c06531da79 100644 --- a/src/material-experimental/mdc-card/module.ts +++ b/src/material-experimental/mdc-card/module.ts @@ -9,12 +9,45 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; -import {MatCard} from './card'; +import { + MatCard, + MatCardActions, + MatCardAvatar, + MatCardContent, + MatCardFooter, + MatCardHeader, + MatCardImage, + MatCardLgImage, + MatCardMdImage, + MatCardSmImage, + MatCardSubtitle, + MatCardTitle, + MatCardTitleGroup, + MatCardXlImage, +} from './card'; + + +const CARD_DIRECTIVES = [ + MatCard, + MatCardActions, + MatCardAvatar, + MatCardContent, + MatCardFooter, + MatCardHeader, + MatCardImage, + MatCardLgImage, + MatCardMdImage, + MatCardSmImage, + MatCardSubtitle, + MatCardTitle, + MatCardTitleGroup, + MatCardXlImage +]; @NgModule({ imports: [MatCommonModule, CommonModule], - exports: [MatCard, MatCommonModule], - declarations: [MatCard], + exports: [CARD_DIRECTIVES, MatCommonModule], + declarations: CARD_DIRECTIVES, }) export class MatCardModule { } diff --git a/src/material-experimental/mdc-helpers/_mdc-helpers.scss b/src/material-experimental/mdc-helpers/_mdc-helpers.scss index f3d7db088988..7ec68c9fe6fe 100644 --- a/src/material-experimental/mdc-helpers/_mdc-helpers.scss +++ b/src/material-experimental/mdc-helpers/_mdc-helpers.scss @@ -114,7 +114,7 @@ $mat-typography-level-mappings: ( $primary: mat-color(map-get($theme, primary)); $accent: mat-color(map-get($theme, accent)); $warn: mat-color(map-get($theme, warn)); - $background: mat-color(map-get($theme, background), background); + $background-palette: map-get($theme, background); // Save the original values. $orig-mdc-theme-primary: $mdc-theme-primary; @@ -135,8 +135,8 @@ $mat-typography-level-mappings: ( $mdc-theme-secondary: $accent !global; $mdc-theme-on-secondary: if(mdc-theme-contrast-tone($mdc-theme-secondary) == 'dark', #000, #fff) !global; - $mdc-theme-background: $background !global; - $mdc-theme-surface: $background !global; + $mdc-theme-background: mat-color($background-palette, background) !global; + $mdc-theme-surface: mat-color($background-palette, card) !global; $mdc-theme-on-surface: if(mdc-theme-contrast-tone($mdc-theme-surface) == 'dark', #000, #fff) !global; $mdc-theme-error: $warn !global;