diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index ad18f185ffe7..996308f8f745 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -4,6 +4,8 @@ import MDApplication from "@goauthentik/docs/add-secure-apps/applications/index. import "@goauthentik/elements/AppIcon.js"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/Markdown"; +import "@goauthentik/elements/SidebarHelp/SidebarHelp.js"; +import { SidebarHelpController } from "@goauthentik/elements/SidebarHelp/SidebarHelpController.js"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; @@ -18,6 +20,7 @@ import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import SidebarHelp from "@goauthentik/elements/SidebarHelp/SidebarHelp.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import { Application, CoreApi } from "@goauthentik/api"; @@ -63,6 +66,8 @@ export class ApplicationListPage extends WithBrandConfig(TablePage) @property() order = "name"; + sidebarHelpController = new SidebarHelpController(this); + async apiEndpoint(): Promise> { return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({ ...(await this.defaultEndpointConfig()), @@ -71,7 +76,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage) } static get styles(): CSSResult[] { - return super.styles.concat(PFCard, applicationListStyle); + return super.styles.concat(PFCard, SidebarHelp, applicationListStyle); } columns(): TableColumn[] { @@ -90,13 +95,12 @@ export class ApplicationListPage extends WithBrandConfig(TablePage) } renderSidebarAfter(): TemplateResult { - return html`
-
-
- -
-
-
`; + return html``; } renderToolbarSelected(): TemplateResult { diff --git a/web/src/elements/SidebarHelp/README.md b/web/src/elements/SidebarHelp/README.md new file mode 100644 index 000000000000..e67fac95b29c --- /dev/null +++ b/web/src/elements/SidebarHelp/README.md @@ -0,0 +1,5 @@ +The SidebarHelp feature uses some fairly fiddly CSS to rotate the "Documentation" button in a way +that doesn't take up too much screen real-estate, because the rotation is purely visual; the layout +flow is still driven by the size of the button as if it were horizontal. Using the SidebarHelp means +enabling a special controller to adjust the width of the container to the _height_ of the button +when the button is rotated into place. diff --git a/web/src/elements/SidebarHelp/SidebarHelp.css b/web/src/elements/SidebarHelp/SidebarHelp.css new file mode 100644 index 000000000000..1d7bde39800c --- /dev/null +++ b/web/src/elements/SidebarHelp/SidebarHelp.css @@ -0,0 +1,14 @@ +ak-sidebar-help-toggle { + display: none; +} + +@media screen and (min-width: 768px) { + ak-sidebar-help-toggle { + display: block; + } +} + +ak-sidebar-help-toggle.pf-m-width-default { + background-color: inherit; + max-width: 3rem; +} diff --git a/web/src/elements/SidebarHelp/SidebarHelp.ts b/web/src/elements/SidebarHelp/SidebarHelp.ts new file mode 100644 index 000000000000..72f5a79be36e --- /dev/null +++ b/web/src/elements/SidebarHelp/SidebarHelp.ts @@ -0,0 +1,119 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Markdown"; +import { bound } from "@goauthentik/elements/decorators/bound"; + +import { msg } from "@lit/localize"; +import { css, html } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; +import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; +import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css"; + +import { SidebarHelpToggleEvent } from "./events.js"; + +/** + * A "Display documentation for this page" element. + * + * Based on the Patternfly "sidebar" pattern, this shows a vertically rotated button with a label to + * indicate that it leads to documentation; when pressed, the button is replaced with the + * documentation, rendered as a Markdown document. + * + * The SidebarHelp feature uses some fairly fiddly CSS to rotate the "Documentation" button in a way + * that doesn't take up too much screen real-estate, because the rotation is purely visual; the + * layout flow is still driven by the size of the button as if it were horizontal. Using the + * SidebarHelp means enabling a special SidebarHelpController on the container to adjust the width + * of the container to the *height* of the button when the button is rotated into place. + * + * @element ak-sidebar-help + * + * The events fired by this component are not for general use. + */ + +@customElement("ak-sidebar-help") +export class SidebarHelp extends AKElement { + static get styles() { + return [ + PFCard, + PFButton, + PFDisplay, + PFFlex, + PFSpacing, + css` + .vert { + transform-origin: bottom left; + translate: 0 -100%; + rotate: 90deg; + } + .ak-fit-text { + width: fit-content; + } + `, + ]; + } + + /* + * @attr The content of the documentation to be shown + */ + @property({ attribute: false }) + content: string = ""; + + /* + * @attr The style to use when the content is visible + */ + @property({ attribute: "active-style" }) + activeStyle = "pf-m-width-25"; + + /* + * @attr The label on the button when the content is not visible. + */ + @property() + label: string = msg("Documentation"); + + @state() + showing = false; + + @query("#toggle") + button!: HTMLButtonElement; + + @bound + toggle() { + this.showing = !this.showing; + } + + render() { + if (!this.showing) { + return html``; + } + + return html` +
+
+ +
+
+ +
+
+ `; + } + + updated() { + this.dispatchEvent(new SidebarHelpToggleEvent(this)); + } +} diff --git a/web/src/elements/SidebarHelp/SidebarHelpController.ts b/web/src/elements/SidebarHelp/SidebarHelpController.ts new file mode 100644 index 000000000000..398bf6343ce2 --- /dev/null +++ b/web/src/elements/SidebarHelp/SidebarHelpController.ts @@ -0,0 +1,51 @@ +import { bound } from "@goauthentik/elements/decorators/bound"; + +import { LitElement, ReactiveController, ReactiveControllerHost } from "lit"; + +import { SidebarHelpToggleEvent } from "./events"; + +type ReactiveLitElement = LitElement & ReactiveControllerHost; + +const DEFAULT_STYLE = "pf-m-width-default"; + +/** + * A "Display documentation for this page" helper. Attach this controller to any element that + * contains one or more SidebarHelp entries. It adjusts the width of the sidebar controller when + * hidden to that of the button's *height*, since the button has been rotated 90° around a + * corner-oriented axis. + * + * The events consumed by this component are not for general use. + */ + +export class SidebarHelpController implements ReactiveController { + host: ReactiveLitElement; + + constructor(host: ReactiveLitElement) { + (this.host = host).addController(this); + } + + @bound + toggleHelpToggle(ev: SidebarHelpToggleEvent) { + const { source } = ev; + if (!source.showing) { + source.classList.remove(source.activeStyle); + source.classList.add(DEFAULT_STYLE); + const { width } = source.button.getBoundingClientRect(); + source.style.setProperty("width", `${width}px`); + return; + } + requestAnimationFrame(() => { + source.style.removeProperty("width"); + source.classList.remove(DEFAULT_STYLE); + source.classList.add(source.activeStyle); + }); + } + + hostConnected() { + this.host.addEventListener(SidebarHelpToggleEvent.eventName, this.toggleHelpToggle); + } + + hostDisconnected() { + this.host.removeEventListener(SidebarHelpToggleEvent.eventName, this.toggleHelpToggle); + } +} diff --git a/web/src/elements/SidebarHelp/events.ts b/web/src/elements/SidebarHelp/events.ts new file mode 100644 index 000000000000..23022b3ab44f --- /dev/null +++ b/web/src/elements/SidebarHelp/events.ts @@ -0,0 +1,16 @@ +import type { SidebarHelp } from "./SidebarHelp.js"; + +export class SidebarHelpToggleEvent extends Event { + static readonly eventName = "ak-sidebar-help-toggle-request"; + source: SidebarHelp; + constructor(source: SidebarHelp) { + super(SidebarHelpToggleEvent.eventName, { bubbles: true, composed: true }); + this.source = source; + } +} + +declare global { + interface GlobalEventHandlersEventMap { + [SidebarHelpToggleEvent.eventName]: SidebarHelpToggleEvent; + } +}