Skip to content

Commit 5b2ac63

Browse files
refactor(ActionSheet): api alignment (#5956)
BREAKING CHANGE: the `portalContainer` prop has been removed as it's not needed anymore BREAKING CHANGE: the `showCancelButton` has been renamed to `hideCancelButton` and the logic has been inverted. --------- Co-authored-by: Lukas Harbarth <[email protected]>
1 parent 841e1e9 commit 5b2ac63

File tree

6 files changed

+78
-97
lines changed

6 files changed

+78
-97
lines changed

docs/MigrationGuide.mdx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,36 @@ Please use the following components instead:
263263

264264
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"`
265265

266+
## Components with API Changes
267+
268+
### ActionSheet
269+
270+
The prop `portalContainer` has been removed as it is no longer needed due to the [popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) which is now used in the UI5 Web Components.
271+
For a better aligned API, the `showCancelButton` prop has been replaced wih the `hideCancelButton` prop and the logic has been inverted.
272+
You only need to apply changes to your code if `showCancelButton` has been set to `false`.
273+
274+
```tsx
275+
// v1
276+
import { ActionSheet, Button } from '@ui5/webcomponents-react';
277+
278+
function MyComponent() {
279+
return (
280+
<ActionSheet showCancelButton={false}>
281+
<Button>Action 1</Button>
282+
</ActionSheet>
283+
);
284+
}
285+
286+
// v2
287+
import { ActionSheet, Button } from '@ui5/webcomponents-react';
288+
289+
function MyComponent() {
290+
return (
291+
<ActionSheet hideCancelButton>
292+
<Button>Action 1</Button>
293+
</ActionSheet>
294+
);
295+
}
296+
```
297+
266298
<Footer />

packages/cli/src/scripts/codemod/transforms/v2/codemodConfig.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
"changedProps": {
1212
"onAfterClose": "onClose",
1313
"onAfterOpen": "onOpen",
14-
"placementType": "placement"
15-
}
14+
"placementType": "placement",
15+
"showCancelButton": "hideCancelButton"
16+
},
17+
"removedProps": ["portalContainer"]
1618
},
1719
"Avatar": {},
1820
"Badge": {

packages/cli/src/scripts/codemod/transforms/v2/main.cts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ export default function transform(file: FileInfo, api: API, options?: Options):
9191
return;
9292
}
9393

94+
if (componentName === 'ActionSheet') {
95+
jsxElements.forEach((el) => {
96+
const showCancelButton = j(el).find(j.JSXAttribute, { name: { name: 'showCancelButton' } });
97+
if (showCancelButton.size() > 0) {
98+
const attr = showCancelButton.get();
99+
if (
100+
attr.value.value &&
101+
((attr.value.value.type === 'JSXAttribute' && attr.value.value === false) ||
102+
(attr.value.value.type === 'JSXExpressionContainer' && attr.value.value.expression.value === false))
103+
) {
104+
j(el)
105+
.find(j.JSXOpeningElement)
106+
.get()
107+
.value.attributes.push(j.jsxAttribute(j.jsxIdentifier('hideCancelButton'), null));
108+
}
109+
showCancelButton.remove();
110+
isDirty = true;
111+
}
112+
});
113+
}
114+
94115
// Special Handling for logic inversions, etc.
95116
if (componentName === 'Button') {
96117
jsxElements.forEach((el) => {

packages/main/src/components/ActionSheet/ActionSheet.mdx

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@ import * as ComponentStories from './ActionSheet.stories';
2121

2222
<br />
2323

24-
#### since 0.22.0
24+
You can open and close the `ActionSheet` component in a declarative way using the `open` and `opener` prop.
2525

26-
We recommend opening and closing the `ActionSheet` component in a declarative way using the `open` and `opener` prop.
27-
You can still use the imperative way which is outlined below.
28-
29-
```jsx
26+
```tsx
3027
const MyComponentWithActionSheet = () => {
3128
const [actionSheetIsOpen, setActionSheetIsOpen] = useState(false);
3229
return (
@@ -53,7 +50,8 @@ const MyComponentWithActionSheet = () => {
5350

5451
**Opening an `ActionSheet` by reference and not by `id`**
5552

56-
This web component exposes a way to pass a reference of an element instead of an `id` to the `opener` prop. Since this is not supported when passing the reference in a declarative way to a React `prop`, you have to attach the ref directly on the web component.
53+
The `ActionSheet` exposes a way to pass a reference of an element instead of an `id` to the `opener` prop.
54+
Since this is not supported when passing the reference in a declarative way to a React `prop`, you have to attach the ref directly on the web component.
5755
You can do that by e.g. leveraging a React Ref, and then set the corresponding property there.
5856

5957
<MessageStrip
@@ -83,54 +81,4 @@ const ActionSheetComponent = () => {
8381
};
8482
```
8583

86-
#### before 0.22.0
87-
88-
`ActionSheets` can only be opened by attaching a `ref` to the component and then call the corresponding **`showAt`** method. The method receives the target element - _on which the `ActionSheet` is to be opened_ - as parameter.
89-
90-
```jsx
91-
const ActionSheetComponent = () => {
92-
const actionSheetRef = useRef(null);
93-
const onButtonClick = (e) => {
94-
actionSheetRef.current.showAt(e.target);
95-
};
96-
return (
97-
<>
98-
<Button onClick={onButtonClick}>Open ActionSheet</Button>
99-
<ActionSheet ref={actionSheetRef}>
100-
<Button icon="add">Accept</Button>
101-
<Button>Reject</Button>
102-
<Button>This is my super long text!</Button>
103-
</ActionSheet>
104-
</>
105-
);
106-
};
107-
```
108-
109-
## Using ActionSheets inside other components
110-
111-
`ActionSheets` are often used within other components, when opened this could sometimes have unwanted side effects.
112-
In this case, we recommend using [createPortal](https://reactjs.org/docs/portals.html) to mount the `ActionSheet` outside of the DOM hierarchy of the parent component.
113-
114-
```JSX
115-
const ActionSheetComponent = () => {
116-
const actionSheetRef = useRef(null);
117-
const onButtonClick = (e) => {
118-
actionSheetRef.current.showAt(e.target);
119-
};
120-
return (
121-
<>
122-
<Button onClick={onButtonClick}>Open ActionSheet</Button>
123-
{createPortal(
124-
<ActionSheet ref={actionSheetRef}>
125-
<Button icon="add">Accept</Button>
126-
<Button>Reject</Button>
127-
<Button>This is my super long text!</Button>
128-
</ActionSheet>,
129-
document.body
130-
)}
131-
</>
132-
);
133-
};
134-
```
135-
13684
<Footer />

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import declineIcon from '@ui5/webcomponents-icons/dist/decline.js';
77
import deleteIcon from '@ui5/webcomponents-icons/dist/delete.js';
88
import emailIcon from '@ui5/webcomponents-icons/dist/email.js';
99
import forwardIcon from '@ui5/webcomponents-icons/dist/forward.js';
10-
import { useState } from 'react';
10+
import { useEffect, useState } from 'react';
1111
import { Button } from '../../webComponents/index.js';
1212
import { ActionSheet } from './index.js';
1313

@@ -41,10 +41,12 @@ const meta = {
4141
export default meta;
4242
type Story = StoryObj<typeof meta>;
4343

44-
//TODO: check docs for outdated info
4544
export const Default: Story = {
4645
render(args) {
4746
const [actionSheetOpen, setActionSheetOpen] = useState<boolean | undefined>(args.open);
47+
useEffect(() => {
48+
setActionSheetOpen(args.open);
49+
}, [args.open]);
4850
return (
4951
<>
5052
<Button

packages/main/src/components/ActionSheet/index.tsx

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import { useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base';
66
import { clsx } from 'clsx';
77
import type { ReactElement } from 'react';
88
import { forwardRef, useEffect, useReducer, useRef, useState } from 'react';
9-
import { createPortal } from 'react-dom';
109
import { AVAILABLE_ACTIONS, CANCEL, X_OF_Y } from '../../i18n/i18n-defaults.js';
1110
import { addCustomCSSWithScoping } from '../../internal/addCustomCSSWithScoping.js';
12-
import { useCanRenderPortal } from '../../internal/ssr.js';
13-
import { flattenFragments } from '../../internal/utils.js';
11+
import { flattenFragments, getUi5TagWithSuffix } from '../../internal/utils.js';
1412
import { CustomThemingParameters } from '../../themes/CustomVariables.js';
1513
import type { UI5WCSlotsNode } from '../../types/index.js';
1614
import type {
@@ -42,9 +40,9 @@ export interface ActionSheetPropTypes extends Omit<ResponsivePopoverPropTypes, '
4240
*/
4341
children?: ReactElement<ButtonPropTypes> | ReactElement<ButtonPropTypes>[];
4442
/**
45-
* Displays a cancel button below the action buttons on mobile devices. No cancel button will be shown on desktop and tablet devices.
43+
* Hides the cancel button below the action buttons on mobile devices. No cancel button will be shown on desktop and tablet devices.
4644
*/
47-
showCancelButton?: boolean;
45+
hideCancelButton?: boolean;
4846
/**
4947
* Defines internally used a11y properties.
5048
*/
@@ -53,14 +51,6 @@ export interface ActionSheetPropTypes extends Omit<ResponsivePopoverPropTypes, '
5351
role?: string;
5452
};
5553
};
56-
/**
57-
* Defines where modals are rendered into via `React.createPortal`.
58-
*
59-
* You can find out more about this [here](https://sap.github.io/ui5-webcomponents-react/?path=/docs/knowledge-base-working-with-portals--page).
60-
*
61-
* Defaults to: `document.body`
62-
*/
63-
portalContainer?: Element;
6454
}
6555

6656
if (isPhone()) {
@@ -126,18 +116,7 @@ function ActionSheetButton(props: ActionSheetButtonPropTypes) {
126116
*
127117
*/
128118
const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((props, ref) => {
129-
const {
130-
a11yConfig,
131-
children,
132-
className,
133-
header,
134-
headerText,
135-
portalContainer,
136-
showCancelButton = true,
137-
onOpen,
138-
open,
139-
...rest
140-
} = props;
119+
const { a11yConfig, children, className, header, headerText, hideCancelButton, onOpen, open, ...rest } = props;
141120

142121
useStylesheet(styleData, ActionSheet.displayName);
143122

@@ -148,18 +127,16 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
148127
}, 0);
149128
const childrenToRender = flattenFragments(children);
150129
const childrenArrayLength = childrenToRender.length;
151-
const childrenLength = isPhone() && showCancelButton ? childrenArrayLength + 1 : childrenArrayLength;
130+
const childrenLength = isPhone() && !hideCancelButton ? childrenArrayLength + 1 : childrenArrayLength;
152131

153-
const [internalOpen, setInternalOpen] = useState(open);
132+
const [internalOpen, setInternalOpen] = useState(undefined);
154133
useEffect(() => {
155-
setInternalOpen(open);
134+
const tagName = getUi5TagWithSuffix('ui5-responsive-popover');
135+
void customElements.whenDefined(tagName).then(() => {
136+
setInternalOpen(open);
137+
});
156138
}, [open]);
157139

158-
const canRenderPortal = useCanRenderPortal();
159-
if (!canRenderPortal) {
160-
return null;
161-
}
162-
163140
const handleCancelBtnClick = () => {
164141
setInternalOpen(false);
165142
};
@@ -237,7 +214,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
237214
};
238215

239216
const displayHeader = isPhone();
240-
return createPortal(
217+
return (
241218
<ResponsivePopover
242219
open={internalOpen}
243220
headerText={displayHeader ? headerText : undefined}
@@ -257,7 +234,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
257234
ref={actionBtnsRef}
258235
>
259236
{childrenToRender.map(renderActionSheetButton)}
260-
{isPhone() && showCancelButton && (
237+
{isPhone() && !hideCancelButton && (
261238
<Button
262239
design={ButtonDesign.Negative}
263240
onClick={handleCancelBtnClick}
@@ -270,8 +247,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
270247
</Button>
271248
)}
272249
</div>
273-
</ResponsivePopover>,
274-
portalContainer ?? document.body
250+
</ResponsivePopover>
275251
);
276252
});
277253

0 commit comments

Comments
 (0)