import './MigrationGuide.css'; import { Footer, TableOfContent, FilterBarExample } from '@sb/components'; import { Canvas, Meta } from '@storybook/blocks'; import { MessageStrip } from '@ui5/webcomponents-react';
The most important breaking changes of the corresponding releases are outlined here. For a full list of all changes, please refer to the list of releases or the changelog.
Older releases
This migration guide only covers breaking changes when updating from v1 to v2. For migration guides for older releases, please refer to our Migration Guide Archive.
The breaking changes listed here only affect our codebase. Changes related solely to the underlying web component are not tracked here. For a complete list of breaking changes in UI5 Web Components, please refer to their Migration Guide.
Note: Our codemod
covers changes from ui5-webcomponents as well.
To make the migration to UI5 Web Components (for React) v2 easier, we provide a codemod which tries to transform most of the breaking changes.
<MessageStrip
hideCloseButton
design="Critical"
children={
<>
The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code
thoroughly!
Applying the codemod might break your code formatting, so please don't forget to run prettier and/or eslint
after you've applied the codemod!
</>
}
/>
npx @ui5/webcomponents-react-cli codemod --transform v2 \
--src ./path/to/src \
--typescript # only if you use TypeScript in your project, omit if you use JavaScript
In the SAP Devtoberfest 2024 session, we showcased the most prominent new features in version 2 of UI5 Web Components and UI5 Web Components for React, and provided an example of how to migrate from version 1 to version 2 using our Codemod.
The minimum required react
and react-dom
version is now 18.0.0
.
Previously, we created enums for all props that used multiple string values to set the corresponding design, mode, etc. Since enums for web components have been exported directly by UI5 Web Components for some time, we had been proxying the imports to avoid breaking changes.
With version 2, we took the opportunity to remove all proxied enums, so they must now be imported directly from @ui5/webcomponents
or the corresponding package.
Note: You can use our Codemod to simplify the refactoring process.
For example:
// v1
import { ButtonDesign } from '@ui5/webcomponents-react';
// v2
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
UI5 Web Components for React is no longer relying on react-jss
internally, hence the dependency is now removed and the react-jss
ThemeProvider is no longer rendered as part of our ThemeProvider
.
If you are relying on react-jss
in your application, please make sure to render your own react-jss
ThemeProvider:
import { ThemeProvider } from '@ui5/webcomponents-react';
import { ThemingParameters } from '@ui5/webcomponents-react-base';
import { ThemeProvider as ReactJssThemeProvider } from 'react-jss';
function MyRootComponent() {
return (
<ThemeProvider>
<ReactJssThemeProvider theme={ThemingParameters}>{/* your app content goes here */}</ReactJssThemeProvider>
</ThemeProvider>
);
}
We stopped recommending jest as a testing framework over a year ago, thus the jestSetup
file is removed. We recommend using Cypress instead.
- Removed
dangerouslySetInnerHTML
from general prop types since it was never intended to be used with our library. If you've previously used this prop and the component didn't change with the update, then it might still work, but you'll probably need to suppress the TypeScript error.
Starting with v2, the ThemeProvider
will apply the Fiori styles to all scrollbars on the page.
If you have previously used our global style classes sapScrollBar
or inheritingSapScrollBar
, you can now remove them.
To opt-out of this behavior, you can add the .ui5-content-native-scrollbars
class to the element to prevent the scrollbar styling from being applied.
More details can be found in our Styling Knowledge Base.
The spacing
variables of our base package (@ui5/webcomponents-react-base
) have been removed.
Most variables can be replaced by applying the corresponding CSS classes from the @sap-ui/common-css
package to your element, for all others you can find the respective CSS properties and values below.
Full Documentation of Common CSS classes:
Show
Removed Variable | Equivalent Common CSS Class |
---|---|
All Around Margins | |
sapUiTinyMargin |
.sap-margin-tiny |
sapUiSmallMargin |
.sap-margin-small |
sapUiMediumMargin |
.sap-margin-medium |
sapUiLargeMargin |
.sap-margin-large |
Single Side Margins | |
sapUiTinyMarginTop |
.sap-margin-top-tiny |
sapUiSmallMarginTop |
.sap-margin-top-small |
sapUiMediumMarginTop |
.sap-margin-top-medium |
sapUiLargeMarginTop |
.sap-margin-top-large |
sapUiTinyMarginBottom |
.sap-margin-bottom-tiny |
sapUiSmallMarginBottom |
.sap-margin-bottom-small |
sapUiMediumMarginBottom |
.sap-margin-bottom-medium |
sapUiLargeMarginBottom |
.sap-margin-bottom-large |
sapUiTinyMarginBegin |
.sap-margin-begin-tiny |
sapUiSmallMarginBegin |
.sap-margin-begin-small |
sapUiMediumMarginBegin |
.sap-margin-begin-medium |
sapUiLargeMarginBegin |
.sap-margin-begin-large |
sapUiTinyMarginEnd |
.sap-margin-end-tiny |
sapUiSmallMarginEnd |
.sap-margin-end-small |
sapUiMediumMarginEnd |
.sap-margin-end-medium |
sapUiLargeMarginEnd |
.sap-margin-end-large |
Horizontal Margins | |
sapUiTinyMarginBeginEnd |
.sap-margin-x-tiny |
sapUiSmallMarginBeginEnd |
.sap-margin-x-small |
sapUiMediumMarginBeginEnd |
.sap-margin-x-medium |
sapUiLargeMarginBeginEnd |
.sap-margin-x-large |
Vertical Margins | |
sapUiTinyMarginTopBottom |
.sap-margin-y-tiny |
sapUiSmallMarginTopBottom |
.sap-margin-y-small |
sapUiMediumMarginTopBottom |
.sap-margin-y-medium |
sapUiLargeMarginTopBottom |
.sap-margin-y-large |
Responsive Margins | |
sapUiResponsiveMargin |
.sap-margin-responsive |
Negative Margins | |
sapUiTinyNegativeMarginBeginEnd |
.sap-margin-tiny-negative |
sapUiSmallNegativeMarginBeginEnd |
.sap-margin-small-negative |
sapUiMediumNegativeMarginBeginEnd |
.sap-margin-medium-negative |
sapUiLargeNegativeMarginBeginEnd |
.sap-margin-large-negative |
All Around Padding | |
sapUiContentPadding |
.sap-padding |
Responsive Paddings | |
sapUiResponsiveContentPadding |
.sap-padding-responsive |
Horizontal Paddings | |
sapUiTinyPaddingBeginEnd |
.sap-padding-x-tiny |
sapUiSmallPaddingBeginEnd |
.sap-padding-x-small |
sapUiMediumPaddingBeginEnd |
.sap-padding-x-medium |
sapUiLargePaddingBeginEnd |
.sap-padding-x-large |
No Padding | |
sapUiNoContentPadding |
.sap-padding-none |
Show
Removed Variable | Property and Value |
---|---|
sapUiNoMargin |
margin: 0 !important; |
sapUiNoMarginTop |
margin-top: 0 !important; |
sapUiNoMarginBottom |
margin-bottom: 0 !important; |
sapUiForceWidthAuto |
width: auto !important; |
The useResponsiveContentPadding
hook has been removed.
You can now apply responsive content paddings by applying the sap-content-paddings-container
class from @sap-ui/common-css
package to your element.
You can find a more detailed documentation here.
TableGroupRow
has been removed.NotificationAction
has been removed. You can use theMenu
component instead.SelectMenu
andSelectMenuOption
have been removed. TheSelect
andOption
now allow custom content.MicroBarChart
has been removed (from@ui5/webcomponents-react-charts
) as it is not covered by design specs anymore.
With the release of UI5 Web Components v2, some component names have been renamed. Because we use these web component names (class names) as React component names, the renamings are considered breaking changes in our repository.
The list below shows only the previous and updated component names. For details on changes concerning attributes, properties, methods, etc., please refer to the UI5 Web Components Migration Guide.
Note: The tag name in the parenthesis is the old tag name.
StandardListItem
has been renamed toListItemStandard
(ui5-li
).CustomListItem
has been renamed toListItemCustom
(ui5-li-custom
).MultiComboBoxGroupItem
has been renamed toMultiComboBoxItemGroup
(ui5-mcb-group-item
).TableColumn
has been renamed toTableHeaderCell
(ui5-mcb-group-item
).Badge
has been renamed toTag
(ui5-tag
).ComboBoxGroupItem
has been renamed toComboBoxItemGroup
(ui5-cb-group-item
).GroupHeaderListItem
has been renamed toListItemGroup
(ui5-li-groupheader
).
The deprecated AnalyticalCard
component has been replaced with the Card
web component as a drop-in replacement:
// v1
import { AnalyticalCard, AnalyticalCardHeader } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<AnalyticalCard header={<AnalyticalCardHeader />}>
<span>My Content</span>
</AnalyticalCard>
);
}
// v2
import { AnalyticalCardHeader, Card } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<Card header={<AnalyticalCardHeader />}>
<span>My Content</span>
</Card>
);
}
The DynamicPage
component has been replaced with the ui5-dynamic-page
web component. This comes with a few breaking changes:
backgroundDesign
is not available anymore. To set the background of the page you can use standard CSS and the respective CSS variables instead:- List:
var(--sapGroup_ContentBackground)
- Solid:
var(--sapBackgroundColor)
- Transparent:
transparent
- List:
alwaysShowContentHeader
has been renamed toheaderPinned
headerCollapsed
has been renamed toheaderSnapped
headerContentPinnable
(default:true
) has been replaced byhidePinButton
(default:false
)footer
has been replaced byfooterArea
and is now aslot
. To display the footer, you now have to set theshowFooter
prop totrue
.headerContent
has been replaced byheaderArea
and is now aslot
headerTitle
has been replaced bytitleArea
and is now aslot
Events:
-
onPinnedStateChange
has been replaced byonPinButtonToggle
. -
onToggleHeaderContent
has been replaced byonTitleToggle
.// v1 function DynamicPageComponent(props) { const [pinned, setPinned] = useState(false); const [expanded, setExpanded] = useState(true); return ( <DynamicPage {...props} onPinnedStateChange={(pinned) => setPinned(pinned)} onToggleHeaderContent={(visible) => { setExpanded(visible); }} /> ); } // v2 function DynamicPageComponent(props) { const [pinned, setPinned] = useState(false); const [expanded, setExpanded] = useState(true); return ( <DynamicPage {...props} onPinButtonToggle={(event) => setPinned(event.target.headerPinned)} onTitleToggle={(event) => { setExpanded(!event.target.headerSnapped); }} /> ); }
preserveHeaderStateOnScroll
: You should be able to achieve the same behavior with theheaderPinned
prop.showHideHeaderButton
: Hiding the expand/collapse button is not supported by design anymore.
The DynamicPageHeader
component has been replaced with the ui5-dynamic-page-header
web component.
Since the ObjectPage
isn't compatible with the DynamicPageHeader
web component, please use the ObjectPageHeader
component in the ObjectPage
instead.
The DynamicPageTitle
component has been replaced with the ui5-dynamic-page-title
web component. This comes with a few breaking changes listed below.
Since the ObjectPage
isn't compatible with the DynamicPageTitle
web component, please use the ObjectPageTitle
component in the ObjectPage
instead.
-
actions
has been replaced byactionsBar
and is now aslot
. Instead of passing actions (e.g.Buttons
) directly, it is now recommended to use theToolbar
component in order to preserve the intended design. -
navigationActions
has been replaced bynavigationBar
and is now aslot
. Instead of passing actions (e.g.Buttons
) directly, it is now recommended to use theToolbar
component in order to preserve the intended design. -
subHeader
has been renamed tosubheading
and is now a slot. -
header
has been renamed toheading
and is now aslot
. Thefont-size
isn't automatically adjusted anymore, so to keep the intended design you can leverage the newsnappedHeading
prop and apply the corresponding CSS Variables yourself. (see example below)Example:
<DynamicPageTitle heading={<Title style={{ fontSize: 'var(--sapObjectHeader_Title_FontSize)' }}>Header Title</Title>} snappedHeading={ <Title style={{ fontSize: 'var(--sapObjectHeader_Title_SnappedFontSize)' }}>Snapped Header Title</Title> } />
-
showSubHeaderRight
is not defined by the global design guidelines and is therefore not available with the new web component. -
actionsToolbarProps
is not necessary anymore, as you now can pass aToolbar
yourself. -
navigationActionsToolbarProps
is not necessary anymore, as you now can pass aToolbar
yourself. -
expandedContent
is now part of thesubheading
prop, so if you've rendered aMessageStrip
below thesubHeader
for example, you can now render the subheading and additional content both in the same slot. -
snappedContent
is now part of thesnappedSubheading
prop, so if you've rendered aMessageStrip
below thesubHeader
for example, you can now render the subheading and additional content both in the same slot.Example for combined
subHeader
andexpanded/snappedContent
insubheading
/snappedSubheading
:<DynamicPageTitle subheading={ <> <Label>Subheader</Label> <MessageStrip>Information (only visible if header content is expanded)</MessageStrip> </> } snappedSubheading={ <> <Label>Snapped Subheader</Label> <MessageStrip>Information (only visible if header content is collapsed (snapped))</MessageStrip> </> } />
The Form
component has been replaced with the ui5-form
Web Component.
You can use the new Form
component as a feature complete replacement of the old Form component with the important differences to mention:
- The
ui5-form
web component doesn't implement the events, attributes and properties of an HTMLform
element. So, if you want these features to be available, we recommend wrapping theForm
component inside a HTMLform
element. - You can't mix
FormGroup
s andFormItem
s as children of the Form. Either only useFormItem
s or onlyFormGroup
s withFormItem
s inside. - Additional HTML elements between
Form / FormGroup / FormItem
are not allowed. You can still use custom React components if they render aFormGroup
orFormItem
as most outer element (or a fragment).
// v1
import { Form, FormGroup, FormItem } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<Form
backgroundDesign="Solid"
titleText="My Form"
labelSpanS={1}
labelSpanM={2}
labelSpanL={3}
labelSpanXL={4}
columnsS={1}
columnsM={2}
columnsL={3}
columnsXL={4}
as={'form'}
onSubmit={(e) => {
/*handleSubmit*/
}}
>
<FormGroup titleText="My Form Group" as="h5">
<FormItem label={'MyLabel'}>{/* FormItem Content */}</FormItem>
</FormGroup>
</Form>
);
}
// v2
import { Form, FormGroup, FormItem, Label } from '@ui5/webcomponents-react';
function MyComponent() {
return (
// To implement HTML form features, use the `form` element
<form
onSubmit={(e) => {
/*handleSubmit*/
}}
>
{/* `backgroundDesign` and `as` have been removed without replacement */}
<Form
// `titleText` has been renamed to `headerText`
headerText="My Form"
// the `columnsX` props have been merged into the `layout` string
layout="S1 M2 L3 XL4"
// the `labelSpanX` props have been merged into the `labelSpan` string
labelSpan="S1 M2 L3 XL4"
>
{/* `titleText` has been renamed to `headerText`, `as` has been removed */}
<FormGroup headerText="My Form Group">
{/* the `label` prop has been renamed to a `labelContent` slot.
It doesn't support strings anymore, it's recommended to use the `Label` component in this slot. */}
<FormItem labelContent={<Label>MyLabel</Label>}>{/* FormItem Content */}</FormItem>
</FormGroup>
</Form>
</form>
);
}
There is no longer a concept of a Loader component defined by the UX guidelines. To indicate a loading state, it is now recommended using the BusyIndicator
instead.
For backwards compatibility, the Loader is still available in the @ui5/webcomponents-react-compat
package, but it may lack accessibility features and no longer receives feature updates.
Unfortunately, there is no general rule of thumb for how to replace the Loader
component with the BusyIndicator
component. In most cases it should be sufficient wrapping your component inside the BusyIndicator
, as shown below:
// v1
<Card header={<CardHeader titleText="Card Header" />}>
<Loader />
<div style={{ height: '400px', padding: '1rem' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sed felis tristique, molestie tellus id, rutrum
urna. Quisque mattis risus imperdiet gravida accumsan. Proin elementum efficitur diam eu interdum.
</div>
</Card>
// v2
<Card header={<CardHeader titleText="Card Header" />}>
<BusyIndicator active delay={0}>
<div style={{ height: '400px', padding: '1rem' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sed felis tristique, molestie tellus id,
rutrum urna. Quisque mattis risus imperdiet gravida accumsan. Proin elementum efficitur diam eu interdum.
</div>
</BusyIndicator>
</Card>
However, for components that apply complex styles such as absolute/fixed
positioning, this might not be the case, as the BusyIndicator
brings its own set of styles.
In such instances, we recommend positioning the BusyIndicator
above the element that should receive a loading indicator e.g. via position: absolute
.
If you encounter any issues migrating to the BusyIndicator
, please feel free to reach out via GitHub Discussions or create an Issue if the behavior seems like a bug.
If you'd like to keep the Loader
component instead of the BusyIndicator
component, you now need to include the @ui5/webcomponents-react-compat
package in your dependencies and adjust all import paths accordingly:
// v1
import { Loader } from '@ui5/webcomponents-react';
// v2
import { Loader } from '@ui5/webcomponents-react-compat';
The Text
component has been replaced with the ui5-text
Web Component.
The following props have been removed:
wrapping
Can be achieved via themaxLines
property. IfmaxLines
is set to1
, the text will not wrap, otherwise it will wrap.renderWhitespace
Can be achieved by addingwhite-space: pre-wrap;
via inline styles orclassName
to theText
component.hyphenated
andemptyIndicator
These props are currently not supported by the newui5-text
component. You can follow this feature request for updates.
// v1
import { Text } from '@ui5/webcomponents-react';
function MyComponent() {
return <Text wrapping={false}>Lorem Impsum</Text>;
}
// v2
import { Text } from '@ui5/webcomponents-react';
function MyComponent() {
return <Text maxLines={1}>Lorem Impsum</Text>;
}
The Toolbar
component has been replaced with the UI5 Web Components Toolbar component (which was previously exported in this project as ToolbarV2
).
The old Toolbar
implementation has been moved to the @ui5/webcomponents-react-compat
package with all its subcomponents:
ToolbarSeparator
ToolbarSpacer
OverflowToolbarButton
OverflowToolbarToggleButton
- enum
ToolbarDesign
- enum
ToolbarStyle
Although the old Toolbar
is still available in the @ui5/webcomponents-react-compat
package, we strongly recommend to migrate to the new Toolbar
instead.
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 and update your implementation accordingly with the new components.
The prop portalContainer
has been removed as it is no longer needed due to the popover API which is now used in the UI5 Web Components.
For a better aligned API, the showCancelButton
prop has been replaced wih the hideCancelButton
prop and the logic has been inverted. Also, the a11yConfig
prop has been renamed to accessibilityAttributes
.
// v1
import { ActionSheet, Button } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<ActionSheet showCancelButton={false} a11yConfig={{ actionSheetMobileContent: { role: 'menu' } }}>
<Button>Action 1</Button>
</ActionSheet>
);
}
// v2
import { ActionSheet, Button } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<ActionSheet hideCancelButton accessibilityAttributes={{ actionSheetMobileContent: { role: 'menu' } }}>
<Button>Action 1</Button>
</ActionSheet>
);
}
TypeScript Changes:
The internal table instance types were updated, leading to stricter types, so depending on your implementation, you might encounter ts-errors.
Renamed or Changed Props:
alwaysShowSubComponent
has been removed, please usesubComponentsBehavior
withAnalyticalTableSubComponentsBehavior.Visible
instead.- The default value of
sortable
(true
) has been removed. To allow sorting in your table please setsortable
totrue
yourself.
Removed Props:
portalContainer
has been removed as it's no longer needed due to the Popover API used in thePopover
ui5 web component.
Changed Events:
onRowSelect
: Since calculatingselectedFlatRows
was very costly, it has been removed from thedetail
event object. If you still want to use it, you can calculate it yourself:
const handleOnRowSelect = (event) => {
const { selectedRowIds, rowsById } = event.detail;
const selectedFlatRows = Object.keys(selectedRowIds).reduce((acc, key) => {
if (selectedRowIds[key]) {
acc.push(rowsById[key]);
}
return acc;
}, []);
console.log(selectedFlatRows);
};
Renamed Enums:
Names of the following enums have changed:
TableScaleWidthMode
is nowAnalyticalTableScaleWidthMode
TableSelectionBehavior
is nowAnalyticalTableSelectionBehavior
TableSelectionMode
is nowAnalyticalTableSelectionMode
TableVisibleRowCountMode
is nowAnalyticalTableVisibleRowCountMode
Changed Enums:
- The properties and values for the
AnalyticalTableSelectionMode
enum has been changed.SingleSelect
is nowSingle
MultiSelect
is nowMultiple
.
Column Properties Changes
canReorder
has been removed, please usedisableDragAndDrop
instead.
// v1
const columns = [{ ...otherProperties, canReorder: false }];
<AnalyticalTable
{...otherProps}
columns={columns}
alwaysShowSubComponent
scaleWidthMode={TableScaleWidthMode.Grow}
selectionBehavior={TableSelectionBehavior.Row}
selectionMode={TableSelectionMode.MultiSelect}
// selectionMode={TableSelectionMode.SingleSelect}
visibleRowCountMode={TableVisibleRowCountMode.Interactive}
/>;
// v2
const columns = [{ ...otherProps, disableDragAndDrop: true }];
<AnalyticalTable
{...otherProps}
columns={columns}
subComponentsBehavior={AnalyticalTableSubComponentsBehavior.Visible}
scaleWidthMode={AnalyticalTableScaleWidthMode.Grow}
selectionBehavior={AnalyticalTableSelectionBehavior.Row}
selectionMode={AnalyticalTableSelectionMode.Multiple}
// selectionMode={AnalyticalTableSelectionMode.Single}
visibleRowCountMode={AnalyticalTableVisibleRowCountMode.Interactive}
/>;
The prop portalContainer
has been removed as it is no longer needed due to the popover API which is now used in the UI5 Web Components.
As the underlying Text
component has been replaced with the UI5 Web Component, some inherited props hyphenated
and emptyIndicator
from the Text
component have been removed.
You can follow this feature request for updates.
The FilterBar
component has been completely overhauled, eliminating reference copying of input/filter elements as well as internal selection and reordering, which resulted in API changes for events.
These changes were made to remove (React) anti-patterns and improve the component's performance and maintainability. Below, you'll find descriptions of what each point entails:
-
Reference Copying: Previously, the
FilterBar
copied thechildren
,values
, and other properties of input components likeInput
orMultiComboBox
to the filters dialog. Since this occurred outside the React lifecycle, it could be considered an anti-pattern. Synchronizing these references was complex, requiring dedicated logic for most UI5 Web Component input components. Additionally, many implementations likely fully control the component, making reference copying redundant. For these reasons, we decided to remove this functionality and developers should now manage input values directly, for example, using React state. This has the advantage, that you can use every input component and not only UI5 Web Components (although that's still recommended). -
Selection & Reordering: In V1, the selection and reordering features applied user changes internally to the
FilterBar
. This meant that if a user selected a filter in the filters dialog, the change was also reflected in theFilterBar
. This approach proved problematic when the component was fully controlled externally, leading to synchronization issues between internal and external changes. Additionally, with the introduction of the V2Table
component from UI5 Web Components, which has updated API and markup, some changes to the event API were necessary. The advantage of the new approach is, that developers can now decide how and when changes are applied, such as only after closing the filters dialog or as live updates.
const initialState = {
age: 37,
countries: {},
currency: 'USD',
date: '',
search: ''
};
function reducer(state, action) {
switch (action.type) {
case 'SET_AGE':
return { ...state, age: action.payload };
case 'SET_COUNTRIES':
return { ...state, countries: action.payload };
case 'SET_CURRENCY':
return { ...state, currency: action.payload };
case 'SET_DATE':
return { ...state, date: action.payload };
case 'SET_SEARCH':
return { ...state, search: action.payload };
case 'SET_STATE':
return { ...state, ...action.payload };
case 'DIALOG_RESTORE':
return action.payload;
default:
return state;
}
}
export function FilterBarExample() {
const [filterState, dispatch] = useReducer(reducer, initialState);
const { age, countries, currency, date, search } = filterState;
const dialogStateRef = useRef({});
const [visibleChildrenByKey, setVisibleChildrenByKey] = useState<Record<string, boolean>>({
'0': true,
'1': true,
'2': true
});
const [orderedFilterKeys, setOrderedFilterKeys] = useState(['0', '1', '2', '3']);
const handleSearch = (e) => {
dispatch({ type: 'SET_SEARCH', payload: e.target.value });
};
const handleAgeChange = (e) => {
const { value } = e.target;
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
dialogStateRef.current.age = value;
} else {
dispatch({ type: 'SET_AGE', payload: value });
}
};
const handleCountriesChange = (e) => {
const newCountries = e.detail.items.reduce((acc, cur) => {
return { ...acc, [cur.getAttribute('text').toLowerCase()]: true };
}, {});
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
dialogStateRef.current.countries = newCountries;
} else {
dispatch({ type: 'SET_COUNTRIES', payload: newCountries });
}
};
const handleCurrencyChange = (e) => {
const currency = e.detail.selectedOption.textContent;
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
dialogStateRef.current.currency = currency;
} else {
dispatch({ type: 'SET_CURRENCY', payload: currency });
}
};
const handleDateChange = (e) => {
const { value } = e.target;
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
dialogStateRef.current.date = value;
} else if (e.detail.valid) {
dispatch({ type: 'SET_DATE', payload: value });
}
};
const handleFiltersDialogSave: FilterBarPropTypes['onFiltersDialogSave'] = (e) => {
setOrderedFilterKeys(e.detail.reorderedFilterKeys);
setVisibleChildrenByKey(
e.detail.selectedFilterKeys.reduce((acc, cur) => {
acc[cur] = true;
return acc;
}, {})
);
dispatch({ type: 'SET_STATE', payload: dialogStateRef.current });
};
return (
<ThemeProvider>
<Text>
The FilterBar applies filter changes inside the FilterBar immediately and inside the dialog only after 'OK' has
been pressed.
</Text>
<FilterBar
header={
<Title level={TitleLevel.H2} size={TitleLevel.H4}>
Apply changes after dialog save
</Title>
}
enableReordering
onFiltersDialogSave={handleFiltersDialogSave}
search={<Input onInput={handleSearch} />}
>
{orderedFilterKeys.map((filterKey) => {
const isHidden = !visibleChildrenByKey[filterKey];
switch (filterKey) {
case '0':
return (
<FilterGroupItem key={0} filterKey="0" label="Age" required>
<StepInput value={age} onChange={handleAgeChange} required />
</FilterGroupItem>
);
case '1':
return (
<FilterGroupItem
key={1}
filterKey="1"
label="Countries"
active={Object.keys(countries).length > 0}
hiddenInFilterBar={isHidden}
>
<MultiComboBox onSelectionChange={handleCountriesChange}>
<MultiComboBoxItem text="Argentina" selected={countries.argentina} />
<MultiComboBoxItem text="Bulgaria" selected={countries.bulgaria} />
<MultiComboBoxItem text="Finland" selected={countries.finland} />
<MultiComboBoxItem text="Germany" selected={countries.germany} />
<MultiComboBoxItem text="Ireland" selected={countries.ireland} />
<MultiComboBoxItem text="Ukraine" selected={countries.ukraine} />
<MultiComboBoxItem text="USA" selected={countries.usa} />
</MultiComboBox>
</FilterGroupItem>
);
case '2':
return (
<FilterGroupItem
key={2}
filterKey="2"
label="Currency"
active={!!currency}
hiddenInFilterBar={isHidden}
>
<Select onChange={handleCurrencyChange}>
<Option additionalText="€" selected={currency === 'EUR'}>
EUR
</Option>
<Option additionalText="$" selected={currency === 'USD'}>
USD
</Option>
<Option additionalText="£" selected={currency === 'GBP'}>
GBP
</Option>
<Option additionalText="₺" selected={currency === 'TRY'}>
TRY
</Option>
<Option additionalText="¥" selected={currency === 'JPY'}>
JPY
</Option>
</Select>
</FilterGroupItem>
);
case '3':
return (
<FilterGroupItem key={3} filterKey="3" label="Date" active={!!date} hiddenInFilterBar={isHidden}>
<DatePicker value={date} onChange={handleDateChange} style={{ minWidth: 'auto' }} />
</FilterGroupItem>
);
default:
return null;
}
})}
</FilterBar>
<FlexBox direction={FlexBoxDirection.Column}>
<FlexBox>
<Label showColon>Search</Label>
<Text>{search}</Text>
</FlexBox>
<FlexBox>
<Label showColon>Age</Label>
<Text>{age}</Text>
</FlexBox>
<FlexBox>
<Label showColon>Countries</Label>
<Text>{JSON.stringify(countries)}</Text>
</FlexBox>
<FlexBox>
<Label showColon>Currency</Label>
<Text>{currency}</Text>
</FlexBox>
<FlexBox>
<Label showColon>Date</Label>
<Text>{date}</Text>
</FlexBox>
<hr style={{ width: '100%' }} />
<FlexBox>
<Label showColon>Visible Filters</Label>
<Text>{Object.keys(visibleChildrenByKey).join(', ')}</Text>
</FlexBox>
<FlexBox>
<Label showColon>Filters Order</Label>
<Text>{orderedFilterKeys.join(', ')}</Text>
</FlexBox>
</FlexBox>
</ThemeProvider>
);
}
<summary>Show Code</summary>
-
portalContainer
has been removed as it's no longer needed due to the Popover API used in thePopover
ui5 web component. -
onToggleFilters
: Thedetail
property of the event now only includesvisible
andnativeDetail
properties.filters
andsearch
have been removed.// v1 interface OnToggleFiltersEvent extends Omit<MouseEvent, 'detail'> { detail: { visible: boolean; filters: HTMLElement[]; search: HTMLElement; nativeDetail: number }; } onToggleFilters?: (event: OnToggleFiltersEvent) => void; // v2 interface OnToggleFiltersEvent extends Omit<MouseEvent, 'detail'> { detail: { visible: boolean; nativeDetail: number }; } onToggleFilters?: (event: OnToggleFiltersEvent) => void;
-
onFiltersDialogSave
: Thedetail
property of the event now only includesselectedFilterKeys
,reorderedFilterKeys
andnativeDetail
properties.elements
,toggledElements
,filters
,search
,orderIds
have been removed.// v1 interface OnFiltersDialogSaveEvent extends Omit<MouseEvent, 'detail'> { detail: { elements: Record<string, HTMLElement>; toggledElements?: Record<string, HTMLElement>; filters: HTMLElement[]; search: HTMLElement; orderIds: string[]; nativeDetail: number; }; } onFiltersDialogSave?: (event: OnFiltersDialogSaveEvent) => void; // v2 interface OnFiltersDialogSaveEvent extends Omit<MouseEvent, 'detail'> { detail: { selectedFilterKeys: string[]; reorderedFilterKeys: string[]; nativeDetail: number; }; } onFiltersDialogSave?: (event: OnFiltersDialogSaveEvent) => void;
-
onFiltersDialogCancel
: The event is now a callback instead of aUi5CustomEvent
. It implements theescPressed
parameter.// v1 onFiltersDialogCancel?: (event: Ui5CustomEvent) => void; // v2 onFiltersDialogCancel?: (escPressed: boolean) => void;
-
onFiltersDialogClose
: The event is now a callback instead of aUi5CustomEvent
. It implements thecloseTrigger
parameter.// v1 onFiltersDialogClose?: (event: Ui5CustomEvent) => void; // v2 interface FiltersDialogSelectionChangePayload { toggledFilterKeys: Set<string>; selected: boolean | undefined; selectedFilterKeys: Set<string>; previousSelectedFilterKeys: Set<string>; } onFiltersDialogClose?: (closeTrigger: 'cancelButtonPressed' | 'okButtonPressed' | 'escPressed') => void;
-
onFiltersDialogSelectionChange
: The event is now a callback instead of aUi5CustomEvent
. It implements a payload object as parameter.// v1 onFiltersDialogSelectionChange?: ( event: Ui5CustomEvent< TableSelectionDomRef, { element: TableRowDomRef; checked: boolean; selectedRows: unknown[]; previouslySelectedRows: unknown[] } > ) => void; // v2 onFiltersDialogSelectionChange?: (payload: FiltersDialogSelectionChangePayload) => void;
-
onFiltersDialogSearch
: The event is now a standardInput
onInput
event. Thedetail
propertiesvalue
andelement
have been removed.// v1 onFiltersDialogSearch?: (event: CustomEvent<{ value: string; element: HTMLElement }>) => void; // v2 onFiltersDialogSearch?: InputPropTypes['onInput'];
-
onClear
: The event is now a standardToolbarButton
onClick
event. Thedetail
propertiesfilters
andsearch
have been removed.// v1 onClear?: (event: CustomEvent<{ filters: HTMLElement[]; search: HTMLElement }>) => void; // v2 onClear?: ToolbarButtonPropTypes['onClick'];
-
onGo
: The event is now a standardToolbarButton
onClick
event. Thedetail
propertieselements
,filters
,search
,nativeDetail
have been removed.// v1 onGo?: (event: OnGoEvent) => void; // v2 onGo?: ToolbarButtonPropTypes['onClick'];
-
onRestore
: The event is now a callback instead of aCustomEvent
. It implements a payload object as parameter.// v1 onRestore?: ( event: CustomEvent<{ source: string; filters: HTMLElement[] | TableRowDomRef[]; search?: HTMLElement; nativeDetail?: number; }> ) => void; // v2 interface RestorePayload { source: 'dialog' | 'filterBar'; selectedFilterKeys: string[]; previousSelectedFilterKeys: string[] | null; reorderedFilterKeys: string[] | null; } onRestore?: (payload: RestorePayload) => void;
-
onFiltersDialogOpen
(TypeScript): The target of the event is now aToolbarButton
.// v1 onFiltersDialogOpen?: ButtonPropTypes['onClick']; // v2 onFiltersDialogOpen?: ToolbarButtonPropTypes['onClick'];
For a better aligned API, the visible
and visibleInFilterBar
(defaulted to true)
props has been replaced with hidden
and hiddenInFilterBar
(no default value).
You only need to apply changes to your code if visible
or visibleInFilterBar
have been set to false
.
Additionally, each FilterGroupItem
now needs a unique filterKey
in scope of each FilterBar
. Since the orderId
would then be redundant, we've removed this prop.
// v1
import { FilterBar, FilterGroupItem, Input } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<FilterBar enableReordering>
<FilterGroupItem visible={false} orderId="0">
<Input />
</FilterGroupItem>
<FilterGroupItem visibleInFilterBar={false} orderId="1">
<Input />
</FilterGroupItem>
<FilterGroupItem orderId="2">
<Input />
</FilterGroupItem>
</FilterBar>
);
}
// v2
import { FilterBar, FilterGroupItem, Input } from '@ui5/webcomponents-react';
function MyComponent() {
return (
<FilterBar enableReordering>
<FilterGroupItem hidden filterKey="0">
<Input />
</FilterGroupItem>
<FilterGroupItem hiddenInFilterBar filterKey="1">
<Input />
</FilterGroupItem>
<FilterGroupItem filterKey="2">
<Input />
</FilterGroupItem>
</FilterBar>
);
}
onClose
is now a plain callback function and noCustomEvent
anymore. It receives two parameters,action
andescPressed
.
// v1
// onClose?: (event: CustomEvent<{ action: MessageBoxAction }>) => void;
<MessageBox
onClose={(event) => {
console.log(event.detail.action);
}}
>
{children}
</MessageBox>
// v2
// onClose?: (action: MessageBoxActionType | undefined, escPressed?: true) => void;
<MessageBox
onClose={(action, escPressed) => {
console.log(action, escPressed);
}}
>
{children}
</MessageBox>
All Modal helper hooks have been removed. They can be replaced with the regular methods:
useShowDialog
-->showDialog
useShowPopover
-->showPopover
useShowResponsivePopover
-->showResponsivePopover
useShowMenu
-->showMenu
useShowMessageBox
-->showMessageBox
useShowToast
-->showToast
The regular methods are now general purpose, so they can be used both inside the React content (components) as well as outside of the React context (redux, redux-saga, etc.).
In order to provide a place for the Modals
helper to mount the popovers, you have to render the new Modals
component in your application tree.
In addition, the modals are now rendered as children of the <Modals>
component instead of document.body
by default.
The newly introduced DynamicPage
web component comes with its own DynamicPageHeader
and DynamicPageTitle
components, which are unfortunately incompatible with our ObjectPage
implementation.
Please use the ObjectPageHeader
or ObjectPageTitle
component instead.
Removed Props:
showHideHeaderButton
: Hiding the expand/collapse button is not supported by design anymore.showTitleInHeaderContent
: Showing theheaderTitle
as part of theheaderContent
is not supported by design anymore.
Refactored Props:
headerContent
has been renamed toheaderArea
and now only accepts theObjectPageHeader
component.headerTitle
has been renamed totitleArea
and now only accepts theObjectPageTitle
component.headerContentPinnable
has been renamed tohidePinButton
and the logic has been inverted. The pin button is now shown by default.
Renamed Props:
a11yConfig
has been renamed toaccessibilityAttributes
a11yConfig.dynamicPageAnchorBar
has been renamed toaccessibilityAttributes.objectPageAnchorBar
alwaysShowContentHeader
has been renamed toheaderPinned
footer
has been renamed tofooterArea
onToggleHeaderContent
has been renamed toonToggleHeaderArea
onPinnedStateChange
has been renamed toonPinButtonToggle
Also, the namings of internal data-component-name
attributes have been adjusted accordingly. E.g. data-component-name="DynamicPageTitleSubHeader"
has been renamed to data-component-name="ObjectPageTitleSubHeader"
The ObjectPageTitle
component is the renamed implementation of the old (React only) DynamicPageTitle
component. Now, it should only be used in the ObjectPage
.
Removed Props:
actionsToolbarProps
: Since it's now recommended passing theToolbar
component directly, this prop is redundant.navigationActionsToolbarProps
: Since it's now recommended passing theToolbar
component directly, this prop is redundant.showSubHeaderRight
: Displaying the subheader in the same line as the header is not supported by design anymore.
Refactored Props:
actions
has been renamed toactionsBar
. Instead of single actions, theToolbar
component should now be passed.navigationActions
has been renamed tonavigationBar
. Instead of single actions, theToolbar
component should now be passed. TheObjectPageTitle
still offers support for the legacyToolbar
.
The ObjectPageTitle
still offers support for the legacy Toolbar
. You can find out more about this here.
// v1
<DynamicPageTitle
showSubHeaderRight
actionsToolbarProps={{ style: { background: 'red' } }}
navigationActionsToolbarProps={{ style: { background: 'red' } }}
actions={
<>
<Button>Action 1</Button>
<Button>Action 2</Button>
</>
}
navigationActions={
<>
<Button>Navigation-Action 1</Button>
<Button>Navigation-Action 2</Button>
</>
}
/>
// v2
<ObjectPageTitle
// `showSubHeaderRight` has been removed without replacement
// You can now pass all toolbar props directly to the toolbar,
// making `actionsToolbarProps` and `navigationActionsToolbarProps` redundant
actionsBar={
<Toolbar design="Transparent" style={{ background: 'red' }}>
<ToolbarButton text="Action 1" />
<ToolbarButton text="Action 2" />
</Toolbar>
}
navigationBar={
<Toolbar design="Transparent" style={{ background: 'red' }}>
<ToolbarButton text="Navigation-Action 1" />
<ToolbarButton text="Navigation-Action 2" />
</Toolbar>
}
/>
The prop titleText
is now required and the default value true
has been removed for the titleTextUppercase
prop to comply with the updated Fiori design guidelines.
The prop titleText
is now required.
For better alignment with the UI5 Web Components the active
prop has been renamed to interactive
.
mode
has been renamed toselectionMode
onAfterClose
has been renamed toonClose
onAfterOpen
has been renamed toonOpen
The prop withoutModalsProvider
has been removed.
In order to provide a place for the Modals
helper to mount the popovers, you have to render the new Modals
component in your application tree.
The portalContainer
prop has been removed as it is no longer needed.
For better alignment with the UI5 Web Components, the following enums have been renamed:
MessageBoxActions
has been renamed toMessageBoxAction
MessageBoxTypes
has been renamed toMessageBoxType
Themes
has been renamed toTheme