diff --git a/Makefile b/Makefile index 78eb5ec34..3343c9968 100644 --- a/Makefile +++ b/Makefile @@ -140,6 +140,9 @@ web-watch: touch web/dist/.gitkeep cd web && npm run watch +web-storybook-watch: + cd web && npm run storybook + web-lint-fix: cd web && npm run prettier diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts index 63dcb3cce..e6583e476 100644 --- a/web/.storybook/main.ts +++ b/web/.storybook/main.ts @@ -1,6 +1,5 @@ import replace from "@rollup/plugin-replace"; import type { StorybookConfig } from "@storybook/web-components-vite"; -import path from "path"; import { cwd } from "process"; import postcssLit from "rollup-plugin-postcss-lit"; import tsconfigPaths from "vite-tsconfig-paths"; diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index 3d38fc3ad..243901e01 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -4,7 +4,7 @@ import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { adaptCSS } from "@goauthentik/common/utils"; import { localized } from "@lit/localize"; -import { LitElement } from "lit"; +import { CSSResult, LitElement } from "lit"; import { state } from "lit/decorators.js"; import AKGlobal from "@goauthentik/common/styles/authentik.css"; @@ -20,6 +20,13 @@ export function rootInterface(): T | undefined { return el[0] as T; } +export function ensureCSSStyleSheet(css: CSSStyleSheet | CSSResult): CSSStyleSheet { + if (css instanceof CSSResult) { + return css.styleSheet!; + } + return css; +} + let css: Promise | undefined; function fetchCustomCSS(): Promise { if (!css) { @@ -66,7 +73,10 @@ export class AKElement extends LitElement { if ("ShadyDOM" in window) { styleRoot = document; } - styleRoot.adoptedStyleSheets = adaptCSS([...styleRoot.adoptedStyleSheets, AKGlobal]); + styleRoot.adoptedStyleSheets = adaptCSS([ + ...styleRoot.adoptedStyleSheets, + ensureCSSStyleSheet(AKGlobal), + ]); this._initTheme(styleRoot); this._initCustomCSS(styleRoot); return root; @@ -151,7 +161,7 @@ export class AKElement extends LitElement { const stylesheet = AKElement.themeToStylesheet(theme); const oldStylesheet = AKElement.themeToStylesheet(this._activeTheme); if (stylesheet) { - root.adoptedStyleSheets = [...root.adoptedStyleSheets, stylesheet]; + root.adoptedStyleSheets = [...root.adoptedStyleSheets, ensureCSSStyleSheet(stylesheet)]; } if (oldStylesheet) { root.adoptedStyleSheets = root.adoptedStyleSheets.filter((v) => v !== oldStylesheet); @@ -173,7 +183,7 @@ export class Interface extends AKElement { constructor() { super(); - document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFBase]; + document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; tenant().then((tenant) => (this.tenant = tenant)); config().then((config) => (this.config = config)); } diff --git a/web/src/flow/stages/access_denied/AccessDeniedStage.stories.ts b/web/src/flow/stages/access_denied/AccessDeniedStage.stories.ts new file mode 100644 index 000000000..f6711f274 --- /dev/null +++ b/web/src/flow/stages/access_denied/AccessDeniedStage.stories.ts @@ -0,0 +1,56 @@ +import type { StoryObj } from "@storybook/web-components"; + +import { html } from "lit"; + +import "@patternfly/patternfly/components/Login/login.css"; + +import { AccessDeniedChallenge, ChallengeChoices, UiThemeEnum } from "@goauthentik/api"; + +import "../../../stories/flow-interface"; +import "./AccessDeniedStage"; + +export default { + title: "Flow / Stages / AccessDeniedStage", +}; + +export const LoadingNoChallenge = () => { + return html` + + `; +}; + +export const Challenge: StoryObj = { + render: ({ theme, challenge }) => { + return html` + `; + }, + args: { + theme: "automatic", + challenge: { + type: ChallengeChoices.Native, + pendingUser: "foo", + pendingUserAvatar: "https://picsum.photos/64", + errorMessage: "This is an error message", + } as AccessDeniedChallenge, + }, + argTypes: { + theme: { + options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark], + control: { + type: "select", + }, + }, + }, +}; diff --git a/web/src/stories/Button.stories.ts b/web/src/stories/Button.stories.ts deleted file mode 100644 index 68a23133c..000000000 --- a/web/src/stories/Button.stories.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/web-components"; - -import type { ButtonProps } from "./Button"; -import { Button } from "./Button"; - -// More on how to set up stories at: https://storybook.js.org/docs/web-components/writing-stories/introduction -const meta = { - title: "Example/Button", - tags: ["autodocs"], - render: (args) => Button(args), - argTypes: { - backgroundColor: { control: "color" }, - onClick: { action: "onClick" }, - size: { - control: { type: "select" }, - options: ["small", "medium", "large"], - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// More on writing stories with args: https://storybook.js.org/docs/web-components/writing-stories/args -export const Primary: Story = { - args: { - primary: true, - label: "Button", - }, -}; - -export const Secondary: Story = { - args: { - label: "Button", - }, -}; - -export const Large: Story = { - args: { - size: "large", - label: "Button", - }, -}; - -export const Small: Story = { - args: { - size: "small", - label: "Button", - }, -}; diff --git a/web/src/stories/Button.ts b/web/src/stories/Button.ts deleted file mode 100644 index 9216d79f4..000000000 --- a/web/src/stories/Button.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { html } from "lit"; -import { styleMap } from "lit/directives/style-map.js"; - -import "./button.css"; - -export interface ButtonProps { - /** - * Is this the principal call to action on the page? - */ - primary?: boolean; - /** - * What background color to use - */ - backgroundColor?: string; - /** - * How large should the button be? - */ - size?: "small" | "medium" | "large"; - /** - * Button contents - */ - label: string; - /** - * Optional click handler - */ - onClick?: () => void; -} -/** - * Primary UI component for user interaction - */ -export const Button = ({ primary, backgroundColor, size, label, onClick }: ButtonProps) => { - const mode = primary ? "storybook-button--primary" : "storybook-button--secondary"; - - return html` - - `; -}; diff --git a/web/src/stories/Header.stories.ts b/web/src/stories/Header.stories.ts deleted file mode 100644 index 82a5679b1..000000000 --- a/web/src/stories/Header.stories.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/web-components"; - -import type { HeaderProps } from "./Header"; -import { Header } from "./Header"; - -const meta = { - title: "Example/Header", - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/web-components/writing-docs/autodocs - tags: ["autodocs"], - render: (args: HeaderProps) => Header(args), -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const LoggedIn: Story = { - args: { - user: { - name: "Jane Doe", - }, - }, -}; - -export const LoggedOut: Story = {}; diff --git a/web/src/stories/Header.ts b/web/src/stories/Header.ts deleted file mode 100644 index 58f7bdae5..000000000 --- a/web/src/stories/Header.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { html } from "lit"; - -import "./header.css"; - -import { Button } from "./Button"; - -type User = { - name: string; -}; - -export interface HeaderProps { - user?: User; - onLogin: () => void; - onLogout: () => void; - onCreateAccount: () => void; -} - -export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => html` -
-
-
- - - - - - - -

Acme

-
-
- ${user - ? Button({ size: "small", onClick: onLogout, label: "Log out" }) - : html`${Button({ - size: "small", - onClick: onLogin, - label: "Log in", - })} - ${Button({ - primary: true, - size: "small", - onClick: onCreateAccount, - label: "Sign up", - })}`} -
-
-
-`; diff --git a/web/src/stories/Introduction.mdx b/web/src/stories/Introduction.mdx deleted file mode 100644 index a9216640f..000000000 --- a/web/src/stories/Introduction.mdx +++ /dev/null @@ -1,214 +0,0 @@ -import { Meta } from "@storybook/blocks"; - -import Code from "./assets/code-brackets.svg"; -import Colors from "./assets/colors.svg"; -import Comments from "./assets/comments.svg"; -import Direction from "./assets/direction.svg"; -import Flow from "./assets/flow.svg"; -import Plugin from "./assets/plugin.svg"; -import Repo from "./assets/repo.svg"; -import StackAlt from "./assets/stackalt.svg"; - - - - - -# Welcome to Storybook - -Storybook helps you build UI components in isolation from your app's business logic, data, and context. -That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA. - -Browse example stories now by navigating to them in the sidebar. -View their code in the `stories` directory to learn how they work. -We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages. - -
Configure
- - - -
Learn
- - - -
- TipEdit the Markdown in{" "} - stories/Introduction.stories.mdx -
diff --git a/web/src/stories/Page.stories.ts b/web/src/stories/Page.stories.ts deleted file mode 100644 index ccbf797fb..000000000 --- a/web/src/stories/Page.stories.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/web-components"; - -import * as HeaderStories from "./Header.stories"; -import type { PageProps } from "./Page"; -import { Page } from "./Page"; - -const meta = { - title: "Example/Page", - render: (args: PageProps) => Page(args), -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const LoggedIn: Story = { - args: { - // More on composing args: https://storybook.js.org/docs/web-components/writing-stories/args#args-composition - ...HeaderStories.LoggedIn.args, - }, -}; - -export const LoggedOut: Story = { - args: { - ...HeaderStories.LoggedOut.args, - }, -}; diff --git a/web/src/stories/Page.ts b/web/src/stories/Page.ts deleted file mode 100644 index 2827f7e95..000000000 --- a/web/src/stories/Page.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { html } from "lit"; - -import "./page.css"; - -import { Header } from "./Header"; - -type User = { - name: string; -}; - -export interface PageProps { - user?: User; - onLogin: () => void; - onLogout: () => void; - onCreateAccount: () => void; -} - -export const Page = ({ user, onLogin, onLogout, onCreateAccount }: PageProps) => html` -
- ${Header({ - user, - onLogin, - onLogout, - onCreateAccount, - })} - -
-

Pages in Storybook

-

- We recommend building UIs with a - - component-driven process starting with atomic components and ending with pages. -

-

- Render pages with mock data. This makes it easy to build and review page states - without needing to navigate to them in your app. Here are some handy patterns for - managing page data in Storybook: -

-
    -
  • - Use a higher-level connected component. Storybook helps you compose such data - from the "args" of child component stories -
  • -
  • - Assemble data in the page component from your services. You can mock these - services out using Storybook. -
  • -
-

- Get a guided tutorial on component-driven development at - - Storybook tutorials - - . Read more in the - - docs - - . -

-
- Tip Adjust the width of the canvas with the - - - - - - Viewports addon in the toolbar -
-
-
-`; diff --git a/web/src/stories/assets/code-brackets.svg b/web/src/stories/assets/code-brackets.svg deleted file mode 100644 index 73de94776..000000000 --- a/web/src/stories/assets/code-brackets.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/code-brackets \ No newline at end of file diff --git a/web/src/stories/assets/colors.svg b/web/src/stories/assets/colors.svg deleted file mode 100644 index 17d58d516..000000000 --- a/web/src/stories/assets/colors.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/colors \ No newline at end of file diff --git a/web/src/stories/assets/comments.svg b/web/src/stories/assets/comments.svg deleted file mode 100644 index 6493a139f..000000000 --- a/web/src/stories/assets/comments.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/comments \ No newline at end of file diff --git a/web/src/stories/assets/direction.svg b/web/src/stories/assets/direction.svg deleted file mode 100644 index 65676ac27..000000000 --- a/web/src/stories/assets/direction.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/direction \ No newline at end of file diff --git a/web/src/stories/assets/flow.svg b/web/src/stories/assets/flow.svg deleted file mode 100644 index 8ac27db40..000000000 --- a/web/src/stories/assets/flow.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/flow \ No newline at end of file diff --git a/web/src/stories/assets/plugin.svg b/web/src/stories/assets/plugin.svg deleted file mode 100644 index 29e5c690c..000000000 --- a/web/src/stories/assets/plugin.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/plugin \ No newline at end of file diff --git a/web/src/stories/assets/repo.svg b/web/src/stories/assets/repo.svg deleted file mode 100644 index f386ee902..000000000 --- a/web/src/stories/assets/repo.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/repo \ No newline at end of file diff --git a/web/src/stories/assets/stackalt.svg b/web/src/stories/assets/stackalt.svg deleted file mode 100644 index 9b7ad2743..000000000 --- a/web/src/stories/assets/stackalt.svg +++ /dev/null @@ -1 +0,0 @@ -illustration/stackalt \ No newline at end of file diff --git a/web/src/stories/button.css b/web/src/stories/button.css deleted file mode 100644 index b42bca3c5..000000000 --- a/web/src/stories/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; -} -.storybook-button--primary { - color: white; - background-color: #1ea7fd; -} -.storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; -} -.storybook-button--small { - font-size: 12px; - padding: 10px 16px; -} -.storybook-button--medium { - font-size: 14px; - padding: 11px 20px; -} -.storybook-button--large { - font-size: 16px; - padding: 12px 24px; -} diff --git a/web/src/stories/flow-interface.ts b/web/src/stories/flow-interface.ts new file mode 100644 index 000000000..3975e29f7 --- /dev/null +++ b/web/src/stories/flow-interface.ts @@ -0,0 +1,15 @@ +import { FlowExecutor } from "@goauthentik/app/flow/FlowExecutor"; + +import { customElement, property } from "lit/decorators.js"; + +import { UiThemeEnum } from "@goauthentik/api"; + +@customElement("ak-storybook-interface-flow") +export class StoryFlowInterface extends FlowExecutor { + @property() + storyTheme: UiThemeEnum = UiThemeEnum.Dark; + + async getTheme(): Promise { + return this.storyTheme; + } +} diff --git a/web/src/stories/header.css b/web/src/stories/header.css deleted file mode 100644 index f71e7915e..000000000 --- a/web/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.storybook-header { - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -.storybook-header svg { - display: inline-block; - vertical-align: top; -} - -.storybook-header h1 { - font-weight: 700; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - color: #333; - font-size: 14px; - margin-right: 10px; -} diff --git a/web/src/stories/interface.ts b/web/src/stories/interface.ts new file mode 100644 index 000000000..c4e2dc03d --- /dev/null +++ b/web/src/stories/interface.ts @@ -0,0 +1,15 @@ +import { Interface } from "@goauthentik/app/elements/Base"; + +import { customElement, property } from "lit/decorators.js"; + +import { UiThemeEnum } from "@goauthentik/api"; + +@customElement("ak-storybook-interface") +export class StoryInterface extends Interface { + @property() + storyTheme: UiThemeEnum = UiThemeEnum.Dark; + + async getTheme(): Promise { + return this.storyTheme; + } +} diff --git a/web/src/stories/page.css b/web/src/stories/page.css deleted file mode 100644 index 4aa2c1c57..000000000 --- a/web/src/stories/page.css +++ /dev/null @@ -1,69 +0,0 @@ -.storybook-page { - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 24px; - padding: 48px 20px; - margin: 0 auto; - max-width: 600px; - color: #333; -} - -.storybook-page h2 { - font-weight: 700; - font-size: 32px; - line-height: 1; - margin: 0 0 4px; - display: inline-block; - vertical-align: top; -} - -.storybook-page p { - margin: 1em 0; -} - -.storybook-page a { - text-decoration: none; - color: #1ea7fd; -} - -.storybook-page ul { - padding-left: 30px; - margin: 1em 0; -} - -.storybook-page li { - margin-bottom: 8px; -} - -.storybook-page .tip { - display: inline-block; - border-radius: 1em; - font-size: 11px; - line-height: 12px; - font-weight: 700; - background: #e7fdd8; - color: #66bf3c; - padding: 4px 12px; - margin-right: 10px; - vertical-align: top; -} - -.storybook-page .tip-wrapper { - font-size: 13px; - line-height: 20px; - margin-top: 40px; - margin-bottom: 40px; -} - -.storybook-page .tip-wrapper svg { - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - vertical-align: top; - margin-top: 3px; -} - -.storybook-page .tip-wrapper svg path { - fill: #1ea7fd; -}