@@ -5371,7 +5371,7 @@ exports[`AnalyticalTable with highlight row 1`] = `
@@ -5454,7 +5454,7 @@ exports[`AnalyticalTable with highlight row 1`] = `
@@ -5503,7 +5503,7 @@ exports[`AnalyticalTable with highlight row 1`] = `
@@ -5552,7 +5552,7 @@ exports[`AnalyticalTable with highlight row 1`] = `
@@ -5868,7 +5868,7 @@ exports[`AnalyticalTable without selection Column 1`] = `
@@ -5940,7 +5940,7 @@ exports[`AnalyticalTable without selection Column 1`] = `
@@ -6003,7 +6003,7 @@ exports[`AnalyticalTable without selection Column 1`] = `
@@ -6038,7 +6038,7 @@ exports[`AnalyticalTable without selection Column 1`] = `
@@ -6073,7 +6073,7 @@ exports[`AnalyticalTable without selection Column 1`] = `
diff --git a/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts b/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts
index 9c4b1cbc3cb..ca3b771aea6 100644
--- a/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts
+++ b/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts
@@ -61,30 +61,27 @@ const getRowProps = (rowProps, { instance, row }) => {
const { classes, selectionBehavior, selectionMode, alternateRowColor } = webComponentsReactProperties;
const isEmptyRow = row.original?.emptyRow;
let className = classes.tr;
+ const rowCanBeSelected =
+ [TableSelectionMode.SINGLE_SELECT, TableSelectionMode.MULTI_SELECT].includes(selectionMode) && !isEmptyRow;
if (row.isGrouped) {
className += ` ${classes.tableGroupHeader}`;
}
- if (isEmptyRow) {
- className += ` ${classes.emptyRow}`;
- }
-
if (alternateRowColor && row.index % 2 !== 0) {
className += ` ${classes.alternateRowColor}`;
}
- if (TableSelectionBehavior.ROW_SELECTOR === selectionBehavior) {
- className += ` ${classes.selectionModeRowSelector}`;
- }
-
const newRowProps = {
className,
role: 'row',
'aria-rowindex': row.index
};
- if ([TableSelectionMode.SINGLE_SELECT, TableSelectionMode.MULTI_SELECT].includes(selectionMode) && !isEmptyRow) {
+ if (rowCanBeSelected) {
+ if (TableSelectionBehavior.ROW_SELECTOR !== selectionBehavior) {
+ newRowProps.className += ` ${classes.trActive}`;
+ }
if (row.isSelected) {
newRowProps[ROW_SELECTION_ATTRIBUTE] = '';
}
diff --git a/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx b/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx
index 80a2bdb5d9e..ed04e476b52 100644
--- a/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx
+++ b/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx
@@ -64,9 +64,6 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
};
const classNames = StyleClassHelper.of(classes.tbody, GlobalStyleClasses.sapScrollBar);
- if (selectionMode === TableSelectionMode.SINGLE_SELECT || selectionMode === TableSelectionMode.MULTI_SELECT) {
- classNames.put(classes.selectable);
- }
const lastScrollTop = useRef(0);
diff --git a/packages/main/src/components/FilterBar/FilterBar.jss.ts b/packages/main/src/components/FilterBar/FilterBar.jss.ts
index c696b1c210f..9606b8a4c19 100644
--- a/packages/main/src/components/FilterBar/FilterBar.jss.ts
+++ b/packages/main/src/components/FilterBar/FilterBar.jss.ts
@@ -44,29 +44,18 @@ const styles = {
headerRowRight: {
display: 'flex',
justifyContent: 'flex-end',
- flexGrow: 1
- },
- // is being applied to the span which represents the InfoLabel Text
- label: {
- fontSize: ThemingParameters.sapFontSmallSize,
- fontFamily: ThemingParameters.sapFontFamily,
- lineHeight: '1.125rem',
- fontWeight: 600,
- letterSpacing: '0.0125rem',
- textTransform: 'uppercase',
- textAlign: 'center',
- verticalAlign: 'top',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- display: 'inline-block',
- color: ThemingParameters.sapTextColor
- },
- // specific padding needed for purely numeric input
- numeric: {},
- // specific padding needed for text input
- text: {},
- // displayOnly mode
- displayOnly: {}
+ flexGrow: 1,
+ '& ui5-button': {
+ marginLeft: '0.5rem'
+ }
+ },
+ showFiltersBtn: { minWidth: '108px' },
+ loadingContainer: {
+ marginBottom: '0.5rem',
+ display: 'flex',
+ width: '100%',
+ justifyContent: 'center'
+ }
};
export default styles;
diff --git a/packages/main/src/components/FilterBar/FilterBar.test.tsx b/packages/main/src/components/FilterBar/FilterBar.test.tsx
index a139d33cbff..0258f8c08b2 100644
--- a/packages/main/src/components/FilterBar/FilterBar.test.tsx
+++ b/packages/main/src/components/FilterBar/FilterBar.test.tsx
@@ -1,124 +1,287 @@
import { createPassThroughPropsTest } from '@shared/tests/utils';
-import { mount } from 'enzyme';
+import { Bar } from '@ui5/webcomponents-react/lib/Bar';
+import { Button } from '@ui5/webcomponents-react/lib/Button';
+import { CheckBox } from '@ui5/webcomponents-react/lib/CheckBox';
+import { DatePicker } from '@ui5/webcomponents-react/lib/DatePicker';
import { FilterBar } from '@ui5/webcomponents-react/lib/FilterBar';
-import { FilterItem } from '@ui5/webcomponents-react/lib/FilterItem';
-import { FilterType } from '@ui5/webcomponents-react/lib/FilterType';
+import { FilterGroupItem } from '@ui5/webcomponents-react/lib/FilterGroupItem';
import { Input } from '@ui5/webcomponents-react/lib/Input';
+import { MultiComboBox } from '@ui5/webcomponents-react/lib/MultiComboBox';
+import { Option } from '@ui5/webcomponents-react/lib/Option';
+import { Select } from '@ui5/webcomponents-react/lib/Select';
import { Switch } from '@ui5/webcomponents-react/lib/Switch';
import { VariantManagement } from '@ui5/webcomponents-react/lib/VariantManagement';
+import { MultiComboBoxItem } from '@ui5/webcomponents-react/lib/MultiComboBoxItem';
+import { mount } from 'enzyme';
import React from 'react';
+import { act } from 'react-dom/test-utils';
const variantItems = [
{ label: 'Variant 1', key: '1' },
{ label: 'Variant 2', key: '2' }
];
-const filterItems = [
- { text: 'Text 1', key: '1' },
- { text: 'Text 2', key: '2' }
-];
const variants =
;
const search =
;
describe('FilterBar', () => {
- it('Render without crashing', () => {
+ it('Render without crashing - default props', () => {
const wrapper = mount(
-
- alert(e.getParameter('selectedItem').key)}
- filterItems={filterItems}
- label="Classification"
- key="classification"
- type={FilterType.Select}
- />
-
-
-
+
+
+
+
);
expect(wrapper.render()).toMatchSnapshot();
});
- it('Hide Filter Bar', () => {
+ it('Render without crashing - w/ filter dialog', () => {
const wrapper = mount(
-
- alert(e.getParameter('selectedItem').key)}
- filterItems={filterItems}
- label="Classification"
- key="classification"
- type={FilterType.Select}
- />
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ expect(wrapper.render()).toMatchSnapshot();
+ });
+
+ it('Hide FilterBar', () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+ const toggleBtn = wrapper.find('Button');
+ const toggleBtnBefore = toggleBtn.render();
+ act(() => {
+ toggleBtn.getElement().props.onClick();
+ });
+ expect(toggleBtn.render()).not.toBe(toggleBtnBefore);
+ expect(wrapper.render()).toMatchSnapshot();
+ });
+
+ it('Toggle Filters Dialog', () => {
+ const wrapper = mount(
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
- const component = wrapper.find('ui5-button').last().instance() as any;
- component.fireEvent('click');
+ const openFiltersDialogBtn = wrapper.find('.FilterBar-headerRowRight-0-2-7').childAt(3);
+ act(() => {
+ openFiltersDialogBtn.prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeTruthy();
+ expect(wrapper.render()).toMatchSnapshot();
+ const filtersDialogBtns = (index) => wrapper.find(Bar).find(Button).at(index);
+ act(() => {
+ //@ts-ignore
+ filtersDialogBtns(0).prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeFalsy();
expect(wrapper.render()).toMatchSnapshot();
+ act(() => {
+ openFiltersDialogBtn.prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeTruthy();
+ act(() => {
+ //@ts-ignore
+ filtersDialogBtns(3).prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeFalsy();
+ act(() => {
+ openFiltersDialogBtn.prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeTruthy();
+ act(() => {
+ //@ts-ignore
+ filtersDialogBtns(4).prop('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('ui5-dialog').exists()).toBeFalsy();
});
- it('Select Filter Item', () => {
+ it('Group Filter Items mounted in Dialog', () => {
const wrapper = mount(
-
- alert(e.getParameter('selectedItem').key)}
- filterItems={filterItems}
- label="Classification"
- key="classification"
- type={FilterType.Select}
- />
- alert(e.getParameter('selectedItem').key)}
- loading
- filterItems={filterItems}
- label="Classification"
- key="classification2"
- type={FilterType.Select}
- />
- alert(e.getParameter('selectedItem').key)}
- // filterItems={filterItems}
- label="Classification"
- key="classification3"
- type={FilterType.Default}
- />
- alert(e.getParameter('selectedItem').key)}
- filterItems={filterItems}
- label="Classification"
- key="classification3"
- type={FilterType.MultiSelect}
- />
+
+
+
+
+
+
+
+
+
+
);
- wrapper.find('ui5-option').at(1).simulate('change');
+ const filterItemsFB = wrapper.find(FilterGroupItem);
+ const openFiltersDialogBtn = wrapper.find('.FilterBar-headerRowRight-0-2-7').childAt(3);
+ act(() => {
+ openFiltersDialogBtn.prop('onClick')();
+ });
+ wrapper.update();
- expect(wrapper.render()).toMatchSnapshot();
+ const filterItemsDialog = wrapper.find('ui5-dialog').find(FilterGroupItem);
+
+ const filterItemsDialogString = filterItemsDialog.debug({ ignoreProps: true });
+ const filterItemsFBString = filterItemsFB.debug({ ignoreProps: true });
+ // @ts-ignore
+ expect(filterItemsDialogString).toEqual(filterItemsFBString);
+ const filterItemMandatory = filterItemsDialog.at(2);
+ expect(filterItemMandatory.prop('required')).toBeTruthy();
+ const checkbox = filterItemMandatory.parents().find(CheckBox).at(0);
+ expect(checkbox.prop('disabled')).toBeTruthy();
});
createPassThroughPropsTest(FilterBar);
diff --git a/packages/main/src/components/FilterBar/FilterBarDialog.jss.ts b/packages/main/src/components/FilterBar/FilterBarDialog.jss.ts
new file mode 100644
index 00000000000..74093d4deab
--- /dev/null
+++ b/packages/main/src/components/FilterBar/FilterBarDialog.jss.ts
@@ -0,0 +1,72 @@
+import { CssSizeVariables } from '@ui5/webcomponents-react-base/lib/CssSizeVariables';
+import { sapUiContentPadding } from '@ui5/webcomponents-react-base/lib/spacing';
+
+const styles = {
+ dialog: {
+ ...sapUiContentPadding,
+ display: 'flex',
+ flexDirection: 'column',
+ maxWidth: '960px',
+ width: '80vw',
+ maxHeight: '70vh'
+ },
+ header: {
+ padding: '0.25rem 1rem 0 1rem',
+ '& *': {
+ margin: '0.25rem 0 0.25rem 0'
+ },
+ '& ui5-input': {
+ width: '100%'
+ }
+ },
+ footer: {
+ '& :not(:last-child)': {
+ marginRight: '0.25rem'
+ }
+ },
+ groupContainer: {
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ groupTitle: {
+ maxWidth: '85%',
+ marginRight: '0.5rem'
+ },
+ filters: {
+ padding: '1rem 0 2rem 0'
+ },
+ singleFilter: {
+ display: 'grid',
+ gridTemplateColumns: `auto minmax(${CssSizeVariables.sapWcrCheckBoxWidthHeight},7%)`,
+ gridTemplateRows: 'auto',
+ gridColumnGap: '0.5rem',
+ '@media(max-width:700px)': {
+ marginTop: '0.5rem'
+ },
+ '& ui5-checkbox': {
+ placeSelf: 'center start',
+ '@media(max-width:700px)': {
+ marginTop: '0.8rem',
+ paddingLeft: 0,
+ placeSelf: 'end start'
+ }
+ }
+ },
+
+ fbSearch: {
+ '@media(min-width:700px)': {
+ display: 'grid',
+ gridTemplateColumns: '20% auto 7%',
+ gridTemplateRows: 'auto',
+ gridRowGap: '0.5rem',
+ gridColumnGap: '0.5rem'
+ },
+ paddingBottom: '2rem',
+ width: '100%',
+ '& ui5-input': {
+ width: '100%'
+ }
+ }
+};
+
+export default styles;
diff --git a/packages/main/src/components/FilterBar/FilterDialog.tsx b/packages/main/src/components/FilterBar/FilterDialog.tsx
new file mode 100644
index 00000000000..491b999ef45
--- /dev/null
+++ b/packages/main/src/components/FilterBar/FilterDialog.tsx
@@ -0,0 +1,283 @@
+import '@ui5/webcomponents-icons/dist/icons/search';
+import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
+import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
+import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
+import {
+ BASIC,
+ CANCEL,
+ CLEAR,
+ RESTORE,
+ SAVE,
+ SEARCH_FOR_FILTERS,
+ SHOW_ON_FILTER_BAR
+} from '@ui5/webcomponents-react/dist/assets/i18n/i18n-defaults';
+import { Bar } from '@ui5/webcomponents-react/lib/Bar';
+import { BarDesign } from '@ui5/webcomponents-react/lib/BarDesign';
+import { Button } from '@ui5/webcomponents-react/lib/Button';
+import { ButtonDesign } from '@ui5/webcomponents-react/lib/ButtonDesign';
+import { CheckBox } from '@ui5/webcomponents-react/lib/CheckBox';
+import { Dialog } from '@ui5/webcomponents-react/lib/Dialog';
+import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
+import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems';
+import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
+import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent';
+import { Icon } from '@ui5/webcomponents-react/lib/Icon';
+import { Input } from '@ui5/webcomponents-react/lib/Input';
+import { Text } from '@ui5/webcomponents-react/lib/Text';
+import { Title } from '@ui5/webcomponents-react/lib/Title';
+import { TitleLevel } from '@ui5/webcomponents-react/lib/TitleLevel';
+import React, { Children, cloneElement, ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import styles from './FilterBarDialog.jss';
+import { filterValue, renderSearchWithValue } from './utils';
+
+const useStyles = createComponentStyles(styles, { name: 'FilterBarDialog' });
+export const FilterDialog = (props) => {
+ const {
+ filterBarRefs,
+ open,
+ handleDialogClose,
+ children,
+ showClearButton,
+ showRestoreButton,
+ showGoButton,
+ showSearch,
+ renderFBSearch,
+ handleClearFilters,
+ handleRestoreFilters,
+ handleDialogSave,
+ searchValue,
+ handleSearchValueChange,
+ onGo,
+ handleSelectionChange,
+ handleDialogSearch,
+ handleDialogCancel
+ } = props;
+ const classes = useStyles();
+ const [searchString, setSearchString] = useState('');
+ const searchRef = useRef(null);
+ const [toggledFilters, setToggledFilters] = useState({});
+ const dialogRefs = useRef({});
+ const dialogRef = useRef();
+
+ const [
+ basicText,
+ cancelText,
+ clearText,
+ restoreText,
+ saveText,
+ searchForFiltersText,
+ showOnFilterBarText
+ ] = useI18nText(
+ '@ui5/webcomponents-react',
+ BASIC,
+ CANCEL,
+ CLEAR,
+ RESTORE,
+ SAVE,
+ SEARCH_FOR_FILTERS,
+ SHOW_ON_FILTER_BAR
+ );
+
+ useEffect(() => {
+ if (open) {
+ dialogRef.current.open();
+ }
+ }, [open]);
+
+ const handleSearch = useCallback(
+ (e) => {
+ if (handleDialogSearch) {
+ handleDialogSearch(enrichEventWithDetails(e, { value: e.target.value }));
+ }
+ setSearchString(e.target.value);
+ },
+ [setSearchString, handleDialogSearch]
+ );
+ const handleSave = useCallback(
+ (e) => {
+ if (renderFBSearch) {
+ handleSearchValueChange(searchRef.current?.children[1].value);
+ }
+ handleDialogSave(e, dialogRefs.current, toggledFilters);
+ },
+ [renderFBSearch, handleSearchValueChange, searchRef, handleDialogSave, toggledFilters, dialogRefs]
+ );
+
+ const handleClose = useCallback(
+ (e) => {
+ if (!showGoButton) {
+ handleSave(e);
+ return;
+ }
+ handleDialogClose(e);
+ },
+ [showGoButton, handleSave, handleDialogClose]
+ );
+
+ const handleDialogGo = useCallback(
+ (e) => {
+ if (onGo) {
+ onGo(enrichEventWithDetails(e));
+ }
+ handleDialogClose(e);
+ },
+ [onGo, handleDialogClose]
+ );
+
+ const handleRestore = useCallback(
+ (e) => {
+ handleRestoreFilters(e, 'dialog');
+ },
+ [handleRestoreFilters]
+ );
+
+ const handleCancel = useCallback(
+ (e) => {
+ if (handleDialogCancel) {
+ handleDialogCancel(enrichEventWithDetails(e));
+ }
+ handleDialogClose(e);
+ },
+ [handleDialogCancel]
+ );
+
+ const footerContentRight = useMemo(
+ () => (
+
+ {showGoButton && (
+
+ )}
+ {showClearButton && }
+ {showRestoreButton && }
+
+
+
+ ),
+ [
+ showGoButton,
+ classes.footer,
+ handleDialogGo,
+ showClearButton,
+ handleClearFilters,
+ showRestoreButton,
+ handleRestore,
+ handleSave,
+ handleCancel
+ ]
+ );
+
+ const renderFooter = useCallback(() => {
+ return ;
+ }, [footerContentRight]);
+
+ const renderHeader = useCallback(
+ () => (
+
+ Filters
+ {showSearch && (
+ } />
+ )}
+
+ ),
+ [classes.header, showSearch, handleSearch]
+ );
+
+ const renderChildren = useCallback(() => {
+ return children
+ .filter((item) => {
+ if (item.type.displayName !== 'FilterGroupItem') return true; //needed for deprecated FilterItem or custom elements
+ return (
+ !!item?.props &&
+ item.props?.visible &&
+ (item.props?.label?.toLowerCase().includes(searchString.toLowerCase()) || searchString.length === 0)
+ );
+ })
+ .map((child) => {
+ if (child.type.displayName !== 'FilterGroupItem') return child; //needed for deprecated FilterItem or custom elements
+ const filterBarItemRef = filterBarRefs.current[child.key];
+ let filterItemProps = {};
+ if (filterBarItemRef) {
+ filterItemProps = filterValue(filterBarItemRef, child);
+ }
+ if (!child.props.children) return child;
+ return cloneElement(child as ReactElement, {
+ children: {
+ ...child.props.children,
+ props: {
+ ...child.props.children.props,
+ ...filterItemProps
+ },
+ ref: (node) => {
+ dialogRefs.current[child.key] = node;
+ }
+ }
+ });
+ });
+ }, [children, searchString, filterBarRefs]);
+
+ const handleCheckBoxChange = useCallback(
+ (element, toggledFilters) => (e) => {
+ if (handleSelectionChange) {
+ handleSelectionChange(enrichEventWithDetails(e, { element, checked: e.target.checked }));
+ }
+ setToggledFilters({ ...toggledFilters, [element.key]: e.target.checked });
+ },
+ [setToggledFilters, handleSelectionChange]
+ );
+ const renderGroups = useCallback(() => {
+ let groups = {};
+ Children.forEach(renderChildren(), (child) => {
+ const childGroups = child.props.groupName ?? 'default';
+ if (groups[childGroups]) {
+ groups[childGroups].push(child);
+ } else {
+ groups[childGroups] = [child];
+ }
+ });
+ return Object.keys(groups)
+ .sort((x, y) => (x === 'default' ? -1 : y === 'role' ? 1 : 0))
+ .map((item, index) => {
+ const filters = groups[item].map((el) => {
+ return (
+
+ {el}
+
+
+ );
+ });
+ return (
+
+
+
+ {item === 'default' ? basicText : item}
+
+ {index === 0 && {showOnFilterBarText}}
+
+
{filters}
+
+ );
+ });
+ }, [renderChildren, toggledFilters, handleCheckBoxChange]);
+
+ return (
+
+ );
+};
diff --git a/packages/main/src/components/FilterBar/__snapshots__/FilterBar.test.tsx.snap b/packages/main/src/components/FilterBar/__snapshots__/FilterBar.test.tsx.snap
index a0c775f1c03..9c136e6c121 100644
--- a/packages/main/src/components/FilterBar/__snapshots__/FilterBar.test.tsx.snap
+++ b/packages/main/src/components/FilterBar/__snapshots__/FilterBar.test.tsx.snap
@@ -1,66 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`FilterBar Hide Filter Bar 1`] = `
+exports[`FilterBar Hide FilterBar 1`] = `
+`;
+
+exports[`FilterBar Render without crashing - default props 1`] = `
+
+
+
-
- Custom Filter 1
-
-
+
+
+ Classification
+
+
+
+ Option 1
+
+
+ Option 2
+
+
+ Option 3
+
+
+ Option 4
+
+
`;
-exports[`FilterBar Render without crashing 1`] = `
+exports[`FilterBar Render without crashing - w/ filter dialog 1`] = `
@@ -196,74 +182,921 @@ exports[`FilterBar Render without crashing 1`] = `
-
-
-
- Classification
-
+
+
+
+ Input
+
+
+
+
+
+
+
+
+
+
+
+ SELECT w/ initial selected
+
+
+
+ Option 1
+
+ Option 2
+
+
+ Option 3
+
+
+ Option 4
+
+
+
+
+
+
+
+
+
+ SELECT w/o initial selected
+
+
+
+
- Text 1
+ Test 1
+ Test 2
+
+
- Text 2
+ Test 3
+
+
+ Test 4
+
+
+ Test 5
-
- Custom Filter 1
-
-
-
+
+
+ Group 2:
+ MultBox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Group 2:
+ Date Picker
+
+
`;
-exports[`FilterBar Select Filter Item 1`] = `
+exports[`FilterBar Toggle Filters Dialog 1`] = `
+Array [
+
+
+
+ Filters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Basic
+
+
+ Show on Filter Bar
+
+
+
+
+
+
+
+
+
+
+
+ SELECT w/ initial selected
+
+
+
+
+ Option 1
+
+
+ Option 2
+
+
+ Option 3
+
+
+ Option 4
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT w/o initial selected
+
+
+
+
+ Test 1
+
+
+ Test 2
+
+
+ Test 3
+
+
+ Test 4
+
+
+ Test 5
+
+
+
+
+
+
+
+
+
+
+
+ Group 2
+
+
+
+
+
+
+
+
+
+ MultBox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Date Picker
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+ SELECT w/ initial selected
+
+
+
+
+ Option 1
+
+
+ Option 2
+
+
+ Option 3
+
+
+ Option 4
+
+
+
+
+
+
+
+
+
+ SELECT w/o initial selected
+
+
+
+
+ Test 1
+
+
+ Test 2
+
+
+ Test 3
+
+
+ Test 4
+
+
+ Test 5
+
+
+
+
+
+
+
+
+ Group 2:
+ MultBox
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Group 2:
+ Date Picker
+
+
+
+
+
+
+
,
+]
+`;
+
+exports[`FilterBar Toggle Filters Dialog 2`] = `
@@ -320,109 +1153,243 @@ exports[`FilterBar Select Filter Item 1`] = `
+
+
+
-
- Classification
-
-
+
+
+ Input
+
+
+
+
+
+
+
+
+
+
- Text 1
+
+ SELECT w/ initial selected
+
+
+
+
+ Option 1
- Text 2
+ Option 2
+
+
+ Option 3
+
+
+ Option 4
-
- Classification
-
-
+
+
+ SELECT w/o initial selected
+
+
+
+ Test 1
+
+
+ Test 2
+
+
+ Test 3
+
+
+ Test 4
+
+
+ Test 5
+
+
-
- Classification
-
-
+
+ Group 2:
+ MultBox
+
+
+
+ >
+
+
+
+
+
-
- Classification
-
-
-
- Text 1
-
-
- Text 2
-
-
+ Group 2:
+ Date Picker
+
+
+
diff --git a/packages/main/src/components/FilterBar/demo.stories.tsx b/packages/main/src/components/FilterBar/demo.stories.tsx
index 304767b878d..cb348e05d44 100644
--- a/packages/main/src/components/FilterBar/demo.stories.tsx
+++ b/packages/main/src/components/FilterBar/demo.stories.tsx
@@ -1,12 +1,14 @@
import { action } from '@storybook/addon-actions';
-import { boolean, select } from '@storybook/addon-knobs';
+import { boolean, number, text } from '@storybook/addon-knobs';
+import { DatePicker } from '@ui5/webcomponents-react/lib/DatePicker';
import { FilterBar } from '@ui5/webcomponents-react/lib/FilterBar';
-import { FilterItem } from '@ui5/webcomponents-react/lib/FilterItem';
-import { FilterType } from '@ui5/webcomponents-react/lib/FilterType';
+import { FilterGroupItem } from '@ui5/webcomponents-react/lib/FilterGroupItem';
import { Input } from '@ui5/webcomponents-react/lib/Input';
-import { PlacementType } from '@ui5/webcomponents-react/lib/PlacementType';
+import { MultiComboBox } from '@ui5/webcomponents-react/lib/MultiComboBox';
+import { MultiComboBoxItem } from '@ui5/webcomponents-react/lib/MultiComboBoxItem';
+import { Option } from '@ui5/webcomponents-react/lib/Option';
+import { Select } from '@ui5/webcomponents-react/lib/Select';
import { Switch } from '@ui5/webcomponents-react/lib/Switch';
-import { TitleLevel } from '@ui5/webcomponents-react/lib/TitleLevel';
import { VariantManagement } from '@ui5/webcomponents-react/lib/VariantManagement';
import React from 'react';
@@ -14,61 +16,176 @@ const variantItems = [
{ label: 'Variant 1', key: '1' },
{ label: 'Variant 2', key: '2' }
];
-const filterItems = [
- { text: 'Text 1', key: '1' },
- { text: 'Text 2', key: '2' }
-];
-export const renderStory = () => {
+export const renderDefaultStory = () => {
+ return (
+
}
+ variants={
}
+ useToolbar={boolean('useToolbar', true)}
+ filterBarExpanded={boolean('filterBarExpanded', true)}
+ loading={boolean('loading', false)}
+ considerGroupName={boolean('considerGroupName', false)}
+ filterContainerWidth={text('auto', undefined)}
+ activeFiltersCount={number('activeFiltersCount', undefined)}
+ showClearOnFB={boolean('showClearOnFB', false)}
+ showRestoreOnFB={boolean('showRestoreOnFB', false)}
+ showGo={boolean('showGo', false)}
+ showGoOnFB={boolean('showGoOnFB', false)}
+ showFilterConfiguration={boolean('showFilterConfiguration', false)}
+ showSearchOnFiltersDialog={boolean('showSearchOnFiltersDialog', false)}
+ showClearButton={boolean('showClearButton', false)}
+ showRestoreButton={boolean('showRestoreButton', false)}
+ onToggleFilters={action('onToggleFilters')}
+ onFiltersDialogOpen={action('onFiltersDialogOpen')}
+ onFiltersDialogClose={action('onFiltersDialogClose')}
+ onFiltersDialogSave={action('onFiltersDialogSave')}
+ onFiltersDialogClear={action('onFiltersDialogClear')}
+ onClear={action('onClear')}
+ onFiltersDialogSelectionChange={action('onFiltersDialogSelectionChange')}
+ onFiltersDialogSearch={action('onFiltersDialogSearch')}
+ onGo={action('onGo')}
+ onRestore={action('onRestore')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+renderDefaultStory.story = {
+ name: 'Default'
+};
+
+export const renderStoryWithFiltersDialog = () => {
return (
}
- variants={
-
- }
+ variants={
}
+ useToolbar={boolean('useToolbar', true)}
+ filterBarExpanded={boolean('filterBarExpanded', true)}
+ loading={boolean('loading', false)}
+ considerGroupName={boolean('considerGroupName', true)}
+ filterContainerWidth={text('filterContainerWidth', '13rem')}
+ activeFiltersCount={number('activeFiltersCount', 0)}
+ showClearOnFB={boolean('showClearOnFB', true)}
+ showRestoreOnFB={boolean('showRestoreOnFB', true)}
+ showGo={boolean('showGo', true)}
+ showGoOnFB={boolean('showGoOnFB', true)}
+ showFilterConfiguration={boolean('showFilterConfiguration', true)}
+ showSearchOnFiltersDialog={boolean('showSearchOnFiltersDialog', true)}
+ showClearButton={boolean('showClearButton', true)}
+ showRestoreButton={boolean('showRestoreButton', true)}
+ onToggleFilters={action('onToggleFilters')}
+ onFiltersDialogOpen={action('onFiltersDialogOpen')}
+ onFiltersDialogClose={action('onFiltersDialogClose')}
+ onFiltersDialogSave={action('onFiltersDialogSave')}
+ onFiltersDialogClear={action('onFiltersDialogClear')}
+ onClear={action('onClear')}
+ onFiltersDialogSelectionChange={action('onFiltersDialogSelectionChange')}
+ onFiltersDialogSearch={action('onFiltersDialogSearch')}
+ onGo={action('onGo')}
+ onRestore={action('onRestore')}
>
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
-renderStory.storyName = 'Default';
+renderStoryWithFiltersDialog.story = {
+ name: 'With Filters Dialog'
+};
export default {
title: 'Components / FilterBar',
component: FilterBar,
parameters: {
- subcomponents: { FilterItem }
+ subcomponents: { FilterGroupItem }
}
};
diff --git a/packages/main/src/components/FilterBar/index.tsx b/packages/main/src/components/FilterBar/index.tsx
index 4e560de74c9..ca9232dec49 100644
--- a/packages/main/src/components/FilterBar/index.tsx
+++ b/packages/main/src/components/FilterBar/index.tsx
@@ -1,34 +1,152 @@
+import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
+import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
+import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
+import {
+ CLEAR,
+ FILTERS,
+ GO,
+ HIDE_FILTER_BAR,
+ RESTORE,
+ SHOW_FILTER_BAR
+} from '@ui5/webcomponents-react/dist/assets/i18n/i18n-defaults';
+import { BusyIndicator } from '@ui5/webcomponents-react/lib/BusyIndicator';
+import { BusyIndicatorSize } from '@ui5/webcomponents-react/lib/BusyIndicatorSize';
import { Button } from '@ui5/webcomponents-react/lib/Button';
import { ButtonDesign } from '@ui5/webcomponents-react/lib/ButtonDesign';
-import React, { FC, forwardRef, ReactNode, ReactNodeArray, RefObject, useCallback, useState } from 'react';
-import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
+import React, {
+ Children,
+ cloneElement,
+ CSSProperties,
+ FC,
+ forwardRef,
+ ReactElement,
+ ReactNode,
+ ReactNodeArray,
+ RefObject,
+ useCallback,
+ useEffect,
+ useRef,
+ useState
+} from 'react';
import { CommonProps } from '../../interfaces/CommonProps';
import styles from './FilterBar.jss';
+import { FilterDialog } from './FilterDialog';
+import { filterValue, renderSearchWithValue } from './utils';
export interface FilterBarPropTypes extends CommonProps {
- variants?: ReactNode;
- search?: ReactNode;
children: ReactNode | ReactNodeArray;
+ search?: ReactNode;
+ variants?: ReactNode;
+ useToolbar?: boolean;
+ filterBarExpanded?: boolean;
+ filterContainerWidth?: CSSProperties['width'];
+ considerGroupName?: boolean;
+ showClearOnFB?: boolean;
+ showGoOnFB?: boolean;
+ showFilterConfiguration?: boolean;
+ showClearButton?: boolean;
+ showRestoreButton?: boolean;
+ showGo?: boolean;
+ activeFiltersCount?: number | string;
+ loading?: boolean;
+ showSearchOnFiltersDialog?: boolean;
+ showRestoreOnFB?: boolean;
+ onToggleFilters?: (event: CustomEvent<{ visible?: boolean }>) => void;
+ onFiltersDialogSave?: (event: CustomEvent<{ elements?: unknown; toggledElements?: unknown }>) => void;
+ onFiltersDialogClear?: (event: CustomEvent) => void;
+ onFiltersDialogCancel?: (event: CustomEvent) => void;
+ onFiltersDialogOpen?: (event: CustomEvent) => void;
+ onFiltersDialogClose?: (event: CustomEvent) => void;
+ onFiltersDialogSelectionChange?: (event: CustomEvent<{ element?: unknown; checked?: unknown }>) => void;
+ onFiltersDialogSearch?: (event: CustomEvent<{ value?: unknown }>) => void;
+ onClear?: (event: CustomEvent) => void;
+ onGo?: (event: CustomEvent) => void;
+ onRestore?: (event: CustomEvent<{ source?: unknown }>) => void;
}
-interface FilterBarInternalProps extends FilterBarPropTypes {}
-
const useStyles = createComponentStyles(styles, { name: 'FilterBar' });
/**
*
import { FilterBar } from '@ui5/webcomponents-react/lib/FilterBar';
*/
const FilterBar: FC
= forwardRef((props: FilterBarPropTypes, ref: RefObject) => {
- const { children, search, variants, className, style, tooltip, slot } = props as FilterBarInternalProps;
- const [showFilters, setShowFilters] = useState(true);
+ const {
+ children,
+ useToolbar,
+ loading,
+ filterBarExpanded,
+ considerGroupName,
+ filterContainerWidth,
+ activeFiltersCount,
+ showClearOnFB,
+ showGoOnFB,
+ showGo,
+ showFilterConfiguration,
+ showRestoreOnFB,
+ showClearButton,
+ showRestoreButton,
+ showSearchOnFiltersDialog,
+ style,
+ className,
+ tooltip,
+ slot,
+ search,
+ variants,
- const classes = useStyles();
+ onToggleFilters,
+ onFiltersDialogOpen,
+ onFiltersDialogCancel,
+ onFiltersDialogClose,
+ onFiltersDialogSave,
+ onFiltersDialogClear,
+ onClear,
+ onFiltersDialogSelectionChange,
+ onFiltersDialogSearch,
+ onGo,
+ onRestore
+ } = props;
+ const [showFilters, setShowFilters] = useState(useToolbar ? filterBarExpanded : true);
+ const [mountFilters, setMountFilters] = useState(true);
+ const [dialogOpen, setDialogOpen] = useState(false);
+ const [searchValue, setSearchValue] = useState('');
+ const searchRef = useRef(null);
+ const filterRefs = useRef({});
+ const [dialogRefs, setDialogRefs] = useState({});
+ const [toggledFilters, setToggledFilters] = useState({});
+ const prevVisibleInFilterBarProps = useRef({});
- const handleHideFilterBar = useCallback(() => {
- setShowFilters(!showFilters);
- }, [showFilters]);
+ const [clearText, restoreText, showFilterBarText, hideFilterBarText, goText, filtersText] = useI18nText(
+ '@ui5/webcomponents-react',
+ CLEAR,
+ RESTORE,
+ SHOW_FILTER_BAR,
+ HIDE_FILTER_BAR,
+ GO,
+ FILTERS
+ );
+
+ useEffect(() => {
+ if (showFilterConfiguration) {
+ Children.toArray(children).forEach((item: ReactElement) => {
+ if (
+ prevVisibleInFilterBarProps.current?.[item.key] !== undefined &&
+ prevVisibleInFilterBarProps.current?.[item.key] !== item.props.visibleInFilterBar
+ ) {
+ const updatedToggledFilters = toggledFilters;
+ delete updatedToggledFilters[item.key];
+ setToggledFilters(updatedToggledFilters);
+ }
+ });
+ }
+ }, [children, prevVisibleInFilterBarProps, setToggledFilters, toggledFilters, showFilterConfiguration]);
+
+ useEffect(() => {
+ setShowFilters(useToolbar ? filterBarExpanded : true);
+ }, [setShowFilters, useToolbar, filterBarExpanded]);
+
+ const classes = useStyles();
const filterAreaClasses = StyleClassHelper.of(classes.filterArea);
if (showFilters) {
@@ -37,35 +155,257 @@ const FilterBar: FC = forwardRef((props: FilterBarPropTypes,
filterAreaClasses.put(classes.filterAreaClosed);
}
- const passThroughProps = usePassThroughHtmlProps(props);
+ const handleToggle = useCallback(
+ (e) => {
+ if (onToggleFilters) {
+ onToggleFilters(enrichEventWithDetails(e, { visible: !showFilters }));
+ }
+ setShowFilters(!showFilters);
+ },
+ [showFilters, onToggleFilters, setShowFilters]
+ );
+
+ const handleDialogSave = useCallback(
+ (e, dialogRefs, updatedToggledFilters) => {
+ setDialogRefs(dialogRefs);
+ setToggledFilters({ ...toggledFilters, ...updatedToggledFilters });
+ if (onFiltersDialogSave) {
+ onFiltersDialogSave(enrichEventWithDetails(e));
+ }
+ handleDialogClose(e);
+ },
+ [setDialogOpen, setDialogRefs, setToggledFilters, onFiltersDialogSave, toggledFilters]
+ );
- const filterBarClasses = StyleClassHelper.of(classes.outerContainer);
+ const handleDialogOpen = useCallback(
+ (e) => {
+ setDialogOpen(true);
+ if (onFiltersDialogOpen) {
+ onFiltersDialogOpen(enrichEventWithDetails(e));
+ }
+ },
+ [setDialogOpen, onFiltersDialogOpen]
+ );
+
+ const handleDialogClose = useCallback(
+ (e) => {
+ if (onFiltersDialogClose) {
+ onFiltersDialogClose(enrichEventWithDetails(e));
+ }
+ setDialogOpen(false);
+ },
+ [setDialogOpen, onFiltersDialogClose]
+ );
+
+ const passThroughProps = usePassThroughHtmlProps(props, [
+ 'onToggleFilters',
+ 'onFiltersDialogOpen',
+ 'onFiltersDialogClose',
+ 'onFiltersDialogSave',
+ 'onFiltersDialogClear',
+ 'onClear',
+ 'onFiltersDialogSelectionChange',
+ 'onFiltersDialogSearch',
+ 'onGo',
+ 'onRestore',
+ 'onFiltersDialogCancel'
+ ]);
+
+ const safeChildren = useCallback(() => {
+ if (showFilterConfiguration && Object.keys(toggledFilters).length > 0) {
+ return Children.toArray(children).map((child: ReactElement) => {
+ if (toggledFilters?.[child.key] !== undefined) {
+ return cloneElement(child, {
+ visibleInFilterBar: toggledFilters[child.key]
+ });
+ }
+ return child;
+ });
+ }
+ return Children.toArray(children) as any;
+ }, [toggledFilters, children]);
+
+ const renderChildren = useCallback(() => {
+ let childProps = { considerGroupName: considerGroupName, inFB: true } as any;
+ return safeChildren()
+ .filter((item) => {
+ if (item.type.displayName !== 'FilterGroupItem') return true; //needed for deprecated FilterItem or custom elements
+
+ return !!item?.props && item?.props.visible && item.props?.visibleInFilterBar;
+ })
+ .map((child) => {
+ if (child.type.displayName !== 'FilterGroupItem') return child; //needed for deprecated FilterItem or custom elements
+ if (filterContainerWidth) {
+ childProps.style = { width: filterContainerWidth, ...child.props.style };
+ }
+ if (!showFilterConfiguration) {
+ return cloneElement(child as ReactElement, { ...childProps });
+ }
+ prevVisibleInFilterBarProps.current[child.key] = child.props.visibleInFilterBar;
+ let filterItemProps = {};
+ if (Object.keys(dialogRefs).length > 0) {
+ const dialogItemRef = dialogRefs[child.key];
+ if (dialogItemRef) {
+ filterItemProps = filterValue(dialogItemRef, child);
+ }
+ }
+ if (!child.props.children)
+ return cloneElement(child as ReactElement, {
+ ...childProps
+ });
+ return cloneElement(child as ReactElement, {
+ ...childProps,
+ children: {
+ ...child.props.children,
+ props: {
+ ...child.props.children.props,
+ ...filterItemProps
+ },
+ ref: (node) => {
+ filterRefs.current[child.key] = node;
+ }
+ }
+ });
+ });
+ }, [filterContainerWidth, considerGroupName, dialogRefs, safeChildren, showFilterConfiguration]);
+
+ const handleSearchValueChange = useCallback(
+ (newVal) => {
+ setSearchValue(newVal);
+ },
+ [setSearchValue]
+ );
+
+ const handleRestoreFilters = useCallback(
+ (e, source) => {
+ if (source === 'dialog' && showGo) {
+ setDialogOpen(false);
+ setDialogOpen(true);
+ } else if (source === 'filterBar' && showGoOnFB) {
+ setMountFilters(false);
+ setMountFilters(true);
+ }
+ if (onRestore) {
+ onRestore(enrichEventWithDetails(e, { source }));
+ }
+ },
+ [setDialogOpen, showGo, showGoOnFB, onRestore]
+ );
+
+ const handleFBRestore = useCallback(
+ (e) => {
+ handleRestoreFilters(e, 'filterBar');
+ },
+ [handleRestoreFilters]
+ );
+
+ const cssClasses = StyleClassHelper.of(classes.outerContainer);
if (className) {
- filterBarClasses.put(className);
+ cssClasses.put(className);
}
return (
-
-
- {variants}
- {search &&
{search}
}
-
-
-
+ <>
+ {dialogOpen && showFilterConfiguration && (
+
+ {safeChildren()}
+
+ )}
+
+ {loading ? (
+
+ ) : (
+ <>
+
+ {variants}
+ {search && (
+
+ {renderSearchWithValue(search, searchValue)}
+
+ )}
+ {useToolbar && (
+
+ {showClearOnFB && (
+
+ )}
+ {showRestoreOnFB && (
+
+ )}
+
+ {showFilterConfiguration && (
+
+ )}
+ {showGoOnFB && (
+
+ )}
+
+ )}
+
+ {mountFilters &&
{renderChildren()}
}
+ >
+ )}
-
{children}
-
+ >
);
});
+FilterBar.defaultProps = {
+ useToolbar: true,
+ filterBarExpanded: true,
+ showClearOnFB: false,
+ showGo: false,
+ showRestoreOnFB: false,
+ showGoOnFB: false,
+ showFilterConfiguration: false,
+ showClearButton: false,
+ showRestoreButton: false,
+ showSearchOnFiltersDialog: false,
+ considerGroupName: false,
+ loading: false,
+ onToggleFilters: null,
+ onFiltersDialogOpen: null,
+ onFiltersDialogCancel: null,
+ onFiltersDialogClose: null,
+ onFiltersDialogSave: null,
+ onFiltersDialogClear: null,
+ onClear: null,
+ onFiltersDialogSelectionChange: null,
+ onFiltersDialogSearch: null,
+ onGo: null,
+ onRestore: null
+};
+
FilterBar.displayName = 'FilterBar';
export { FilterBar };
diff --git a/packages/main/src/components/FilterBar/utils.tsx b/packages/main/src/components/FilterBar/utils.tsx
new file mode 100644
index 00000000000..c8e444574e4
--- /dev/null
+++ b/packages/main/src/components/FilterBar/utils.tsx
@@ -0,0 +1,38 @@
+import { cloneElement } from 'react';
+
+export const filterValue = (ref, child) => {
+ const tagName = ref.tagName;
+ let filterItemProps = {};
+ if (
+ tagName === 'UI5-INPUT' ||
+ tagName === 'UI5-DATEPICKER' ||
+ tagName === 'UI5-DATETIME-PICKER' ||
+ tagName === 'UI5-DURATION-PICKER'
+ ) {
+ filterItemProps = { value: ref.value };
+ }
+ if (tagName === 'UI5-COMBOBOX') {
+ filterItemProps = { value: ref.value, filterValue: ref.filterValue };
+ }
+ if (tagName === 'UI5-SELECT' || tagName === 'UI5-MULTI-COMBOBOX') {
+ const selectedIndices = Array.from(ref.children as HTMLCollectionOf
)
+ .map((item, index) => (item.selected ? index : false))
+ .filter((el) => el !== false);
+ const selectedIndicesSet = new Set(selectedIndices);
+ const options = child.props.children.props.children?.map((item, index) => {
+ if (selectedIndicesSet.has(index)) {
+ return cloneElement(item, { selected: true });
+ }
+ return cloneElement(item, { selected: false });
+ });
+ filterItemProps = { children: options };
+ }
+ if (tagName === 'UI5-SWITCH' || tagName === 'UI5-CHECKBOX') {
+ filterItemProps = { checked: ref.checked };
+ }
+ return filterItemProps;
+};
+
+export const renderSearchWithValue = (renderSearchElement, searchValue) => {
+ return cloneElement(renderSearchElement, { value: searchValue });
+};
diff --git a/packages/main/src/components/FilterGroupItem/FilterGroupItem.jss.ts b/packages/main/src/components/FilterGroupItem/FilterGroupItem.jss.ts
new file mode 100644
index 00000000000..7a3688639ab
--- /dev/null
+++ b/packages/main/src/components/FilterGroupItem/FilterGroupItem.jss.ts
@@ -0,0 +1,45 @@
+const styles = {
+ filterItem: {
+ width: '13rem',
+ marginRight: '1rem',
+ marginBottom: '1rem'
+ },
+ filterItemDialog: {
+ flexGrow: 1,
+ overflow: 'hidden'
+ },
+ innerFilterItemContainer: {
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'start'
+ },
+ innerFilterItemContainerDialog: {
+ display: 'grid',
+ gridTemplateColumns: '20% calc(80% - 1rem)',
+ '@media(max-width:700px)': {
+ gridTemplateColumns: '100%'
+ },
+ gridTemplateRows: 'auto',
+ gridRowGap: '0px',
+ gridColumnGap: '1rem',
+ '& :first-child': {
+ maxWidth: '100%',
+ placeSelf: 'center end',
+ '@media(max-width:700px)': {
+ placeSelf: 'center start'
+ }
+ },
+ '& :last-child': {
+ placeSelf: 'center auto',
+ width: '100%'
+ }
+ },
+ loadingContainer: {
+ display: 'flex',
+ width: '100%',
+ height: '1.625rem',
+ justifyContent: 'center'
+ }
+};
+
+export default styles;
diff --git a/packages/main/src/components/FilterGroupItem/index.tsx b/packages/main/src/components/FilterGroupItem/index.tsx
new file mode 100644
index 00000000000..0e0f6e47c7f
--- /dev/null
+++ b/packages/main/src/components/FilterGroupItem/index.tsx
@@ -0,0 +1,91 @@
+import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
+import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
+import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
+import { BusyIndicator } from '@ui5/webcomponents-react/lib/BusyIndicator';
+import { BusyIndicatorSize } from '@ui5/webcomponents-react/lib/BusyIndicatorSize';
+import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
+import { Label } from '@ui5/webcomponents-react/lib/Label';
+import React, { FC, forwardRef, ReactElement, RefObject } from 'react';
+import { CommonProps } from '../../interfaces/CommonProps';
+import styles from './FilterGroupItem.jss';
+
+const useStyles = createComponentStyles(styles, { name: 'FilterGroupItem' });
+
+const emptyObject = {};
+
+export interface FilterGroupItemPropTypes extends CommonProps {
+ children: ReactElement;
+ label?: string;
+ groupName?: string;
+ labelTooltip?: string;
+ loading?: boolean;
+ required?: boolean;
+ visible?: boolean;
+ visibleInFilterBar?: boolean;
+ considerGroupName?: boolean;
+}
+
+export const FilterGroupItem: FC = forwardRef(
+ (props: FilterGroupItemPropTypes, ref: RefObject) => {
+ const classes = useStyles();
+ const {
+ groupName,
+ considerGroupName,
+ label,
+ labelTooltip,
+ required,
+ visible,
+ visibleInFilterBar,
+ children,
+ style,
+ loading,
+ className,
+ tooltip,
+ slot,
+ // @ts-ignore
+ inFB
+ } = props;
+
+ const passThroughProps = usePassThroughHtmlProps(props);
+
+ const styleClasses = StyleClassHelper.of(inFB ? classes.filterItem : classes.filterItemDialog);
+ if (className) {
+ styleClasses.put(className);
+ }
+
+ if (!required && (!visible || (inFB && !visibleInFilterBar))) return null;
+ return (
+
+
+
+
+
+ {loading ? (
+
+ ) : (
+ children
+ )}
+
+
+ );
+ }
+);
+
+FilterGroupItem.displayName = 'FilterGroupItem';
+
+FilterGroupItem.defaultProps = {
+ groupName: 'default',
+ visible: true,
+ visibleInFilterBar: true,
+ required: false
+};
diff --git a/packages/main/src/components/FilterItem/index.tsx b/packages/main/src/components/FilterItem/index.tsx
index d79132c184a..a78bad6eb34 100644
--- a/packages/main/src/components/FilterItem/index.tsx
+++ b/packages/main/src/components/FilterItem/index.tsx
@@ -1,4 +1,6 @@
+import { deprecationNotice } from '@ui5/webcomponents-react-base/lib/Utils';
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
+import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
import { BusyIndicator } from '@ui5/webcomponents-react/lib/BusyIndicator';
@@ -10,8 +12,7 @@ import { MultiComboBox } from '@ui5/webcomponents-react/lib/MultiComboBox';
import { Option } from '@ui5/webcomponents-react/lib/Option';
import { Select } from '@ui5/webcomponents-react/lib/Select';
import { StandardListItem } from '@ui5/webcomponents-react/lib/StandardListItem';
-import React, { FC, forwardRef, ReactNode, RefObject, useMemo } from 'react';
-import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
+import React, { FC, forwardRef, ReactNode, RefObject, useEffect, useMemo } from 'react';
import { CommonProps } from '../../interfaces/CommonProps';
import styles from './FilterItem.jss';
@@ -48,6 +49,13 @@ const FilterItem: FC = forwardRef((props: FilterItemPropTyp
} = props as FilterItemPropTypes;
const classes = useStyles();
+ useEffect(() => {
+ deprecationNotice(
+ 'FilterItem',
+ "'@ui5/webcomponents-react/lib/FilterItem' is deprecated and will be removed in the next major release.\nPlease use '@ui5/webcomponents-react/lib/FilterGroupItem' instead."
+ );
+ }, []);
+
function getItemByKey(key) {
return filterItems.filter((item) => item.key === key)[0];
}
diff --git a/packages/main/src/components/Loader/index.tsx b/packages/main/src/components/Loader/index.tsx
index 137b31055cb..698b69f2744 100644
--- a/packages/main/src/components/Loader/index.tsx
+++ b/packages/main/src/components/Loader/index.tsx
@@ -1,7 +1,7 @@
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
-import { useI18nBundle } from '@ui5/webcomponents-react-base/lib/hooks';
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
+import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
import { PLEASE_WAIT } from '@ui5/webcomponents-react/dist/assets/i18n/i18n-defaults';
import { LoaderType } from '@ui5/webcomponents-react/lib/LoaderType';
import React, { CSSProperties, FC, forwardRef, RefObject, useEffect, useMemo, useState } from 'react';
@@ -55,7 +55,7 @@ const Loader: FC = forwardRef((props: LoaderProps, ref: RefObject = forwardRef((props: LoaderProps, ref: RefObject*:not(:last-child)': {
- marginRight: '0.5rem'
+ '& > *': {
+ margin: '0 0.25rem'
},
'& > ui5-button': {
display: 'flex'
diff --git a/packages/main/src/components/MessageBox/__snapshots__/MessageBox.test.tsx.snap b/packages/main/src/components/MessageBox/__snapshots__/MessageBox.test.tsx.snap
index ee625d26bba..9b3a7e6a30a 100644
--- a/packages/main/src/components/MessageBox/__snapshots__/MessageBox.test.tsx.snap
+++ b/packages/main/src/components/MessageBox/__snapshots__/MessageBox.test.tsx.snap
@@ -9,13 +9,9 @@ exports[`MessageBox Confirm - Cancel 1`] = `
data-type="Confirm"
slot="header"
>
-
-
-
+
@@ -58,13 +54,9 @@ exports[`MessageBox Confirm - OK 1`] = `
data-type="Confirm"
slot="header"
>
-
-
-
+
@@ -107,13 +99,9 @@ exports[`MessageBox Error 1`] = `
data-type="Error"
slot="header"
>
-
-
-
+
@@ -149,13 +137,9 @@ exports[`MessageBox Information 1`] = `
data-type="Information"
slot="header"
>
-
-
-
+
@@ -191,13 +175,9 @@ exports[`MessageBox No Title 1`] = `
data-type="Confirm"
slot="header"
>
-
-
-
+
@@ -238,13 +218,9 @@ exports[`MessageBox Not open 1`] = `
data-type="Success"
slot="header"
>
-
-
-
+
@@ -280,13 +256,9 @@ exports[`MessageBox Show 1`] = `
data-type="Confirm"
slot="header"
>
-
-
-
+
@@ -329,13 +301,9 @@ exports[`MessageBox Success 1`] = `
data-type="Success"
slot="header"
>
-
-
-
+
@@ -371,13 +339,9 @@ exports[`MessageBox Success w/ custom title 1`] = `
data-type="Success"
slot="header"
>
-
-
-
+
@@ -413,13 +377,9 @@ exports[`MessageBox Warning 1`] = `
data-type="Warning"
slot="header"
>
-
-
-
+
diff --git a/packages/main/src/components/MessageBox/index.tsx b/packages/main/src/components/MessageBox/index.tsx
index 048f2de10f7..77112646bff 100644
--- a/packages/main/src/components/MessageBox/index.tsx
+++ b/packages/main/src/components/MessageBox/index.tsx
@@ -5,7 +5,7 @@ import '@ui5/webcomponents-icons/dist/icons/message-success';
import '@ui5/webcomponents-icons/dist/icons/message-warning';
import '@ui5/webcomponents-icons/dist/icons/question-mark';
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
-import { useConsolidatedRef, useI18nBundle, usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/hooks';
+import { useConsolidatedRef, useI18nText, usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/hooks';
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
import {
@@ -40,15 +40,15 @@ import { Ui5DialogDomRef } from '../../interfaces/Ui5DialogDomRef';
import styles from './MessageBox.jss';
const actionTextMap = new Map();
-actionTextMap.set(MessageBoxActions.ABORT, ABORT);
-actionTextMap.set(MessageBoxActions.CANCEL, CANCEL);
-actionTextMap.set(MessageBoxActions.CLOSE, CLOSE);
-actionTextMap.set(MessageBoxActions.DELETE, DELETE);
-actionTextMap.set(MessageBoxActions.IGNORE, IGNORE);
-actionTextMap.set(MessageBoxActions.NO, NO);
-actionTextMap.set(MessageBoxActions.OK, OK);
-actionTextMap.set(MessageBoxActions.RETRY, RETRY);
-actionTextMap.set(MessageBoxActions.YES, YES);
+actionTextMap.set(MessageBoxActions.ABORT, 0);
+actionTextMap.set(MessageBoxActions.CANCEL, 1);
+actionTextMap.set(MessageBoxActions.CLOSE, 2);
+actionTextMap.set(MessageBoxActions.DELETE, 3);
+actionTextMap.set(MessageBoxActions.IGNORE, 4);
+actionTextMap.set(MessageBoxActions.NO, 5);
+actionTextMap.set(MessageBoxActions.OK, 6);
+actionTextMap.set(MessageBoxActions.RETRY, 7);
+actionTextMap.set(MessageBoxActions.YES, 8);
export interface MessageBoxPropTypes extends CommonProps {
open?: boolean;
@@ -68,8 +68,6 @@ const useStyles = createComponentStyles(styles, { name: 'MessageBox' });
const MessageBox: FC = forwardRef((props: MessageBoxPropTypes, ref: Ref) => {
const { open, type, children, className, style, tooltip, slot, title, icon, actions, onClose } = props;
- const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
-
const classes = useStyles();
const iconToRender = useMemo(() => {
@@ -92,26 +90,53 @@ const MessageBox: FC = forwardRef((props: MessageBoxPropTyp
return null;
}, [icon, type]);
- const titleToRender = useMemo(() => {
+ const [
+ titleConfirmation,
+ titleError,
+ titleInformation,
+ titleSuccess,
+ titleWarning,
+ titleHighlight,
+ ...actionTranslations
+ ] = useI18nText(
+ '@ui5/webcomponents-react',
+ CONFIRMATION,
+ ERROR,
+ INFORMATION,
+ SUCCESS,
+ WARNING,
+ HIGHLIGHT,
+ ABORT,
+ CANCEL,
+ CLOSE,
+ DELETE,
+ IGNORE,
+ NO,
+ OK,
+ RETRY,
+ YES
+ );
+
+ const titleToRender = () => {
if (title) {
return title;
}
switch (type) {
case MessageBoxTypes.CONFIRM:
- return i18nBundle.getText(CONFIRMATION);
+ return titleConfirmation;
case MessageBoxTypes.ERROR:
- return i18nBundle.getText(ERROR);
+ return titleError;
case MessageBoxTypes.INFORMATION:
- return i18nBundle.getText(INFORMATION);
+ return titleInformation;
case MessageBoxTypes.SUCCESS:
- return i18nBundle.getText(SUCCESS);
+ return titleSuccess;
case MessageBoxTypes.WARNING:
- return i18nBundle.getText(WARNING);
+ return titleWarning;
case MessageBoxTypes.HIGHLIGHT:
- return i18nBundle.getText(HIGHLIGHT);
+ return titleHighlight;
}
return null;
- }, [title, type, i18nBundle]);
+ };
const actionsToRender = useMemo(() => {
if (actions && actions.length > 0) {
@@ -158,14 +183,13 @@ const MessageBox: FC = forwardRef((props: MessageBoxPropTyp
onAfterClose={open ? handleOnClose : null}
header={
- {!!iconToRender && {iconToRender}
}
- {titleToRender}
+ {iconToRender}
+ {titleToRender()}
}
footer={