Skip to content

Commit 0ef66dd

Browse files
authored
feat(Toolbar): initial component implementation (#543)
1 parent 826ef49 commit 0ef66dd

25 files changed

+779
-24
lines changed

config/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
transformIgnorePatterns: ['node_modules/(?!(@ui5|lit-html))'],
2727
moduleNameMapper: {
2828
'^@shared/(.*)$': '<rootDir>/shared/$1',
29+
'^@tests/(.*)$': '<rootDir>/shared/tests/$1',
2930
'^@ui5/webcomponents-react/dist/(.*)$': '<rootDir>/packages/main/dist/$1',
3031
'^@ui5/webcomponents-react/(.*)$': '<rootDir>/packages/main/src/$1',
3132
'^@ui5/webcomponents-react-base/third-party/(.*)$': '<rootDir>/packages/base/third-party/$1',

config/jestsetup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ResizeObserver from 'resize-observer-polyfill';
77
import 'intersection-observer';
88
import '@ui5/webcomponents/dist/generated/json-imports/i18n';
99
import 'whatwg-fetch';
10+
import '@testing-library/jest-dom';
1011

1112
// React 16 Enzyme adapter
1213
Enzyme.configure({ adapter: new Adapter() });

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@rollup/plugin-node-resolve": "^7.1.3",
5151
"@rollup/plugin-replace": "^2.3.2",
5252
"@storybook/storybook-deployer": "^2.8.5",
53+
"@testing-library/jest-dom": "^5.8.0",
5354
"@testing-library/react": "^10.0.4",
5455
"@types/enzyme": "^3.10.5",
5556
"@types/jest": "^25.2.3",

packages/base/src/styling/CssSizeVariables.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export enum CssSizeVariablesNames {
1515
sapWcrAnalyticalTableTreePaddingLevel2 = 'sapWcrAnalyticalTableTreePaddingLevel2',
1616
sapWcrAnalyticalTableTreePaddingLevel3 = 'sapWcrAnalyticalTableTreePaddingLevel3',
1717
sapWcrCheckBoxWidthHeight = 'sapWcrCheckBoxWidthHeight',
18-
sapWcrAnalyticalTableSelectionColumnWidth = 'sapWcrAnalyticalTableSelectionColumnWidth'
18+
sapWcrAnalyticalTableSelectionColumnWidth = 'sapWcrAnalyticalTableSelectionColumnWidth',
19+
sapWcrToolbarHeight = 'sapWcrToolbarHeight',
20+
sapWcrToolbarPopoverContentPadding = 'sapWcrToolbarPopoverContentPadding',
21+
sapWcrToolbarSeparatorHeight = 'sapWcrToolbarSeparatorHeight'
1922
}
2023

2124
export const CssSizeVariables: Record<CssSizeVariablesNames, string> = Object.values(CssSizeVariablesNames).reduce(
@@ -45,7 +48,9 @@ export const cssVariablesStyles = `
4548
--${CssSizeVariablesNames.sapWcrAnalyticalTableTreePaddingLevel3}:2.75rem;
4649
--${CssSizeVariablesNames.sapWcrCheckBoxWidthHeight}:2.75rem;
4750
--${CssSizeVariablesNames.sapWcrAnalyticalTableSelectionColumnWidth}:55px;
48-
51+
--${CssSizeVariablesNames.sapWcrToolbarHeight}:2.75rem;
52+
--${CssSizeVariablesNames.sapWcrToolbarPopoverContentPadding}:0.25rem 0.5rem;
53+
--${CssSizeVariablesNames.sapWcrToolbarSeparatorHeight}: 2rem;
4954
}
5055
5156
[data-ui5-compact-size],
@@ -67,5 +72,8 @@ export const cssVariablesStyles = `
6772
--${CssSizeVariablesNames.sapWcrAnalyticalTableTreePaddingLevel3}:2rem;
6873
--${CssSizeVariablesNames.sapWcrCheckBoxWidthHeight}:2rem;
6974
--${CssSizeVariablesNames.sapWcrAnalyticalTableSelectionColumnWidth}:40px;
75+
--${CssSizeVariablesNames.sapWcrToolbarHeight}:2rem;
76+
--${CssSizeVariablesNames.sapWcrToolbarPopoverContentPadding}:0.1875rem 0.375rem;
77+
--${CssSizeVariablesNames.sapWcrToolbarSeparatorHeight}: 1.5rem;
7078
}
7179
`;

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ import { GlobalStyleClassesStyles } from './GlobalStyleClasses.jss';
1111

1212
const useStyles = createComponentStyles(GlobalStyleClassesStyles);
1313

14-
declare global {
15-
interface Window {
16-
CSSVarsPonyfill: {
17-
cssVars: (options: any) => void;
18-
};
19-
}
20-
}
21-
2214
const cssVarsPonyfillNeeded = () => !!window.CSSVarsPonyfill;
2315

2416
export interface ThemeProviderProps {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import '@ui5/webcomponents-icons/dist/icons/overflow';
2+
import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters';
3+
import { ButtonDesign } from '@ui5/webcomponents-react/lib/ButtonDesign';
4+
import { PlacementType } from '@ui5/webcomponents-react/lib/PlacementType';
5+
import { Popover } from '@ui5/webcomponents-react/lib/Popover';
6+
import { ToggleButton } from '@ui5/webcomponents-react/lib/ToggleButton';
7+
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
8+
import { createPortal } from 'react-dom';
9+
10+
export function OverflowPopover(props) {
11+
const { lastVisibleIndex, contentClass, children } = props;
12+
const popoverRef = useRef();
13+
const [pressed, setPressed] = useState(false);
14+
15+
const handleToggleButtonClick = (e) => {
16+
if (popoverRef.current) {
17+
if (!pressed) {
18+
popoverRef.current.openBy(e.target);
19+
setPressed(true);
20+
} else {
21+
popoverRef.current.close();
22+
}
23+
}
24+
};
25+
26+
useEffect(() => {
27+
return () => {
28+
if (popoverRef.current) {
29+
popoverRef.current.close();
30+
}
31+
};
32+
}, []);
33+
34+
const handleClose = () => {
35+
setPressed(false);
36+
};
37+
38+
const renderChildren = useCallback(() => {
39+
return React.Children.toArray(children).map((item: ReactElement<any>, index) => {
40+
if (index > lastVisibleIndex) {
41+
if (item.type.displayName === 'ToolbarSeparator') {
42+
return React.cloneElement(item, {
43+
style: {
44+
height: '0.0625rem',
45+
margin: '0.375rem 0.1875rem',
46+
width: '100%',
47+
background: ThemingParameters.sapToolbar_SeparatorColor
48+
}
49+
});
50+
}
51+
return item;
52+
}
53+
return null;
54+
});
55+
}, [children, lastVisibleIndex]);
56+
57+
return (
58+
<>
59+
<ToggleButton
60+
design={ButtonDesign.Transparent}
61+
icon="sap-icon://overflow"
62+
onClick={handleToggleButtonClick}
63+
pressed={pressed}
64+
/>
65+
{createPortal(
66+
<Popover placementType={PlacementType.Bottom} ref={popoverRef} onAfterClose={handleClose}>
67+
<div className={contentClass}>{renderChildren()}</div>
68+
</Popover>,
69+
document.body
70+
)}
71+
</>
72+
);
73+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters';
2+
import { CssSizeVariables } from '@ui5/webcomponents-react-base/lib/CssSizeVariables';
3+
4+
export const styles = {
5+
outerContainer: {
6+
width: '100%',
7+
height: CssSizeVariables.sapWcrToolbarHeight,
8+
position: 'relative',
9+
overflow: 'hidden',
10+
display: 'flex',
11+
justifyContent: 'space-between',
12+
alignItems: 'center',
13+
borderBottom: `solid 0.0625rem ${ThemingParameters.sapGroup_TitleBorderColor}`
14+
},
15+
hasOverflow: {
16+
'& $toolbar': {
17+
maxWidth: 'calc(100% - 44px)'
18+
}
19+
},
20+
clear: {
21+
borderBottom: 'none'
22+
},
23+
active: {
24+
cursor: 'pointer',
25+
'&:active': {
26+
backgroundColor: ThemingParameters.sapActiveColor
27+
},
28+
'&:hover': {
29+
backgroundColor: ThemingParameters.sapList_Hover_Background
30+
}
31+
},
32+
info: {
33+
// This color is most similar to darken(@sapUiBaseColor, 10)
34+
backgroundColor: ThemingParameters.sapList_HeaderBorderColor,
35+
'&$active': {
36+
backgroundColor: ThemingParameters.sapInfobar_Active_Background,
37+
'&:active': {
38+
backgroundColor: `${ThemingParameters.sapInfobar_Active_Background}`
39+
},
40+
'&:hover': {
41+
backgroundColor: ThemingParameters.sapInfobar_Hover_Background
42+
}
43+
}
44+
},
45+
solid: {
46+
backgroundColor: ThemingParameters.sapBackgroundColor
47+
},
48+
transparent: {
49+
backgroundColor: ThemingParameters.sapToolbar_Background
50+
},
51+
toolbar: {
52+
width: '100%',
53+
'& >:first-child:not(.spacer)': {
54+
margin: '0 0.25rem 0 0'
55+
},
56+
'& >:last-child:not(.spacer)': {
57+
margin: '0 0.5rem 0 0.25rem'
58+
},
59+
'& > *:not(first-child):not(last-child):not(.spacer)': {
60+
margin: '0 0.25rem'
61+
},
62+
display: 'flex',
63+
alignItems: 'center',
64+
maxWidth: '100%'
65+
},
66+
overflowButtonContainer: {
67+
marginRight: '0.5rem'
68+
},
69+
popoverContent: {
70+
maxWidth: '20rem',
71+
padding: CssSizeVariables.sapWcrToolbarPopoverContentPadding,
72+
display: 'flex',
73+
flexDirection: 'column'
74+
}
75+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { action } from '@storybook/addon-actions';
2+
import { boolean, select } from '@storybook/addon-knobs';
3+
import React from 'react';
4+
import { Text } from '@ui5/webcomponents-react/lib/Text';
5+
import { ToolbarDesign } from '@ui5/webcomponents-react/lib/ToolbarDesign';
6+
import { Button } from '@ui5/webcomponents-react/lib/Button';
7+
import { ToolbarStyle } from '@ui5/webcomponents-react/lib/ToolbarStyle';
8+
import { ToolbarSeparator } from '@ui5/webcomponents-react/lib/ToolbarSeparator';
9+
import { ToolbarSpacer } from '@ui5/webcomponents-react/lib/ToolbarSpacer';
10+
import { Toolbar } from '@ui5/webcomponents-react/lib/Toolbar';
11+
12+
export const renderStory = () => {
13+
return (
14+
<Toolbar
15+
active={boolean('active', false)}
16+
toolbarStyle={select<ToolbarStyle>('toolbarStyle', ToolbarStyle, ToolbarStyle.Standard)}
17+
design={select<ToolbarDesign>('design', ToolbarDesign, ToolbarDesign.Auto)}
18+
onToolbarClick={action('onToolbarClick')}
19+
>
20+
<Text>Item1</Text>
21+
<Button
22+
onClick={(e) => {
23+
//use e.stopPropagation() to prevent event bubbling
24+
e.stopPropagation();
25+
}}
26+
>
27+
Item2
28+
</Button>
29+
<Button>Item3</Button>
30+
<Button>Item4</Button>
31+
<Button>Item5</Button>
32+
<Text>Item6</Text>
33+
<Button>Item8</Button>
34+
<Button>Item9</Button>
35+
<Button>Item10</Button>
36+
<Button>Item11</Button>
37+
<Button>Item12</Button>
38+
</Toolbar>
39+
);
40+
};
41+
42+
renderStory.story = {
43+
name: 'Default'
44+
};
45+
46+
export const withSpacerAndSeparator = () => {
47+
return (
48+
<Toolbar
49+
active={boolean('active', false)}
50+
toolbarStyle={select<ToolbarStyle>('toolbarStyle', ToolbarStyle, ToolbarStyle.Standard)}
51+
design={select<ToolbarDesign>('design', ToolbarDesign, ToolbarDesign.Auto)}
52+
onToolbarClick={action('onToolbarClick')}
53+
>
54+
<ToolbarSpacer />
55+
<Text>Item1</Text>
56+
<Button>Item2</Button>
57+
<ToolbarSpacer />
58+
<Button>Item3</Button>
59+
<Button>Item4</Button>
60+
<Button>Item5</Button>
61+
<Text>Item6</Text>
62+
<Button>Item8</Button>
63+
<ToolbarSeparator />
64+
<Button>Item9</Button>
65+
<Button>Item10</Button>
66+
<ToolbarSeparator />
67+
<Button>Item11</Button>
68+
<Button>Item12</Button>
69+
</Toolbar>
70+
);
71+
};
72+
73+
withSpacerAndSeparator.story = {
74+
name: 'with spacer and separator'
75+
};
76+
77+
export default {
78+
title: 'Components / Toolbar',
79+
component: Toolbar
80+
};

0 commit comments

Comments
 (0)