diff --git a/components/grid/__tests__/index.test.tsx b/components/grid/__tests__/index.test.tsx index 466f33e9d492..cbe5d55b7876 100644 --- a/components/grid/__tests__/index.test.tsx +++ b/components/grid/__tests__/index.test.tsx @@ -143,4 +143,44 @@ describe('Grid', () => { xxl: false, }); }); + + it('should align by responsive align prop', () => { + const matchMediaSpy = jest.spyOn(window, 'matchMedia'); + matchMediaSpy.mockImplementation( + query => + ({ + addListener: (cb: (e: { matches: boolean }) => void) => { + cb({ matches: query === '(max-width: 575px)' }); + }, + removeListener: jest.fn(), + matches: query === '(max-width: 575px)', + } as any), + ); + const { container } = render(); + expect(container.innerHTML).toContain('ant-row-middle'); + const { container: container2 } = render(); + expect(container2.innerHTML).toContain('ant-row-middle'); + const { container: container3 } = render(); + expect(container3.innerHTML).not.toContain('ant-row-middle'); + }); + + it('should justify by responsive justify prop', () => { + const matchMediaSpy = jest.spyOn(window, 'matchMedia'); + matchMediaSpy.mockImplementation( + query => + ({ + addListener: (cb: (e: { matches: boolean }) => void) => { + cb({ matches: query === '(max-width: 575px)' }); + }, + removeListener: jest.fn(), + matches: query === '(max-width: 575px)', + } as any), + ); + const { container } = render(); + expect(container.innerHTML).toContain('ant-row-center'); + const { container: container2 } = render(); + expect(container2.innerHTML).toContain('ant-row-center'); + const { container: container3 } = render(); + expect(container3.innerHTML).not.toContain('ant-row-center'); + }); }); diff --git a/components/grid/index.en-US.md b/components/grid/index.en-US.md index 6387aa9b4aaf..ef285894530a 100644 --- a/components/grid/index.en-US.md +++ b/components/grid/index.en-US.md @@ -44,9 +44,9 @@ If the Ant Design grid layout component does not meet your needs, you can use th | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| align | Vertical alignment | `top` \| `middle` \| `bottom` | `top` | | +| align | Vertical alignment | `top` \| `middle` \| `bottom` \| `stretch` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'top' \| 'middle' \| 'bottom' \| 'stretch'}` | `top` | object: 4.24.0 | | gutter | Spacing between grids, could be a number or a object like { xs: 8, sm: 16, md: 24}. Or you can use array to make horizontal and vertical spacing work at the same time `[horizontal, vertical]` | number \| object \| array | 0 | | -| justify | Horizontal arrangement | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` | `start` | | +| justify | Horizontal arrangement | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'start' \| 'end' \| 'center' \| 'space-around' \| 'space-between' \| 'space-evenly'}` | `start` | object: 4.24.0 | | wrap | Auto wrap line | boolean | true | 4.8.0 | ### Col diff --git a/components/grid/index.zh-CN.md b/components/grid/index.zh-CN.md index e8ed5b41ac04..6fbe36bcad6b 100644 --- a/components/grid/index.zh-CN.md +++ b/components/grid/index.zh-CN.md @@ -43,9 +43,9 @@ Ant Design 的布局组件若不能满足你的需求,你也可以直接使用 | 成员 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` | `top` | | +| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` \| `stretch` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'top' \| 'middle' \| 'bottom' \| 'stretch'}` | `top` | object: 4.24.0 | | gutter | 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 `[水平间距, 垂直间距]` | number \| object \| array | 0 | | -| justify | 水平排列方式 | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` | `start` | | +| justify | 水平排列方式 | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'start' \| 'end' \| 'center' \| 'space-around' \| 'space-between' \| 'space-evenly'}` | `start` | object: 4.24.0 | | wrap | 是否自动换行 | boolean | true | 4.8.0 | ### Col diff --git a/components/grid/row.tsx b/components/grid/row.tsx index 1457dfcfd0ff..ff32a141fb77 100644 --- a/components/grid/row.tsx +++ b/components/grid/row.tsx @@ -10,16 +10,50 @@ import RowContext from './RowContext'; const RowAligns = tuple('top', 'middle', 'bottom', 'stretch'); const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between', 'space-evenly'); +type Responsive = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'; +type ResponsiveLike = { + [key in Responsive]?: T; +}; + type Gap = number | undefined; export type Gutter = number | undefined | Partial>; + +type ResponsiveAligns = ResponsiveLike; +type ResponsiveJustify = ResponsiveLike; export interface RowProps extends React.HTMLAttributes { gutter?: Gutter | [Gutter, Gutter]; - align?: typeof RowAligns[number]; - justify?: typeof RowJustify[number]; + align?: typeof RowAligns[number] | ResponsiveAligns; + justify?: typeof RowJustify[number] | ResponsiveJustify; prefixCls?: string; wrap?: boolean; } +function useMergePropByScreen(oriProp: RowProps['align'] | RowProps['justify'], screen: ScreenMap) { + const [prop, setProp] = React.useState(typeof oriProp === 'string' ? oriProp : ''); + + const clacMergeAlignOrJustify = () => { + if (typeof oriProp !== 'object') { + return; + } + for (let i = 0; i < responsiveArray.length; i++) { + const breakpoint: Breakpoint = responsiveArray[i]; + // if do not match, do nothing + if (!screen[breakpoint]) continue; + const curVal = oriProp[breakpoint]; + if (curVal !== undefined) { + setProp(curVal); + return; + } + } + }; + + React.useEffect(() => { + clacMergeAlignOrJustify(); + }, [JSON.stringify(oriProp), screen]); + + return prop; +} + const Row = React.forwardRef((props, ref) => { const { prefixCls: customizePrefixCls, @@ -43,6 +77,20 @@ const Row = React.forwardRef((props, ref) => { xl: true, xxl: true, }); + // to save screens info when responsiveObserve callback had been call + const [curScreens, setCurScreens] = React.useState({ + xs: false, + sm: false, + md: false, + lg: false, + xl: false, + xxl: false, + }); + + // ================================== calc reponsive data ================================== + const mergeAlign = useMergePropByScreen(align, curScreens); + + const mergeJustify = useMergePropByScreen(justify, curScreens); const supportFlexGap = useFlexGapSupport(); @@ -51,6 +99,7 @@ const Row = React.forwardRef((props, ref) => { // ================================== Effect ================================== React.useEffect(() => { const token = ResponsiveObserve.subscribe(screen => { + setCurScreens(screen); const currentGutter = gutterRef.current || 0; if ( (!Array.isArray(currentGutter) && typeof currentGutter === 'object') || @@ -89,8 +138,8 @@ const Row = React.forwardRef((props, ref) => { prefixCls, { [`${prefixCls}-no-wrap`]: wrap === false, - [`${prefixCls}-${justify}`]: justify, - [`${prefixCls}-${align}`]: align, + [`${prefixCls}-${mergeJustify}`]: mergeJustify, + [`${prefixCls}-${mergeAlign}`]: mergeAlign, [`${prefixCls}-rtl`]: direction === 'rtl', }, className,