zulip/static/js/components.js

171 lines
4.7 KiB
JavaScript

import $ from "jquery";
import * as keydown_util from "./keydown_util";
/* USAGE:
Toggle x = components.toggle({
selected: Integer selected_index,
values: Array<Object> [
{ label: i18n.t("String title") }
],
callback: function () {
// .. on value change.
},
}).get();
*/
export function toggle(opts) {
const component = (function render_component(opts) {
const _component = $("<div class='tab-switcher'></div>");
if (opts.html_class) {
// add a check inside passed arguments in case some extra
// classes need to be added for correct alignment or other purposes
_component.addClass(opts.html_class);
}
for (const [i, value] of opts.values.entries()) {
// create a tab with a tab-id so they don't have to be referenced
// by text value which can be inconsistent.
const tab = $("<div>", {
class: "ind-tab",
"data-tab-key": value.key,
"data-tab-id": i,
tabindex: 0,
});
/* istanbul ignore if */
if (value.label_html !== undefined) {
const html = value.label_html;
tab.html(html);
} else {
tab.text(value.label);
}
// add proper classes for styling in CSS.
if (i === 0) {
// this should be default selected unless otherwise specified.
tab.addClass("first selected");
} else if (i === opts.values.length - 1) {
tab.addClass("last");
} else {
tab.addClass("middle");
}
_component.append(tab);
}
return _component;
})(opts);
const meta = {
$ind_tab: component.find(".ind-tab"),
idx: -1,
};
// Returns false if the requested tab is disabled.
function select_tab(idx) {
const elem = meta.$ind_tab.eq(idx);
if (elem.hasClass("disabled")) {
return false;
}
meta.$ind_tab.removeClass("selected");
elem.addClass("selected");
meta.idx = idx;
if (opts.callback) {
opts.callback(opts.values[idx].label, opts.values[idx].key);
}
if (!opts.child_wants_focus) {
elem.trigger("focus");
}
return true;
}
function maybe_go_left() {
// Select the first non-disabled tab to the left, if any.
let i = 1;
while (meta.idx - i >= 0) {
if (select_tab(meta.idx - i)) {
return true;
}
i += 1;
}
return false;
}
function maybe_go_right() {
// Select the first non-disabled tab to the right, if any.
let i = 1;
while (meta.idx + i <= opts.values.length - 1) {
if (select_tab(meta.idx + i)) {
return true;
}
i += 1;
}
return false;
}
(function () {
meta.$ind_tab.on("click", function () {
const idx = $(this).data("tab-id");
select_tab(idx);
});
keydown_util.handle({
elem: meta.$ind_tab,
handlers: {
left_arrow: maybe_go_left,
right_arrow: maybe_go_right,
},
});
// We should arguably default opts.selected to 0.
if (typeof opts.selected === "number") {
select_tab(opts.selected);
}
})();
const prototype = {
// Skip disabled tabs and go to the next one.
maybe_go_left,
maybe_go_right,
disable_tab(name) {
const value = opts.values.find((o) => o.key === name);
const idx = opts.values.indexOf(value);
meta.$ind_tab.eq(idx).addClass("disabled");
},
enable_tab(name) {
const value = opts.values.find((o) => o.key === name);
const idx = opts.values.indexOf(value);
meta.$ind_tab.eq(idx).removeClass("disabled");
},
value() {
if (meta.idx >= 0) {
return opts.values[meta.idx].label;
}
/* istanbul ignore next */
return undefined;
},
get() {
return component;
},
// go through the process of finding the correct tab for a given name,
// and when found, select that one and provide the proper callback.
goto(name) {
const value = opts.values.find((o) => o.label === name || o.key === name);
const idx = opts.values.indexOf(value);
if (idx >= 0) {
select_tab(idx);
}
},
};
return prototype;
}