web/admin: add Radio control, search-select fixes (#4333)

* move search select to forms folder

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add radio, migrate smaller lists

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* move dropdown when scrolling, hide when container out of frame

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2023-01-02 14:51:44 +01:00
committed by GitHub
parent 9564894eda
commit ba5cd6e719
42 changed files with 697 additions and 602 deletions

View File

@ -2,8 +2,8 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages";
import { camelToSnake, convertToSlug } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/SearchSelect";
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import "@polymer/iron-form/iron-form";
import { IronFormElement } from "@polymer/iron-form/iron-form";

View File

@ -90,6 +90,7 @@ export class HorizontalFormElement extends AKElement {
case "ak-codemirror":
case "ak-chip-group":
case "ak-search-select":
case "ak-radio":
(input as HTMLInputElement).name = this.name;
break;
default:

View File

@ -65,7 +65,16 @@ export class ModalForm extends ModalButton {
</h1>
</div>
</section>
<section class="pf-c-modal-box__body">
<section
class="pf-c-modal-box__body"
@scroll=${() => {
window.dispatchEvent(
new CustomEvent("scroll", {
bubbles: true,
}),
);
}}
>
<slot name="form"></slot>
</section>
<footer class="pf-c-modal-box__footer">

View File

@ -0,0 +1,81 @@
import { AKElement } from "@goauthentik/elements/Base";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import AKGlobal from "@goauthentik/common/styles/authentik.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export interface RadioOption<T> {
label: string;
description?: TemplateResult;
default: boolean;
value: T;
}
@customElement("ak-radio")
export class Radio<T> extends AKElement {
@property({ attribute: false })
options: RadioOption<T>[] = [];
@property()
name = "";
@property()
value?: T;
@property({ attribute: false })
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onChange: (value: T) => void = (value: T) => {
return;
};
static get styles(): CSSResult[] {
return [
PFBase,
PFRadio,
PFForm,
AKGlobal,
css`
.pf-c-form__group-control {
padding-top: calc(
var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3
);
}
`,
];
}
render(): TemplateResult {
if (!this.value) {
const def = this.options.filter((opt) => opt.default);
if (def.length > 0) {
this.value = def[0].value;
}
}
return html`<div class="pf-c-form__group-control pf-m-stack">
${this.options.map((opt) => {
const elId = `${this.name}-${opt.value}`;
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="${this.name}"
id=${elId}
@change=${() => {
this.value = opt.value;
this.onChange(opt.value);
}}
.checked=${opt.value === this.value}
/>
<label class="pf-c-radio__label" for=${elId}>${opt.label}</label>
${opt.description
? html`<span class="pf-c-radio__description">${opt.description}</span>`
: html``}
</div>`;
})}
</div>`;
}
}

View File

@ -66,11 +66,25 @@ export class SearchSelect<T> extends AKElement {
});
};
scrollHandler?: () => void;
observer: IntersectionObserver;
dropdownContainer: HTMLDivElement;
constructor() {
super();
this.dropdownContainer = document.createElement("div");
this.observer = new IntersectionObserver(() => {
this.open = false;
this.shadowRoot
?.querySelectorAll<HTMLInputElement>(
".pf-c-form-control.pf-c-select__toggle-typeahead",
)
.forEach((input) => {
input.blur();
});
});
this.observer.observe(this);
}
toForm(): unknown {
@ -102,12 +116,20 @@ export class SearchSelect<T> extends AKElement {
document.body.append(this.dropdownContainer);
this.updateData();
this.addEventListener(EVENT_REFRESH, this.updateData);
this.scrollHandler = () => {
this.requestUpdate();
};
window.addEventListener("scroll", this.scrollHandler);
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener(EVENT_REFRESH, this.updateData);
if (this.scrollHandler) {
window.removeEventListener("scroll", this.scrollHandler);
}
this.dropdownContainer.remove();
this.observer.disconnect();
}
/*
@ -238,7 +260,7 @@ export class SearchSelect<T> extends AKElement {
setTimeout(() => {
this.open = false;
this.renderMenu();
}, 200);
}, 100);
}}
.value=${this.selectedObject
? this.renderElement(this.selectedObject)