feat: add support to responsive sizes in Avatar component (#26244)

fix lint and test failures

update avatar demo card title

update ts type check for undefined
This commit is contained in:
Will Soares 2020-08-29 02:43:52 -03:00 committed by GitHub
parent a8b4e3c2e5
commit 9101f3666a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 19 deletions

View File

@ -1,6 +1,7 @@
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'; export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
export type BreakpointMap = Partial<Record<Breakpoint, string>>; export type BreakpointMap = Partial<Record<Breakpoint, string>>;
export type ScreenMap = Partial<Record<Breakpoint, boolean>>; export type ScreenMap = Partial<Record<Breakpoint, boolean>>;
export type ScreenSizeMap = Partial<Record<Breakpoint, number>>;
export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'];

View File

@ -1,13 +1,19 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import Avatar from '..'; import Avatar from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import useBreakpoint from '../../grid/hooks/useBreakpoint';
jest.mock('../../grid/hooks/useBreakpoint');
describe('Avatar Render', () => { describe('Avatar Render', () => {
mountTest(Avatar); mountTest(Avatar);
rtlTest(Avatar); rtlTest(Avatar);
const sizes = { xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 };
let originOffsetWidth; let originOffsetWidth;
beforeAll(() => { beforeAll(() => {
// Mock offsetHeight // Mock offsetHeight
@ -152,6 +158,19 @@ describe('Avatar Render', () => {
expect(wrapper).toMatchRenderedSnapshot(); expect(wrapper).toMatchRenderedSnapshot();
}); });
Object.entries(sizes).forEach(([key, value]) => {
it(`adjusts component size to ${value} when window size is ${key}`, () => {
const wrapper = global.document.createElement('div');
useBreakpoint.mockReturnValue({ [key]: true });
act(() => {
ReactDOM.render(<Avatar size={sizes} />, wrapper);
});
expect(wrapper).toMatchSnapshot();
});
});
it('support onMouseEnter', () => { it('support onMouseEnter', () => {
const onMouseEnter = jest.fn(); const onMouseEnter = jest.fn();
const wrapper = mount(<Avatar onMouseEnter={onMouseEnter}>TestString</Avatar>); const wrapper = mount(<Avatar onMouseEnter={onMouseEnter}>TestString</Avatar>);

View File

@ -1,5 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Avatar Render adjusts component size to 24 when window size is xs 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 24px; height: 24px; line-height: 24px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render adjusts component size to 32 when window size is sm 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 32px; height: 32px; line-height: 32px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render adjusts component size to 40 when window size is md 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 40px; height: 40px; line-height: 40px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render adjusts component size to 64 when window size is lg 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 64px; height: 64px; line-height: 64px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render adjusts component size to 80 when window size is xl 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 80px; height: 80px; line-height: 80px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render adjusts component size to 100 when window size is xxl 1`] = `
<div>
<span
class="ant-avatar ant-avatar-circle"
style="width: 100px; height: 100px; line-height: 100px; font-size: 18px;"
>
<span
class="ant-avatar-string"
style="transform: scale(0.32) translateX(-50%);"
/>
</span>
</div>
`;
exports[`Avatar Render fallback 1`] = ` exports[`Avatar Render fallback 1`] = `
<span <span
class="ant-avatar ant-avatar-circle ant-avatar-image" class="ant-avatar ant-avatar-circle ant-avatar-image"

View File

@ -469,6 +469,25 @@ Array [
] ]
`; `;
exports[`renders ./components/avatar/demo/fallback.md correctly 1`] = `
Array [
<span
class="ant-avatar ant-avatar-circle ant-avatar-image"
>
<img
src="http://abc.com/not-exist.jpg"
/>
</span>,
<span
class="ant-avatar ant-avatar-circle ant-avatar-image"
>
<img
src="http://abc.com/not-exist.jpg"
/>
</span>,
]
`;
exports[`renders ./components/avatar/demo/group.md correctly 1`] = ` exports[`renders ./components/avatar/demo/group.md correctly 1`] = `
Array [ Array [
<div <div
@ -583,23 +602,31 @@ Array [
] ]
`; `;
exports[`renders ./components/avatar/demo/fallback.md correctly 1`] = ` exports[`renders ./components/avatar/demo/responsive.md correctly 1`] = `
Array [ <span
class="ant-avatar ant-avatar-circle ant-avatar-icon"
>
<span <span
class="ant-avatar ant-avatar-circle ant-avatar-image" aria-label="ant-design"
class="anticon anticon-ant-design"
role="img"
> >
<img <svg
src="http://abc.com/not-exist.jpg" aria-hidden="true"
/> class=""
</span>, data-icon="ant-design"
<span fill="currentColor"
class="ant-avatar ant-avatar-circle ant-avatar-image" focusable="false"
> height="1em"
<img viewBox="64 64 896 896"
src="http://abc.com/not-exist.jpg" width="1em"
/> >
</span>, <path
] d="M716.3 313.8c19-18.9 19-49.7 0-68.6l-69.9-69.9.1.1c-18.5-18.5-50.3-50.3-95.3-95.2-21.2-20.7-55.5-20.5-76.5.5L80.9 474.2a53.84 53.84 0 000 76.4L474.6 944a54.14 54.14 0 0076.5 0l165.1-165c19-18.9 19-49.7 0-68.6a48.7 48.7 0 00-68.7 0l-125 125.2c-5.2 5.2-13.3 5.2-18.5 0L189.5 521.4c-5.2-5.2-5.2-13.3 0-18.5l314.4-314.2c.4-.4.9-.7 1.3-1.1 5.2-4.1 12.4-3.7 17.2 1.1l125.2 125.1c19 19 49.8 19 68.7 0zM408.6 514.4a106.3 106.2 0 10212.6 0 106.3 106.2 0 10-212.6 0zm536.2-38.6L821.9 353.5c-19-18.9-49.8-18.9-68.7.1a48.4 48.4 0 000 68.6l83 82.9c5.2 5.2 5.2 13.3 0 18.5l-81.8 81.7a48.4 48.4 0 000 68.6 48.7 48.7 0 0068.7 0l121.8-121.7a53.93 53.93 0 00-.1-76.4z"
/>
</svg>
</span>
</span>
`; `;
exports[`renders ./components/avatar/demo/toggle-debug.md correctly 1`] = ` exports[`renders ./components/avatar/demo/toggle-debug.md correctly 1`] = `

View File

@ -4,6 +4,8 @@ import classNames from 'classnames';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning'; import devWarning from '../_util/devWarning';
import { composeRef } from '../_util/ref'; import { composeRef } from '../_util/ref';
import { Breakpoint, responsiveArray, ScreenSizeMap } from '../_util/responsiveObserve';
import useBreakpoint from '../grid/hooks/useBreakpoint';
export interface AvatarProps { export interface AvatarProps {
/** Shape of avatar, options:`circle`, `square` */ /** Shape of avatar, options:`circle`, `square` */
@ -12,7 +14,7 @@ export interface AvatarProps {
* Size of avatar, options: `large`, `small`, `default` * Size of avatar, options: `large`, `small`, `default`
* or a custom number size * or a custom number size
* */ * */
size?: 'large' | 'small' | 'default' | number; size?: 'large' | 'small' | 'default' | number | ScreenSizeMap;
gap?: number; gap?: number;
/** Src of image avatar */ /** Src of image avatar */
src?: string; src?: string;
@ -109,6 +111,25 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
...others ...others
} = props; } = props;
const screens = useBreakpoint();
const responsiveSizeStyle: React.CSSProperties = React.useMemo(() => {
if (typeof size !== 'object') {
return {};
}
const currentBreakpoint: Breakpoint = responsiveArray.find(screen => screens[screen])!;
const currentSize = size[currentBreakpoint];
return currentSize
? {
width: currentSize,
height: currentSize,
lineHeight: `${currentSize}px`,
fontSize: icon ? currentSize / 2 : 18,
}
: {};
}, [screens, size]);
devWarning( devWarning(
!(typeof icon === 'string' && icon.length > 2), !(typeof icon === 'string' && icon.length > 2),
'Avatar', 'Avatar',
@ -193,7 +214,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
return ( return (
<span <span
{...others} {...others}
style={{ ...sizeStyle, ...others.style }} style={{ ...sizeStyle, ...responsiveSizeStyle, ...others.style }}
className={classString} className={classString}
ref={avatarNodeMergeRef as any} ref={avatarNodeMergeRef as any}
> >

View File

@ -0,0 +1,27 @@
---
order: 5
title:
zh-CN: 响应式尺寸
en-US: Responsive Size
---
## zh-CN
头像大小可以根据屏幕大小自动调整。
## en-US
Avatar size can be automatically adjusted based on the screen size.
```tsx
import { Avatar } from 'antd';
import { AntDesignOutlined } from '@ant-design/icons';
ReactDOM.render(
<Avatar
size={{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }}
icon={<AntDesignOutlined />}
/>,
mountNode,
);
```

View File

@ -15,7 +15,7 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s,
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| icon | Custom icon type for an icon avatar | ReactNode | - | | | icon | Custom icon type for an icon avatar | ReactNode | - | |
| shape | The shape of avatar | `circle` \| `square` | `circle` | | | shape | The shape of avatar | `circle` \| `square` | `circle` | |
| size | The size of the avatar | number \| `large` \| `small` \| `default` | `default` | | | size | The size of the avatar | number \| `large` \| `small` \| `default` \| `{ xs: number, sm: number, ...}` | `default` | 4.7.0 |
| src | The address of the image for an image avatar | string | - | | | src | The address of the image for an image avatar | string | - | |
| srcSet | A list of sources to use for different screen resolutions | string | - | | | srcSet | A list of sources to use for different screen resolutions | string | - | |
| alt | This attribute defines the alternative text describing the image | string | - | | | alt | This attribute defines the alternative text describing the image | string | - | |

View File

@ -20,7 +20,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/aBcnbw68hP/Avatar.svg
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| icon | 设置头像的自定义图标 | ReactNode | - | | | icon | 设置头像的自定义图标 | ReactNode | - | |
| shape | 指定头像的形状 | `circle` \| `square` | `circle` | | | shape | 指定头像的形状 | `circle` \| `square` | `circle` | |
| size | 设置头像的大小 | number \| `large` \| `small` \| `default` | `default` | | | size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| `{ xs: number, sm: number, ...}` | `default` | 4.7.0 |
| src | 图片类头像的资源地址 | string | - | | | src | 图片类头像的资源地址 | string | - | |
| srcSet | 设置图片类头像响应式资源地址 | string | - | | | srcSet | 设置图片类头像响应式资源地址 | string | - | |
| alt | 图像无法显示时的替代文本 | string | - | | | alt | 图像无法显示时的替代文本 | string | - | |

View File

@ -2,4 +2,5 @@ import '../../style/index.less';
import './index.less'; import './index.less';
// style dependencies // style dependencies
// deps-lint-skip: grid
import '../../popover/style'; import '../../popover/style';