Skip to content

Commit

Permalink
Use AppRootPortal directly in the ModalRoot/PopoutRoot/ActionSheet
Browse files Browse the repository at this point in the history
To avoid rendering modal/popout in SplitLayout during SSR and after
hydration
  • Loading branch information
andrey-medvedev-vk committed Oct 11, 2024
1 parent 1cdb438 commit 2eced1a
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 133 deletions.
23 changes: 13 additions & 10 deletions packages/vkui/src/components/ActionSheet/ActionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useAdaptivityWithJSMediaQueries } from '../../hooks/useAdaptivityWithJS
import { useObjectMemo } from '../../hooks/useObjectMemo';
import { usePlatform } from '../../hooks/usePlatform';
import { useCSSKeyframesAnimationController } from '../../lib/animation';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
import { useScrollLock } from '../AppRoot/ScrollContext';
import { PopoutWrapper } from '../PopoutWrapper/PopoutWrapper';
import { Footnote } from '../Typography/Footnote/Footnote';
Expand Down Expand Up @@ -140,15 +141,17 @@ export const ActionSheet = ({
}

return (
<PopoutWrapper
closing={Boolean(closingBy)}
alignY="bottom"
className={className}
style={style}
onClick={onCloseWithOther}
fixed
>
{actionSheet}
</PopoutWrapper>
<AppRootPortal>
<PopoutWrapper
closing={Boolean(closingBy)}
alignY="bottom"
className={className}
style={style}
onClick={onCloseWithOther}
fixed
>
{actionSheet}
</PopoutWrapper>
</AppRootPortal>
);
};
117 changes: 60 additions & 57 deletions packages/vkui/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
HasDataAttribute,
HasRootRef,
} from '../../types';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
import { useScrollLock } from '../AppRoot/ScrollContext';
import type { ButtonProps } from '../Button/Button';
import { FocusTrap } from '../FocusTrap/FocusTrap';
Expand Down Expand Up @@ -134,67 +135,69 @@ export const Alert = ({
useScrollLock();

return (
<PopoutWrapper
className={className}
closing={closing}
style={style}
onClick={close}
getRootRef={getRootRef}
>
<FocusTrap
{...restProps}
{...animationHandlers}
getRootRef={elementRef}
onClick={stopPropagation}
onClose={close}
autoFocus={animationState === 'entered'}
className={classNames(
styles.host,
platform === 'ios' && styles.ios,
platform === 'vkcom' && styles.vkcom,
closing ? styles.closing : styles.opening,
isDesktop && styles.desktop,
)}
role="alertdialog"
aria-modal
aria-labelledby={headerId}
aria-describedby={textId}
<AppRootPortal>
<PopoutWrapper
className={className}
closing={closing}
style={style}
onClick={close}
getRootRef={getRootRef}
>
<div
<FocusTrap
{...restProps}
{...animationHandlers}
getRootRef={elementRef}
onClick={stopPropagation}
onClose={close}
autoFocus={animationState === 'entered'}
className={classNames(
styles.content,
dismissButtonMode === 'inside' && styles.contentWithButton,
styles.host,
platform === 'ios' && styles.ios,
platform === 'vkcom' && styles.vkcom,
closing ? styles.closing : styles.opening,
isDesktop && styles.desktop,
)}
role="alertdialog"
aria-modal
aria-labelledby={headerId}
aria-describedby={textId}
>
{hasReactNode(header) && <AlertHeader id={headerId}>{header}</AlertHeader>}
{hasReactNode(text) && <AlertText id={textId}>{text}</AlertText>}
{children}
{isDismissButtonVisible && dismissButtonMode === 'inside' && (
<IconButton
label={dismissLabel}
className={classNames(styles.dismiss, 'vkuiInternalAlert__dismiss')}
onClick={close}
hoverMode="opacity"
activeMode="opacity"
data-testid={dismissButtonTestId}
>
<Icon20Cancel />
</IconButton>
<div
className={classNames(
styles.content,
dismissButtonMode === 'inside' && styles.contentWithButton,
)}
>
{hasReactNode(header) && <AlertHeader id={headerId}>{header}</AlertHeader>}
{hasReactNode(text) && <AlertText id={textId}>{text}</AlertText>}
{children}
{isDismissButtonVisible && dismissButtonMode === 'inside' && (
<IconButton
label={dismissLabel}
className={classNames(styles.dismiss, 'vkuiInternalAlert__dismiss')}
onClick={close}
hoverMode="opacity"
activeMode="opacity"
data-testid={dismissButtonTestId}
>
<Icon20Cancel />
</IconButton>
)}
</div>
<AlertActions
actions={actions}
actionsAlign={actionsAlign}
actionsLayout={actionsLayout}
renderAction={renderAction}
onItemClick={onItemClick}
/>
{isDismissButtonVisible && dismissButtonMode === 'outside' && (
<ModalDismissButton onClick={close} data-testid={dismissButtonTestId}>
{dismissLabel}
</ModalDismissButton>
)}
</div>
<AlertActions
actions={actions}
actionsAlign={actionsAlign}
actionsLayout={actionsLayout}
renderAction={renderAction}
onItemClick={onItemClick}
/>
{isDismissButtonVisible && dismissButtonMode === 'outside' && (
<ModalDismissButton onClick={close} data-testid={dismissButtonTestId}>
{dismissLabel}
</ModalDismissButton>
)}
</FocusTrap>
</PopoutWrapper>
</FocusTrap>
</PopoutWrapper>
</AppRootPortal>
);
};
119 changes: 62 additions & 57 deletions packages/vkui/src/components/ModalRoot/ModalRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type DOMProps, withDOM } from '../../lib/dom';
import { getNavId } from '../../lib/getNavId';
import { rubber } from '../../lib/touch';
import { warnOnce } from '../../lib/warnOnce';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
import { ConfigProviderContext } from '../ConfigProvider/ConfigProviderContext';
import { FocusTrap } from '../FocusTrap/FocusTrap';
import { type CustomTouchEvent, Touch } from '../Touch/Touch';
Expand Down Expand Up @@ -544,63 +545,67 @@ class ModalRootTouchComponent extends React.Component<
}

return (
<TouchRootContext.Provider value={true}>
<ModalRootContext.Provider value={this.modalRootContext}>
<Touch
className={classNames(
styles.host,
this.props.configProvider?.hasCustomPanelHeaderAfter &&
styles.hasCustomPanelHeaderAfterSlot,
touchDown && classNames(styles.touched, 'vkuiInternalModalRoot--touched'),
!!(enteringModal || exitingModal) &&
classNames(styles.switching, 'vkuiInternalModalRoot--switching'),
)}
onMove={this.onTouchMove}
onEnd={this.onTouchEnd}
onScroll={this.onScroll}
>
<div
data-testid={modalOverlayTestId}
className={styles.mask}
onClick={this.props.onExit}
ref={this.maskElementRef}
/>
<div className={styles.viewport} ref={this.viewportRef}>
{this.getModals().map((Modal) => {
const modalId = getNavId(Modal.props, warn);
const _modalState = this.props.getModalState(modalId);
if ((modalId !== activeModal && modalId !== exitingModal) || !_modalState) {
return null;
}
const modalState = { ..._modalState };

const isPage = modalState.type === 'page';
const key = `modal-${modalId}`;

return (
<FocusTrap
key={key}
onClose={this.props.onExit}
timeout={this.timeout}
className={classNames(
styles.modal,

dragging && 'vkuiInternalModalRoot__modal--dragging',

isPage && modalState.expandable && 'vkuiInternalModalRoot__modal--expandable',
isPage && modalState.collapsed && 'vkuiInternalModalRoot__modal--collapsed',
)}
autoFocus={false}
restoreFocus={false}
>
{Modal}
</FocusTrap>
);
})}
</div>
</Touch>
</ModalRootContext.Provider>
</TouchRootContext.Provider>
<AppRootPortal>
<TouchRootContext.Provider value={true}>
<ModalRootContext.Provider value={this.modalRootContext}>
<Touch
className={classNames(
styles.host,
this.props.configProvider?.hasCustomPanelHeaderAfter &&
styles.hasCustomPanelHeaderAfterSlot,
touchDown && classNames(styles.touched, 'vkuiInternalModalRoot--touched'),
!!(enteringModal || exitingModal) &&
classNames(styles.switching, 'vkuiInternalModalRoot--switching'),
)}
onMove={this.onTouchMove}
onEnd={this.onTouchEnd}
onScroll={this.onScroll}
>
<div
data-testid={modalOverlayTestId}
className={styles.mask}
onClick={this.props.onExit}
ref={this.maskElementRef}
/>
<div className={styles.viewport} ref={this.viewportRef}>
{this.getModals().map((Modal) => {
const modalId = getNavId(Modal.props, warn);
const _modalState = this.props.getModalState(modalId);
if ((modalId !== activeModal && modalId !== exitingModal) || !_modalState) {
return null;
}
const modalState = { ..._modalState };

const isPage = modalState.type === 'page';
const key = `modal-${modalId}`;

return (
<FocusTrap
key={key}
onClose={this.props.onExit}
timeout={this.timeout}
className={classNames(
styles.modal,

dragging && 'vkuiInternalModalRoot__modal--dragging',

isPage &&
modalState.expandable &&
'vkuiInternalModalRoot__modal--expandable',
isPage && modalState.collapsed && 'vkuiInternalModalRoot__modal--collapsed',
)}
autoFocus={false}
restoreFocus={false}
>
{Modal}
</FocusTrap>
);
})}
</div>
</Touch>
</ModalRootContext.Provider>
</TouchRootContext.Provider>
</AppRootPortal>
);
}
}
Expand Down
5 changes: 2 additions & 3 deletions packages/vkui/src/components/PopoutRoot/PopoutRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import { classNames } from '@vkontakte/vkjs';
import type { HTMLAttributesWithRootRef } from '../../types';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
import { RootComponent } from '../RootComponent/RootComponent';
import styles from './PopoutRoot.module.css';

Expand Down Expand Up @@ -42,10 +41,10 @@ export const PopoutRoot = ({
return (
<RootComponent {...restProps} baseClassName={styles.host}>
{children}
<AppRootPortal>
<div>
{!!popout && <PopoutRootPopout>{popout}</PopoutRootPopout>}
{!!modal && <PopoutRootModal>{modal}</PopoutRootModal>}
</AppRootPortal>
</div>
</RootComponent>
);
};
15 changes: 9 additions & 6 deletions packages/vkui/src/components/ScreenSpinner/ScreenSpinner.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import * as React from 'react';
import { AppRootPortal } from '../AppRoot/AppRootPortal';
import { useScrollLock } from '../AppRoot/ScrollContext';
import { PopoutWrapper } from '../PopoutWrapper/PopoutWrapper';
import { ScreenSpinnerContainer } from './ScreenSpinnerContainer';
Expand Down Expand Up @@ -31,12 +32,14 @@ export const ScreenSpinner: React.FC<ScreenSpinnerProps> & {
useScrollLock();

return (
<PopoutWrapper className={className} style={style} noBackground>
<ScreenSpinnerContainer state={state} mode={mode} caption={caption} customIcon={customIcon}>
<ScreenSpinnerLoader {...restProps} />
<ScreenSpinnerSwapIcon onClick={onClick} cancelLabel={cancelLabel} />
</ScreenSpinnerContainer>
</PopoutWrapper>
<AppRootPortal>
<PopoutWrapper className={className} style={style} noBackground>
<ScreenSpinnerContainer state={state} mode={mode} caption={caption} customIcon={customIcon}>
<ScreenSpinnerLoader {...restProps} />
<ScreenSpinnerSwapIcon onClick={onClick} cancelLabel={cancelLabel} />
</ScreenSpinnerContainer>
</PopoutWrapper>
</AppRootPortal>
);
};

Expand Down

0 comments on commit 2eced1a

Please sign in to comment.