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. - - - - - + + + + Optimize for Touch Input + + + Increases the size and spacing of controls to allow you to interact with them more easily using your fingertip. This is useful for hybrid devices that combine touch and mouse events. - - - + + + + + + + + + + + + + + + + + + + @@ -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); + } + });
ui5-user-settings-appearance-view-group