mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 13:29:02 +08:00
feat: replace modal with new design (#4324)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { ModalProps } from '../modal';
|
||||
import { Modal, ModalCloseButton } from '../modal';
|
||||
import {
|
||||
StyledColumnButtonWrapper,
|
||||
StyledConfirmContent,
|
||||
StyledConfirmTitle,
|
||||
StyledModalWrapper,
|
||||
StyledRowButtonWrapper,
|
||||
} from './styles';
|
||||
|
||||
export type ConfirmProps = {
|
||||
title?: string;
|
||||
content?: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
// TODO: Confirm button's color should depend on confirm type
|
||||
confirmType?: 'primary' | 'warning' | 'error';
|
||||
buttonDirection?: 'row' | 'column';
|
||||
onConfirm?: () => void;
|
||||
onCancel?: () => void;
|
||||
cancelButtonTestId?: string;
|
||||
confirmButtonTestId?: string;
|
||||
} & Omit<ModalProps, 'children'>;
|
||||
|
||||
export const Confirm = ({
|
||||
title,
|
||||
content,
|
||||
confirmText,
|
||||
confirmType,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
buttonDirection = 'row',
|
||||
cancelText = 'Cancel',
|
||||
open,
|
||||
cancelButtonTestId = '',
|
||||
confirmButtonTestId = '',
|
||||
}: ConfirmProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const cancelText_ = useMemo<string>(() => {
|
||||
return cancelText === 'Cancel'
|
||||
? t['com.affine.confirmModal.button.cancel']()
|
||||
: cancelText;
|
||||
}, [cancelText, t]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel?.();
|
||||
}, [onCancel]);
|
||||
const handleConfirm = useCallback(() => {
|
||||
onConfirm?.();
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal open={open} disablePortal={false}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={handleCancel} />
|
||||
<StyledConfirmTitle>{title}</StyledConfirmTitle>
|
||||
<StyledConfirmContent>{content}</StyledConfirmContent>
|
||||
{buttonDirection === 'row' ? (
|
||||
<StyledRowButtonWrapper>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
size="large"
|
||||
style={{ marginRight: '24px' }}
|
||||
data-testid={cancelButtonTestId}
|
||||
>
|
||||
{cancelText_}
|
||||
</Button>
|
||||
<Button
|
||||
type={confirmType}
|
||||
onClick={handleConfirm}
|
||||
size="large"
|
||||
data-testid={confirmButtonTestId}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</StyledRowButtonWrapper>
|
||||
) : (
|
||||
<StyledColumnButtonWrapper>
|
||||
<Button
|
||||
type={confirmType}
|
||||
onClick={handleConfirm}
|
||||
style={{ width: '284px', height: '38px', textAlign: 'center' }}
|
||||
data-testid={confirmButtonTestId}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
width: '284px',
|
||||
height: '38px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
data-testid={cancelButtonTestId}
|
||||
>
|
||||
{cancelText_}
|
||||
</Button>
|
||||
</StyledColumnButtonWrapper>
|
||||
)}
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Confirm;
|
||||
@@ -1 +0,0 @@
|
||||
export * from './confirm';
|
||||
@@ -1,46 +0,0 @@
|
||||
import { displayFlex, styled } from '../../styles';
|
||||
import { ModalWrapper } from '../modal';
|
||||
|
||||
export const StyledModalWrapper = styled(ModalWrapper)(() => {
|
||||
return {
|
||||
minWidth: '460px',
|
||||
maxWidth: '560px',
|
||||
maxHeight: '292px',
|
||||
padding: '44px 84px 32px 84px',
|
||||
overflow: 'auto',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledConfirmTitle = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
fontWeight: 600,
|
||||
textAlign: 'center',
|
||||
lineHeight: '28px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledConfirmContent = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
textAlign: 'center',
|
||||
marginTop: '12px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: '26px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledColumnButtonWrapper = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
marginTop: '32px',
|
||||
};
|
||||
});
|
||||
export const StyledRowButtonWrapper = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'row',
|
||||
marginTop: '32px',
|
||||
};
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button, type ButtonType } from '@toeverything/components/button';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { Modal, type ModalProps } from './modal';
|
||||
import { ModalCloseButton } from './modal-close-button';
|
||||
import { ModalWrapper } from './modal-wrapper';
|
||||
import {
|
||||
StyledModalContent,
|
||||
StyledModalFooter,
|
||||
StyledModalTitle,
|
||||
} from './styles';
|
||||
|
||||
export interface BaseModalProps
|
||||
extends Omit<ModalProps, 'onClose' | 'children'> {
|
||||
title?: string;
|
||||
content?: string;
|
||||
confirmText?: string;
|
||||
confirmType?: ButtonType;
|
||||
onClose: () => void;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmModal = ({
|
||||
open,
|
||||
onClose,
|
||||
confirmText,
|
||||
confirmType = 'primary',
|
||||
onCancel,
|
||||
onConfirm,
|
||||
title,
|
||||
content,
|
||||
}: BaseModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="auth-modal"
|
||||
>
|
||||
<ModalWrapper
|
||||
width={480}
|
||||
minHeight={194}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
padding: '20px 24px 0',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={22} right={20} onClick={handleClose} />
|
||||
<StyledModalTitle>{title}</StyledModalTitle>
|
||||
<StyledModalContent>{content}</StyledModalContent>
|
||||
<StyledModalFooter>
|
||||
<Button onClick={onCancel} style={{ marginRight: 20 }}>
|
||||
{t['com.affine.confirmModal.button.cancel']()}
|
||||
</Button>
|
||||
<Button type={confirmType} onClick={onConfirm}>
|
||||
{confirmText || t['Confirm']()}
|
||||
</Button>
|
||||
</StyledModalFooter>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import Modal from './modal';
|
||||
|
||||
export * from './confirm-modal';
|
||||
export * from './modal';
|
||||
export * from './modal-close-button';
|
||||
export * from './modal-wrapper';
|
||||
|
||||
export default Modal;
|
||||
@@ -1,39 +0,0 @@
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
IconButton,
|
||||
type IconButtonProps,
|
||||
} from '@toeverything/components/button';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export type ModalCloseButtonProps = {
|
||||
top?: number;
|
||||
right?: number;
|
||||
absolute?: boolean;
|
||||
} & Omit<IconButtonProps, 'children'> &
|
||||
HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const ModalCloseButton = ({
|
||||
absolute = true,
|
||||
right,
|
||||
top,
|
||||
...props
|
||||
}: ModalCloseButtonProps) => {
|
||||
return (
|
||||
<IconButton
|
||||
style={
|
||||
absolute
|
||||
? {
|
||||
position: 'absolute',
|
||||
top: top ?? 24,
|
||||
right: right ?? 40,
|
||||
zIndex: 1,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
data-testid="modal-close-button"
|
||||
{...props}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const ModalWrapper = styled('div')<{
|
||||
width?: CSSProperties['width'];
|
||||
height?: CSSProperties['height'];
|
||||
minHeight?: CSSProperties['minHeight'];
|
||||
}>(({ width, height, minHeight }) => {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
minHeight,
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
boxShadow: 'var(--affine-shadow-3)',
|
||||
borderRadius: '12px',
|
||||
position: 'relative',
|
||||
maxHeight: 'calc(100vh - 32px)',
|
||||
};
|
||||
});
|
||||
|
||||
export default ModalWrapper;
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { ModalProps as ModalUnstyledOwnProps } from '@mui/base/Modal';
|
||||
import Fade from '@mui/material/Fade';
|
||||
|
||||
import { StyledBackdrop, StyledModal } from './styles';
|
||||
const Backdrop = ({
|
||||
open,
|
||||
...other
|
||||
}: {
|
||||
open?: boolean;
|
||||
className: string;
|
||||
}) => {
|
||||
return (
|
||||
<Fade in={open}>
|
||||
<StyledBackdrop {...other} />
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export type ModalProps = {
|
||||
wrapperPosition?: ['top' | 'bottom' | 'center', 'left' | 'right' | 'center'];
|
||||
} & ModalUnstyledOwnProps;
|
||||
|
||||
const transformConfig = {
|
||||
top: 'flex-start',
|
||||
bottom: 'flex-end',
|
||||
center: 'center',
|
||||
left: 'flex-start',
|
||||
right: 'flex-end',
|
||||
};
|
||||
|
||||
export const Modal = (props: ModalProps) => {
|
||||
const {
|
||||
wrapperPosition = ['center', 'center'],
|
||||
open,
|
||||
children,
|
||||
...otherProps
|
||||
} = props;
|
||||
const [vertical, horizontal] = wrapperPosition;
|
||||
// Fixme: This is a workaround for Mui bug
|
||||
// Refs: https://github.com/mui/material-ui/issues/33748
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledModal
|
||||
{...otherProps}
|
||||
open={open}
|
||||
slots={{ backdrop: Backdrop }}
|
||||
alignItems={transformConfig[vertical]}
|
||||
justifyContent={transformConfig[horizontal]}
|
||||
disableEnforceFocus
|
||||
>
|
||||
<Fade in={open}>{children}</Fade>
|
||||
</StyledModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Modal as ModalUnstyled } from '@mui/base/Modal';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const StyledBackdrop = styled('div')(() => {
|
||||
return {
|
||||
zIndex: '-1',
|
||||
position: 'fixed',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
left: '0',
|
||||
backgroundColor: 'var(--affine-background-modal-color)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModal = styled(ModalUnstyled, {
|
||||
shouldForwardProp: prop => {
|
||||
return !['justifyContent', 'alignItems'].includes(prop as string);
|
||||
},
|
||||
})<{
|
||||
alignItems: CSSProperties['alignItems'];
|
||||
justifyContent: CSSProperties['justifyContent'];
|
||||
}>(({ alignItems, justifyContent }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
alignItems,
|
||||
justifyContent,
|
||||
position: 'fixed',
|
||||
left: '0',
|
||||
top: '0',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
'*': {
|
||||
WebkitTapHighlightColor: 'transparent',
|
||||
outline: 'none',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(() => {
|
||||
return {
|
||||
marginTop: 40,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalTitle = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h-6)',
|
||||
};
|
||||
});
|
||||
export const StyledModalContent = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '24px',
|
||||
marginTop: '12px',
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user