diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b8b76cab3..a9c5124437b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,50 +5,44 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## [2.25.1](https://github.com/UI5/webcomponents-react/compare/v2.25.0...v2.25.1) (2026-08-17) - ### Bug Fixes -* **AnalyticalTable:** account for horizontal scrollbar height in "Auto" row count ([#8859](https://github.com/UI5/webcomponents-react/issues/8859)) ([d63fd3a](https://github.com/UI5/webcomponents-react/commit/d63fd3ada19ed9802385c479d53cdb90116f0d05)), closes [#8849](https://github.com/UI5/webcomponents-react/issues/8849) -* **AnalyticalTable:** prevent sticky column drag highlight from foreign drags ([#8863](https://github.com/UI5/webcomponents-react/issues/8863)) ([520a162](https://github.com/UI5/webcomponents-react/commit/520a1624dcf1908e6ff09ea4ca39cd5b326f4f18)), closes [#8861](https://github.com/UI5/webcomponents-react/issues/8861) -* **AnalyticalTable:** use native scrollbar for macOS overlay scrollbar modes ([#8834](https://github.com/UI5/webcomponents-react/issues/8834)) ([228cea8](https://github.com/UI5/webcomponents-react/commit/228cea8223beef677249ecfea26cc902a3173903)), closes [#8802](https://github.com/UI5/webcomponents-react/issues/8802) -* **deps:** update dependency @tanstack/react-virtual to v3.14.9 ([#8812](https://github.com/UI5/webcomponents-react/issues/8812)) ([6fdbf43](https://github.com/UI5/webcomponents-react/commit/6fdbf434439f25a8b0e40ef5ee2d6278042cf1ac)) +- **AnalyticalTable:** account for horizontal scrollbar height in "Auto" row count ([#8859](https://github.com/UI5/webcomponents-react/issues/8859)) ([d63fd3a](https://github.com/UI5/webcomponents-react/commit/d63fd3ada19ed9802385c479d53cdb90116f0d05)), closes [#8849](https://github.com/UI5/webcomponents-react/issues/8849) +- **AnalyticalTable:** prevent sticky column drag highlight from foreign drags ([#8863](https://github.com/UI5/webcomponents-react/issues/8863)) ([520a162](https://github.com/UI5/webcomponents-react/commit/520a1624dcf1908e6ff09ea4ca39cd5b326f4f18)), closes [#8861](https://github.com/UI5/webcomponents-react/issues/8861) +- **AnalyticalTable:** use native scrollbar for macOS overlay scrollbar modes ([#8834](https://github.com/UI5/webcomponents-react/issues/8834)) ([228cea8](https://github.com/UI5/webcomponents-react/commit/228cea8223beef677249ecfea26cc902a3173903)), closes [#8802](https://github.com/UI5/webcomponents-react/issues/8802) +- **deps:** update dependency @tanstack/react-virtual to v3.14.9 ([#8812](https://github.com/UI5/webcomponents-react/issues/8812)) ([6fdbf43](https://github.com/UI5/webcomponents-react/commit/6fdbf434439f25a8b0e40ef5ee2d6278042cf1ac)) ## [2.25.0](https://github.com/UI5/webcomponents-react/compare/v2.24.1...v2.25.0) (2026-07-30) - ### Features -* **mcp-server:** add web component testing docs + isAbstract flag ([#8773](https://github.com/UI5/webcomponents-react/issues/8773)) ([dad25c7](https://github.com/UI5/webcomponents-react/commit/dad25c77facedca4f5d03947e252e38fdab18c8a)) -* update to UI5 Web Components 2.25.0 ([#8828](https://github.com/UI5/webcomponents-react/issues/8828)) ([1550c8b](https://github.com/UI5/webcomponents-react/commit/1550c8bd3d8d1c5fb0c89d9bc35760ec1cbbb216)) - +- **mcp-server:** add web component testing docs + isAbstract flag ([#8773](https://github.com/UI5/webcomponents-react/issues/8773)) ([dad25c7](https://github.com/UI5/webcomponents-react/commit/dad25c77facedca4f5d03947e252e38fdab18c8a)) +- update to UI5 Web Components 2.25.0 ([#8828](https://github.com/UI5/webcomponents-react/issues/8828)) ([1550c8b](https://github.com/UI5/webcomponents-react/commit/1550c8bd3d8d1c5fb0c89d9bc35760ec1cbbb216)) ### Bug Fixes -* **deps:** update dependency @tanstack/react-virtual to v3.14.6 ([#8783](https://github.com/UI5/webcomponents-react/issues/8783)) ([7e99a22](https://github.com/UI5/webcomponents-react/commit/7e99a22bfda9550869fa924f22dcaad41fc67a47)) -* **deps:** update react monorepo to v19.2.8 ([#8816](https://github.com/UI5/webcomponents-react/issues/8816)) ([b3a041e](https://github.com/UI5/webcomponents-react/commit/b3a041e580f3a2344ee4ce2c08a60e4374f5dc82)) +- **deps:** update dependency @tanstack/react-virtual to v3.14.6 ([#8783](https://github.com/UI5/webcomponents-react/issues/8783)) ([7e99a22](https://github.com/UI5/webcomponents-react/commit/7e99a22bfda9550869fa924f22dcaad41fc67a47)) +- **deps:** update react monorepo to v19.2.8 ([#8816](https://github.com/UI5/webcomponents-react/issues/8816)) ([b3a041e](https://github.com/UI5/webcomponents-react/commit/b3a041e580f3a2344ee4ce2c08a60e4374f5dc82)) ## [2.24.1](https://github.com/UI5/webcomponents-react/compare/v2.24.0...v2.24.1) (2026-07-09) - ### Bug Fixes -* **AnalyticalTable:** announce selection change by screen readers ([#8768](https://github.com/UI5/webcomponents-react/issues/8768)) ([405f72e](https://github.com/UI5/webcomponents-react/commit/405f72e5cb30e53e6a3ef64e364980b178fd3916)) +- **AnalyticalTable:** announce selection change by screen readers ([#8768](https://github.com/UI5/webcomponents-react/issues/8768)) ([405f72e](https://github.com/UI5/webcomponents-react/commit/405f72e5cb30e53e6a3ef64e364980b178fd3916)) ## [2.24.0](https://github.com/UI5/webcomponents-react/compare/v2.23.2...v2.24.0) (2026-07-06) - ### Features -* **AnalyticalTable:** expose `className` & `classNameHeader` column options ([#8747](https://github.com/UI5/webcomponents-react/issues/8747)) ([e3dc256](https://github.com/UI5/webcomponents-react/commit/e3dc256218fe17ad418d4185f5a3e9688a5eeb2c)) -* update to UI5 Web Components 2.24.0 ([#8765](https://github.com/UI5/webcomponents-react/issues/8765)) ([d0315a4](https://github.com/UI5/webcomponents-react/commit/d0315a48582e581ac87a0f8e0bbc8be75af3ebc3)) - +- **AnalyticalTable:** expose `className` & `classNameHeader` column options ([#8747](https://github.com/UI5/webcomponents-react/issues/8747)) ([e3dc256](https://github.com/UI5/webcomponents-react/commit/e3dc256218fe17ad418d4185f5a3e9688a5eeb2c)) +- update to UI5 Web Components 2.24.0 ([#8765](https://github.com/UI5/webcomponents-react/issues/8765)) ([d0315a4](https://github.com/UI5/webcomponents-react/commit/d0315a48582e581ac87a0f8e0bbc8be75af3ebc3)) ### Bug Fixes -* **AnalyticalTable:** show expand icon in Quartz theme ([#8716](https://github.com/UI5/webcomponents-react/issues/8716)) ([bd15575](https://github.com/UI5/webcomponents-react/commit/bd155755c06b8090d69fbc2111ccb98e03fc7e74)), closes [#8689](https://github.com/UI5/webcomponents-react/issues/8689) -* **deps:** update dependency @tanstack/react-virtual to v3.14.5 ([#8742](https://github.com/UI5/webcomponents-react/issues/8742)) ([1e6f92d](https://github.com/UI5/webcomponents-react/commit/1e6f92dbb11432e2cf95f0937d4e09a29d5192d0)) -* **MessageView:** reset filter and details when children change ([#8717](https://github.com/UI5/webcomponents-react/issues/8717)) ([dfa4e06](https://github.com/UI5/webcomponents-react/commit/dfa4e06c3346e8daf7e8f34e51bdacad33b0b39b)), closes [#8707](https://github.com/UI5/webcomponents-react/issues/8707) -* **ObjectPageTitle:** correct screen reader reading order ([#8753](https://github.com/UI5/webcomponents-react/issues/8753)) ([cbdf944](https://github.com/UI5/webcomponents-react/commit/cbdf944bf404b0262abf83612459c89a29a44ddd)), closes [#8752](https://github.com/UI5/webcomponents-react/issues/8752) +- **AnalyticalTable:** show expand icon in Quartz theme ([#8716](https://github.com/UI5/webcomponents-react/issues/8716)) ([bd15575](https://github.com/UI5/webcomponents-react/commit/bd155755c06b8090d69fbc2111ccb98e03fc7e74)), closes [#8689](https://github.com/UI5/webcomponents-react/issues/8689) +- **deps:** update dependency @tanstack/react-virtual to v3.14.5 ([#8742](https://github.com/UI5/webcomponents-react/issues/8742)) ([1e6f92d](https://github.com/UI5/webcomponents-react/commit/1e6f92dbb11432e2cf95f0937d4e09a29d5192d0)) +- **MessageView:** reset filter and details when children change ([#8717](https://github.com/UI5/webcomponents-react/issues/8717)) ([dfa4e06](https://github.com/UI5/webcomponents-react/commit/dfa4e06c3346e8daf7e8f34e51bdacad33b0b39b)), closes [#8707](https://github.com/UI5/webcomponents-react/issues/8707) +- **ObjectPageTitle:** correct screen reader reading order ([#8753](https://github.com/UI5/webcomponents-react/issues/8753)) ([cbdf944](https://github.com/UI5/webcomponents-react/commit/cbdf944bf404b0262abf83612459c89a29a44ddd)), closes [#8752](https://github.com/UI5/webcomponents-react/issues/8752) ## [2.23.2](https://github.com/UI5/webcomponents-react/compare/v2.23.1...v2.23.2) (2026-06-29) diff --git a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx index 94c724dad80..9da49b51ece 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx @@ -1932,6 +1932,102 @@ describe('ObjectPage', () => { cy.findByText('Subsection1').should('be.visible'); }); + it('fitContent sections', () => { + const FitContentComp = () => ( + + +
+
Tall leaf content
+
+
+ + +
Content A
+
+ +
Content B
+
+
+ +
Normal content
+
+
+ ); + + cy.mount(); + + // leaf fitContent: content scrolls internally, OP container does not (footer stays pinned) + cy.findByTestId('leaf-scroller').then(($el) => { + const el = $el[0]; + expect(el.scrollHeight).to.be.greaterThan(el.clientHeight); + }); + // a leaf fitContent section (no subsections) must not become its own scroll container + cy.get('[data-component-name="ObjectPageSection"]').should('not.have.css', 'overflow-y', 'auto'); + cy.findByTestId('op').then(($op) => { + expect($op[0].scrollHeight).to.equal($op[0].clientHeight); + }); + cy.findByText('Accept').should('be.visible'); + + // fitContent + subsections: the section becomes its own scroll container, OP container does not scroll + cy.get('[ui5-tabcontainer]').findUi5TabByText('Subsections').click(); + cy.wait(100); + cy.get('[data-component-name="ObjectPageSection"]').should('have.css', 'overflow-y', 'auto'); + cy.findByTestId('op').then(($op) => { + expect($op[0].scrollHeight).to.equal($op[0].clientHeight); + }); + cy.findByText('Accept').should('be.visible'); + + // regression: unitless `0` broke the sticky offset calc() and fell back to `auto` + cy.findByText('Sub A') + .parent() + .should('have.css', 'position', 'sticky') + .and(($el) => { + expect($el.css('inset-block-start')).to.not.equal('auto'); + }); + + // sticky header sticks to the section top, not below the ObjectPage header + cy.get('[data-component-name="ObjectPageSection"]').scrollTo(0, 400); + cy.findByText('Sub A') + .parent() + .then(($hdr) => { + const header = $hdr[0]; + const section = header.closest('[data-component-name="ObjectPageSection"]'); + expect(Math.abs(header.getBoundingClientRect().top - section.getBoundingClientRect().top)).to.be.lessThan(3); + }); + + // sub-tab scroll-to scrolls the section (not the OP) and lands the subsection at the section top + cy.get('[data-component-name="ObjectPageSection"]').scrollTo('top'); + cy.get('[ui5-tabcontainer]').findUi5TabOpenPopoverButtonByText('Subsections').click(); + cy.get('[ui5-list]').should('be.visible'); + cy.wait(200); + cy.realPress('ArrowDown'); + cy.realPress('ArrowDown'); + cy.realPress('Enter'); + cy.wait(300); + cy.get('[data-component-name="ObjectPageSection"]').then(($section) => { + const section = $section[0]; + expect(section.scrollTop).to.be.greaterThan(0); + const targetTop = document.getElementById('ObjectPageSubSection-subB').getBoundingClientRect().top; + const sectionTop = section.getBoundingClientRect().top; + expect(Math.abs(targetTop - sectionTop)).to.be.lessThan(40); + }); + + // non-fitContent section keeps the normal page scroll (OP container scrolls) + cy.get('[ui5-tabcontainer]').findUi5TabByText('Normal').click(); + cy.wait(100); + cy.get('[data-component-name="ObjectPageSection"]').should('not.have.css', 'overflow-y', 'auto'); + cy.findByTestId('op').then(($op) => { + expect($op[0].scrollHeight).to.be.greaterThan($op[0].clientHeight); + }); + }); + it('IconTabBar: tab switch preserves header collapsed/expanded state and stays scroll-expandable', () => { cy.viewport(1440, 900); cy.mount( diff --git a/packages/main/src/components/ObjectPage/ObjectPage.mdx b/packages/main/src/components/ObjectPage/ObjectPage.mdx index f0d929e8492..ee64b317568 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.mdx +++ b/packages/main/src/components/ObjectPage/ObjectPage.mdx @@ -72,19 +72,36 @@ This component exposes public methods. You can use them directly on the instance -## TabBar ObjectPage with fullscreen section +## IconTabBar ObjectPage with fullscreen section -To render a single section in fullscreen mode, set its height to `100%`. +To render a section in fullscreen mode, so that it fills the remaining available height instead of growing with its content, set the `fitContent` prop on the `ObjectPageSection`. To make content fill or scroll within the section, wrap it in an element with `flex: 1` and `min-height: 0`. -**Note:** This is only supported for sections in `TabBar` mode! Using multiple sections with `height: 100%;` on the same page will most probably break your layout. +**Note:** `fitContent` is only supported in `IconTabBar` mode. ### Example section ```jsx - -
+ +
+
+
+ +``` + +### Sections with subsections + +When a `fitContent` section contains `ObjectPageSubSection`s, keep them as direct children — don't wrap them. The section becomes the scroll container itself, so overflowing subsections scroll within it while the footer stays visible. + +```jsx + + +
+ + +
+ ``` diff --git a/packages/main/src/components/ObjectPage/ObjectPage.module.css b/packages/main/src/components/ObjectPage/ObjectPage.module.css index 87132f90837..aabd388e528 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.module.css +++ b/packages/main/src/components/ObjectPage/ObjectPage.module.css @@ -128,6 +128,13 @@ z-index: 0; } +/* compound selector for specificity over the base .content rule */ +.content.fitContent { + display: flex; + flex-direction: column; + min-height: 0; +} + @container (max-width: 599px) { .header, .headerContainer { diff --git a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx index fc046a78df9..7ec697c637b 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx @@ -12,6 +12,8 @@ import { fn } from 'storybook/test'; import { Toolbar as LegacyToolbar, ToolbarSpacer as LegacyToolbarSpacer } from '../../../../compat/src/index.js'; import type { ObjectPageDomRef } from '../../index.js'; import { + AnalyticalTable, + AnalyticalTableVisibleRowCountMode, Bar, Breadcrumbs, BreadcrumbsItem, @@ -33,6 +35,12 @@ import { ObjectPageMode, ObjectPageSection, ObjectPageSubSection, + Table, + TableCell, + TableGrowing, + TableHeaderCell, + TableHeaderRow, + TableRow, Text, Title, Toolbar, @@ -41,6 +49,18 @@ import { import { Tag } from '../../webComponents/Tag/index.js'; import { ObjectPage } from './index.js'; +const tableRows = Array.from({ length: 60 }, (_, i) => ({ + name: `Employee ${i + 1}`, + role: i % 2 ? 'Developer' : 'Designer', + location: i % 3 ? 'Walldorf' : 'San Jose', +})); + +const tableColumns = [ + { accessor: 'name', Header: 'Name' }, + { accessor: 'role', Header: 'Role' }, + { accessor: 'location', Header: 'Location' }, +]; + const meta = { title: 'Layouts & Floorplans / ObjectPage', component: ObjectPage, @@ -426,22 +446,87 @@ export const SectionWithCustomHeader: Story = { }; export const FullScreenSingleSection: Story = { - args: { selectedSectionId: 'section1' }, name: 'with fullscreen section', render(args) { return ( - +
- It is recommended to only use fullscreen sections in TabBar mode, otherwise your layout will most probably - break! + It is recommended to only use fullscreen sections in `IconTabBar` mode, otherwise your layout will most + probably break! +
+
+ +
+
- -
+ + + Name + Role + Location + + } + features={} + > + {tableRows.map((row, i) => ( + + + {row.name} + + + {row.role} + + + {row.location} + + + ))} +
+
+ +
+
+
- -
+ + +
+ Fullscreen section with subsections. The content fits, so nothing scrolls. +
+
+ +
More content that still fits.
+
+
+ + {Array.from({ length: 5 }, (_, i) => ( + +
+ Tall subsection content — the section scrolls internally while the footer stays visible. +
+
+ ))} +
+ +
+ This section doesn't use `fitContent`, so it grows with its content and the page scrolls normally. +
+
+ +
+ This section doesn't use `fitContent` and its content exceeds the viewport, so the standard page scroll + is visible. +
); diff --git a/packages/main/src/components/ObjectPage/index.tsx b/packages/main/src/components/ObjectPage/index.tsx index 2df6f2c6b7b..bceee0fd3b2 100644 --- a/packages/main/src/components/ObjectPage/index.tsx +++ b/packages/main/src/components/ObjectPage/index.tsx @@ -124,6 +124,10 @@ const ObjectPage = forwardRef((props, ref () => (mode === ObjectPageMode.IconTabBar ? getSectionById(children, internalSelectedSectionId) : null), [mode, children, internalSelectedSectionId], ); + const isActiveSectionFitContent = + mode === ObjectPageMode.IconTabBar && + isValidElement(currentTabModeSection) && + !!currentTabModeSection.props.fitContent; const [toggledCollapsedHeaderWasVisible, setToggledCollapsedHeaderWasVisible] = useState(false); const sections = mode === ObjectPageMode.IconTabBar ? currentTabModeSection : children; const scrollEndHandler = useOnScrollEnd({ objectPageRef, setTabSelectId }); @@ -261,6 +265,19 @@ const ObjectPage = forwardRef((props, ref const section = getSectionElementById(objectPageRef.current, isSubSection, id); scrollTimeout.current = performance.now() + 500; if (section) { + // fitContent sections with subsections are their own scroll container, so scroll the section, not the ObjectPage + const fitContentScroller = + isSubSection && isActiveSectionFitContent + ? section.closest('[data-component-name="ObjectPageSection"]') + : null; + if (fitContentScroller && fitContentScroller.scrollHeight > fitContentScroller.clientHeight) { + section.focus({ preventScroll: true }); + const sectionRect = section.getBoundingClientRect(); + const scrollerRect = fitContentScroller.getBoundingClientRect(); + fitContentScroller.scrollTop = sectionRect.top - scrollerRect.top + fitContentScroller.scrollTop; + return; + } + const safeTopHeaderHeight = topHeaderHeight || prevTopHeaderHeight.current; const scrollMargin = @@ -299,6 +316,7 @@ const ObjectPage = forwardRef((props, ref headerPinned, headerCollapsed, headerContentHeight, + isActiveSectionFitContent, ], ); @@ -892,7 +910,7 @@ const ObjectPage = forwardRef((props, ref )}
{ if (node) { if (mode === ObjectPageMode.IconTabBar && wasUserSectionChange) { @@ -924,9 +942,9 @@ const ObjectPage = forwardRef((props, ref aria-hidden="true" /> {placeholder ? placeholder : sections} -