Skip to content

Commit 122dec9

Browse files
authored
feat(VariantManagement): add onManageViewsCancel and onSaveViewCancel events (#5123)
Closes #5047
1 parent 6a3247e commit 122dec9

File tree

7 files changed

+311
-169
lines changed

7 files changed

+311
-169
lines changed

packages/main/src/components/VariantManagement/ManageViewsDialog.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isPhone, isTablet } from '@ui5/webcomponents-base/dist/Device.js';
22
import searchIcon from '@ui5/webcomponents-icons/dist/search.js';
3-
import { ThemingParameters, useI18nBundle } from '@ui5/webcomponents-react-base';
3+
import { enrichEventWithDetails, ThemingParameters, useI18nBundle } from '@ui5/webcomponents-react-base';
44
import type { MouseEventHandler, ReactNode } from 'react';
55
import React, { Children, useEffect, useRef, useState } from 'react';
66
import { createPortal } from 'react-dom';
@@ -22,11 +22,13 @@ import { cssVarVersionInfoPrefix } from '../../internal/utils.js';
2222
import { Bar } from '../../webComponents/Bar/index.js';
2323
import { Button } from '../../webComponents/Button/index.js';
2424
import { Dialog } from '../../webComponents/Dialog/index.js';
25+
import type { InputDomRef } from '../../webComponents/index.js';
2526
import { Icon, Input } from '../../webComponents/index.js';
2627
import { Table } from '../../webComponents/Table/index.js';
2728
import { TableColumn } from '../../webComponents/TableColumn/index.js';
2829
import { FlexBox } from '../FlexBox/index.js';
2930
import { ManageViewsTableRows } from './ManageViewsTableRows.js';
31+
import type { VariantManagementPropTypes } from './types.js';
3032
import type { VariantItemPropTypes } from './VariantItem.js';
3133

3234
const _popupDefaultHeaderHeight = `var(${cssVarVersionInfoPrefix}popup_default_header_height)`;
@@ -83,6 +85,7 @@ interface ManageViewsDialogPropTypes {
8385
variantNames: string[];
8486
portalContainer: Element;
8587
showOnlyFavorites?: boolean;
88+
onManageViewsCancel?: VariantManagementPropTypes['onManageViewsCancel'];
8689
}
8790

8891
export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
@@ -96,7 +99,8 @@ export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
9699
showCreatedBy,
97100
variantNames,
98101
portalContainer,
99-
showOnlyFavorites
102+
showOnlyFavorites,
103+
onManageViewsCancel
100104
} = props;
101105
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
102106
const cancelText = i18nBundle.getText(CANCEL);
@@ -110,7 +114,7 @@ export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
110114
const searchText = i18nBundle.getText(SEARCH);
111115

112116
const [changedVariantNames, setChangedVariantNames] = useState(new Map());
113-
const [invalidVariants, setInvalidVariants] = useState<Record<string, HTMLInputElement>>({});
117+
const [invalidVariants, setInvalidVariants] = useState<Record<string, InputDomRef & { isInvalid?: boolean }>>({});
114118

115119
const classes = useStyles();
116120

@@ -204,6 +208,31 @@ export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
204208
}
205209
};
206210

211+
const handleClose = (e) => {
212+
if (e.detail.escPressed) {
213+
handleCancel(e);
214+
} else {
215+
onAfterClose(e);
216+
}
217+
};
218+
219+
const handleCancel = (e) => {
220+
if (typeof onManageViewsCancel === 'function') {
221+
onManageViewsCancel(
222+
enrichEventWithDetails(e, {
223+
invalidVariants
224+
})
225+
);
226+
}
227+
setInvalidVariants((prev) => {
228+
Object.values(prev).forEach((item) => {
229+
item.isInvalid = false;
230+
});
231+
return {};
232+
});
233+
onAfterClose(e);
234+
};
235+
207236
const handleSearchInput = (e) => {
208237
const lowerCaseVal = e.target.value.toLowerCase();
209238
setFilteredProps(
@@ -225,6 +254,7 @@ export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
225254
className={classes.manageViewsDialog}
226255
data-component-name="VariantManagementManageViewsDialog"
227256
onAfterClose={onAfterClose}
257+
onBeforeClose={handleClose}
228258
headerText={manageViewsText}
229259
header={
230260
<FlexBox direction={FlexBoxDirection.Column} style={{ width: '100%' }} alignItems={FlexBoxAlignItems.Center}>
@@ -247,7 +277,7 @@ export const ManageViewsDialog = (props: ManageViewsDialogPropTypes) => {
247277
<Button design={ButtonDesign.Emphasized} onClick={handleSave}>
248278
{saveText}
249279
</Button>
250-
<Button design={ButtonDesign.Transparent} onClick={onAfterClose}>
280+
<Button design={ButtonDesign.Transparent} onClick={handleCancel}>
251281
{cancelText}
252282
</Button>
253283
</>

packages/main/src/components/VariantManagement/SaveViewDialog.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useI18nBundle, useIsomorphicId } from '@ui5/webcomponents-react-base';
1+
import { enrichEventWithDetails, useI18nBundle, useIsomorphicId } from '@ui5/webcomponents-react-base';
22
import { clsx } from 'clsx';
33
import React, { useRef, useState } from 'react';
44
import { createPortal } from 'react-dom';
@@ -22,6 +22,7 @@ import type { SelectedVariant } from '../../internal/VariantManagementContext.js
2222
import type { ButtonDomRef, DialogDomRef, InputPropTypes } from '../../webComponents/index.js';
2323
import { Bar, Button, CheckBox, Dialog, Input, Label } from '../../webComponents/index.js';
2424
import { FlexBox } from '../FlexBox/index.js';
25+
import type { VariantManagementPropTypes } from './types.js';
2526

2627
const useStyles = createUseStyles(
2728
{
@@ -47,6 +48,7 @@ interface SaveViewDialogPropTypes {
4748
variantNames: string[];
4849
portalContainer: Element;
4950
saveViewInputProps?: Omit<InputPropTypes, 'value'>;
51+
onSaveViewCancel?: VariantManagementPropTypes['onSaveViewCancel'];
5052
}
5153

5254
export const SaveViewDialog = (props: SaveViewDialogPropTypes) => {
@@ -59,7 +61,8 @@ export const SaveViewDialog = (props: SaveViewDialogPropTypes) => {
5961
showSetAsDefault,
6062
variantNames,
6163
portalContainer,
62-
saveViewInputProps
64+
saveViewInputProps,
65+
onSaveViewCancel
6366
} = props;
6467
const saveViewDialogRef = useRef<DialogDomRef>(null);
6568
const inputRef = useRef(undefined);
@@ -117,7 +120,29 @@ export const SaveViewDialog = (props: SaveViewDialogPropTypes) => {
117120
}
118121
};
119122

120-
const handleCancel = () => {
123+
const handleClose = (e) => {
124+
if (e.detail.escPressed) {
125+
handleCancel(e);
126+
} else {
127+
onAfterClose(e);
128+
}
129+
};
130+
131+
const handleCancel = (e) => {
132+
if (typeof onSaveViewCancel === 'function') {
133+
onSaveViewCancel(
134+
enrichEventWithDetails(e, {
135+
...selectedVariant,
136+
children: variantName,
137+
isDefault,
138+
global: isPublic,
139+
applyAutomatically,
140+
isInvalid
141+
})
142+
);
143+
}
144+
setIsInvalid(false);
145+
inputRef.current.isInvalid = false;
121146
saveViewDialogRef.current.close();
122147
};
123148

@@ -143,6 +168,7 @@ export const SaveViewDialog = (props: SaveViewDialogPropTypes) => {
143168
ref={saveViewDialogRef}
144169
headerText={headingText}
145170
onAfterClose={onAfterClose}
171+
onBeforeClose={handleClose}
146172
footer={
147173
<Bar
148174
design={BarDesign.Footer}

packages/main/src/components/VariantManagement/VariantManagement.cy.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ describe('VariantManagement', () => {
105105
cy.get('[ui5-dialog]').should('be.visible');
106106
cy.findByTestId('12chars').typeIntoUi5Input('A');
107107
cy.findByTestId('12chars').should('have.attr', 'value-state', 'Error');
108+
cy.realPress('Tab');
109+
cy.realPress('Escape');
110+
cy.findByText('Manage').click();
111+
cy.findByTestId('12chars').should('have.attr', 'value-state', 'None');
112+
cy.findByTestId('12chars').typeIntoUi5Input('A');
113+
cy.findByTestId('12chars').should('have.attr', 'value-state', 'Error');
114+
cy.findByText('Cancel').click();
115+
cy.findByText('Manage').click();
116+
cy.findByTestId('12chars').should('have.attr', 'value-state', 'None');
117+
cy.findByTestId('12chars').typeIntoUi5Input('A');
118+
cy.findByTestId('12chars').should('have.attr', 'value-state', 'Error');
108119
cy.findByText('Save').click();
109120
cy.get('[ui5-dialog]').should('be.visible');
110121
cy.findByTestId('12chars').typeIntoUi5Input('{backspace}');
@@ -123,6 +134,19 @@ describe('VariantManagement', () => {
123134
cy.get('[ui5-dialog]').should('be.visible');
124135
cy.findByTestId('alphanumeric').typeIntoUi5Input('$');
125136
cy.findByTestId('alphanumeric').should('have.attr', 'value-state', 'Error');
137+
cy.realPress('Tab');
138+
cy.realPress('Escape');
139+
cy.contains('Only alphanumeric chars in Save View input').click();
140+
cy.findByText('Save As').click();
141+
cy.findByTestId('alphanumeric').should('have.attr', 'value-state', 'None');
142+
cy.findByTestId('alphanumeric').typeIntoUi5Input('$');
143+
cy.findByTestId('alphanumeric').should('have.attr', 'value-state', 'Error');
144+
cy.findByText('Cancel').click();
145+
cy.contains('Only alphanumeric chars in Save View input').click();
146+
cy.findByText('Save As').click();
147+
cy.findByTestId('alphanumeric').should('have.attr', 'value-state', 'None');
148+
cy.findByTestId('alphanumeric').typeIntoUi5Input('$');
149+
cy.findByTestId('alphanumeric').should('have.attr', 'value-state', 'Error');
126150
cy.findByText('Save').click();
127151
cy.get('[ui5-dialog]').should('be.visible');
128152
cy.focused().should('have.attr', 'value-state', 'Error');

packages/main/src/components/VariantManagement/VariantManagement.mdx

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ These props accept props of the `Input` component with which you are able to ove
3939
<summary>Show Code</summary>
4040

4141
```jsx
42-
export const VariantManagementWithCustomValidation = () => {
42+
const VariantManagementWithCustomValidation = ({ selectedByIndex = 1 }) => {
4343
const [valueStateSaveView, setValueStateSaveView] = useState(undefined);
44-
const [customSaveViewVariantText, setCustomSaveViewVariantText] = useState(
45-
'Only alphanumeric chars in Save View input'
46-
);
4744
const [valueStateManageViews, setValueStateManageViews] = useState(undefined);
48-
const [customManageViewsVariantText, setCustomManageViewsVariantText] = useState('Max 12 chars');
45+
const [values, setValues] = useState({
46+
1: 'Only alphanumeric chars in Save View input',
47+
2: 'Max 12 chars'
48+
});
4949

5050
const handleSaveViewInput = (e) => {
5151
// only allow alphanumeric and space characters
@@ -60,7 +60,10 @@ export const VariantManagementWithCustomValidation = () => {
6060
}
6161
};
6262
const handleSaveAs = (e) => {
63-
setCustomSaveViewVariantText(e.detail.children);
63+
setValues((prev) => ({
64+
...prev,
65+
[e.detail['data-id']]: e.detail.children
66+
}));
6467
};
6568

6669
const handleManageViewInput = (e) => {
@@ -79,33 +82,52 @@ export const VariantManagementWithCustomValidation = () => {
7982
// if is custom manage view variant and is not in error state, set children to new value
8083
const isCustomManageViewsItem = e.detail.updatedVariants.find((item) => item['data-custom-manage-views']);
8184
if (!valueStateManageViews && isCustomManageViewsItem) {
82-
setCustomManageViewsVariantText(isCustomManageViewsItem.children);
85+
setValues((prev) => ({
86+
...prev,
87+
[isCustomManageViewsItem['data-id']]: isCustomManageViewsItem.children
88+
}));
8389
}
8490
};
91+
// reset value-state if user closes the dialogs without saving (cancel click or ESC press)
92+
const handleManageViewsCancel = () => {
93+
setValueStateManageViews(undefined);
94+
};
95+
const handleSaveViewCancel = () => {
96+
setValueStateSaveView(undefined);
97+
};
8598
return (
86-
<VariantManagement onSaveAs={handleSaveAs} onSaveManageViews={handleSaveManageViews}>
99+
<VariantManagement
100+
onSaveAs={handleSaveAs}
101+
onSaveManageViews={handleSaveManageViews}
102+
onManageViewsCancel={handleManageViewsCancel}
103+
onSaveViewCancel={handleSaveViewCancel}
104+
>
87105
<VariantItem
88-
data-custom-save-view
106+
data-id={1}
107+
selected={selectedByIndex === 0}
89108
saveViewInputProps={{
90109
valueState: valueStateSaveView,
91110
valueStateMessage: valueStateSaveView ? (
92111
<div>Only alphanumeric and space characters allowed!</div>
93112
) : undefined,
94-
onInput: handleSaveViewInput
113+
onInput: handleSaveViewInput,
114+
'data-testid': 'alphanumeric'
95115
}}
96116
>
97-
{customSaveViewVariantText}
117+
{values[1]}
98118
</VariantItem>
99119
<VariantItem
100120
data-custom-manage-views
101-
selected
121+
data-id={2}
122+
selected={selectedByIndex === 1}
102123
manageViewsInputProps={{
103124
valueState: valueStateManageViews,
104125
valueStateMessage: valueStateManageViews ? <div>No more than 12 characters allowed!</div> : undefined,
105-
onInput: handleManageViewInput
126+
onInput: handleManageViewInput,
127+
'data-testid': '12chars'
106128
}}
107129
>
108-
{customManageViewsVariantText}
130+
{values[2]}
109131
</VariantItem>
110132
</VariantManagement>
111133
);

packages/main/src/components/VariantManagement/VariantManagement.stories.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta, StoryObj } from '@storybook/react';
2-
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
2+
import { useEffect, useReducer, useRef, useState } from 'react';
33
import { PopoverPlacementType, TitleLevel, ValueState } from '../../enums/index.js';
44
import { DatePicker } from '../../webComponents/DatePicker/index.js';
55
import { MultiComboBox } from '../../webComponents/MultiComboBox/index.js';
@@ -65,11 +65,8 @@ export const Default: Story = {
6565
export const WithCustomValidation: Story = {
6666
render: ({ selectedByIndex = 1 }: any) => {
6767
const [valueStateSaveView, setValueStateSaveView] = useState(undefined);
68-
const [customSaveViewVariantText, setCustomSaveViewVariantText] = useState(
69-
'Only alphanumeric chars in Save View input'
70-
);
7168
const [valueStateManageViews, setValueStateManageViews] = useState(undefined);
72-
const [customManageViewsVariantText, setCustomManageViewsVariantText] = useState('Max 12 chars');
69+
const [values, setValues] = useState({ 1: 'Only alphanumeric chars in Save View input', 2: 'Max 12 chars' });
7370

7471
const handleSaveViewInput = (e) => {
7572
// only allow alphanumeric and space characters
@@ -84,7 +81,7 @@ export const WithCustomValidation: Story = {
8481
}
8582
};
8683
const handleSaveAs = (e) => {
87-
setCustomSaveViewVariantText(e.detail.children);
84+
setValues((prev) => ({ ...prev, [e.detail['data-id']]: e.detail.children }));
8885
};
8986

9087
const handleManageViewInput = (e) => {
@@ -103,13 +100,25 @@ export const WithCustomValidation: Story = {
103100
// if is custom manage view variant and is not in error state, set children to new value
104101
const isCustomManageViewsItem = e.detail.updatedVariants.find((item) => item['data-custom-manage-views']);
105102
if (!valueStateManageViews && isCustomManageViewsItem) {
106-
setCustomManageViewsVariantText(isCustomManageViewsItem.children);
103+
setValues((prev) => ({ ...prev, [isCustomManageViewsItem['data-id']]: isCustomManageViewsItem.children }));
107104
}
108105
};
106+
// reset value-state if user closes the dialogs without saving (cancel click or ESC press)
107+
const handleManageViewsCancel = () => {
108+
setValueStateManageViews(undefined);
109+
};
110+
const handleSaveViewCancel = () => {
111+
setValueStateSaveView(undefined);
112+
};
109113
return (
110-
<VariantManagement onSaveAs={handleSaveAs} onSaveManageViews={handleSaveManageViews}>
114+
<VariantManagement
115+
onSaveAs={handleSaveAs}
116+
onSaveManageViews={handleSaveManageViews}
117+
onManageViewsCancel={handleManageViewsCancel}
118+
onSaveViewCancel={handleSaveViewCancel}
119+
>
111120
<VariantItem
112-
data-custom-save-view
121+
data-id={1}
113122
selected={selectedByIndex === 0}
114123
saveViewInputProps={{
115124
valueState: valueStateSaveView,
@@ -121,10 +130,11 @@ export const WithCustomValidation: Story = {
121130
'data-testid': 'alphanumeric'
122131
}}
123132
>
124-
{customSaveViewVariantText}
133+
{values[1]}
125134
</VariantItem>
126135
<VariantItem
127136
data-custom-manage-views
137+
data-id={2}
128138
selected={selectedByIndex === 1}
129139
manageViewsInputProps={{
130140
valueState: valueStateManageViews,
@@ -134,7 +144,7 @@ export const WithCustomValidation: Story = {
134144
'data-testid': '12chars'
135145
}}
136146
>
137-
{customManageViewsVariantText}
147+
{values[2]}
138148
</VariantItem>
139149
</VariantManagement>
140150
);

0 commit comments

Comments
 (0)