web: Replace ad-hoc toggle control with ak-toggle-group
This commit replaces various ad-hoc implementations of the Patternfly Toggle Group HTML with a web component that encapsulates all of the needed behavior and exposes a single API with a single event handler, return the value of the option clicked. The results are: Lots of visual clutter is eliminated. A single link of: ``` <div class="pf-c-toggle-group__item"> <button class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy ? "pf-m-selected" : ""}" type="button" @click=${() => { this.mode = ProxyMode.Proxy; }}> <span class="pf-c-toggle-group__text">${msg("Proxy")}</span> </button> </div> <div class="pf-c-divider pf-m-vertical" role="separator"></div> ``` Now looks like: ``` <option value=${ProxyMode.Proxy}>${msg("Proxy")}</option> ``` This also means that the three pages that used the Patternfly Toggle Group could eliminate all of their Patternfly PFToggleGroup needs, as well as the `justify-content: center` extension, which also eliminated the `css` import. The savings aren't as spectacular as I'd hoped: removed 178 lines, but added 123; total savings 55 lines of code. I still count this a win: we need never write another toggle component again, and any bugs, extensions or features we may want to add can be centralized or forked without risking the whole edifice.
This commit is contained in:
parent
9e34a74a48
commit
df16dc3088
|
@ -9,12 +9,11 @@ import "@goauthentik/elements/forms/SearchSelect";
|
|||
import YAML from "yaml";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
|
||||
import { BlueprintFile, BlueprintInstance, ManagedApi } from "@goauthentik/api";
|
||||
|
||||
|
@ -51,15 +50,7 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
|||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFToggleGroup,
|
||||
PFContent,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
);
|
||||
return [...super.styles, PFContent];
|
||||
}
|
||||
|
||||
async send(data: BlueprintInstance): Promise<BlueprintInstance> {
|
||||
|
@ -105,52 +96,16 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
|||
</ak-form-element-horizontal>
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.source ===
|
||||
blueprintSource.file
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.source = blueprintSource.file;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("Local path")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.source ===
|
||||
blueprintSource.oci
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.source = blueprintSource.oci;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("OCI Registry")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.source ===
|
||||
blueprintSource.internal
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.source = blueprintSource.internal;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("Internal")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ak-toggle-group
|
||||
value=${this.source}
|
||||
@ak-toggle=${(ev: CustomEvent<{ value: blueprintSource }>) => {
|
||||
this.source = ev.detail.value;
|
||||
}}
|
||||
>
|
||||
<option value=${blueprintSource.file}>${msg("Local path")}</option>
|
||||
<option value=${blueprintSource.oci}>${msg("OCI Registry")}</option>
|
||||
<option value=${blueprintSource.internal}>${msg("Internal")}</option>
|
||||
</ak-toggle-group>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
${this.source === blueprintSource.file
|
||||
|
|
|
@ -5,12 +5,11 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
|||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, css } from "lit";
|
||||
import { CSSResult } from "lit";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
|
@ -70,15 +69,7 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFToggleGroup,
|
||||
PFContent,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
);
|
||||
return [...super.styles, PFContent];
|
||||
}
|
||||
|
||||
send(data: PolicyBinding): Promise<unknown> {
|
||||
|
@ -112,55 +103,22 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||
}
|
||||
|
||||
renderModeSelector(): TemplateResult {
|
||||
return html` <div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.policy
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.policy;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("Policy")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.group
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.group;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("Group")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.user
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.user;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("User")}</span>
|
||||
</button>
|
||||
</div>`;
|
||||
return html` <ak-toggle-group
|
||||
value=${this.policyGroupUser}
|
||||
@ak-toggle=${(ev: CustomEvent<{ value: target }>) => {
|
||||
this.policyGroupUser = ev.detail.value;
|
||||
}}
|
||||
>
|
||||
<option value=${target.policy}>${msg("Policy")}</option>
|
||||
<option value=${target.group}>${msg("Group")}</option>
|
||||
<option value=${target.user}>${msg("User")}</option>
|
||||
</ak-toggle-group>`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">${this.renderModeSelector()}</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Policy")}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-toggle-group";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
@ -8,14 +9,13 @@ import "@goauthentik/elements/forms/SearchSelect";
|
|||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, css } from "lit";
|
||||
import { CSSResult } from "lit";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
|
||||
|
||||
import {
|
||||
|
@ -35,17 +35,7 @@ import {
|
|||
@customElement("ak-provider-proxy-form")
|
||||
export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFToggleGroup,
|
||||
PFContent,
|
||||
PFList,
|
||||
PFSpacing,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
);
|
||||
return [...super.styles, PFContent, PFList, PFSpacing];
|
||||
}
|
||||
|
||||
async loadInstance(pk: number): Promise<ProxyProvider> {
|
||||
|
@ -137,51 +127,22 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
}
|
||||
|
||||
renderModeSelector(): TemplateResult {
|
||||
return html` <div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.Proxy;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${msg("Proxy")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.ForwardSingle;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text"
|
||||
>${msg("Forward auth (single application)")}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.ForwardDomain;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text"
|
||||
>${msg("Forward auth (domain level)")}</span
|
||||
>
|
||||
</button>
|
||||
</div>`;
|
||||
return html`
|
||||
<ak-toggle-group
|
||||
value=${this.mode}
|
||||
@ak-toggle=${(ev: CustomEvent<{ value: ProxyMode }>) => {
|
||||
this.mode = ev.detail.value;
|
||||
}}
|
||||
>
|
||||
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
|
||||
<option value=${ProxyMode.ForwardSingle}>
|
||||
${msg("Forward auth (single application)")}
|
||||
</option>
|
||||
<option value=${ProxyMode.ForwardDomain}>
|
||||
${msg("Forward auth (domain level)")}
|
||||
</option>
|
||||
</ak-toggle-group>
|
||||
`;
|
||||
}
|
||||
|
||||
renderSettings(): TemplateResult {
|
||||
|
@ -362,9 +323,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
</ak-form-element-horizontal>
|
||||
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">${this.renderModeSelector()}</div>
|
||||
<div class="pf-c-card__footer">${this.renderSettings()}</div>
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${msg("Token validity")} name="accessTokenValidity">
|
||||
|
|
90
web/src/components/ak-toggle-group.ts
Normal file
90
web/src/components/ak-toggle-group.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
|
||||
type Pair = [string, string];
|
||||
|
||||
/**
|
||||
* Toggle Group
|
||||
*
|
||||
* An implementation of the Patternfly Toggle Group as a LitElement
|
||||
*
|
||||
* @element ak-toggle-group
|
||||
*
|
||||
* @fires ak-toggle - Fired when someone clicks on a toggle option. Carries the value of the option.
|
||||
*/
|
||||
|
||||
// MYNIS:
|
||||
// A 'name' property so that the event carries *which* toggle group emitted the event.
|
||||
|
||||
@customElement("ak-toggle-group")
|
||||
export class AkToggleGroup extends CustomEmitterElement(AKElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
PFToggleGroup,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* The value (causes highlighting, value is returned)
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String, reflect: true })
|
||||
value = "";
|
||||
|
||||
get rawOptions(): HTMLOptionElement[] {
|
||||
return Array.from(this.querySelectorAll("option") ?? []);
|
||||
}
|
||||
|
||||
get options(): Pair[] {
|
||||
return Array.from(this.rawOptions).map(
|
||||
(option: HTMLOptionElement): Pair => [
|
||||
option.getAttribute("value") ?? "",
|
||||
option.textContent ?? "",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const last = this.options.length - 1;
|
||||
const mkClass = (v: string) => ({
|
||||
"pf-c-toggle-group__button": true,
|
||||
"pf-m-selected": this.value === v,
|
||||
});
|
||||
|
||||
const mkClick = (v: string) => () => {
|
||||
this.dispatchCustomEvent("ak-toggle", { value: v });
|
||||
};
|
||||
|
||||
return html` <div class="pf-c-toggle-group">
|
||||
${this.options.map(
|
||||
([key, label], idx) =>
|
||||
html`<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="${classMap(mkClass(key))}"
|
||||
type="button"
|
||||
@click=${mkClick(key)}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${label}</span>
|
||||
</button>
|
||||
</div>
|
||||
${idx < last
|
||||
? html`<div class="pf-c-divider pf-m-vertical" role="separator"></div>`
|
||||
: nothing} `,
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default AkToggleGroup;
|
|
@ -5,6 +5,7 @@
|
|||
"@goauthentik/app/*": ["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/*"],
|
||||
|
|
Reference in a new issue