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
73 changes: 69 additions & 4 deletions components/grid/row.tsx
Expand Up @@ -10,12 +10,17 @@ 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>>;
export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
gutter?: Gutter | [Gutter, Gutter];
align?: typeof RowAligns[number];
justify?: typeof RowJustify[number];
align?: typeof RowAligns[number] | ResponsiveLike<typeof RowAligns[number]>;
justify?: typeof RowJustify[number] | ResponsiveLike<typeof RowJustify[number]>;
prefixCls?: string;
wrap?: boolean;
}
Expand Down Expand Up @@ -43,6 +48,18 @@ 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, setJustify] = React.useState(typeof justify === 'string' ? justify : '');

const supportFlexGap = useFlexGapSupport();

Expand All @@ -51,6 +68,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 +101,53 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
return results;
};

// ================================== calc reponsive data ==================================
const clacMergeAlign = () => {
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
if (typeof align === 'object') {
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 curAlign = align[breakpoint];
if (align.other && !curScreens[breakpoint]) {
const otherVal = align.other;
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
if (!align[breakpoint]) {
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
setMergeAlign(otherVal);
}
} else if (curScreens[breakpoint] && curAlign !== undefined) {
setMergeAlign(curAlign!);
}
}
}
};

const clacMergeJustify = () => {
if (typeof justify === 'object') {
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint: Breakpoint = responsiveArray[i];
// When 'justify' sets the 'other' attribute,
// we need to set the value of the response attribute not explicitly set in 'justify' to the value of 'other'
const curJustify = justify[breakpoint];
if (justify.other && !curScreens[breakpoint]) {
const otherVal = justify.other;
if (!justify[breakpoint]) {
setMergeAlign(otherVal);
}
} else if (curScreens[breakpoint] && curJustify !== undefined) {
setJustify(curJustify);
}
}
}
};

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 +179,14 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
[gutterH, gutterV, wrap, supportFlexGap],
);

React.useEffect(() => {
clacMergeAlign();
}, [align, curScreens]);
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved

React.useEffect(() => {
clacMergeJustify();
}, [justify, curScreens]);

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