ui: add RadioBasedTabs

does the "tabs" seen on the invite tab (invite expiry/user expiry), and
will be used in all other similar cases.
This commit is contained in:
Harvey Tindall
2025-12-21 19:55:49 +00:00
parent 41334e9051
commit 3e39657642
4 changed files with 118 additions and 38 deletions

View File

@@ -597,16 +597,7 @@
<span class="heading">{{ .strings.create }}</span>
<div class="flex flex-col md:flex-row gap-3" id="create-inv">
<div class="card ~neutral @low flex flex-col gap-2 flex-1">
<div class="flex flex-row gap-2">
<label class="w-1/2">
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
</label>
<label class="w-1/2">
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
</label>
</div>
<div id="invites-duration-type-tabs"></div>
<div id="inv-duration" class="flex flex-col gap-2">
<div class="flex flex-row gap-2">
<div class="grow flex flex-col gap-4">

View File

@@ -2872,7 +2872,17 @@ class UserInfo extends PaginatedList {
<div class="flex flex-row gap-2">
</div>
<div class="flex flex-col gap-1">
<h2 class="heading text-2xl">${window.lang.strings("activity")}</h2>
<div class="flex flex-row gap-2">
<label class="grow">
<input type="radio" name="jf-activity-source" class="jf-activity-source-jellyfin unfocused" checked>
<span class="button ~neutral @high supra w-full text-center">${window.lang.strings("jellyfin")}</span>
</label>
<label class="grow">
<input type="radio" name="jf-activity-source" class="jf-activity-source-jfa-go unfocused">
<span class="button ~neutral @low supra w-full text-center">jfa-go</span>
</label>
</div>
<!-- <h2 class="heading text-2xl">${window.lang.strings("activity")}</h2> -->
<div class="card @low overflow-x-scroll jf-activity-table">
<table class="table text-xs leading-5">
<thead>

View File

@@ -13,7 +13,7 @@ import {
} from "../modules/common.js";
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { reloadProfileNames } from "../modules/profiles.js";
import { HiddenInputField } from "./ui.js";
import { HiddenInputField, RadioBasedTabSelector } from "./ui.js";
declare var window: GlobalWindow;
@@ -945,8 +945,8 @@ export class createInvite {
private _userHours = document.getElementById("user-hours") as HTMLSelectElement;
private _userMinutes = document.getElementById("user-minutes") as HTMLSelectElement;
private _invDurationButton = document.getElementById("radio-inv-duration") as HTMLInputElement;
private _userExpiryButton = document.getElementById("radio-user-expiry") as HTMLInputElement;
// private _invDurationButton = document.getElementById("radio-inv-duration") as HTMLInputElement;
// private _userExpiryButton = document.getElementById("radio-user-expiry") as HTMLInputElement;
private _invDuration = document.getElementById("inv-duration");
private _userExpiry = document.getElementById("user-expiry");
@@ -1191,30 +1191,18 @@ export class createInvite {
this.uses = 1;
this.label = "";
const checkDuration = () => {
const invSpan = this._invDurationButton.nextElementSibling as HTMLSpanElement;
const userSpan = this._userExpiryButton.nextElementSibling as HTMLSpanElement;
if (this._invDurationButton.checked) {
this._invDuration.classList.remove("unfocused");
this._userExpiry.classList.add("unfocused");
invSpan.classList.add("@high");
invSpan.classList.remove("@low");
userSpan.classList.add("@low");
userSpan.classList.remove("@high");
} else if (this._userExpiryButton.checked) {
this._userExpiry.classList.remove("unfocused");
this._invDuration.classList.add("unfocused");
invSpan.classList.add("@low");
invSpan.classList.remove("@high");
userSpan.classList.add("@high");
userSpan.classList.remove("@low");
}
};
this._userExpiryButton.checked = false;
this._invDurationButton.checked = true;
this._userExpiryButton.onchange = checkDuration;
this._invDurationButton.onchange = checkDuration;
new RadioBasedTabSelector(
document.getElementById("invites-duration-type-tabs"),
"invites-duration-type",
{
name: window.lang.strings("inviteDuration"),
content: this._invDuration,
},
{
name: window.lang.strings("userExpiry"),
content: this._userExpiry,
},
);
this._days.onchange = this._checkDurationValidity;
this._months.onchange = this._checkDurationValidity;

View File

@@ -1,3 +1,5 @@
declare var window: GlobalWindow;
export interface HiddenInputConf {
container: HTMLElement;
onSet: () => void;
@@ -109,3 +111,92 @@ export class HiddenInputField {
this.setEditing(!this.editing, false, noSave);
}
}
export interface RadioBasedTab {
name: string;
content: HTMLElement;
onShow?: () => void;
onHide?: () => void;
}
interface RadioBasedTabItem {
tab: RadioBasedTab;
input: HTMLInputElement;
button: HTMLElement;
}
export class RadioBasedTabSelector {
private _id: string;
private _container: HTMLElement;
private _tabs: RadioBasedTabItem[];
private _selected: string;
constructor(container: HTMLElement, id: string, ...tabs: RadioBasedTab[]) {
this._container = container;
this._container.classList.add("flex", "flex-row", "gap-2");
this._tabs = [];
this._id = id;
let i = 0;
const frag = document.createDocumentFragment();
for (let tab of tabs) {
const label = document.createElement("label");
label.classList.add("grow");
label.innerHTML = `
<input type="radio" name="${this._id}" value="${tab.name}" class="unfocused" ${i == 0 ? "checked" : ""}>
<span class="button ~neutral ${i == 0 ? "@high" : "@low"} radio-tab-button supra w-full text-center">${tab.name}</span>
`;
let ft: RadioBasedTabItem = {
tab: tab,
input: label.getElementsByTagName("input")[0] as HTMLInputElement,
button: label.getElementsByClassName("radio-tab-button")[0] as HTMLElement
};
ft.input.onclick = () => {
ft.input.checked = true;
this.checkSource();
};
frag.appendChild(label);
this._tabs.push(ft);
i++;
}
this._container.replaceChildren(frag);
this.selected = this._tabs[0].tab.name;
}
checkSource = () => {
for (let tab of this._tabs) {
if (tab.input.checked) {
this._selected = tab.tab.name;
tab.tab.content.classList.remove("unfocused");
tab.button.classList.add("@high");
tab.button.classList.remove("@low");
if (tab.tab.onShow) tab.tab.onShow();
} else {
tab.tab.content.classList.add("unfocused");
tab.button.classList.add("@low");
tab.button.classList.remove("@high");
if (tab.tab.onHide) tab.tab.onHide();
}
}
};
get selected(): string { return this._selected; }
set selected(name: string) {
for (let tab of this._tabs) {
if (tab.tab.name == name) {
this._selected = tab.tab.name;
tab.input.checked = true;
tab.tab.content.classList.remove("unfocused");
tab.button.classList.add("@high");
tab.button.classList.remove("@low");
if (tab.tab.onShow) tab.tab.onShow();
} else {
tab.input.checked = false;
tab.tab.content.classList.add("unfocused");
tab.button.classList.add("@low");
tab.button.classList.remove("@high");
if (tab.tab.onHide) tab.tab.onHide();
}
}
}
}