Skip to content

Latest commit

 

History

History
1254 lines (1006 loc) · 49.3 KB

MigrationGuide.mdx

File metadata and controls

1254 lines (1006 loc) · 49.3 KB

import './MigrationGuide.css'; import { Footer, TableOfContent, FilterBarExample } from '@sb/components'; import { Canvas, Meta } from '@storybook/blocks'; import { MessageStrip } from '@ui5/webcomponents-react';

Migration Guide (v1 to v2)

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.

UI5 Web Components Migration Guide

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.

Codemod

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

SAP Devtoberfest 2024 session

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.

General changes

Minimal React Version

The minimum required react and react-dom version is now 18.0.0.

UI5 Web Components Enums are no longer exported

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';

Removed react-jss

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>
  );
}

Removed jestSetup file

We stopped recommending jest as a testing framework over a year ago, thus the jestSetup file is removed. We recommend using Cypress instead.

Changes Exclusive to TypeScript

  • 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.

Scrollbar Styling

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.

Spacing

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:

Common CSS substitute 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

Removed variables without substitute

Show
Removed Variable Property and Value
sapUiNoMargin margin: 0 !important;
sapUiNoMarginTop margin-top: 0 !important;
sapUiNoMarginBottom margin-bottom: 0 !important;
sapUiForceWidthAuto width: auto !important;

Removed hooks

useResponsiveContentPadding

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.

Removed Components

  • TableGroupRow has been removed.
  • NotificationAction has been removed. You can use the Menu component instead.
  • SelectMenu and SelectMenuOption have been removed. The Select and Option now allow custom content.
  • MicroBarChart has been removed (from @ui5/webcomponents-react-charts) as it is not covered by design specs anymore.

Renamed Components

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 to ListItemStandard (ui5-li).
  • CustomListItem has been renamed to ListItemCustom (ui5-li-custom).
  • MultiComboBoxGroupItem has been renamed to MultiComboBoxItemGroup (ui5-mcb-group-item).
  • TableColumn has been renamed to TableHeaderCell (ui5-mcb-group-item).
  • Badge has been renamed to Tag (ui5-tag).
  • ComboBoxGroupItem has been renamed to ComboBoxItemGroup (ui5-cb-group-item).
  • GroupHeaderListItem has been renamed to ListItemGroup (ui5-li-groupheader).

Replaced Components

AnalyticalCard

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>
  );
}

DynamicPage

The DynamicPage component has been replaced with the ui5-dynamic-page web component. This comes with a few breaking changes:

Replaced Props

  • 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
  • alwaysShowContentHeader has been renamed to headerPinned
  • headerCollapsed has been renamed to headerSnapped
  • headerContentPinnable (default: true) has been replaced by hidePinButton (default: false)
  • footer has been replaced by footerArea and is now a slot. To display the footer, you now have to set the showFooter prop to true.
  • headerContent has been replaced by headerArea and is now a slot
  • headerTitle has been replaced by titleArea and is now a slot

Events:

  • onPinnedStateChange has been replaced by onPinButtonToggle.

  • onToggleHeaderContent has been replaced by onTitleToggle.

    // 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);
          }}
        />
      );
    }

Removed Props

  • preserveHeaderStateOnScroll: You should be able to achieve the same behavior with the headerPinned prop.
  • showHideHeaderButton: Hiding the expand/collapse button is not supported by design anymore.

DynamicPageHeader

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.

DynamicPageTitle

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.

Replaced Props

  • actions has been replaced by actionsBar and is now a slot. Instead of passing actions (e.g. Buttons) directly, it is now recommended to use the Toolbar component in order to preserve the intended design.

  • navigationActions has been replaced by navigationBar and is now a slot. Instead of passing actions (e.g. Buttons) directly, it is now recommended to use the Toolbar component in order to preserve the intended design.

  • subHeader has been renamed to subheading and is now a slot.

  • header has been renamed to heading and is now a slot. The font-size isn't automatically adjusted anymore, so to keep the intended design you can leverage the new snappedHeading 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>
      }
    />

Removed Props

  • 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 a Toolbar yourself.

  • navigationActionsToolbarProps is not necessary anymore, as you now can pass a Toolbar yourself.

  • expandedContent is now part of the subheading prop, so if you've rendered a MessageStrip below the subHeader for example, you can now render the subheading and additional content both in the same slot.

  • snappedContent is now part of the snappedSubheading prop, so if you've rendered a MessageStrip below the subHeader for example, you can now render the subheading and additional content both in the same slot.

    Example for combined subHeader and expanded/snappedContent in subheading/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>
        </>
      }
    />

Form

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:

  1. The ui5-form web component doesn't implement the events, attributes and properties of an HTML form element. So, if you want these features to be available, we recommend wrapping the Form component inside a HTML form element.
  2. You can't mix FormGroups and FormItems as children of the Form. Either only use FormItems or only FormGroups with FormItems inside.
  3. Additional HTML elements between Form / FormGroup / FormItem are not allowed. You can still use custom React components if they render a FormGroup or FormItem 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>
  );
}

Loader

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.

Replacing Loader with BusyIndicator

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.

Keep the Loader

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';

Text

The Text component has been replaced with the ui5-text Web Component.

The following props have been removed:

  • wrapping Can be achieved via the maxLines property. If maxLines is set to 1, the text will not wrap, otherwise it will wrap.
  • renderWhitespace Can be achieved by adding white-space: pre-wrap; via inline styles or className to the Text component.
  • hyphenated and emptyIndicator These props are currently not supported by the new ui5-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>;
}

Toolbar

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.

Components with API Changes

ActionSheet

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>
  );
}

AnalyticalTable

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 use subComponentsBehavior with AnalyticalTableSubComponentsBehavior.Visible instead.
  • The default value of sortable (true) has been removed. To allow sorting in your table please set sortable to true yourself.

Removed Props:

  • portalContainer has been removed as it's no longer needed due to the Popover API used in the Popover ui5 web component.

Changed Events:

  • onRowSelect: Since calculating selectedFlatRows was very costly, it has been removed from the detail 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 now AnalyticalTableScaleWidthMode
  • TableSelectionBehavior is now AnalyticalTableSelectionBehavior
  • TableSelectionMode is now AnalyticalTableSelectionMode
  • TableVisibleRowCountMode is now AnalyticalTableVisibleRowCountMode

Changed Enums:

  • The properties and values for the AnalyticalTableSelectionMode enum has been changed.
    • SingleSelect is now Single
    • MultiSelect is now Multiple.

Column Properties Changes

  • canReorder has been removed, please use disableDragAndDrop 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}
/>;

Expandable Text

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.

FilterBar

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 the children, values, and other properties of input components like Input or MultiComboBox 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 the FilterBar. 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 V2 Table 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.

Interactive Example


Code
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>

API Changes

  • portalContainer has been removed as it's no longer needed due to the Popover API used in the Popover ui5 web component.

  • onToggleFilters: The detail property of the event now only includes visible and nativeDetail properties. filters and search 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: The detail property of the event now only includes selectedFilterKeys, reorderedFilterKeys and nativeDetail 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 a Ui5CustomEvent. It implements the escPressed parameter.

    // v1
    onFiltersDialogCancel?: (event: Ui5CustomEvent) => void;
    // v2
    onFiltersDialogCancel?: (escPressed: boolean) => void;
  • onFiltersDialogClose: The event is now a callback instead of a Ui5CustomEvent. It implements the closeTrigger 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 a Ui5CustomEvent. 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 standard Input onInput event. The detail properties value and element have been removed.

    // v1
    onFiltersDialogSearch?: (event: CustomEvent<{ value: string; element: HTMLElement }>) => void;
    // v2
    onFiltersDialogSearch?: InputPropTypes['onInput'];
  • onClear: The event is now a standard ToolbarButton onClick event. The detail properties filters and search have been removed.

    // v1
    onClear?: (event: CustomEvent<{ filters: HTMLElement[]; search: HTMLElement }>) => void;
    // v2
    onClear?: ToolbarButtonPropTypes['onClick'];
  • onGo: The event is now a standard ToolbarButton onClick event. The detail properties elements, filters, search, nativeDetail have been removed.

    // v1
    onGo?: (event: OnGoEvent) => void;
    // v2
    onGo?: ToolbarButtonPropTypes['onClick'];
  • onRestore: The event is now a callback instead of a CustomEvent. 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 a ToolbarButton.

    // v1
    onFiltersDialogOpen?: ButtonPropTypes['onClick'];
    // v2
    onFiltersDialogOpen?: ToolbarButtonPropTypes['onClick'];

FilterGroupItem

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>
  );
}

MessageBox

  • onClose is now a plain callback function and no CustomEvent anymore. It receives two parameters, action and escPressed.
// 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>

Modals

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.

ObjectPage

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 the headerTitle as part of the headerContent is not supported by design anymore.

Refactored Props:

  • headerContent has been renamed to headerArea and now only accepts the ObjectPageHeader component.
  • headerTitle has been renamed to titleArea and now only accepts the ObjectPageTitle component.
  • headerContentPinnable has been renamed to hidePinButton and the logic has been inverted. The pin button is now shown by default.

Renamed Props:

  • a11yConfig has been renamed to accessibilityAttributes
  • a11yConfig.dynamicPageAnchorBar has been renamed to accessibilityAttributes.objectPageAnchorBar
  • alwaysShowContentHeader has been renamed to headerPinned
  • footer has been renamed to footerArea
  • onToggleHeaderContent has been renamed to onToggleHeaderArea
  • onPinnedStateChange has been renamed to onPinButtonToggle

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"

ObjectPageTitle (f.k.a. DynamicPageTitle)

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 the Toolbar component directly, this prop is redundant.
  • navigationActionsToolbarProps: Since it's now recommended passing the Toolbar 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 to actionsBar. Instead of single actions, the Toolbar component should now be passed.
  • navigationActions has been renamed to navigationBar. Instead of single actions, the Toolbar component should now be passed. The ObjectPageTitle still offers support for the legacy Toolbar.

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>
  }
/>

ObjectPageSection

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.

ObjectPageSubSection

The prop titleText is now required.

ObjectStatus

For better alignment with the UI5 Web Components the active prop has been renamed to interactive.

SelectDialog

  • mode has been renamed to selectionMode
  • onAfterClose has been renamed to onClose
  • onAfterOpen has been renamed to onOpen

ThemeProvider

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.

VariantManagement

The portalContainer prop has been removed as it is no longer needed.

Enum Changes

For better alignment with the UI5 Web Components, the following enums have been renamed:

  • MessageBoxActions has been renamed to MessageBoxAction
  • MessageBoxTypes has been renamed to MessageBoxType
  • Themes has been renamed to Theme