diff --git a/.changeset/label-link-variant.md b/.changeset/label-link-variant.md
new file mode 100644
index 0000000000..4cd9dabadf
--- /dev/null
+++ b/.changeset/label-link-variant.md
@@ -0,0 +1,7 @@
+---
+"@patternfly/elements": minor
+---
+
+`pf-label`: added link variant. Set the `href` attribute to render the
+label text as a clickable link. On hover, the label border thickens
+and changes color to indicate interactivity.
diff --git a/elements/pf-label/demo/link.html b/elements/pf-label/demo/link.html
new file mode 100644
index 0000000000..4eb4c4f070
--- /dev/null
+++ b/elements/pf-label/demo/link.html
@@ -0,0 +1,14 @@
+
+ Blue
+ Green
+ Orange
+ Red Hat
+ Purple
+ Cyan
+ Gold
+ Grey
+
+
+
diff --git a/elements/pf-label/pf-label.css b/elements/pf-label/pf-label.css
index f5489dc070..000d70205a 100644
--- a/elements/pf-label/pf-label.css
+++ b/elements/pf-label/pf-label.css
@@ -2,6 +2,10 @@
position: relative;
white-space: nowrap;
border: 0;
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--200, #8a8d90));
}
pf-icon, ::slotted(pf-icon) {
@@ -79,6 +83,16 @@ pf-icon, ::slotted(pf-icon) {
--pf-global--icon--FontSize--sm: 12px;
}
+.outline {
+ /** outline label background color */
+ --pf-c-label--BackgroundColor: var(--pf-c-label--m-outline--BackgroundColor, #ffffff);
+ --pf-c-label__content--before--BorderColor: var(--pf-global--palette--black-300, #d2d2d2);
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2));
+}
+
.blue {
/** blue label content text color */
--pf-c-label__content--Color: var(--pf-c-label--m-blue__content--Color, var(--pf-global--info-color--200, #002952));
@@ -86,11 +100,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-blue--BackgroundColor, var(--pf-global--palette--blue-50, #e7f1fa));
/** blue label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-blue__content--before--BorderColor, var(--pf-global--palette--blue-100, #bee1f4));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-blue__content--link--hover--before--BorderColor,
+ var(--pf-global--primary-color--100, #06c)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-blue__icon--Color, var(--pf-global--primary-color--100, #06c)));
}
.blue.outline {
/** outline blue label content text color */
--pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-blue__content--Color, var(--pf-global--primary-color--100, #06c)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-blue__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.cyan {
@@ -100,11 +126,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-cyan--BackgroundColor, var(--pf-global--palette--cyan-50, #f2f9f9));
/** cyan label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-cyan__content--before--BorderColor, var(--pf-global--palette--cyan-100, #a2d9d9));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-cyan__content--link--hover--before--BorderColor,
+ var(--pf-global--default-color--200, #009596)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-cyan__icon--Color, var(--pf-global--default-color--200, #009596)));
}
.cyan.outline {
/** outline cyan label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-cyan__content--Color, var(--pf-global--palette--cyan-400, #005f60)))
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-cyan__content--Color, var(--pf-global--palette--cyan-400, #005f60)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-cyan__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.green {
@@ -114,11 +152,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-green--BackgroundColor, var(--pf-global--palette--green-50, #f3faf2));
/** green label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-green__content--before--BorderColor, var(--pf-global--palette--green-100, #bde5b8));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-green__content--link--hover--before--BorderColor,
+ var(--pf-global--success-color--100, #3e8635)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-green__icon--Color, var(--pf-global--success-color--100, #3e8635)));
}
-.green.outline{
+.green.outline {
/** outline green label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-green__content--Color, var(--pf-global--success-color--100, #3e8635)))
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-green__content--Color, var(--pf-global--success-color--100, #3e8635)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-green__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.orange {
@@ -128,11 +178,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-orange--BackgroundColor, var(--pf-global--palette--orange-50, #fff6ec));
/** orange label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-orange__content--before--BorderColor, var(--pf-global--palette--orange-100, #f4b678));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-orange__content--link--hover--before--BorderColor,
+ var(--pf-global--palette--orange-300, #ec7a08)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-orange__icon--Color, var(--pf-global--palette--orange-300, #ec7a08)));
}
.orange.outline {
/** outline orange label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-orange__content--Color, var(--pf-global--palette--orange-500, #8f4700)))
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-orange__content--Color, var(--pf-global--palette--orange-500, #8f4700)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-orange__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.purple {
@@ -142,11 +204,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-purple--BackgroundColor, var(--pf-global--palette--purple-50, #f2f0fc));
/** purple label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-purple__content--before--BorderColor, var(--pf-global--palette--purple-100, #cbc1ff));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-purple__content--link--hover--before--BorderColor,
+ var(--pf-global--palette--purple-500, #6753ac)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-purple__icon--Color, var(--pf-global--palette--purple-500, #6753ac)));
}
.purple.outline {
/** outline purple label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-purple__content--Color, var(--pf-global--palette--purple-500, #6753ac)))
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-purple__content--Color, var(--pf-global--palette--purple-500, #6753ac)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-purple__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.red {
@@ -156,11 +230,23 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-red--BackgroundColor, var(--pf-global--palette--red-50, #faeae8));
/** red label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-red__content--before--BorderColor, var(--pf-global--palette--red-100, #c9190b));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-red__content--link--hover--before--BorderColor,
+ var(--pf-global--danger-color--100, #c9190b)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-red__icon--Color, var(--pf-global--danger-color--100, #c9190b)));
}
.red.outline {
/** outline red label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-red__content--Color, var(--pf-global--danger-color--100, #c9190b)))
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-red__content--Color, var(--pf-global--danger-color--100, #c9190b)));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-red__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.gold {
@@ -170,57 +256,30 @@ pf-icon, ::slotted(pf-icon) {
--pf-c-label--BackgroundColor: var(--pf-c-label--m-gold--BackgroundColor, var(--pf-global--palette--gold-50, #fdf7e7));
/** gold label content border color */
--pf-c-label__content--before--BorderColor: var(--pf-c-label--m-gold__content--before--BorderColor, var(--pf-global--palette--gold-100, #f9e0a2));
+
+ --_label-link-hover-border-color:
+ var(--pf-c-label__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-gold__content--link--hover--before--BorderColor,
+ var(--pf-global--palette--gold-300, #f4c145)));
+
+ --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-gold__icon--Color, var(--pf-global--palette--gold-400, #f0ab00)));
}
.gold.outline {
/** outline gold label content text color */
- --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-gold__content--Color, var(--pf-global--palette--gold-600, #795600)))
-}
+ --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-gold__content--Color, var(--pf-global--palette--gold-600, #795600)));
-.outline {
- /** outline label background color */
- --pf-c-label--BackgroundColor: var(--pf-c-label--m-outline--BackgroundColor, #ffffff);
- --pf-c-label__content--before--BorderColor: var(--pf-global--palette--black-300, #d2d2d2);
+ --_label-link-hover-border-color:
+ var(--pf-c-label--m-outline__content--link--hover--before--BorderColor,
+ var(--pf-c-label--m-outline--m-gold__content--link--hover--before--BorderColor,
+ var(--pf-global--BorderColor--100, #d2d2d2)));
}
.hasIcon [part=icon] {
left: var(--pf-c-label--PaddingLeft, var(--pf-global--spacer--md, 1rem));
margin-inline-end: var(--pf-c-label__icon--MarginRight, var(--pf-global--spacer--xs, 0.25rem));
-}
-
-.blue .hasIcon [part=icon] {
- /** blue label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-blue__icon--Color, var(--pf-global--primary-color--100, #06c)));
-}
-
-.cyan .hasIcon [part=icon] {
- /** cyan label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-cyan__icon--Color, var(--pf-global--default-color--200, #009596)));
-}
-
-.green .hasIcon [part=icon] {
- /** green label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-green__icon--Color, var(--pf-global--success-color--100, #3e8635)));
-}
-
-.orange .hasIcon [part=icon] {
- /** orange label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-orange__icon--Color, var(--pf-global--palette--orange-300, #ec7a08)));
-}
-
-.purple .hasIcon [part=icon] {
- /** purple label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-purple__icon--Color, var(--pf-global--palette--purple-500, #6753ac)));
-}
-
-.red .hasIcon [part=icon] {
- /** red label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-red__icon--Color, var(--pf-global--danger-color--100, #c9190b)));
-}
-
-.gold .hasIcon [part=icon] {
- /** gold label icon color */
- color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-gold__icon--Color, var(--pf-global--palette--gold-400, #f0ab00)));
+ /** label icon color */
+ color: var(--_label-icon-color);
}
pf-button {
@@ -240,6 +299,25 @@ pf-button {
margin-left: var(--pf-c-label__c-button--MarginLeft, 0.25rem);
}
+#link {
+ color: inherit;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+:host(:hover) #link ~ *,
+:host(:focus-within) #link ~ *,
+#link:hover,
+#link:focus {
+ cursor: pointer;
+}
+
+:host(:hover) #container:has(#link)::before,
+:host(:focus-within) #container:has(#link)::before {
+ border-width: var(--pf-c-label__content--link--hover--before--BorderWidth, 2px);
+ border-color: var(--_label-link-hover-border-color);
+}
+
svg {
vertical-align:-0.125em;
fill: currentColor;
diff --git a/elements/pf-label/pf-label.ts b/elements/pf-label/pf-label.ts
index eea234bd62..52462efa95 100644
--- a/elements/pf-label/pf-label.ts
+++ b/elements/pf-label/pf-label.ts
@@ -2,6 +2,7 @@ import { LitElement, html, isServer, type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { property } from 'lit/decorators/property.js';
import { classMap } from 'lit/directives/class-map.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js';
@@ -68,6 +69,9 @@ export class PfLabel extends LitElement {
/** Text label for a removable label's close button */
@property({ attribute: 'close-button-label' }) closeButtonLabel?: string;
+ /** When set, the label becomes a link. The label text renders inside an anchor element. */
+ @property({ reflect: true }) href?: string;
+
/** Represents the state of the anonymous and icon slots */
#slots = new SlotController(this, null, 'icon');
@@ -79,9 +83,10 @@ export class PfLabel extends LitElement {
}
override render(): TemplateResult<1> {
- const { compact, truncated } = this;
+ const { compact, truncated, href } = this;
const { variant, color, icon } = this;
const hasIcon = !!icon || this.#slots.hasSlotted('icon');
+ const isLink = !!href;
return html`
+ ${isLink ? html`
+
+ ` : html`
+ `}
Default Compact
`;
+const exampleWithHref = html`
+ Link Label
+`;
+
describe('', function() {
before(function() {
@@ -140,4 +144,29 @@ describe('', function() {
expect(containerStyles.getPropertyValue('padding-bottom')).to.equal('0px');
expect(containerStyles.getPropertyValue('padding-left')).to.equal('8px'); // 0.5rem = 8px @ 16px browser default
});
+
+ describe('with href attribute', function() {
+ let element: PfLabel;
+ beforeEach(async function() {
+ element = await createFixture(exampleWithHref);
+ await element.updateComplete;
+ });
+
+ it('should render an anchor element', function() {
+ const link = element.shadowRoot!.querySelector('#link');
+ expect(link).to.be.an.instanceOf(HTMLAnchorElement);
+ });
+
+ it('should set the href on the anchor', function() {
+ const link = element.shadowRoot!.querySelector('#link') as HTMLAnchorElement;
+ expect(link.href).to.equal('https://example.com/');
+ });
+
+ it('should not render an anchor when href is not set', async function() {
+ const el = await createFixture(example);
+ await el.updateComplete;
+ const link = el.shadowRoot!.querySelector('#link');
+ expect(link).to.be.null;
+ });
+ });
});