mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
feat: Modal width
support responsive size (#51653)
Some checks failed
Publish Any Commit / build (push) Has been cancelled
🔀 Sync mirror to Gitee / mirror (push) Has been cancelled
✅ test / lint (push) Has been cancelled
✅ test / test-react-legacy (16, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (16, 2/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 2/2) (push) Has been cancelled
✅ test / test-node (push) Has been cancelled
✅ test / test-react-latest (dom, 1/2) (push) Has been cancelled
✅ test / test-react-latest (dom, 2/2) (push) Has been cancelled
✅ test / build (push) Has been cancelled
✅ test / test lib/es module (es, 1/2) (push) Has been cancelled
✅ test / test lib/es module (es, 2/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 1/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 2/2) (push) Has been cancelled
👁️ Visual Regression Persist Start / test image (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 2/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Has been cancelled
✅ test / test-coverage (push) Has been cancelled
Some checks failed
Publish Any Commit / build (push) Has been cancelled
🔀 Sync mirror to Gitee / mirror (push) Has been cancelled
✅ test / lint (push) Has been cancelled
✅ test / test-react-legacy (16, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (16, 2/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 2/2) (push) Has been cancelled
✅ test / test-node (push) Has been cancelled
✅ test / test-react-latest (dom, 1/2) (push) Has been cancelled
✅ test / test-react-latest (dom, 2/2) (push) Has been cancelled
✅ test / build (push) Has been cancelled
✅ test / test lib/es module (es, 1/2) (push) Has been cancelled
✅ test / test lib/es module (es, 2/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 1/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 2/2) (push) Has been cancelled
👁️ Visual Regression Persist Start / test image (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 2/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Has been cancelled
✅ test / test-coverage (push) Has been cancelled
* feat: Modal support responsive width * docs: update doc * test: update snapshot
This commit is contained in:
parent
1b8e956a62
commit
fdb07d1864
@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { Breakpoint } from '../_util/responsiveObserver';
|
||||
import type { LiteralUnion } from '../_util/type';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import RowContext from './RowContext';
|
||||
@ -20,19 +21,15 @@ export interface ColSize {
|
||||
pull?: ColSpanType;
|
||||
}
|
||||
|
||||
export interface ColProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface ColProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
Partial<Record<Breakpoint, ColSpanType | ColSize>> {
|
||||
flex?: FlexType;
|
||||
span?: ColSpanType;
|
||||
order?: ColSpanType;
|
||||
offset?: ColSpanType;
|
||||
push?: ColSpanType;
|
||||
pull?: ColSpanType;
|
||||
xs?: ColSpanType | ColSize;
|
||||
sm?: ColSpanType | ColSize;
|
||||
md?: ColSpanType | ColSize;
|
||||
lg?: ColSpanType | ColSize;
|
||||
xl?: ColSpanType | ColSize;
|
||||
xxl?: ColSpanType | ColSize;
|
||||
prefixCls?: string;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import type { AliasToken, FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
|
||||
// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default
|
||||
@ -183,26 +183,34 @@ export const prepareColComponentToken: GetDefaultToken<'Grid'> = () => ({});
|
||||
// ============================== Export ==============================
|
||||
export const useRowStyle = genStyleHooks('Grid', genGridRowStyle, prepareRowComponentToken);
|
||||
|
||||
export const getMediaSize = (token: AliasToken) => {
|
||||
const mediaSizesMap = {
|
||||
xs: token.screenXSMin,
|
||||
sm: token.screenSMMin,
|
||||
md: token.screenMDMin,
|
||||
lg: token.screenLGMin,
|
||||
xl: token.screenXLMin,
|
||||
xxl: token.screenXXLMin,
|
||||
} as const;
|
||||
|
||||
return mediaSizesMap;
|
||||
};
|
||||
|
||||
export const useColStyle = genStyleHooks(
|
||||
'Grid',
|
||||
(token) => {
|
||||
const gridToken: GridColToken = mergeToken<GridColToken>(token, {
|
||||
gridColumns: 24, // Row is divided into 24 parts in Grid
|
||||
});
|
||||
const gridMediaSizesMap = {
|
||||
'-sm': gridToken.screenSMMin,
|
||||
'-md': gridToken.screenMDMin,
|
||||
'-lg': gridToken.screenLGMin,
|
||||
'-xl': gridToken.screenXLMin,
|
||||
'-xxl': gridToken.screenXXLMin,
|
||||
} as const;
|
||||
type GridMediaSize = keyof typeof gridMediaSizesMap;
|
||||
const gridMediaSizesMap: Record<string, number> = getMediaSize(gridToken);
|
||||
delete gridMediaSizesMap.xs;
|
||||
|
||||
return [
|
||||
genGridColStyle(gridToken),
|
||||
genGridStyle(gridToken, ''),
|
||||
genGridStyle(gridToken, '-xs'),
|
||||
Object.keys(gridMediaSizesMap)
|
||||
.map((key) => genGridMediaStyle(gridToken, gridMediaSizesMap[key as GridMediaSize], key))
|
||||
.map((key) => genGridMediaStyle(gridToken, gridMediaSizesMap[key], `-${key}`))
|
||||
.reduce((pre, cur) => ({ ...pre, ...cur }), {}),
|
||||
];
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import ContextIsolator from '../_util/ContextIsolator';
|
||||
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
|
||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import { Breakpoint } from '../_util/responsiveObserver';
|
||||
import { canUseDocElement } from '../_util/styleChecker';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import zIndexContext from '../_util/zindexContext';
|
||||
@ -124,12 +125,36 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
// ============================ zIndex ============================
|
||||
const [zIndex, contextZIndex] = useZIndex('Modal', restProps.zIndex);
|
||||
|
||||
// =========================== Width ============================
|
||||
const [numWidth, responsiveWidth] = React.useMemo<
|
||||
[string | number | undefined, Partial<Record<Breakpoint, string | number>> | undefined]
|
||||
>(() => {
|
||||
if (width && typeof width === 'object') {
|
||||
return [undefined, width];
|
||||
}
|
||||
return [width, undefined];
|
||||
}, [width]);
|
||||
|
||||
const responsiveWidthVars = React.useMemo(() => {
|
||||
const vars: Record<string, string> = {};
|
||||
if (responsiveWidth) {
|
||||
Object.keys(responsiveWidth).forEach((breakpoint) => {
|
||||
const breakpointWidth = responsiveWidth[breakpoint as Breakpoint];
|
||||
if (breakpointWidth !== undefined) {
|
||||
vars[`--${prefixCls}-${breakpoint}-width`] =
|
||||
typeof breakpointWidth === 'number' ? `${breakpointWidth}px` : breakpointWidth;
|
||||
}
|
||||
});
|
||||
}
|
||||
return vars;
|
||||
}, [responsiveWidth]);
|
||||
|
||||
// =========================== Render ===========================
|
||||
return wrapCSSVar(
|
||||
<ContextIsolator form space>
|
||||
<zIndexContext.Provider value={contextZIndex}>
|
||||
<Dialog
|
||||
width={width}
|
||||
width={numWidth}
|
||||
{...restProps}
|
||||
zIndex={zIndex}
|
||||
getContainer={getContainer === undefined ? getContextPopupContainer : getContainer}
|
||||
@ -149,7 +174,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
||||
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
|
||||
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
|
||||
className={classNames(hashId, className, modalContext?.className)}
|
||||
style={{ ...modalContext?.style, ...style }}
|
||||
style={{ ...modalContext?.style, ...style, ...responsiveWidthVars }}
|
||||
classNames={{
|
||||
...modalContext?.classNames,
|
||||
...modalClassNames,
|
||||
|
@ -192,4 +192,20 @@ describe('Modal', () => {
|
||||
expect(document.querySelector('.first-origin')).toMatchSnapshot();
|
||||
expect(document.querySelector('.second-props-origin')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('responsive width', () => {
|
||||
render(
|
||||
<Modal open width={{ xs: '90%', sm: '80%', md: '70%', lg: '60%', xl: '50%', xxl: '40%' }} />,
|
||||
);
|
||||
|
||||
const modalEle = document.querySelector<HTMLDivElement>('.ant-modal')!;
|
||||
expect(modalEle).toHaveStyle({
|
||||
'--ant-modal-xs-width': '90%',
|
||||
'--ant-modal-sm-width': '80%',
|
||||
'--ant-modal-md-width': '70%',
|
||||
'--ant-modal-lg-width': '60%',
|
||||
'--ant-modal-xl-width': '50%',
|
||||
'--ant-modal-xxl-width': '40%',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1228,14 +1228,26 @@ exports[`renders components/modal/demo/static-info.tsx extend context correctly
|
||||
exports[`renders components/modal/demo/static-info.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/modal/demo/width.tsx extend context correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
<div
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<span>
|
||||
Open Modal of 1000px width
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal of 1000px width
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal of responsive width
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/width.tsx extend context correctly 2`] = `[]`;
|
||||
|
@ -1188,14 +1188,26 @@ exports[`renders components/modal/demo/static-info.tsx correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/width.tsx correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
<div
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<span>
|
||||
Open Modal of 1000px width
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal of 1000px width
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal of responsive width
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/wireframe.tsx correctly 1`] = `
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { Button, Flex, Modal } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openResponsive, setOpenResponsive] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical gap="middle" align="flex-start">
|
||||
{/* Basic */}
|
||||
<Button type="primary" onClick={() => setOpen(true)}>
|
||||
Open Modal of 1000px width
|
||||
</Button>
|
||||
@ -21,7 +23,31 @@ const App: React.FC = () => {
|
||||
<p>some contents...</p>
|
||||
<p>some contents...</p>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
{/* Responsive */}
|
||||
<Button type="primary" onClick={() => setOpenResponsive(true)}>
|
||||
Open Modal of responsive width
|
||||
</Button>
|
||||
<Modal
|
||||
title="Modal responsive width"
|
||||
centered
|
||||
open={openResponsive}
|
||||
onOk={() => setOpenResponsive(false)}
|
||||
onCancel={() => setOpenResponsive(false)}
|
||||
width={{
|
||||
xs: '90%',
|
||||
sm: '80%',
|
||||
md: '70%',
|
||||
lg: '60%',
|
||||
xl: '50%',
|
||||
xxl: '40%',
|
||||
}}
|
||||
>
|
||||
<p>some contents...</p>
|
||||
<p>some contents...</p>
|
||||
<p>some contents...</p>
|
||||
</Modal>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -72,7 +72,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| loading | Show the skeleton | boolean | | 5.18.0 |
|
||||
| title | The modal dialog's title | ReactNode | - | |
|
||||
| open | Whether the modal dialog is visible or not | boolean | false | |
|
||||
| width | Width of the modal dialog | string \| number | 520 | |
|
||||
| width | Width of the modal dialog | string \| number \| [Breakpoint](/components/grid-cn#col) | 520 | Breakpoint: 5.23.0 |
|
||||
| wrapClassName | The class name of the container of the modal dialog | string | - | |
|
||||
| zIndex | The `z-index` of the Modal | number | 1000 | |
|
||||
| onCancel | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button | function(e) | - | |
|
||||
|
@ -73,7 +73,7 @@ demo:
|
||||
| loading | 显示骨架屏 | boolean | | 5.18.0 |
|
||||
| title | 标题 | ReactNode | - | |
|
||||
| open | 对话框是否可见 | boolean | - | |
|
||||
| width | 宽度 | string \| number | 520 | |
|
||||
| width | 宽度 | string \| number \| [Breakpoint](/components/grid-cn#col) | 520 | Breakpoint: 5.23.0 |
|
||||
| wrapClassName | 对话框外层容器的类名 | string | - | |
|
||||
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
|
||||
| onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - | |
|
||||
|
@ -1,10 +1,11 @@
|
||||
import type React from 'react';
|
||||
import type { DialogProps } from 'rc-dialog';
|
||||
|
||||
import { Breakpoint } from '../_util/responsiveObserver';
|
||||
import type { ButtonProps, LegacyButtonType } from '../button/button';
|
||||
import type { DirectionType } from '../config-provider';
|
||||
|
||||
interface ModalCommonProps extends Omit<DialogProps, 'footer'> {
|
||||
interface ModalCommonProps extends Omit<DialogProps, 'footer' | 'width'> {
|
||||
footer?:
|
||||
| React.ReactNode
|
||||
| ((
|
||||
@ -30,7 +31,7 @@ export interface ModalProps extends ModalCommonProps {
|
||||
/** Centered Modal */
|
||||
centered?: boolean;
|
||||
/** Width of the modal dialog */
|
||||
width?: string | number;
|
||||
width?: string | number | Partial<Record<Breakpoint, string | number>>;
|
||||
/** Text of the OK button */
|
||||
okText?: React.ReactNode;
|
||||
/** Button `type` of the OK button */
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type React from 'react';
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { getMediaSize } from '../../grid/style';
|
||||
import { genFocusStyle, resetComponent } from '../../style';
|
||||
import { initFadeMotion, initZoomMotion } from '../../style/motion';
|
||||
import type {
|
||||
@ -386,6 +387,30 @@ const genRTLStyle: GenerateStyle<ModalToken> = (token) => {
|
||||
};
|
||||
};
|
||||
|
||||
const genResponsiveWidthStyle: GenerateStyle<ModalToken> = (token) => {
|
||||
const { componentCls } = token;
|
||||
|
||||
const gridMediaSizesMap: Record<string, number> = getMediaSize(token);
|
||||
delete gridMediaSizesMap.xs;
|
||||
|
||||
const responsiveStyles = Object.keys(gridMediaSizesMap).map((key) => ({
|
||||
[`@media (min-width: ${unit(gridMediaSizesMap[key])})`]: {
|
||||
width: `var(--${componentCls.replace('.', '')}-${key}-width)`,
|
||||
},
|
||||
}));
|
||||
|
||||
return {
|
||||
[`${componentCls}-root`]: {
|
||||
[componentCls]: [
|
||||
{
|
||||
width: `var(--${componentCls.replace('.', '')}-xs-width)`,
|
||||
},
|
||||
...responsiveStyles,
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export const prepareToken: (token: Parameters<GenStyleFn<'Modal'>>[0]) => ModalToken = (token) => {
|
||||
const headerPaddingVertical = token.padding;
|
||||
@ -453,6 +478,7 @@ export default genStyleHooks(
|
||||
genRTLStyle(modalToken),
|
||||
genModalMaskStyle(modalToken),
|
||||
initZoomMotion(modalToken, 'zoom'),
|
||||
genResponsiveWidthStyle(modalToken),
|
||||
];
|
||||
},
|
||||
prepareComponentToken,
|
||||
|
Loading…
Reference in New Issue
Block a user