Skip to content

refactor(AnalyticalTable): remove unnecessary portalContainer prop #6039

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

Merged
merged 5 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions docs/MigrationGuide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ function MyComponent() {
- `alwaysShowSubComponent` has been removed, please use `subComponentsBehavior` with `AnalyticalTableSubComponentsBehavior.Visibe` instead.
- The default value of `sortable` (`true`) has been removed. To allow sorting in your table please set `sorting` to `true` yourself.

**Removed Props:**

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

**Renamed Enums:**

Only the names of the following enums have changed, so it's sufficient to replace them with the new name.
Expand Down
20 changes: 18 additions & 2 deletions docs/knowledge-base/Portals.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { Meta } from '@storybook/addon-docs';
import { Footer, TableOfContent } from '@sb/components';
import MessageStripDesign from '@ui5/webcomponents/dist/types/MessageStripDesign.js';
import { MessageStrip } from '@ui5/webcomponents-react';

<Meta title="Working with Portals" />
<Meta title="Working with Portals (v1)" />

# Working with Portals
# Working with Portals in v1

<MessageStrip
hideCloseButton
design={MessageStripDesign.Critical}
children={
<>
Since v2, it's not necessary to use portals anymore, as all popovers and dialogs are now using the{' '}
<ui5-link target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API">
Popover API
</ui5-link>
.
</>
}
/>

This entry explains why portals are used in UI5 Web Components for React components and what you need to consider when using them.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ interface ColumnHeaderContainerProps {
onGroupByChanged: (e: CustomEvent<{ column?: Record<string, unknown>; isGrouped?: boolean }>) => void;
resizeInfo: Record<string, unknown>;
isRtl: boolean;
portalContainer: Element;
columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
uniqueId: string;
showVerticalEndBorder: boolean;
Expand All @@ -27,7 +26,6 @@ export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderCont
onGroupByChanged,
resizeInfo,
isRtl,
portalContainer,
columnVirtualizer,
uniqueId,
showVerticalEndBorder
Expand Down Expand Up @@ -86,7 +84,6 @@ export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderCont
virtualColumn={virtualColumn}
columnVirtualizer={columnVirtualizer}
isRtl={isRtl}
portalContainer={portalContainer}
>
{column.render('Header')}
</ColumnHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import iconSortDescending from '@ui5/webcomponents-icons/dist/sort-descending.js
import { enrichEventWithDetails, useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base';
import type { MutableRefObject } from 'react';
import { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import { FlexBoxAlignItems, TextAlign } from '../../../enums/index.js';
import { CLEAR_SORTING, GROUP, SORT_ASCENDING, SORT_DESCENDING, UNGROUP } from '../../../i18n/i18n-defaults.js';
import { useCanRenderPortal } from '../../../internal/ssr.js';
Expand All @@ -31,13 +30,12 @@ export interface ColumnHeaderModalProperties {
onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void;
open: boolean;
setPopoverOpen: (open: boolean) => void;
portalContainer: Element;
isRtl: boolean;
openerRef: MutableRefObject<HTMLDivElement>;
}

export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => {
const { column, onSort, onGroupBy, open, setPopoverOpen, portalContainer, isRtl, openerRef } = props;
const { column, onSort, onGroupBy, open, setPopoverOpen, isRtl, openerRef } = props;
useStylesheet(styleData, ColumnHeaderModal.displayName);
const showFilter = column.canFilter;
const showGroup = column.canGroupBy;
Expand Down Expand Up @@ -164,7 +162,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => {
return null;
}

return createPortal(
return (
<Popover
open={open}
hideArrow
Expand Down Expand Up @@ -218,8 +216,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => {
</ListItemStandard>
)}
</List>
</Popover>,
portalContainer ?? document.body
</Popover>
);
};
ColumnHeaderModal.displayName = 'ColumnHeaderModal';
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export interface ColumnHeaderProps {
columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
isRtl: boolean;
children: ReactNode | ReactNode[];
portalContainer: Element;
columnId?: string;
showVerticalEndBorder: boolean;

Expand Down Expand Up @@ -81,7 +80,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
visibleColumnIndex,
onClick,
onKeyDown,
portalContainer,
isFiltered,
title,
'aria-label': ariaLabel,
Expand Down Expand Up @@ -239,7 +237,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
openerRef={columnHeaderRef}
open={popoverOpen}
setPopoverOpen={setPopoverOpen}
portalContainer={portalContainer}
/>
)}
</div>
Expand Down
22 changes: 9 additions & 13 deletions packages/main/src/components/AnalyticalTable/Recipes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ Below you can find two examples of typical pain points:
In general, we recommend mounting modals like Dialogs or Popovers outside the `AnalyticalTable`.
Still, it's possible to also render them inside a custom cell, but there are some things to consider:

- Use `createPortal` to render the modal outside the `AnalyticalTable` to prevent visual issues.
- Use conditional rendering to prevent the modal from being rendered for every cell.
- Call `event.stopPropagation()` in the respective handler of the event if you're facing issues with focus handling (`onFocus`), keyboard navigation (i.a. `onKeyDown`), etc.

Expand All @@ -254,18 +253,15 @@ const columns = [
return (
<>
<Button onClick={toggle}>Open</Button>
{createPortal(
<Dialog
open={open}
initialFocus={uniqueId}
onFocus={(e) => {
e.stopPropagation();
}}
>
<Input placeholder="Focus me!" id={uniqueId} />
</Dialog>,
document.body
)}
<Dialog
open={open}
initialFocus={uniqueId}
onFocus={(e) => {
e.stopPropagation();
}}
>
<Input placeholder="Focus me!" id={uniqueId} />
</Dialog>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useStylesheet, useI18nBundle } from '@ui5/webcomponents-react-base';
import type { MutableRefObject } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { DRAG_TO_RESIZE } from '../../i18n/i18n-defaults.js';
import { useCanRenderPortal } from '../../internal/ssr.js';
import { classNames, styleData } from './VerticalResizer.module.css.js';
Expand All @@ -13,7 +12,6 @@ interface VerticalResizerProps {
internalRowHeight: number;
hasPopInColumns: boolean;
popInRowHeight: number;
portalContainer: Element;
rowsLength: number;
visibleRows: number;
handleOnLoadMore: (e: Event) => void;
Expand All @@ -34,7 +32,6 @@ export const VerticalResizer = (props: VerticalResizerProps) => {
internalRowHeight,
hasPopInColumns,
popInRowHeight,
portalContainer,
rowsLength,
visibleRows,
handleOnLoadMore
Expand Down Expand Up @@ -154,15 +151,12 @@ export const VerticalResizer = (props: VerticalResizerProps) => {
role="separator"
title={i18nBundle.getText(DRAG_TO_RESIZE)}
>
{resizerPosition &&
isDragging &&
createPortal(
<div
className={classNames.resizer}
style={{ top: resizerPosition.top, left: resizerPosition.left, width: resizerPosition.width }}
/>,
portalContainer ?? document.body
)}
{resizerPosition && isDragging && (
<div
className={classNames.resizer}
style={{ top: resizerPosition.top, left: resizerPosition.left, width: resizerPosition.width }}
/>
)}
</div>
);
};
Expand Down
3 changes: 0 additions & 3 deletions packages/main/src/components/AnalyticalTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
noDataText,
overscanCount,
overscanCountHorizontal = 5,
portalContainer,
retainColumnWidth,
reactTableOptions,
renderRowSubComponent,
Expand Down Expand Up @@ -768,7 +767,6 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
onSort={onSort}
onGroupByChanged={onGroupByChanged}
isRtl={isRtl}
portalContainer={portalContainer}
columnVirtualizer={columnVirtualizer}
uniqueId={uniqueId}
showVerticalEndBorder={showVerticalEndBorder}
Expand Down Expand Up @@ -851,7 +849,6 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
dispatch={dispatch}
extensionsHeight={extensionsHeight}
internalRowHeight={internalRowHeight}
portalContainer={portalContainer}
rowsLength={rows.length}
visibleRows={internalVisibleRowCount}
handleOnLoadMore={handleOnLoadMore}
Expand Down
8 changes: 0 additions & 8 deletions packages/main/src/components/AnalyticalTable/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,14 +615,6 @@ export interface AnalyticalTablePropTypes extends Omit<CommonProps, 'title'> {
* @since 1.19.0
*/
subComponentsBehavior?: AnalyticalTableSubComponentsBehavior | keyof typeof AnalyticalTableSubComponentsBehavior;
/**
* Defines where modals and other elements which should be mounted outside the DOM hierarchy 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;

// events
/**
Expand Down
28 changes: 0 additions & 28 deletions packages/main/src/components/MessageBox/MessageBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,6 @@ const MessageBoxComponent = () => {
};
```

## Using MessageBoxes inside other components

`MessageBoxes` 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 `MessageBox` outside of the DOM hierarchy of the parent component.

```JSX
const MessageBoxComponent = () => {
const [open, setOpen] = useState(false);
const onButtonClick = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<Button onClick={onButtonClick}>Open Messagebox</Button>
{createPortal(
<MessageBox open={open} onClose={handleClose}>
Content
</MessageBox>,
document.body
)}
</>
);
};
```

# More Examples

<br />
Expand Down
17 changes: 4 additions & 13 deletions packages/main/src/components/MessageBox/MessageBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import { isChromatic } from '@sb/utils';
import type { Meta, StoryObj } from '@storybook/react';
import { forwardRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { useEffect, useState } from 'react';
import { MessageBoxAction } from '../../enums/MessageBoxAction';
import { MessageBoxType } from '../../enums/MessageBoxType';
import type { DialogDomRef } from '../../webComponents';
import { Button } from '../../webComponents/Button/index';
import type { MessageBoxPropTypes } from './index.js';
import { MessageBox as OriginalMessageBox } from './index.js';

// todo remove once portals are supported inline, or popovers are supported w/o having to mount them to the body
const MessageBox = forwardRef<DialogDomRef, MessageBoxPropTypes>((args, ref) =>
createPortal(<OriginalMessageBox {...args} ref={ref} />, document.body)
);
MessageBox.displayName = 'MessageBox';
import { MessageBox } from './index.js';

const meta = {
title: 'Modals & Popovers / MessageBox',
component: OriginalMessageBox,
component: MessageBox,
argTypes: {
header: {
control: { disable: true }
Expand All @@ -38,7 +29,7 @@ const meta = {
chromatic: { delay: 1000 }
},
tags: ['package:@ui5/webcomponents', 'cem-module:Dialog']
} satisfies Meta<typeof OriginalMessageBox>;
} satisfies Meta<typeof MessageBox>;

export default meta;
type Story = StoryObj<typeof meta>;
Expand Down
1 change: 0 additions & 1 deletion packages/main/src/components/MessageBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ const getActions = (actions, type): (string | ReactElement<ButtonPropTypes>)[] =

/**
* The `MessageBox` component provides easier methods to create a `Dialog`, such as standard alerts, confirmation dialogs, or arbitrary message dialogs.
* For convenience, it also provides an `open` prop, so it is not necessary to attach a `ref` to open the `MessageBox`.
*/
const MessageBox = forwardRef<DialogDomRef, MessageBoxPropTypes>((props, ref) => {
const {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,22 @@
import { generateMessageItems } from '@sb/mockData/generateMessageItems';
import { generateMessageItems } from '@sb/mockData/generateMessageItems.js';
import type { Meta, StoryObj } from '@storybook/react';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
import arrowLeftIcon from '@ui5/webcomponents-icons/dist/slim-arrow-left.js';
import { forwardRef, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useRef, useState } from 'react';
import { FlexBoxAlignItems, FlexBoxJustifyContent } from '../../enums/index.js';
import type {
DialogDomRef,
DialogPropTypes,
ResponsivePopoverDomRef,
ResponsivePopoverPropTypes
} from '../../webComponents';
import { Bar } from '../../webComponents/Bar/index.js';
import { Button } from '../../webComponents/Button/index.js';
import { Dialog as OriginalDialog } from '../../webComponents/Dialog';
import { Dialog } from '../../webComponents/Dialog/index.js';
import { Link } from '../../webComponents/Link/index.js';
import { ResponsivePopover as OriginalResponsivePopover } from '../../webComponents/ResponsivePopover';
import { ResponsivePopover } from '../../webComponents/ResponsivePopover/index.js';
import { Title } from '../../webComponents/Title/index.js';
import { FlexBox } from '../FlexBox/index.js';
import { MessageViewButton } from '../MessageViewButton/index.js';
import { MessageItem } from './MessageItem.js';
import { MessageView } from './index.js';

// todo remove once portals are supported inline, or popovers are supported w/o having to mount them to the body
const Dialog = forwardRef<DialogDomRef, DialogPropTypes>((args, ref) =>
createPortal(<OriginalDialog {...args} ref={ref} />, document.body)
);
Dialog.displayName = 'Dialog';

// todo remove once portals are supported inline, or popovers are supported w/o having to mount them to the body
const ResponsivePopover = forwardRef<ResponsivePopoverDomRef, ResponsivePopoverPropTypes>((args, ref) =>
createPortal(<OriginalResponsivePopover {...args} ref={ref} />, document.body)
);
ResponsivePopover.displayName = 'ResponsivePopover';

// TODO: check docs for outdated info

const meta = {
Expand Down
Loading
Loading