diff --git a/packages/main/cypress/specs/TabContainer.cy.tsx b/packages/main/cypress/specs/TabContainer.cy.tsx index 9020c529d239..42ef3bcd1ce4 100644 --- a/packages/main/cypress/specs/TabContainer.cy.tsx +++ b/packages/main/cypress/specs/TabContainer.cy.tsx @@ -7,6 +7,7 @@ import Separator from "../../src/TabSeparator.js" import Table from "../../src/Table.js"; import TableCell from "../../src/TableCell.js"; import TableRow from "../../src/TableRow.js"; +import TableHeaderRow from "../../src/TableHeaderRow.js"; import TableHeaderCell from "../../src/TableHeaderCell.js"; import "@ui5/webcomponents-icons/dist/employee.js" import "@ui5/webcomponents-icons/dist/menu.js" @@ -577,8 +578,10 @@ describe("TabContainer general interaction", () => { - Source - Method + + Source + Method + Cell 1 Cell 2 @@ -603,8 +606,10 @@ describe("TabContainer general interaction", () => {
- Source - Method + + Source + Method + Cell 3 Cell 4 diff --git a/packages/main/cypress/specs/TableGroupRow.cy.tsx b/packages/main/cypress/specs/TableGroupRow.cy.tsx new file mode 100644 index 000000000000..cdfd9a5a8f64 --- /dev/null +++ b/packages/main/cypress/specs/TableGroupRow.cy.tsx @@ -0,0 +1,309 @@ +import Table from "../../src/Table.js"; +import TableHeaderRow from "../../src/TableHeaderRow.js"; +import TableHeaderCell from "../../src/TableHeaderCell.js"; +import TableRow from "../../src/TableRow.js"; +import TableCell from "../../src/TableCell.js"; +import TableGroupRow from "../../src/TableGroupRow.js"; +import TableSelectionMulti from "../../src/TableSelectionMulti.js"; +import TableRowActionNavigation from "../../src/TableRowActionNavigation.js"; +import Text from "../../src/Text.js"; + +describe("Table - Group Rows", () => { + function mountGroupedTable() { + cy.mount( +
+ + City + Country + Population + + + Country: Germany + + + Berlin + Germany + 3,748,148 + + + Munich + Germany + 1,471,508 + + + Country: France + + + Paris + France + 2,161,000 + +
+ ); + } + + it("should render group rows and data rows", () => { + mountGroupedTable(); + + cy.get("[ui5-table-group-row]").should("have.length", 2); + cy.get("[ui5-table-row]:not([ui5-table-group-row])").should("have.length", 3); + cy.get("#group1").should("contain.text", "Country: Germany"); + cy.get("#group2").should("contain.text", "Country: France"); + }); + + it("should have aria-roledescription on group rows", () => { + mountGroupedTable(); + + cy.get("#group1").should("have.attr", "aria-roledescription"); + cy.get("#group2").should("have.attr", "aria-roledescription"); + }); + + it("should use role=row on group rows", () => { + mountGroupedTable(); + + cy.get("#group1").should("have.attr", "role", "row"); + cy.get("#table") + .shadow() + .find("#table") + .should("have.attr", "role", "grid"); + }); + + it("should have group cell spanning all columns", () => { + mountGroupedTable(); + + cy.get("#group1") + .shadow() + .find("#group-cell") + .should("have.attr", "role", "gridcell") + .and("have.attr", "aria-colindex", "1") + .and("have.attr", "aria-colspan", "2"); + }); + + it("should set aria-rowindex sequentially on all rows", () => { + mountGroupedTable(); + + cy.get("#group1").should("have.attr", "aria-rowindex", "2"); + cy.get("#row1").should("have.attr", "aria-rowindex", "3"); + cy.get("#row2").should("have.attr", "aria-rowindex", "4"); + cy.get("#group2").should("have.attr", "aria-rowindex", "5"); + cy.get("#row3").should("have.attr", "aria-rowindex", "6"); + }); + + it("should not be selectable", () => { + cy.mount( + + + + City + + + Group + + + Berlin + +
+ ); + + cy.get("#group1").should("not.have.attr", "aria-selected"); + }); + + it("should not affect Select All behavior", () => { + cy.mount( + + + + City + + + Group + + + Berlin + + + Munich + +
+ ); + + // Select all via header checkbox + cy.get("[ui5-table-header-row]") + .shadow() + .find("#selection-component") + .realClick(); + + // Both data rows should be selected + cy.get("#row1").should("have.attr", "aria-selected", "true"); + cy.get("#row2").should("have.attr", "aria-selected", "true"); + // Group row should NOT be selected + cy.get("#group1").should("not.have.attr", "aria-selected"); + }); + + it("should reset row alternation after each group header row", () => { + cy.mount( + + + City + + + Group 1 + + + A + + + B + + + C + + + Group 2 + + + D + + + E + +
+ ); + + // After group1: rowA(1)=not, rowB(2)=alternate, rowC(3)=not + cy.get("#rowA").should("not.have.attr", "_alternate"); + cy.get("#rowB").should("have.attr", "_alternate"); + cy.get("#rowC").should("not.have.attr", "_alternate"); + + // After group2: reset → rowD(1)=not, rowE(2)=alternate + cy.get("#rowD").should("not.have.attr", "_alternate"); + cy.get("#rowE").should("have.attr", "_alternate"); + + // Group rows never get _alternate + cy.get("#group1").should("not.have.attr", "_alternate"); + cy.get("#group2").should("not.have.attr", "_alternate"); + }); + + it("should not throw with popin mode and group rows", () => { + cy.mount( + + + City + Country + Population + + + Group + + + Berlin + Germany + 3,748,148 + +
+ ); + + // Shrink to trigger popin + cy.get("#table").invoke("css", "width", "300px"); + + // Should not throw — table and rows intact + cy.get("#table").should("exist"); + cy.get("#group1").should("exist"); + cy.get("#row1").should("exist"); + + // Expand again + cy.get("#table").invoke("css", "width", "800px"); + cy.get("#group1").should("contain.text", "Group"); + }); + + it("should be keyboard navigable as a single-cell row", () => { + mountGroupedTable(); + + // Click on left edge to focus the row itself (not a cell inside) + cy.get("#row1").click("left"); + cy.get("#row1").should("be.focused"); + + // Arrow up should land on the group row + cy.get("#row1").type("{uparrow}"); + cy.get("#group1").should("be.focused"); + }); + + it("should not move focus on left/right arrow keys", () => { + mountGroupedTable(); + + cy.get("#row1").click("left"); + cy.get("#row1").type("{uparrow}"); + cy.get("#group1").should("be.focused"); + + // Left/right should not move focus away from the group row + cy.get("#group1").type("{leftarrow}"); + cy.get("#group1").should("be.focused"); + + cy.get("#group1").type("{rightarrow}"); + cy.get("#group1").should("be.focused"); + }); + + it("should preserve column position when navigating through a group row", () => { + mountGroupedTable(); + + // Focus the second cell of row1 + cy.get("#row1").click("left"); + cy.get("#row1").type("{rightarrow}{rightarrow}"); + cy.get("#row1").children("[ui5-table-cell]").eq(1).should("be.focused"); + + // Navigate up to the group row + cy.realPress("ArrowUp"); + cy.get("#group1").should("be.focused"); + + // Navigate back down — should return to the same column (2nd cell) + cy.realPress("ArrowDown"); + cy.get("#row1").children("[ui5-table-cell]").eq(1).should("be.focused"); + }); + + it("should expose empty cells array even when children are slotted", () => { + cy.mount( + + + Col + + + Group with content + +
+ ); + + cy.get("#group1").then(($el) => { + const groupRow = $el[0] as unknown as TableGroupRow; + expect(groupRow.cells).to.have.length(0); + }); + }); + + it("should not render actions cell when table has rowActionCount", () => { + cy.mount( + + + City + + + Group + + + Berlin + + +
+ ); + + // Data row should have actions cell + cy.get("#row1") + .shadow() + .find("#actions-cell") + .should("exist"); + + // Group row should NOT have actions cell + cy.get("#group1") + .shadow() + .find("#actions-cell") + .should("not.exist"); + }); +}); diff --git a/packages/main/src/GridWalker.ts b/packages/main/src/GridWalker.ts index f80a454820d5..a0763ea8f00c 100644 --- a/packages/main/src/GridWalker.ts +++ b/packages/main/src/GridWalker.ts @@ -17,11 +17,17 @@ class GridWalker { } left() { - this.colPos = Math.max(this.getColPos() - 1, 0); + const cellCount = this.grid[this.getRowPos()].length; + if (cellCount > 1) { + this.colPos = Math.max(this.getColPos() - 1, 0); + } } right() { - this.colPos = Math.min(this.getColPos() + 1, this.grid[this.getRowPos()].length - 1); + const cellCount = this.grid[this.getRowPos()].length; + if (cellCount > 1) { + this.colPos = Math.min(this.getColPos() + 1, cellCount - 1); + } } up() { diff --git a/packages/main/src/Table.ts b/packages/main/src/Table.ts index a93ef0cbcda9..809fc5292120 100644 --- a/packages/main/src/Table.ts +++ b/packages/main/src/Table.ts @@ -455,12 +455,23 @@ class Table extends UI5Element { } onBeforeRendering(): void { + let alternateIndex = 0; + const hasFlexibleColumns = this._hasFlexibleColumns; + const rowActionCount = this.rowActionCount > 0 && this.rows.length > 0 ? this.rowActionCount : 0; this._renderNavigated = this.rows.some(row => row.navigated); - [...this.headerRow, ...this.rows].forEach((row, index) => { - row._rowActionCount = this.rows.length > 0 ? this.rowActionCount : 0; - row._renderNavigated = this._renderNavigated; - row._renderDummyCell = !this._hasFlexibleColumns; - row._alternate = this.alternateRowColors && index % 2 === 0; + [...this.headerRow, ...this.rows].forEach(row => { + if (!row.isGroupRow()) { + row._rowActionCount = rowActionCount; + row._renderDummyCell = !hasFlexibleColumns; + row._renderNavigated = this._renderNavigated; + row._alternate = this.alternateRowColors && alternateIndex++ % 2 === 0; + } else { + row._rowActionCount = 0; + row._renderDummyCell = !hasFlexibleColumns && !this._hasPopin; + row._renderNavigated = false; + row._alternate = false; + alternateIndex = 1; + } }); this.style.setProperty("--ui5_grid_sticky_top", this.stickyTop); @@ -589,8 +600,8 @@ class Table extends UI5Element { this.rows.forEach(row => { const cell = row.cells[headerIndex]; if (cell) { - row.cells[headerIndex]._popinHidden = headerCell.popinHidden; - row.cells[headerIndex]._popin = headerCell._popin; + cell._popinHidden = headerCell.popinHidden; + cell._popin = headerCell._popin; } }); } @@ -652,9 +663,9 @@ class Table extends UI5Element { // Dummy Cell Width (before actions when popin, after navigated otherwise) const dummyColumnWidth = !this._hasFlexibleColumns ? "minmax(0, 1fr)" : ""; - const hasPopinCells = this.headerRow[0]._popinCells.length > 0; + const hasPopin = this._hasPopin; - if (dummyColumnWidth && hasPopinCells) { + if (dummyColumnWidth && hasPopin) { widths.push(dummyColumnWidth); } @@ -668,13 +679,17 @@ class Table extends UI5Element { widths.push(`var(--_ui5_table_navigated_cell_width)`); } - if (dummyColumnWidth && !hasPopinCells) { + if (dummyColumnWidth && !hasPopin) { widths.push(dummyColumnWidth); } return widths.join(" "); } + get _hasPopin() { + return this.overflowMode === TableOverflowMode.Popin && this.headerRow?.[0]?._hasPopin; + } + get _hasFlexibleColumns(): boolean { return this.headerRow?.[0]?._visibleCells.some(cell => !isValidColumnWidth(cell.width)); } diff --git a/packages/main/src/TableCustomAnnouncement.ts b/packages/main/src/TableCustomAnnouncement.ts index 09917a9449fd..75cd9ec92741 100644 --- a/packages/main/src/TableCustomAnnouncement.ts +++ b/packages/main/src/TableCustomAnnouncement.ts @@ -4,6 +4,7 @@ import type Table from "./Table.js"; import type TableRow from "./TableRow.js"; import type TableCell from "./TableCell.js"; import type TableHeaderRow from "./TableHeaderRow.js"; +import type TableGroupRow from "./TableGroupRow.js"; import { TABLE_ROW, TABLE_ROW_INDEX, @@ -12,6 +13,7 @@ import { TABLE_ROW_NAVIGABLE, TABLE_ROW_NAVIGATED, TABLE_COLUMN_HEADER_ROW, + TABLE_GROUP_ROW, } from "./generated/i18n/i18n-defaults.js"; /** @@ -86,10 +88,16 @@ class TableCustomAnnouncement extends TableExtension { } const descriptions = [ - this.i18nBundle.getText(TABLE_ROW), + this.i18nBundle.getText(row.isGroupRow() ? TABLE_GROUP_ROW : TABLE_ROW), this.i18nBundle.getText(TABLE_ROW_INDEX, row.ariaRowIndex!, this._table._ariaRowCount), ]; + const groupRow = this._findGroupRow(row); + if (groupRow) { + const groupDescription = getCustomAnnouncement(groupRow._groupCell, { lessDetails: true }); + descriptions.push(groupDescription); + } + if (row._isSelected) { descriptions.push(this.i18nBundle.getText(TABLE_ROW_SELECTED)); } @@ -132,6 +140,17 @@ class TableCustomAnnouncement extends TableExtension { this._handleTableElementFocusin(cell); } } + + private _findGroupRow(row: TableRow): TableGroupRow | undefined { + const rows = this._table.rows; + const rowIndex = rows.indexOf(row); + for (let i = rowIndex; i >= 0; i--) { + if (rows[i].isGroupRow()) { + return rows[i] as TableGroupRow; + } + } + return undefined; + } } export default TableCustomAnnouncement; diff --git a/packages/main/src/TableGroupRow.ts b/packages/main/src/TableGroupRow.ts new file mode 100644 index 000000000000..ace272036563 --- /dev/null +++ b/packages/main/src/TableGroupRow.ts @@ -0,0 +1,122 @@ +import { customElement } from "@ui5/webcomponents-base/dist/decorators.js"; +import query from "@ui5/webcomponents-base/dist/decorators/query.js"; +import TableRow from "./TableRow.js"; +import TableRowBase from "./TableRowBase.js"; +import TableGroupRowTemplate from "./TableGroupRowTemplate.js"; +import TableGroupRowCss from "./generated/themes/TableGroupRow.css.js"; +import type TableCell from "./TableCell.js"; +import type TableRowActionBase from "./TableRowActionBase.js"; +import type { DefaultSlot, Slot } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import { + TABLE_GROUP_ROW, +} from "./generated/i18n/i18n-defaults.js"; + +const EMPTY_CELLS = [] as unknown as DefaultSlot; +const EMPTY_ACTIONS = [] as unknown as Slot; + +/** + * @class + * + * ### Overview + * + * The `ui5-table-group-row` component represents a group header row in the `ui5-table`. + * It is used to visually group rows and spans across all visible table columns. + * + * ### Usage + * + * The `ui5-table-group-row` is placed as a direct child of `ui5-table`, alongside `ui5-table-row` elements. + * Rows following a group row are considered part of that group until the next group row. + * + * ```html + * + * ... + * Country: Germany + * ... + * ... + * Country: France + * ... + * + * ``` + * + * ### Unsupported Features + * + * The following features of `ui5-table-row` are currently not supported by `ui5-table-group-row` and have no effect: + * + * - **Cells** (`cells` slot): Group rows render a single spanning cell with a text. Any slotted `ui5-table-cell` elements are ignored. + * - **Actions** (`actions` slot): Row actions such as `ui5-table-row-action` or `ui5-table-row-action-navigation` are not rendered. + * - **Navigation** (`navigated` property): The navigated indicator is not rendered on group rows. + * - **Interactive** (`interactive` property): Group rows do not support click/activation behavior. + * - **Selection** (`rowKey` property`): Group rows cannot be selected. They are excluded from select all and range selection operations. + * - **Virtualizer** (`position` property`): Group rows are not supported by the `ui5-table-virtualizer`. + * + * ### ES6 Module Import + * + * `import "@ui5/webcomponents/dist/TableGroupRow.js";` + * + * @constructor + * @extends TableRow + * @since 2.22.0 + * @public + */ +@customElement({ + tag: "ui5-table-group-row", + styles: [TableRowBase.styles, TableGroupRowCss], + template: TableGroupRowTemplate, +}) +class TableGroupRow extends TableRow { + @query("#group-cell") + _groupCell!: TableCell; + + constructor() { + super(); + Object.defineProperty(this, "cells", { + get: () => EMPTY_CELLS, + }); + Object.defineProperty(this, "actions", { + get: () => EMPTY_ACTIONS, + }); + } + + isGroupRow(): boolean { + return true; + } + + onEnterDOM() { + super.onEnterDOM(); + this.toggleAttribute("ui5-table-row", true); + this.setAttribute("aria-roledescription", TableGroupRow.i18nBundle.getText(TABLE_GROUP_ROW)); + } + + get _tableSelection() { + return undefined; + } + + get _hasSelector() { + return false; + } + + get _isSelectable() { + return false; + } + + get _isInteractive() { + return false; + } + + get _isNavigable() { + return false; + } + + get _hasPopin() { + return false; + } + + get _ariaColSpan(): number { + const colSpan = this._table?.headerRow[0]?._visibleCells.length ?? 1; + return (this._renderDummyCell) ? colSpan - 1 : colSpan; + } +} + +TableGroupRow.define(); + +export default TableGroupRow; diff --git a/packages/main/src/TableGroupRowTemplate.tsx b/packages/main/src/TableGroupRowTemplate.tsx new file mode 100644 index 000000000000..c983b0215df6 --- /dev/null +++ b/packages/main/src/TableGroupRowTemplate.tsx @@ -0,0 +1,24 @@ +import TableCell from "./TableCell.js"; +import type TableGroupRow from "./TableGroupRow.js"; + +export default function TableGroupRowTemplate(this: TableGroupRow) { + return ( + <> + + + + + { this._renderDummyCell && + + } + + ); +} diff --git a/packages/main/src/TableRowBase.ts b/packages/main/src/TableRowBase.ts index f75a9767eac8..254dec64fff1 100644 --- a/packages/main/src/TableRowBase.ts +++ b/packages/main/src/TableRowBase.ts @@ -56,6 +56,10 @@ abstract class TableRowBase extends return false; } + isGroupRow(): boolean { + return false; + } + onEnterDOM() { !this.role && this.setAttribute("role", "row"); this.toggleAttribute("ui5-table-row-base", true); diff --git a/packages/main/src/TableSelection.ts b/packages/main/src/TableSelection.ts index 08e60c9beca4..e65c3d2392f4 100644 --- a/packages/main/src/TableSelection.ts +++ b/packages/main/src/TableSelection.ts @@ -178,7 +178,7 @@ class TableSelection extends UI5Element implements ITableFeature { } const selectedArray = this.selectedAsArray; - return this._table.rows.every(row => { + return this._table.rows.filter(row => row._isSelectable).every(row => { const rowKey = this.getRowKey(row); return selectedArray.includes(rowKey); }); @@ -229,7 +229,7 @@ class TableSelection extends UI5Element implements ITableFeature { _selectHeaderRow(selected: boolean) { const selectedSet = this.selectedAsSet; - this._table!.rows.forEach(row => { + this._table!.rows.filter(row => row._isSelectable).forEach(row => { const rowKey = this.getRowKey(row); selectedSet[selected ? "add" : "delete"](rowKey); }); diff --git a/packages/main/src/TableSelectionMulti.ts b/packages/main/src/TableSelectionMulti.ts index 36ad46d319e8..34e46b5377d8 100644 --- a/packages/main/src/TableSelectionMulti.ts +++ b/packages/main/src/TableSelectionMulti.ts @@ -145,7 +145,7 @@ class TableSelectionMulti extends TableSelectionBase { } const selectedSet = this.getSelectedAsSet(); - return this._table.rows.every(row => { + return this._table.rows.filter(row => row._isSelectable).every(row => { const rowKey = this.getRowKey(row); return selectedSet.has(rowKey); }); diff --git a/packages/main/src/TableVirtualizer.ts b/packages/main/src/TableVirtualizer.ts index 87f9f49f4acc..8bd0857cf9a0 100644 --- a/packages/main/src/TableVirtualizer.ts +++ b/packages/main/src/TableVirtualizer.ts @@ -52,6 +52,7 @@ type RangeChangeEventDetail = { * This allows large numbers of rows to exist, but maintain high performance by only paying the cost for those that are currently visible. * * **Note:** The maximum number of virtualized rows is limited by browser constraints, specifically the maximum supported height for a DOM element. + * **Note:** The `ui5-table-group-row` component is not supported by the virtualizer. Only `ui5-table-row` elements can be virtualized. * * ### ES6 Module Import * `import "@ui5/webcomponents/dist/TableVirtualizer.js";` diff --git a/packages/main/src/bundle.esm.ts b/packages/main/src/bundle.esm.ts index 0dd3e72531f0..747c29e96aec 100644 --- a/packages/main/src/bundle.esm.ts +++ b/packages/main/src/bundle.esm.ts @@ -66,9 +66,12 @@ import FormItem from "./FormItem.js"; import FormGroup from "./FormGroup.js"; import FileUploader from "./FileUploader.js"; import Table from "./Table.js"; +import TableCell from "./TableCell.js"; import TableHeaderCell from "./TableHeaderCell.js"; import TableHeaderCellActionAI from "./TableHeaderCellActionAI.js"; +import TableRow from "./TableRow.js"; import TableHeaderRow from "./TableHeaderRow.js"; +import TableGroupRow from "./TableGroupRow.js"; import TableGrowing from "./TableGrowing.js"; import TableSelection from "./TableSelection.js"; import TableSelectionMulti from "./TableSelectionMulti.js"; diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index d6f9bc6b3c89..b43e02c4c878 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -884,6 +884,8 @@ TABLE_COLUMNHEADER_SELECTALL_DESCRIPTION=Select All Checkbox TABLE_COLUMNHEADER_CLEARALL_DESCRIPTION=Clear All Button #XACT: ARIA announcement of a table row TABLE_ROW=Row +#XACT: ARIA roledescription for a group row in the table +TABLE_GROUP_ROW=Group Row #XACT: Description for the popin containing column header TABLE_ROW_POPIN=Row Popin #XACT: ARIA announcement for the position of a row among all table rows diff --git a/packages/main/src/themes/TableGroupRow.css b/packages/main/src/themes/TableGroupRow.css new file mode 100644 index 000000000000..8d65dc823ee7 --- /dev/null +++ b/packages/main/src/themes/TableGroupRow.css @@ -0,0 +1,20 @@ +:host { + color: var(--sapList_TableGroupHeaderTextColor); + background: var(--sapList_TableGroupHeaderBackground); + font-weight: bold; + min-height: 2rem; +} + +#group-cell { + grid-column: 1 / -1; + padding-inline: var(--_ui5_first_table_cell_horizontal_padding); +} + +:host([_render-dummy-cell]) #group-cell { + grid-column: 1 / -2; +} + +:host(:first-of-type) > [ui5-table-cell][aria-colspan] { + border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); + border-top: none; +} diff --git a/packages/main/src/themes/TableRowBase.css b/packages/main/src/themes/TableRowBase.css index bd8088f128fa..2120d2a23cb7 100644 --- a/packages/main/src/themes/TableRowBase.css +++ b/packages/main/src/themes/TableRowBase.css @@ -52,6 +52,11 @@ border-inline-start: none; } +:host(:not([_has-popin])) #dummy-cell { + background: var(--sapList_Background); + border-top: none; +} + :host([tabindex]:focus) { outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor); outline-offset: calc(-1 * var(--sapContent_FocusWidth)); diff --git a/packages/main/test/pages/TableGrouping.html b/packages/main/test/pages/TableGrouping.html new file mode 100644 index 000000000000..c7f07417716e --- /dev/null +++ b/packages/main/test/pages/TableGrouping.html @@ -0,0 +1,134 @@ + + + + + + Table Grouping TestPage + + + + + + +
+ Table Grouping + Group by Country + Enable Alternating Colors +
+ + + + + City + Area (km²) + Population + + + + + + + \ No newline at end of file diff --git a/packages/website/docs/_components_pages/main/Table/Table.mdx b/packages/website/docs/_components_pages/main/Table/Table.mdx index dd2d159d020d..3399f2d4630f 100644 --- a/packages/website/docs/_components_pages/main/Table/Table.mdx +++ b/packages/website/docs/_components_pages/main/Table/Table.mdx @@ -3,6 +3,7 @@ slug: ../../Table --- import Basic from "../../../_samples/main/Table/Basic/Basic.md"; +import Grouping from "../../../_samples/main/Table/Grouping/Grouping.md"; import Popin from "../../../_samples/main/Table/Popin/Popin.md"; import Scroll from "../../../_samples/main/Table/ScrollMode/Scroll.md"; import StickyHeader from "../../../_samples/main/Table/StickyHeader/StickyHeader.md"; @@ -62,4 +63,10 @@ will fire the `row-click` event. Enable Drag and Drop by using the `move-over` and `move` event in combination with the `movable` property on the `ui5-table-row`. - \ No newline at end of file + + +### Row Grouping + +Use `ui5-table-group-row` to visually group rows. Group rows are placed as siblings of `ui5-table-row` in the table's default slot. + + \ No newline at end of file diff --git a/packages/website/docs/_components_pages/main/Table/TableGroupRow.mdx b/packages/website/docs/_components_pages/main/Table/TableGroupRow.mdx new file mode 100644 index 000000000000..1fee8f8941f2 --- /dev/null +++ b/packages/website/docs/_components_pages/main/Table/TableGroupRow.mdx @@ -0,0 +1,12 @@ +--- +slug: ../../TableGroupRow +--- + +import Grouping from "../../../_samples/main/Table/Grouping/Grouping.md"; + +<%COMPONENT_OVERVIEW%> + +<%COMPONENT_METADATA%> + +## Basic Sample + \ No newline at end of file diff --git a/packages/website/docs/_samples/compat/Table/Grouping/sample.tsx b/packages/website/docs/_samples/compat/Table/Grouping/sample.tsx index 120dbff06e90..641541bd8a83 100644 --- a/packages/website/docs/_samples/compat/Table/Grouping/sample.tsx +++ b/packages/website/docs/_samples/compat/Table/Grouping/sample.tsx @@ -3,7 +3,7 @@ import CompatTableClass from "@ui5/webcomponents-compat/dist/Table.js"; import CompatTableRowClass from "@ui5/webcomponents-compat/dist/TableRow.js"; import TableColumnClass from "@ui5/webcomponents-compat/dist/TableColumn.js"; import CompatTableCellClass from "@ui5/webcomponents-compat/dist/TableCell.js"; -import TableGroupRowClass from "@ui5/webcomponents-compat/dist/TableGroupRow.js"; +import CompatTableGroupRowClass from "@ui5/webcomponents-compat/dist/TableGroupRow.js"; import TextClass from "@ui5/webcomponents/dist/Text.js"; import LabelClass from "@ui5/webcomponents/dist/Label.js"; @@ -11,7 +11,7 @@ const CompatTable = createReactComponent(CompatTableClass); const CompatTableRow = createReactComponent(CompatTableRowClass); const CompatTableColumn = createReactComponent(TableColumnClass); const CompatTableCell = createReactComponent(CompatTableCellClass); -const TableGroupRow = createReactComponent(TableGroupRowClass); +const TableGroupRow = createReactComponent(CompatTableGroupRowClass); const Text = createReactComponent(TextClass); const Label = createReactComponent(LabelClass); diff --git a/packages/website/docs/_samples/main/Table/DragAndDrop/sample.html b/packages/website/docs/_samples/main/Table/DragAndDrop/sample.html index f197766b00f6..dbf08c5c4ccd 100644 --- a/packages/website/docs/_samples/main/Table/DragAndDrop/sample.html +++ b/packages/website/docs/_samples/main/Table/DragAndDrop/sample.html @@ -13,31 +13,27 @@ - Product - Supplier - Dimensions - Weight - Price + Product + Supplier + Dimensions + Price Notebook Basic 15
HT-1000
Very Best Screens 30 x 18 x 3 cm - 4.2 KG 956 EUR
Notebook Basic 17
HT-1001
Smartcards 29 x 17 x 3.1 cm - 4.5 KG 1249 EUR
Notebook Basic 18
HT-1002
Technocom 32 x 21 x 4 cm - 3.7 KG 29 EUR
diff --git a/packages/website/docs/_samples/main/Table/DragAndDrop/sample.tsx b/packages/website/docs/_samples/main/Table/DragAndDrop/sample.tsx index 09ad8cf262e4..5029420e19ef 100644 --- a/packages/website/docs/_samples/main/Table/DragAndDrop/sample.tsx +++ b/packages/website/docs/_samples/main/Table/DragAndDrop/sample.tsx @@ -71,19 +71,16 @@ function App() { <> - + Product - + Supplier - + Dimensions - - Weight - - + Price @@ -101,11 +98,6 @@ function App() { - - -
+ + City + Area (km²) + Population + + Country: Germany + + Berlin + 891.20 + 3,748,148 + + + Munich + 310.71 + 1,471,508 + + + Hamburg + 755.22 + 1,899,160 + + Country: France + + Paris + 105.40 + 2,161,000 + + + Lyon + 47.87 + 513,275 + +
+ + ); +} + +export default App; diff --git a/packages/website/docs/_samples/main/Table/Interactive/main.js b/packages/website/docs/_samples/main/Table/Interactive/main.js index 582d18461af2..8e39643a765c 100644 --- a/packages/website/docs/_samples/main/Table/Interactive/main.js +++ b/packages/website/docs/_samples/main/Table/Interactive/main.js @@ -8,6 +8,6 @@ const table = document.getElementById("table"); const toast = document.getElementById("message"); table.addEventListener("row-click", (e) => { - toast.textContent = `Row with key "${e.detail.row.key}" was pressed!`; + toast.textContent = `Row with key "${e.detail.row.rowKey}" was pressed!`; toast.open = true; }); \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Table/Interactive/sample.html b/packages/website/docs/_samples/main/Table/Interactive/sample.html index c2895867f12e..21a23c8071a1 100644 --- a/packages/website/docs/_samples/main/Table/Interactive/sample.html +++ b/packages/website/docs/_samples/main/Table/Interactive/sample.html @@ -14,32 +14,28 @@ - Product - Supplier - Dimensions - Weight - Price + Product + Supplier + Dimensions + Price Notebook Basic 15
HT-1000
Very Best Screens 30 x 18 x 3 cm - 4.2 KG 956 EUR
Notebook Basic 17
HT-1001
Smartcards 29 x 17 x 3.1 cm - 4.5 KG 1249 EUR
Notebook Basic 18
HT-1002
Technocom 32 x 21 x 4 cm - 3.7 KG 29 EUR
diff --git a/packages/website/docs/_samples/main/Table/Interactive/sample.tsx b/packages/website/docs/_samples/main/Table/Interactive/sample.tsx index 5e0e1f9c436f..95f2725983ec 100644 --- a/packages/website/docs/_samples/main/Table/Interactive/sample.tsx +++ b/packages/website/docs/_samples/main/Table/Interactive/sample.tsx @@ -33,19 +33,16 @@ function App() { {/* playground-fold */} - + Product - + Supplier - + Dimensions - - Weight - - + Price @@ -64,11 +61,6 @@ function App() { - - -
- + {/* playground-fold */} diff --git a/packages/website/src/components/Editor/ReactPlayground.tsx b/packages/website/src/components/Editor/ReactPlayground.tsx index fdba04efcc97..cec1200bfad4 100644 --- a/packages/website/src/components/Editor/ReactPlayground.tsx +++ b/packages/website/src/components/Editor/ReactPlayground.tsx @@ -134,6 +134,7 @@ import TableHeaderRowClass from "@ui5/webcomponents/dist/TableHeaderRow.js"; import TableHeaderCellClass from "@ui5/webcomponents/dist/TableHeaderCell.js"; import TableRowClass from "@ui5/webcomponents/dist/TableRow.js"; import TableCellClass from "@ui5/webcomponents/dist/TableCell.js"; +import TableGroupRowClass from "@ui5/webcomponents/dist/TableGroupRow.js"; import TreeClass from "@ui5/webcomponents/dist/Tree.js"; import TreeItemClass from "@ui5/webcomponents/dist/TreeItem.js"; import TreeItemCustomClass from "@ui5/webcomponents/dist/TreeItemCustom.js"; @@ -260,7 +261,7 @@ import CompatTableClass from "@ui5/webcomponents-compat/dist/Table.js"; import TableColumnClass from "@ui5/webcomponents-compat/dist/TableColumn.js"; import CompatTableRowClass from "@ui5/webcomponents-compat/dist/TableRow.js"; import CompatTableCellClass from "@ui5/webcomponents-compat/dist/TableCell.js"; -import TableGroupRowClass from "@ui5/webcomponents-compat/dist/TableGroupRow.js"; +import CompatTableGroupRowClass from "@ui5/webcomponents-compat/dist/TableGroupRow.js"; // Import icons commonly used in samples import "@ui5/webcomponents-icons/dist/AllIcons.js"; @@ -301,7 +302,7 @@ const ComponentClasses: Record = { RadioButtonClass, ProgressIndicatorClass, RatingIndicatorClass, SliderClass, RangeSliderClass, StepInputClass, PopoverClass, ResponsivePopoverClass, ToastClass, MessageStripClass, BusyIndicatorClass, TabContainerClass, TabClass, TabSeparatorClass, TableClass, TableHeaderRowClass, TableHeaderCellClass, - TableRowClass, TableCellClass, TreeClass, TreeItemClass, TreeItemCustomClass, PanelClass, + TableRowClass, TableCellClass, TableGroupRowClass, TreeClass, TreeItemClass, TreeItemCustomClass, PanelClass, ToolbarClass, ToolbarButtonClass, ToolbarSpacerClass, ToolbarSeparatorClass, ToolbarSelectClass, ToolbarSelectOptionClass, SegmentedButtonClass, SegmentedButtonItemClass, ComboBoxClass, ComboBoxItemClass, ComboBoxItemGroupClass, MultiComboBoxClass, MultiComboBoxItemClass, @@ -331,7 +332,7 @@ const ComponentClasses: Record = { // ai package AIButtonClass, AIButtonStateClass, AIInputClass, AITextAreaClass, AIPromptInputClass, // compat package - CompatTableClass, TableColumnClass, CompatTableRowClass, CompatTableCellClass, TableGroupRowClass, + CompatTableClass, TableColumnClass, CompatTableRowClass, CompatTableCellClass, CompatTableGroupRowClass, }; interface ReactPlaygroundProps {