Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(grid): row's 'align' and 'justify' support reponsive value #37860

Merged
merged 16 commits into from Oct 12, 2022
48 changes: 48 additions & 0 deletions components/grid/__tests__/index.test.tsx
Expand Up @@ -143,4 +143,52 @@ 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(<Row align="middle" />);
expect(container.innerHTML).toContain('ant-row-middle');
const { container: container2 } = render(<Row align={{ xs: 'middle' }} />);
expect(container2.innerHTML).toContain('ant-row-middle');
const { container: container3 } = render(<Row align={{ lg: 'middle' }} />);
expect(container3.innerHTML).not.toContain('ant-row-middle');
const { container: container4 } = render(<Row align={{ lg: 'middle', other: 'bottom' }} />);
expect(container4.innerHTML).toContain('ant-row-bottom');
const { container: container5 } = render(<Row align={{ xs: 'middle', other: 'bottom' }} />);
expect(container5.innerHTML).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(<Row justify="center" />);
expect(container.innerHTML).toContain('ant-row-center');
const { container: container2 } = render(<Row justify={{ xs: 'center' }} />);
expect(container2.innerHTML).toContain('ant-row-center');
const { container: container3 } = render(<Row justify={{ lg: 'center' }} />);
expect(container3.innerHTML).not.toContain('ant-row-center');
const { container: container4 } = render(<Row justify={{ lg: 'center', other: 'end' }} />);
expect(container4.innerHTML).toContain('ant-row-end');
const { container: container5 } = render(<Row justify={{ xs: 'center', other: 'end' }} />);
expect(container5.innerHTML).toContain('ant-row-center');
});
});
6 changes: 3 additions & 3 deletions components/grid/index.zh-CN.md
Expand Up @@ -42,10 +42,10 @@ Ant Design 的布局组件若不能满足你的需求,你也可以直接使用
### Row

| 成员 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` | `top` | |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` \| `stretch` \| `{[key in 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'other']: 'top' | 'middle' | 'bottom' | 'stretch'}` | `top` | |
| 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' | 'other']: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'}` | `start` | |
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
| wrap | 是否自动换行 | boolean | true | 4.8.0 |

### Col
Expand Down
57 changes: 53 additions & 4 deletions components/grid/row.tsx
Expand Up @@ -10,12 +10,20 @@ 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' | 'other';
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
type ResponsiveLike<T> = {
[key in Responsive]?: T;
};

type Gap = number | undefined;
export type Gutter = number | undefined | Partial<Record<Breakpoint, number>>;

type ResponsiveAligns = ResponsiveLike<typeof RowAligns[number]>;
type ResponsiveJustify = ResponsiveLike<typeof RowJustify[number]>;
export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
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;
}
Expand Down Expand Up @@ -43,6 +51,20 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
xl: true,
xxl: true,
});
// to save screens info when responsiveObserve callback had been call
const [curScreens, setCurScreens] = React.useState<ScreenMap>({
xs: false,
sm: false,
md: false,
lg: false,
xl: false,
xxl: false,
});

const [mergeAlign, setMergeAlign] = React.useState(typeof align === 'string' ? align : '');
const [mergeJustify, setMergeJustify] = React.useState(
typeof justify === 'string' ? justify : '',
);

const supportFlexGap = useFlexGapSupport();

Expand All @@ -51,6 +73,7 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
// ================================== Effect ==================================
React.useEffect(() => {
const token = ResponsiveObserve.subscribe(screen => {
setCurScreens(screen);
const currentGutter = gutterRef.current || 0;
if (
(!Array.isArray(currentGutter) && typeof currentGutter === 'object') ||
Expand Down Expand Up @@ -83,14 +106,36 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
return results;
};

// ================================== calc reponsive data ==================================
const clacMergeAlignOrJustify = (propName: 'align' | 'justify') => {
if (typeof props[propName] !== 'object') {
return;
}
const prop = props[propName] as ResponsiveLike<any>;
const updator = propName === 'align' ? setMergeAlign : setMergeJustify;
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint: Breakpoint = responsiveArray[i];
// When 'align' sets the 'other' attribute,
// we need to set the value of the response attribute not explicitly set in 'align' to the value of 'other'
const curVal = prop[breakpoint];
if (prop.other && !curScreens[breakpoint]) {
if (!curVal) {
updator(prop.other);
}
} else if (curScreens[breakpoint] && curVal !== undefined) {
updator(curVal);
}
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
}
};

const prefixCls = getPrefixCls('row', customizePrefixCls);
const gutters = getGutter();
const classes = classNames(
prefixCls,
{
[`${prefixCls}-no-wrap`]: wrap === false,
[`${prefixCls}-${justify}`]: justify,
[`${prefixCls}-${align}`]: align,
[`${prefixCls}-${mergeJustify}`]: mergeJustify,
[`${prefixCls}-${mergeAlign}`]: mergeAlign,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
Expand Down Expand Up @@ -122,6 +167,10 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
[gutterH, gutterV, wrap, supportFlexGap],
);

// re-calc responsive data
React.useEffect(() => clacMergeAlignOrJustify('align'), [align, curScreens]);
React.useEffect(() => clacMergeAlignOrJustify('justify'), [justify, curScreens]);
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved

return (
<RowContext.Provider value={rowContext}>
<div {...others} className={classes} style={{ ...rowStyle, ...style }} ref={ref}>
Expand Down