settings: Update save button style when "Saved" is shown.

This commit updates the save button style in the settings component to
ensure that the button appears as a borderless attention + success
intent action button alongside the "Saved" label, when an updated
setting is saved.
This commit is contained in:
Sayam Samal
2025-04-02 04:34:34 +05:30
committed by Tim Abbott
parent 203ca08446
commit b115368a81
4 changed files with 54 additions and 6 deletions

View File

@@ -1,6 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import * as loading from "./loading.ts"; import * as loading from "./loading.ts";
import {COMPONENT_INTENT_VALUES} from "./types.ts";
import type {ComponentIntent} from "./types.ts"; import type {ComponentIntent} from "./types.ts";
export const ACTION_BUTTON_ATTENTION_VALUES = ["primary", "quiet", "borderless"] as const; export const ACTION_BUTTON_ATTENTION_VALUES = ["primary", "quiet", "borderless"] as const;
@@ -50,3 +51,32 @@ export function hide_button_loading_indicator($button: JQuery): void {
$button.find(".zulip-icon").css("visibility", "visible"); $button.find(".zulip-icon").css("visibility", "visible");
$button.find(".action-button-label").css("visibility", "visible"); $button.find(".action-button-label").css("visibility", "visible");
} }
export function modify_action_button_style(
$button: JQuery,
opts: {
attention?: ActionButtonAttention;
intent?: ComponentIntent;
},
): void {
if (opts.attention === undefined && opts.intent === undefined) {
// If neither attention nor intent is provided, do nothing.
return;
}
const action_button_attention_pattern = ACTION_BUTTON_ATTENTION_VALUES.join("|");
const component_intent_pattern = COMPONENT_INTENT_VALUES.join("|");
const action_button_style_regex = new RegExp(
`action-button-(${action_button_attention_pattern})-(${component_intent_pattern})`,
);
const action_button_style_regex_match = $button.attr("class")?.match(action_button_style_regex);
if (!action_button_style_regex_match) {
// If the button doesn't have the expected class, do nothing.
return;
}
const [action_button_style_class, old_attention, old_intent] = action_button_style_regex_match;
// Replace the old attention and intent values with the new ones, if provided.
$button.removeClass(action_button_style_class);
$button.addClass(
`action-button-${opts.attention ?? old_attention}-${opts.intent ?? old_intent}`,
);
}

View File

@@ -557,19 +557,17 @@ export function change_save_button_state($element: JQuery, state: string): void
return; return;
} }
let button_text; let button_text = $t({defaultMessage: "Save changes"});
let data_status; let data_status;
let is_show; let is_show;
switch (state) { switch (state) {
case "unsaved": case "unsaved":
button_text = $t({defaultMessage: "Save changes"});
data_status = "unsaved"; data_status = "unsaved";
is_show = true; is_show = true;
$element.find(".discard-button").show(); $element.find(".discard-button").show();
break; break;
case "saved": case "saved":
button_text = $t({defaultMessage: "Save changes"});
data_status = ""; data_status = "";
is_show = false; is_show = false;
break; break;
@@ -584,7 +582,6 @@ export function change_save_button_state($element: JQuery, state: string): void
buttons.show_button_loading_indicator($save_button); buttons.show_button_loading_indicator($save_button);
break; break;
case "failed": case "failed":
button_text = $t({defaultMessage: "Save changes"});
data_status = "failed"; data_status = "failed";
is_show = true; is_show = true;
break; break;
@@ -595,9 +592,23 @@ export function change_save_button_state($element: JQuery, state: string): void
break; break;
} }
if (button_text !== undefined) { requestAnimationFrame(() => {
// We need to use requestAnimationFrame to ensure that the
// button text and style are updated in the same frame.
$textEl.text(button_text); $textEl.text(button_text);
} if (state === "succeeded") {
buttons.modify_action_button_style($save_button, {
attention: "borderless",
intent: "success",
});
} else {
buttons.modify_action_button_style($save_button, {
attention: "primary",
intent: "brand",
});
}
});
assert(data_status !== undefined); assert(data_status !== undefined);
$save_button.attr("data-status", data_status); $save_button.attr("data-status", data_status);
if (state === "unsaved") { if (state === "unsaved") {

View File

@@ -885,6 +885,10 @@ input.settings_text_input {
&.hide { &.hide {
display: none; display: none;
} }
.save-button[data-status="saved"] {
pointer-events: none;
}
} }
.stream-privacy-type-icon { .stream-privacy-type-icon {

View File

@@ -19,10 +19,13 @@ mock_esm("../src/loading", {
mock_esm("../src/buttons", { mock_esm("../src/buttons", {
show_button_loading_indicator: noop, show_button_loading_indicator: noop,
hide_button_loading_indicator: noop, hide_button_loading_indicator: noop,
modify_action_button_style: noop,
}); });
mock_esm("../src/scroll_util", {scroll_element_into_container: noop}); mock_esm("../src/scroll_util", {scroll_element_into_container: noop});
set_global("document", "document-stub"); set_global("document", "document-stub");
set_global("requestAnimationFrame", (func) => func());
const settings_account = zrequire("settings_account"); const settings_account = zrequire("settings_account");
const settings_components = zrequire("settings_components"); const settings_components = zrequire("settings_components");
const settings_config = zrequire("settings_config"); const settings_config = zrequire("settings_config");