web: Document the labels. Fix the alerts
This commit documents and adds unit tests for our `Labels` component, which are usually called "chips" in other design systems. I've also reverted to allowing the components that take 'level' information to take it as a single argument that is _either_ an attribute or a property. If it's a property, it reverts to the older behavior.
This commit is contained in:
@ -7,14 +7,6 @@ import { classMap } from "lit/directives/class-map.js";
|
||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IAlert {
|
||||
inline?: boolean;
|
||||
warning?: boolean;
|
||||
info?: boolean;
|
||||
success?: boolean;
|
||||
danger?: boolean;
|
||||
}
|
||||
|
||||
export enum Level {
|
||||
Warning = "pf-m-warning",
|
||||
Info = "pf-m-info",
|
||||
@ -22,6 +14,15 @@ export enum Level {
|
||||
Danger = "pf-m-danger",
|
||||
}
|
||||
|
||||
export const levelNames = ["warning", "info", "success", "danger"];
|
||||
export type Levels = (typeof levelNames)[number];
|
||||
|
||||
export interface IAlert {
|
||||
inline?: boolean;
|
||||
icon?: string;
|
||||
level?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Alert
|
||||
* @element ak-alert
|
||||
@ -41,60 +42,29 @@ export class Alert extends AKElement implements IAlert {
|
||||
inline = false;
|
||||
|
||||
/**
|
||||
* Fallback method of determining severity
|
||||
* Method of determining severity
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
level: Level = Level.Warning;
|
||||
level: Level | Levels = Level.Warning;
|
||||
|
||||
/**
|
||||
* Highest severity level.
|
||||
* Icon to display
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
danger = false;
|
||||
|
||||
/**
|
||||
* Next severity level.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
warning = false;
|
||||
|
||||
/**
|
||||
* Next severity level. The default severity level.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
success = false;
|
||||
|
||||
/**
|
||||
* Lowest severity level.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
info = false;
|
||||
@property()
|
||||
icon = "fa-exclamation-circle";
|
||||
|
||||
static get styles() {
|
||||
return [PFBase, PFAlert];
|
||||
}
|
||||
|
||||
get classmap() {
|
||||
const leveltags = ["danger", "warning", "success", "info"].filter(
|
||||
// @ts-ignore
|
||||
(level) => this[level] && this[level] === true,
|
||||
);
|
||||
|
||||
if (leveltags.length > 1) {
|
||||
console.warn("ak-alert has multiple levels defined");
|
||||
}
|
||||
const level = leveltags.length > 0 ? `pf-m-${leveltags[0]}` : this.level;
|
||||
|
||||
const level = levelNames.includes(this.level)
|
||||
? `pf-m-${this.level}`
|
||||
: (this.level as string);
|
||||
return {
|
||||
"pf-c-alert": true,
|
||||
"pf-m-inline": this.inline,
|
||||
@ -105,7 +75,7 @@ export class Alert extends AKElement implements IAlert {
|
||||
render() {
|
||||
return html`<div class="${classMap(this.classmap)}">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<i class="fas ${this.icon}"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
<slot></slot>
|
||||
|
@ -2,58 +2,103 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export enum PFColor {
|
||||
Green = "pf-m-green",
|
||||
Orange = "pf-m-orange",
|
||||
Red = "pf-m-red",
|
||||
Grey = "",
|
||||
Green = "success",
|
||||
Orange = "warning",
|
||||
Red = "danger",
|
||||
Grey = "info",
|
||||
}
|
||||
|
||||
@customElement("ak-label")
|
||||
export class Label extends AKElement {
|
||||
@property()
|
||||
color: PFColor = PFColor.Grey;
|
||||
export const levelNames = ["warning", "info", "success", "danger"];
|
||||
export type Level = (typeof levelNames)[number];
|
||||
|
||||
type Chrome = [Level, PFColor, string, string];
|
||||
const chromeList: Chrome[] = [
|
||||
["danger", PFColor.Red, "pf-m-red", "fa-times"],
|
||||
["warning", PFColor.Orange, "pf-m-orange", "fa-exclamation-triangle"],
|
||||
["success", PFColor.Green, "pf-m-green", "fa-check"],
|
||||
["info", PFColor.Grey, "pf-m-grey", "fa-info-circle"],
|
||||
];
|
||||
|
||||
export interface ILabel {
|
||||
icon?: string;
|
||||
compact?: boolean;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Label
|
||||
* @element ak-label
|
||||
*
|
||||
* Labels are in-page elements for labeling visual elements.
|
||||
*
|
||||
* @slot - Content of the label
|
||||
*/
|
||||
@customElement("ak-label")
|
||||
export class Label extends AKElement implements ILabel {
|
||||
/**
|
||||
* The icon to show next to the label
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* When true, creates a smaller label with tighter layout
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean })
|
||||
compact = false;
|
||||
|
||||
/**
|
||||
* Severity level
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
color: PFColor | Level = PFColor.Grey;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLabel];
|
||||
}
|
||||
|
||||
getDefaultIcon(): string {
|
||||
switch (this.color) {
|
||||
case PFColor.Green:
|
||||
return "fa-check";
|
||||
case PFColor.Orange:
|
||||
return "fa-exclamation-triangle";
|
||||
case PFColor.Red:
|
||||
return "fa-times";
|
||||
case PFColor.Grey:
|
||||
return "fa-info-circle";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
get classesAndIcon() {
|
||||
const chrome = chromeList.find(
|
||||
([level, color]) => this.color === level || this.color === color,
|
||||
);
|
||||
const [illo, icon] = chrome ? chrome.slice(2) : ["pf-m-grey", "fa-info-circle"];
|
||||
return {
|
||||
classes: {
|
||||
"pf-c-label": true,
|
||||
"pf-m-compact": this.compact,
|
||||
...(illo ? { [illo]: true } : {}),
|
||||
},
|
||||
icon: this.icon ? this.icon : icon,
|
||||
};
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<span class="pf-c-label ${this.color} ${this.compact ? "pf-m-compact" : ""}">
|
||||
const { classes, icon } = this.classesAndIcon;
|
||||
return html`<span class=${classMap(classes)}>
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i
|
||||
class="fas fa-fw ${this.icon || this.getDefaultIcon()}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i class="fas fa-fw ${icon}" aria-hidden="true"></i>
|
||||
</span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-label": Label;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ The default state of an alert is _warning_.
|
||||
|
||||
### Info
|
||||
|
||||
The icon can be changed via the `icon` attribute. It takes the name of a valid `fas`-class Font
|
||||
Awesome icon. Changing the icon can be helpful, as not everyone can see the color changes.
|
||||
|
||||
<Story of={AlertStories.InfoAlert} />
|
||||
|
||||
### Success
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { Alert, type IAlert } from "../Alert.js";
|
||||
import "../Alert.js";
|
||||
@ -17,10 +18,8 @@ const metadata: Meta<Alert> = {
|
||||
},
|
||||
argTypes: {
|
||||
inline: { control: "boolean" },
|
||||
warning: { control: "boolean" },
|
||||
info: { control: "boolean" },
|
||||
success: { control: "boolean" },
|
||||
danger: { control: "boolean" },
|
||||
level: { control: "text" },
|
||||
icon: { control: "text" },
|
||||
// @ts-ignore
|
||||
message: { control: "text" },
|
||||
},
|
||||
@ -31,15 +30,11 @@ export default metadata;
|
||||
export const DefaultStory: StoryObj = {
|
||||
args: {
|
||||
inline: false,
|
||||
warning: false,
|
||||
info: false,
|
||||
success: false,
|
||||
danger: false,
|
||||
message: "You should be alarmed.",
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
render: ({ inline, warning, info, success, danger, message }: IAlertForTesting) => {
|
||||
render: ({ inline, level, icon, message }: IAlertForTesting) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-alert {
|
||||
@ -48,13 +43,7 @@ export const DefaultStory: StoryObj = {
|
||||
max-width: 32rem;
|
||||
}
|
||||
</style>
|
||||
<ak-alert
|
||||
?inline=${inline}
|
||||
?warning=${warning}
|
||||
?info=${info}
|
||||
?success=${success}
|
||||
?danger=${danger}
|
||||
>
|
||||
<ak-alert level=${ifDefined(level)} ?inline=${inline} icon=${ifDefined(icon)}>
|
||||
<p>${message}</p>
|
||||
</ak-alert>
|
||||
</div>`;
|
||||
@ -63,15 +52,18 @@ export const DefaultStory: StoryObj = {
|
||||
|
||||
export const SuccessAlert = {
|
||||
...DefaultStory,
|
||||
args: { ...DefaultStory, ...{ success: true, message: "He's a tribute to your genius!" } },
|
||||
args: { ...DefaultStory, ...{ level: "success", message: "He's a tribute to your genius!" } },
|
||||
};
|
||||
|
||||
export const InfoAlert = {
|
||||
...DefaultStory,
|
||||
args: { ...DefaultStory, ...{ info: true, message: "An octopus has tastebuds on its arms." } },
|
||||
args: {
|
||||
...DefaultStory,
|
||||
...{ level: "info", icon: "fa-coffee", message: "It is time for coffee." },
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerAlert = {
|
||||
...DefaultStory,
|
||||
args: { ...DefaultStory, ...{ danger: true, message: "Danger, Will Robinson! Danger!" } },
|
||||
args: { ...DefaultStory, ...{ level: "danger", message: "Danger, Will Robinson! Danger!" } },
|
||||
};
|
||||
|
@ -16,17 +16,11 @@ describe("ak-alert", () => {
|
||||
});
|
||||
|
||||
it("should render an alert with the attribute", async () => {
|
||||
render(html`<ak-alert info>This is an alert</ak-alert>`, document.body);
|
||||
render(html`<ak-alert level="info">This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with conflicting attributes in priority order", async () => {
|
||||
render(html`<ak-alert danger warning>This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-danger");
|
||||
await expect(await $("ak-alert").$(">>>.pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with an inline class and the default level", async () => {
|
||||
render(html`<ak-alert inline>This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$(">>>div")).toHaveElementClass("pf-m-warning");
|
||||
|
@ -6,7 +6,7 @@ import * as ExpandStories from "./Expand.stories";
|
||||
|
||||
# Expand
|
||||
|
||||
Expand is an in-page element used to hid cluttering details that a user may wish to reveal, such as raw
|
||||
Expand is an in-page element used to hide cluttering details that a user may wish to reveal, such as raw
|
||||
details of an alert or event.
|
||||
|
||||
It has one unnamed slot for the content to be displayed.
|
||||
|
53
web/src/elements/stories/Label.docs.mdx
Normal file
53
web/src/elements/stories/Label.docs.mdx
Normal file
@ -0,0 +1,53 @@
|
||||
import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
|
||||
|
||||
import * as LabelStories from "./Label.stories";
|
||||
|
||||
<Meta of={LabelStories} />
|
||||
|
||||
# Labels
|
||||
|
||||
Labels are in-page elements that provide pointers or guidance. Frequently called "chips" in other
|
||||
design systems. Labels are used alongside other elements to warn users of conditions or status that
|
||||
they might want to be aware of
|
||||
|
||||
## Usage
|
||||
|
||||
```Typescript
|
||||
import "@goauthentik/elements/Label.js";
|
||||
```
|
||||
|
||||
Note that the content of a label _must_ be a valid HTML component; plain text does not work here. The
|
||||
default label is informational:
|
||||
|
||||
```html
|
||||
<ak-label><p>This is the content of your alert!</p></ak-label>
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
### Default: Info
|
||||
|
||||
The default state of an alert is _info_.
|
||||
|
||||
<Story of={LabelStories.DefaultStory} />
|
||||
|
||||
### Warning
|
||||
|
||||
The icon can be changed via the `icon` attribute. It takes the name of a valid `fas`-class Font
|
||||
Awesome icon. Changing the icon can be helpful, as not everyone can see the color changes. It has
|
||||
also been set "compact" to show the difference.
|
||||
|
||||
<Story of={LabelStories.CompactWarningLabel} />
|
||||
|
||||
### Success
|
||||
|
||||
This label is illustrated using the PFColor enum, rather than the attribute name. Note that the content
|
||||
is _slotted_, and can be styled.
|
||||
|
||||
<Story of={LabelStories.SuccessLabel} />
|
||||
|
||||
### Danger
|
||||
|
||||
This label is illustrated using the PFColor enum, rather than the attribute name.
|
||||
|
||||
<Story of={LabelStories.DangerLabel} />
|
83
web/src/elements/stories/Label.stories.ts
Normal file
83
web/src/elements/stories/Label.stories.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { type ILabel, Label, PFColor } from "../Label.js";
|
||||
import "../Label.js";
|
||||
|
||||
type ILabelForTesting = ILabel & { message: string };
|
||||
|
||||
const metadata: Meta<Label> = {
|
||||
title: "Elements/<ak-label>",
|
||||
component: "ak-label",
|
||||
parameters: {
|
||||
docs: {
|
||||
description: "An alert",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
compact: { control: "boolean" },
|
||||
color: { control: "text" },
|
||||
icon: { control: "text" },
|
||||
// @ts-ignore
|
||||
message: { control: "text" },
|
||||
},
|
||||
};
|
||||
|
||||
export default metadata;
|
||||
|
||||
export const DefaultStory: StoryObj = {
|
||||
args: {
|
||||
compact: false,
|
||||
message: "Eat at Joe's.",
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
render: ({ compact, color, icon, message }: ILabelForTesting) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-label {
|
||||
display: inline-block;
|
||||
width: 48rem;
|
||||
max-width: 48rem;
|
||||
}
|
||||
</style>
|
||||
<ak-label color=${ifDefined(color)} ?compact=${compact} icon=${ifDefined(icon)}>
|
||||
<p>${message}</p>
|
||||
</ak-label>
|
||||
</div>`;
|
||||
},
|
||||
};
|
||||
|
||||
export const SuccessLabel = {
|
||||
...DefaultStory,
|
||||
args: {
|
||||
...DefaultStory,
|
||||
...{
|
||||
color: PFColor.Green,
|
||||
message: html`I'll show them! I'll show them <i>all</i> ! Mwahahahahaha!`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CompactWarningLabel = {
|
||||
...DefaultStory,
|
||||
args: {
|
||||
...DefaultStory,
|
||||
...{
|
||||
compact: true,
|
||||
color: "warning",
|
||||
icon: "fa-coffee",
|
||||
message: "It is time for coffee.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerLabel = {
|
||||
...DefaultStory,
|
||||
args: {
|
||||
...DefaultStory,
|
||||
...{ color: "danger", message: "Grave danger? Is there another kind?" },
|
||||
},
|
||||
};
|
53
web/src/elements/stories/Label.test.ts
Normal file
53
web/src/elements/stories/Label.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html, render } from "lit";
|
||||
|
||||
import "../Label.js";
|
||||
import { PFColor } from "../Label.js";
|
||||
|
||||
describe("ak-label", () => {
|
||||
it("should render a label with the enum", async () => {
|
||||
render(html`<ak-label color=${PFColor.Red}>This is a label</ak-label>`, document.body);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-c-label");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).not.toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-red");
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-times");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a label with the attribute", async () => {
|
||||
render(html`<ak-label color="success">This is a label</ak-label>`, document.body);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-green");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compart label with the default level", async () => {
|
||||
render(html`<ak-label compact>This is a label</ak-label>`, document.body);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-info-circle");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compact label with an icon and the default level", async () => {
|
||||
render(html`<ak-label compact icon="fa-coffee">This is a label</ak-label>`, document.body);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-coffee");
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user