diff --git a/packages/main/src/components/AnalyticalTable/types/index.ts b/packages/main/src/components/AnalyticalTable/types/index.ts index 1d622ba829a..96b416779cc 100644 --- a/packages/main/src/components/AnalyticalTable/types/index.ts +++ b/packages/main/src/components/AnalyticalTable/types/index.ts @@ -1030,6 +1030,7 @@ export interface AnalyticalTablePropTypes extends Omit { nativeDetail: number; }>, ) => void; + //todo: add cursor pointer when this prop is active in next major version update. /** * Fired when a row is clicked */ diff --git a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx index cba4e1b224d..7e01ac6b534 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.cy.tsx @@ -662,29 +662,52 @@ describe('ObjectPage', () => { cy.findByTestId('footer').should('be.visible'); }); - it('single section - Default mode', () => { - document.body.style.margin = '0px'; - const TestComp = ({ - mode, - height, - withFooter, - }: { - height: CSSProperties['height']; - withFooter?: boolean; - mode: ObjectPageMode; - }) => { - const ref = useRef(null); - const [showCurrentHeights, setShowCurrentHeights] = useState({ offset: null, scroll: null }); - return ( - + const TestSingleSectionComp = ({ + mode, + height, + withFooter, + withSubSections, + }: { + height: CSSProperties['height']; + withFooter?: boolean; + mode: ObjectPageMode; + withSubSections?: boolean; + }) => { + const ref = useRef(null); + const [showCurrentHeights, setShowCurrentHeights] = useState({ offset: null, scroll: null }); + return ( + + {withSubSections ? ( + + +
+ + {JSON.stringify(showCurrentHeights)} +
+
+ +
+
+
+ ) : (
-
- ); - }; - cy.mount(); + )} +
+ ); + }; + + it('single section - Default mode', () => { + document.body.style.margin = '0px'; + + cy.mount(); + + cy.get('[data-component-name="ObjectPageTabContainerPlaceholder"]').should('exist'); + cy.get('[data-component-name="ObjectPageTabContainer"]').should('not.exist'); + cy.findByText('Update Heights').click(); - cy.findByText('{"offset":1080,"scroll":2330}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2280}').should('exist'); cy.findByTestId('op').scrollTo('bottom'); cy.findByText('Update Heights').click({ force: true }); - cy.findByText('{"offset":1080,"scroll":2330}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2280}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); - cy.findByText('{"offset":1080,"scroll":2370}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2320}').should('exist'); cy.findByTestId('op').scrollTo('bottom'); cy.findByText('Update Heights').click({ force: true }); - cy.findByText('{"offset":1080,"scroll":2370}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2320}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); @@ -732,7 +764,7 @@ describe('ObjectPage', () => { cy.findByText('Update Heights').click({ force: true }); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); @@ -744,7 +776,7 @@ describe('ObjectPage', () => { cy.findByText('Update Heights').click({ force: true }); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('https://github.com/UI5/webcomponents-react').should('be.visible'); cy.wait(50); @@ -757,67 +789,36 @@ describe('ObjectPage', () => { cy.get('[data-component-name="ObjectPageAnchorBarExpandBtn"]').click(); cy.findByText('https://github.com/UI5/webcomponents-react').should('not.be.visible'); + + cy.log('with subsections'); + cy.mount(); + cy.get('[data-component-name="ObjectPageTabContainerPlaceholder"]').should('exist'); + cy.get('[data-component-name="ObjectPageTabContainer"]').should('not.exist'); }); it('single section - Tab mode', () => { document.body.style.margin = '0px'; - const TestComp = ({ - mode, - height, - withFooter, - }: { - height: CSSProperties['height']; - withFooter?: boolean; - mode: ObjectPageMode; - }) => { - const ref = useRef(null); - const [showCurrentHeights, setShowCurrentHeights] = useState({ offset: null, scroll: null }); - return ( - - -
- - {JSON.stringify(showCurrentHeights)} -
-
-
- ); - }; - cy.mount(); + cy.mount(); + + cy.get('[data-component-name="ObjectPageTabContainerPlaceholder"]').should('exist'); + cy.get('[data-component-name="ObjectPageTabContainer"]').should('not.exist'); + cy.findByText('Update Heights').click(); - cy.findByText('{"offset":1080,"scroll":2290}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2240}').should('exist'); cy.findByTestId('op').scrollTo('bottom'); cy.findByText('Update Heights').click({ force: true }); - cy.findByText('{"offset":1080,"scroll":2290}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2240}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); - cy.findByText('{"offset":1080,"scroll":2350}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2300}').should('exist'); cy.findByTestId('op').scrollTo('bottom'); cy.findByText('Update Heights').click({ force: true }); - cy.findByText('{"offset":1080,"scroll":2350}').should('exist'); + cy.findByText('{"offset":1080,"scroll":2300}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); @@ -829,7 +830,7 @@ describe('ObjectPage', () => { cy.findByText('Update Heights').click({ force: true }); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('Update Heights').click(); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); @@ -841,7 +842,7 @@ describe('ObjectPage', () => { cy.findByText('Update Heights').click({ force: true }); cy.findByText('{"offset":1080,"scroll":1080}').should('exist'); - cy.mount(); + cy.mount(); cy.findByText('https://github.com/UI5/webcomponents-react').should('be.visible'); cy.wait(50); @@ -854,7 +855,13 @@ describe('ObjectPage', () => { cy.get('[data-component-name="ObjectPageAnchorBarExpandBtn"]').click(); cy.findByText('https://github.com/UI5/webcomponents-react').should('not.be.visible'); + + cy.log('with subsections'); + cy.mount(); + cy.get('[data-component-name="ObjectPageTabContainerPlaceholder"]').should('exist'); + cy.get('[data-component-name="ObjectPageTabContainer"]').should('not.exist'); }); + [ObjectPageMode.Default, ObjectPageMode.IconTabBar].forEach((mode) => { it(`ObjectPageSection/SubSection: Custom header & hideTitleText (mode: ${mode})`, () => { document.body.style.margin = '0px'; diff --git a/packages/main/src/components/ObjectPage/ObjectPage.mdx b/packages/main/src/components/ObjectPage/ObjectPage.mdx index 244b7ae1c93..f0d929e8492 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.mdx +++ b/packages/main/src/components/ObjectPage/ObjectPage.mdx @@ -88,6 +88,12 @@ To render a single section in fullscreen mode, set its height to `100%`. ``` +## ObjectPage with single section + +When only a single section is available, the tabbar is hidden. + + + ## Opening popover components by pressing an action Please see the [Docs](?path=/docs/layouts-floorplans-toolbar--docs#open-popovers-with-toolbarbutton) of the `Toolbar` component. diff --git a/packages/main/src/components/ObjectPage/ObjectPage.module.css b/packages/main/src/components/ObjectPage/ObjectPage.module.css index c163698ddcf..4e65a0756b6 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.module.css +++ b/packages/main/src/components/ObjectPage/ObjectPage.module.css @@ -104,6 +104,13 @@ background: var(--sapObjectHeader_Background); } +.tabContainerPlaceholder { + composes: tabContainer; + box-shadow: var(--sapContent_HeaderShadow); + height: 1px; + flex-shrink: 0; +} + .tabContainerComponent { &::part(content) { display: none; diff --git a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx index e339c20e9ae..bf8e26d8e79 100644 --- a/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx +++ b/packages/main/src/components/ObjectPage/ObjectPage.stories.tsx @@ -56,7 +56,6 @@ const meta = { }, args: { mode: ObjectPageMode.Default, - selectedSectionId: 'goals', imageShapeCircle: true, image: SampleImage, style: { height: '700px', maxHeight: '90vh' }, @@ -451,6 +450,93 @@ export const FullScreenSingleSection: Story = { }, }; +export const SingleSection: Story = { + name: 'with single section', + render(args) { + return ( + + + +
+ Job Classification}> + + Senior UI Developer + + + + Job Title}> + Developer + + Employee Class}> + Employee + + Manager}> + + Dan Smith + + + + Pay Grade}> + Salary Grade 18 (GR-14) + + FTE}> + 1 + +
+
+ +
+ Start Date}> + Jan 01, 2018 + + End Date}> + Dec 31, 9999 + + Payroll Start Date}> + Jan 01, 2018 + + Benefits Start Date}> + Jul 01, 2018 + + Company Car Eligibility}> + Jan 01, 2021 + + Equity Start Date}> + Jul 01, 2018 + +
+
+ +
+ Manager}> + John Doe + + Scrum Master}> + Michael Adams + + Product Owner}> + John Miller + +
+
+
+
+ ); + }, +}; + export const LegacyToolbarSupport: Story = { render(args) { const objectPageRef = useRef(null); diff --git a/packages/main/src/components/ObjectPage/index.tsx b/packages/main/src/components/ObjectPage/index.tsx index 2aa24cca808..bfd9ae0c18e 100644 --- a/packages/main/src/components/ObjectPage/index.tsx +++ b/packages/main/src/components/ObjectPage/index.tsx @@ -11,7 +11,17 @@ import { } from '@ui5/webcomponents-react-base'; import { clsx } from 'clsx'; import type { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, UIEventHandler } from 'react'; -import { cloneElement, forwardRef, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + cloneElement, + Children, + forwardRef, + isValidElement, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { ObjectPageMode } from '../../enums/ObjectPageMode.js'; import { safeGetChildrenArray } from '../../internal/safeGetChildrenArray.js'; import { useObserveHeights } from '../../internal/useObserveHeights.js'; @@ -86,6 +96,8 @@ const ObjectPage = forwardRef((props, ref ); const [tabSelectId, setTabSelectId] = useState(null); const titleAreaInteractive = headerArea && !preserveHeaderStateOnClick; + const hasOnlySingleSection = Children.count(children) === 1; + const tabContainerHeaderHeight = hasOnlySingleSection ? 1 : TAB_CONTAINER_HEADER_HEIGHT; const isProgrammaticallyScrolled = useRef(false); const [componentRef, objectPageRef] = useSyncRef(ref); @@ -151,7 +163,7 @@ const ObjectPage = forwardRef((props, ref scrollTimeout, }, ); - const scrollPaddingBlock = `${Math.ceil(12 + topHeaderHeight + TAB_CONTAINER_HEADER_HEIGHT + (!headerCollapsed && headerPinned ? headerContentHeight : 0))}px ${footerArea ? 'calc(var(--_ui5wcr-BarHeight) + 1.25rem)' : 0}`; + const scrollPaddingBlock = `${Math.ceil(12 + topHeaderHeight + tabContainerHeaderHeight + (!headerCollapsed && headerPinned ? headerContentHeight : 0))}px ${footerArea ? 'calc(var(--_ui5wcr-BarHeight) + 1.25rem)' : 0}`; useEffect(() => { if (typeof onToggleHeaderArea === 'function' && isToggledRef.current) { @@ -208,7 +220,7 @@ const ObjectPage = forwardRef((props, ref const scrollMargin = -1 /* reduce margin-block so that intersection observer detects correct section*/ + safeTopHeaderHeight + - TAB_CONTAINER_HEADER_HEIGHT + + tabContainerHeaderHeight + (headerPinned && !headerCollapsed ? headerContentHeight : 0); section.style.scrollMarginBlockStart = scrollMargin + 'px'; if (isSubSection) { @@ -427,7 +439,16 @@ const ObjectPage = forwardRef((props, ref return () => { observer.disconnect(); }; - }, [topHeaderHeight, headerContentHeight, currentTabModeSection, children, mode, isHeaderPinnedAndExpanded]); + }, [ + topHeaderHeight, + headerContentHeight, + currentTabModeSection, + children, + mode, + isHeaderPinnedAndExpanded, + hasOnlySingleSection, + objectPageRef, + ]); const onToggleHeaderContentVisibility = (e) => { isToggledRef.current = true; @@ -460,7 +481,7 @@ const ObjectPage = forwardRef((props, ref } const sectionNodes = objectPageRef.current?.querySelectorAll('section[data-component-name="ObjectPageSection"]'); // only the sticky part of the header must be added as margin - const rootMargin = `-${((headerPinned && !headerCollapsed) || scrolledHeaderExpanded ? totalHeaderHeight : topHeaderHeight) + TAB_CONTAINER_HEADER_HEIGHT}px 0px 0px 0px`; + const rootMargin = `-${((headerPinned && !headerCollapsed) || scrolledHeaderExpanded ? totalHeaderHeight : topHeaderHeight) + tabContainerHeaderHeight}px 0px 0px 0px`; const observer = new IntersectionObserver( (entries) => { @@ -614,8 +635,8 @@ const ObjectPage = forwardRef((props, ref ...style, [ObjectPageCssVariables.fullHeaderHeight]: headerPinned || scrolledHeaderExpanded - ? `${topHeaderHeight + (headerCollapsed === true ? 0 : headerContentHeight) + TAB_CONTAINER_HEADER_HEIGHT}px` - : `${topHeaderHeight + TAB_CONTAINER_HEADER_HEIGHT}px`, + ? `${topHeaderHeight + (headerCollapsed === true ? 0 : headerContentHeight) + tabContainerHeaderHeight}px` + : `${topHeaderHeight + tabContainerHeaderHeight}px`, } as CSSProperties; if (headerCollapsed === true && headerArea) { objectPageStyles[ObjectPageCssVariables.titleFontSize] = ThemingParameters.sapObjectHeader_Title_SnappedFontSize; @@ -723,7 +744,21 @@ const ObjectPage = forwardRef((props, ref /> )} - {!placeholder && ( + {hasOnlySingleSection && ( +