Skip to content

Commit

Permalink
fix(plasma-core): refactor in PopupBase/ModalBase
Browse files Browse the repository at this point in the history
  • Loading branch information
kayman233 committed Sep 29, 2023
1 parent da23685 commit 9eb47ae
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 94 deletions.
12 changes: 9 additions & 3 deletions packages/plasma-b2c/api/plasma-b2c.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ import { PopoverPlacement } from '@salutejs/plasma-hope';
import { PopoverProps } from '@salutejs/plasma-hope';
import { Popup } from '@salutejs/plasma-hope';
import { PopupBase } from '@salutejs/plasma-hope';
import { PopupBaseContext } from '@salutejs/plasma-hope';
import { PopupBasePlacement } from '@salutejs/plasma-hope';
import { PopupBaseProps } from '@salutejs/plasma-hope';
import { PopupBaseProvider } from '@salutejs/plasma-hope';
import { PopupContextType } from '@salutejs/plasma-hope';
import { PopupInfo } from '@salutejs/plasma-hope';
import { PopupProps } from '@salutejs/plasma-hope';
import { PreviewGallery } from '@salutejs/plasma-hope';
import { PreviewGalleryItemProps } from '@salutejs/plasma-hope';
Expand Down Expand Up @@ -264,6 +265,7 @@ import { useDebouncedFunction } from '@salutejs/plasma-core';
import { useFocusTrap } from '@salutejs/plasma-hope';
import { useForkRef } from '@salutejs/plasma-core';
import { useIsomorphicLayoutEffect } from '@salutejs/plasma-core';
import { usePopupBaseContext } from '@salutejs/plasma-hope';
import { useToast } from '@salutejs/plasma-hope';
import { ValidationResult } from '@salutejs/plasma-hope';
import { View } from '@salutejs/plasma-core';
Expand Down Expand Up @@ -651,14 +653,16 @@ export { Popup }

export { PopupBase }

export { PopupBaseContext }

export { PopupBasePlacement }

export { PopupBaseProps }

export { PopupBaseProvider }

export { PopupContextType }

export { PopupInfo }

export { PopupProps }

export { PreviewGallery }
Expand Down Expand Up @@ -873,6 +877,8 @@ export { useForkRef }

export { useIsomorphicLayoutEffect }

export { usePopupBaseContext }

export { useToast }

export { ValidationResult }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const StyledWrapper = styled.div`
height: 1200px;
`;

// TODO: новый отдельный оверлей #778
const ModalOverlayVariables = createGlobalStyle`
body {
--plasma-modal-blur-overlay-color: ${darkOverlayBlur};
Expand Down
3 changes: 2 additions & 1 deletion packages/plasma-b2c/src/components/PopupBase/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { PopupBaseProvider, PopupBaseContext } from '@salutejs/plasma-hope';
export { PopupBaseProvider, usePopupBaseContext } from '@salutejs/plasma-hope';
export type { PopupInfo, PopupContextType } from '@salutejs/plasma-hope';

export { PopupBase } from '@salutejs/plasma-hope';
export type { PopupBaseProps, PopupBasePlacement } from '@salutejs/plasma-hope';
28 changes: 22 additions & 6 deletions packages/plasma-core/api/plasma-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,11 +859,6 @@ export const Popup: React_2.NamedExoticComponent<PopupProps & React_2.RefAttribu
// @public
export const PopupBase: React_2.ForwardRefExoticComponent<PopupBaseProps & React_2.RefAttributes<HTMLDivElement>>;

// Warning: (ae-forgotten-export) The symbol "PopupBaseController" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export const PopupBaseContext: React_2.Context<PopupBaseController>;

// Warning: (ae-forgotten-export) The symbol "BasicPopupBasePlacement" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "MixedPopupBasePlacement" needs to be exported by the entry point index.d.ts
//
Expand All @@ -877,9 +872,9 @@ export interface PopupBaseProps extends React_2.HTMLAttributes<HTMLDivElement> {
isOpen: boolean;
// (undocumented)
offset?: [number | string, number | string];
overlay?: React_2.ReactNode;
// (undocumented)
placement?: PopupBasePlacement;
// Warning: (ae-forgotten-export) The symbol "PopupInfo" needs to be exported by the entry point index.d.ts
popupInfo?: PopupInfo;
zIndex?: string;
}
Expand All @@ -889,6 +884,24 @@ export const PopupBaseProvider: React_2.FC<{
children: ReactNode;
}>;

// @public (undocumented)
export interface PopupContextType {
// (undocumented)
items: PopupInfo[];
// (undocumented)
register: (info: PopupInfo) => void;
// (undocumented)
unregister: (id: string) => void;
}

// @public (undocumented)
export interface PopupInfo {
// (undocumented)
id: string;
// (undocumented)
info?: Object;
}

// @public (undocumented)
export interface PopupProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
Expand Down Expand Up @@ -1334,6 +1347,9 @@ export const usePaginationDots: ({ items, index, visibleItems }: SmartPagination
activeId: string | number;
};

// @public (undocumented)
export const usePopupBaseContext: () => PopupContextType;

// @public
export const useResizeObserver: <T extends HTMLElement>(ref: MutableRefObject<T | null>, callback: (element: T) => void) => void;

Expand Down
53 changes: 28 additions & 25 deletions packages/plasma-core/src/components/ModalBase/ModalBase.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useCallback, useContext, FC, useEffect } from 'react';
import React, { useCallback, FC, useEffect } from 'react';
import styled, { createGlobalStyle, css } from 'styled-components';

import { useFocusTrap, useUniqId } from '../../hooks';
import { PopupBaseContext, PopupInfo } from '../PopupBase/PopupBaseContext';
import { usePopupBaseContext } from '../PopupBase/PopupBaseContext';
import { DEFAULT_Z_INDEX, PopupBase, PopupBaseProps } from '../PopupBase/PopupBase';

import { ModalInfo, getIdLastModal } from './ModalBaseContext';

export interface ModalBaseProps extends Omit<PopupBaseProps, 'frame'> {
/**
* Нужно ли применять blur для подложки.
Expand Down Expand Up @@ -41,6 +43,7 @@ export interface ModalBaseProps extends Omit<PopupBaseProps, 'frame'> {
onClose?: () => void;
}

// TODO: новый отдельный оверлей #778
const StyledOverlay = styled.div<{ transparent?: boolean; $withBlur?: boolean; clickable?: boolean; zIndex?: string }>`
position: absolute;
Expand Down Expand Up @@ -97,11 +100,11 @@ export const ModalBase: FC<ModalBaseProps> = ({
}) => {
const uniqId = useUniqId();
const innerId = id || uniqId;
const controller = useContext(PopupBaseContext);
const popupController = usePopupBaseContext();

const trapRef = useFocusTrap(true, initialFocusRef, focusAfterRef);

const onOverlayClickCallback = useCallback(
const onOverlayKeyDown = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (!closeOnOverlayClick) {
return;
Expand All @@ -112,62 +115,62 @@ export const ModalBase: FC<ModalBaseProps> = ({
return;
}

onClose?.();
if (onClose) {
onClose();
}
},
[closeOnOverlayClick, onOverlayClick, onClose],
);

// Вызов обработчика текущего окна
const onOverlayKeyDown = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
controller.callCurrentModalClose(event);
},
[controller.items],
);

// При ESC закрывает текущее окно, если это возможно
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
if (closeOnEsc && event.keyCode === ESCAPE_KEYCODE && controller.getIdLastModal() === innerId) {
if (closeOnEsc && event.keyCode === ESCAPE_KEYCODE && getIdLastModal(popupController.items) === innerId) {
if (onEscKeyDown) {
onEscKeyDown(event);
return;
}

onClose?.();
if (onClose) {
onClose();
}
}
},
[onClose, onEscKeyDown, controller.items, closeOnEsc],
[onClose, onEscKeyDown, popupController, closeOnEsc],
);

useEffect(() => {
window.addEventListener('keydown', onKeyDown);
return () => {
window.removeEventListener('keydown', onKeyDown);
};
}, [onClose, onEscKeyDown, controller.items, closeOnEsc]);
}, [onClose, onEscKeyDown, popupController, closeOnEsc]);

const modalInfo: PopupInfo = {
const modalInfo: ModalInfo = {
id: innerId,
isModal: true,
onOverlayClick: onOverlayClickCallback,
info: {
isModal: true,
},
};

if (!isOpen) {
return null;
}

return (
<>
<NoScroll />
<StyledOverlay clickable={closeOnOverlayClick} onClick={onOverlayKeyDown} $withBlur={withBlur} />
<PopupBase
id={innerId}
frame="document"
isOpen={isOpen}
placement={placement}
ref={trapRef}
popupInfo={modalInfo}
overlay={
<StyledOverlay
transparent={getIdLastModal(popupController.items) !== innerId}
clickable={closeOnOverlayClick}
onClick={onOverlayKeyDown}
$withBlur={withBlur}
/>
}
{...rest}
>
{children}
Expand Down
21 changes: 21 additions & 0 deletions packages/plasma-core/src/components/ModalBase/ModalBaseContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PopupInfo } from '../PopupBase/PopupBaseContext';

export interface ModalInfo extends PopupInfo {
id: string;
info?: {
isModal?: true;
};
}

/**
* Взаимодействие с модальными оконами.
*/
const getLastModal = (items: ModalInfo[]) => {
const modals = items.filter((item: ModalInfo) => item?.info?.isModal);
const lastModal = modals && (modals[modals.length - 1] as ModalInfo);
return lastModal;
};

export const getIdLastModal = (items: ModalInfo[]) => {
return getLastModal(items)?.id;
};
52 changes: 35 additions & 17 deletions packages/plasma-core/src/components/PopupBase/PopupBase.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useEffect, useRef, useState, useContext } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import styled, { css } from 'styled-components';

import { useUniqId } from '../../hooks';

import { PopupBaseContext, POPOVER_PORTAL_ID, PopupInfo } from './PopupBaseContext';
import { POPOVER_PORTAL_ID, PopupInfo, usePopupBaseContext } from './PopupBaseContext';

type BasicPopupBasePlacement = 'center' | 'top' | 'bottom' | 'right' | 'left';
type MixedPopupBasePlacement = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
Expand Down Expand Up @@ -33,6 +33,10 @@ export interface PopupBaseProps extends React.HTMLAttributes<HTMLDivElement> {
* Содержимое PopupBase.
*/
children?: React.ReactNode;
/**
* Соседний элемент для окна в портале.
*/
overlay?: React.ReactNode;
/**
* Значение z-index для PopupBase.
*/
Expand Down Expand Up @@ -138,13 +142,19 @@ const PopupBaseRoot = styled.div<HidingProps & PopupBaseRootProps>`
*/

export const PopupBase = React.forwardRef<HTMLDivElement, PopupBaseProps>(
({ id, isOpen, placement, offset, frame = 'document', children, role, zIndex, popupInfo, ...rest }, ref) => {
(
{ id, isOpen, placement, offset, frame = 'document', children, overlay, role, zIndex, popupInfo, ...rest },
ref,
) => {
// Внутренее состояние, необходимое для правильного отображения вложенных окон, а также для анимации
const [isClosed, setClosed] = useState(!isOpen);

const uniqId = useUniqId();
const innerId = id || uniqId;

const portalRef = useRef<HTMLElement | null>(null);

const controller = useContext(PopupBaseContext);
const popupController = usePopupBaseContext();

const [, forceRender] = useState(false);

Expand All @@ -168,33 +178,41 @@ export const PopupBase = React.forwardRef<HTMLDivElement, PopupBaseProps>(
* отобразился после записи DOM элемента в portalRef.current
*/
forceRender(true);

return () => {
controller.unregister(innerId);
};
}, [controller, innerId, zIndex]);
}, []);

useEffect(() => {
// сначала добавление/удаление из контекста
if (isOpen) {
controller.register({ id: innerId, ...popupInfo });
popupController.register({ id: innerId, ...popupInfo });
} else {
controller.unregister(innerId);
popupController.unregister(innerId);
}
// затем отображение
setClosed(!isOpen);
}, [isOpen]);

if (!isOpen) {
if (isClosed) {
return null;
}

return (
<>
{portalRef.current &&
ReactDOM.createPortal(
<PopupBaseRoot ref={ref} placement={placement} frame={frame} offset={offset} zIndex={zIndex}>
<PopupBaseView {...rest} role={role}>
{children}
</PopupBaseView>
</PopupBaseRoot>,
<>
{overlay}
<PopupBaseRoot
ref={ref}
placement={placement}
frame={frame}
offset={offset}
zIndex={zIndex}
>
<PopupBaseView {...rest} role={role}>
{children}
</PopupBaseView>
</PopupBaseRoot>
</>,
portalRef.current,
)}
</>
Expand Down
Loading

0 comments on commit 9eb47ae

Please sign in to comment.