diff --git a/addons/html_builder/static/src/utils/utils_css.js b/addons/html_builder/static/src/utils/utils_css.js
index b0bff528f6b92..780aad19a4339 100644
--- a/addons/html_builder/static/src/utils/utils_css.js
+++ b/addons/html_builder/static/src/utils/utils_css.js
@@ -600,7 +600,7 @@ export function applyNeededCss(
{ force = false, allowImportant = true } = {}
) {
if (force) {
- el.style.setProperty(cssProp, cssValue, "important");
+ el.style.setProperty(cssProp, cssValue, allowImportant ? "important" : "");
return true;
}
el.style.removeProperty(cssProp);
diff --git a/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.js b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.js
new file mode 100644
index 0000000000000..a5630a1744bd8
--- /dev/null
+++ b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.js
@@ -0,0 +1,22 @@
+import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
+import { _t } from "@web/core/l10n/translation";
+
+export class ScrollButtonOption extends BaseOptionComponent {
+ static template = "html_builder.ScrollButtonOption";
+ static props = {};
+
+ setup() {
+ super.setup();
+ this.state = useDomState((editingElement) => ({
+ heightLabel:
+ editingElement.dataset.snippet === "s_image_gallery"
+ ? _t("Min-Height")
+ : _t("Height"),
+ heightFieldEnabled: editingElement.dataset.snippet === "s_image_gallery",
+ }));
+ }
+
+ showHeightField() {
+ return this.state.heightFieldEnabled && this.isActiveItem("fit_content_opt");
+ }
+}
diff --git a/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.xml b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.xml
new file mode 100644
index 0000000000000..f2d4860f46b6b
--- /dev/null
+++ b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ Auto
+ 50%
+ 100%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ Extra-Small
+ Small
+ Medium
+ Large
+ Extra-Large
+
+
+
+
+
+
diff --git a/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option_plugin.js b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option_plugin.js
new file mode 100644
index 0000000000000..23c6bc3dc67ba
--- /dev/null
+++ b/addons/html_builder/static/src/website_builder/plugins/options/scroll_button_option_plugin.js
@@ -0,0 +1,96 @@
+import { Plugin } from "@html_editor/plugin";
+import { _t } from "@web/core/l10n/translation";
+import { registry } from "@web/core/registry";
+import { ScrollButtonOption } from "./scroll_button_option";
+import { classAction } from "@html_builder/core/core_builder_action_plugin";
+
+class ScrollButtonOptionPlugin extends Plugin {
+ static id = "scrollButtonOption";
+ resources = {
+ builder_options: [
+ {
+ OptionComponent: ScrollButtonOption,
+ selector: "section",
+ exclude:
+ "[data-snippet] :not(.oe_structure) > [data-snippet],.s_instagram_page,.o_mega_menu > section,.s_appointments .s_dynamic_snippet_content",
+ },
+ ],
+ builder_actions: {
+ addScrollButton: {
+ isApplied: ({ editingElement }) =>
+ !!editingElement.querySelector(":scope > .o_scroll_button"),
+ apply: ({ editingElement }) => {
+ let button = this.buttonCache.get(editingElement);
+ if (!button) {
+ const anchor = document.createElement("a");
+ anchor.classList.add(
+ "o_scroll_button",
+ "mb-3",
+ "rounded-circle",
+ "align-items-center",
+ "justify-content-center",
+ "mx-auto",
+ "bg-primary",
+ "o_not_editable"
+ );
+ anchor.href = "#";
+ anchor.contentEditable = "false";
+ anchor.title = _t("Scroll down to next section");
+ const arrow = document.createElement("i");
+ arrow.classList.add("fa", "fa-angle-down", "fa-3x");
+ anchor.appendChild(arrow);
+ button = anchor;
+ this.buttonCache.set(editingElement, button);
+ }
+ editingElement.appendChild(button);
+ },
+ clean: this.removeButton.bind(this),
+ },
+ scrollButtonSectionHeightClassAction: {
+ ...classAction,
+ apply: (args) => {
+ classAction.apply(args);
+ const {
+ editingElement,
+ param: { mainParam },
+ } = args;
+ // If a "d-lg-block" class exists on the section (e.g., for
+ // mobile visibility option), it should be replaced with a
+ // "d-lg-flex" class. This ensures that the section has the
+ // "display: flex" property applied, which is the default
+ // rule for both "height" option classes.
+ if (mainParam) {
+ editingElement.classList.replace("d-lg-block", "d-lg-flex");
+ } else if (editingElement.classList.contains("d-lg-flex")) {
+ // There are no known cases, but we still make sure that
+ // the element doesn't have a "display: flex"
+ // originally.
+ editingElement.classList.remove("d-lg-flex");
+ const sectionStyle = window.getComputedStyle(editingElement);
+ const hasDisplayFlex = sectionStyle.getPropertyValue("display") === "flex";
+ editingElement.classList.add(hasDisplayFlex ? "d-lg-flex" : "d-lg-block");
+ }
+ },
+ clean: (args) => {
+ classAction.clean(args);
+ if (args.param.mainParam === "o_full_screen_height") {
+ this.removeButton(args);
+ }
+ },
+ },
+ },
+ };
+
+ setup() {
+ this.buttonCache = new Map();
+ }
+
+ removeButton({ editingElement }) {
+ const button = editingElement.querySelector(":scope > .o_scroll_button");
+ if (button) {
+ button.remove();
+ this.buttonCache.set(editingElement, button);
+ }
+ }
+}
+registry.category("website-plugins").add(ScrollButtonOptionPlugin.id, ScrollButtonOptionPlugin);