Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): Add support for radio groups #35

Merged
merged 1 commit into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/userscript/source/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,16 @@ export class SettingTriggerMax extends SettingTrigger implements SettingMax {
this.max = setting.max ?? this.max;
}
}

export class SettingOptions<T = string> {
readonly #options: Array<{ label: string; value: T }>;
selected: T | undefined;

get options() {
return this.#options;
}

constructor(options = new Array<{ label: string; value: T }>()) {
this.#options = options;
}
}
22 changes: 22 additions & 0 deletions packages/userscript/source/ui/components/Fieldset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UserScript } from "../../UserScript";
import { UiComponent } from "./UiComponent";

export class Fieldset extends UiComponent {
readonly element: JQuery<HTMLElement>;

/**
* Constructs a `Fieldset`.
*
* @param host A reference to the host.
* @param label The label on the fieldset.
*/
constructor(host: UserScript, label: string) {
super(host);

const element = $("<fieldset/>").addClass("ks-fieldset");
const legend = $("<legend/>").text(label).addClass("ks-label");
element.append(legend);

this.element = element;
}
}
48 changes: 48 additions & 0 deletions packages/userscript/source/ui/components/OptionsListItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { SettingOptions } from "../../settings/Settings";
import { UserScript } from "../../UserScript";
import { Fieldset } from "./Fieldset";
import { RadioItem } from "./RadioItem";
import { UiComponent } from "./UiComponent";

export class OptionsListItem<TSetting extends SettingOptions = SettingOptions> extends UiComponent {
readonly fieldset: Fieldset;
readonly element: JQuery<HTMLElement>;

/**
* Construct a new options setting element.
* This is a list of options, where the selected option will be put into the setting.
*
* @param host The userscript instance.
* @param label The label on the setting element.
* @param setting The setting this element is linked to.
* @param handler The event handlers for this setting element.
* @param handler.onCheck Will be invoked when the user selects an option.
* @param readOnly Should the user be prevented from changing the value of the input?
*/
constructor(
host: UserScript,
label: string,
setting: TSetting,
handler: {
onCheck: () => void;
},
readOnly = false
) {
super(host);

this.element = $(`<li/>`);

this.fieldset = new Fieldset(host, label);
this.addChild(this.fieldset);

for (const option of setting.options) {
this.fieldset.addChild(
new RadioItem(host, setting, option, label, handler, false, false, readOnly)
);
}
}

refreshUi() {
super.refreshUi();
}
}
74 changes: 74 additions & 0 deletions packages/userscript/source/ui/components/RadioItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { SettingOptions } from "../../settings/Settings";
import { UserScript } from "../../UserScript";
import { UiComponent } from "./UiComponent";

export class RadioItem<TSetting extends SettingOptions = SettingOptions> extends UiComponent {
readonly setting: TSetting;
readonly element: JQuery<HTMLElement>;
readonly input: JQuery<HTMLElement>;

readOnly: boolean;

/**
* Construct a new radio setting element.
* This is a radio input that is expected to be hosted in a `Fieldset`.
*
* @param host The userscript instance.
* @param setting The setting this element is linked to.
* @param option The specific option out of the setting that this radio item represents.
* @param groupKey A unique name for the group of radio items this one belongs to.
* @param handler The event handlers for this setting element.
* @param handler.onCheck Will be invoked when the user selects this radio item.
* @param delimiter Should there be additional padding below this element?
* @param upgradeIndicator Should an indicator be rendered in front of the elemnt,
* to indicate that this is an upgrade of a prior setting?
* @param readOnly Should the user be prevented from changing the value of the input?
*/
constructor(
host: UserScript,
setting: TSetting,
option: TSetting["options"][0],
groupKey: string,
handler: {
onCheck: () => void;
},
delimiter = false,
upgradeIndicator = false,
readOnly = false
) {
super(host);

const element = $(`<div/>`);
for (const cssClass of ["ks-setting", delimiter ? "ks-delimiter" : ""]) {
element.addClass(cssClass);
}

const elementLabel = $("<label/>", {
text: `${upgradeIndicator ? `⮤ ` : ""}${option.label}`,
}).addClass("ks-label");

const input = $("<input/>", {
name: groupKey,
type: "radio",
}).addClass("ks-radio");

this.readOnly = readOnly;

input.on("change", () => {
this.setting.selected = option.value;
handler.onCheck();
});

elementLabel.prepend(input);
element.append(elementLabel);

this.input = input;
this.element = element;
this.setting = setting;
}

refreshUi() {
super.refreshUi();
this.input.prop("disabled", this.readOnly);
}
}