Skip to content

Commit

Permalink
feat(ui5-barcode-scanner-dialog): added support for custom header and…
Browse files Browse the repository at this point in the history
… footer slots

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
  • Loading branch information
kgogov committed Oct 21, 2024
1 parent 56ad311 commit 56c252c
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 8 deletions.
128 changes: 124 additions & 4 deletions packages/fiori/cypress/specs/BarcodeScannerDialog.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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")!;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
});
Expand Down Expand Up @@ -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",
});
});
Expand Down Expand Up @@ -347,3 +354,116 @@ describe("BarcodeScannerDialog", () => {
});
});
});

describe("BarcodeScannerDialog with Custom Slots", () => {
let openDialogHandler: EventListener | null;
let closeDialogHandler: EventListener | null;

beforeEach(() => {
cy.mount(html`
<ui5-barcode-scanner-dialog id="dlgScanCustom">
<div slot="header" class="custom-dialog-header">
<ui5-title level="H2">My Custom Header</ui5-title>
</div>
<div slot="footer" class="custom-dialog-footer">
<ui5-button id="customCloseBtn">My Custom Button</ui5-button>
</div>
</ui5-barcode-scanner-dialog>
<ui5-button id="btnScanCustom" icon="camera">Open Custom Scanner Dialog</ui5-button>
`);

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<BarcodeScannerDialog>("#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 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");
});
});
5 changes: 3 additions & 2 deletions packages/fiori/src/BarcodeScannerDialog.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<ui5-dialog stretch class="ui5-barcode-scanner-dialog-root" .open="{{_open}}" @ui5-close="{{_closeDialog}}">
<slot name="header"></slot>
<div class="ui5-barcode-scanner-dialog-video-wrapper">
<video autoplay playsinline muted class="ui5-barcode-scanner-dialog-video"></video>
<canvas class="ui5-barcode-scanner-dialog-overlay"></canvas>
</div>
<div slot="footer" class="ui5-barcode-scanner-dialog-footer">
<slot name="footer" class="ui5-barcode-scanner-dialog-footer">
<ui5-button design="Transparent" @click={{_closeDialog}}>{{_cancelButtonText}}</ui5-button>
</div>
</slot>
</ui5-dialog>
<ui5-busy-indicator class="ui5-barcode-scanner-dialog-busy" ?active={{loading}} size="L"
text="{{_busyIndicatorText}}"></ui5-busy-indicator>
29 changes: 29 additions & 0 deletions packages/fiori/src/BarcodeScannerDialog.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<HTMLElement>;

/**
* 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 will be rendered automatically,
* allowing users to close the dialog without any additional implementation.
*
* @public
* @since 2.4.0
*/
@slot()
footer!: Array<HTMLElement>;

/**
* Indicates whether the dialog is open.
*
Expand Down
4 changes: 4 additions & 0 deletions packages/fiori/src/themes/BarcodeScannerDialog.css
Original file line number Diff line number Diff line change
@@ -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 */
Expand All @@ -29,6 +32,7 @@
display: flex;
justify-content: flex-end;
width: 100%;
padding-top: .4375rem;
}

/* busy indicator */
Expand Down
39 changes: 38 additions & 1 deletion packages/fiori/test/pages/BarcodeScannerDialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@

<ui5-barcode-scanner-dialog id="dlgScan"></ui5-barcode-scanner-dialog>

<ui5-button id="btnScan" icon="camera" tooltip="Start Camera">Scan</ui5-button>
<ui5-barcode-scanner-dialog id="dlgScanCustom">
<div slot="header" class="custom-dialog-header">
<ui5-title level="H2">My Custom Header</ui5-title>
</div>
<div slot="footer" class="custom-dialog-footer">
<ui5-button id="btnCloseDialogCustom" icon="da" design="Attention">My Custom Button</ui5-button>
</div>
</ui5-barcode-scanner-dialog>

<ui5-button id="btnScan" icon="camera" tooltip="Start Camera">Open Default Dialog</ui5-button>
<ui5-button id="btnScanCustom" icon="camera" tooltip="Start Camera Custom" design="Emphasized">Open Custom Dialog</ui5-button>

<div>
<ui5-label id="scanResult"></ui5-label>
<ui5-label id="scanError"></ui5-label>
Expand All @@ -34,7 +45,10 @@

<script>
const dlgScan = document.getElementById("dlgScan");
const dlgScanCustom = document.getElementById("dlgScanCustom");
const btnScan = document.getElementById("btnScan");
const btnScanCustom = document.getElementById("btnScanCustom");
const btnCloseDialogCustom = document.getElementById("btnCloseDialogCustom");
const scanResult = document.getElementById("scanResult");
const scanError = document.getElementById("scanError");

Expand All @@ -43,19 +57,42 @@
document.querySelector(".open-state").textContent = `${dlgScan.open}`;
});

btnScanCustom.addEventListener("click", (event) => {
dlgScanCustom.open = true;
document.querySelector(".open-state").textContent = `${dlgScanCustom.open}`;
});

btnCloseDialogCustom.addEventListener("click", (event) => {
dlgScanCustom.open = false;
});

dlgScan.addEventListener("ui5-scan-success", (event) => {
scanResult.innerHTML = event.detail.text;
dlgScan.open = false;
});

dlgScanCustom.addEventListener("ui5-scan-success", (event) => {
scanResult.innerHTML = event.detail.text;
dlgScanCustom.open = false;
});

dlgScan.addEventListener("ui5-scan-error", (event) => {
scanError.innerHTML = event.detail.message;
dlgScan.open = false;
});

dlgScanCustom.addEventListener("ui5-scan-error", (event) => {
scanError.innerHTML = event.detail.message;
dlgScanCustom.open = false;
});

dlgScan.addEventListener("ui5-close", (event) => {
document.querySelector(".open-state").textContent = `${dlgScan.open}`;
});

dlgScanCustom.addEventListener("ui5-close", (event) => {
document.querySelector(".open-state").textContent = `${dlgScanCustom.open}`;
});
</script>
</body>
</html>
11 changes: 10 additions & 1 deletion packages/fiori/test/pages/styles/BarcodeScannerDialog.css
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 56c252c

Please sign in to comment.