web: update storybook, storybook a few things, fix a few things
After examining how people like Adobe and Salesforce do things, I have updated the storybook configuration to provide run-time configuration of light/dark mode (although right now nothing happens), inject the correct styling into the page, and update the preview handling so that we can see the components better. We'll see how this pans out. I have provided stories for the AggregateCard, AggregatePromiseCard, and a new QuickActionsCard. I also fixed a bug in AggregatePromiseCard where it would fail to report a fetch error. It will only report that "the operation falied," but it will give the full error into the console. **As an experiment**, I have changed the interpreter for `lint:precommit` and `build:watch` to use [Bun](https://bun.sh/) instead of NodeJS. We have observed significant speed-ups and much better memory management with Bun for these two operations. Those are both developer-facing operations, the behavior of the system undur current CI/CD should not change. And finally, I've switched the QuickActionsCard view in Admin-Overview to use the new component. Looks the same. Reads *way* easier. :-)
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import { create } from "@storybook/theming/create";
|
||||
|
||||
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
export default create({
|
||||
base: "light",
|
||||
base: isDarkMode ? "dark" : "light",
|
||||
brandTitle: "authentik Storybook",
|
||||
brandUrl: "https://goauthentik.io",
|
||||
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
|
||||
|
@ -19,6 +19,20 @@ const config: StorybookConfig = {
|
||||
"@jeysal/storybook-addon-css-user-preferences",
|
||||
"storybook-addon-mock",
|
||||
],
|
||||
staticDirs: [
|
||||
{
|
||||
from: "../node_modules/@patternfly/patternfly/patternfly-base.css",
|
||||
to: "@patternfly/patternfly/patternfly-base.css",
|
||||
},
|
||||
{
|
||||
from: "../src/common/styles/authentik.css",
|
||||
to: "@goauthentik/common/styles/authentik.css",
|
||||
},
|
||||
{
|
||||
from: "../src/common/styles/theme-dark.css",
|
||||
to: "@goauthentik/common/styles/theme-dark.css",
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/web-components-vite",
|
||||
options: {},
|
||||
|
60
web/.storybook/preview-head.html
Normal file
60
web/.storybook/preview-head.html
Normal file
@ -0,0 +1,60 @@
|
||||
<link rel="stylesheet" href="@patternfly/patternfly/patternfly-base.css" />
|
||||
<link rel="stylesheet" href="@goauthentik/common/styles/authentik.css" />
|
||||
<style>
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sb-main-padded {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.story-shadow-container {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0.25em #ddd;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
margin: 0 auto;
|
||||
max-width: 72em;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.docs-story .story-shadow-container {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.story-shadow-container[display-mode="flex-wrap"] {
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 2rem -1rem 1rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.title code {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: bold;
|
||||
padding: 0.1rem 0.25rem;
|
||||
}
|
||||
|
||||
.sbdocs-preview .hljs {
|
||||
color: #fff !important;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.sbdocs-pre > div {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
@ -9,6 +9,11 @@ import "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
method: "alphabetical",
|
||||
},
|
||||
},
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
cssUserPrefs: {
|
||||
"prefers-color-scheme": "light",
|
||||
|
@ -10,12 +10,12 @@
|
||||
"build-locales:repair": "prettier --write ./src/locale-codes.ts",
|
||||
"esbuild:build": "node build.mjs",
|
||||
"esbuild:build-proxy": "node build.mjs --proxy",
|
||||
"esbuild:watch": "node build.mjs --watch",
|
||||
"esbuild:watch": "bun build.mjs --watch",
|
||||
"build": "run-s build-locales esbuild:build",
|
||||
"build-proxy": "run-s build-locales esbuild:build-proxy",
|
||||
"watch": "run-s build-locales esbuild:watch",
|
||||
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix",
|
||||
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=65536' node scripts/eslint-precommit.mjs",
|
||||
"lint:precommit": "bun scripts/eslint-precommit.mjs",
|
||||
"lint:spelling": "node scripts/check-spelling.mjs",
|
||||
"lit-analyse": "lit-analyzer src",
|
||||
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
|
||||
|
@ -29,6 +29,7 @@ const eslintConfig = {
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
|
||||
ignorePatterns: ["!./.storybook/**/*.ts"],
|
||||
rules: {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
@ -60,9 +61,14 @@ const modified = (s) => isModified.test(s);
|
||||
const isCheckable = /\.(ts|js|mjs)$/;
|
||||
const checkable = (s) => isCheckable.test(s);
|
||||
|
||||
const ignored = /\/\.storybook\//;
|
||||
const notIgnored = (s) => !ignored.test(s);
|
||||
|
||||
const updated = statuses.reduce(
|
||||
(acc, [status, filename]) =>
|
||||
modified(status) && checkable(filename) ? [...acc, path.join(projectRoot, filename)] : acc,
|
||||
modified(status) && checkable(filename) && notIgnored(filename)
|
||||
? [...acc, path.join(projectRoot, filename)]
|
||||
: acc,
|
||||
[],
|
||||
);
|
||||
|
||||
@ -72,5 +78,6 @@ const formatter = await eslint.loadFormatter("stylish");
|
||||
const resultText = formatter.format(results);
|
||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultText);
|
||||
process.exit(errors > 1 ? 1 : 0);
|
||||
|
63
web/scripts/eslint.mjs
Normal file
63
web/scripts/eslint.mjs
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node --max_old_space_size=65536
|
||||
import { execFileSync } from "child_process";
|
||||
import { ESLint } from "eslint";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
// Code assumes this script is in the './web/scripts' folder.
|
||||
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
process.chdir(path.join(projectRoot, "./web"));
|
||||
|
||||
const eslintConfig = {
|
||||
fix: true,
|
||||
overrideConfig: {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:custom-elements/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
project: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "lit", "custom-elements"],
|
||||
ignorePatterns: ["authentik-live-tests/**"],
|
||||
rules: {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { avoidEscape: true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const eslint = new ESLint(eslintConfig);
|
||||
const results = await eslint.lintFiles(".");
|
||||
const formatter = await eslint.loadFormatter("stylish");
|
||||
const resultText = formatter.format(results);
|
||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultText);
|
||||
process.exit(errors > 1 ? 1 : 0);
|
@ -12,6 +12,8 @@ import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
import "@goauthentik/elements/cards/AggregatePromiseCard";
|
||||
import "@goauthentik/elements/cards/QuickActionsCard.js";
|
||||
import type { QuickAction } from "@goauthentik/elements/cards/QuickActionsCard.js";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
@ -20,7 +22,6 @@ import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
@ -33,6 +34,11 @@ export function versionFamily(): string {
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
const RELEASE = `${VERSION.split(".").slice(0, -1).join(".")}#fixed-in-${VERSION.replaceAll(
|
||||
".",
|
||||
"",
|
||||
)}`;
|
||||
|
||||
@customElement("ak-admin-overview")
|
||||
export class AdminOverviewPage extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
@ -41,7 +47,6 @@ export class AdminOverviewPage extends AKElement {
|
||||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFList,
|
||||
PFDivider,
|
||||
css`
|
||||
.pf-l-grid__item {
|
||||
@ -64,6 +69,14 @@ export class AdminOverviewPage extends AKElement {
|
||||
];
|
||||
}
|
||||
|
||||
quickActions: QuickAction[] = [
|
||||
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
|
||||
[msg("Check the logs"), paramURL("/events/log")],
|
||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
||||
[msg("Manage users"), paramURL("/identity/users")],
|
||||
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${RELEASE}`, true],
|
||||
];
|
||||
|
||||
@state()
|
||||
user?: SessionUser;
|
||||
|
||||
@ -83,56 +96,8 @@ export class AdminOverviewPage extends AKElement {
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl pf-l-grid pf-m-gutter"
|
||||
>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-share"
|
||||
header=${msg("Quick actions")}
|
||||
.isCenter=${false}
|
||||
>
|
||||
<ul class="pf-c-list">
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
href=${paramURL("/core/applications", {
|
||||
createForm: true,
|
||||
})}
|
||||
>${msg("Create a new application")}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pf-u-mb-xl" href=${paramURL("/events/log")}
|
||||
>${msg("Check the logs")}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
target="_blank"
|
||||
href="https://goauthentik.io/integrations/"
|
||||
>${msg("Explore integrations")}<i
|
||||
class="fas fa-external-link-alt ak-external-link"
|
||||
></i
|
||||
></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pf-u-mb-xl" href=${paramURL("/identity/users")}
|
||||
>${msg("Manage users")}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
target="_blank"
|
||||
href="https://goauthentik.io/docs/releases/${versionFamily()}#fixed-in-${VERSION.replaceAll(
|
||||
".",
|
||||
"",
|
||||
)}"
|
||||
>${msg("Check the release notes")}<i
|
||||
class="fas fa-external-link-alt ak-external-link"
|
||||
></i
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</ak-aggregate-card>
|
||||
<ak-quick-actions-card .actions=${this.quickActions}>
|
||||
</ak-quick-actions-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
|
||||
<ak-aggregate-card
|
||||
|
@ -8,8 +8,16 @@ import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export interface IAggregateCard {
|
||||
icon?: string;
|
||||
header?: string;
|
||||
headerLink?: string;
|
||||
subtext?: string;
|
||||
leftJustified?: boolean;
|
||||
}
|
||||
|
||||
@customElement("ak-aggregate-card")
|
||||
export class AggregateCard extends AKElement {
|
||||
export class AggregateCard extends AKElement implements IAggregateCard {
|
||||
@property()
|
||||
icon?: string;
|
||||
|
||||
@ -22,8 +30,8 @@ export class AggregateCard extends AKElement {
|
||||
@property()
|
||||
subtext?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
isCenter = true;
|
||||
@property({ type: Boolean, attribute: "left-justified" })
|
||||
leftJustified = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFCard, PFFlex].concat([
|
||||
@ -80,7 +88,7 @@ export class AggregateCard extends AKElement {
|
||||
</div>
|
||||
${this.renderHeaderLink()}
|
||||
</div>
|
||||
<div class="pf-c-card__body ${this.isCenter ? "center-value" : ""}">
|
||||
<div class="pf-c-card__body ${this.leftJustified ? "" : "center-value"}">
|
||||
${this.renderInner()}
|
||||
${this.subtext ? html`<p class="subtext">${this.subtext}</p>` : html``}
|
||||
</div>
|
||||
@ -88,3 +96,9 @@ export class AggregateCard extends AKElement {
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-aggregate-card": AggregateCard;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,34 @@
|
||||
import { PFSize } from "@goauthentik/common/enums.js";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";
|
||||
import { AggregateCard, type IAggregateCard } from "@goauthentik/elements/cards/AggregateCard";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
export interface IAggregatePromiseCard extends IAggregateCard {
|
||||
promise?: Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
@customElement("ak-aggregate-card-promise")
|
||||
export class AggregatePromiseCard extends AggregateCard {
|
||||
export class AggregatePromiseCard extends AggregateCard implements IAggregatePromiseCard {
|
||||
@property({ attribute: false })
|
||||
promise?: Promise<Record<string, unknown>>;
|
||||
|
||||
async promiseProxy(): Promise<TemplateResult> {
|
||||
if (!this.promise) {
|
||||
return html``;
|
||||
async promiseProxy(): Promise<TemplateResult | typeof nothing> {
|
||||
try {
|
||||
if (!this.promise) {
|
||||
return nothing;
|
||||
}
|
||||
const value = await this.promise;
|
||||
return html`<i class="fa fa-check-circle"></i> ${value.toString()}`;
|
||||
} catch (error: unknown) {
|
||||
console.warn(error);
|
||||
return html`<i class="fa fa-exclamation-circle"></i> ${msg(
|
||||
"Operation failed to complete",
|
||||
)}`;
|
||||
}
|
||||
const value = await this.promise;
|
||||
return html`<i class="fa fa-check-circle"></i> ${value.toString()}`;
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
@ -25,3 +37,9 @@ export class AggregatePromiseCard extends AggregateCard {
|
||||
</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-aggregate-card-promise": AggregatePromiseCard;
|
||||
}
|
||||
}
|
||||
|
73
web/src/elements/cards/QuickActionsCard.ts
Normal file
73
web/src/elements/cards/QuickActionsCard.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/cards/AggregateCard.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export type QuickAction = [label: string, url: string, isExternal?: boolean];
|
||||
|
||||
/**
|
||||
* class QuickActionsCard
|
||||
* element ak-quick-actions-card
|
||||
*
|
||||
* Specialized card for navigation.
|
||||
*/
|
||||
|
||||
export interface IQuickActionsCard {
|
||||
title: string;
|
||||
actions: QuickAction[];
|
||||
}
|
||||
|
||||
@customElement("ak-quick-actions-card")
|
||||
export class QuickActionsCard extends AKElement implements IQuickActionsCard {
|
||||
static get styles() {
|
||||
return [PFBase, PFList];
|
||||
}
|
||||
|
||||
/**
|
||||
* Card title
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
title = msg("Quick actions");
|
||||
|
||||
/**
|
||||
* Card contents. An array of [label, url, isExternal]. External links will
|
||||
* be rendered with an external link icon and will always open in a new tab.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Array })
|
||||
actions: QuickAction[] = [];
|
||||
|
||||
render() {
|
||||
const renderItem = ([label, url, external]: QuickAction) =>
|
||||
html` <li>
|
||||
<a class="pf-u-mb-xl" href=${url} ${external ? 'target="_blank"' : ""}>
|
||||
${external
|
||||
? html`${label} <i
|
||||
class="fas fa-external-link-alt ak-external-link"
|
||||
></i>`
|
||||
: label}
|
||||
</a>
|
||||
</li>`;
|
||||
|
||||
return html` <ak-aggregate-card icon="fa fa-share" header=${this.title} left-justified>
|
||||
<ul class="pf-c-list">
|
||||
${map(this.actions, renderItem)}
|
||||
</ul>
|
||||
</ak-aggregate-card>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-quick-actions-card": QuickActionsCard;
|
||||
}
|
||||
}
|
62
web/src/elements/cards/stories/AggregateCard.stories.ts
Normal file
62
web/src/elements/cards/stories/AggregateCard.stories.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { AggregateCard, type IAggregateCard } from "../AggregateCard.js";
|
||||
import "../AggregateCard.js";
|
||||
|
||||
const metadata: Meta<AggregateCard> = {
|
||||
title: "Elements/<ak-aggregate-card>",
|
||||
component: "ak-aggregate-card",
|
||||
parameters: {
|
||||
docs: {
|
||||
description: "A specialized card for displaying collections",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
icon: { control: "text" },
|
||||
header: { control: "text" },
|
||||
headerLink: { control: "text" },
|
||||
subtext: { control: "text" },
|
||||
leftJustified: { control: "boolean" },
|
||||
},
|
||||
};
|
||||
|
||||
export default metadata;
|
||||
|
||||
export const DefaultStory: StoryObj = {
|
||||
args: {
|
||||
icon: undefined,
|
||||
header: "Default",
|
||||
headerLink: undefined,
|
||||
subtext: undefined,
|
||||
isCenter: false,
|
||||
},
|
||||
render: ({ icon, header, headerLink, subtext, leftJustified }: IAggregateCard) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-aggregate-card {
|
||||
display: inline-block;
|
||||
width: 32rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
</style>
|
||||
<ak-aggregate-card
|
||||
header=${ifDefined(header)}
|
||||
headerLink=${ifDefined(headerLink)}
|
||||
subtext=${ifDefined(subtext)}
|
||||
icon=${ifDefined(icon)}
|
||||
?left-justified=${leftJustified}
|
||||
>
|
||||
<p>
|
||||
Form without content style without meaning quick-win, for that is a good problem
|
||||
to have, so this is our north star design. Can you champion this cross sabers
|
||||
run it up the flagpole, ping the boss and circle back race without a finish line
|
||||
in an ideal world. Price point innovation is hot right now, nor it's not hard
|
||||
guys, but race without a finish line, nor thought shower.
|
||||
</p>
|
||||
</ak-aggregate-card>
|
||||
</div>`;
|
||||
},
|
||||
};
|
@ -0,0 +1,95 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { AggregatePromiseCard, type IAggregatePromiseCard } from "../AggregatePromiseCard.js";
|
||||
import "../AggregatePromiseCard.js";
|
||||
|
||||
const metadata: Meta<AggregatePromiseCard> = {
|
||||
title: "Elements/<ak-aggregate-card-promise>",
|
||||
component: "ak-aggregate-card-promise",
|
||||
parameters: {
|
||||
docs: {
|
||||
description: "A specialized card for displaying information after a fetch",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
icon: { control: "text" },
|
||||
header: { control: "text" },
|
||||
headerLink: { control: "text" },
|
||||
subtext: { control: "text" },
|
||||
leftJustified: { control: "boolean" },
|
||||
},
|
||||
};
|
||||
|
||||
export default metadata;
|
||||
|
||||
const text =
|
||||
"Curl up and sleep on the freshly laundered towels mew, but make meme, make cute face growl at dogs in my sleep. Scratch me there, elevator butt humans,humans, humans oh how much they love us felines we are the center of attention they feed, they clean hopped up on catnip mice. Kitty time flop over, for see owner, run in terror";
|
||||
|
||||
export const DefaultStory: StoryObj = {
|
||||
args: {
|
||||
icon: undefined,
|
||||
header: "Default",
|
||||
headerLink: undefined,
|
||||
subtext: "Demo has an eight second delay until resolution",
|
||||
leftJustified: false,
|
||||
},
|
||||
render: ({ icon, header, headerLink, subtext, leftJustified }: IAggregatePromiseCard) => {
|
||||
const runThis = (timeout: number, value: string) =>
|
||||
new Promise((resolve) => setTimeout(resolve, timeout, value));
|
||||
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-aggregate-card-promise {
|
||||
display: inline-block;
|
||||
width: 32rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
</style>
|
||||
<ak-aggregate-card-promise
|
||||
header=${ifDefined(header)}
|
||||
headerLink=${ifDefined(headerLink)}
|
||||
subtext=${ifDefined(subtext)}
|
||||
icon=${ifDefined(icon)}
|
||||
?left-justified=${leftJustified}
|
||||
.promise=${runThis(8000, text)}
|
||||
>
|
||||
</ak-aggregate-card-promise>
|
||||
</div>`;
|
||||
},
|
||||
};
|
||||
|
||||
export const PromiseRejected: StoryObj = {
|
||||
args: {
|
||||
icon: undefined,
|
||||
header: "Default",
|
||||
headerLink: undefined,
|
||||
subtext: "Demo has an eight second delay until rejection",
|
||||
leftJustified: false,
|
||||
},
|
||||
render: ({ icon, header, headerLink, subtext, leftJustified }: IAggregatePromiseCard) => {
|
||||
const runThis = (timeout: number, value: string) =>
|
||||
new Promise((_resolve, reject) => setTimeout(reject, timeout, value));
|
||||
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-aggregate-card-promise {
|
||||
display: inline-block;
|
||||
width: 32rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
</style>
|
||||
<ak-aggregate-card-promise
|
||||
header=${ifDefined(header)}
|
||||
headerLink=${ifDefined(headerLink)}
|
||||
subtext=${ifDefined(subtext)}
|
||||
icon=${ifDefined(icon)}
|
||||
?left-justified=${leftJustified}
|
||||
.promise=${runThis(8000, text)}
|
||||
>
|
||||
</ak-aggregate-card-promise>
|
||||
</div>`;
|
||||
},
|
||||
};
|
47
web/src/elements/cards/stories/QuickActionCard.stories.ts
Normal file
47
web/src/elements/cards/stories/QuickActionCard.stories.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "../QuickActionsCard.js";
|
||||
import { QuickAction, QuickActionsCard } from "../QuickActionsCard.js";
|
||||
|
||||
const ACTIONS: QuickAction[] = [
|
||||
["Create a new application", "/core/applications"],
|
||||
["Check the logs", "/events/log"],
|
||||
["Explore integrations", "https://goauthentik.io/integrations/", true],
|
||||
["Manage users", "/identity/users"],
|
||||
["Check the release notes", "https://goauthentik.io/docs/releases/", true],
|
||||
];
|
||||
|
||||
const metadata: Meta<QuickActionsCard> = {
|
||||
title: "Elements/<ak-quick-action-card>",
|
||||
component: "ak-quick-action-card",
|
||||
parameters: {
|
||||
docs: {
|
||||
description: "A specialized card for a list of navigation links",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
title: { control: "text" },
|
||||
},
|
||||
};
|
||||
|
||||
export default metadata;
|
||||
|
||||
export const DefaultStory: StoryObj = {
|
||||
args: {
|
||||
title: "Quick actions",
|
||||
},
|
||||
render: ({ title }) => {
|
||||
return html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
||||
<style>
|
||||
ak-quick-actions-card {
|
||||
display: inline-block;
|
||||
width: 16rem;
|
||||
max-width: 16rem;
|
||||
}
|
||||
</style>
|
||||
<ak-quick-actions-card title=${title} .actions=${ACTIONS}></ak-quick-actions-card>
|
||||
</div>`;
|
||||
},
|
||||
};
|
@ -2,17 +2,17 @@
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@goauthentik/authentik/*": ["src/*"],
|
||||
"@goauthentik/admin/*": ["src/admin/*"],
|
||||
"@goauthentik/common/*": ["src/common/*"],
|
||||
"@goauthentik/components/*": ["src/components/*"],
|
||||
"@goauthentik/authentik/*": ["./src/*"],
|
||||
"@goauthentik/admin/*": ["./src/admin/*"],
|
||||
"@goauthentik/common/*": ["./src/common/*"],
|
||||
"@goauthentik/components/*": ["./src/components/*"],
|
||||
"@goauthentik/docs/*": ["../website/docs/*"],
|
||||
"@goauthentik/elements/*": ["src/elements/*"],
|
||||
"@goauthentik/flow/*": ["src/flow/*"],
|
||||
"@goauthentik/locales/*": ["src/locales/*"],
|
||||
"@goauthentik/polyfill/*": ["src/polyfill/*"],
|
||||
"@goauthentik/standalone/*": ["src/standalone/*"],
|
||||
"@goauthentik/user/*": ["src/user/*"]
|
||||
},
|
||||
"@goauthentik/elements/*": ["./src/elements/*"],
|
||||
"@goauthentik/flow/*": ["./src/flow/*"],
|
||||
"@goauthentik/locales/*": ["./src/locales/*"],
|
||||
"@goauthentik/polyfill/*": ["./src/polyfill/*"],
|
||||
"@goauthentik/standalone/*": ["./src/standalone/*"],
|
||||
"@goauthentik/user/*": ["./src/user/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user