Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ActionSheet): api alignment #5956

Merged
merged 7 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/MigrationGuide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,36 @@ Please use the following components instead:

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

## Components with API Changes

### ActionSheet

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.
For a better aligned API, the `showCancelButton` prop has been replaced wih the `hideCancelButton` prop and the logic has been inverted.
You only need to apply changes to your code if `showCancelButton` has been set to `false`.

```tsx
// v1
import { ActionSheet, Button } from '@ui5/webcomponents-react';

function MyComponent() {
return (
<ActionSheet showCancelButton={false}>
<Button>Action 1</Button>
</ActionSheet>
);
}

// v2
import { ActionSheet, Button } from '@ui5/webcomponents-react';

function MyComponent() {
return (
<ActionSheet hideCancelButton>
<Button>Action 1</Button>
</ActionSheet>
);
}
```

<Footer />
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
"changedProps": {
"onAfterClose": "onClose",
"onAfterOpen": "onOpen",
"placementType": "placement"
}
"placementType": "placement",
"showCancelButton": "hideCancelButton"
},
"removedProps": ["portalContainer"]
},
"Avatar": {},
"Badge": {
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/scripts/codemod/transforms/v2/main.cts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ export default function transform(file: FileInfo, api: API, options?: Options):
return;
}

if (componentName === 'ActionSheet') {
jsxElements.forEach((el) => {
const showCancelButton = j(el).find(j.JSXAttribute, { name: { name: 'showCancelButton' } });
if (showCancelButton.size() > 0) {
const attr = showCancelButton.get();
if (
attr.value.value &&
((attr.value.value.type === 'JSXAttribute' && attr.value.value === false) ||
(attr.value.value.type === 'JSXExpressionContainer' && attr.value.value.expression.value === false))
) {
j(el)
.find(j.JSXOpeningElement)
.get()
.value.attributes.push(j.jsxAttribute(j.jsxIdentifier('hideCancelButton'), null));
}
showCancelButton.remove();
isDirty = true;
}
});
}

// Special Handling for logic inversions, etc.
if (componentName === 'Button') {
jsxElements.forEach((el) => {
Expand Down
60 changes: 4 additions & 56 deletions packages/main/src/components/ActionSheet/ActionSheet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ import * as ComponentStories from './ActionSheet.stories';

<br />

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

We recommend opening and closing the `ActionSheet` component in a declarative way using the `open` and `opener` prop.
You can still use the imperative way which is outlined below.

```jsx
```tsx
const MyComponentWithActionSheet = () => {
const [actionSheetIsOpen, setActionSheetIsOpen] = useState(false);
return (
Expand All @@ -53,7 +50,8 @@ const MyComponentWithActionSheet = () => {

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

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.
The `ActionSheet` 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.
You can do that by e.g. leveraging a React Ref, and then set the corresponding property there.

<MessageStrip
Expand Down Expand Up @@ -83,54 +81,4 @@ const ActionSheetComponent = () => {
};
```

#### before 0.22.0

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

```jsx
const ActionSheetComponent = () => {
const actionSheetRef = useRef(null);
const onButtonClick = (e) => {
actionSheetRef.current.showAt(e.target);
};
return (
<>
<Button onClick={onButtonClick}>Open ActionSheet</Button>
<ActionSheet ref={actionSheetRef}>
<Button icon="add">Accept</Button>
<Button>Reject</Button>
<Button>This is my super long text!</Button>
</ActionSheet>
</>
);
};
```

## Using ActionSheets inside other components

`ActionSheets` are often used within other components, when opened this could sometimes have unwanted side effects.
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.

```JSX
const ActionSheetComponent = () => {
const actionSheetRef = useRef(null);
const onButtonClick = (e) => {
actionSheetRef.current.showAt(e.target);
};
return (
<>
<Button onClick={onButtonClick}>Open ActionSheet</Button>
{createPortal(
<ActionSheet ref={actionSheetRef}>
<Button icon="add">Accept</Button>
<Button>Reject</Button>
<Button>This is my super long text!</Button>
</ActionSheet>,
document.body
)}
</>
);
};
```

<Footer />
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import declineIcon from '@ui5/webcomponents-icons/dist/decline.js';
import deleteIcon from '@ui5/webcomponents-icons/dist/delete.js';
import emailIcon from '@ui5/webcomponents-icons/dist/email.js';
import forwardIcon from '@ui5/webcomponents-icons/dist/forward.js';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Button } from '../../webComponents/index.js';
import { ActionSheet } from './index.js';

Expand Down Expand Up @@ -41,10 +41,12 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;

//TODO: check docs for outdated info
export const Default: Story = {
render(args) {
const [actionSheetOpen, setActionSheetOpen] = useState<boolean | undefined>(args.open);
useEffect(() => {
setActionSheetOpen(args.open);
}, [args.open]);
return (
<>
<Button
Expand Down
50 changes: 13 additions & 37 deletions packages/main/src/components/ActionSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import { useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base';
import { clsx } from 'clsx';
import type { ReactElement } from 'react';
import { forwardRef, useEffect, useReducer, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { AVAILABLE_ACTIONS, CANCEL, X_OF_Y } from '../../i18n/i18n-defaults.js';
import { addCustomCSSWithScoping } from '../../internal/addCustomCSSWithScoping.js';
import { useCanRenderPortal } from '../../internal/ssr.js';
import { flattenFragments } from '../../internal/utils.js';
import { flattenFragments, getUi5TagWithSuffix } from '../../internal/utils.js';
import { CustomThemingParameters } from '../../themes/CustomVariables.js';
import type { UI5WCSlotsNode } from '../../types/index.js';
import type {
Expand Down Expand Up @@ -42,9 +40,9 @@ export interface ActionSheetPropTypes extends Omit<ResponsivePopoverPropTypes, '
*/
children?: ReactElement<ButtonPropTypes> | ReactElement<ButtonPropTypes>[];
/**
* Displays a cancel button below the action buttons on mobile devices. No cancel button will be shown on desktop and tablet devices.
* Hides the cancel button below the action buttons on mobile devices. No cancel button will be shown on desktop and tablet devices.
*/
showCancelButton?: boolean;
hideCancelButton?: boolean;
/**
* Defines internally used a11y properties.
*/
Expand All @@ -53,14 +51,6 @@ export interface ActionSheetPropTypes extends Omit<ResponsivePopoverPropTypes, '
role?: string;
};
};
/**
* Defines where modals are rendered into via `React.createPortal`.
*
* You can find out more about this [here](https://sap.github.io/ui5-webcomponents-react/?path=/docs/knowledge-base-working-with-portals--page).
*
* Defaults to: `document.body`
*/
portalContainer?: Element;
}

if (isPhone()) {
Expand Down Expand Up @@ -126,18 +116,7 @@ function ActionSheetButton(props: ActionSheetButtonPropTypes) {
*
*/
const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((props, ref) => {
const {
a11yConfig,
children,
className,
header,
headerText,
portalContainer,
showCancelButton = true,
onOpen,
open,
...rest
} = props;
const { a11yConfig, children, className, header, headerText, hideCancelButton, onOpen, open, ...rest } = props;

useStylesheet(styleData, ActionSheet.displayName);

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

const [internalOpen, setInternalOpen] = useState(open);
const [internalOpen, setInternalOpen] = useState(undefined);
useEffect(() => {
setInternalOpen(open);
const tagName = getUi5TagWithSuffix('ui5-responsive-popover');
void customElements.whenDefined(tagName).then(() => {
setInternalOpen(open);
});
}, [open]);

const canRenderPortal = useCanRenderPortal();
if (!canRenderPortal) {
return null;
}

const handleCancelBtnClick = () => {
setInternalOpen(false);
};
Expand Down Expand Up @@ -237,7 +214,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
};

const displayHeader = isPhone();
return createPortal(
return (
<ResponsivePopover
open={internalOpen}
headerText={displayHeader ? headerText : undefined}
Expand All @@ -257,7 +234,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
ref={actionBtnsRef}
>
{childrenToRender.map(renderActionSheetButton)}
{isPhone() && showCancelButton && (
{isPhone() && !hideCancelButton && (
<Button
design={ButtonDesign.Negative}
onClick={handleCancelBtnClick}
Expand All @@ -270,8 +247,7 @@ const ActionSheet = forwardRef<ResponsivePopoverDomRef, ActionSheetPropTypes>((p
</Button>
)}
</div>
</ResponsivePopover>,
portalContainer ?? document.body
</ResponsivePopover>
);
});

Expand Down
Loading