diff --git a/.changeset/many-frogs-film.md b/.changeset/many-frogs-film.md new file mode 100644 index 0000000000..90ff92818b --- /dev/null +++ b/.changeset/many-frogs-film.md @@ -0,0 +1,27 @@ +--- +"@patternfly/pfe-card": major +--- + +## Patternfly Card 1:1 🎉 + +This release migrates Patternfly Card `pfe-card` to Patternfly 1:1 and matches the style and functionality to PFv4. + +This will have several breaking changes from the existing `pfe-card`. Many of the attributes will be moving to `rh-card`. + +### NEW: Features and Updates +- Styles now match PFv4 Card. +- Size Attribute: `small` is updated to `compact` and `large` is now an option + - Example: ``. +- Rounded Attribute: NEW! Optionally applies a border radius for the drop shadow and/or border of the card. + - Example: `` +- FullHeight Attribute: NEW! Optionally allow the card to take up the full height of the parent element. + - Example: `` +- Plain Attribute: NEW! Removes the border on the card element. + - Example: `` + + +### Breaking Changes +- No longer includes an `imgSrc` attribute. +- No longer has a dark mode theme. +- No longer includes a `border` attribute. + diff --git a/elements/pfe-card/BaseCard.scss b/elements/pfe-card/BaseCard.scss new file mode 100644 index 0000000000..ea392d3f15 --- /dev/null +++ b/elements/pfe-card/BaseCard.scss @@ -0,0 +1,28 @@ +:host { + display: flex; + flex-direction: column; +} + +[part=header] { + display: flex; + flex-direction: row; + align-items: center; +} + +[part=body] ::slotted(:not([slot]):first-of-type) { + margin-block-start: 0; +} + +[part=body] ::slotted(:not([slot]):last-of-type) { + margin-block-end: 0; +} + +[part=footer] { + display: flex; + gap: 0.5em; +} + +.empty { + display: none; +} + diff --git a/elements/pfe-card/BaseCard.ts b/elements/pfe-card/BaseCard.ts new file mode 100644 index 0000000000..63a5c0f2fb --- /dev/null +++ b/elements/pfe-card/BaseCard.ts @@ -0,0 +1,53 @@ +import { LitElement, html } from 'lit'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import { classMap } from 'lit/directives/class-map.js'; + +import style from './BaseCard.scss'; + +/** + * This element creates a header, body, and footer region in which to place + * content or other components. + * + * @summary Gives a preview of information in a small layout + * + * @slot header + * If this slot is used, we expect a heading level tag (h1, h2, h3, h4, h5, h6). + * An icon, svg, or use of the icon component are also valid in this region. + * @slot - Any content that is not designated for the header or footer slot, will go to this slot. + * @slot footer + * Use this slot for anything that you want to be stuck to the base of the card. + * + * @csspart header - The container for *header* content + * @csspart body - The container for *body* content + * @csspart footer - The container for *footer* content + */ +export abstract class BaseCard extends LitElement { + static readonly version = '{{version}}'; + + static readonly styles = [style]; + + protected slots = new SlotController(this, 'header', null, 'footer'); + + render() { + return html` +
+ +
+ +
+ +
+ `; + } +} diff --git a/elements/pfe-card/custom-elements-manifest.config.js b/elements/pfe-card/custom-elements-manifest.config.js index 3dcb0a2905..a0cc65240b 100644 --- a/elements/pfe-card/custom-elements-manifest.config.js +++ b/elements/pfe-card/custom-elements-manifest.config.js @@ -1,5 +1,5 @@ import { pfeCustomElementsManifestConfig } from '@patternfly/pfe-tools/custom-elements-manifest.js'; export default pfeCustomElementsManifestConfig({ - globs: ['pfe-*.ts'], + globs: ['pfe-*.ts', 'BaseCard.ts'], }); diff --git a/elements/pfe-card/demo/bg-pattern.png b/elements/pfe-card/demo/bg-pattern.png deleted file mode 100644 index 481e114a56..0000000000 Binary files a/elements/pfe-card/demo/bg-pattern.png and /dev/null differ diff --git a/elements/pfe-card/demo/demo.css b/elements/pfe-card/demo/demo.css index 80d9dcaa89..218a3ccc6f 100644 --- a/elements/pfe-card/demo/demo.css +++ b/elements/pfe-card/demo/demo.css @@ -1,26 +1,44 @@ [data-demo] { - padding: 1em; + display: contents; +} + +main { + padding: 2em; + background: #eeeeee; display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, calc(25% - 32px))); - grid-auto-rows: max-content; - gap: 32px; + grid-template-columns: 1fr; + grid-template-rows: max-content 1fr; } -.button-series::part(footer) { - display: flex; - gap: 15px; +@media (min-width: 800px) { + main { + grid-template-columns: 1fr 4fr; + grid-template-rows: 1fr; + } } -#first-image { - grid-column: 1/2; +form { + display: grid; + grid-template-columns: max-content auto; + grid-auto-rows: max-content; + gap: 1em; } -h2[slot="header"] { - font-size: 1.1em; - font-weight: bolder; +pfe-card { + max-width: 50%; + margin: 0 auto; } -.custom-border { - --pfe-card--BorderColor: #eee; - --pfe-card--BorderWeight: 1px; +.resize { + padding: 2em; + resize: vertical; + overflow: auto; + min-height: 40vh; + background: repeating-linear-gradient( + 45deg, + #fff, + #fff 10px, + #eee 10px, + #eee 20px + ); } diff --git a/elements/pfe-card/demo/kitten-200x150.jpeg b/elements/pfe-card/demo/kitten-200x150.jpeg deleted file mode 100644 index 984860bc57..0000000000 Binary files a/elements/pfe-card/demo/kitten-200x150.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/kitten-400x250.jpeg b/elements/pfe-card/demo/kitten-400x250.jpeg deleted file mode 100644 index 13fb6f3f12..0000000000 Binary files a/elements/pfe-card/demo/kitten-400x250.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/kitten-500x250.jpeg b/elements/pfe-card/demo/kitten-500x250.jpeg deleted file mode 100644 index 0aa443dcca..0000000000 Binary files a/elements/pfe-card/demo/kitten-500x250.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/kitten-500x500.jpeg b/elements/pfe-card/demo/kitten-500x500.jpeg deleted file mode 100644 index fdbde2e068..0000000000 Binary files a/elements/pfe-card/demo/kitten-500x500.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/kitten-650x250.jpeg b/elements/pfe-card/demo/kitten-650x250.jpeg deleted file mode 100644 index 7d6efda883..0000000000 Binary files a/elements/pfe-card/demo/kitten-650x250.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/kitten-900x300.jpeg b/elements/pfe-card/demo/kitten-900x300.jpeg deleted file mode 100644 index 96dc7c11b9..0000000000 Binary files a/elements/pfe-card/demo/kitten-900x300.jpeg and /dev/null differ diff --git a/elements/pfe-card/demo/pfe-card.html b/elements/pfe-card/demo/pfe-card.html index 38c9b1db1a..8f7f506815 100644 --- a/elements/pfe-card/demo/pfe-card.html +++ b/elements/pfe-card/demo/pfe-card.html @@ -4,98 +4,33 @@ - + + - -

Lightest card

-

This is the lightest pfe-card and a link, and a visited link with border.

- Try - Buy -
- - -

Default card

- Unwrapped item. This is the default pfe-card and a link, and a visited link. - Try - Buy -
- - -

Darker Card

-

This is the darker pfe-card and a link, and a visited link.

- Try - Buy -
- - -

Darkest Card

-

This is the darkest pfe-card and a link, and a visited link.

- Learn more -
- - -

Complement Card

-

This is the complement pfe-card and a link, and a visited link.

- Learn more -
- - -

Accent Card

-

This is the accent pfe-card and a link, and a visited link.

- Learn more -
- - - A cute kitten -

Imaged card with top & side overflow

-

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat porro similique saepe tempora vel! Facilis, - porro?

- Learn more -
+
+ + - -

Imaged card with side overflow + header

- A cute kitten -

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat porro similique saepe tempora vel! Facilis, - porro?

- Learn more -
+ + - -

Imaged card with side overflow

- A cute kitten - Learn more -
+ + - -

Imaged card with side & bottom overflow

-

Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta rerum tempore natus alias! Cumque illum - provident corrupti voluptates.

- A cute kitten -
+ + - -

Background Image on card

-

Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta rerum tempore natus alias! Cumque illum - provident corrupti voluptates.

-
+ + + +
- -

Deprecated color attribute

-

Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta rerum tempore natus alias! Cumque illum - provident corrupti voluptates.

- - - -
+
+ +

Lightest card

+

This is the lightest pfe-card and a link, and a visited link with border.

+ Try + Buy +
+
diff --git a/elements/pfe-card/demo/pfe-card.js b/elements/pfe-card/demo/pfe-card.js index b226a181c9..f8973dd13c 100644 --- a/elements/pfe-card/demo/pfe-card.js +++ b/elements/pfe-card/demo/pfe-card.js @@ -1,9 +1,22 @@ -import '@patternfly/pfe-band'; import '@patternfly/pfe-card'; -import '@patternfly/pfe-select'; +import '@patternfly/pfe-button'; +import '@patternfly/pfe-switch'; +const form = document.getElementById('card-settings'); +const card = document.querySelector('pfe-card'); -const root = document.querySelector('[data-demo="pfe-card"]')?.shadowRoot ?? document; - -root.querySelector('#context-selector').addEventListener('select', event => { - event.target.closest('pfe-card').setAttribute('color', event.value); +form.addEventListener('change', function(event) { + const { checked } = event.target; + const { attribute } = event.target.dataset; + switch (attribute) { + case 'flat': + case 'rounded': + case 'plain': + case 'full-height': + card.toggleAttribute(attribute, checked); + break; + case 'size': + card.setAttribute('size', form.querySelector('label[for="size"]:not([hidden])').textContent.toLowerCase()); + break; + } }); + diff --git a/elements/pfe-card/docs/pfe-card.md b/elements/pfe-card/docs/pfe-card.md index 744588d189..00217b31e7 100644 --- a/elements/pfe-card/docs/pfe-card.md +++ b/elements/pfe-card/docs/pfe-card.md @@ -2,43 +2,43 @@ Cards are flexible surfaces used to group information in a small layout. They give small previews of information or provide secondary content in relation to the content it's near. Several cards can be used together to group related information. -
+

Default card

This is the default card

Link in the footer
- -

Lightest card

-

This is the lightest card with a border

+ +

Compact card

+

This is the compact card

Link in the footer
- -

Darker card

-

This is the darker card

+ +

Rounded card

+

This is the rounded card

Link in the footer
- -

Darkest card

-

This is the darkest card

+ +

Large card

+

This is the large card

Link in the footer
- -

Complement card

-

This is the complement card

+ +

Full Height card

+

This is the full height card

Link in the footer
- -

Accent card

-

This is the accent card

+ +

Plain card

+

This is the plain card

Link in the footer
-
+ {% endrenderOverview %} {% band header="Usage" %} diff --git a/elements/pfe-card/package.json b/elements/pfe-card/package.json index 93db34af1a..dbc43bd297 100644 --- a/elements/pfe-card/package.json +++ b/elements/pfe-card/package.json @@ -10,6 +10,7 @@ "types": "./pfe-card.d.ts", "exports": { ".": "./pfe-card.js", + "./BaseCard.js": "./BaseCard.js", "./*": "./*.js" }, "publishConfig": { @@ -27,7 +28,6 @@ "build:clean": "npm run clean", "build:esbuild": "node ../../scripts/build.js --include pfe-card", "build:analyze": "npm run analyze", - "build:lightdom": "sass pfe-card--lightdom.scss pfe-card--lightdom.min.css --style=compressed --load-path=../../node_modules", "build:types": "tsc -b .", "🧑‍🔬-----TEST-------🧑‍🔬": "❓ Test packages", "test": "wtr --files './test/*.spec.ts' --config ../../web-test-runner.config.js", diff --git a/elements/pfe-card/pfe-card--lightdom.scss b/elements/pfe-card/pfe-card--lightdom.scss deleted file mode 100644 index 357107be13..0000000000 --- a/elements/pfe-card/pfe-card--lightdom.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use "@patternfly/pfe-sass" as *; - -@include configure($name: 'card'); diff --git a/elements/pfe-card/pfe-card.scss b/elements/pfe-card/pfe-card.scss index cdb3e02a3f..4300a73f05 100644 --- a/elements/pfe-card/pfe-card.scss +++ b/elements/pfe-card/pfe-card.scss @@ -1,278 +1,72 @@ -@use "@patternfly/pfe-sass" as *; - -@include configure( - $name: 'card', - $variables: ( - // Individual padding overrides available - PaddingTop: calc(#{pfe-var(container-spacer)} * 2), - PaddingRight: calc(#{pfe-var(container-spacer)} * 2), - PaddingBottom: calc(#{pfe-var(container-spacer)} * 2), - PaddingLeft: calc(#{pfe-var(container-spacer)} * 2), - - //-- Border settings - BorderRadius: pfe-var(surface--border-radius), - - //-- Color properties - BackgroundColor: pfe-var(surface--base), - context: pfe-var(surface--base--context), - BackgroundPosition: center center, - - spacing: pfe-var(container-spacer), - - header: ( - BackgroundColor: rgba(0, 0, 0, pfe-var(opacity)), - BackgroundColor--dark: rgba(255, 255, 255, pfe-var(opacity)), - Color: pfe-broadcasted(text) - ), - - footer: ( - spacing--horizontal: calc(#{pfe-var(container-spacer)} / 2) - ), - ), -); - -// Nested internal variables (pfe-local calls), maps cannot "self-reference" -@include merge-local-variables(( - // Internal spacing; elements inside the card (header, body, footer regions) - spacing--vertical: pfe-local(spacing), - - // Combine above variables or allow single override point via variable - Padding: pfe-local(PaddingTop) pfe-local(PaddingRight) pfe-local(PaddingBottom) pfe-local(PaddingLeft), - - //-- Border variable encompasses width, style, and color - Border: pfe-local(BorderWidth, 0) pfe-local(BorderStyle, solid) pfe-local(BorderColor, pfe-var(surface--border)) -)); - -$size-small: ( - PaddingTop: pfe-var(container-spacer), - PaddingRight: pfe-var(container-spacer), - PaddingBottom: pfe-var(container-spacer), - PaddingLeft: pfe-var(container-spacer) -); - :host { - --context: #{pfe-local(context)}; - - --pfe-theme--color--surface--lightest: #ffffff; - --pfe-theme--color--surface--lighter: #ececec; - --pfe-theme--color--surface--base: #f0f0f0; - --pfe-theme--color--surface--darker: #3c3f42; - --pfe-theme--color--surface--darkest: #151515; - --pfe-theme--color--surface--accent: #004080; - --pfe-theme--color--surface--complement: #002952; - - // Start of style declarations for host element - display: flex; - flex-direction: column; - justify-items: flex-start; - // This allows the card to fill it's container if necessary - align-self: stretch; - - padding: pfe-local(Padding); - border: pfe-local(Border); // @TODO add automatic border when lightest card is on lightest background? - border-radius: pfe-local(BorderRadius); - - // This property ensures that children in the slots do no overflow - // the border-radius being set on the container - overflow: hidden; - - // Base colors - background-color: var(--pfe-card--BackgroundColor, var(--pfe-context-background-color)); - background-position: pfe-local(BackgroundPosition); - color: pfe-broadcasted(text); - - // Remove background color for print - @include pfe-no-print-background; - - // Add the border to the card for print - @include pfe-print-media { - border-radius: pfe-fetch(surface--border-radius); - border: pfe-fetch(surface--border-width) pfe-fetch(surface--border-style) pfe-fetch(surface--border); - } + background-color: var(--pf-c-card--BackgroundColor, var(--pf-global--BackgroundColor--100, #ffffff)); + box-shadow: var(--pf-c-card--BoxShadow, var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06))); } -/* @deprecated */ -:host([color]), -:host([color-palette]), -:host([on]) { - background-color: var(--pfe-card--BackgroundColor, var(--pfe-context-background-color)); +:host([size="compact"]) { + --_pf-c-card__body--FontSize: var(--pf-c-card--size-compact__body--FontSize, var(--pf-global--FontSize--sm, .875rem)); + --_pf-c-card__footer--FontSize: var(--pf-c-card--size-compact__footer--FontSize, var(--pf-global--spacer--md, 1rem)); + --_pf-c-card--first-child--PaddingTop: var(--pf-c-card--size-compact--first-child--PaddingTop, var(--pf-global--spacer--lg, 1.5rem)); + --_pf-c-card--child--PaddingRight: var(--pf-c-card--size-compact--child--PaddingRight, var(--pf-global--spacer--md, 1rem)); + --_pf-c-card--child--PaddingBottom: var(--pf-c-card--size-compact--child--PaddingBottom, var(--pf-global--spacer--md, 1rem)); + --_pf-c-card--child--PaddingLeft: var(--pf-c-card--size-compact--child--PaddingLeft, var(--pf-global--spacer--md, 1rem)); + --_pf-c-card__title--not--last-child--PaddingBottom: var(--pf-c-card--size-compact__title--not--last-child--PaddingBottom, var(--pf-global--spacer--sm, .5rem)); } -:host([size="small"]) { - @include pfe-print-local($size-small); +:host([size="large"]) { + --pf-c-card__title--FontSize: var(--pf-c-card--size-large__title--FontSize, var(--pf-global--FontSize--xl, 1.25rem)); + --_pf-c-card--first-child--PaddingTop: var(--pf-c-card--size-large--first-child--PaddingTop, var(--pf-global--spacer--xl, 2rem)); + --_pf-c-card--child--PaddingRight: var(--pf-c-card--size-large--child--PaddingRight, var(--pf-global--spacer--xl, 2rem)); + --_pf-c-card--child--PaddingBottom: var(--pf-c-card--size-large--child--PaddingBottom, var(--pf-global--spacer--xl, 2rem)); + --_pf-c-card--child--PaddingLeft: var(--pf-c-card--size-large--child--PaddingLeft, var(--pf-global--spacer--xl, 2rem)); + --_pf-c-card__title--not--last-child--PaddingBottom: var(--pf-c-card--size-large__title--not--last-child--PaddingBottom, var(--pf-global--spacer--lg, 1.5rem)); } -:host([border]:not([border="false"])) { - --pfe-card--BorderWidth: #{pfe-fetch(surface--border-width)}; +:host([flat]) { + --pf-c-card--BoxShadow: none; + border: var(--pf-c-card--m-flat--BorderWidth, var(--pf-global--BorderWidth--sm, 1px)) solid var(--pf-c-card--m-flat--BorderColor, var(--pf-global--BorderColor--100, #d2d2d2)); } -// Targets the wrappers in the shadow DOM -.pfe-card { - &__header, - &__body, - &__footer { - ::slotted([overflow~="top"]) { - z-index: 1; - margin-top: -2rem; //IE11 fallback - margin-top: calc(-1 * #{pfe-local(PaddingTop)}) !important; - :host(.has-header) & { - padding-top: pfe-local(spacing); - } - } - - ::slotted([overflow~="right"]) { - margin-right: -2rem; //IE11 fallback - margin-right: calc(-1 * #{pfe-local(PaddingRight)}); - } - - ::slotted([overflow~="bottom"]) { - margin-bottom: -2rem; //IE11 fallback - margin-bottom: calc(-1 * calc(#{pfe-local(PaddingBottom)} + #{pfe-local(BorderRadius)})); - align-self: flex-end; - } - - ::slotted([overflow~="left"]) { - margin-left: -2rem; //IE11 fallback - margin-left: calc(-1 * #{pfe-local(PaddingLeft)}); - } - - ::slotted(img) { - max-width: 100% !important; - align-self: flex-start; //Don't stretch image 100% with other Flexbox items in card. - object-fit: cover; // Fix distortion - } - - ::slotted(img:not[overflow]) { - align-self: flex-start; //Don't stretch image 100% with other Flexbox items in card. - } - - ::slotted(img[overflow]) { - max-width: unset !important; - } - - ::slotted(img[overflow~="right"]) { - width: calc(100% + 2rem) !important; //IE11 fallback - width: calc(100% + #{pfe-local(PaddingRight)}) !important; - } - - ::slotted(img[overflow~="left"]) { - width: calc(100% + 2rem) !important; //IE11 fallback - width: calc(100% + #{pfe-local(PaddingLeft)}) !important; - } - - ::slotted(img[overflow~="right"][overflow~="left"]) { - width: calc(100% + 4rem) !important; //IE11 fallback - width: calc(100% + #{pfe-local(PaddingRight)} + #{pfe-local(PaddingLeft)}) !important; - } - } - &__header { - z-index: 2; - // Declare the header background color - background-color: pfe-local(BackgroundColor, $region: header); - color: pfe-local($cssvar: Color, $region: header); - :host([color-palette^="dark"]) & { - // Declare the header background color - background-color: pfe-local(BackgroundColor--dark, $region: header); - } - - // Margin styles on header region - margin-top: calc(#{pfe-local(PaddingTop)} * -1) !important; - margin-right: calc(#{pfe-local(PaddingRight)} * -1); - margin-bottom: pfe-local(spacing--vertical); - margin-left: calc(#{pfe-local(PaddingLeft)} * -1); - - // Padding for the header region - padding-top: pfe-local(spacing--vertical); - padding-right: pfe-local(PaddingRight); - padding-left: pfe-local(PaddingLeft); - padding-bottom: pfe-local(spacing--vertical); - - &:not(.has-body,.has-footer) { - margin-bottom: pfe-local(PaddingBottom); - } - - ::slotted([overflow~="top"]) { - --pfe-card__overflow--MarginTop: calc(#{pfe-local(PaddingTop)} * -1); - } - - &:not(.has-header) { - display: none; - } - - &.has-body,&.has-footer ::slotted([overflow~="bottom"]) { - --pfe-card__overflow--MarginBottom: calc(#{pfe-local(spacing--vertical)} * -1); - } - - ::slotted([overflow~="bottom"]) { - --pfe-card__overflow--MarginBottom: calc(#{pfe-local(PaddingBottom)} * -1); - } - - @each $tag in (h1, h2, h3, h4, h5, h6) { - ::slotted(#{$tag}) { - margin-bottom: 0; - } - } - } - &__body { - &:not(.has-header) ::slotted([overflow~="top"]) { - --pfe-card__overflow--MarginTop: calc(#{pfe-local(PaddingTop)} * -1); - } - - ::slotted([overflow~="top"]) { - z-index: 1; - --pfe-card__overflow--MarginTop: calc(#{pfe-local(spacing--vertical)} * -1); - } - - ::slotted([overflow~="bottom"]) { - --pfe-card__overflow--MarginBottom: calc(#{pfe-local(PaddingBottom)} * -1); - } - - &.has-footer ::slotted([overflow~="bottom"]) { - --pfe-card__overflow--MarginBottom: calc(#{pfe-local(spacing--vertical)} * -1); - } +:host([plain]) { + --pf-c-card--BoxShadow: var(--pf-c-card--m-plain--BoxShadow, none); + --pf-c-card--BackgroundColor: var(--pf-c-card--m-plain--BackgroundColor, transparent); +} - &:is(.has-header){ - ::slotted(img[overflow~="top"]){ - padding-top: pfe-local(spacing); - } - } +:host([rounded]) { + border-radius: var(--pf-c-card--m-rounded--BorderRadius, var(--pf-global--BorderRadius--sm, 3px)); +} - &:not(.has-footer) { - margin-bottom: 0; - } - } - &__footer { - margin-top: auto; // This allows the footer to move to the very bottom +:host([full-height]) { + height: var(--pf-c-card--m-full-height--Height, 100%); + --_pf-c-card__body--FullHeight--Flex: 1 1 auto; +} - display: flex; - flex-direction: pfe-local(Row, row, $region: footer); - flex-wrap: pfe-local(Wrap, wrap, $region: footer); - // Aligns buttons and CTAs - align-items: pfe-local(AlignItems, baseline, $region: footer); +[part="header"], +[part="body"], +[part="footer"] { + padding-inline-start: var(--_pf-c-card--child--PaddingLeft, var(--pf-global--spacer--lg, 1.5rem)); + padding-inline-end: var(--_pf-c-card--child--PaddingRight, var(--pf-global--spacer--lg, 1.5rem)); + padding-block-end: var(--_pf-c-card--child--PaddingBottom, var(--pf-global--spacer--lg, 1.5rem)); +} - ::slotted([overflow~="bottom"]) { - --pfe-card__overflow--MarginBottom: calc(#{pfe-local(PaddingBottom)} * -1); - } +[part="body"] { + font-size: var(--_pf-c-card__body--FontSize, var(--pf-global--FontSize--md, 1rem)); + flex: var(--_pf-c-card__body--FullHeight--Flex, initial); +} - &:not(.has-footer) { - display: none; - } - } - &__header, - &__body { - margin-bottom: pfe-local(spacing--vertical); +header { + padding-block-start: var(--_pf-c-card--first-child--PaddingTop, var(--pf-global--spacer--lg, 1.5rem)); + padding-block-end: var(--_pf-c-card__title--not--last-child--PaddingBottom, var(--pf-global--spacer--md, 1rem)); +} - //-- Slotted styles for typography - // Remove margins from typography inside the slots - @each $tag in (p, h1, h2, h3, h4, h5, h6) { - ::slotted(#{$tag}:first-child) { - // Remove top margins - margin-top: 0; - } - } - } +header ::slotted(*) { + font-family: var(--pf-c-card__title--FontFamily, var(--pf-global--FontFamily--heading--sans-serif, "RedHatDisplayUpdated", helvetica, arial, sans-serif)); + font-size: var(--pf-c-card__title--FontSize, var(--pf-global--FontSize--md, 1rem)); + font-weight: var(--pf-c-card__title--FontWeight, var(--pf-global--FontWeight--bold, 700)); + margin-block: 0; } -#header ::slotted(:is(h1,h2,h3,h4,h5,h6)) { - font-weight: var(--pfe-card-header-font-weight, 500); +[part="footer"] { + font-size: var(--_pf-c-card__footer--FontSize, var(--pf-global--FontSize--md, 1rem)); } + diff --git a/elements/pfe-card/pfe-card.ts b/elements/pfe-card/pfe-card.ts index 2aff589d9e..6505cdf4ac 100644 --- a/elements/pfe-card/pfe-card.ts +++ b/elements/pfe-card/pfe-card.ts @@ -1,19 +1,7 @@ -import type { ColorPalette, ColorTheme } from '@patternfly/pfe-core/controllers/color-context.js'; - -import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { - colorContextConsumer, - colorContextProvider, - deprecation, - observed, - pfelement -} from '@patternfly/pfe-core/decorators.js'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; import style from './pfe-card.scss'; +import { BaseCard } from './BaseCard'; /** * This element creates a header, body, and footer region in which to place @@ -27,123 +15,64 @@ import style from './pfe-card.scss'; * @slot - Any content that is not designated for the header or footer slot, will go to this slot. * @slot footer * Use this slot for anything that you want to be stuck to the base of the card. - * This region is bottom-aligned. - * - * @slot pfe-card--header - {@deprecated use header} - * @slot pfe-card--footer - {@deprecated use footer} * * @csspart header - The container for *header* content * @csspart body - The container for *body* content * @csspart footer - The container for *footer* content * * - * @cssproperty {} --pfe-theme--color--surface--lightest {@default `#ffffff`} - * @cssproperty {} --pfe-theme--color--surface--lighter {@default `#ececec`} - * @cssproperty {} --pfe-theme--color--surface--base {@default `#f0f0f0`} - * @cssproperty {} --pfe-theme--color--surface--darker {@default `#3c3f42`} - * @cssproperty {} --pfe-theme--color--surface--darkest {@default `#151515`} - * @cssproperty {} --pfe-theme--color--surface--accent {@default `#004080`} - * @cssproperty {} --pfe-theme--color--surface--complement {@default `#002952`} - * - * @cssproperty --pfe-band--BackgroundColor - * Though using the `color` attribute is strongly recommended when setting the background color for the band, you can also use completely custom colors by updating the `--pfe-band--BackgroundColor` variable. If you update this value manually, you should also update the `--theme` context variable to invoke the right theme on it and it's child elements. Supported themes include: `light`, `dark`, and `saturated`. - * @cssproperty --pfe-card--BackgroundPosition - * This is designed for use with the `img-src` attribute to allow you to align your background image. Default value is `center center`. - * {@default `center center`} - * @cssproperty --pfe-card--Border - * This allows the customization of a border around the entire container. - * {@default `center center`} - * @cssproperty --pfe-card--Border - * This allows the customization of a border around the entire container. - * @cssproperty --pfe-card--BorderRadius - * This allows the customization of a border radius around the entire container. - * @cssproperty --pfe-card--BorderWeight - * This allows the customization of a border weight around the entire container. - * @cssproperty --pfe-card--BorderStyle - * This allows the customization of a border style around the entire container. - * @cssproperty --pfe-card--BorderColor - * This allows the customization of a border color around the entire container. + * @cssproperty {} --pf-c-card--BackgroundColor {@default `#ffffff`} + * @cssproperty {} --pf-c-card--BoxShadow {@default `0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)`} + * @cssproperty {} --pf-c-card--size-compact__body--FontSize {@default `.875rem`} + * @cssproperty {} --pf-c-card--size-compact__footer--FontSize {@default `1rem`} + * @cssproperty {} --pf-c-card--size-compact--first-child--PaddingTop {@default `1.5rem`} + * @cssproperty {} --pf-c-card--size-compact--child--PaddingRight {@default `1rem`} + * @cssproperty {} --pf-c-card--size-compact--child--PaddingBottom {@default `1rem`} + * @cssproperty {} --pf-c-card--size-compact--child--PaddingLeft {@default `1rem`} + * @cssproperty {} --pf-c-card--size-compact__title--not--last-child--PaddingBottom {@default `.5rem`} + * @cssproperty {} --pf-c-card--size-large__title--FontSize {@default `1.25rem`} + * @cssproperty {} --pf-c-card--size-large--first-child--PaddingTop {@default `2rem`} + * @cssproperty {} --pf-c-card--size-large--child--PaddingRight {@default `2rem`} + * @cssproperty {} --pf-c-card--size-large--child--PaddingBottom {@default `2rem`} + * @cssproperty {} --pf-c-card--size-large--child--PaddingLeft {@default `2rem`} + * @cssproperty {} --pf-c-card--size-large__title--not--last-child--PaddingBottom {@default `1.5rem`} + * @cssproperty {} --pf-c-card--m-flat--BorderWidth {@default `1px solid #d2d2d2`} + * @cssproperty {} --pf-c-card--m-plain--BoxShadow {@default `none`} + * @cssproperty {} --pf-c-card--m-plain--BackgroundColor {@default `transparent`} + * @cssproperty {} --pf-c-card--m-rounded--BorderRadius {@default `3px`} + * @cssproperty {} --pf-c-card--m-full-height--Height {@default `100%`} + * @cssproperty {} --pf-c-card__title--FontFamily {@default `"RedHatDisplayUpdated", helvetica, arial, sans-serif`} + * @cssproperty {} --pf-c-card__title--FontSize {@default `1rem`} + * @cssproperty {} --pf-c-card__title--FontWeight {@default `700`} */ -@customElement('pfe-card') @pfelement() -export class PfeCard extends LitElement { +@customElement('pfe-card') +export class PfeCard extends BaseCard { static readonly version = '{{version}}'; - static readonly styles = [style]; + static readonly styles = [...BaseCard.styles, style]; /** - * Optional background image applied to the entire card container. - * Alignment of this image can be managed using the `--pfe-card--BackgroundPosition` - * variable which is set to `center center` by default. + * Optionally provide a size for the card and the card contents. + * The default is set to `undefined` and provides default styles. + * Compact provides styles which decreases the padding between the sections. + * Large provides styles which increases the padding between the sections and the font size for the title, header, and footer. */ - @observed - @property({ attribute: 'img-src', reflect: true }) imgSrc?: string; + @property({ reflect: true }) size?: 'compact' | 'large'; /** - * Sets color palette, which affects the element's styles as well as descendants' color theme. - * Overrides parent color context. - * Your theme will influence these colors so check there first if you are seeing inconsistencies. - * See [CSS Custom Properties](#css-custom-properties) for default values - * - * Card always resets its context to `base`, unless explicitly provided with a `color-palette`. - */ - @colorContextProvider() - @property({ reflect: true, attribute: 'color-palette' }) colorPalette?: ColorPalette = 'base'; - - /** @deprecated use `color-palette` */ - @deprecation({ alias: 'colorPalette', attribute: 'color' }) color?: ColorPalette; + * Optionally apply a border radius for the drop shadow and/or border. + */ + @property({ type: Boolean, reflect: true }) rounded = false; /** - * Sets color theme based on parent context - */ - @colorContextConsumer() - @property({ reflect: true }) on?: ColorTheme; - - /** Optionally adjusts the padding on the container. Accepts: `small`. */ - @property({ reflect: true }) size?: 'small'; + * Optionally allow the card to take up the full height of the parent element. + */ + @property({ type: Boolean, reflect: true, attribute: 'full-height' }) fullHeight = false; /** - * Optionally apply a border color and weight to the entire card container. - * The default color and weight is `#d2d2d2` and `1px`, respectively. + * Optionally remove the border on the card container. */ - @property({ type: Boolean, reflect: true }) border = false; - - protected slots = new SlotController(this, { - slots: ['header', null, 'footer'], - deprecations: { - header: 'pfe-card--header', - footer: 'pfe-card--footer', - } - }); - - render() { - const classes = { - 'has-header': this.slots.hasSlotted('header', 'pfe-card--header'), - 'has-footer': this.slots.hasSlotted('footer', 'pfe-card--footer'), - 'has-body': this.slots.hasSlotted(), - }; - - return html` -
- - -
-
- -
- - `; - } - - /** Update the background image */ - protected _imgSrcChanged(_oldValue: unknown, newValue: unknown) { - if (typeof this.imgSrc === 'string') { - // Set the image as the background image - this.style.backgroundImage = newValue ? `url('${newValue}')` : ``; - } - } + @property({ type: Boolean, reflect: true }) plain = false; } declare global { diff --git a/elements/pfe-card/test/pfe-card.spec.ts b/elements/pfe-card/test/pfe-card.spec.ts index 1b7c0757b2..06b9c89b02 100644 --- a/elements/pfe-card/test/pfe-card.spec.ts +++ b/elements/pfe-card/test/pfe-card.spec.ts @@ -1,382 +1,181 @@ import { expect, html, aTimeout } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { hexToRgb, getColor } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; - +import { a11ySnapshot, findAccessibilityNode } from '@web/test-runner-commands'; import '@patternfly/pfe-tools/test/stub-logger.js'; import { PfeCard } from '@patternfly/pfe-card'; -/** Returns the luminance value from RGB */ -const luminance = (r: number, g: number, b: number): number => { - return (0.2126 * r / 255 + 0.7152 * g / 255 + 0.0722 * b / 255); -}; - -const TEMPLATES = { - - card1: html` - -

Card 1

-

This is pfe-card.

-
Text in footer
-
`, - - card2: html` - - - `, - - card3: html` - -

Card 3

- -
`, - - card4: html` - -

Card 4

-

This is pfe-card.

-
Text in footer
-
`, - -}; - -const dynamicHeaderFooter = html` - - This is the card - `; - describe('', function() { it('should upgrade', async function() { - expect(await createFixture(TEMPLATES.card1)) + expect(await createFixture(html``)) .to.be.an.instanceof(customElements.get('pfe-card')) .and .to.be.an.instanceof(PfeCard); }); - it(`should render a header and footer when content for those slots are added dynamically`, async function() { - const element = await createFixture(dynamicHeaderFooter); - - const header = document.createElement('h2'); - header.setAttribute('slot', 'header'); - header.textContent = 'Card Header'; - - const footer = document.createElement('div'); - footer.setAttribute('slot', 'footer'); - footer.textContent = 'This is the footer'; - - element.prepend(header); - element.appendChild(footer); - - await aTimeout(50); - - const cardHeaderSlot = - element.shadowRoot!.querySelector('slot[name="header"]')!; - const cardFooterSlot = - element.shadowRoot!.querySelector('slot[name="footer"]')!; - - await aTimeout(50); - - expect(cardHeaderSlot.assignedNodes().length).to.equal(1); - expect(cardFooterSlot.assignedNodes().length).to.equal(1); - }); - - it(`should render a header and footer when content for those deprecated slots are added dynamically`, async function() { - const element = await createFixture(dynamicHeaderFooter); - - const header = document.createElement('h2'); - header.setAttribute('slot', 'pfe-card--header'); - header.textContent = 'Card Header'; - - const footer = document.createElement('div'); - footer.setAttribute('slot', 'pfe-card--footer'); - footer.textContent = 'This is the footer'; - - element.prepend(header); - element.appendChild(footer); - - await element.updateComplete; - - const cardHeaderSlot = - element.shadowRoot!.querySelector('slot[name="pfe-card--header"]')!; - const cardFooterSlot = - element.shadowRoot!.querySelector('slot[name="pfe-card--footer"]')!; - - await element.updateComplete; + describe('with header and footer content', function() { + let element: PfeCard; + let origHeight: number; - expect(cardHeaderSlot.assignedNodes().length).to.equal(1); - expect(cardFooterSlot.assignedNodes().length).to.equal(1); - }); + beforeEach(async function() { + element = await createFixture(html` + +

Card 1

+

This is pfe-card.

+ Text in footer +
`); + }); - // Iterate over the colors object to test expected background color results - describe('color palettes', function() { - // Themes and their expected hex values - Object.entries({ - default: '#f0f0f0', - darker: '#3c3f42', - darkest: '#151515', - accent: '#004080', - complement: '#002952', - lightest: '#ffffff', - }).forEach(([colorName, colorValue]) => { - it(`it should have a background color of ${colorValue} when color-palette is ${colorName}`, async function() { - const element = await createFixture(TEMPLATES.card1); - // If this is not the default color, update the color attribute - if (colorName !== 'default') { - element.setAttribute('color-palette', colorName); - } + beforeEach(function() { + const { height } = element.getBoundingClientRect(); + origHeight = height; + }); - // Get the background color value - const [r, g, b] = getColor(element, 'background-color'); - // Test that the color is rendering as expected - expect([r, g, b]).to.deep.equal(hexToRgb(colorValue)); - // Test that the color is working - if (['dark', 'darker', 'darkest', 'complement', 'accent'].includes(colorName)) { - expect(luminance(r, g, b)).to.be.lessThan(0.5); - } else { - expect(luminance(r, g, b)).to.be.greaterThan(0.5); - } + describe('size', function() { + describe('unset', function() { + it('should have default sizing for card parts', function() { + expect(getComputedStyle(element.querySelector('p')!, 'body font-size').getPropertyValue('font-size')).to.equal('16px'); + expect(getComputedStyle(element.querySelector('[slot=footer]')!, 'footer font-size').getPropertyValue('font-size')).to.equal('16px'); + }); }); - it(`it should have a background color of ${colorValue} when deprecated color attribute is ${colorName}`, async function() { - const element = await createFixture(TEMPLATES.card1); - // If this is not the default color, update the color attribute - if (colorName !== 'default') { - element.setAttribute('color', colorName); - } - - await element.updateComplete; + describe('compact', function() { + beforeEach(async function() { + element.setAttribute('size', 'compact'); + }); + it('should be smaller', function() { + const { height } = element.getBoundingClientRect(); + expect(origHeight, 'height').to.be.greaterThan(height); + }); + }); - // Get the background color value - const [r, g, b] = getColor(element, 'background-color'); - // Test that the color is rendering as expected - expect([r, g, b]).to.deep.equal(hexToRgb(colorValue)); - // Test that the color is working - if (['dark', 'darker', 'darkest', 'complement', 'accent'].includes(colorName)) { - expect(luminance(r, g, b)).to.be.lessThan(0.5); - } else { - expect(luminance(r, g, b)).to.be.greaterThan(0.5); - } + describe('large', function() { + beforeEach(async function() { + element.setAttribute('size', 'large'); + }); + it('should have larger font sizes for body, footer, more padding for header', function() { + const { height } = element.getBoundingClientRect(); + expect(origHeight, 'height').to.be.lessThan(height); + expect(getComputedStyle(element.querySelector('p')!, 'body font-size').getPropertyValue('font-size')) + .to.equal('16px'); + expect(getComputedStyle(element.querySelector('[slot=footer]')!, 'footer font-size').getPropertyValue('font-size')) + .to.equal('16px'); + }); }); }); - }); - - it('should have standard padding when size is not set', async function() { - const element = await createFixture(TEMPLATES.card1); - expect(getComputedStyle(element).getPropertyValue('padding')).to.equal('32px'); - }); - - it('should have reduced padding when size is small', async function() { - const element = await createFixture(TEMPLATES.card1); - element.setAttribute('size', 'small'); - expect(getComputedStyle(element).getPropertyValue('padding')).to.equal('16px'); - element.removeAttribute('size'); - }); - it('should have a standard border when border is set', async function() { - const element = await createFixture(TEMPLATES.card1); - element.setAttribute('border', ''); - - expect(getColor(element, 'border-left-color')).to.deep.equal(hexToRgb('#d2d2d2')); - expect(Math.round(parseFloat(getComputedStyle(element).getPropertyValue('border-left-width')))) - .to.equal(1); - }); - - // Iterate over the slots object to test expected results - describe('slots', function() { - Object.entries({ - header: { - name: 'header', - class: 'pfe-card__header', - content: 'Card 1', - }, - body: { - name: undefined, - class: 'pfe-card__body', - content: 'This is pfe-card.', - }, - footer: { - name: 'footer', - class: 'pfe-card__footer', - content: 'Text in footer', - }, - }).forEach(([title, config]) => { - it(`${title} content is placed into correct slot`, async function() { - const element = await createFixture(TEMPLATES.card1); - const selector = title !== 'body' ? `[slot=${config.name}]` : 'p'; - expect(element.querySelector(selector)!.assignedSlot) - .to.equal(element.shadowRoot!.querySelector(`.${config.class} > *`) ); + describe('rounded', function() { + describe('unset', function() { + it('should have default border radius', function() { + expect(getComputedStyle(element).getPropertyValue('border-radius')).to.equal('0px'); + }); + }); - const content = element.shadowRoot!.querySelector(`.${config.class} > *`)! - .assignedNodes() - .map(n => n.textContent) - .join('') - .trim(); - expect(content).to.equal(config.content); + describe('is set', function() { + it('should have 3px border radius', async function() { + element.setAttribute('rounded', ''); + await element.updateComplete; + expect(getComputedStyle(element).getPropertyValue('border-radius')).to.equal('3px'); + }); }); }); - }); - describe('deprecated slots', function() { - let element: PfeCard|undefined; + describe('flat', function() { + describe('unset', function() { + it('should have default box shadow and border (none)', function() { + expect(getComputedStyle(element).getPropertyValue('box-shadow')).not.to.equal('none'); + expect(getComputedStyle(element).getPropertyValue('border')).to.equal('0px none rgb(0, 0, 0)'); + }); + }); - beforeEach(async function() { - element = await createFixture(html` - -

Deprecated header

-

This is pfe-card.

-
Deprecated footer
-
`); - }); + describe('is set', function() { + it('should not have a box shadow and should have an added border', async function() { + element.setAttribute('flat', ''); + await element.updateComplete; - afterEach(function() { - element = undefined; + expect(getComputedStyle(element).getPropertyValue('box-shadow')).to.equal('none'); + expect(getComputedStyle(element).getPropertyValue('border')).not.to.equal('none'); + }); + }); }); - Object.entries({ - header: { - name: 'pfe-card--header', - class: 'pfe-card__header', - content: 'Deprecated header', - }, - body: { - name: undefined, - class: 'pfe-card__body', - content: 'This is pfe-card.', - }, - footer: { - name: 'pfe-card--footer', - class: 'pfe-card__footer', - content: 'Deprecated footer', - }, - }).forEach(([title, config]) => { - it(`${title} content is placed into correct slot`, async function() { - const selector = title !== 'body' ? `[slot="${config.name}"]` : 'p'; - - const slotSelector = config.name ? `.${config.class} > [name="${config.name}"]` : `.${config.class} > slot`; - - expect(element!.querySelector(selector)!.assignedSlot) - .to.equal(element!.shadowRoot!.querySelector(slotSelector)); + describe('full-height', function() { + describe('is not set', function() { + it('should not be taller', async function() { + const { height } = element.getBoundingClientRect(); + expect(height).to.equal(origHeight); + }); + }); - const content = element!.shadowRoot! - .querySelector(slotSelector)! - .assignedNodes() - .map(n => n.textContent) - .join('') - .trim(); + describe('is set', function() { + beforeEach(async function() { + element.toggleAttribute('full-height', true); + await element.updateComplete; + }); - expect(content).to.equal(config.content); + it('should be taller', async function() { + const { height } = element.getBoundingClientRect(); + expect(height).to.be.greaterThan(origHeight); + }); }); }); - }); - // Iterate over possibilities for images - describe('overflow', function() { - ['top', 'right', 'bottom', 'left'].forEach(direction => { - it(`image should overflow to the ${direction}`, async function() { - const element = await createFixture(TEMPLATES.card2); - const image = element.querySelector('img')!; - image.setAttribute('overflow', direction); - await aTimeout(50); - const margin = direction !== 'bottom' ? '-32px' : '-35px'; - expect(getComputedStyle(image).getPropertyValue(`margin-${direction}`)).to.equal(margin); + describe('plain', function() { + describe('unset', function() { + it('should have the default box shadow and background-color', function() { + expect(getComputedStyle(element).getPropertyValue('box-shadow')).not.to.be.undefined; + expect(getComputedStyle(element).getPropertyValue('background-color')).to.equal('rgb(255, 255, 255)'); + }); + }); + describe('is set', function() { + it('should have transparent background color and no box shadow', async function() { + element.setAttribute('plain', ''); + expect(getComputedStyle(element).getPropertyValue('box-shadow')).to.equal('none'); + expect(getComputedStyle(element).getPropertyValue('background-color')).to.equal('rgba(0, 0, 0, 0)'); + }); }); }); - it('image should overflow all padding', async function() { - const element = await createFixture(TEMPLATES.card3); - const image = element.querySelector('img')!; - expect(image.getAttribute('overflow')).to.equal('top right bottom left'); - const style = getComputedStyle(image); - await aTimeout(50); - expect(style.getPropertyValue('margin-top'), 'margin-top').to.equal('-32px'); - expect(style.getPropertyValue('margin-right'), 'margin-right').to.equal('-32px'); - expect(style.getPropertyValue('margin-bottom'), 'margin-bottom').to.equal('-35px'); - expect(style.getPropertyValue('margin-left'), 'margin-left').to.equal('-32px'); + describe('accessible', function() { + it('should be accessible in both the light and shadow dom', async function() { + expect(element).to.be.accessible(); + }); + + it.skip('should have an article element wrapper in the shadow dom', async function() { + const snapshot = await a11ySnapshot(); + const foundNode = findAccessibilityNode(snapshot, node => node.role === 'WebArea'); + expect(foundNode, 'A node with the supplied name has been found.').to.not.be.null; + }); }); }); - // Iterate over the custom properties to test overrides work - describe('custom properties', function() { - Object.values({ - paddingTop: { - variable: '--pfe-card--PaddingTop', - css: 'padding-top', - default: '32px', - custom: '28px', - }, - paddingRight: { - variable: '--pfe-card--PaddingRight', - css: 'padding-right', - default: '32px', - custom: '28px', - }, - paddingBottom: { - variable: '--pfe-card--PaddingBottom', - css: 'padding-bottom', - default: '32px', - custom: '28px', - }, - paddingLeft: { - variable: '--pfe-card--PaddingLeft', - css: 'padding-left', - default: '32px', - custom: '28px', - }, - padding: { - variable: '--pfe-card--Padding', - css: 'padding', - default: '32px', - custom: '20px 10px', - }, - borderRadius: { - variable: '--pfe-card--BorderRadius', - css: 'border-radius', - default: '3px', - custom: '50%', - }, - border: { - variable: '--pfe-card--Border', - css: 'border', - default: '0px solid rgb(210, 210, 210)', - custom: '1px solid #eee', - customExpected: '1px solid rgb(238, 238, 238)', - }, - backgroundColor: { - variable: '--pfe-card--BackgroundColor', - css: 'background-color', - default: '#dfdfdf', - defaultExpected: 'rgb(240, 240, 240)', - custom: 'hotpink', - customExpected: 'rgb(255, 105, 180)', - }, - }).forEach(async property => { - describe(`${property.variable}`, function() { - let element: PfeCard; - let style: CSSStyleDeclaration; - beforeEach(async function() { - element = await createFixture(TEMPLATES.card1); - style = getComputedStyle(element); - await aTimeout(50); - }); + it(`should render a header and footer when content for those slots are added dynamically`, async function() { + const element = await createFixture(html` + + This is the card + `); - it('Uses expected default', async function() { - const actual = style.getPropertyValue(property.css); - const expected = property.defaultExpected ?? property.default; - expect(actual).to.equal(expected); - }); + const header = document.createElement('h2'); + header.setAttribute('slot', 'header'); + header.textContent = 'Card Header'; + + const footer = document.createElement('div'); + footer.setAttribute('slot', 'footer'); + footer.textContent = 'This is the footer'; - it('Allows user customization', async function() { - // Update the variable - element.style.setProperty(property.variable, property.custom); + element.prepend(header); + element.appendChild(footer); - // Test the update worked - await aTimeout(50); + await aTimeout(50); - const actual = style.getPropertyValue(property.css); - const expected = property.customExpected ?? property.custom; - expect(actual).to.equal(expected); - }); - }); - }); + const cardHeaderSlot = + element.shadowRoot!.querySelector('slot[name="header"]')!; + const cardFooterSlot = + element.shadowRoot!.querySelector('slot[name="footer"]')!; + + await aTimeout(50); + + expect(cardHeaderSlot.assignedNodes().length).to.equal(1); + expect(cardFooterSlot.assignedNodes().length).to.equal(1); }); }); diff --git a/web-test-runner.config.js b/web-test-runner.config.js index 8afea64ce2..3d2b182a3f 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -1,5 +1,6 @@ import { pfeTestRunnerConfig } from '@patternfly/pfe-tools/test-runner.js'; import { fakePrismModule } from './web-dev-server.config.js'; +import { a11ySnapshotPlugin } from '@web/test-runner-commands/plugins'; export default pfeTestRunnerConfig({ tsconfig: 'tsconfig.settings.json', @@ -8,5 +9,6 @@ export default pfeTestRunnerConfig({ // reporter: 'default', plugins: [ fakePrismModule(), + a11ySnapshotPlugin() ], });