From d1a70d54d1b5f76da721609f0424285d88537f7f Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 8 Jul 2024 12:07:00 +0200 Subject: [PATCH 1/5] AnalyticalTable: remove `portalContainer` --- .../ColumnHeader/ColumnHeaderContainer.tsx | 3 --- .../ColumnHeader/ColumnHeaderModal.tsx | 9 +++----- .../AnalyticalTable/ColumnHeader/index.tsx | 3 --- .../components/AnalyticalTable/Recipes.mdx | 22 ++++++++----------- .../AnalyticalTable/VerticalResizer.tsx | 18 +++++---------- .../src/components/AnalyticalTable/index.tsx | 3 --- .../components/AnalyticalTable/types/index.ts | 8 ------- 7 files changed, 18 insertions(+), 48 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx index b67436aca51..99106d78a8c 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx @@ -13,7 +13,6 @@ interface ColumnHeaderContainerProps { onGroupByChanged: (e: CustomEvent<{ column?: Record; isGrouped?: boolean }>) => void; resizeInfo: Record; isRtl: boolean; - portalContainer: Element; columnVirtualizer: Virtualizer; uniqueId: string; showVerticalEndBorder: boolean; @@ -27,7 +26,6 @@ export const ColumnHeaderContainer = forwardRef {column.render('Header')} diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx index 637e8e8120d..78d9c56aa55 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx @@ -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'; @@ -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; } 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; @@ -164,7 +162,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { return null; } - return createPortal( + return ( { )} - , - portalContainer ?? document.body + ); }; ColumnHeaderModal.displayName = 'ColumnHeaderModal'; diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx index 839609cca8f..0e9e29abb37 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx @@ -37,7 +37,6 @@ export interface ColumnHeaderProps { columnVirtualizer: Virtualizer; isRtl: boolean; children: ReactNode | ReactNode[]; - portalContainer: Element; columnId?: string; showVerticalEndBorder: boolean; @@ -81,7 +80,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { visibleColumnIndex, onClick, onKeyDown, - portalContainer, isFiltered, title, 'aria-label': ariaLabel, @@ -239,7 +237,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { openerRef={columnHeaderRef} open={popoverOpen} setPopoverOpen={setPopoverOpen} - portalContainer={portalContainer} /> )} diff --git a/packages/main/src/components/AnalyticalTable/Recipes.mdx b/packages/main/src/components/AnalyticalTable/Recipes.mdx index b5fb594cc59..26e84e42d93 100644 --- a/packages/main/src/components/AnalyticalTable/Recipes.mdx +++ b/packages/main/src/components/AnalyticalTable/Recipes.mdx @@ -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. @@ -254,18 +253,15 @@ const columns = [ return ( <> - {createPortal( - { - e.stopPropagation(); - }} - > - - , - document.body - )} + { + e.stopPropagation(); + }} + > + + ); } diff --git a/packages/main/src/components/AnalyticalTable/VerticalResizer.tsx b/packages/main/src/components/AnalyticalTable/VerticalResizer.tsx index 978d5d9c3e2..08cf25d078a 100644 --- a/packages/main/src/components/AnalyticalTable/VerticalResizer.tsx +++ b/packages/main/src/components/AnalyticalTable/VerticalResizer.tsx @@ -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'; @@ -13,7 +12,6 @@ interface VerticalResizerProps { internalRowHeight: number; hasPopInColumns: boolean; popInRowHeight: number; - portalContainer: Element; rowsLength: number; visibleRows: number; handleOnLoadMore: (e: Event) => void; @@ -34,7 +32,6 @@ export const VerticalResizer = (props: VerticalResizerProps) => { internalRowHeight, hasPopInColumns, popInRowHeight, - portalContainer, rowsLength, visibleRows, handleOnLoadMore @@ -154,15 +151,12 @@ export const VerticalResizer = (props: VerticalResizerProps) => { role="separator" title={i18nBundle.getText(DRAG_TO_RESIZE)} > - {resizerPosition && - isDragging && - createPortal( -
, - portalContainer ?? document.body - )} + {resizerPosition && isDragging && ( +
+ )}
); }; diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index 3f4efb7998b..15977649585 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -139,7 +139,6 @@ const AnalyticalTable = forwardRef { * @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 /** From 3e55db22fe072f0d4bf5908ca717405845e29bac Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 8 Jul 2024 12:10:58 +0200 Subject: [PATCH 2/5] docs(MessageBox): remove `createPortal` example --- .../src/components/MessageBox/MessageBox.mdx | 28 ------------------- .../MessageBox/MessageBox.stories.tsx | 17 +++-------- .../main/src/components/MessageBox/index.tsx | 1 - 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/packages/main/src/components/MessageBox/MessageBox.mdx b/packages/main/src/components/MessageBox/MessageBox.mdx index 10288a57fba..69abe06afdc 100644 --- a/packages/main/src/components/MessageBox/MessageBox.mdx +++ b/packages/main/src/components/MessageBox/MessageBox.mdx @@ -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 ( - <> - - {createPortal( - - Content - , - document.body - )} - - ); -}; -``` - # More Examples
diff --git a/packages/main/src/components/MessageBox/MessageBox.stories.tsx b/packages/main/src/components/MessageBox/MessageBox.stories.tsx index f31c8fd0de0..bdf1be1072c 100644 --- a/packages/main/src/components/MessageBox/MessageBox.stories.tsx +++ b/packages/main/src/components/MessageBox/MessageBox.stories.tsx @@ -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((args, ref) => - createPortal(, 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 } @@ -38,7 +29,7 @@ const meta = { chromatic: { delay: 1000 } }, tags: ['package:@ui5/webcomponents', 'cem-module:Dialog'] -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/packages/main/src/components/MessageBox/index.tsx b/packages/main/src/components/MessageBox/index.tsx index a13d89c3d54..e2488b52a3d 100644 --- a/packages/main/src/components/MessageBox/index.tsx +++ b/packages/main/src/components/MessageBox/index.tsx @@ -134,7 +134,6 @@ const getActions = (actions, type): (string | ReactElement)[] = /** * 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((props, ref) => { const { From 3923878db1c196215ea321b999ee143367360287 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 8 Jul 2024 12:35:31 +0200 Subject: [PATCH 3/5] docs adjustments --- docs/knowledge-base/Portals.mdx | 20 +++++++++-- .../MessageView/MessageView.stories.tsx | 27 +++----------- .../ColorPalettePopover.stories.tsx | 17 +++------ .../main/src/webComponents/Dialog/Dialog.mdx | 20 ----------- .../webComponents/Dialog/Dialog.stories.tsx | 16 +++------ .../src/webComponents/Popover/Popover.mdx | 20 ----------- .../webComponents/Popover/Popover.stories.tsx | 15 +++----- .../ResponsivePopover/ResponsivePopover.mdx | 20 ----------- .../ResponsivePopover.stories.tsx | 16 +++------ .../ViewSettingsDialog.stories.tsx | 35 +++++++++---------- 10 files changed, 55 insertions(+), 151 deletions(-) diff --git a/docs/knowledge-base/Portals.mdx b/docs/knowledge-base/Portals.mdx index 8b983145919..87882c4ea3f 100644 --- a/docs/knowledge-base/Portals.mdx +++ b/docs/knowledge-base/Portals.mdx @@ -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'; - + -# Working with Portals +# Working with Portals in v1 + + + Since v2, it's not necessary to use portals anymore, as all popovers and dialogs are now using the{' '} + + Popover API + + . + + } +/> This entry explains why portals are used in UI5 Web Components for React components and what you need to consider when using them. diff --git a/packages/main/src/components/MessageView/MessageView.stories.tsx b/packages/main/src/components/MessageView/MessageView.stories.tsx index 4fc05f3a1e9..8ec1f2460f0 100644 --- a/packages/main/src/components/MessageView/MessageView.stories.tsx +++ b/packages/main/src/components/MessageView/MessageView.stories.tsx @@ -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((args, ref) => - createPortal(, 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((args, ref) => - createPortal(, document.body) -); -ResponsivePopover.displayName = 'ResponsivePopover'; - // TODO: check docs for outdated info const meta = { diff --git a/packages/main/src/webComponents/ColorPalettePopover/ColorPalettePopover.stories.tsx b/packages/main/src/webComponents/ColorPalettePopover/ColorPalettePopover.stories.tsx index 7901937d506..65674b47717 100644 --- a/packages/main/src/webComponents/ColorPalettePopover/ColorPalettePopover.stories.tsx +++ b/packages/main/src/webComponents/ColorPalettePopover/ColorPalettePopover.stories.tsx @@ -1,15 +1,13 @@ import { isChromatic } from '@sb/utils'; import type { Meta, StoryObj } from '@storybook/react'; -import { forwardRef, useEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import { useEffect, useRef, useState } from 'react'; import { Button } from '../Button'; import { ColorPaletteItem } from '../ColorPaletteItem'; -import type { ColorPalettePopoverDomRef, ColorPalettePopoverPropTypes } from './index'; -import { ColorPalettePopover as OriginalColorPalettePopover } from './index'; +import { ColorPalettePopover } from './index'; const meta = { title: 'Modals & Popovers / ColorPalettePopover', - component: OriginalColorPalettePopover, + component: ColorPalettePopover, argTypes: { children: { control: { disable: true } }, defaultColor: { control: { type: 'color' } } @@ -18,14 +16,7 @@ const meta = { chromatic: { delay: 1000 } }, tags: ['package:@ui5/webcomponents'] -} satisfies Meta; - -// todo remove once portals are supported inline, or general popovers are supported w/o having to mount them to the body -const ColorPalettePopover = forwardRef((args, ref) => - createPortal(, document.body) -); - -ColorPalettePopover.displayName = 'ColorPalettePopover'; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/packages/main/src/webComponents/Dialog/Dialog.mdx b/packages/main/src/webComponents/Dialog/Dialog.mdx index 60798bd786a..a944e672d42 100644 --- a/packages/main/src/webComponents/Dialog/Dialog.mdx +++ b/packages/main/src/webComponents/Dialog/Dialog.mdx @@ -62,26 +62,6 @@ const DialogComponent = () => { }; ``` -## Using Dialogs inside other components - -`Dialogs` 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 `Dialog` outside of the DOM hierarchy of the parent component. - -```jsx -const DialogComponent = () => { - const dialogRef = useRef(null); - const onButtonClick = () => { - dialogRef.current.show(); - }; - return ( - <> - - {createPortal(, document.body)} - - ); -}; -``` - ## Closing Dialogs Closing `Dialogs` works in the same way as opening them. diff --git a/packages/main/src/webComponents/Dialog/Dialog.stories.tsx b/packages/main/src/webComponents/Dialog/Dialog.stories.tsx index 8c8601314fa..7bd028c059b 100644 --- a/packages/main/src/webComponents/Dialog/Dialog.stories.tsx +++ b/packages/main/src/webComponents/Dialog/Dialog.stories.tsx @@ -3,21 +3,13 @@ import type { Meta, StoryObj } from '@storybook/react'; import BarDesign from '@ui5/webcomponents/dist/types/BarDesign.js'; import settingsIcon from '@ui5/webcomponents-icons/dist/settings.js'; import { clsx } from 'clsx'; -import { forwardRef, useEffect, useState } from 'react'; -import { createPortal } from 'react-dom'; -import type { DialogDomRef, DialogPropTypes } from '../index.js'; +import { useEffect, useState } from 'react'; import { Bar, Button, Icon, List, ListItemStandard, Title } from '../index.js'; -import { Dialog as OriginalDialog } from './index'; - -// todo remove once portals are supported inline, or popovers are supported w/o having to mount them to the body -const Dialog = forwardRef((args, ref) => - createPortal(, document.body) -); -Dialog.displayName = 'Dialog'; +import { Dialog } from './index'; const meta = { title: 'Modals & Popovers / Dialog', - component: OriginalDialog, + component: Dialog, argTypes: { footer: { control: { disable: true } }, header: { control: { disable: true } } @@ -29,7 +21,7 @@ const meta = { className: 'footerPartNoPadding' }, tags: ['package:@ui5/webcomponents'] -} satisfies Meta; +} satisfies Meta; //TODO: check all "modals" for outdated info export default meta; diff --git a/packages/main/src/webComponents/Popover/Popover.mdx b/packages/main/src/webComponents/Popover/Popover.mdx index aa4df098dc9..5e70ec09756 100644 --- a/packages/main/src/webComponents/Popover/Popover.mdx +++ b/packages/main/src/webComponents/Popover/Popover.mdx @@ -102,26 +102,6 @@ const PopoverComponent = () => { }; ``` -## Using Popovers inside other components - -`Popovers` 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 `Popover` outside of the DOM hierarchy of the parent component. - -```jsx -const PopoverComponent = () => { - const popoverRef = useRef(null); - const onButtonClick = (e) => { - popoverRef.current.showAt(e.target); - }; - return ( - <> - - {createPortal(, document.body)} - - ); -}; -``` - ## Closing Popovers Closing `Popovers` works in the same way as opening them. diff --git a/packages/main/src/webComponents/Popover/Popover.stories.tsx b/packages/main/src/webComponents/Popover/Popover.stories.tsx index 081e01be3d3..13de102d7d9 100644 --- a/packages/main/src/webComponents/Popover/Popover.stories.tsx +++ b/packages/main/src/webComponents/Popover/Popover.stories.tsx @@ -6,8 +6,7 @@ import PopoverHorizontalAlign from '@ui5/webcomponents/dist/types/PopoverHorizon import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; import PopoverVerticalAlign from '@ui5/webcomponents/dist/types/PopoverVerticalAlign.js'; import { clsx } from 'clsx'; -import { forwardRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import { useState } from 'react'; import { Bar } from '../Bar'; import { Button } from '../Button'; import { Icon } from '../Icon'; @@ -15,17 +14,11 @@ import { Label } from '../Label'; import { List } from '../List'; import { ListItemStandard } from '../ListItemStandard'; import { Title } from '../Title'; -import type { PopoverDomRef, PopoverPropTypes } from './index'; -import { Popover as OriginalPopover } from './index'; - -const Popover = forwardRef((args, ref) => - createPortal(, document.body) -); -Popover.displayName = 'Popover'; +import { Popover } from './index'; const meta = { title: 'Modals & Popovers / Popover', - component: OriginalPopover, + component: Popover, argTypes: { footer: { control: { disable: true } }, header: { control: { disable: true } } @@ -39,7 +32,7 @@ const meta = { className: 'footerPartNoPadding' }, tags: ['package:@ui5/webcomponents'] -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.mdx b/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.mdx index 044ba6dde21..1590440694d 100644 --- a/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.mdx +++ b/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.mdx @@ -102,26 +102,6 @@ const PopoverComponent = () => { }; ``` -## Using ResponsivePopovers inside other components - -`ResponsivePopovers` 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 `ResponsivePopover` outside of the DOM hierarchy of the parent component. - -```jsx -const PopoverComponent = () => { - const popoverRef = useRef(null); - const onButtonClick = (e) => { - popoverRef.current.showAt(e.target); - }; - return ( - <> - - {createPortal(, document.body)} - - ); -}; -``` - ## Closing ResponsivePopovers Closing `ResponsivePopovers` works in the same way as opening them. diff --git a/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.stories.tsx b/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.stories.tsx index 9fad7779be2..a334f2f0105 100644 --- a/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.stories.tsx +++ b/packages/main/src/webComponents/ResponsivePopover/ResponsivePopover.stories.tsx @@ -4,8 +4,7 @@ import PopoverHorizontalAlign from '@ui5/webcomponents/dist/types/PopoverHorizon import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; import PopoverVerticalAlign from '@ui5/webcomponents/dist/types/PopoverVerticalAlign.js'; import { clsx } from 'clsx'; -import { forwardRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import { useState } from 'react'; import { Bar } from '../Bar'; import { Button } from '../Button'; import { Icon } from '../Icon'; @@ -13,19 +12,12 @@ import { Label } from '../Label'; import { List } from '../List'; import { ListItemStandard } from '../ListItemStandard'; import { Title } from '../Title'; -import type { ResponsivePopoverDomRef, ResponsivePopoverPropTypes } from './index'; -import { ResponsivePopover as OriginalResponsivePopover } from './index'; +import { ResponsivePopover } from './index'; import '@ui5/webcomponents-icons/dist/settings.js'; -// todo remove once portals are supported inline, or popovers are supported w/o having to mount them to the body -const ResponsivePopover = forwardRef((args, ref) => - createPortal(, document.body) -); -ResponsivePopover.displayName = 'ResponsivePopover'; - const meta = { title: 'Modals & Popovers / ResponsivePopover', - component: OriginalResponsivePopover, + component: ResponsivePopover, argTypes: { footer: { control: { disable: true } }, children: { control: { disable: true } }, @@ -45,7 +37,7 @@ const meta = { className: 'footerPartNoPadding' }, tags: ['package:@ui5/webcomponents'] -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/packages/main/src/webComponents/ViewSettingsDialog/ViewSettingsDialog.stories.tsx b/packages/main/src/webComponents/ViewSettingsDialog/ViewSettingsDialog.stories.tsx index 4896912d5bd..f70e5e11207 100644 --- a/packages/main/src/webComponents/ViewSettingsDialog/ViewSettingsDialog.stories.tsx +++ b/packages/main/src/webComponents/ViewSettingsDialog/ViewSettingsDialog.stories.tsx @@ -1,15 +1,12 @@ import { isChromatic } from '@sb/utils'; import type { Meta, StoryObj } from '@storybook/react'; -import { forwardRef, useEffect, useRef } from 'react'; -import { createPortal } from 'react-dom'; -import type { ViewSettingsDialogPropTypes } from '../../index'; +import { useEffect, useState } from 'react'; import { Button, FilterItem, FilterItemOption, SortItem } from '../../index'; -import type { ViewSettingsDialogDomRef } from './index.js'; -import { ViewSettingsDialog as OriginalViewSettingsDialog } from './index.js'; +import { ViewSettingsDialog } from './index.js'; const meta = { title: 'Modals & Popovers / ViewSettingsDialog', - component: OriginalViewSettingsDialog, + component: ViewSettingsDialog, argTypes: { filterItems: { control: { disable: true } }, sortItems: { control: { disable: true } } @@ -52,34 +49,36 @@ const meta = { chromatic: { delay: 999 } }, tags: ['package:@ui5/webcomponents-fiori'] -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; -const ViewSettingsDialog = forwardRef((args, ref) => - createPortal(, document.body) -); -ViewSettingsDialog.displayName = 'ViewSettingsDialog'; - export const Default: Story = { render: (args) => { - const ref = useRef(null); + const [open, setOpen] = useState(isChromatic || args.open); useEffect(() => { - if (isChromatic) { - ref.current.show(); + if (!isChromatic) { + setOpen(args.open); } - }, []); + }, [args.open, isChromatic]); return ( <> - + { + setOpen(false); + args.onClose(e); + }} + /> ); } From ad7207a77d1f868a9558200a7405b6bbfd0e15db Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 8 Jul 2024 12:39:29 +0200 Subject: [PATCH 4/5] Update MigrationGuide.mdx --- docs/MigrationGuide.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/MigrationGuide.mdx b/docs/MigrationGuide.mdx index 351fe314510..0a874df5be5 100644 --- a/docs/MigrationGuide.mdx +++ b/docs/MigrationGuide.mdx @@ -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. From aca8815b976a301dce725b81f7f619b01bf3b001 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 8 Jul 2024 12:49:00 +0200 Subject: [PATCH 5/5] remove `canRenderPortal` check --- .../AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx | 9 +-------- .../src/components/AnalyticalTable/VerticalResizer.tsx | 6 ------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx index 78d9c56aa55..e8328e4124e 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx @@ -11,7 +11,6 @@ import type { MutableRefObject } from 'react'; import { useEffect, useRef } from 'react'; 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'; import { stopPropagation } from '../../../internal/stopPropagation.js'; import { getUi5TagWithSuffix } from '../../../internal/utils.js'; import { Icon } from '../../../webComponents/Icon/index.js'; @@ -148,19 +147,13 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { } }; - const canRenderPortal = useCanRenderPortal(); - useEffect(() => { if (open && ref.current && openerRef.current) { void customElements.whenDefined(getUi5TagWithSuffix('ui5-popover')).then(() => { ref.current.opener = openerRef.current; }); } - }, [open, canRenderPortal]); - - if (!canRenderPortal) { - return null; - } + }, [open]); return ( { isInitial.current = false; }, [rowsLength, visibleRows]); - const canRenderPortal = useCanRenderPortal(); - if (!canRenderPortal) { - return null; - } - return (