From d80f45792d46b921d6def1bcd9e26d729172aac6 Mon Sep 17 00:00:00 2001 From: luvi Date: Wed, 15 Feb 2023 21:33:49 +0000 Subject: [PATCH] [IMP] web: add Notebook component features This commit improves the Notebook component to support the ability to disable some tabs. Once the page is disabled, the tab is greyed out and set as inactive. Also, an icon can be placed in the tab, before the label. This change is used since the Notebook component is used as the sidebar element during the web_studio refactor. The sidebar needed to disable a tab depending certain conditions. Now, the component has this behavior by default once a page has the isDisabled attribute. To display an icon before the label, you can use the icons props, as an object, which keys are page names, and values are font awesome classnames. A test has been added for each new feature introduced. Part-of: odoo/odoo#115799 --- .../web/static/src/core/notebook/notebook.js | 18 +++- .../static/src/core/notebook/notebook.scss | 7 ++ .../web/static/src/core/notebook/notebook.xml | 7 +- .../web/static/tests/core/notebook_tests.js | 89 +++++++++++++++++++ 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/core/notebook/notebook.js b/addons/web/static/src/core/notebook/notebook.js index 1721cc6b553..32660271057 100644 --- a/addons/web/static/src/core/notebook/notebook.js +++ b/addons/web/static/src/core/notebook/notebook.js @@ -2,7 +2,14 @@ import { scrollTo } from "@web/core/utils/scrolling"; -import { Component, onWillDestroy, onWillUpdateProps, useEffect, useRef, useState } from "@odoo/owl"; +import { + Component, + onWillDestroy, + onWillUpdateProps, + useEffect, + useRef, + useState, +} from "@odoo/owl"; /** * A notebook component that will render only the current page and allow @@ -109,7 +116,9 @@ export class Notebook extends Component { } activatePage(pageIndex) { - this.state.currentPage = pageIndex; + if (!this.disabledPages.includes(pageIndex)) { + this.state.currentPage = pageIndex; + } } computePages(props) { @@ -121,6 +130,7 @@ export class Notebook extends Component { page.isVisible = true; } } + this.disabledPages = []; const pages = []; const pagesWithIndex = []; for (const [k, v] of Object.entries({ ...props.slots, ...props.pages })) { @@ -130,6 +140,9 @@ export class Notebook extends Component { } else { pages.push([id, v]); } + if (v.isDisabled) { + this.disabledPages.push(k); + } } for (const page of pagesWithIndex) { pages.splice(page[1].index, 0, page); @@ -176,5 +189,6 @@ Notebook.props = { anchors: { type: Object, optional: true }, defaultPage: { type: String, optional: true }, orientation: { type: String, optional: true }, + icons: { type: Object, optional: true }, onPageUpdate: { type: Function, optional: true }, }; diff --git a/addons/web/static/src/core/notebook/notebook.scss b/addons/web/static/src/core/notebook/notebook.scss index f6df01d3254..ae0fb423fc9 100644 --- a/addons/web/static/src/core/notebook/notebook.scss +++ b/addons/web/static/src/core/notebook/notebook.scss @@ -26,6 +26,13 @@ .nav-item { white-space: nowrap; margin: 0 -1px 0 0; + + &.disabled { + .nav-link { + cursor: not-allowed; + opacity: .3; + } + } } .nav-link { diff --git a/addons/web/static/src/core/notebook/notebook.xml b/addons/web/static/src/core/notebook/notebook.xml index 755e13e0a1d..cea82b93b3b 100644 --- a/addons/web/static/src/core/notebook/notebook.xml +++ b/addons/web/static/src/core/notebook/notebook.xml @@ -5,8 +5,11 @@
diff --git a/addons/web/static/tests/core/notebook_tests.js b/addons/web/static/tests/core/notebook_tests.js index 061a7679059..4f19b302620 100644 --- a/addons/web/static/tests/core/notebook_tests.js +++ b/addons/web/static/tests/core/notebook_tests.js @@ -334,4 +334,93 @@ QUnit.module("Components", (hooks) => { assert.containsOnce(target, ".page2"); assert.strictEqual(target.querySelector(".nav-link.active").textContent, "page2"); }); + + QUnit.test("disabled pages are greyed out and can't be toggled", async (assert) => { + class Parent extends Component {} + Parent.components = { Notebook }; + Parent.template = xml` + + +
+ + +
+ + +
+ + `; + + const env = await makeTestEnv(); + await mount(Parent, target, { env }); + assert.containsOnce(target, ".page1", "the default page is displayed"); + assert.hasClass( + target.querySelector(".nav-item:nth-child(2)"), + "disabled", + "tab of the disabled page is greyed out" + ); + + await click(target.querySelector(".nav-item:nth-child(2) .nav-link")); + assert.containsOnce(target, ".page1", "the same page is still displayed"); + + await click(target.querySelector(".nav-item:nth-child(3) .nav-link")); + assert.containsOnce(target, ".page3", "the third page is now displayed"); + }); + + QUnit.test("icons can be given for each page tab", async (assert) => { + class Parent extends Component { + get icons() { + return { + 1: "fa-trash", + 3: "fa-pencil", + }; + } + } + Parent.components = { Notebook }; + Parent.template = xml` + + +
+ + +
+ + +
+ + `; + + const env = await makeTestEnv(); + await mount(Parent, target, { env }); + assert.hasClass( + target.querySelector(".nav-item:nth-child(1) i"), + "fa-trash", + "tab of the first page has the given icon" + ); + assert.strictEqual( + target.querySelector(".nav-item:nth-child(1)").textContent, + "page1", + "tab of the second page has the right text" + ); + assert.containsNone( + target.querySelector(".nav-item:nth-child(2)"), + "i", + "tab of the second page doesn't have an icon" + ); + assert.strictEqual( + target.querySelector(".nav-item:nth-child(2)").textContent, + "page2", + "tab of the second page has the right text" + ); + assert.hasClass( + target.querySelector(".nav-item:nth-child(3) i"), + "fa-pencil", + "tab of the third page has the given icon" + ); + assert.strictEqual( + target.querySelector(".nav-item:nth-child(3)").textContent, + "page3", + "tab of the second page has the right text" + ); + }); });