Персист модалок: черновики, восстановление после F5, предупреждение при выходе #30

Merged
gorbenko.pavel merged 7 commits from feature/form-drafts into main 2026-10-19 04:46:16 +00:00
7 changed files with 43 additions and 8 deletions
Showing only changes of commit b94e6e60f5 - Show all commits
@@ -1,6 +1,7 @@
import * as React from 'react';
import styles from './Modal.module.scss';
import { Button } from './Button';
import { ModalOverlay } from './ModalOverlay';
import { texts } from '../../texts';
export interface IConfirmDialogProps {
@@ -20,7 +21,7 @@ export const ConfirmDialog: React.FC<IConfirmDialogProps> = ({
onCancel,
busy
}) => (
<div className={styles.overlay} onClick={e => { if (e.target === e.currentTarget) onCancel(); }}>
<ModalOverlay onBackdropClick={onCancel}>
<div className={`${styles.box} ${styles.narrow}`}>
<div className={styles.title}>{title}</div>
<p className={styles.text}>{text}</p>
@@ -29,5 +30,5 @@ export const ConfirmDialog: React.FC<IConfirmDialogProps> = ({
<Button variant="danger" icon="trash" onClick={onConfirm} disabled={busy}>{confirmLabel}</Button>
</div>
</div>
</div>
</ModalOverlay>
);
@@ -7,9 +7,17 @@
display: flex;
align-items: center;
justify-content: center;
z-index: 300;
// Выше шапки SharePoint, но ниже слоёв Fluent UI (1000000). Оверлей рендерится
// порталом в body (см. ModalOverlay), поэтому сравнивается с шапкой напрямую.
z-index: 100000;
backdrop-filter: blur(6px);
padding: 20px;
// Портал вне корня веб-части — шрифт и метрики .shell (AdminApp.module.scss)
// сюда не наследуются, задаём заново.
font-family: $font-sans;
font-size: $fs-body;
line-height: 1.55;
box-sizing: border-box;
}
.box {
@@ -1,6 +1,7 @@
import * as React from 'react';
import styles from './Modal.module.scss';
import { IconButton } from './IconButton';
import { ModalOverlay } from './ModalOverlay';
import { texts } from '../../texts';
export interface IModalProps {
@@ -11,8 +12,7 @@ export interface IModalProps {
}
export const Modal: React.FC<IModalProps> = ({ title, onClose, footer, children }) => (
// Клик по подложке закрывает окно, клик внутри — нет (проверяем сам currentTarget).
<div className={styles.overlay} onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
<ModalOverlay onBackdropClick={onClose}>
<div className={styles.box}>
<div className={styles.header}>
<div className={styles.title}>{title}</div>
@@ -21,5 +21,5 @@ export const Modal: React.FC<IModalProps> = ({ title, onClose, footer, children
<div className={styles.body}>{children}</div>
{footer && <div className={styles.footer}>{footer}</div>}
</div>
</div>
</ModalOverlay>
);
@@ -0,0 +1,25 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import styles from './Modal.module.scss';
export interface IModalOverlayProps {
/** Клик по подложке (не по содержимому окна). */
onBackdropClick: () => void;
children: React.ReactNode;
}
/**
* Затемнение под модалкой — порталом в document.body, а не внутри веб-части.
* На живой странице SharePoint канва веб-части лежит в своём контексте наложения
* ниже шапки (suite bar), и position: fixed-оверлей изнутри неё оказывался ПОД
* шапкой: верх окна, центрированного по высоте экрана, уезжал за неё. В workbench
* шапки нет — там было незаметно.
*/
export const ModalOverlay: React.FC<IModalOverlayProps> = ({ onBackdropClick, children }) =>
ReactDOM.createPortal(
// Клик по подложке закрывает окно, клик внутри — нет (проверяем сам currentTarget).
<div className={styles.overlay} onClick={e => { if (e.target === e.currentTarget) onBackdropClick(); }}>
{children}
</div>,
document.body
);
@@ -1,6 +1,6 @@
@import '../../../../styles/design-tokens';
// Ниже Modal.module.scss/.overlay (z-index 300) — диалоги, которые может
// Ниже Modal.module.scss/.overlay (z-index 100000) — диалоги, которые может
// открыть сама длительная операция (например, разрешение конфликта), должны
// быть ПОВЕРХ этой подложки, а не под ней.
.overlay {
@@ -73,7 +73,7 @@
box-shadow: $sh-modal;
border: 1px solid $gray-200;
padding: 6px;
z-index: 400;
z-index: 100001; // над оверлеем модалки (Modal.module.scss)
max-height: 320px;
display: flex;
flex-direction: column;
@@ -17,6 +17,7 @@ export * from './PrimaryCell';
export * from './ActionsCell';
export * from './CellNote';
export * from './Modal';
export * from './ModalOverlay';
export * from './ConfirmDialog';
export * from './FormModal';
export * from './FormGrid';