import type React from 'react'; import { clearFix, genFocusStyle, resetComponent } from '../../style'; import { initFadeMotion, initZoomMotion } from '../../style/motion'; import type { AliasToken, FullToken, GenerateStyle } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'; /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken { // Component token here /** * @desc 顶部背景色 * @descEN Background color of header */ headerBg: string; /** * @desc 标题行高 * @descEN Line height of title */ titleLineHeight: number; /** * @desc 标题字体大小 * @descEN Font size of title */ titleFontSize: number; /** * @desc 标题字体颜色 * @descEN Font color of title */ titleColor: string; /** * @desc 内容区域背景色 * @descEN Background color of content */ contentBg: string; /** * @desc 底部区域背景色 * @descEN Background color of footer */ footerBg: string; } export interface ModalToken extends FullToken<'Modal'> { // Custom token here modalHeaderHeight: number; modalBodyPadding: number; modalHeaderPadding: string; modalHeaderBorderWidth: number; modalHeaderBorderStyle: string; modalHeaderBorderColorSplit: string; modalFooterBorderColorSplit: string; modalFooterBorderStyle: string; modalFooterPaddingVertical: number; modalFooterPaddingHorizontal: number; modalFooterBorderWidth: number; modalIconHoverColor: string; modalCloseIconColor: string; modalCloseBtnSize: number; modalConfirmIconSize: number; } function box(position: React.CSSProperties['position']): React.CSSProperties { return { position, top: 0, insetInlineEnd: 0, bottom: 0, insetInlineStart: 0, }; } export const genModalMaskStyle: GenerateStyle> = (token) => { const { componentCls, antCls } = token; return [ { [`${componentCls}-root`]: { [`${componentCls}${antCls}-zoom-enter, ${componentCls}${antCls}-zoom-appear`]: { // reset scale avoid mousePosition bug transform: 'none', opacity: 0, animationDuration: token.motionDurationSlow, // https://github.com/ant-design/ant-design/issues/11777 userSelect: 'none', }, // https://github.com/ant-design/ant-design/issues/37329 // https://github.com/ant-design/ant-design/issues/40272 [`${componentCls}${antCls}-zoom-leave ${componentCls}-content`]: { pointerEvents: 'none', }, [`${componentCls}-mask`]: { ...box('fixed'), zIndex: token.zIndexPopupBase, height: '100%', backgroundColor: token.colorBgMask, [`${componentCls}-hidden`]: { display: 'none', }, }, [`${componentCls}-wrap`]: { ...box('fixed'), overflow: 'auto', outline: 0, WebkitOverflowScrolling: 'touch', }, }, }, { [`${componentCls}-root`]: initFadeMotion(token) }, ]; }; const genModalStyle: GenerateStyle = (token) => { const { componentCls } = token; return [ // ======================== Root ========================= { [`${componentCls}-root`]: { [`${componentCls}-wrap`]: { zIndex: token.zIndexPopupBase, position: 'fixed', inset: 0, overflow: 'auto', outline: 0, WebkitOverflowScrolling: 'touch', }, [`${componentCls}-wrap-rtl`]: { direction: 'rtl', }, [`${componentCls}-centered`]: { textAlign: 'center', '&::before': { display: 'inline-block', width: 0, height: '100%', verticalAlign: 'middle', content: '""', }, [componentCls]: { top: 0, display: 'inline-block', paddingBottom: 0, textAlign: 'start', verticalAlign: 'middle', }, }, [`@media (max-width: ${token.screenSMMax})`]: { [componentCls]: { maxWidth: 'calc(100vw - 16px)', margin: `${token.marginXS} auto`, }, [`${componentCls}-centered`]: { [componentCls]: { flex: 1, }, }, }, }, }, // ======================== Modal ======================== { [componentCls]: { ...resetComponent(token), pointerEvents: 'none', position: 'relative', top: 100, width: 'auto', maxWidth: `calc(100vw - ${token.margin * 2}px)`, margin: '0 auto', paddingBottom: token.paddingLG, [`${componentCls}-title`]: { margin: 0, color: token.titleColor, fontWeight: token.fontWeightStrong, fontSize: token.titleFontSize, lineHeight: token.titleLineHeight, wordWrap: 'break-word', }, [`${componentCls}-content`]: { position: 'relative', backgroundColor: token.contentBg, backgroundClip: 'padding-box', border: 0, borderRadius: token.borderRadiusLG, boxShadow: token.boxShadow, pointerEvents: 'auto', padding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`, }, [`${componentCls}-close`]: { position: 'absolute', top: (token.modalHeaderHeight - token.modalCloseBtnSize) / 2, insetInlineEnd: (token.modalHeaderHeight - token.modalCloseBtnSize) / 2, zIndex: token.zIndexPopupBase + 10, padding: 0, color: token.modalCloseIconColor, fontWeight: token.fontWeightStrong, lineHeight: 1, textDecoration: 'none', background: 'transparent', borderRadius: token.borderRadiusSM, width: token.modalCloseBtnSize, height: token.modalCloseBtnSize, border: 0, outline: 0, cursor: 'pointer', transition: `color ${token.motionDurationMid}, background-color ${token.motionDurationMid}`, '&-x': { display: 'flex', fontSize: token.fontSizeLG, fontStyle: 'normal', lineHeight: `${token.modalCloseBtnSize}px`, justifyContent: 'center', textTransform: 'none', textRendering: 'auto', }, '&:hover': { color: token.modalIconHoverColor, backgroundColor: token.wireframe ? 'transparent' : token.colorFillContent, textDecoration: 'none', }, '&:active': { backgroundColor: token.wireframe ? 'transparent' : token.colorFillContentHover, }, ...genFocusStyle(token), }, [`${componentCls}-header`]: { color: token.colorText, background: token.headerBg, borderRadius: `${token.borderRadiusLG}px ${token.borderRadiusLG}px 0 0`, marginBottom: token.marginXS, }, [`${componentCls}-body`]: { fontSize: token.fontSize, lineHeight: token.lineHeight, wordWrap: 'break-word', }, [`${componentCls}-footer`]: { textAlign: 'end', background: token.footerBg, marginTop: token.marginSM, [`${token.antCls}-btn + ${token.antCls}-btn:not(${token.antCls}-dropdown-trigger)`]: { marginBottom: 0, marginInlineStart: token.marginXS, }, }, [`${componentCls}-open`]: { overflow: 'hidden', }, }, }, // ======================== Pure ========================= { [`${componentCls}-pure-panel`]: { top: 'auto', padding: 0, display: 'flex', flexDirection: 'column', [`${componentCls}-content, ${componentCls}-body, ${componentCls}-confirm-body-wrapper`]: { display: 'flex', flexDirection: 'column', flex: 'auto', }, [`${componentCls}-confirm-body`]: { marginBottom: 'auto', }, }, }, ]; }; const genModalConfirmStyle: GenerateStyle = (token) => { const { componentCls } = token; const confirmComponentCls = `${componentCls}-confirm`; return { [confirmComponentCls]: { '&-rtl': { direction: 'rtl', }, [`${token.antCls}-modal-header`]: { display: 'none', }, [`${confirmComponentCls}-body-wrapper`]: { ...clearFix(), }, [`${confirmComponentCls}-body`]: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', [`${confirmComponentCls}-title`]: { flex: '0 0 100%', display: 'block', // create BFC to avoid // https://user-images.githubusercontent.com/507615/37702510-ba844e06-2d2d-11e8-9b67-8e19be57f445.png overflow: 'hidden', color: token.colorTextHeading, fontWeight: token.fontWeightStrong, fontSize: token.titleFontSize, lineHeight: token.titleLineHeight, [`+ ${confirmComponentCls}-content`]: { marginBlockStart: token.marginXS, flexBasis: '100%', maxWidth: `calc(100% - ${token.modalConfirmIconSize + token.marginSM}px)`, }, }, [`${confirmComponentCls}-content`]: { color: token.colorText, fontSize: token.fontSize, }, [`> ${token.iconCls}`]: { flex: 'none', marginInlineEnd: token.marginSM, fontSize: token.modalConfirmIconSize, [`+ ${confirmComponentCls}-title`]: { flex: 1, }, // `content` after `icon` should set marginLeft [`+ ${confirmComponentCls}-title + ${confirmComponentCls}-content`]: { marginInlineStart: token.modalConfirmIconSize + token.marginSM, }, }, }, [`${confirmComponentCls}-btns`]: { textAlign: 'end', marginTop: token.marginSM, [`${token.antCls}-btn + ${token.antCls}-btn`]: { marginBottom: 0, marginInlineStart: token.marginXS, }, }, }, [`${confirmComponentCls}-error ${confirmComponentCls}-body > ${token.iconCls}`]: { color: token.colorError, }, [`${confirmComponentCls}-warning ${confirmComponentCls}-body > ${token.iconCls}, ${confirmComponentCls}-confirm ${confirmComponentCls}-body > ${token.iconCls}`]: { color: token.colorWarning, }, [`${confirmComponentCls}-info ${confirmComponentCls}-body > ${token.iconCls}`]: { color: token.colorInfo, }, [`${confirmComponentCls}-success ${confirmComponentCls}-body > ${token.iconCls}`]: { color: token.colorSuccess, }, }; }; const genRTLStyle: GenerateStyle = (token) => { const { componentCls } = token; return { [`${componentCls}-root`]: { [`${componentCls}-wrap-rtl`]: { direction: 'rtl', [`${componentCls}-confirm-body`]: { direction: 'rtl', }, }, }, }; }; const genWireframeStyle: GenerateStyle = (token) => { const { componentCls, antCls } = token; const confirmComponentCls = `${componentCls}-confirm`; return { [componentCls]: { [`${componentCls}-content`]: { padding: 0, }, [`${componentCls}-header`]: { padding: token.modalHeaderPadding, borderBottom: `${token.modalHeaderBorderWidth}px ${token.modalHeaderBorderStyle} ${token.modalHeaderBorderColorSplit}`, marginBottom: 0, }, [`${componentCls}-body`]: { padding: token.modalBodyPadding, }, [`${componentCls}-footer`]: { padding: `${token.modalFooterPaddingVertical}px ${token.modalFooterPaddingHorizontal}px`, borderTop: `${token.modalFooterBorderWidth}px ${token.modalFooterBorderStyle} ${token.modalFooterBorderColorSplit}`, borderRadius: `0 0 ${token.borderRadiusLG}px ${token.borderRadiusLG}px`, marginTop: 0, }, }, [confirmComponentCls]: { [`${antCls}-modal-body`]: { padding: `${token.padding * 2}px ${token.padding * 2}px ${token.paddingLG}px`, }, [`${confirmComponentCls}-body`]: { [`> ${token.iconCls}`]: { marginInlineEnd: token.margin, // `content` after `icon` should set marginLeft [`+ ${confirmComponentCls}-title + ${confirmComponentCls}-content`]: { marginInlineStart: token.modalConfirmIconSize + token.margin, }, }, }, [`${confirmComponentCls}-btns`]: { marginTop: token.marginLG, }, }, }; }; // ============================== Export ============================== export default genComponentStyleHook( 'Modal', (token) => { const headerPaddingVertical = token.padding; const headerFontSize = token.fontSizeHeading5; const headerLineHeight = token.lineHeightHeading5; const modalToken = mergeToken(token, { modalBodyPadding: token.paddingLG, modalHeaderPadding: `${headerPaddingVertical}px ${token.paddingLG}px`, modalHeaderBorderWidth: token.lineWidth, modalHeaderBorderStyle: token.lineType, modalHeaderBorderColorSplit: token.colorSplit, modalHeaderHeight: headerLineHeight * headerFontSize + headerPaddingVertical * 2, modalFooterBorderColorSplit: token.colorSplit, modalFooterBorderStyle: token.lineType, modalFooterPaddingVertical: token.paddingXS, modalFooterPaddingHorizontal: token.padding, modalFooterBorderWidth: token.lineWidth, modalIconHoverColor: token.colorIconHover, modalCloseIconColor: token.colorIcon, modalCloseBtnSize: token.fontSize * token.lineHeight, modalConfirmIconSize: token.fontSize * token.lineHeight, }); return [ genModalStyle(modalToken), genModalConfirmStyle(modalToken), genRTLStyle(modalToken), genModalMaskStyle(modalToken), token.wireframe && genWireframeStyle(modalToken), initZoomMotion(modalToken, 'zoom'), ]; }, (token) => ({ footerBg: 'transparent', headerBg: token.colorBgElevated, titleLineHeight: token.lineHeightHeading5, titleFontSize: token.fontSizeHeading5, contentBg: token.colorBgElevated, titleColor: token.colorTextHeading, }), );