Skip to content

Commit 4473118

Browse files
authored
refactor(FilterBar): remove reference copying of filter/input elements (#6214)
BREAKING CHANGE: The `FilterBar` component was completely overhauled and references of input elements aren’t copied to the filters dialog anymore, also internal logic for reordering and selection has been removed, meaning it’s necessary to control their values manually (e.g. via React state). BREAKING CHANGE: `onToggleFilters`: The `detail` property of the event now only includes `visible` and `nativeDetail` properties. `filters` and `search` have been removed. BREAKING CHANGE: `onFiltersDialogSave`: The `detail` property of the event now only includes `selectedFilterKeys`, `reorderedFilterKeys` and `nativeDetail` properties. `elements`, `toggledElements`, `filters`, `search`, `orderIds` have been removed. BREAKING CHANGE: `onFiltersDialogCancel`: The event is now a callback instead of a `Ui5CustomEvent`. It implements the `escPressed` parameter. BREAKING CHANGE: `onFiltersDialogClose`: The event is now a callback instead of a `Ui5CustomEvent`. It implements the `closeTrigger` parameter. BREAKING CHANGE: `onFiltersDialogSelectionChange`: The event is now a callback instead of a `Ui5CustomEvent`. It implements a payload object as parameter. BREAKING CHANGE: `onFiltersDialogSearch`: The event is now a standard `Input` `onInput` event. The `detail` properties `value` and `element` have been removed. BREAKING CHANGE: `onClear`: The event is now a standard `ToolbarButton` `onClick` event. The `detail` properties `filters` and `search` have been removed. BREAKING CHANGE: `onGo`: The event is now a standard `ToolbarButton` `onClick` event. The `detail` properties `elements`, `filters`, `search`, `nativeDetail` have been removed.
 BREAKING CHANGE: `onRestore`: The event is now a callback instead of a `CustomEvent`. It implements a payload object as parameter. BREAKING CHANGE: `onFiltersDialogOpen (TypeScript)`: The target of the event is now a `ToolbarButton`. BREAKING CHANGE: `portalContainer` has been removed as it's no longer needed due to the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) used in the `Popover` ui5 web component. BREAKING CHANGE: `FilterGroupItem`: `orderId` has been removed. Please use `filterKey` instead. Fixes #5652
1 parent 7a46973 commit 4473118

File tree

19 files changed

+2006
-1106
lines changed

19 files changed

+2006
-1106
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.filterBarDemo {
2+
padding: 1rem;
3+
border: var(--sapElement_BorderWidth) solid var(--sapNeutralBorderColor);
4+
border-radius: var(--sapElement_BorderCornerRadius);
5+
}
6+
7+
.filterBarDemo [ui5-dialog] {
8+
max-height: 80vh;
9+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
2+
import { useReducer, useRef, useState } from 'react';
3+
import {
4+
DatePicker,
5+
FilterBar,
6+
FilterBarPropTypes,
7+
FilterGroupItem,
8+
FlexBox,
9+
FlexBoxDirection,
10+
Input,
11+
Label,
12+
MultiComboBox,
13+
MultiComboBoxItem,
14+
Option,
15+
Select,
16+
StepInput,
17+
Text,
18+
ThemeProvider,
19+
Title
20+
} from '@ui5/webcomponents-react';
21+
import classes from './FilterBarExample.module.css';
22+
23+
const initialState = {
24+
age: 37,
25+
countries: {},
26+
currency: 'USD',
27+
date: '',
28+
search: ''
29+
};
30+
31+
function reducer(state, action) {
32+
switch (action.type) {
33+
case 'SET_AGE':
34+
return { ...state, age: action.payload };
35+
case 'SET_COUNTRIES':
36+
return { ...state, countries: action.payload };
37+
case 'SET_CURRENCY':
38+
return { ...state, currency: action.payload };
39+
case 'SET_DATE':
40+
return { ...state, date: action.payload };
41+
case 'SET_SEARCH':
42+
return { ...state, search: action.payload };
43+
case 'SET_STATE':
44+
return { ...state, ...action.payload };
45+
case 'DIALOG_RESTORE':
46+
return action.payload;
47+
default:
48+
return state;
49+
}
50+
}
51+
52+
export function FilterBarExample() {
53+
const [filterState, dispatch] = useReducer(reducer, initialState);
54+
const { age, countries, currency, date, search } = filterState;
55+
const dialogStateRef = useRef({});
56+
const [visibleChildrenByKey, setVisibleChildrenByKey] = useState<Record<string, boolean>>({
57+
'0': true,
58+
'1': true,
59+
'2': true
60+
});
61+
const [orderedFilterKeys, setOrderedFilterKeys] = useState(['0', '1', '2', '3']);
62+
63+
const handleSearch = (e) => {
64+
dispatch({ type: 'SET_SEARCH', payload: e.target.value });
65+
};
66+
const handleAgeChange = (e) => {
67+
const { value } = e.target;
68+
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
69+
dialogStateRef.current.age = value;
70+
} else {
71+
dispatch({ type: 'SET_AGE', payload: value });
72+
}
73+
};
74+
75+
const handleCountriesChange = (e) => {
76+
const newCountries = e.detail.items.reduce((acc, cur) => {
77+
return { ...acc, [cur.getAttribute('text').toLowerCase()]: true };
78+
}, {});
79+
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
80+
dialogStateRef.current.countries = newCountries;
81+
} else {
82+
dispatch({ type: 'SET_COUNTRIES', payload: newCountries });
83+
}
84+
};
85+
86+
const handleCurrencyChange = (e) => {
87+
const currency = e.detail.selectedOption.textContent;
88+
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
89+
dialogStateRef.current.currency = currency;
90+
} else {
91+
dispatch({ type: 'SET_CURRENCY', payload: currency });
92+
}
93+
};
94+
95+
const handleDateChange = (e) => {
96+
const { value } = e.target;
97+
if (e.currentTarget.parentElement.dataset.inFiltersDialog) {
98+
dialogStateRef.current.date = value;
99+
} else if (e.detail.valid) {
100+
dispatch({ type: 'SET_DATE', payload: value });
101+
}
102+
};
103+
104+
const handleFiltersDialogSave: FilterBarPropTypes['onFiltersDialogSave'] = (e) => {
105+
setOrderedFilterKeys(e.detail.reorderedFilterKeys);
106+
setVisibleChildrenByKey(
107+
e.detail.selectedFilterKeys.reduce((acc, cur) => {
108+
acc[cur] = true;
109+
return acc;
110+
}, {})
111+
);
112+
dispatch({ type: 'SET_STATE', payload: dialogStateRef.current });
113+
};
114+
115+
return (
116+
<ThemeProvider>
117+
<div className={classes.filterBarDemo}>
118+
<Text>
119+
The FilterBar applies filter changes inside the FilterBar immediately and inside the dialog only after 'OK'
120+
has been pressed.
121+
</Text>
122+
<FilterBar
123+
header={
124+
<Title level={TitleLevel.H2} size={TitleLevel.H4}>
125+
Apply changes after dialog save
126+
</Title>
127+
}
128+
enableReordering
129+
onFiltersDialogSave={handleFiltersDialogSave}
130+
search={<Input onInput={handleSearch} />}
131+
>
132+
{orderedFilterKeys.map((filterKey) => {
133+
const isHidden = !visibleChildrenByKey[filterKey];
134+
switch (filterKey) {
135+
case '0':
136+
return (
137+
<FilterGroupItem key={0} filterKey="0" label="Age" required>
138+
<StepInput value={age} onChange={handleAgeChange} required />
139+
</FilterGroupItem>
140+
);
141+
case '1':
142+
return (
143+
<FilterGroupItem
144+
key={1}
145+
filterKey="1"
146+
label="Countries"
147+
active={Object.keys(countries).length > 0}
148+
hiddenInFilterBar={isHidden}
149+
>
150+
<MultiComboBox onSelectionChange={handleCountriesChange}>
151+
<MultiComboBoxItem text="Argentina" selected={countries.argentina} />
152+
<MultiComboBoxItem text="Bulgaria" selected={countries.bulgaria} />
153+
<MultiComboBoxItem text="Finland" selected={countries.finland} />
154+
<MultiComboBoxItem text="Germany" selected={countries.germany} />
155+
<MultiComboBoxItem text="Ireland" selected={countries.ireland} />
156+
<MultiComboBoxItem text="Ukraine" selected={countries.ukraine} />
157+
<MultiComboBoxItem text="USA" selected={countries.usa} />
158+
</MultiComboBox>
159+
</FilterGroupItem>
160+
);
161+
case '2':
162+
return (
163+
<FilterGroupItem
164+
key={2}
165+
filterKey="2"
166+
label="Currency"
167+
active={!!currency}
168+
hiddenInFilterBar={isHidden}
169+
>
170+
<Select onChange={handleCurrencyChange}>
171+
<Option additionalText="€" selected={currency === 'EUR'}>
172+
EUR
173+
</Option>
174+
<Option additionalText="$" selected={currency === 'USD'}>
175+
USD
176+
</Option>
177+
<Option additionalText="£" selected={currency === 'GBP'}>
178+
GBP
179+
</Option>
180+
<Option additionalText="₺" selected={currency === 'TRY'}>
181+
TRY
182+
</Option>
183+
<Option additionalText="¥" selected={currency === 'JPY'}>
184+
JPY
185+
</Option>
186+
</Select>
187+
</FilterGroupItem>
188+
);
189+
case '3':
190+
return (
191+
<FilterGroupItem key={3} filterKey="3" label="Date" active={!!date} hiddenInFilterBar={isHidden}>
192+
<DatePicker value={date} onChange={handleDateChange} style={{ minWidth: 'auto' }} />
193+
</FilterGroupItem>
194+
);
195+
default:
196+
return null;
197+
}
198+
})}
199+
</FilterBar>
200+
<FlexBox direction={FlexBoxDirection.Column}>
201+
<FlexBox>
202+
<Label showColon>Search</Label>
203+
<Text>{search}</Text>
204+
</FlexBox>
205+
<FlexBox>
206+
<Label showColon>Age</Label>
207+
<Text>{age}</Text>
208+
</FlexBox>
209+
<FlexBox>
210+
<Label showColon>Countries</Label>
211+
<Text>{JSON.stringify(countries)}</Text>
212+
</FlexBox>
213+
<FlexBox>
214+
<Label showColon>Currency</Label>
215+
<Text>{currency}</Text>
216+
</FlexBox>
217+
<FlexBox>
218+
<Label showColon>Date</Label>
219+
<Text>{date}</Text>
220+
</FlexBox>
221+
<hr style={{ width: '100%' }} />
222+
<FlexBox>
223+
<Label showColon>Visible Filters</Label>
224+
<Text>{Object.keys(visibleChildrenByKey).join(', ')}</Text>
225+
</FlexBox>
226+
<FlexBox>
227+
<Label showColon>Filters Order</Label>
228+
<Text>{orderedFilterKeys.join(', ')}</Text>
229+
</FlexBox>
230+
</FlexBox>
231+
</div>
232+
</ThemeProvider>
233+
);
234+
}

.storybook/components/index.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
export * from './ArgTypesWithNote';
2-
export * from './ControlsWithNote';
3-
export * from './DocsHeader';
4-
export * from './Footer';
5-
export * from './ProductsTable';
6-
export * from './ProjectTemplate';
7-
export * from './TableOfContent';
8-
export * from './LabelWithWrapping';
9-
export * from './CommandsAndQueries';
1+
export * from './ArgTypesWithNote.js';
2+
export * from './ControlsWithNote.js';
3+
export * from './DocsHeader.js';
4+
export * from './Footer.js';
5+
export * from './ProductsTable.js';
6+
export * from './ProjectTemplate.js';
7+
export * from './TableOfContent.js';
8+
export * from './LabelWithWrapping.js';
9+
export * from './CommandsAndQueries.js';
10+
export * from './FilterBarExample.js';

0 commit comments

Comments
 (0)