Skip to content

Commit bf60767

Browse files
refactor: replace Toolbar with UI5 Web Component (#6061)
BREAKING CHANGE: the `Toolbar` component and its related components have been moved to the `@ui5/webcomponents-react-compat` package. BREAKING CHANGE: the `ToolbarV2` component has been renamed to `Toolbar` BREAKING CHANGE: the `ToolbarSpacerV2` component has been renamed to `ToolbarSpacer` BREAKING CHANGE: the `ToolbarSeparatorV2` component has been renamed to `ToolbarSeparator` --------- Co-authored-by: Lukas Harbarth <[email protected]>
1 parent 3822bee commit bf60767

File tree

28 files changed

+913
-113
lines changed

28 files changed

+913
-113
lines changed

.storybook/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const replaceSubComps = {
5757
ListItemBase: ['ListItemStandard', 'ListItemCustom', 'ListItemGroup'],
5858
InputSuggestionItem: ['SuggestionItem', 'SuggestionItemGroup'],
5959
NotificationListItemBase: ['NotificationListItem'],
60-
ToolbarItem: ['ToolbarSeparatorV2', 'ToolbarSpacerV2', 'ToolbarButton', 'ToolbarSelect', 'ToolbarSelectOption'],
60+
ToolbarItem: ['ToolbarSeparator', 'ToolbarSpacer', 'ToolbarButton', 'ToolbarSelect', 'ToolbarSelectOption'],
6161
TreeItemBase: ['TreeItem', 'TreeItemCustom'],
6262
AvatarGroupItem: ['Avatar'],
6363
TableFeature: ['TableGrowing', 'TableSelection']

docs/MigrationGuide.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,22 @@ function MyComponent() {
424424
}
425425
```
426426

427+
### Toolbar
428+
429+
The `Toolbar` component has been replaced with the UI5 Web Components Toolbar component (which was previously exported in this project as `ToolbarV2`).
430+
The old `Toolbar` implementation has been moved to the `@ui5/webcomponents-react-compat` package with all its subcomponents:
431+
432+
- `ToolbarSeparator`
433+
- `ToolbarSpacer`
434+
- `OverflowToolbarButton`
435+
- `OverflowToolbarToggleButton`
436+
- enum `ToolbarDesign`
437+
- enum `ToolbarStyle`
438+
439+
Although the old `Toolbar` is still available in the `@ui5/webcomponents-react-compat` package, we strongly recommend to migrate to the new `Toolbar` instead.
440+
441+
As the new `Toolbar` is a completely different component, we can't offer a proper migration guide, so it's best to check the [Toolbar documentation](?path=/docs/layouts-floorplans-toolbar--docs) and update your implementation accordingly with the new components.
442+
427443
## Components with API Changes
428444

429445
### ActionSheet

packages/cli/src/scripts/codemod/transforms/v2/codemodConfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@
622622
"TitleLevel": "@ui5/webcomponents/dist/types/TitleLevel.js",
623623
"ToastPlacement": "@ui5/webcomponents/dist/types/ToastPlacement.js",
624624
"ToolbarAlign": "@ui5/webcomponents/dist/types/ToolbarAlign.js",
625+
"ToolbarDesign": "@ui5/webcomponents/dist/types/ToolbarDesign.js",
625626
"ToolbarItemOverflowBehavior": "@ui5/webcomponents/dist/types/ToolbarItemOverflowBehavior.js",
626627
"UploadState": "@ui5/webcomponents-fiori/dist/types/UploadState.js",
627628
"ValueState": "@ui5/webcomponents-base/dist/types/ValueState.js",

packages/main/src/components/OverflowToolbarButton/index.tsx renamed to packages/compat/src/components/OverflowToolbarButton/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use client';
22

3+
import { Button } from '@ui5/webcomponents-react';
4+
import type { ButtonDomRef, ButtonPropTypes } from '@ui5/webcomponents-react';
35
import type { ReactNode } from 'react';
46
import { forwardRef } from 'react';
57
import { useOverflowPopoverContext } from '../../internal/OverflowPopoverContext.js';
6-
import type { ButtonDomRef, ButtonPropTypes } from '../../webComponents/index.js';
7-
import { Button } from '../../webComponents/index.js';
88

99
export interface OverflowToolbarButtonPropTypes extends Omit<ButtonPropTypes, 'children' | 'icon'> {
1010
/**

packages/main/src/components/OverflowToolbarToggleButton/index.tsx renamed to packages/compat/src/components/OverflowToolbarToggleButton/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use client';
22

3+
import type { ToggleButtonDomRef, ToggleButtonPropTypes } from '@ui5/webcomponents-react';
4+
import { ToggleButton } from '@ui5/webcomponents-react';
35
import type { ReactNode } from 'react';
46
import { forwardRef } from 'react';
57
import { useOverflowPopoverContext } from '../../internal/OverflowPopoverContext.js';
6-
import type { ToggleButtonDomRef, ToggleButtonPropTypes } from '../../webComponents/index.js';
7-
import { ToggleButton } from '../../webComponents/index.js';
88

99
export interface OverflowToolbarToggleButtonPropTypes extends Omit<ToggleButtonPropTypes, 'children' | 'icon'> {
1010
/**
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
2+
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
3+
import PopupAccessibleRole from '@ui5/webcomponents/dist/types/PopupAccessibleRole.js';
4+
import iconOverflow from '@ui5/webcomponents-icons/dist/overflow.js';
5+
import type {
6+
ButtonPropTypes,
7+
PopoverDomRef,
8+
ToggleButtonDomRef,
9+
ToggleButtonPropTypes
10+
} from '@ui5/webcomponents-react';
11+
import { Popover, ToggleButton } from '@ui5/webcomponents-react';
12+
import { useCanRenderPortal } from '@ui5/webcomponents-react/dist/internal/ssr.js';
13+
import { stopPropagation } from '@ui5/webcomponents-react/dist/internal/stopPropagation.js';
14+
import { getUi5TagWithSuffix } from '@ui5/webcomponents-react/dist/internal/utils.js';
15+
import { Device, useSyncRef } from '@ui5/webcomponents-react-base';
16+
import { clsx } from 'clsx';
17+
import type { Dispatch, FC, ReactElement, ReactNode, Ref, SetStateAction } from 'react';
18+
import { cloneElement, useEffect, useRef, useState } from 'react';
19+
import { createPortal } from 'react-dom';
20+
import { getOverflowPopoverContext } from '../../internal/OverflowPopoverContext.js';
21+
import type { ToolbarSeparatorPropTypes } from '../ToolbarSeparator/index.js';
22+
import type { ToolbarPropTypes } from './index.js';
23+
24+
interface OverflowPopoverProps {
25+
lastVisibleIndex: number;
26+
classes: Record<string, string>;
27+
children: ReactNode[];
28+
portalContainer: Element;
29+
overflowContentRef: Ref<HTMLDivElement>;
30+
numberOfAlwaysVisibleItems?: number;
31+
showMoreText: string;
32+
overflowPopoverRef?: Ref<PopoverDomRef>;
33+
overflowButton?: ReactElement<ToggleButtonPropTypes> | ReactElement<ButtonPropTypes>;
34+
setIsMounted: Dispatch<SetStateAction<boolean>>;
35+
a11yConfig?: ToolbarPropTypes['a11yConfig'];
36+
}
37+
38+
const isPhone = Device.isPhone();
39+
40+
export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopoverProps) => {
41+
const {
42+
lastVisibleIndex,
43+
classes,
44+
children,
45+
portalContainer,
46+
overflowContentRef,
47+
numberOfAlwaysVisibleItems,
48+
showMoreText,
49+
overflowButton,
50+
overflowPopoverRef,
51+
setIsMounted,
52+
a11yConfig
53+
} = props;
54+
const [pressed, setPressed] = useState(false);
55+
const toggleBtnRef = useRef<ToggleButtonDomRef>(null);
56+
const [componentRef, popoverRef] = useSyncRef(overflowPopoverRef);
57+
58+
useEffect(() => {
59+
setIsMounted(true);
60+
return () => {
61+
setIsMounted(false);
62+
};
63+
}, []);
64+
65+
const handleToggleButtonClick = (e) => {
66+
e.stopPropagation();
67+
setPressed((prev) => {
68+
if (!prev) {
69+
if (popoverRef.current) {
70+
popoverRef.current.opener = e.target;
71+
}
72+
return true;
73+
}
74+
return false;
75+
});
76+
};
77+
78+
const handleBeforeOpen = () => {
79+
if (toggleBtnRef.current) {
80+
toggleBtnRef.current.accessibilityAttributes = { expanded: true, hasPopup: 'menu' };
81+
}
82+
};
83+
const handleAfterOpen = () => {
84+
setPressed(true);
85+
};
86+
87+
const handleClose = (e) => {
88+
if (toggleBtnRef.current) {
89+
toggleBtnRef.current.accessibilityAttributes = { expanded: false, hasPopup: 'menu' };
90+
}
91+
stopPropagation(e);
92+
setPressed(false);
93+
};
94+
95+
useEffect(() => {
96+
const tagName = getUi5TagWithSuffix('ui5-toggle-button');
97+
void customElements.whenDefined(tagName).then(() => {
98+
if (toggleBtnRef.current) {
99+
toggleBtnRef.current.accessibilityAttributes = { expanded: pressed, hasPopup: 'menu' };
100+
}
101+
});
102+
}, []);
103+
104+
const clonedOverflowButtonClick = (e) => {
105+
if (typeof overflowButton?.props?.onClick === 'function') {
106+
overflowButton.props.onClick(e);
107+
}
108+
if (!e.defaultPrevented) {
109+
handleToggleButtonClick(e);
110+
}
111+
};
112+
113+
const canRenderPortal = useCanRenderPortal();
114+
115+
const accessibleRole = (() => {
116+
if (a11yConfig?.overflowPopover?.contentRole) {
117+
return PopupAccessibleRole.None;
118+
}
119+
return a11yConfig?.overflowPopover?.role;
120+
})();
121+
122+
const OverflowPopoverContextProvider = getOverflowPopoverContext().Provider;
123+
124+
return (
125+
<OverflowPopoverContextProvider value={{ inPopover: true }}>
126+
{overflowButton ? (
127+
cloneElement(overflowButton, { onClick: clonedOverflowButtonClick })
128+
) : (
129+
<ToggleButton
130+
ref={toggleBtnRef}
131+
design={ButtonDesign.Transparent}
132+
icon={iconOverflow}
133+
onClick={handleToggleButtonClick}
134+
pressed={pressed}
135+
accessibleName={showMoreText}
136+
tooltip={showMoreText}
137+
data-component-name="ToolbarOverflowButton"
138+
/>
139+
)}
140+
{canRenderPortal &&
141+
createPortal(
142+
<Popover
143+
data-component-name="ToolbarOverflowPopover"
144+
className={clsx(classes.popover, isPhone && classes.popoverPhone)}
145+
placement={PopoverPlacement.Bottom}
146+
ref={componentRef}
147+
open={pressed}
148+
onClose={handleClose}
149+
onBeforeOpen={handleBeforeOpen}
150+
onOpen={handleAfterOpen}
151+
hideArrow
152+
accessibleRole={accessibleRole}
153+
>
154+
<div
155+
className={classes.popoverContent}
156+
ref={overflowContentRef}
157+
role={a11yConfig?.overflowPopover?.contentRole}
158+
data-component-name="ToolbarOverflowPopoverContent"
159+
>
160+
{children.map((item, index) => {
161+
if (index > lastVisibleIndex && index > numberOfAlwaysVisibleItems - 1) {
162+
// @ts-expect-error: if props is not defined, it doesn't have an id (is not a ReactElement)
163+
if (item?.props?.id) {
164+
// @ts-expect-error: item is ReactElement
165+
return cloneElement(item, { id: `${item.props.id}-overflow` });
166+
}
167+
// @ts-expect-error: if type is not defined, it's not a spacer
168+
if (item.type?.displayName === 'ToolbarSeparator') {
169+
return cloneElement(item as ReactElement<ToolbarSeparatorPropTypes>, {
170+
style: {
171+
height: '0.0625rem',
172+
margin: '0.375rem 0.1875rem',
173+
width: '100%'
174+
}
175+
});
176+
}
177+
return item;
178+
}
179+
return null;
180+
})}
181+
</div>
182+
</Popover>,
183+
portalContainer ?? document.body
184+
)}
185+
</OverflowPopoverContextProvider>
186+
);
187+
};

packages/main/src/components/Toolbar/Toolbar.cy.tsx renamed to packages/compat/src/components/Toolbar/Toolbar.cy.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,18 @@ import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
22
import PopupAccessibleRole from '@ui5/webcomponents/dist/types/PopupAccessibleRole.js';
33
import { setTheme } from '@ui5/webcomponents-base/dist/config/Theme.js';
44
import menu2Icon from '@ui5/webcomponents-icons/dist/menu2.js';
5+
import type { PopoverDomRef } from '@ui5/webcomponents-react';
6+
import { Button, Input, Text, ToggleButton } from '@ui5/webcomponents-react';
57
import { ThemingParameters } from '@ui5/webcomponents-react-base';
68
import { useRef, useState } from 'react';
7-
import type { PopoverDomRef, ToolbarPropTypes } from '../..';
8-
import {
9-
Button,
10-
Input,
11-
OverflowToolbarButton,
12-
Text,
13-
Toolbar,
14-
ToggleButton,
15-
ToolbarSeparator,
16-
ToolbarSpacer,
17-
ToolbarStyle,
18-
OverflowToolbarToggleButton
19-
} from '../..';
20-
import { ToolbarDesign } from '../../enums/index.js';
9+
import { ToolbarDesign } from '../../enums/ToolbarDesign.js';
10+
import { ToolbarStyle } from '../../enums/ToolbarStyle.js';
11+
import { OverflowToolbarButton } from '../OverflowToolbarButton/index.js';
12+
import { OverflowToolbarToggleButton } from '../OverflowToolbarToggleButton/index.js';
13+
import { ToolbarSeparator } from '../ToolbarSeparator/index.js';
14+
import { ToolbarSpacer } from '../ToolbarSpacer/index.js';
15+
import type { ToolbarPropTypes } from './index.js';
16+
import { Toolbar } from './index.js';
2117
import { cssVarToRgb, cypressPassThroughTestsFactory, mountWithCustomTagName } from '@/cypress/support/utils';
2218

2319
interface PropTypes {

packages/main/src/components/Toolbar/Toolbar.mdx renamed to packages/compat/src/components/Toolbar/Toolbar.mdx

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
11
import { ArgTypesWithNote, ControlsWithNote, DocsHeader, Footer } from '@sb/components';
22
import { Canvas, Description, Markdown, Meta } from '@storybook/blocks';
33
import MessageStripDesign from '@ui5/webcomponents/dist/types/MessageStripDesign.js';
4+
import { MessageStrip } from '@ui5/webcomponents-react';
45
import * as ComponentStories from './Toolbar.stories';
56
import SubcomponentsSection from '@sb/docs/SubcomponentsSection.md?raw';
6-
import {
7-
OverflowToolbarButton,
8-
OverflowToolbarToggleButton,
9-
MessageStrip,
10-
ToolbarSpacer,
11-
ToolbarSeparator
12-
} from '../..';
7+
import { OverflowToolbarButton, OverflowToolbarToggleButton, ToolbarSpacer, ToolbarSeparator } from '../..';
138

149
<Meta of={ComponentStories} />
1510

1611
<DocsHeader
1712
subComponents={['OverflowToolbarButton', 'OverflowToolbarToggleButton', 'ToolbarSpacer', 'ToolbarSeparator']}
1813
/>
1914

20-
<MessageStrip
21-
design={MessageStripDesign.Critical}
22-
hideCloseButton
23-
children={
24-
<>
25-
This component may be replaced by the <code>ui5-toolbar</code> web-component (currently available as{' '}
26-
<code>ToolbarV2</code>) with our next major release. If you only need to pass components supported by{' '}
27-
<code>ToolbarV2</code> then please consider using <code>ToolbarV2</code> instead of this component.
28-
</>
29-
}
30-
/>
31-
3215
## Example
3316

3417
<Canvas of={ComponentStories.Default} />
@@ -140,6 +123,8 @@ You can achieve that either by leveraging the `onOverflowChange` event and retri
140123

141124
<details>
142125

126+
{' '}
127+
143128
<summary>Set opener ID via click handler</summary>
144129

145130
```jsx
@@ -176,9 +161,11 @@ const ToolbarComponent = () => {
176161
```
177162

178163
</details>
179-
164+
180165
<details>
181166

167+
{' '}
168+
182169
<summary>Set opener ID via onOverflowChange handler</summary>
183170

184171
```jsx

0 commit comments

Comments
 (0)