diff --git a/.changeset/neat-toes-guess.md b/.changeset/neat-toes-guess.md new file mode 100644 index 0000000000..8fdd46ea25 --- /dev/null +++ b/.changeset/neat-toes-guess.md @@ -0,0 +1,15 @@ +--- +"@patternfly/elements": minor +--- + +✨ Added `` + +```html + +``` diff --git a/docs/_plugins/html-example.cjs b/docs/_plugins/html-example.cjs index faaf33e2b0..df61ac6a21 100644 --- a/docs/_plugins/html-example.cjs +++ b/docs/_plugins/html-example.cjs @@ -28,9 +28,7 @@ module.exports = function(eleventyConfig) { content = await renderFromFile.call(this, dedent(content), kwargs); } return /* html */` -
- ${content} -
+
${content.trim()}
View HTML Source diff --git a/docs/main.css b/docs/main.css index 146037683b..d11fd8fb3b 100644 --- a/docs/main.css +++ b/docs/main.css @@ -281,7 +281,36 @@ header.band h1 { padding: var(--pf-global--spacer--md, 1rem); background-color: var(--pf-global--BackgroundColor--100, #fff); border-bottom: var(--pf-global--BorderWidth--sm, 1px) solid var(--pf-global--BorderColor--300, #f0f0f0); - transition: width .2s ease-in-out; +} + +.example-preview.pf-background-image { + position: relative; + z-index: 0; + height: 350px; + overflow: hidden; + color: var(--pf-theme--color--white, #fff); +} + +.example-preview.pf-background-image pf-background-image { + position: absolute; + z-index: -1; + top: 0; + left: 0; +} + +.example-preview.pf-background-image pf-background-image::part(container) { + position: relative; +} + +.example-preview.pf-background-image pf-background-image, +.example-preview.pf-background-image pf-background-image::part(container) { + height: 100%; + width: 100%; +} + +.example-preview.pf-background-image pf-background-image::part(container)::after { + position: absolute; + background-size: cover; } section.api.band.api-properties dl { @@ -731,6 +760,7 @@ code { .html-example { display: flex; padding-block: 6px; + transition: width .2s ease-in-out; } .html-example:hover summary { diff --git a/elements/package.json b/elements/package.json index 0cd47fe88f..98933cf996 100644 --- a/elements/package.json +++ b/elements/package.json @@ -18,6 +18,7 @@ "./pf-accordion/pf-accordion.js": "./pf-accordion/pf-accordion.js", "./pf-avatar/BaseAvatar.js": "./pf-avatar/BaseAvatar.js", "./pf-avatar/pf-avatar.js": "./pf-avatar/pf-avatar.js", + "./pf-background-image/pf-background-image.js": "./pf-background-image/pf-background-image.js", "./pf-badge/BaseBadge.js": "./pf-badge/BaseBadge.js", "./pf-badge/pf-badge.js": "./pf-badge/pf-badge.js", "./pf-banner/pf-banner.js": "./pf-banner/pf-banner.js", diff --git a/elements/pf-background-image/README.md b/elements/pf-background-image/README.md new file mode 100644 index 0000000000..c4dc0e7eaf --- /dev/null +++ b/elements/pf-background-image/README.md @@ -0,0 +1,37 @@ +# Background Image + +A **background image** allows you to place an image in the background of your page or area of a page. + + +## Installation +Load `` via CDN: + +```html + + + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-background-image/pf-background-image.js'; +``` + +## Usage + +```html + +``` + +[docs]: https://patternflyelements.org/components/background-image diff --git a/elements/pf-background-image/demo/filter-override.html b/elements/pf-background-image/demo/filter-override.html new file mode 100644 index 0000000000..aec2f02c8c --- /dev/null +++ b/elements/pf-background-image/demo/filter-override.html @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/elements/pf-background-image/demo/pf-background-image.html b/elements/pf-background-image/demo/pf-background-image.html new file mode 100644 index 0000000000..4427190c69 --- /dev/null +++ b/elements/pf-background-image/demo/pf-background-image.html @@ -0,0 +1,18 @@ + + + + + diff --git a/elements/pf-background-image/demo/pfbg.jpg b/elements/pf-background-image/demo/pfbg.jpg new file mode 100644 index 0000000000..88ee1f4250 Binary files /dev/null and b/elements/pf-background-image/demo/pfbg.jpg differ diff --git a/elements/pf-background-image/demo/pfbg_1200.jpg b/elements/pf-background-image/demo/pfbg_1200.jpg new file mode 100644 index 0000000000..0c0e826257 Binary files /dev/null and b/elements/pf-background-image/demo/pfbg_1200.jpg differ diff --git a/elements/pf-background-image/demo/pfbg_576.jpg b/elements/pf-background-image/demo/pfbg_576.jpg new file mode 100644 index 0000000000..a0a7fc3cdb Binary files /dev/null and b/elements/pf-background-image/demo/pfbg_576.jpg differ diff --git a/elements/pf-background-image/demo/pfbg_768.jpg b/elements/pf-background-image/demo/pfbg_768.jpg new file mode 100644 index 0000000000..af83172425 Binary files /dev/null and b/elements/pf-background-image/demo/pfbg_768.jpg differ diff --git a/elements/pf-background-image/demo/pfbg_768@2x.jpg b/elements/pf-background-image/demo/pfbg_768@2x.jpg new file mode 100644 index 0000000000..a4ca09cb82 Binary files /dev/null and b/elements/pf-background-image/demo/pfbg_768@2x.jpg differ diff --git a/elements/pf-background-image/demo/sibling-content.html b/elements/pf-background-image/demo/sibling-content.html new file mode 100644 index 0000000000..3aa3502980 --- /dev/null +++ b/elements/pf-background-image/demo/sibling-content.html @@ -0,0 +1,26 @@ + + + + +
+

Sibling Content

+ Button +
+ + + diff --git a/elements/pf-background-image/docs/pf-background-image.md b/elements/pf-background-image/docs/pf-background-image.md new file mode 100644 index 0000000000..52f2db0eea --- /dev/null +++ b/elements/pf-background-image/docs/pf-background-image.md @@ -0,0 +1,65 @@ +{% renderOverview %} + {% htmlexample class="pf-background-image" %} + + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/). + +{% endrenderOverview %} + +{% band header="Usage" %} + ### Sibling content w/ no filter + {% htmlexample class="pf-background-image" %} + +

Sibling Content

+ Button + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/sibling-content/). + + ### Override SVG Filter + + [MDN documentation for ``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter) + + {% htmlexample class="pf-background-image" %} + + + + + + + + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/filter-override/). + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-background-image/docs/screenshot.png b/elements/pf-background-image/docs/screenshot.png new file mode 100644 index 0000000000..f7b9df8e54 Binary files /dev/null and b/elements/pf-background-image/docs/screenshot.png differ diff --git a/elements/pf-background-image/pf-background-image.css b/elements/pf-background-image/pf-background-image.css new file mode 100644 index 0000000000..16d62e2354 --- /dev/null +++ b/elements/pf-background-image/pf-background-image.css @@ -0,0 +1,66 @@ +:host { + display: flex; + + --_background-image: var(--pf-c-background-image--BackgroundImage); +} + +#outer-container { + display: contents; +} + +#container { + padding: 0; + margin: 0; + background-color: transparent; +} + +#container::after { + display: block; + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + content: ""; + background-color: var(--pf-c-background-image--BackgroundColor, var(--pf-global--BackgroundColor--dark-100, #151515)); + background-image: var(--_background-image); + filter: var(--pf-c-background-image--Filter, url("#image_overlay")); + background-repeat: no-repeat; + background-size: cover; +} + +slot[name="filter"] { + display: none; +} + +slot[part="content"] { + display: block; + position: relative; + z-index: 1; + color: white; +} + +@media screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + #container::after { + background-image: var(--pf-c-background-image--BackgroundImage-2x, var(--_background-image-2x, var(--_background-image))); + } +} + +@media screen and (min-width: 576px) { + #container::after { + background-image: var(--pf-c-background-image--BackgroundImage--sm, var(--_background-image-sm, var(--_background-image))); + } +} + +@media screen and (min-width: 576px) and (-webkit-min-device-pixel-ratio: 2), (min-width: 576px) and (min-resolution: 192dpi) { + #container::after { + background-image: var(--pf-c-background-image--BackgroundImage--sm-2x, var(--_background-image-sm-2x, var(--_background-image))); + } +} + +@media screen and (min-width: 992px) { + #container::after { + background-image: var(--pf-c-background-image--BackgroundImage--lg, var(--_background-image-lg, var(--_background-image))); + } +} diff --git a/elements/pf-background-image/pf-background-image.ts b/elements/pf-background-image/pf-background-image.ts new file mode 100644 index 0000000000..ff12937d9e --- /dev/null +++ b/elements/pf-background-image/pf-background-image.ts @@ -0,0 +1,124 @@ +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js'; +import { property } from 'lit/decorators/property.js'; + + +import { styleMap, type StyleInfo } from 'lit/directives/style-map.js'; + +import styles from './pf-background-image.css'; + +/** + * A **background image** allows you to place an image in the background of your page or area of a page. + * @summary Allows users to place an image in the background of your page or area of a page. + * + * @slot filter - Overrides the default svg filter for the background image. + * + * @cssprop {} --pf-c-background-image--BackgroundColor {@default `#151515`} + * @cssprop --pf-c-background-image--BackgroundImage + * @cssprop --pf-c-background-image--BackgroundImage-2x + * @cssprop --pf-c-background-image--BackgroundImage--sm + * @cssprop --pf-c-background-image--BackgroundImage--sm-2x + * @cssprop --pf-c-background-image--BackgroundImage--lg + * @cssprop --pf-c-background-image--Filter {@default `url("#image_overlay")`} + * + */ +@customElement('pf-background-image') +export class PfBackgroundImage extends LitElement { + static readonly styles = [styles]; + + /** + * The URL for the image shown on mobile + */ + @property({ reflect: true }) src?: string; + + /** + * The image shown on mobile with 2x DPI + */ + @property({ reflect: true, attribute: 'src-2x' }) src2x?: string; + + /** + * The URL for the image shown on small screens (min-width: 576px) + */ + @property({ reflect: true, attribute: 'src-sm' }) srcSm?: string; + + /** + * The URL for the image shown on small screens (min-width: 576px) with 2x DPI + */ + @property({ reflect: true, attribute: 'src-sm-2x' }) srcSm2x?: string; + + /** + * The URL for the image shown on large screens (min-width: 992px) + */ + @property({ reflect: true, attribute: 'src-lg' }) srcLg?: string; + + /** + * Apply SVG Filter to the image + */ + @property({ type: Boolean, reflect: true }) filter = false; + + @queryAssignedElements({ slot: 'filter', selector: 'svg' }) private _svg?: SVGElement[]; + + #svg?: SVGElement; + + #updated = false; + + render() { + const cssProps = { + '--_background-image': this.src, + '--_background-image-2x': this.src2x, + '--_background-image-sm': this.srcSm, + '--_background-image-sm-2x': this.srcSm2x, + '--_background-image-lg': this.srcLg + } as StyleInfo; + + Object.entries(cssProps).forEach(([key, value]) => { + // if the value is undefined, remove the css property + if (!value) { + delete cssProps[key]; + } else { + // otherwise, add the value with the url() css function + cssProps[key] = `url(${value})`; + } + }); + + return html` +
+ ${!this.filter ? html`` : html` + + ${(this.#svg && this.#updated) ? this.#svg : html` + + + + + + + + + + + + `} + + `} +
+ `; + } + + #onSlotChange() { + const [svg] = this._svg as Array; + if (svg) { + this.#svg = svg; + this.#updated = true; + this.requestUpdate(); + } else { + this.#updated = false; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-background-image': PfBackgroundImage; + } +} diff --git a/elements/pf-background-image/test/pf-background-image.e2e.ts b/elements/pf-background-image/test/pf-background-image.e2e.ts new file mode 100644 index 0000000000..591f19a641 --- /dev/null +++ b/elements/pf-background-image/test/pf-background-image.e2e.ts @@ -0,0 +1,12 @@ +import { test } from '@playwright/test'; +import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; + +const tagName = 'pf-background-image'; + +test.describe(tagName, () => { + test('snapshot', async ({ page }) => { + const componentPage = new PfeDemoPage(page, tagName); + await componentPage.navigate(); + await componentPage.snapshot(); + }); +}); diff --git a/elements/pf-background-image/test/pf-background-image.spec.ts b/elements/pf-background-image/test/pf-background-image.spec.ts new file mode 100644 index 0000000000..3fb0a58432 --- /dev/null +++ b/elements/pf-background-image/test/pf-background-image.spec.ts @@ -0,0 +1,102 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { PfBackgroundImage } from '@patternfly/elements/pf-background-image/pf-background-image.js'; +import { setViewport } from '@web/test-runner-commands'; + +const example = html` + + +`; + +function isMobile() { + const matches = !!window.matchMedia('(max-width: 575px)').matches; + return matches; +} + +function isTablet() { + const matches = !!window.matchMedia('(min-width: 576px)').matches; + return matches; +} + +function isDesktop() { + const matches = !!window.matchMedia('(min-width: 992px)').matches; + return matches; +} + +describe('', function() { + describe('simply instantiating', function() { + let element: PfBackgroundImage; + + it('imperatively instantiates', function() { + expect(document.createElement('pf-background-image')).to.be.an.instanceof(PfBackgroundImage); + }); + + it('should upgrade', async function() { + element = await createFixture(html``); + const klass = customElements.get('pf-background-image'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfBackgroundImage); + }); + + describe('adjusting window size', function() { + describe('when the window is less than 576px wide', function() { + beforeEach(async function() { + await setViewport({ width: 320, height: 500 }); + await element.updateComplete; + expect(isMobile()).to.be.true; + }); + + it('should background image', async function() { + const element = await createFixture(example); + await element.updateComplete; + const container = element.shadowRoot!.querySelector('#container')!; + const afterStyles = getComputedStyle(container, '::after'); + expect(afterStyles.getPropertyValue('background-image')).to.contain('/components/background-image/demo/pfbg.jpg'); + }); + }); + }); + + describe('when the window is less greater then 576px wide', function() { + beforeEach(async function() { + await setViewport({ width: 576, height: 500 }); + await element.updateComplete; + expect(isTablet()).to.be.true; + }); + + it('should background image', async function() { + const element = await createFixture(example); + await element.updateComplete; + const container = element.shadowRoot!.querySelector('#container')!; + const afterStyles = getComputedStyle(container, '::after'); + /* the image sizes are not named based off viewport but their own intrinsic size */ + expect(afterStyles.getPropertyValue('background-image')).to.contain('/components/background-image/demo/pfbg_768.jpg'); + }); + }); + + describe('when the window is less greater then 992px wide', function() { + beforeEach(async function() { + await setViewport({ width: 992, height: 500 }); + await element.updateComplete; + expect(isDesktop()).to.be.true; + }); + + it('should background image', async function() { + const element = await createFixture(example); + await element.updateComplete; + const container = element.shadowRoot!.querySelector('#container')!; + const afterStyles = getComputedStyle(container, '::after'); + /* the image sizes are not named based off viewport but their own intrinsic size */ + expect(afterStyles.getPropertyValue('background-image')).to.contain('/components/background-image/demo/pfbg_1200.jpg'); + }); + }); + }); +}); +