From 52a6ff9131ed4e11c3b9bfc5015b222130a7b1bf Mon Sep 17 00:00:00 2001 From: Konstantin Gogov Date: Tue, 22 Oct 2024 17:41:15 +0300 Subject: [PATCH] feat(ui5-barcode-scanner-dialog): added support for custom header and footer slots (#10066) This update introduces two new slot attributes for `ui5-barcode-scanner-dialog`, allowing developers to customize the header and footer of the dialog. If no custom content is provided, the dialog will use its default layout. The new slots enhance flexibility for developers, enabling the addition of custom titles, buttons, and any other elements in the header and footer areas. Related: #8919 --- .../cypress/specs/BarcodeScannerDialog.cy.ts | 128 +++++++++++++++++- packages/fiori/src/BarcodeScannerDialog.hbs | 5 +- packages/fiori/src/BarcodeScannerDialog.ts | 29 ++++ .../fiori/src/themes/BarcodeScannerDialog.css | 4 + .../test/pages/BarcodeScannerDialog.html | 39 +++++- .../pages/styles/BarcodeScannerDialog.css | 11 +- 6 files changed, 208 insertions(+), 8 deletions(-) diff --git a/packages/fiori/cypress/specs/BarcodeScannerDialog.cy.ts b/packages/fiori/cypress/specs/BarcodeScannerDialog.cy.ts index e87df3fb908a..c85e47d5529c 100644 --- a/packages/fiori/cypress/specs/BarcodeScannerDialog.cy.ts +++ b/packages/fiori/cypress/specs/BarcodeScannerDialog.cy.ts @@ -4,6 +4,7 @@ import "../../src/BarcodeScannerDialog.js"; import type BarcodeScannerDialog from "../../src/BarcodeScannerDialog.js"; describe("BarcodeScannerDialog", () => { + let openDialogHandler: EventListener | null; let handleScanSuccess: (event: CustomEvent) => void; let handleScanError: (event: CustomEvent) => void; @@ -49,6 +50,12 @@ describe("BarcodeScannerDialog", () => { handleScanError = undefined!; } + const btnScan = doc.querySelector("#btnScan"); + if (btnScan && openDialogHandler) { + btnScan.removeEventListener("click", openDialogHandler); + } + openDialogHandler = null; + // Clear the scan result and error const scanResultElement = doc.querySelector("#scanResult")!; const scanErrorElement = doc.querySelector("#scanError")!; @@ -82,7 +89,7 @@ describe("BarcodeScannerDialog", () => { // Close the dialog using the close button cy.get("@dialog") .shadow() - .find("ui5-dialog [slot=footer] ui5-button") + .find("ui5-dialog slot[name=footer] ui5-button") .realClick(); // Verify the dialog is closed @@ -188,7 +195,7 @@ describe("BarcodeScannerDialog", () => { // Close the dialog using the close button cy.get("@dialog") .shadow() - .find("ui5-dialog [slot=footer] ui5-button") + .find("ui5-dialog slot[name=footer] ui5-button") .realClick(); // Verify the dialog is closed @@ -266,7 +273,7 @@ describe("BarcodeScannerDialog", () => { const dlgScan = $dialog.get(0) as BarcodeScannerDialog; // Simulate the scan success - dlgScan.fireEvent("scan-success", { + dlgScan.fireDecoratorEvent("scan-success", { text: "mocked-scan-result", rawBytes: new Uint8Array(), }); @@ -301,7 +308,7 @@ describe("BarcodeScannerDialog", () => { const dlgScan = $dialog.get(0) as BarcodeScannerDialog; // Simulate the scan error - dlgScan.fireEvent("scan-error", { + dlgScan.fireDecoratorEvent("scan-error", { message: "mocked-scan-error", }); }); @@ -347,3 +354,116 @@ describe("BarcodeScannerDialog", () => { }); }); }); + +describe("BarcodeScannerDialog with Custom Slots", () => { + let openDialogHandler: EventListener | null; + let closeDialogHandler: EventListener | null; + + beforeEach(() => { + cy.mount(html` + +
+ My Custom Header +
+ +
+ Open Custom Scanner Dialog + `); + + cy.get("#dlgScanCustom").as("customDialog"); + cy.get("#btnScanCustom").as("customButton"); + cy.get("@customDialog") + .shadow() + .find(".ui5-barcode-scanner-dialog-video") + .as("videoElement"); + + cy.document().then(doc => { + const dlgScanCustom = doc.querySelector("#dlgScanCustom")!; + const btnScanCustom = doc.querySelector("#btnScanCustom")!; + const btnScanCustomClose = doc.querySelector("#customCloseBtn")!; + + btnScanCustom.addEventListener("click", () => { + dlgScanCustom.open = true; + }); + + btnScanCustomClose.addEventListener("click", () => { + dlgScanCustom.open = false; + }); + }); + }); + + afterEach(() => { + cy.document().then(doc => { + const btnScanCustom = doc.querySelector("#btnScanCustom"); + const btnScanCustomClose = doc.querySelector("#customCloseBtn"); + + if (btnScanCustom && openDialogHandler) { + btnScanCustom.removeEventListener("click", openDialogHandler); + } + openDialogHandler = null; + + if (btnScanCustomClose && closeDialogHandler) { + btnScanCustomClose.removeEventListener("click", closeDialogHandler); + } + + closeDialogHandler = null; + }); + }); + + it("renders custom header when provided", () => { + // Click the button to open the custom dialog + cy.get("@customButton").realClick(); + + // Wait for the video to be ready + cy.get("@videoElement").should($video => { + const videoEl = $video[0] as HTMLVideoElement; + expect(videoEl.readyState, "Video readyState should be >= 1").to.be.at.least(1); + }); + + // Assert that the dialog is open + cy.get("@customDialog") + .shadow() + .find("ui5-dialog") + .should("have.attr", "open"); + + // Verify that the custom header is rendered + cy.get("@customDialog") + .find("[slot=header] ui5-title") + .should("contain.text", "My Custom Header"); + }); + + it("renders custom footer and functions correctly", () => { + // Click the button to open the custom dialog + cy.get("@customButton").realClick(); + + // Wait for the video to be ready + cy.get("@videoElement").should($video => { + const videoEl = $video[0] as HTMLVideoElement; + expect(videoEl.readyState, "Video readyState should be >= 1").to.be.at.least(1); + }); + + // Assert that the dialog is open + cy.get("@customDialog") + .shadow() + .find("ui5-dialog") + .should("have.attr", "open"); + + // Verify that the custom footer is rendered + cy.get("@customDialog") + .find("[slot=footer] ui5-button") + .should("contain.text", "My Custom Close Button"); + + // Test that the custom button closes the dialog + cy.get("@customDialog") + .find("#customCloseBtn") + .realClick(); + + // Verify the dialog is closed + cy.get("@customDialog") + .shadow() + .find("ui5-dialog") + .should("not.have.attr", "open"); + }); +}); diff --git a/packages/fiori/src/BarcodeScannerDialog.hbs b/packages/fiori/src/BarcodeScannerDialog.hbs index a1c293ee9499..f9d3bf23c968 100644 --- a/packages/fiori/src/BarcodeScannerDialog.hbs +++ b/packages/fiori/src/BarcodeScannerDialog.hbs @@ -1,11 +1,12 @@ +
- +
\ No newline at end of file diff --git a/packages/fiori/src/BarcodeScannerDialog.ts b/packages/fiori/src/BarcodeScannerDialog.ts index 8b6f61a97363..63253391eef6 100644 --- a/packages/fiori/src/BarcodeScannerDialog.ts +++ b/packages/fiori/src/BarcodeScannerDialog.ts @@ -1,4 +1,5 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import Dialog from "@ui5/webcomponents/dist/Dialog.js"; @@ -131,6 +132,34 @@ type BarcodeScannerDialogScanErrorEventDetail = { }) class BarcodeScannerDialog extends UI5Element { + /** + * Defines the header HTML Element. + * + * **Note:** If `header` slot is provided, the labelling of the dialog is a responsibility of the application developer. + * `accessibleName` should be used. + * + * @public + * @since 2.4.0 + */ + @slot() + header!: Array; + + /** + * Defines the footer HTML Element. + * + * **Note:** When you provide custom content for the `footer` slot, the default close button is not rendered. + * This means you need to include your own mechanism within the custom `footer` to close the dialog, + * such as a button with an event listener that closes the dialog. + * + * **Note:** If the `footer` slot is not provided, a default footer with a close button is rendered automatically, + * allowing users to close the dialog without any additional implementation. + * + * @public + * @since 2.4.0 + */ + @slot() + footer!: Array; + /** * Indicates whether the dialog is open. * diff --git a/packages/fiori/src/themes/BarcodeScannerDialog.css b/packages/fiori/src/themes/BarcodeScannerDialog.css index a78877072a1e..6f58927cadb4 100644 --- a/packages/fiori/src/themes/BarcodeScannerDialog.css +++ b/packages/fiori/src/themes/BarcodeScannerDialog.css @@ -1,9 +1,12 @@ .ui5-barcode-scanner-dialog-root::part(content) { + display: flex; + flex-direction: column; padding: .4375rem; } .ui5-barcode-scanner-dialog-video-wrapper { position: relative; + min-height: 0; } /* video */ @@ -29,6 +32,7 @@ display: flex; justify-content: flex-end; width: 100%; + padding-top: .4375rem; } /* busy indicator */ diff --git a/packages/fiori/test/pages/BarcodeScannerDialog.html b/packages/fiori/test/pages/BarcodeScannerDialog.html index 9f759b7c0cba..26d2402e70f0 100644 --- a/packages/fiori/test/pages/BarcodeScannerDialog.html +++ b/packages/fiori/test/pages/BarcodeScannerDialog.html @@ -24,7 +24,18 @@ - Scan + +
+ My Custom Header +
+ +
+ + Open Default Dialog + Open Custom Dialog +
@@ -34,7 +45,10 @@ diff --git a/packages/fiori/test/pages/styles/BarcodeScannerDialog.css b/packages/fiori/test/pages/styles/BarcodeScannerDialog.css index 2b317116075f..07470d6ee251 100644 --- a/packages/fiori/test/pages/styles/BarcodeScannerDialog.css +++ b/packages/fiori/test/pages/styles/BarcodeScannerDialog.css @@ -1,3 +1,12 @@ .barcodescannerdialog1auto { - background-color: var(--sapBackgroundColor); + background-color: var(--sapBackgroundColor); } + +.custom-dialog-header { + text-align: center; + padding-bottom: .4375rem; +} + +.custom-dialog-footer { + height: auto; +} \ No newline at end of file