Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
nativeDetail: number;
}>,
) => void;
//todo: add cursor pointer when this prop is active in next major version update.
/**
* Fired when a row is clicked
*/
Expand Down
286 changes: 187 additions & 99 deletions packages/main/src/components/ObjectPage/ObjectPage.cy.tsx

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions packages/main/src/components/ObjectPage/ObjectPage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ To render a single section in fullscreen mode, set its height to `100%`.
</ObjectPageSection>
```

## ObjectPage with single section

When only a single section is available, the tabbar is hidden.

<Canvas of={ComponentStories.SingleSection} />

## 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.
Expand Down
28 changes: 12 additions & 16 deletions packages/main/src/components/ObjectPage/ObjectPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
container: objectPage / inline-size;
--_ui5wcr_ObjectPage_header_display: block;
--_ui5wcr_ObjectPage_title_fontsize: var(--sapObjectHeader_Title_FontSize);
--_ui5wcr_ObjectPage_header_height: 0;

box-sizing: border-box;
position: relative;
Expand All @@ -23,19 +24,6 @@
&[data-in-iframe='true'] {
scroll-behavior: auto;
}

/*invisible first heading*/
section[id*='ObjectPageSection-']:first-of-type > div[role='heading'] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
white-space: nowrap;
}
}

.iconTabBarMode section[data-component-name='ObjectPageSection'] > div[role='heading'] {
Expand All @@ -49,7 +37,7 @@
background-color: var(--sapObjectHeader_Background);
position: sticky;
inset-block-start: 0;
z-index: 4;
z-index: 5;
cursor: pointer;
display: grid;
grid-auto-columns: 100%;
Expand Down Expand Up @@ -102,7 +90,7 @@

.anchorBar {
position: sticky;
z-index: 4;
z-index: 5;
}

.tabContainerSpacer {
Expand All @@ -112,10 +100,17 @@

.tabContainer {
position: sticky;
z-index: 3;
z-index: 4;
background: var(--sapObjectHeader_Background);
}

.tabContainerPlaceholder {
composes: tabContainer;
box-shadow: var(--sapContent_HeaderShadow);
height: 1px;
flex-shrink: 0;
}

.tabContainerComponent {
&::part(content) {
display: none;
Expand Down Expand Up @@ -169,6 +164,7 @@
position: sticky;
inset-block-end: 0.5rem;
margin: 0 0.5rem;
z-index: 4;
}

.footerSpacer {
Expand Down
151 changes: 149 additions & 2 deletions packages/main/src/components/ObjectPage/ObjectPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
},
args: {
mode: ObjectPageMode.Default,
selectedSectionId: 'goals',
imageShapeCircle: true,
image: SampleImage,
style: { height: '700px', maxHeight: '90vh' },
Expand Down Expand Up @@ -345,7 +344,68 @@
aria-label="Personal"
header={<MessageStrip hideCloseButton>Custom Header Section Two</MessageStrip>}
>
<div style={{ width: '100%', height: '500px', background: 'cadetblue' }} />
<ObjectPageSubSection
titleText="Connect"
id="personal-connect"
aria-label="Connect"
actions={
<>
<Button design={ButtonDesign.Emphasized} style={{ minWidth: '120px' }}>
Custom Action
</Button>
<Button design={ButtonDesign.Transparent} icon="action-settings" tooltip="settings" />
<Button design={ButtonDesign.Transparent} icon="download" tooltip="download" />
</>
}
>
<Form style={{ alignItems: 'baseline' }}>
<FormGroup headerText="Phone Numbers">
<FormItem labelContent={<Label showColon>Home</Label>}>
<Text>+1 234-567-8901</Text>
<Text>+1 234-567-5555</Text>
</FormItem>
</FormGroup>
<FormGroup headerText="Social Accounts">
<FormItem labelContent={<Label showColon>LinkedIn</Label>}>
<Text>/DeniseSmith</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Twitter</Label>}>
<Text>@DeniseSmith</Text>
</FormItem>
</FormGroup>
<FormGroup headerText="Addresses">
<FormItem labelContent={<Label showColon>Home Address</Label>}>
<Text>2096 Mission Street</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Mailing Address</Label>}>
<Text>PO Box 32114</Text>
</FormItem>
</FormGroup>
<FormGroup headerText="Mailing Address">
<FormItem labelContent={<Label showColon>Work</Label>}>
<Text>[email protected]</Text>
</FormItem>
</FormGroup>
</Form>
</ObjectPageSubSection>
<ObjectPageSubSection
titleText="Payment Information"
id="personal-payment-information"
aria-label="Payment Information"
>
<Form>
<FormGroup headerText="Salary">
<FormItem labelContent={<Label showColon>Bank Transfer</Label>}>
<Text>Money Bank, Inc.</Text>
</FormItem>
</FormGroup>
<FormGroup headerText="Payment method for Expenses">
<FormItem labelContent={<Label showColon>Extra Travel Expenses</Label>}>
<Text>Cash 100 USD</Text>
</FormItem>
</FormGroup>
</Form>
</ObjectPageSubSection>
</ObjectPageSection>
<ObjectPageSection
titleText="Employment"
Expand Down Expand Up @@ -390,6 +450,93 @@
},
};

export const SingleSection: Story = {
name: 'with single section',
render(args) {
return (
<ObjectPage {...args}>
<ObjectPageSection titleText="Employment" id="employment" aria-label="Employment">
<ObjectPageSubSection
titleText="Job Information"
id="employment-job-information"
aria-label="Job Information"
>
<Form>
<FormItem labelContent={<Label showColon>Job Classification</Label>}>
<FlexBox direction={FlexBoxDirection.Column}>
<Text>Senior UI Developer</Text>
<Label>(UIDEV-SR)</Label>
</FlexBox>
</FormItem>
<FormItem labelContent={<Label showColon>Job Title</Label>}>
<Text>Developer</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Employee Class</Label>}>
<Text>Employee</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Manager</Label>}>
<FlexBox direction={FlexBoxDirection.Column}>
<Text>Dan Smith</Text>
<Label>Development Manager</Label>
</FlexBox>
</FormItem>
<FormItem labelContent={<Label showColon>Pay Grade</Label>}>
<Text>Salary Grade 18 (GR-14)</Text>
</FormItem>
<FormItem labelContent={<Label showColon>FTE</Label>}>
<Text>1</Text>
</FormItem>
</Form>
</ObjectPageSubSection>
<ObjectPageSubSection
titleText="Employee Details"
id="employment-employee-details"
aria-label="Employee Details"
>
<Form>
<FormItem labelContent={<Label showColon>Start Date</Label>}>
<Text>Jan 01, 2018</Text>
</FormItem>
<FormItem labelContent={<Label showColon>End Date</Label>}>
<Text>Dec 31, 9999</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Payroll Start Date</Label>}>
<Text>Jan 01, 2018</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Benefits Start Date</Label>}>
<Text>Jul 01, 2018</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Company Car Eligibility</Label>}>
<Text>Jan 01, 2021</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Equity Start Date</Label>}>
<Text>Jul 01, 2018</Text>
</FormItem>
</Form>
</ObjectPageSubSection>
<ObjectPageSubSection
titleText="Job Relationship"
id="employment-job-relationship"
aria-label="Job Relationship"
>
<Form>
<FormItem labelContent={<Label showColon>Manager</Label>}>
<Text>John Doe</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Scrum Master</Label>}>

Check notice on line 526 in packages/main/src/components/ObjectPage/ObjectPage.stories.tsx

View check run for this annotation

In Solidarity / Inclusive Language

Match Found

Please consider an alternative to `Master`. Possibilities include: `primary`, `main`, `leader`, `active`, `writer`
Raw output
/master/gi
<Text>Michael Adams</Text>
</FormItem>
<FormItem labelContent={<Label showColon>Product Owner</Label>}>
<Text>John Miller</Text>
</FormItem>
</Form>
</ObjectPageSubSection>
</ObjectPageSection>
</ObjectPage>
);
},
};

export const LegacyToolbarSupport: Story = {
render(args) {
const objectPageRef = useRef<ObjectPageDomRef>(null);
Expand Down
54 changes: 47 additions & 7 deletions packages/main/src/components/ObjectPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -38,6 +48,7 @@ import { useOnScrollEnd } from './useOnScrollEnd.js';
const ObjectPageCssVariables = {
headerDisplay: '--_ui5wcr_ObjectPage_header_display',
titleFontSize: '--_ui5wcr_ObjectPage_title_fontsize',
fullHeaderHeight: '--_ui5wcr_ObjectPage_header_height',
};

const TAB_CONTAINER_HEADER_HEIGHT = 44 + 4; // tabbar height + custom 4px padding-block-start
Expand Down Expand Up @@ -84,6 +95,8 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
selectedSectionId ?? firstSectionId,
);
const [tabSelectId, setTabSelectId] = useState<null | string>(null);
const hasOnlySingleSection = Children.count(children) === 1;
const tabContainerHeaderHeight = hasOnlySingleSection ? 1 : TAB_CONTAINER_HEADER_HEIGHT;

const isProgrammaticallyScrolled = useRef(false);
const [componentRef, objectPageRef] = useSyncRef(ref);
Expand Down Expand Up @@ -149,7 +162,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((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) {
Expand Down Expand Up @@ -206,7 +219,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((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) {
Expand Down Expand Up @@ -425,7 +438,16 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((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;
Expand Down Expand Up @@ -458,7 +480,7 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((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) => {
Expand Down Expand Up @@ -610,7 +632,11 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
});
const objectPageStyles: CSSProperties = {
...style,
};
[ObjectPageCssVariables.fullHeaderHeight]:
headerPinned || scrolledHeaderExpanded
? `${topHeaderHeight + (headerCollapsed === true ? 0 : headerContentHeight) + tabContainerHeaderHeight}px`
: `${topHeaderHeight + tabContainerHeaderHeight}px`,
} as CSSProperties;
if (headerCollapsed === true && headerArea) {
objectPageStyles[ObjectPageCssVariables.titleFontSize] = ThemingParameters.sapObjectHeader_Title_SnappedFontSize;
}
Expand Down Expand Up @@ -717,7 +743,21 @@ const ObjectPage = forwardRef<ObjectPageDomRef, ObjectPagePropTypes>((props, ref
/>
</div>
)}
{!placeholder && (
{hasOnlySingleSection && (
<div
ref={tabContainerContainerRef}
aria-hidden="true"
data-component-name="ObjectPageTabContainerPlaceholder"
className={classNames.tabContainerPlaceholder}
style={{
insetBlockStart:
headerPinned || scrolledHeaderExpanded
? `${topHeaderHeight + (headerCollapsed === true ? 0 : headerContentHeight)}px`
: `${topHeaderHeight}px`,
}}
/>
)}
{!placeholder && !hasOnlySingleSection && (
<div
ref={tabContainerContainerRef}
className={classNames.tabContainer}
Expand Down
Loading
Loading