web: provide storybook demos and docs for existing tests (#11651)

* Added tests and refinements as tests indicate.

* Building out the test suite.

* web: test the simple things. Fix what the tests revealed.

- Move `EmptyState.test.ts` into the `./tests` folder.
- Provide unit tests for:
  - Alert
  - Divider
  - Expand
  - Label
  - LoadingOverlay
- Give all tested items an Interface and a functional variant for rendering
- Give Label an alternative syntax for declaring alert levels
- Remove the slot name in LoadingOverlay
  - Change the slot call in `./enterprise/rac/index.ts` to not need the slot name as well
- Change the attribute names `topMost`, `textOpen`, and `textClosed` to `topmost`, `text-open`, and
  `text-closed`, respectively.
  - Change locations in the code where those are used to correspond

** Why interfaces: **

Provides another check on the input/output boundaries of our elements, gives Storybook and
WebdriverIO another validation to check, and guarantees any rendering functions cannot be passed
invalid property names.

** Why functions for rendering: **

Providing functions for rendering gets us one step closer to dynamically defining our forms-in-code
at runtime without losing any type safety.

** Why rename the attributes: **

A *very* subtle bug:
[Element:setAttribute()](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)
automatically "converts an attribute name to all lower-case when called on an HTML element in an
HTML document." The three attributes renamed are all treated *as* attributes, either classic boolean
or stringly-typed attributes, and attempting to manipulate them with `setAttribute()` will fail.

All of these attributes are presentational; none of them end up in a transaction with the back-end,
so kebab-to-camel conversions are not a concern.

Also, ["topmost" is one word](https://www.merriam-webster.com/dictionary/topmost).

** Why remove the slot name: **

Because there was only one slot.  A name is not needed.

* Fix minor spelling error.

* First pass at a custom, styled input object.

* .

* web: Demo the simple things. Fix things the Demo says need fixing.

- Move the Element's stories into a `./stories` folder
- Provide stories for (these are the same ones "provided tests for" in the [previous
  PR](https://github.com/goauthentik/authentik/pull/11633))
  - Alert
  - Divider
  - Expand
  - Label
  - LoadingOverlay
- Provide Storybook documentation for:
  - AppIcon
  - ActionButton
  - AggregateCard
  - AggregatePromiseCard
  - QuickActionsCard
  - Alert
  - Divider
  - EmptyState
  - Expand
  - Label
  - LoadingOverlay
  - ApplicationEmptyState
- Fix a bug in LoadingOverlay; naming error in nested slots caused any message attached to the
  overlay to not sow up correctly.
- Revise AppIcon to be independent of authentik; it just cares if the data has a name or an icon
  reference, it does not need to know about `Application` objects. As such, it's an *element*, not a
  *component*, and I've moved it into the right location, and updated the few places it is used to
  match.

* Prettier has opinions with which I sometimes diverge.

* Found a bug! Although pf-m-xl was defined as a legal size, there was no code to handle drawing something XL!

* Found a few typos and incorrect API descriptions.
This commit is contained in:
Ken Sternberg
2024-10-14 10:30:09 -07:00
committed by GitHub
parent 6b79190f6c
commit 0a1d283ac8
29 changed files with 1060 additions and 75 deletions

View File

@ -1,91 +0,0 @@
import { PFSize } from "@goauthentik/common/enums.js";
import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import { Application } from "@goauthentik/api";
@customElement("ak-app-icon")
export class AppIcon extends AKElement {
@property({ type: Object, attribute: false })
app?: Application;
@property()
size?: PFSize;
static get styles(): CSSResult[] {
return [
PFFAIcons,
PFAvatar,
css`
:host {
max-height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
}
:host([size="pf-m-lg"]) {
--icon-height: 4rem;
--icon-border: 0.25rem;
}
:host([size="pf-m-md"]) {
--icon-height: 2rem;
--icon-border: 0.125rem;
}
:host([size="pf-m-sm"]) {
--icon-height: 1rem;
--icon-border: 0.125rem;
}
.pf-c-avatar {
--pf-c-avatar--BorderRadius: 0;
--pf-c-avatar--Height: calc(
var(--icon-height) + var(--icon-border) + var(--icon-border)
);
--pf-c-avatar--Width: calc(
var(--icon-height) + var(--icon-border) + var(--icon-border)
);
}
.icon {
font-size: var(--icon-height);
color: var(--ak-global--Color--100);
padding: var(--icon-border);
max-height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
line-height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
filter: drop-shadow(5px 5px 5px rgba(128, 128, 128, 0.25));
}
div {
height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
}
`,
];
}
render(): TemplateResult {
if (!this.app) {
return html`<div><i class="icon fas fa-question-circle"></i></div>`;
}
if (this.app?.metaIcon) {
if (this.app.metaIcon.startsWith("fa://")) {
const icon = this.app.metaIcon.replaceAll("fa://", "");
return html`<div><i class="icon fas ${icon}"></i></div>`;
}
return html`<img
class="icon pf-c-avatar"
src="${ifDefined(this.app.metaIcon)}"
alt="${msg("Application Icon")}"
/>`;
}
return html`<span class="icon">${this.app?.name.charAt(0).toUpperCase()}</span>`;
}
}
export default AppIcon;
declare global {
interface HTMLElementTagNameMap {
"ak-app-icon": AppIcon;
}
}

View File

@ -1,38 +0,0 @@
import "@goauthentik/elements/messages/MessageContainer";
import { Meta } from "@storybook/web-components";
import { TemplateResult, html } from "lit";
import "../ak-app-icon";
import AkAppIcon from "../ak-app-icon";
const metadata: Meta<AkAppIcon> = {
title: "Components / App Icon",
component: "ak-app-icon",
parameters: {
docs: {
description: {
component: "A small card displaying an application icon",
},
},
},
};
export default metadata;
const container = (testItem: TemplateResult) =>
html` <div style="background: #000; padding: 2em">
<style>
li {
display: block;
}
p {
margin-top: 1em;
}
</style>
${testItem}
</div>`;
export const AppIcon = () => {
return container(html`<ak-app-icon .app=${{ name: "Demo app" }} size="pf-m-md"></ak-app-icon>`);
};