From b6b32afe9a22e92972293d849033e7f8e180c696 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Mon, 11 May 2026 21:22:49 -0400 Subject: [PATCH 1/3] feat(banner): add pf-v6-banner --- .gitignore | 1 + elements/pf-v6-banner/demo/basic.html | 23 +++ elements/pf-v6-banner/demo/index.html | 9 ++ elements/pf-v6-banner/demo/status.html | 39 +++++ elements/pf-v6-banner/demo/sticky.html | 50 +++++++ elements/pf-v6-banner/pf-v6-banner.css | 188 +++++++++++++++++++++++++ elements/pf-v6-banner/pf-v6-banner.ts | 87 ++++++++++++ 7 files changed, 397 insertions(+) create mode 100644 elements/pf-v6-banner/demo/basic.html create mode 100644 elements/pf-v6-banner/demo/index.html create mode 100644 elements/pf-v6-banner/demo/status.html create mode 100644 elements/pf-v6-banner/demo/sticky.html create mode 100644 elements/pf-v6-banner/pf-v6-banner.css create mode 100644 elements/pf-v6-banner/pf-v6-banner.ts diff --git a/.gitignore b/.gitignore index db944ad73f..278d51f110 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ core/pfe-core/demo/* # AI .claude/settings.local.json +.claude/worktrees # Temp files *~ diff --git a/elements/pf-v6-banner/demo/basic.html b/elements/pf-v6-banner/demo/basic.html new file mode 100644 index 0000000000..91a4c58891 --- /dev/null +++ b/elements/pf-v6-banner/demo/basic.html @@ -0,0 +1,23 @@ +--- +name: Basic +description: Banners can be styled with one of 9 different colors. A basic banner should only be used when the banner color does not represent status or severity. +--- +Default banner +Red banner +Orangered banner +Orange banner +Yellow banner +Green banner +Teal banner +Blue banner +Purple banner + + + + diff --git a/elements/pf-v6-banner/demo/index.html b/elements/pf-v6-banner/demo/index.html new file mode 100644 index 0000000000..462905d585 --- /dev/null +++ b/elements/pf-v6-banner/demo/index.html @@ -0,0 +1,9 @@ +--- +name: Default +description: A default banner communicates a short snippet of information. +--- +Default banner + + diff --git a/elements/pf-v6-banner/demo/status.html b/elements/pf-v6-banner/demo/status.html new file mode 100644 index 0000000000..21686c2c72 --- /dev/null +++ b/elements/pf-v6-banner/demo/status.html @@ -0,0 +1,39 @@ +--- +name: Status +description: When a banner conveys status, use the status attribute. Include an icon and screen-reader-text for accessibility. +--- + + + Success banner + + + + + Warning banner + + + + + Danger banner + + + + + Info banner + + + + + Custom banner + + + + + diff --git a/elements/pf-v6-banner/demo/sticky.html b/elements/pf-v6-banner/demo/sticky.html new file mode 100644 index 0000000000..3af89c14fa --- /dev/null +++ b/elements/pf-v6-banner/demo/sticky.html @@ -0,0 +1,50 @@ +--- +name: Sticky +description: A sticky banner stays fixed at the top of its container as content scrolls. +--- +Sticky banner +
+

The Open Source Definition

+

https://opensource.org/osd/

+ +

Introduction

+

Open source doesn't just mean access to the source code. The distribution + terms of open-source software must comply with the following criteria:

+ +

1. Free Redistribution

+

The license shall not restrict any party from selling or giving away the + software as a component of an aggregate software distribution containing + programs from several different sources. The license shall not require a + royalty or other fee for such sale.

+ +

2. Source Code

+

The program must include source code, and must allow distribution in + source code as well as compiled form.

+ +

3. Derived Works

+

The license must allow modifications and derived works, and must allow + them to be distributed under the same terms as the license of the original + software.

+ +

4. Integrity of The Author's Source Code

+

The license may restrict source-code from being distributed in modified + form only if the license allows the distribution of "patch files" with the + source code for the purpose of modifying the program at build time.

+ +

5. No Discrimination Against Persons or Groups

+

The license must not discriminate against any person or group of + persons.

+ +

6. No Discrimination Against Fields of Endeavor

+

The license must not restrict anyone from making use of the program in a + specific field of endeavor.

+ +

7. Distribution of License

+

The rights attached to the program must apply to all to whom the program + is redistributed without the need for execution of an additional license by + those parties.

+
+ + diff --git a/elements/pf-v6-banner/pf-v6-banner.css b/elements/pf-v6-banner/pf-v6-banner.css new file mode 100644 index 0000000000..5c4d1e6186 --- /dev/null +++ b/elements/pf-v6-banner/pf-v6-banner.css @@ -0,0 +1,188 @@ +:host { + display: block; + box-sizing: border-box; +} + +:host([hidden]) { + display: none !important; +} + +:host([sticky]) { + position: sticky; + inset-block-start: 0; + z-index: var(--pf-v6-c-banner--m-sticky--ZIndex, + var(--pf-t--global--z-index--md, 300)); + box-shadow: var(--pf-v6-c-banner--m-sticky--BoxShadow, + var(--pf-t--global--box-shadow--md)); +} + +#container { + --_bg: var(--pf-v6-c-banner--BackgroundColor, + var(--pf-t--global--color--nonstatus--gray--default, + light-dark(var(--pf-t--color--gray--20, #e0e0e0), var(--pf-t--color--gray--60, #4d4d4d)))); + --_color: var(--pf-v6-c-banner--Color, + var(--pf-t--global--text--color--nonstatus--on-gray--default, + light-dark(var(--pf-t--color--gray--100, #151515), var(--pf-t--color--white, #ffffff)))); + --_padding-block: var(--pf-v6-c-banner--PaddingBlockStart, + var(--pf-t--global--spacer--xs, 0.25rem)); + --_padding-inline: var(--pf-v6-c-banner--PaddingInlineStart, + var(--pf-t--global--spacer--md, 1rem)); + --_font-size: var(--pf-v6-c-banner--FontSize, + var(--pf-t--global--font--size--body--default, 0.875rem)); + --_border-color: var(--pf-v6-c-banner--BorderColor, + var(--pf-t--global--border--color--high-contrast, transparent)); + --_border-width: var(--pf-v6-c-banner--BorderWidth, + var(--pf-t--global--border--width--high-contrast--regular, 0)); + + flex-shrink: 0; + padding-block: var(--_padding-block); + padding-inline: var(--_padding-inline); + font-size: var(--_font-size); + color: var(--_color); + white-space: nowrap; + background-color: var(--_bg); + border-block: var(--_border-width) solid var(--_border-color); + + @media (min-width: 768px) { + --_padding-inline: var(--pf-v6-c-banner--md--PaddingInlineStart, + var(--pf-t--global--spacer--lg, 1.5rem)); + } + + /* Non-status color modifiers (before status so status takes precedence) */ + + &.red { + --_bg: var(--pf-v6-c-banner--m-red--BackgroundColor, + var(--pf-t--global--color--nonstatus--red--default, + light-dark(var(--pf-t--color--red--20, #fbc5c5), var(--pf-t--color--red--30, #f9a8a8)))); + --_color: var(--pf-v6-c-banner--m-red--Color, + var(--pf-t--global--text--color--nonstatus--on-red--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.orangered { + --_bg: var(--pf-v6-c-banner--m-orangered--BackgroundColor, + var(--pf-t--global--color--nonstatus--orangered--default, + light-dark(var(--pf-t--color--red-orange--20, #fbbea8), var(--pf-t--color--red-orange--30, #f89b78)))); + --_color: var(--pf-v6-c-banner--m-orangered--Color, + var(--pf-t--global--text--color--nonstatus--on-orangered--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.orange { + --_bg: var(--pf-v6-c-banner--m-orange--BackgroundColor, + var(--pf-t--global--color--nonstatus--orange--default, + light-dark(var(--pf-t--color--orange--20, #fccb8f), var(--pf-t--color--orange--30, #f8ae54)))); + --_color: var(--pf-v6-c-banner--m-orange--Color, + var(--pf-t--global--text--color--nonstatus--on-orange--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.yellow { + --_bg: var(--pf-v6-c-banner--m-yellow--BackgroundColor, + var(--pf-t--global--color--nonstatus--yellow--default, + light-dark(var(--pf-t--color--yellow--20, #ffe072), var(--pf-t--color--yellow--30, #ffcc17)))); + --_color: var(--pf-v6-c-banner--m-yellow--Color, + var(--pf-t--global--text--color--nonstatus--on-yellow--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.green { + --_bg: var(--pf-v6-c-banner--m-green--BackgroundColor, + var(--pf-t--global--color--nonstatus--green--default, + light-dark(var(--pf-t--color--green--20, #d1f1bb), var(--pf-t--color--green--30, #afdc8f)))); + --_color: var(--pf-v6-c-banner--m-green--Color, + var(--pf-t--global--text--color--nonstatus--on-green--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.teal { + --_bg: var(--pf-v6-c-banner--m-teal--BackgroundColor, + var(--pf-t--global--color--nonstatus--teal--default, + light-dark(var(--pf-t--color--teal--20, #b9e5e5), var(--pf-t--color--teal--30, #9ad8d8)))); + --_color: var(--pf-v6-c-banner--m-teal--Color, + var(--pf-t--global--text--color--nonstatus--on-teal--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.blue { + --_bg: var(--pf-v6-c-banner--m-blue--BackgroundColor, + var(--pf-t--global--color--nonstatus--blue--default, + light-dark(var(--pf-t--color--blue--20, #b9dafc), var(--pf-t--color--blue--30, #92c5f9)))); + --_color: var(--pf-v6-c-banner--m-blue--Color, + var(--pf-t--global--text--color--nonstatus--on-blue--default, + var(--pf-t--color--gray--100, #151515))); + } + + &.purple { + --_bg: var(--pf-v6-c-banner--m-purple--BackgroundColor, + var(--pf-t--global--color--nonstatus--purple--default, + light-dark(var(--pf-t--color--purple--20, #d0c5f4), var(--pf-t--color--purple--30, #b6a6e9)))); + --_color: var(--pf-v6-c-banner--m-purple--Color, + var(--pf-t--global--text--color--nonstatus--on-purple--default, + var(--pf-t--color--gray--100, #151515))); + } + + /* Status modifiers (after color so status takes precedence at equal specificity) */ + + &.danger { + --_bg: var(--pf-v6-c-banner--m-danger--BackgroundColor, + var(--pf-t--global--color--status--danger--default, + light-dark(var(--pf-t--color--red-orange--60, #b1380b), var(--pf-t--color--red-orange--50, #f0561d)))); + --_color: var(--pf-v6-c-banner--m-danger--Color, + var(--pf-t--global--text--color--status--on-danger--default, + light-dark(var(--pf-t--color--white, #ffffff), var(--pf-t--color--gray--100, #1f1f1f)))); + } + + &.success { + --_bg: var(--pf-v6-c-banner--m-success--BackgroundColor, + var(--pf-t--global--color--status--success--default, + light-dark(var(--pf-t--color--green--60, #3d7317), var(--pf-t--color--green--40, #87bb62)))); + --_color: var(--pf-v6-c-banner--m-success--Color, + var(--pf-t--global--text--color--status--on-success--default, + light-dark(var(--pf-t--color--white, #ffffff), var(--pf-t--color--gray--100, #1f1f1f)))); + } + + &.warning { + --_bg: var(--pf-v6-c-banner--m-warning--BackgroundColor, + var(--pf-t--global--color--status--warning--default, + light-dark(var(--pf-t--color--yellow--30, #ffcc17), var(--pf-t--color--yellow--30, #ffcc17)))); + --_color: var(--pf-v6-c-banner--m-warning--Color, + var(--pf-t--global--text--color--status--on-warning--default, + light-dark(var(--pf-t--color--gray--100, #151515), var(--pf-t--color--gray--100, #1f1f1f)))); + } + + &.info { + --_bg: var(--pf-v6-c-banner--m-info--BackgroundColor, + var(--pf-t--global--color--status--info--default, + light-dark(var(--pf-t--color--purple--50, #5e40be), var(--pf-t--color--purple--30, #b6a6e9)))); + --_color: var(--pf-v6-c-banner--m-info--Color, + var(--pf-t--global--text--color--status--on-info--default, + light-dark(var(--pf-t--color--white, #ffffff), var(--pf-t--color--gray--100, #1f1f1f)))); + } + + &.custom { + --_bg: var(--pf-v6-c-banner--m-custom--BackgroundColor, + var(--pf-t--global--color--status--custom--default, + light-dark(var(--pf-t--color--teal--60, #147878), var(--pf-t--color--teal--40, #63bdbd)))); + --_color: var(--pf-v6-c-banner--m-custom--Color, + var(--pf-t--global--text--color--status--on-custom--default, + light-dark(var(--pf-t--color--white, #ffffff), var(--pf-t--color--gray--100, #1f1f1f)))); + } +} + +::slotted(a) { + color: inherit; + text-decoration-line: underline; +} + +.sr-only { + position: absolute; + inline-size: 1px; + block-size: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} diff --git a/elements/pf-v6-banner/pf-v6-banner.ts b/elements/pf-v6-banner/pf-v6-banner.ts new file mode 100644 index 0000000000..de828d0446 --- /dev/null +++ b/elements/pf-v6-banner/pf-v6-banner.ts @@ -0,0 +1,87 @@ +import { LitElement, html, nothing, type TemplateResult } from 'lit'; + +import { classMap } from 'lit/directives/class-map.js'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import style from './pf-v6-banner.css'; + +export type BannerColor = + | 'red' + | 'orangered' + | 'orange' + | 'yellow' + | 'green' + | 'teal' + | 'blue' + | 'purple'; + +export type BannerStatus = + | 'success' + | 'warning' + | 'danger' + | 'info' + | 'custom'; + +/** + * A banner provides a full-width container for communicating short, + * non-dismissible messages. Use a banner when you need to display a + * brief announcement that allows users to continue without interruption. + * + * Status banners SHOULD include an icon and `screen-reader-text` so + * screen readers can announce the status context (WCAG 1.3.1). Authors + * SHOULD AVOID using color alone to convey meaning (WCAG 1.4.1). + * + * The banner is not focusable. Slotted interactive content like links + * remains focusable via Tab. + * + * @summary Provides a full-width banner for brief, non-dismissible messages. + * @slot - Banner content, including text, links, or icons. For accessibility, + * status banners MUST include `screen-reader-text` and SHOULD include + * a status icon so sighted users can identify the status at a glance. + * @cssprop {} [--pf-v6-c-banner--BackgroundColor] - Overrides the banner background color. Defaults to `--pf-t--global--color--nonstatus--gray--default`. + * @cssprop {} [--pf-v6-c-banner--Color] - Overrides the banner text color. Defaults to `--pf-t--global--text--color--nonstatus--on-gray--default`. + * @cssprop {} [--pf-v6-c-banner--PaddingBlockStart] - Overrides the block padding. Defaults to `--pf-t--global--spacer--xs`. + * @cssprop {} [--pf-v6-c-banner--PaddingInlineStart] - Overrides the inline padding. Defaults to `--pf-t--global--spacer--md`. + * @cssprop {} [--pf-v6-c-banner--md--PaddingInlineStart] - Overrides the inline padding at the medium breakpoint. Defaults to `--pf-t--global--spacer--lg`. + * @cssprop {} [--pf-v6-c-banner--FontSize] - Overrides the font size. Defaults to `--pf-t--global--font--size--body--default`. + * @cssprop {} [--pf-v6-c-banner--BorderColor] - Overrides the block border color, visible in high-contrast mode. Defaults to `--pf-t--global--border--color--high-contrast`. + * @cssprop {} [--pf-v6-c-banner--BorderWidth] - Overrides the block border width, visible in high-contrast mode. Defaults to `--pf-t--global--border--width--high-contrast--regular`. + * @cssprop {} [--pf-v6-c-banner--m-sticky--ZIndex] - Overrides the z-index when sticky. Defaults to `--pf-t--global--z-index--md`. + * @cssprop [--pf-v6-c-banner--m-sticky--BoxShadow] - Overrides the box shadow when sticky. Defaults to `--pf-t--global--box-shadow--md`. + */ +@customElement('pf-v6-banner') +export class PfV6Banner extends LitElement { + static readonly styles: CSSStyleSheet[] = [style]; + + /** Non-status color for the banner background. Overridden by `status` if both are set. */ + @property({ reflect: true }) color?: BannerColor; + + /** Status style for the banner. Conveys semantic meaning and overrides `color`. */ + @property({ reflect: true }) status?: BannerStatus; + + /** Whether the banner sticks to the top of its container. */ + @property({ type: Boolean, reflect: true }) sticky = false; + + /** Text announced by screen readers to indicate the type of banner. */ + @property({ attribute: 'screen-reader-text' }) screenReaderText?: string; + + override render(): TemplateResult { + return html` +
+ ${!this.screenReaderText ? nothing + : html`${this.screenReaderText}`} + +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-banner': PfV6Banner; + } +} From 7b0dfc3345707d1e9cd420b24d3c1ce87a7504fc Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Wed, 13 May 2026 17:06:49 +0300 Subject: [PATCH 2/3] style: format --- elements/pf-v6-banner/pf-v6-banner.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/elements/pf-v6-banner/pf-v6-banner.ts b/elements/pf-v6-banner/pf-v6-banner.ts index de828d0446..a526938fcb 100644 --- a/elements/pf-v6-banner/pf-v6-banner.ts +++ b/elements/pf-v6-banner/pf-v6-banner.ts @@ -67,13 +67,10 @@ export class PfV6Banner extends LitElement { @property({ attribute: 'screen-reader-text' }) screenReaderText?: string; override render(): TemplateResult { + const { color = '', status = '', screenReaderText } = this; return html` -
- ${!this.screenReaderText ? nothing - : html`${this.screenReaderText}`} +
+ ${screenReaderText}
`; From 98c00ade2c27ef5c6f733fa1a633edf87a34e19c Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Wed, 13 May 2026 17:08:12 +0300 Subject: [PATCH 3/3] style: unused import --- elements/pf-v6-banner/pf-v6-banner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/pf-v6-banner/pf-v6-banner.ts b/elements/pf-v6-banner/pf-v6-banner.ts index a526938fcb..29844912b9 100644 --- a/elements/pf-v6-banner/pf-v6-banner.ts +++ b/elements/pf-v6-banner/pf-v6-banner.ts @@ -1,4 +1,4 @@ -import { LitElement, html, nothing, type TemplateResult } from 'lit'; +import { LitElement, html, type TemplateResult } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { customElement } from 'lit/decorators/custom-element.js';