diff --git a/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx b/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx index c19924bf120f..3d56bfdc5986 100644 --- a/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx +++ b/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx @@ -3,6 +3,7 @@ import GroupItem from "../../src/GroupItem.js"; import SortItem from "../../src/SortItem.js"; import FilterItem from "../../src/FilterItem.js"; import FilterItemOption from "../../src/FilterItemOption.js"; +import ViewSettingsCustomTab from "../../src/ViewSettingsCustomTab.js"; describe("View settings dialog - confirm event", () => { it("should throw confirm event after selecting sort options and confirm button", () => { @@ -484,4 +485,121 @@ describe("ViewSettingsDialog Tests", () => { cy.get("@items") .should("have.length", 3); }); + + it("should render custom tabs after built-in tabs", () => { + cy.mount( + + + + + + + +
Advanced settings
+
+ +
Metrics settings
+
+
+ ); + + cy.get("#vsdCustomOrder") + .as("vsd") + .invoke("prop", "open", true); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .as("items") + .should("have.length", 5); + + cy.get("@items") + .eq(0) + .should("have.attr", "data-mode", "Sort"); + + cy.get("@items") + .eq(1) + .should("have.attr", "data-mode", "Filter"); + + cy.get("@items") + .eq(2) + .should("have.attr", "data-mode", "Group"); + + cy.get("@items") + .eq(3) + .should("have.attr", "data-mode", "Custom-0"); + + cy.get("@items") + .eq(3) + .should("have.attr", "tooltip", "Advanced"); + + cy.get("@items") + .eq(4) + .should("have.attr", "data-mode", "Custom-1"); + + cy.get("@items") + .eq(3) + .realClick(); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-title") + .should("have.text", "View Settings"); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "Advanced Settings"); + + cy.get("@vsd") + .find("#advanced-tab-content") + .should("be.visible"); + }); + + it("should render only custom tabs when no built-in tabs are provided", () => { + cy.mount( + + +
General content
+
+ +
Extra content
+
+
+ ); + + cy.get("#vsdCustomOnly") + .as("vsd") + .invoke("prop", "open", true); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .should("have.length", 2); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-title") + .should("have.text", "View Settings"); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "General Settings"); + + cy.get("@vsd") + .find("#general-tab-content") + .should("be.visible"); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .eq(1) + .realClick(); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "Extra Settings"); + }); }); diff --git a/packages/fiori/src/ViewSettingsCustomTab.ts b/packages/fiori/src/ViewSettingsCustomTab.ts new file mode 100644 index 000000000000..3ff828cc7ec8 --- /dev/null +++ b/packages/fiori/src/ViewSettingsCustomTab.ts @@ -0,0 +1,85 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import ViewSettingsCustomTabTemplate from "./ViewSettingsCustomTabTemplate.js"; + +/** + * @class + * + * ### Overview + * + * The `ui5-view-settings-custom-tab` component allows defining custom tabs for the `ui5-view-settings-dialog`. + * + * ### ES6 Module Import + * + * `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` + * + * @constructor + * @extends UI5Element + * @since 2.21.0 + * @public + * @abstract + * @slot {Node[]} default - Defines the custom tab content. + */ +@customElement({ + tag: "ui5-view-settings-custom-tab", + renderer: jsxRenderer, + template: ViewSettingsCustomTabTemplate, +}) +class ViewSettingsCustomTab extends UI5Element { + /** + * Defines the title of the custom tab. + * + * **Note:** It is displayed in the dialog header when this tab is selected. + * @default "" + * @public + */ + @property({ type: String }) + title = ""; + + /** + * Defines the tooltip of the custom tab button. + * + * **Note:** It is shown on the segmented button item. + * @default "" + * @public + */ + @property({ type: String }) + tooltip = ""; + + /** + * Defines the icon of the custom tab. + * + * **Note:** If not provided, the segmented button item is rendered with text. + * @default undefined + * @public + */ + @property() + icon?: string; + + /** + * Defines whether the custom tab is selected initially. + * + * **Note:** If multiple custom tabs are marked as selected, the first one is used. + * @default false + * @public + */ + @property({ type: Boolean }) + selected = false; + + /** + * Defines the custom tab content. + * @public + */ + @slot({ type: Node, "default": true }) + content!: DefaultSlot; + + _individualSlot?: string; +} + +ViewSettingsCustomTab.define(); + +export default ViewSettingsCustomTab; diff --git a/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx new file mode 100644 index 000000000000..e79ecf3d7d05 --- /dev/null +++ b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx @@ -0,0 +1,5 @@ +import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; + +export default function ViewSettingsCustomTabTemplate(this: ViewSettingsCustomTab) { + return ; +} diff --git a/packages/fiori/src/ViewSettingsDialog.ts b/packages/fiori/src/ViewSettingsDialog.ts index 2cfae5ed43fa..5713446e8d69 100644 --- a/packages/fiori/src/ViewSettingsDialog.ts +++ b/packages/fiori/src/ViewSettingsDialog.ts @@ -23,6 +23,7 @@ import "@ui5/webcomponents-icons/dist/nav-back.js"; import type SortItem from "./SortItem.js"; import type FilterItem from "./FilterItem.js"; import type GroupItem from "./GroupItem.js"; +import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; import { VSD_DIALOG_TITLE_SORT, @@ -88,6 +89,11 @@ type VSDInternalSettings = { groupBy: Array, } +type ViewSettingsCustomMode = `Custom-${number}`; +type ViewSettingsDialogInternalMode = `${ViewSettingsDialogMode}` | ViewSettingsCustomMode; + +const CUSTOM_MODE_PREFIX = "Custom-"; + /** * @class * ### Overview @@ -254,7 +260,7 @@ class ViewSettingsDialog extends UI5Element { * @private */ @property() - _currentMode: `${ViewSettingsDialogMode}` = "Sort"; + _currentMode: ViewSettingsDialogInternalMode = ViewSettingsDialogMode.Sort; /** * When in Filter By mode, defines whether we need to show the list of keys, or the list with values. @@ -291,6 +297,25 @@ class ViewSettingsDialog extends UI5Element { @slot() groupItems!: Slot; + /** + * Defines custom tabs for the dialog. + * + * The custom tabs are rendered after the built-in tabs (`Sort`, `Filter`, `Group`). + * + * **Note:** If you want to use this slot, you need to import the item: `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` + * @public + * @since 2.21.0 + */ + @slot({ + type: HTMLElement, + individualSlots: true, + invalidateOnChildChange: { + properties: true, + slots: false, + }, + }) + customTabs!: Slot; + @query("[ui5-list]") _list!: List; @@ -328,6 +353,11 @@ class ViewSettingsDialog extends UI5Element { if (this.shouldBuildGroup) { this._currentMode = ViewSettingsDialogMode.Group; + return; + } + + if (this.shouldBuildCustomTabs && (!this.isModeCustom || !this._selectedCustomTab)) { + this._currentMode = this._defaultMode; } } @@ -377,9 +407,54 @@ class ViewSettingsDialog extends UI5Element { return !!this.groupItems.length; } + get shouldBuildCustomTabs() { + return !!this.customTabs.length; + } + get hasPagination() { - const buildConditions = [this.shouldBuildSort, this.shouldBuildFilter, this.shouldBuildGroup]; - return buildConditions.filter(condition => condition).length > 1; + const builtInTabsCount = [this.shouldBuildSort, this.shouldBuildFilter, this.shouldBuildGroup] + .filter(condition => condition) + .length; + + if (this.shouldBuildCustomTabs) { + return builtInTabsCount + this.customTabs.length > 1; + } + + return builtInTabsCount > 1; + } + + get _defaultMode(): ViewSettingsDialogInternalMode { + if (this.shouldBuildSort) { + return ViewSettingsDialogMode.Sort; + } + + if (this.shouldBuildFilter) { + return ViewSettingsDialogMode.Filter; + } + + if (this.shouldBuildGroup) { + return ViewSettingsDialogMode.Group; + } + + if (this.shouldBuildCustomTabs) { + return this._customMode(this._defaultCustomTabIndex); + } + + return ViewSettingsDialogMode.Sort; + } + + get _defaultCustomTabIndex() { + const selectedIndex = this.customTabs.findIndex(item => item.selected); + return selectedIndex > -1 ? selectedIndex : 0; + } + + get _selectedCustomTab() { + if (!this._isCustomMode(this._currentMode)) { + return; + } + + const currentTabIndex = this._getCustomTabIndexByMode(this._currentMode); + return this.customTabs[currentTabIndex]; } get _filterByTitle() { @@ -580,6 +655,10 @@ class ViewSettingsDialog extends UI5Element { return this._currentMode === ViewSettingsDialogMode.Group; } + get isModeCustom() { + return this._isCustomMode(this._currentMode); + } + get showBackButton() { return this.isModeFilter && this._filterStepTwo; } @@ -613,8 +692,13 @@ class ViewSettingsDialog extends UI5Element { } _handleModeChange(e: CustomEvent) { // use SegmentedButton event when done - const mode: ViewSettingsDialogMode = e.detail.selectedItems[0].getAttribute("data-mode"); - this._currentMode = ViewSettingsDialogMode[mode]; + const mode = (e.detail.selectedItems[0].getAttribute("data-mode") as string | null); + + if (!mode || !this._isValidMode(mode)) { + return; + } + + this._currentMode = mode; } _handleFilterValueItemClick(e: CustomEvent) { @@ -755,7 +839,7 @@ class ViewSettingsDialog extends UI5Element { _restoreConfirmedOnEscape(evt: CustomEvent) { // Dialog#before-close if (evt.detail.escPressed) { this._cancelSettings(); - this._currentMode = ViewSettingsDialogMode.Sort; + this._currentMode = this._defaultMode; this._filterStepTwo = false; } } @@ -776,10 +860,33 @@ class ViewSettingsDialog extends UI5Element { */ _restoreSettings(settings: VSDInternalSettings) { this._currentSettings = JSON.parse(JSON.stringify(settings)); - this._currentMode = ViewSettingsDialogMode.Sort; + this._currentMode = this._defaultMode; this._filterStepTwo = false; } + isCurrentCustomMode(index: number) { + return this._currentMode === this._customMode(index); + } + + _customMode(index: number): ViewSettingsCustomMode { + return `${CUSTOM_MODE_PREFIX}${index}`; + } + + _getCustomTabIndexByMode(mode: ViewSettingsCustomMode) { + return Number(mode.replace(CUSTOM_MODE_PREFIX, "")); + } + + _isCustomMode(mode: string): mode is ViewSettingsCustomMode { + return mode.startsWith(CUSTOM_MODE_PREFIX); + } + + _isValidMode(mode: string): mode is ViewSettingsDialogInternalMode { + return mode === (ViewSettingsDialogMode.Sort as string) + || mode === (ViewSettingsDialogMode.Filter as string) + || mode === (ViewSettingsDialogMode.Group as string) + || this._isCustomMode(mode); + } + /** * Stores `Sort Order` list as recently used control and its selected item in current state. */ diff --git a/packages/fiori/src/ViewSettingsDialogTemplate.tsx b/packages/fiori/src/ViewSettingsDialogTemplate.tsx index 23a000235087..2d1a16a81803 100644 --- a/packages/fiori/src/ViewSettingsDialogTemplate.tsx +++ b/packages/fiori/src/ViewSettingsDialogTemplate.tsx @@ -92,6 +92,19 @@ function _getSplitButtonItems(this: ViewSettingsDialog) { ); } + if (this.shouldBuildCustomTabs) { + this.customTabs.forEach((customTab, index) => { + buttonItems.push( + {customTab.icon ? undefined : customTab.title} + ); + }); + } + return buttonItems; } @@ -115,6 +128,15 @@ function ViewSettingsDialogTemplateContent(this: ViewSettingsDialog) { ViewSettingsDialogSortAndGroupTemplate.call(this, false) )} + {this.isModeCustom && this._selectedCustomTab && ( +
+ {this._selectedCustomTab.title && ( +
{this._selectedCustomTab.title}
+ )} + +
+ )} + ); } diff --git a/packages/fiori/src/bundle.esm.ts b/packages/fiori/src/bundle.esm.ts index 9ccfbb26b1af..7c3d50df4db9 100644 --- a/packages/fiori/src/bundle.esm.ts +++ b/packages/fiori/src/bundle.esm.ts @@ -64,6 +64,7 @@ import UserMenuAccount from "./UserMenuAccount.js"; import UserMenuItem from "./UserMenuItem.js"; import UserMenuItemGroup from "./UserMenuItemGroup.js"; import ViewSettingsDialog from "./ViewSettingsDialog.js"; +import ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; import Wizard from "./Wizard.js"; testAssets.defaultTexts = { ...testAssets.defaultTexts, ...defaultFioriTexts }; diff --git a/packages/fiori/src/themes/ViewSettingsDialog.css b/packages/fiori/src/themes/ViewSettingsDialog.css index f56c85e57ee6..0fd6387ff10b 100644 --- a/packages/fiori/src/themes/ViewSettingsDialog.css +++ b/packages/fiori/src/themes/ViewSettingsDialog.css @@ -65,6 +65,35 @@ height: 100%; } +.ui5-vsd-custom-tab-content { + width: 100%; + height: 100%; + box-sizing: border-box; + padding: 0.5rem 1rem; + overflow: auto; + min-width: 0; +} + +.ui5-vsd-custom-tab-title { + color: var(--sapList_TableGroupHeaderTextColor); + font-family: var(--sapFontHeaderFamily); + font-size: var(--sapFontHeader6Size); + font-weight: bold; + line-height: 2rem; + min-height: 2rem; + margin: 0; +} + +.ui5-vsd-custom-tab-slot::slotted([ui5-view-settings-custom-tab]) { + display: block; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + font-family: var(--sapFontFamily); + color: var(--sapTextColor); +} + [ui5-li-group]::part(header) { overflow: hidden; } diff --git a/packages/fiori/test/pages/ViewSettingsDialog.html b/packages/fiori/test/pages/ViewSettingsDialog.html index f3efd0fbc425..22440366e36e 100644 --- a/packages/fiori/test/pages/ViewSettingsDialog.html +++ b/packages/fiori/test/pages/ViewSettingsDialog.html @@ -128,6 +128,108 @@

ViewSettingsDialog with dataset properties

+

ViewSettingsDialog with default + custom tabs

+ + VSD - Default + Custom Tabs +

+	
+
+		
+		
+
+		
+		
+		
+
+		
+		
+		
+
+		
+			
+			
+		
+
+		
+			
+			
+		
+
+		
+			
+
+ Compact mode + +
+
+ Live updates + +
+ + Cozy + Compact + +
+
+ + +
+ + List + Grid + + + + + + + +
+
+ +
+ +

ViewSettingsDialog with just custom tabs

+ + VSD - Custom Tabs Only +

+	
+		
+			
+
+ Enable notifications + +
+
+ Auto-save + +
+ + Normal + High + +
+
+ + +
+ + Cards + Table + + + + + + + +
+
+
+ + + + diff --git a/packages/fiori/test/pages/styles/ViewSettingsDialog.css b/packages/fiori/test/pages/styles/ViewSettingsDialog.css index c568381c5893..2b2c9315e0a6 100644 --- a/packages/fiori/test/pages/styles/ViewSettingsDialog.css +++ b/packages/fiori/test/pages/styles/ViewSettingsDialog.css @@ -1,3 +1,30 @@ .viewsettingsdialog1auto { background-color: var(--sapBackgroundColor); } + +.vsd-custom-tab-section { + display: flex; + flex-direction: column; + gap: 0.75rem; + font-family: var(--sapFontFamily); + color: var(--sapTextColor); +} + +.vsd-custom-tab-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} + +.vsd-custom-tab-state { + font-family: Consolas, "Courier New", monospace; + font-size: 0.75rem; + white-space: pre-wrap; + background: var(--sapField_Background); + border: 1px solid var(--sapField_BorderColor); + border-radius: 0.25rem; + padding: 0.5rem; + margin: 0.5rem 0 0.75rem; + max-width: 32rem; +} diff --git a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js index 8a5f641d0c3a..64de0e9b286d 100644 --- a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js +++ b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js @@ -1,10 +1,20 @@ import "@ui5/webcomponents/dist/Button.js"; +import "@ui5/webcomponents/dist/ComboBox.js"; +import "@ui5/webcomponents/dist/ComboBoxItem.js"; +import "@ui5/webcomponents/dist/SegmentedButton.js"; +import "@ui5/webcomponents/dist/SegmentedButtonItem.js"; +import "@ui5/webcomponents/dist/StepInput.js"; +import "@ui5/webcomponents/dist/Switch.js"; +import "@ui5/webcomponents/dist/Title.js"; +import "@ui5/webcomponents-icons/dist/action-settings.js"; +import "@ui5/webcomponents-icons/dist/palette.js"; import "@ui5/webcomponents-fiori/dist/ViewSettingsDialog.js"; import "@ui5/webcomponents-fiori/dist/GroupItem.js"; import "@ui5/webcomponents-fiori/dist/SortItem.js"; import "@ui5/webcomponents-fiori/dist/FilterItem.js"; import "@ui5/webcomponents-fiori/dist/FilterItemOption.js"; +import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js"; var vsdResults = document.getElementById("vsdResults"); diff --git a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html index e0d12e54ea89..0b26b846e574 100644 --- a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html +++ b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html @@ -43,6 +43,38 @@ + + +
+
+ Compact mode + +
+
+ Live updates + +
+ + Cozy + Compact + +
+
+ + +
+ + List + Grid + + + + + + + +
+