Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/label-link-variant.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions elements/pf-label/demo/link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<section>
<pf-label color="blue" href="#blue" icon="radiation-alt" removable>Blue</pf-label>
<pf-label color="green" href="#green" icon="radiation-alt">Green</pf-label>
<pf-label color="orange" href="#orange">Orange</pf-label>
<pf-label color="red" href="#red"><span>Red <span class="visually-hidden-class">Hat</span></span></pf-label>
<pf-label color="purple" href="purple" removable>Purple</pf-label>
<pf-label color="cyan" href="#cyan">Cyan</pf-label>
<pf-label color="gold" href="#gold">Gold</pf-label>
<pf-label href="#grey">Grey</pf-label>
</section>

<script type="module">
import '@patternfly/elements/pf-label/pf-label.js';
</script>
172 changes: 125 additions & 47 deletions elements/pf-label/pf-label.css
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -79,18 +83,40 @@ 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));
/** blue label background color */
--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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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;
Expand Down
11 changes: 10 additions & 1 deletion elements/pf-label/pf-label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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');

Expand All @@ -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`
<span id="container"
class="${classMap({
Expand All @@ -101,7 +106,11 @@ export class PfLabel extends LitElement {
.icon="${this.icon || undefined as unknown as string}"></pf-icon>
</slot>
<!-- summary: Must contain the text for the label. -->
${isLink ? html`
<a id="link" href="${ifDefined(href)}"><slot id="text"></slot></a>
` : html`
<slot id="text"></slot>
`}
<!-- summary: container for removable labels' close button -->
<span part="close-button" ?hidden=${!this.removable}>
<pf-button plain
Expand Down
29 changes: 29 additions & 0 deletions elements/pf-label/test/pf-label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const exampleWithCompactAttribute = html`
<pf-label compact>Default Compact</pf-label>
`;

const exampleWithHref = html`
<pf-label href="https://example.com">Link Label</pf-label>
`;


describe('<pf-label>', function() {
before(function() {
Expand Down Expand Up @@ -140,4 +144,29 @@ describe('<pf-label>', 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<PfLabel>(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<PfLabel>(example);
await el.updateComplete;
const link = el.shadowRoot!.querySelector('#link');
expect(link).to.be.null;
});
});
});
Loading