diff --git a/packages/fiori/README.md b/packages/fiori/README.md index 4ebb5385c0f8..857a86d5ab42 100644 --- a/packages/fiori/README.md +++ b/packages/fiori/README.md @@ -7,46 +7,49 @@ such as a common header (ShellBar). ## Provided components -| Web Component | Tag name | Module import | -|-------------------------------------------|----------------------------------|-----------------------------------------------------------------------| -| Barcode Scanner Dialog | `ui5-barcode-scanner-dialog` | `import "@ui5/webcomponents-fiori/dist/BarcodeScannerDialog.js";` | -| Dynamic Side Content | `ui5-dynamic-side-content` | `import "@ui5/webcomponents-fiori/dist/DynamicSideContent.js";` | -| Flexible Column Layout | `ui5-flexible-column-layout` | `import "@ui5/webcomponents-fiori/dist/FlexibleColumnLayout.js";` | -| Illustrated Message | `ui5-illustrated-message` | `import "@ui5/webcomponents-fiori/dist/IllustratedMessage.js";` | -| Media Gallery | `ui5-media-gallery` | `import "@ui5/webcomponents-fiori/dist/MediaGallery.js";` | -| Media Gallery Item | `ui5-media-gallery-item` | comes with `ui5-media-gallery` | -| Notification List | `ui5-notification-list` | `import "@ui5/webcomponents-fiori/dist/NotifcationList.js";` | -| Notification List Item | `ui5-li-notification` | `import "@ui5/webcomponents-fiori/dist/NotifcationListItem.js";` | -| Notification Group List Item | `ui5-li-notification-group` | `import "@ui5/webcomponents-fiori/dist/NotifcationListGroupItem.js";` | -| Notification Action | `ui5-notification-action` | `import "@ui5/webcomponents-fiori/dist/NotificationAction.js";` | -| Page | `ui5-page` | `import "@ui5/webcomponents-fiori/dist/Page.js";` | -| Product Switch | `ui5-product-switch` | `import "@ui5/webcomponents-fiori/dist/ProductSwitch.js";` | -| Product Switch Item | `ui5-product-switch-item` | `import "@ui5/webcomponents-fiori/dist/ProductSwitchItem.js";` | -| Shell Bar | `ui5-shellbar` | `import "@ui5/webcomponents-fiori/dist/ShellBar.js";` | -| Shell Bar Item | `ui5-shellbar-item` | `import "@ui5/webcomponents-fiori/dist/ShellBarItem.js";` | -| Side Navigation | `ui5-side-navigation` | `import "@ui5/webcomponents-fiori/dist/SideNavigation.js";` | -| Side Navigation Item | `ui5-side-navigation-item` | `import "@ui5/webcomponents-fiori/dist/SideNavigationItem.js";` | -| Side Navigation Sub Item | `ui5-side-navigation-sub-item` | `import "@ui5/webcomponents-fiori/dist/SideNavigationSubItem.js";` | -| Side Navigation Group | `ui5-side-navigation-group` | `import "@ui5/webcomponents-fiori/dist/SideNavigationGroup.js";` | -| Timeline | `ui5-timeline` | `import "@ui5/webcomponents-fiori/dist/Timeline.js";` | -| Timeline Item | `ui5-timeline-item` | comes with `ui5-timeline` | -| Timeline Group Item | `ui5-timeline-group-item` | `import "@ui5/webcomponents-fiori/dist/TimelineGroupItem.js";` | -| Upload Collection | `ui5-upload-collection` | `import "@ui5/webcomponents-fiori/dist/UploadCollection.js";` | -| Upload Collection Item | `ui5-upload-collection-item` | `import "@ui5/webcomponents-fiori/dist/UploadCollectionItem.js";` | -| User Menu | `ui5-user-menu` | `import "@ui5/webcomponents-fiori/dist/UserMenu.js";` | -| User Menu Account | `ui5-user-menu-account` | `import "@ui5/webcomponents-fiori/dist/UserMenuAccount.js";` | -| User Menu Item | `ui5-user-menu-item` | `import "@ui5/webcomponents-fiori/dist/UserMenuItem.js";` | -| User Menu Item Group | `ui5-user-menu-item-group` | `import "@ui5/webcomponents-fiori/dist/UserMenuItemGroup.js";` | -| User Settings Dialog | `ui5-user-settings-dialog` | `import "@ui5/webcomponents-fiori/dist/UserSettingsDialog.js";` | -| User Settings Item | `ui5-user-settings-item` | `import "@ui5/webcomponents-fiori/dist/UserSettingsItem.js";` | -| User Settings Account View | `ui5-user-settings-account-view` | `import "@ui5/webcomponents-fiori/dist/UserSettingsAccountView.js";` | -| User Settings View | `ui5-user-settings-view` | `import "@ui5/webcomponents-fiori/dist/UserSettingsView.js";` | -| View Settings Dialog | `ui5-view-settings-dialog` | `import "@ui5/webcomponents-fiori/dist/ViewSettingsDialog.js";` | -| View Settings Dialog - Sort Item | `ui5-sort-item` | `import "@ui5/webcomponents-fiori/dist/SortItem.js";` | -| View Settings Dialog - Filter Item | `ui5-filter-item` | `import "@ui5/webcomponents-fiori/dist/FilterItem.js";` | -| View Settings Dialog - Filter Item Option | `ui5-filter-item-option` | `import "@ui5/webcomponents-fiori/dist/FilterItemOption.js";` | -| Wizard | `ui5-wizard` | `import "@ui5/webcomponents-fiori/dist/Wizard.js";` | -| Wizard Step | `ui5-wizard-step` | comes with `ui5-wizard` | +| Web Component | Tag name | Module import | +|-------------------------------------------|-----------------------------------------|------------------------------------------------------------------------| +| Barcode Scanner Dialog | `ui5-barcode-scanner-dialog` | `import "@ui5/webcomponents-fiori/dist/BarcodeScannerDialog.js";` | +| Dynamic Side Content | `ui5-dynamic-side-content` | `import "@ui5/webcomponents-fiori/dist/DynamicSideContent.js";` | +| Flexible Column Layout | `ui5-flexible-column-layout` | `import "@ui5/webcomponents-fiori/dist/FlexibleColumnLayout.js";` | +| Illustrated Message | `ui5-illustrated-message` | `import "@ui5/webcomponents-fiori/dist/IllustratedMessage.js";` | +| Media Gallery | `ui5-media-gallery` | `import "@ui5/webcomponents-fiori/dist/MediaGallery.js";` | +| Media Gallery Item | `ui5-media-gallery-item` | comes with `ui5-media-gallery` | +| Notification List | `ui5-notification-list` | `import "@ui5/webcomponents-fiori/dist/NotifcationList.js";` | +| Notification List Item | `ui5-li-notification` | `import "@ui5/webcomponents-fiori/dist/NotifcationListItem.js";` | +| Notification Group List Item | `ui5-li-notification-group` | `import "@ui5/webcomponents-fiori/dist/NotifcationListGroupItem.js";` | +| Notification Action | `ui5-notification-action` | `import "@ui5/webcomponents-fiori/dist/NotificationAction.js";` | +| Page | `ui5-page` | `import "@ui5/webcomponents-fiori/dist/Page.js";` | +| Product Switch | `ui5-product-switch` | `import "@ui5/webcomponents-fiori/dist/ProductSwitch.js";` | +| Product Switch Item | `ui5-product-switch-item` | `import "@ui5/webcomponents-fiori/dist/ProductSwitchItem.js";` | +| Shell Bar | `ui5-shellbar` | `import "@ui5/webcomponents-fiori/dist/ShellBar.js";` | +| Shell Bar Item | `ui5-shellbar-item` | `import "@ui5/webcomponents-fiori/dist/ShellBarItem.js";` | +| Side Navigation | `ui5-side-navigation` | `import "@ui5/webcomponents-fiori/dist/SideNavigation.js";` | +| Side Navigation Item | `ui5-side-navigation-item` | `import "@ui5/webcomponents-fiori/dist/SideNavigationItem.js";` | +| Side Navigation Sub Item | `ui5-side-navigation-sub-item` | `import "@ui5/webcomponents-fiori/dist/SideNavigationSubItem.js";` | +| Side Navigation Group | `ui5-side-navigation-group` | `import "@ui5/webcomponents-fiori/dist/SideNavigationGroup.js";` | +| Timeline | `ui5-timeline` | `import "@ui5/webcomponents-fiori/dist/Timeline.js";` | +| Timeline Item | `ui5-timeline-item` | comes with `ui5-timeline` | +| Timeline Group Item | `ui5-timeline-group-item` | `import "@ui5/webcomponents-fiori/dist/TimelineGroupItem.js";` | +| Upload Collection | `ui5-upload-collection` | `import "@ui5/webcomponents-fiori/dist/UploadCollection.js";` | +| Upload Collection Item | `ui5-upload-collection-item` | `import "@ui5/webcomponents-fiori/dist/UploadCollectionItem.js";` | +| User Menu | `ui5-user-menu` | `import "@ui5/webcomponents-fiori/dist/UserMenu.js";` | +| User Menu Account | `ui5-user-menu-account` | `import "@ui5/webcomponents-fiori/dist/UserMenuAccount.js";` | +| User Menu Item | `ui5-user-menu-item` | `import "@ui5/webcomponents-fiori/dist/UserMenuItem.js";` | +| User Menu Item Group | `ui5-user-menu-item-group` | `import "@ui5/webcomponents-fiori/dist/UserMenuItemGroup.js";` | +| User Settings Dialog | `ui5-user-settings-dialog` | `import "@ui5/webcomponents-fiori/dist/UserSettingsDialog.js";` | +| User Settings Item | `ui5-user-settings-item` | `import "@ui5/webcomponents-fiori/dist/UserSettingsItem.js";` | +| User Settings Account View | `ui5-user-settings-account-view` | `import "@ui5/webcomponents-fiori/dist/UserSettingsAccountView.js";` | +| User Settings Appearance View | `ui5-user-settings-appearance-view` | `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceView.js";`| +| User Settings Appearance View Item | `ui5-user-settings-appearance-view-item`| `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewItem.js";`| +| User Settings Appearance View Group | `ui5-user-settings-appearance-view-group`| `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewGroup.js";`| +| User Settings View | `ui5-user-settings-view` | `import "@ui5/webcomponents-fiori/dist/UserSettingsView.js";` | +| View Settings Dialog | `ui5-view-settings-dialog` | `import "@ui5/webcomponents-fiori/dist/ViewSettingsDialog.js";` | +| View Settings Dialog - Sort Item | `ui5-sort-item` | `import "@ui5/webcomponents-fiori/dist/SortItem.js";` | +| View Settings Dialog - Filter Item | `ui5-filter-item` | `import "@ui5/webcomponents-fiori/dist/FilterItem.js";` | +| View Settings Dialog - Filter Item Option | `ui5-filter-item-option` | `import "@ui5/webcomponents-fiori/dist/FilterItemOption.js";` | +| Wizard | `ui5-wizard` | `import "@ui5/webcomponents-fiori/dist/Wizard.js";` | +| Wizard Step | `ui5-wizard-step` | comes with `ui5-wizard` | ## Provided assets diff --git a/packages/fiori/cypress/specs/UserSettingsDialog.cy.tsx b/packages/fiori/cypress/specs/UserSettingsDialog.cy.tsx index d135f665d782..957debd089d4 100644 --- a/packages/fiori/cypress/specs/UserSettingsDialog.cy.tsx +++ b/packages/fiori/cypress/specs/UserSettingsDialog.cy.tsx @@ -1,6 +1,9 @@ import UserSettingsItem from "../../src/UserSettingsItem.js"; import UserSettingsView from "../../src/UserSettingsView.js"; import UserSettingsAccountView from "../../src/UserSettingsAccountView.js"; +import UserSettingsAppearanceView from "../../src/UserSettingsAppearanceView.js"; +import UserSettingsAppearanceViewItem from "../../src/UserSettingsAppearanceViewItem.js"; +import UserSettingsAppearanceViewGroup from "../../src/UserSettingsAppearanceViewGroup.js"; import UserMenuAccount from "../../src/UserMenuAccount.js"; import UserSettingsDialog from "../../src/UserSettingsDialog.js"; import Button from "@ui5/webcomponents/dist/Button.js"; @@ -905,3 +908,435 @@ describe("User account view", () => { cy.get("@clicked").should("have.been.calledOnce"); }); }); + +describe("Appearance view", () => { + it("tests appearance view no config", () => { + cy.mount( + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").should("exist"); + cy.get("@settings").find("[ui5-user-settings-item]").as("settingItem"); + cy.get("@settingItem").should("exist"); + cy.get("@settingItem").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").should("exist"); + }); + + it("tests appearance view text", () => { + cy.mount( + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").should("exist"); + cy.get("@settings").find("[ui5-user-settings-item]").as("settingItem"); + cy.get("@settingItem").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").should("exist"); + cy.get("@appearanceView").should("have.attr", "text", "Themes"); + cy.get("@settingItem").shadow().find("[ui5-tabcontainer]").should("not.exist"); + }); + + it("tests appearance view items exist", () => { + cy.mount( + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").should("exist"); + cy.get("@settings").find("[ui5-user-settings-item]").as("settingItem"); + cy.get("@settingItem").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").should("exist"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("items"); + cy.get("@items").should("have.length", 2); + }); + + it("tests appearance view item properties", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").should("exist"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").should("exist"); + cy.get("@item").should("have.attr", "text", "SAP Morning Horizon"); + cy.get("@item").should("have.attr", "icon", "palette"); + cy.get("@item").should("have.attr", "color-scheme", "Accent5"); + cy.get("@item").then($item => { + const item = $item.get(0) as any; + expect(item.itemKey).to.equal("sap_horizon"); + expect(item.text).to.equal("SAP Morning Horizon"); + expect(item.icon).to.equal("palette"); + expect(item.colorScheme).to.equal("Accent5"); + }); + }); + + it("tests appearance view item default icon", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").then($item => { + const item = $item.get(0) as any; + expect(item.icon).to.equal("product"); + }); + }); + + it("tests appearance view item default color-scheme", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").then($item => { + const item = $item.get(0) as any; + expect(item.colorScheme).to.equal("Accent7"); + }); + }); + + it("tests appearance view item custom color-scheme", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").shadow().find("[ui5-avatar]").as("avatar"); + cy.get("@avatar").should("have.attr", "color-scheme", "Accent3"); + }); + + it("tests appearance view item avatar", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").shadow().find("[ui5-avatar]").as("avatar"); + cy.get("@avatar").should("exist"); + cy.get("@avatar").should("have.length", 2); // Two avatars: one for cozy, one for compact mode + cy.get("@avatar").first().should("have.attr", "icon", "palette"); + cy.get("@avatar").first().should("have.attr", "shape", "Square"); + cy.get("@avatar").first().should("have.attr", "color-scheme", "Accent7"); + }); + + it("tests appearance view item text displayed", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + cy.get("@item").shadow().find(".item-title").as("title"); + cy.get("@title").should("exist"); + cy.get("@title").contains("SAP Morning Horizon"); + }); + + it("tests appearance view with groups", () => { + cy.mount( + + + + + + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-group]").should("have.length", 2); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").should("have.length", 3); + }); + + it("tests appearance view group header-text", () => { + cy.mount( + + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-group]").as("group"); + cy.get("@group").should("have.attr", "header-text", "SAP Horizon"); + cy.get("@group").shadow().find("[ui5-li-group-header]").contains("SAP Horizon"); + }); + + it("tests appearance view mixed grouped and ungrouped items", () => { + cy.mount( + + + + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-group]").should("have.length", 1); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").should("have.length", 2); + }); + + it("tests selection-change event", () => { + cy.mount( + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + + cy.get("@appearanceView").then($view => { + $view.get(0).addEventListener("selection-change", cy.stub().as("selectionChange")); + }); + + cy.get("@appearanceView") + .find("[ui5-user-settings-appearance-view-item]") + .first() + .click(); + + cy.get("@selectionChange").should("have.been.calledOnce"); + }); + + it("tests selection-change event detail contains correct item", () => { + cy.mount( + + + + + + + ); + + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + + cy.get("@appearanceView").then($view => { + $view.get(0).addEventListener("selection-change", cy.stub().as("selectionChangeWithDetail")); + }); + + cy.get("@appearanceView") + .find("[ui5-user-settings-appearance-view-item]") + .eq(1) + .click(); + + cy.get("@selectionChangeWithDetail").should("have.been.calledOnce"); + cy.get("@selectionChangeWithDetail").then((stub: any) => { + const call = stub.getCall(0); + expect(call.args[0].detail.item.itemKey).to.equal("sap_horizon_dark"); + expect(call.args[0].detail.item.text).to.equal("SAP Evening Horizon"); + }); + }); + + it("tests item selection state", () => { + cy.mount( + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").eq(1).then($item => { + const item = $item.get(0) as any; + expect(item.selected).to.be.true; + }); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").eq(0).then($item => { + const item = $item.get(0) as any; + expect(item.selected).to.be.false; + }); + }); + + it("tests item clickable and hover states", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").as("item"); + + cy.get("@item").then($item => { + const item = $item.get(0) as any; + expect(item.type).to.equal("Active"); + }); + + cy.get("@item").shadow().find(".ui5-li-root").should("exist"); + }); + + it("tests additionalContent slot", () => { + cy.mount( + + + +
Additional content here
+
+
+
); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").shadow().find("slot[name='additionalContent']").should("exist"); + }); + + it("tests additionalContent slot renders above items", () => { + cy.mount( + + + +
Additional content above items
+
+
+
); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + + // Get positions of additionalContent and the list + cy.get("@appearanceView").find("#additional-content-test").then($additional => { + const additionalTop = $additional.get(0).getBoundingClientRect().top; + + cy.get("@appearanceView").shadow().find("[ui5-list]").then($list => { + const listTop = $list.get(0).getBoundingClientRect().top; + + // Additional content should be positioned above the list + expect(additionalTop).to.be.lessThan(listTop); + }); + }); + }); + + it("tests selection-change event can be prevented", () => { + cy.mount( + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + + cy.get("@appearanceView").then($view => { + $view.get(0).addEventListener("selection-change", (e: Event) => e.preventDefault()); + $view.get(0).addEventListener("selection-change", cy.stub().as("selectionChange")); + }); + + cy.get("@appearanceView") + .find("[ui5-user-settings-appearance-view-item]") + .eq(1) + .click(); + + cy.get("@selectionChange").should("have.been.calledOnce"); + + // First item should still be selected because event was prevented + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").eq(0).then($item => { + const item = $item.get(0) as any; + expect(item.selected).to.be.true; + }); + + // Second item should not be selected + cy.get("@appearanceView").find("[ui5-user-settings-appearance-view-item]").eq(1).then($item => { + const item = $item.get(0) as any; + expect(item.selected).to.be.false; + }); + }); + + it("tests items in groups fire selection-change event", () => { + cy.mount( + + + + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + + cy.get("@appearanceView").then($view => { + $view.get(0).addEventListener("selection-change", cy.stub().as("selectionChange")); + }); + + cy.get("@appearanceView") + .find("[ui5-user-settings-appearance-view-group]") + .find("[ui5-user-settings-appearance-view-item]") + .first() + .click(); + + cy.get("@selectionChange").should("have.been.calledOnce"); + cy.get("@selectionChange").then((stub: any) => { + const call = stub.getCall(0); + expect(call.args[0].detail.item.itemKey).to.equal("sap_horizon"); + }); + }); + + it("tests appearance view list renders correctly", () => { + cy.mount( + + + + + + ); + cy.get("[ui5-user-settings-dialog]").as("settings"); + cy.get("@settings").find("[ui5-user-settings-appearance-view]").as("appearanceView"); + cy.get("@appearanceView").shadow().find("[ui5-list]").as("list"); + cy.get("@list").should("exist"); + cy.get("@list").should("have.class", "user-settings-appearance-view-list"); + }); +}); diff --git a/packages/fiori/src/UserSettingsAppearanceView.ts b/packages/fiori/src/UserSettingsAppearanceView.ts new file mode 100644 index 000000000000..13fa3888ca56 --- /dev/null +++ b/packages/fiori/src/UserSettingsAppearanceView.ts @@ -0,0 +1,116 @@ +import UserSettingsView from "./UserSettingsView.js"; +import UserSettingsAppearanceViewTemplate from "./UserSettingsAppearanceViewTemplate.js"; +import UserSettingViewCss from "./generated/themes/UserSettingsView.css.js"; +import type UserSettingsAppearanceViewItem from "./UserSettingsAppearanceViewItem.js"; +import type UserSettingsAppearanceViewGroup from "./UserSettingsAppearanceViewGroup.js"; +import type { ListItemClickEventDetail } from "@ui5/webcomponents/dist/List.js"; +import type ListItemBase from "@ui5/webcomponents/dist/ListItemBase.js"; + +import { + customElement, slot, eventStrict as event, +} from "@ui5/webcomponents-base/dist/decorators.js"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; + +type UserSettingsAppearanceViewItemSelectEventDetail = { + item: UserSettingsAppearanceViewItem; +} + +@customElement({ + tag: "ui5-user-settings-appearance-view", + renderer: jsxRenderer, + template: UserSettingsAppearanceViewTemplate, + styles: [UserSettingViewCss], +}) + +/** + * Fired when an item is selected. + * @param {UserSettingsAppearanceViewItem} item The selected `user settings appearance view item`. + * @public + */ +@event("selection-change", { + cancelable: true, +}) + +/** + * @class + * ### Overview + * + * The `ui5-user-settings-appearance-view` represents a view displayed in the `ui5-user-settings-item`. + * + * ### ES6 Module Import + * `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceView.js";` + * + * @constructor + * @extends UserSettingsView + * @experimental + * @public + * @since 2.17.0 + */ +class UserSettingsAppearanceView extends UserSettingsView { + eventDetails!: { + "selection-change": UserSettingsAppearanceViewItemSelectEventDetail; + } + + /** + * Defines the items of the component. + * + * @public + */ + @slot({ + type: HTMLElement, + "default": true, + invalidateOnChildChange: true, + }) + items!: Array; + + /** + * Defines additional content displayed below the items list. + * + * @public + */ + @slot({ + type: HTMLElement, + }) + additionalContent?: Array; + + _getAllItems(): Array { + const allItems: Array = []; + + this.items.forEach(item => { + if (item.tagName === "UI5-USER-SETTINGS-APPEARANCE-VIEW-GROUP") { + const group = item as UserSettingsAppearanceViewGroup; + const groupItems = Array.from(group.children).filter( + child => child.tagName === "UI5-USER-SETTINGS-APPEARANCE-VIEW-ITEM", + ) as Array; + allItems.push(...groupItems); + } else if (item.tagName === "UI5-USER-SETTINGS-APPEARANCE-VIEW-ITEM") { + allItems.push(item as UserSettingsAppearanceViewItem); + } + }); + + return allItems; + } + + _handleItemClick = (e: CustomEvent) => { + const listItem = e.detail.item as ListItemBase & { associatedSettingItem?: UserSettingsAppearanceViewItem }; + if (listItem.tagName === "UI5-USER-SETTINGS-APPEARANCE-VIEW-ITEM") { + const item = listItem as UserSettingsAppearanceViewItem; + const eventPrevented = !this.fireDecoratorEvent("selection-change", { + item, + }); + + if (!eventPrevented) { + this._getAllItems().forEach(viewItem => { + viewItem.selected = false; + }); + item.selected = true; + } + } + }; +} + +UserSettingsAppearanceView.define(); +export default UserSettingsAppearanceView; +export type { + UserSettingsAppearanceViewItemSelectEventDetail, +}; diff --git a/packages/fiori/src/UserSettingsAppearanceViewGroup.ts b/packages/fiori/src/UserSettingsAppearanceViewGroup.ts new file mode 100644 index 000000000000..0c81047d4c45 --- /dev/null +++ b/packages/fiori/src/UserSettingsAppearanceViewGroup.ts @@ -0,0 +1,40 @@ +import ListItemGroup from "@ui5/webcomponents/dist/ListItemGroup.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import type UserSettingsAppearanceViewItem from "./UserSettingsAppearanceViewItem.js"; + +/** + * @class + * ### Overview + * + * The `ui5-user-settings-appearance-view-group` is a special list item group used to group appearance view items. + * + * This is the item to use inside a `ui5-user-settings-appearance-view`. + * + * ### ES6 Module Import + * `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewGroup.js";` + * + * @constructor + * @extends ListItemGroup + * @experimental + * @public + * @since 2.17.0 + */ +@customElement({ + tag: "ui5-user-settings-appearance-view-group", +}) +class UserSettingsAppearanceViewGroup extends ListItemGroup { + /** + * Defines the items of the ui5-user-settings-appearance-view-group. + * @public + */ + @slot({ + "default": true, + invalidateOnChildChange: true, + type: HTMLElement, + }) + declare items: Array; +} + +UserSettingsAppearanceViewGroup.define(); +export default UserSettingsAppearanceViewGroup; diff --git a/packages/fiori/src/UserSettingsAppearanceViewItem.ts b/packages/fiori/src/UserSettingsAppearanceViewItem.ts new file mode 100644 index 000000000000..ec746d63b86a --- /dev/null +++ b/packages/fiori/src/UserSettingsAppearanceViewItem.ts @@ -0,0 +1,73 @@ +import UserSettingViewCss from "./generated/themes/UserSettingsView.css.js"; +import UserSettingsAppearanceViewItemTemplate from "./UserSettingsAppearanceViewItemTemplate.js"; +import UserSettingsAppearanceViewItemCss from "./generated/themes/UserSettingsAppearanceViewItem.css.js"; +import { + customElement, property, +} from "@ui5/webcomponents-base/dist/decorators.js"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import ListItemCustom from "@ui5/webcomponents/dist/ListItemCustom.js"; + +// Import default icon used by appearance view items +import "@ui5/webcomponents-icons/dist/product.js"; + +@customElement({ + tag: "ui5-user-settings-appearance-view-item", + renderer: jsxRenderer, + template: UserSettingsAppearanceViewItemTemplate, + styles: [ListItemCustom.styles, UserSettingViewCss, UserSettingsAppearanceViewItemCss], +}) + +/** + * @class + * ### Overview + * + * The `ui5-user-settings-appearance-view-item` represents a theme/appearance option item + * within the `ui5-user-settings-appearance-view`. + * + * It displays a theme with an avatar icon, text label, and can be selected. + * + * ### ES6 Module Import + * `import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewItem.js";` + * + * @constructor + * @extends ListItemCustom + * @experimental + * @public + * @since 2.17.0 + */ +class UserSettingsAppearanceViewItem extends ListItemCustom { + /** + * Defines the unique identifier of the item. + * @default "" + * @public + */ + @property() + itemKey = ""; + + /** + * Defines the text label displayed for the appearance item. + * @default "" + * @public + */ + @property() + text = ""; + + /** + * Defines the icon of the appearance item. + * @default "product" + * @public + */ + @property() + icon = "product"; + + /** + * Defines the color scheme of the avatar. + * @default "Accent7" + * @public + */ + @property() + colorScheme = "Accent7"; +} + +UserSettingsAppearanceViewItem.define(); +export default UserSettingsAppearanceViewItem; diff --git a/packages/fiori/src/UserSettingsAppearanceViewItemTemplate.tsx b/packages/fiori/src/UserSettingsAppearanceViewItemTemplate.tsx new file mode 100644 index 000000000000..8c471d0ca926 --- /dev/null +++ b/packages/fiori/src/UserSettingsAppearanceViewItemTemplate.tsx @@ -0,0 +1,25 @@ +import type UserSettingsAppearanceViewItem from "./UserSettingsAppearanceViewItem.js"; +import ListItemCustomTemplate from "@ui5/webcomponents/dist/ListItemCustomTemplate.js"; +import Avatar from "@ui5/webcomponents/dist/Avatar.js"; +import AvatarSize from "@ui5/webcomponents/dist/types/AvatarSize.js"; + +export default function UserSettingsAppearanceViewItemTemplate(this: UserSettingsAppearanceViewItem) { + return ListItemCustomTemplate.call(this, { + listItemContent: listItemContent.bind(this), + }); +} + +function listItemContent(this: UserSettingsAppearanceViewItem) { + return ( +
+
+ {/* Two avatars are rendered for different content density modes - CSS controls visibility */} + + +
+ {this.text} +
+
+
+ ); +} diff --git a/packages/fiori/src/UserSettingsAppearanceViewTemplate.tsx b/packages/fiori/src/UserSettingsAppearanceViewTemplate.tsx new file mode 100644 index 000000000000..3a1ae912ee0a --- /dev/null +++ b/packages/fiori/src/UserSettingsAppearanceViewTemplate.tsx @@ -0,0 +1,15 @@ +import type UserSettingsAppearanceView from "./UserSettingsAppearanceView.js"; +import List from "@ui5/webcomponents/dist/List.js"; + +export default function UserSettingsAppearanceViewTemplate(this: UserSettingsAppearanceView) { + return ( + + ); +} diff --git a/packages/fiori/src/bundle.esm.ts b/packages/fiori/src/bundle.esm.ts index 1d4c9bacc443..9ccfbb26b1af 100644 --- a/packages/fiori/src/bundle.esm.ts +++ b/packages/fiori/src/bundle.esm.ts @@ -51,6 +51,9 @@ import UserSettingsItem from "./UserSettingsItem.js"; import SettingsDialog from "./UserSettingsDialog.js"; import UserSettingsView from "./UserSettingsView.js"; import UserSettingsAccountView from "./UserSettingsAccountView.js"; +import UserSettingsAppearanceView from "./UserSettingsAppearanceView.js"; +import UserSettingsAppearanceViewItem from "./UserSettingsAppearanceViewItem.js"; +import UserSettingsAppearanceViewGroup from "./UserSettingsAppearanceViewGroup.js"; import Timeline from "./Timeline.js"; import TimelineGroupItem from "./TimelineGroupItem.js"; import NavigationLayout from "./NavigationLayout.js"; diff --git a/packages/fiori/src/themes/UserSettingsAppearanceViewItem.css b/packages/fiori/src/themes/UserSettingsAppearanceViewItem.css new file mode 100644 index 000000000000..ccb161523b74 --- /dev/null +++ b/packages/fiori/src/themes/UserSettingsAppearanceViewItem.css @@ -0,0 +1,65 @@ +.list-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 0; + width: 100%; +} + +.item-left { + display: flex; + align-items: center; + gap: 0.75rem; + flex: 1; +} + +/* Default: show cozy avatar, hide compact avatar */ +.avatar-cozy { + display: inline-block; +} + +.avatar-compact { + display: none; +} + +/* In compact mode: hide cozy avatar, show compact avatar */ +:host-context(.sapUiSizeCompact) .avatar-cozy, +:host-context(.ui5-content-density-compact) .avatar-cozy, +:host-context([data-ui5-compact-size]) .avatar-cozy { + display: none; +} + +:host-context(.sapUiSizeCompact) .avatar-compact, +:host-context(.ui5-content-density-compact) .avatar-compact, +:host-context([data-ui5-compact-size]) .avatar-compact { + display: inline-block; +} + +.item-texts { + display: flex; + flex-direction: column; +} + +.item-title { + font-weight: 600; + font-size: var(--sapFontSize); + color: var(--sapList_TextColor); + margin: 0.5rem; +} + +:host-context(.sapUiSizeCompact) .item-title, +:host-context([data-ui5-compact-size]) .item-title { + font-size: var(--sapFontLargeSize); +} + +.item-subtitle { + color: var(--sapContent_LabelColor); + font-size: var(--sapFontSize); + margin: 0.5rem; +} + +.item-right { + display: flex; + align-items: center; + gap: 0.5rem; +} diff --git a/packages/fiori/test/pages/UserSettingsDialog.html b/packages/fiori/test/pages/UserSettingsDialog.html index d31175449beb..54d89143dfed 100644 --- a/packages/fiori/test/pages/UserSettingsDialog.html +++ b/packages/fiori/test/pages/UserSettingsDialog.html @@ -11,6 +11,20 @@ + @@ -58,25 +72,34 @@ - - - SAP Morning Horizon - SAP Evening Horizon - SAP High Contrast Black (SAP Horizon) - SAP High Contrast White (SAP Horizon) - - Save - Changes applied. - - - - - + +
+ + +
+ + + + + + + + + + + + + + + +
+
@@ -509,7 +532,6 @@ const toast = [...document.getElementsByTagName("ui5-toast")][0]; const toastReset = document.getElementById("toastReset"); const toastResetAll = document.getElementById("toastResetAll"); - const themeSave =document.getElementById("themeSave"); const notificationsItem = document.getElementById("notifications"); const notificationNavIcon = document.querySelectorAll(".nav-icon"); const notificationSwitch = document.querySelectorAll(".switch"); @@ -582,10 +604,6 @@ mobileSecondPage.text = "Android"; }); - themeSave.addEventListener("click", function () { - toast.open = true; - }); - resetPersonalization.addEventListener("click", function () { toastReset.open = true; }); @@ -687,6 +705,17 @@ notificationSecondPage.text = title; }); }); + + // Theme switching functionality + const appearanceView = document.querySelector("ui5-user-settings-appearance-view"); + + appearanceView.addEventListener("selection-change", (e) => { + const selectedItem = e.detail.item; + + if (selectedItem?.itemKey) { + window["sap-ui-webcomponents-bundle"].configuration.setTheme(selectedItem.itemKey); + } + }); \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/main.js b/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/main.js index 4f9fc8eb4596..f73005be53b0 100644 --- a/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/main.js +++ b/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/main.js @@ -1,7 +1,11 @@ import "@ui5/webcomponents-fiori/dist/UserSettingsAccountView.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceView.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewItem.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewGroup.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsView.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsItem.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsDialog.js"; +import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme.js"; import "@ui5/webcomponents-fiori/dist/ShellBar.js"; import "@ui5/webcomponents-fiori/dist/ShellBarBranding.js"; @@ -19,6 +23,7 @@ import "@ui5/webcomponents/dist/ComboBoxItem.js"; import "@ui5/webcomponents/dist/RadioButton.js"; import "@ui5/webcomponents/dist/Text.js"; import "@ui5/webcomponents/dist/CheckBox.js"; +import "@ui5/webcomponents/dist/Switch.js"; import "@ui5/webcomponents/dist/Toast.js"; import "@ui5/webcomponents/dist/List.js"; import "@ui5/webcomponents/dist/ListItemStandard.js"; @@ -38,6 +43,8 @@ const settingsDialog = document.getElementById("settings"); const settingsDialogItems = [...document.getElementsByTagName("ui5-user-settings-item")]; const account = document.getElementById("account"); const resetAllButton = document.getElementById("reset-all-button"); +// Theme change +const appearanceView = document.querySelector("ui5-user-settings-appearance-view"); //Language and Region const languageRegion = document.getElementById("language-region-container"); const language = document.getElementById("language"); @@ -51,10 +58,8 @@ const mobile2Button = document.getElementById("mobile2-button"); const resetAll = document.getElementById("resetAll"); const resetPersonalization = document.getElementById("resetPersonalization"); -const toast = [...document.getElementsByTagName("ui5-toast")][0]; const toastReset = document.getElementById("toastReset"); const toastResetAll = document.getElementById("toastResetAll"); -const themeSave =document.getElementById("themeSave"); shellbar.addEventListener("ui5-profile-click", (event) => { console.log(" menuShellBar ui5-profile-click") @@ -94,6 +99,15 @@ language.addEventListener("selection-change", function (event) { additionalDialog.open = true; }); +// Theme change +appearanceView.addEventListener("selection-change", (e) => { + const selectedItem = e.detail.item; + + if (selectedItem?.itemKey) { + setTheme(selectedItem.itemKey); + } +}); + dialogClosers.forEach(btn => { btn.addEventListener("click", () => { additionalDialog.open = false; @@ -116,11 +130,6 @@ mobile2Button.addEventListener("click", function () { mobileSecondPage.text = "Android"; }); - -themeSave.addEventListener("click", function () { - toast.open = true; -}); - resetPersonalization.addEventListener("click", function () { toastReset.open = true; }); diff --git a/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/sample.html b/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/sample.html index c7787266b767..b5bae3a24c71 100644 --- a/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/sample.html +++ b/packages/website/docs/_samples/fiori/UserSettingsDialog/Basic/sample.html @@ -12,6 +12,20 @@ } + @@ -57,25 +71,30 @@ - - - SAP Morning Horizon - SAP Evening Horizon - SAP High Contrast Black (SAP Horizon) - SAP High Contrast White (SAP Horizon) - - Save - Changes applied. - - - - - + +
+ + +
+ + + + + + + + + + + + +
diff --git a/packages/website/docs/_samples/patterns/UXCIntegration/Basic/main.js b/packages/website/docs/_samples/patterns/UXCIntegration/Basic/main.js index 46eb7fef6e0d..64b547fbb485 100644 --- a/packages/website/docs/_samples/patterns/UXCIntegration/Basic/main.js +++ b/packages/website/docs/_samples/patterns/UXCIntegration/Basic/main.js @@ -18,6 +18,8 @@ import "@ui5/webcomponents/dist/ComboBoxItem.js"; import "@ui5/webcomponents/dist/RadioButton.js"; import "@ui5/webcomponents/dist/CheckBox.js"; import "@ui5/webcomponents/dist/Toast.js"; +import "@ui5/webcomponents/dist/Switch.js"; + import "@ui5/webcomponents-fiori/dist/ShellBar.js"; import "@ui5/webcomponents-fiori/dist/ShellBarBranding.js"; @@ -46,9 +48,13 @@ import "@ui5/webcomponents-fiori/dist/UserMenuAccount.js"; import "@ui5/webcomponents-fiori/dist/UserMenuItem.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsAccountView.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceView.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewItem.js"; +import "@ui5/webcomponents-fiori/dist/UserSettingsAppearanceViewGroup.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsView.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsItem.js"; import "@ui5/webcomponents-fiori/dist/UserSettingsDialog.js"; +import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme.js"; import "@ui5/webcomponents-icons/dist/globe.js"; import "@ui5/webcomponents-icons/dist/collaborate.js"; @@ -77,6 +83,7 @@ import "@ui5/webcomponents-icons/dist/action-settings.js"; import "@ui5/webcomponents-icons/dist/user-settings.js"; import "@ui5/webcomponents-icons/dist/person-placeholder.js"; import "@ui5/webcomponents-icons/dist/palette.js"; +import "@ui5/webcomponents-icons/dist/product.js"; import "@ui5/webcomponents-icons/dist/iphone.js"; import "@ui5/webcomponents-icons/dist/qr-code.js"; import "@ui5/webcomponents-icons/dist/bell.js"; @@ -275,6 +282,9 @@ userMenu.addEventListener("sign-out-click", function (event) { const settingsDialog = document.getElementById("settings"); const settingsDialogItems = [...document.getElementsByTagName("ui5-user-settings-item")]; +// Theme change +const appearanceView = document.querySelector("ui5-user-settings-appearance-view"); + //Language and Region const languageRegion = document.getElementById("language-region-container"); const language = document.getElementById("language"); @@ -288,11 +298,8 @@ const mobile2Button = document.getElementById("mobile2-button"); const resetAllButton = document.getElementById("reset-all-button"); const resetAll = document.getElementById("resetAll"); const resetPersonalization = document.getElementById("resetPersonalization"); -const toast = document.getElementById("toastThemeSave"); const toastReset = document.getElementById("toastReset"); const toastResetAll = document.getElementById("toastResetAll"); -const themeSave =document.getElementById("themeSave"); - //Language and Region language.addEventListener("selection-change", function (event) { @@ -311,6 +318,15 @@ regionSettings.forEach((settingsItem) => { }); }); +// Theme change +appearanceView.addEventListener("selection-change", (e) => { + const selectedItem = e.detail.item; + + if (selectedItem?.itemKey) { + setTheme(selectedItem.itemKey); + } +}); + mobile1Button.addEventListener("click", function () { mobileSecondPage.selected = true; mobileSecondPage.text = "iOS"; @@ -325,10 +341,6 @@ resetAllButton.addEventListener("click", function () { additionalDialog.open = true; }); -themeSave.addEventListener("click", function () { - toast.open = true; -}); - resetPersonalization.addEventListener("click", function () { toastReset.open = true; }); diff --git a/packages/website/docs/_samples/patterns/UXCIntegration/Basic/sample.html b/packages/website/docs/_samples/patterns/UXCIntegration/Basic/sample.html index 7b2b65588f17..2466dbadc139 100644 --- a/packages/website/docs/_samples/patterns/UXCIntegration/Basic/sample.html +++ b/packages/website/docs/_samples/patterns/UXCIntegration/Basic/sample.html @@ -7,6 +7,20 @@ Sample + @@ -288,25 +302,30 @@ - - - SAP Morning Horizon - SAP Evening Horizon - SAP High Contrast Black (SAP Horizon) - SAP High Contrast White (SAP Horizon) - - Save - Changes applied. - - - - - + +
+ + +
+ + + + + + + + + + + + +