Skip to content

Commit a7ecb3c

Browse files
fix(ObjectPage): support dynamically added sections (#604)
closes #591
1 parent 6c260d8 commit a7ecb3c

File tree

6 files changed

+80
-124
lines changed

6 files changed

+80
-124
lines changed

packages/main/src/components/ObjectPage/ObjectPage.jss.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters';
22

3-
const ObjectPageCssVariables = {
3+
export const ObjectPageCssVariables = {
44
anchorFloat: '--_ui5wcr_ObjectPage_actions_float',
55
anchorLeft: '--_ui5wcr_ObjectPage_actions_left',
66
anchorRight: '--_ui5wcr_ObjectPage_actions_right',
7-
avatarMargin: '--_ui5wcr_ObjectPage_avatar_margin'
7+
avatarMargin: '--_ui5wcr_ObjectPage_avatar_margin',
8+
lastSectionMargin: '--_ui5wcr_ObjectPage_last_section_margin_bottom'
89
};
910

1011
const styles = {
@@ -18,13 +19,17 @@ const styles = {
1819
backgroundColor: ThemingParameters.sapBackgroundColor,
1920
overflowX: 'hidden',
2021
overflowY: 'auto',
22+
[ObjectPageCssVariables.lastSectionMargin]: 0,
2123
'& section[id*="ObjectPageSection-"] > div[role="heading"]': {
2224
display: 'none'
2325
},
2426
// explanation why first-child selector is not sufficient here:
2527
// https://stackoverflow.com/questions/7128406/css-select-the-first-child-from-elements-with-particular-attribute
2628
'& section[id*="ObjectPageSection-"] ~ section[id*="ObjectPageSection-"] > div[role="heading"]': {
2729
display: 'block'
30+
},
31+
'& section[data-component-name="ObjectPageSection"]:last-child': {
32+
marginBottom: `var(${ObjectPageCssVariables.lastSectionMargin})`
2833
}
2934
},
3035
'@global html': {

packages/main/src/components/ObjectPage/ObjectPageAnchorBar.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,20 @@ const ObjectPageAnchorBar = forwardRef((props: Props, ref: RefObject<HTMLElement
121121
[setHeaderPinned]
122122
);
123123

124-
const onTabItemSelect = useCallback((event) => {
125-
const { sectionId, index } = event.detail.tab.dataset;
126-
// eslint-disable-next-line eqeqeq
127-
const section = safeGetChildrenArray(sections).find((el) => el.props.id == sectionId);
128-
handleOnSectionSelected(
129-
enrichEventWithDetails({} as any, {
130-
...section,
131-
index
132-
})
133-
);
134-
}, []);
124+
const onTabItemSelect = useCallback(
125+
(event) => {
126+
const { sectionId, index } = event.detail.tab.dataset;
127+
// eslint-disable-next-line eqeqeq
128+
const section = safeGetChildrenArray(sections).find((el) => el.props.id == sectionId);
129+
handleOnSectionSelected(
130+
enrichEventWithDetails({} as any, {
131+
...section,
132+
index
133+
})
134+
);
135+
},
136+
[sections]
137+
);
135138

136139
const onShowSubSectionPopover = useCallback(
137140
(e, section) => {

packages/main/src/components/ObjectPage/ObjectPageAnchorButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const ObjectPageAnchorButton: FC<ObjectPageAnchorPropTypes> = (props: Obj
2323
if (subSectionsAvailable) {
2424
try {
2525
const element = ref.current?.parentElement?.shadowRoot?.querySelector(
26-
`.ui5-tc__headerList li[aria-posinset="${index + 1}"] .ui5-tc__headerItemContent`
26+
`.ui5-tc__headerList li[aria-posinset="${index + 1}"] .ui5-tab-strip-itemContent`
2727
);
2828

2929
if (element && !element.querySelector('ui5-icon')) {
Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,20 @@
1-
import { Children, ReactElement } from 'react';
1+
import { Children, ReactElement, RefObject } from 'react';
22

33
export const safeGetChildrenArray = <T = any>(children): T[] => Children.toArray(children).filter(Boolean);
44

5-
// export const findSectionIndexById = (sections: ReactElement<any> | Array<ReactElement<any>>, id) => {
6-
// const index = safeGetChildrenArray(sections).findIndex((objectPageSection) => objectPageSection.props?.id === id);
7-
// if (index === -1) {
8-
// return 0;
9-
// }
10-
// return index;
11-
// };
12-
135
export const getSectionById = (sections: ReactElement<any> | ReactElement<any>[], id) => {
146
return safeGetChildrenArray(sections).find((objectPageSection) => objectPageSection.props?.id === id);
157
};
168

17-
// export const getProportionateScrollTop = (activeContainer, passiveContainer, base) => {
18-
// const activeHeight = activeContainer.current.getBoundingClientRect().height;
19-
// const passiveHeight = passiveContainer.current.getBoundingClientRect().height;
20-
//
21-
// return (base / activeHeight) * passiveHeight;
22-
// };
23-
//
24-
// export const bindScrollEvent = (scrollContainer, handler) => {
25-
// if (scrollContainer.current && handler.current) {
26-
// scrollContainer.current.addEventListener('scroll', handler.current, { passive: true });
27-
// }
28-
// };
29-
//
30-
// export const removeScrollEvent = (scrollContainer, handler) => {
31-
// if (scrollContainer.current && handler.current) {
32-
// scrollContainer.current.removeEventListener('scroll', handler.current);
33-
// }
34-
// };
35-
369
export const extractSectionIdFromHtmlId = (id) => {
3710
return id.replace(/^ObjectPageSection-/, '');
3811
};
12+
13+
export const getLastObjectPageSection = (ref: RefObject<HTMLDivElement>): HTMLElement => {
14+
const sections = ref.current.querySelectorAll<HTMLElement>('[id^="ObjectPageSection"]');
15+
if (!sections || sections.length < 1) {
16+
return null;
17+
}
18+
19+
return sections[sections.length - 1];
20+
};

packages/main/src/components/ObjectPage/demo.stories.tsx

Lines changed: 38 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ObjectPageSubSection } from '@ui5/webcomponents-react/lib/ObjectPageSub
1010
import { Text } from '@ui5/webcomponents-react/lib/Text';
1111
import { Title } from '@ui5/webcomponents-react/lib/Title';
1212
import { TitleLevel } from '@ui5/webcomponents-react/lib/TitleLevel';
13-
import React from 'react';
13+
import React, { useState } from 'react';
1414
import SampleImage from './DemoImage.png';
1515
import { createSelectArgTypes } from '@shared/stories/createSelectArgTypes';
1616

@@ -106,81 +106,47 @@ export const renderDemo = (props) => {
106106
};
107107
renderDemo.storyName = 'Default';
108108

109-
export const renderComponentWithSections = (props) => (
110-
<ObjectPage
111-
title="Fiori Object Page Title"
112-
subTitle="Sub Title"
113-
headerActions={[<Button>Action</Button>]}
114-
image={SampleImage}
115-
headerContent={
116-
<>
117-
<div style={{ display: 'flex', flexDirection: 'column' }}>
118-
<Link href="https://www.sap.com">www.myurl.com</Link>
119-
<Text>Address 1</Text>
120-
</div>
121-
<div style={{ display: 'flex', flexDirection: 'column' }}>
122-
<Text>Address 2</Text>
123-
<Text>Address 3</Text>
124-
</div>
125-
</>
126-
}
127-
mode={props.mode}
128-
style={{ height: '700px' }}
129-
>
130-
<ObjectPageSection title="Test 1" id="1">
131-
<Label>My Content 1</Label>
132-
</ObjectPageSection>
133-
<ObjectPageSection title="Test 2" id="2">
134-
<Label>My Content 2</Label>
135-
</ObjectPageSection>
136-
<ObjectPageSection title="Test 3" id="3">
137-
<Label>My Content 3</Label>
138-
</ObjectPageSection>
139-
</ObjectPage>
140-
);
141-
renderComponentWithSections.storyName = 'with Sections Only';
109+
export const renderComponentWithSections = (props) => {
110+
const [numberOfSections, setNumberOfSections] = useState(3);
142111

143-
export const renderShortContent = (props) => {
144112
return (
145-
<div style={{ width: 'calc(100% - 1rem)', height: '100%', position: 'relative', marginTop: '2rem' }}>
146-
<ObjectPage
147-
title={props.title}
148-
subTitle={props.subTitle}
149-
headerActions={[
150-
<Button key="1" design={ButtonDesign.Emphasized}>
151-
Primary Action
152-
</Button>,
153-
<Button key="2">Action</Button>
154-
]}
155-
image={SampleImage}
156-
headerContent={
157-
<>
158-
<div style={{ display: 'flex', flexDirection: 'column' }}>
159-
<Link href="https://www.sap.com">www.myurl.com</Link>
160-
<Text>Address 1</Text>
161-
</div>
162-
<div style={{ display: 'flex', flexDirection: 'column' }}>
163-
<Text>Address 2</Text>
164-
<Text>Address 3</Text>
165-
</div>
166-
</>
167-
}
168-
mode={props.mode}
169-
imageShapeCircle={props.imageShapeCircle}
170-
showHideHeaderButton={props.showHideHeaderButton}
171-
selectedSectionId={props.selectedSectionId}
172-
onSelectedSectionChanged={props.onSelectedSectionChanged}
173-
noHeader={props.noHeader}
174-
style={{ height: '700px' }}
175-
>
176-
<ObjectPageSection title="Test 1" id="1">
177-
<div>My Content 1</div>
178-
</ObjectPageSection>
179-
</ObjectPage>
180-
</div>
113+
<ObjectPage
114+
title="Fiori Object Page Title"
115+
subTitle="Sub Title"
116+
headerActions={[
117+
<Button key="add-section" onClick={() => setNumberOfSections((old) => old + 1)}>
118+
Add Section
119+
</Button>
120+
]}
121+
image={SampleImage}
122+
headerContent={
123+
<>
124+
<div style={{ display: 'flex', flexDirection: 'column' }}>
125+
<Link href="https://www.sap.com">www.myurl.com</Link>
126+
<Text>Address 1</Text>
127+
</div>
128+
<div style={{ display: 'flex', flexDirection: 'column' }}>
129+
<Text>Address 2</Text>
130+
<Text>Address 3</Text>
131+
</div>
132+
</>
133+
}
134+
mode={props.mode}
135+
style={{ height: '700px' }}
136+
>
137+
{Array(numberOfSections)
138+
.fill('')
139+
.map((_, index) => {
140+
return (
141+
<ObjectPageSection title={`Section ${index + 1}`} id={`${index}`} key={index}>
142+
<Label>Section Content {index + 1}</Label>
143+
</ObjectPageSection>
144+
);
145+
})}
146+
</ObjectPage>
181147
);
182148
};
183-
renderShortContent.storyName = 'Short Content';
149+
renderComponentWithSections.storyName = 'with Sections Only';
184150

185151
export default {
186152
title: 'Components / ObjectPage',

packages/main/src/components/ObjectPage/index.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@ import React, {
3333
import { ObjectPageSectionPropTypes } from '../ObjectPageSection';
3434
import { ObjectPageSubSectionPropTypes } from '../ObjectPageSubSection';
3535
import { CollapsedAvatar } from './CollapsedAvatar';
36-
import styles from './ObjectPage.jss';
36+
import styles, { ObjectPageCssVariables } from './ObjectPage.jss';
3737
import { ObjectPageAnchorBar } from './ObjectPageAnchorBar';
3838
import { ObjectPageHeader } from './ObjectPageHeader';
39-
import { extractSectionIdFromHtmlId, getSectionById, safeGetChildrenArray } from './ObjectPageUtils';
39+
import {
40+
extractSectionIdFromHtmlId,
41+
getLastObjectPageSection,
42+
getSectionById,
43+
safeGetChildrenArray
44+
} from './ObjectPageUtils';
4045
import { useObserveHeights } from './useObserveHeights';
4146

4247
declare const ResizeObserver;
@@ -253,12 +258,7 @@ const ObjectPage: FC<ObjectPagePropTypes> = forwardRef((props: ObjectPagePropTyp
253258
const fillerDivObserver = new ResizeObserver(() => {
254259
const maxHeight = Math.min(objectPageRef.current.clientHeight, window.innerHeight);
255260
const availableScrollHeight = maxHeight - totalHeaderHeight;
256-
const sections = objectPageRef.current.querySelectorAll('[id^="ObjectPageSection"]');
257-
if (!sections || sections.length < 1) {
258-
return;
259-
}
260-
261-
const lastSectionDomRef = sections[sections.length - 1] as HTMLElement;
261+
const lastSectionDomRef = getLastObjectPageSection(objectPageRef);
262262
const subSections = lastSectionDomRef.querySelectorAll('[id^="ObjectPageSubSection"]');
263263

264264
let lastSubSectionHeight;
@@ -273,15 +273,15 @@ const ObjectPage: FC<ObjectPagePropTypes> = forwardRef((props: ObjectPagePropTyp
273273
let heightDiff = availableScrollHeight - lastSubSectionHeight;
274274

275275
heightDiff = heightDiff > 0 ? heightDiff : 0;
276-
lastSectionDomRef.style.marginBottom = `${heightDiff}px`;
276+
objectPageRef.current.style.setProperty(ObjectPageCssVariables.lastSectionMargin, `${heightDiff}px`);
277277
});
278278

279279
fillerDivObserver.observe(objectPageRef.current);
280280

281281
return () => {
282282
fillerDivObserver.disconnect();
283283
};
284-
}, [totalHeaderHeight, objectPageRef]);
284+
}, [totalHeaderHeight, objectPageRef, children]);
285285

286286
const fireOnSelectedChangedEvent = debounce((e) => {
287287
onSelectedSectionChanged(

0 commit comments

Comments
 (0)