Skip to content

Commit 5adfc15

Browse files
fix(ObjectPage): Don't crash when conditional rendering is used for children (#284)
1 parent b269da3 commit 5adfc15

File tree

10 files changed

+371
-306
lines changed

10 files changed

+371
-306
lines changed

config/jest.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ module.exports = {
3434
transformIgnorePatterns: ['node_modules/(?!(@ui5|lit-html))'],
3535
moduleNameMapper: {
3636
'^@shared/(.*)$': '<rootDir>/shared/$1',
37-
'^@ui5/webcomponents-react/lib/(.*)$': '<rootDir>/packages/main/src/lib/$1',
38-
'^@ui5/webcomponents-react-base/lib/(.*)$': '<rootDir>/packages/base/src/lib/$1',
39-
'^@ui5/webcomponents-react-charts/lib/(.*)$': '<rootDir>/packages/charts/src/lib/$1',
37+
'^@ui5/webcomponents-react/(.*)$': '<rootDir>/packages/main/src/$1',
38+
'^@ui5/webcomponents-react-base/third-party/(.*)$': '<rootDir>/packages/base/third-party/$1',
39+
'^@ui5/webcomponents-react-base/(.*)$': '<rootDir>/packages/base/src/$1',
40+
'^@ui5/webcomponents-react-charts/(.*)$': '<rootDir>/packages/charts/src/$1',
4041
'\\.(css|less)$': 'identity-obj-proxy'
4142
},
4243
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],

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

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const styles = ({ parameters }: JSSTheme) => ({
3333
boxShadow: `inset 0 -0.0625rem ${parameters.sapUiObjectHeaderBorderColor}, inset 0 0.0625rem ${parameters.sapUiObjectHeaderBorderColor}`,
3434
display: 'flex',
3535
height: '2.75rem',
36-
minHeight: '2.75rem'
36+
minHeight: '2.75rem',
37+
position: 'relative'
3738
},
3839
sectionsContainer: {
3940
'&:before': {
@@ -58,32 +59,6 @@ const styles = ({ parameters }: JSSTheme) => ({
5859
fillerDiv: {
5960
backgroundColor: parameters.sapUiBaseBG
6061
},
61-
outerScrollbar: {
62-
position: 'absolute',
63-
right: 0,
64-
overflow: 'hidden',
65-
height: '100%',
66-
zIndex: ZIndex.ResponsivePopover,
67-
backgroundColor: parameters.sapUiObjectHeaderBackground,
68-
'& ::-webkit-scrollbar': {
69-
backgroundColor: '#ffffff'
70-
},
71-
'& ::-webkit-scrollbar-thumb': {
72-
backgroundColor: '#949494',
73-
'&:hover': {
74-
backgroundColor: '#8c8c8c'
75-
}
76-
},
77-
'& ::-webkit-scrollbar-corner': {
78-
backgroundColor: '#ffffff'
79-
}
80-
},
81-
innerScrollbar: {
82-
width: '34px',
83-
overflowY: 'scroll',
84-
overflowX: 'hidden',
85-
height: '100%'
86-
},
8762
// header
8863
header: {
8964
flexShrink: 0,
@@ -216,18 +191,6 @@ const styles = ({ parameters }: JSSTheme) => ({
216191
display: 'flex',
217192
flexDirection: 'row'
218193
},
219-
flexBoxRow: {
220-
display: 'flex',
221-
flexDirection: 'row'
222-
},
223-
flexBoxColumn: {
224-
display: 'flex',
225-
flexDirection: 'column'
226-
},
227-
flexBoxCenter: {
228-
display: 'flex',
229-
alignItems: 'center'
230-
},
231194
avatar: {
232195
marginRight: '1rem'
233196
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import '@ui5/webcomponents-icons/dist/icons/slim-arrow-down';
12
import { Event } from '@ui5/webcomponents-react-base/lib/Event';
23
import { ScrollLink } from '@ui5/webcomponents-react-base/lib/ScrollLink';
34
import { Icon } from '@ui5/webcomponents-react/lib/Icon';
@@ -50,7 +51,7 @@ const anchorButtonStyles = ({ parameters }: JSSTheme) => ({
5051
}
5152
}
5253
});
53-
const useStyles = createUseStyles<keyof ReturnType<typeof anchorButtonStyles>>(anchorButtonStyles, {
54+
const useStyles = createUseStyles(anchorButtonStyles, {
5455
name: 'ObjectPageAnchorButton'
5556
});
5657

@@ -136,7 +137,7 @@ export const ObjectPageAnchorButton: FC<ObjectPageAnchorPropTypes> = (props) =>
136137
onSetActive={onScrollActive}
137138
activeClass={classes.selected}
138139
alwaysToTop={index === 0}
139-
scrollOffset={45}
140+
scrollOffset={collapsedHeader ? 45 : -45}
140141
>
141142
<span className={classes.button}>{section.props.title}</span>
142143
</ScrollLink>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { AvatarSize } from '@ui5/webcomponents-react/lib/AvatarSize';
2+
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
3+
import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
4+
import React, { CSSProperties, FC, ReactElement } from 'react';
5+
import { safeGetChildrenArray } from './ObjectPageUtils';
6+
7+
interface Props {
8+
image: string | ReactElement<unknown>;
9+
imageShapeCircle: boolean;
10+
classes: any;
11+
showTitleInHeaderContent: boolean;
12+
renderHeaderContentProp: () => JSX.Element;
13+
renderBreadcrumbs: () => JSX.Element;
14+
renderKeyInfos: () => JSX.Element;
15+
title: string;
16+
subTitle: string;
17+
}
18+
19+
const positionRelativeStyle: CSSProperties = { position: 'relative' };
20+
21+
export const ObjectPageHeader: FC<Props> = (props) => {
22+
const {
23+
image,
24+
classes,
25+
imageShapeCircle,
26+
showTitleInHeaderContent,
27+
renderHeaderContentProp,
28+
renderBreadcrumbs,
29+
title,
30+
subTitle,
31+
renderKeyInfos
32+
} = props;
33+
34+
let avatar = null;
35+
36+
if (image) {
37+
if (typeof image === 'string') {
38+
avatar = (
39+
<span
40+
className={classes.headerImage}
41+
style={{ borderRadius: imageShapeCircle ? '50%' : 0, overflow: 'hidden' }}
42+
>
43+
<img src={image} className={classes.image} alt="Company Logo" />
44+
</span>
45+
);
46+
} else {
47+
avatar = React.cloneElement(image, {
48+
size: AvatarSize.L,
49+
className: image.props?.className ? `${classes.headerImage} ${image.props?.className}` : classes.headerImage
50+
} as unknown);
51+
}
52+
}
53+
54+
if (showTitleInHeaderContent) {
55+
const headerContents = renderHeaderContentProp && renderHeaderContentProp();
56+
let firstElement;
57+
let contents = [];
58+
59+
if (headerContents?.type === React.Fragment) {
60+
[firstElement, ...contents] = safeGetChildrenArray(headerContents.props.children);
61+
} else {
62+
firstElement = headerContents;
63+
}
64+
return (
65+
<div className={classes.contentHeader}>
66+
<div className={classes.headerContent}>
67+
<FlexBox>
68+
{avatar}
69+
<FlexBox direction={FlexBoxDirection.Column}>
70+
<div>{renderBreadcrumbs && renderBreadcrumbs()}</div>
71+
<FlexBox>
72+
<FlexBox direction={FlexBoxDirection.Column}>
73+
<h1 className={classes.title}>{title}</h1>
74+
<span className={classes.subTitle}>{subTitle}</span>
75+
<span> {firstElement}</span>
76+
</FlexBox>
77+
<FlexBox>
78+
{contents.map((c, index) => (
79+
<div key={`customContent-${index}`} className={classes.headerCustomContentItem}>
80+
{c}
81+
</div>
82+
))}
83+
</FlexBox>
84+
<div className={classes.keyInfos}>{renderKeyInfos && renderKeyInfos()}</div>
85+
</FlexBox>
86+
</FlexBox>
87+
</FlexBox>
88+
</div>
89+
</div>
90+
);
91+
}
92+
93+
return (
94+
<div style={positionRelativeStyle} className={classes.contentHeader}>
95+
<div className={classes.headerContent}>
96+
{avatar}
97+
{renderHeaderContentProp && <span className={classes.headerCustomContent}>{renderHeaderContentProp()}</span>}
98+
</div>
99+
</div>
100+
);
101+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { ZIndex } from '@ui5/webcomponents-react/enums/ZIndex';
2+
import { JSSTheme } from '@ui5/webcomponents-react/interfaces/JSSTheme';
3+
import React, { FC, RefObject, useMemo } from 'react';
4+
import { createUseStyles } from 'react-jss';
5+
6+
interface Props {
7+
scrollBarRef: RefObject<HTMLDivElement>;
8+
innerScrollBarRef: RefObject<HTMLDivElement>;
9+
width: number;
10+
}
11+
12+
const styles = ({ parameters }: JSSTheme) => ({
13+
outerScrollbar: {
14+
position: 'absolute',
15+
right: 0,
16+
overflow: 'hidden',
17+
height: '100%',
18+
zIndex: ZIndex.ResponsivePopover,
19+
backgroundColor: parameters.sapUiObjectHeaderBackground,
20+
'& ::-webkit-scrollbar': {
21+
backgroundColor: '#ffffff'
22+
},
23+
'& ::-webkit-scrollbar-thumb': {
24+
backgroundColor: '#949494',
25+
'&:hover': {
26+
backgroundColor: '#8c8c8c'
27+
}
28+
},
29+
'& ::-webkit-scrollbar-corner': {
30+
backgroundColor: '#ffffff'
31+
}
32+
},
33+
innerScrollbar: {
34+
width: '34px',
35+
overflowY: 'scroll',
36+
overflowX: 'hidden',
37+
height: '100%'
38+
}
39+
});
40+
41+
const useScrollBarStyles = createUseStyles(styles, { name: 'ObjectPageScrollBar' });
42+
43+
export const ObjectPageScrollBar: FC<Props> = (props) => {
44+
const { scrollBarRef, innerScrollBarRef, width } = props;
45+
46+
const [scrollBarWidthStyle, scrollBarWidthMargin] = useMemo(() => {
47+
return [{ width: `${width}px` }, { marginLeft: `-${width}px`, width: `${2 * width}px` }];
48+
}, [width]);
49+
50+
const classes = useScrollBarStyles();
51+
52+
return (
53+
<div style={scrollBarWidthStyle} className={classes.outerScrollbar}>
54+
<div ref={scrollBarRef} style={scrollBarWidthMargin} className={classes.innerScrollbar}>
55+
<div ref={innerScrollBarRef} style={scrollBarWidthStyle} />
56+
</div>
57+
</div>
58+
);
59+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Children, ReactElement } from 'react';
2+
3+
export const safeGetChildrenArray = (children) => Children.toArray(children).filter(Boolean);
4+
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+
13+
export const getProportionateScrollTop = (activeContainer, passiveContainer, base) => {
14+
const activeHeight = activeContainer.current.getBoundingClientRect().height;
15+
const passiveHeight = passiveContainer.current.getBoundingClientRect().height;
16+
17+
return (base / activeHeight) * passiveHeight;
18+
};
19+
20+
export const bindScrollEvent = (scrollContainer, handler) => {
21+
if (scrollContainer.current && handler.current) {
22+
scrollContainer.current.addEventListener('scroll', handler.current, { passive: true });
23+
}
24+
};
25+
26+
export const removeScrollEvent = (scrollContainer, handler) => {
27+
if (scrollContainer.current && handler.current) {
28+
scrollContainer.current.removeEventListener('scroll', handler.current);
29+
}
30+
};

0 commit comments

Comments
 (0)