From f20a6ba58394f806d89e7a62922730a80cabf056 Mon Sep 17 00:00:00 2001
From: liweijie0812 <674416404@qq.com>
Date: Wed, 24 Apr 2024 12:43:15 +0800
Subject: [PATCH] refactor(avatar): sfc to tsx (#1331)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor(avatar): sfc to tsx
* fix: import avatar
* test: max 测试用例补充
* fix: z-index
---
.../__test__/__snapshots__/demo.test.jsx.snap | 148 +++++++++++++++++-
src/avatar/__test__/index.test.jsx | 9 +-
src/avatar/avatar-group.tsx | 73 +++++++++
src/avatar/avatar-group.vue | 81 ----------
src/avatar/avatar.en-US.md | 10 +-
src/avatar/avatar.md | 10 +-
src/avatar/avatar.tsx | 82 ++++++++++
src/avatar/avatar.vue | 87 ----------
src/avatar/index.ts | 4 +-
src/cell/__test__/index.test.jsx | 2 +-
10 files changed, 319 insertions(+), 187 deletions(-)
create mode 100644 src/avatar/avatar-group.tsx
delete mode 100644 src/avatar/avatar-group.vue
create mode 100644 src/avatar/avatar.tsx
delete mode 100644 src/avatar/avatar.vue
diff --git a/src/avatar/__test__/__snapshots__/demo.test.jsx.snap b/src/avatar/__test__/__snapshots__/demo.test.jsx.snap
index e50f969d6..44138474c 100644
--- a/src/avatar/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/avatar/__test__/__snapshots__/demo.test.jsx.snap
@@ -343,8 +343,50 @@ exports[`Avatar > Avatar actionVue demo works fine 1`] = `
+
-
`;
@@ -927,8 +969,36 @@ exports[`Avatar > Avatar exhibitionVue demo works fine 1`] = `
+
-
`;
@@ -1903,8 +1973,36 @@ exports[`Avatar > Avatar mobileVue demo works fine 1`] = `
+
-
@@ -2271,8 +2369,50 @@ exports[`Avatar > Avatar mobileVue demo works fine 1`] = `
+
-
diff --git a/src/avatar/__test__/index.test.jsx b/src/avatar/__test__/index.test.jsx
index b23dbfc9a..1724326b0 100644
--- a/src/avatar/__test__/index.test.jsx
+++ b/src/avatar/__test__/index.test.jsx
@@ -2,8 +2,8 @@ import { h, nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import { UserIcon } from 'tdesign-icons-vue-next';
-import Avatar from '../avatar.vue';
-import AvatarGroup from '../avatar-group.vue';
+import Avatar from '../avatar';
+import AvatarGroup from '../avatar-group';
import Badge from '../../badge/badge';
const IMAGE = 'https://tdesign.gtimg.com/site/avatar.jpg';
@@ -121,7 +121,7 @@ describe('avatar-group', async () => {
it(': max', async () => {
const wrapper = mount(() => (
-
+
@@ -130,6 +130,7 @@ describe('avatar-group', async () => {
));
const avatarList = wrapper.findAllComponents(Avatar);
- expect(avatarList.length).toBe(1);
+ expect(avatarList.length).toBe(3);
+ expect(avatarList[avatarList.length-1].text()).toBe('+3');
});
});
diff --git a/src/avatar/avatar-group.tsx b/src/avatar/avatar-group.tsx
new file mode 100644
index 000000000..366e4641f
--- /dev/null
+++ b/src/avatar/avatar-group.tsx
@@ -0,0 +1,73 @@
+import { computed, defineComponent, Fragment, provide, RendererNode } from 'vue';
+
+import AvatarGroupProps from './avatar-group-props';
+import config from '../config';
+import TAvatar from './avatar';
+
+import { useTNodeJSX } from '../hooks/tnode';
+import { usePrefixClass } from '../hooks/useClass';
+
+const { prefix } = config;
+const name = `${prefix}-avatar-group`;
+
+export default defineComponent({
+ name,
+ props: AvatarGroupProps,
+ setup(props) {
+ const renderTNodeJSX = useTNodeJSX();
+ const avatarGroupClass = usePrefixClass('avatar-group');
+
+ provide('avatarGroup', { ...props });
+
+ const direction = props.cascading ? props.cascading.split('-')[0] : 'right';
+
+ const avatarGroupClasses = computed(() => [
+ `${avatarGroupClass.value}`,
+ `${avatarGroupClass.value}-offset-${direction}-${props.size.indexOf('px') > -1 ? 'medium' : props.size}`,
+ ]);
+
+ const readerAvatar = () => {
+ const children: Array = renderTNodeJSX('default');
+ const allChildren: Array = [];
+
+ children.forEach((child) => {
+ if (child.type === Fragment) {
+ allChildren.push(...child.children);
+ } else {
+ allChildren.push(child);
+ }
+ });
+
+ let isShowCollapse = false;
+ let avatarList: Array = [];
+ if (allChildren.length > props.max) {
+ avatarList = allChildren.slice(0, props.max);
+ isShowCollapse = true;
+ } else {
+ avatarList = allChildren;
+ }
+
+ if (props.cascading === 'left-up') {
+ const defaultZIndex = 100;
+ for (let index = 0; index < avatarList.length; index++) {
+ avatarList[index].props.style = `z-index: ${defaultZIndex - index * 10}`;
+ }
+ }
+
+ if (isShowCollapse) {
+ const collapseAvatar = renderTNodeJSX('collapseAvatar');
+ avatarList.push(
+
+ {collapseAvatar || `+${allChildren.length - props.max}`}
+ ,
+ );
+ }
+
+ return avatarList;
+ };
+
+ return () => {
+ return {readerAvatar()}
;
+ };
+ },
+});
diff --git a/src/avatar/avatar-group.vue b/src/avatar/avatar-group.vue
deleted file mode 100644
index 1c1608d8b..000000000
--- a/src/avatar/avatar-group.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
diff --git a/src/avatar/avatar.en-US.md b/src/avatar/avatar.en-US.md
index 1235d74b3..12b815505 100644
--- a/src/avatar/avatar.en-US.md
+++ b/src/avatar/avatar.en-US.md
@@ -1,6 +1,7 @@
:: BASE_DOC ::
## API
+
### Avatar Props
name | type | default | description | required
@@ -11,7 +12,7 @@ hideOnLoadFailed | Boolean | false | hide image when loading image failed | N
icon | Slot / Function | - | use icon to fill。Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
image | String | - | images url | N
imageProps | Object | - | Typescript:`ImageProps`,[Image API Documents](./image?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
-shape | String | circle | shape。options:circle/round。Typescript:`ShapeEnum ` `type ShapeEnum = 'circle' \| 'round'`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
+shape | String | circle | shape。options: circle/round。Typescript:`ShapeEnum ` `type ShapeEnum = 'circle' \| 'round'`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
size | String | medium | size | N
onError | Function | | Typescript:`(context: { e: ImageEvent }) => void`
trigger on image load failed | N
@@ -21,17 +22,18 @@ name | params | description
-- | -- | --
error | `(context: { e: ImageEvent })` | trigger on image load failed
+
### AvatarGroup Props
name | type | default | description | required
-- | -- | -- | -- | --
-cascading | String | 'right-up' | multiple images cascading。options:left-up/right-up。Typescript:`CascadingValue` `type CascadingValue = 'left-up' \| 'right-up'`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
+cascading | String | 'right-up' | multiple images cascading。options: left-up/right-up。Typescript:`CascadingValue` `type CascadingValue = 'left-up' \| 'right-up'`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
collapseAvatar | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
max | Number | - | \- | N
size | String | medium | size | N
+### CSS 变量
-### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
@@ -55,4 +57,4 @@ Name | Default Value | Description
--td-avatar-small-width | 40px | -
--td-avatar-text-large-font-size | 16px | -
--td-avatar-text-medium-font-size | @font-size-base | -
---td-avatar-text-small-font-size | @font-size-s | -
+--td-avatar-text-small-font-size | @font-size-s | -
\ No newline at end of file
diff --git a/src/avatar/avatar.md b/src/avatar/avatar.md
index d41830551..69b7371c0 100644
--- a/src/avatar/avatar.md
+++ b/src/avatar/avatar.md
@@ -1,9 +1,10 @@
:: BASE_DOC ::
## API
+
### Avatar Props
-名称 | 类型 | 默认值 | 说明 | 必传
+名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
alt | String | - | 头像替换文本,仅当图片加载失败时有效 | N
badgeProps | Object | - | 头像右上角提示信息,继承 Badge 组件的全部特性。如:小红点,或者数字。TS 类型:`BadgeProps`,[Badge API Documents](./badge?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
@@ -21,17 +22,18 @@ onError | Function | | TS 类型:`(context: { e: ImageEvent }) => void`
-- | -- | --
error | `(context: { e: ImageEvent })` | 图片加载失败时触发
+
### AvatarGroup Props
-名称 | 类型 | 默认值 | 说明 | 必传
+名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
cascading | String | 'right-up' | 图片之间的层叠关系,可选值:左侧图片在上和右侧图片在上。可选项:left-up/right-up。TS 类型:`CascadingValue` `type CascadingValue = 'left-up' \| 'right-up'`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/avatar/type.ts) | N
collapseAvatar | String / Slot / Function | - | 头像数量超出时,会出现一个头像折叠元素。该元素内容可自定义。默认为 `+N`。示例:`+5`,`...`, `更多`。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
max | Number | - | 能够同时显示的最多头像数量 | N
size | String | medium | 尺寸,示例值:small/medium/large/24px/38px 等。优先级低于 Avatar.size | N
-
### CSS 变量
+
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
@@ -55,4 +57,4 @@ size | String | medium | 尺寸,示例值:small/medium/large/24px/38px 等
--td-avatar-small-width | 40px | -
--td-avatar-text-large-font-size | 16px | -
--td-avatar-text-medium-font-size | @font-size-base | -
---td-avatar-text-small-font-size | @font-size-s | -
+--td-avatar-text-small-font-size | @font-size-s | -
\ No newline at end of file
diff --git a/src/avatar/avatar.tsx b/src/avatar/avatar.tsx
new file mode 100644
index 000000000..03eb3579a
--- /dev/null
+++ b/src/avatar/avatar.tsx
@@ -0,0 +1,82 @@
+import { computed, defineComponent, getCurrentInstance, inject, ref } from 'vue';
+import TBadge from '../badge';
+import TImage from '../image';
+import config from '../config';
+import AvatarProps from './props';
+import { TdAvatarGroupProps, TdAvatarProps } from './type';
+import CLASSNAMES from '../shared/constants';
+import { useContent, useTNodeJSX } from '../hooks/tnode';
+import { usePrefixClass } from '../hooks/useClass';
+
+const { prefix } = config;
+const name = `${prefix}-avatar`;
+
+export default defineComponent({
+ name,
+ props: AvatarProps,
+ setup(props) {
+ const renderTNodeJSX = useTNodeJSX();
+ const renderTNodeContent = useContent();
+ const avatarClass = usePrefixClass('avatar');
+
+ const avatarGroupProps = inject('avatarGroup', {});
+ const hasAvatarGroupProps = Object.keys(avatarGroupProps).length > 0;
+
+ const sizeValue = ref(props.size || (avatarGroupProps && avatarGroupProps.size));
+ const sizeReValue = ref((avatarGroupProps && avatarGroupProps.size) || props.size);
+ const sizeClass = `${sizeReValue.value.indexOf('px') > -1 ? 'medium' : sizeReValue.value}`;
+ const avatarClasses = computed(() => [
+ `${avatarClass.value}`,
+ `${avatarClass.value}--${sizeClass}`,
+ {
+ [`${avatarClass.value}--${props.shape}`]: props.shape,
+ },
+ hasAvatarGroupProps ? `${avatarClass.value}--border ${avatarClass.value}--border-${sizeClass}` : '',
+ ]);
+
+ const isCustomSize = computed(() => sizeValue.value && !CLASSNAMES.SIZE[sizeValue.value]);
+ const customSize = computed(() => {
+ return isCustomSize.value
+ ? {
+ height: sizeValue.value,
+ width: sizeValue.value,
+ }
+ : {};
+ });
+
+ const handleImgLoadError = (e: any) => {
+ props.onError?.(e);
+ };
+ return () => {
+ const icon = renderTNodeJSX('icon');
+ const TNodeContent = renderTNodeContent('default', 'content');
+
+ const readerAvatar = () => {
+ if (props.image && !props.hideOnLoadFailed) {
+ return (
+
+ );
+ }
+ if (icon) {
+ return {icon}
;
+ }
+ return {TNodeContent};
+ };
+ return (
+
+ );
+ };
+ },
+});
diff --git a/src/avatar/avatar.vue b/src/avatar/avatar.vue
deleted file mode 100644
index e1824b444..000000000
--- a/src/avatar/avatar.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
diff --git a/src/avatar/index.ts b/src/avatar/index.ts
index 0f72d86a9..d0d020b6d 100644
--- a/src/avatar/index.ts
+++ b/src/avatar/index.ts
@@ -1,5 +1,5 @@
-import _Avatar from './avatar.vue';
-import _AvatarGroup from './avatar-group.vue';
+import _Avatar from './avatar';
+import _AvatarGroup from './avatar-group';
import { withInstall, WithInstallType } from '../shared';
import './style';
diff --git a/src/cell/__test__/index.test.jsx b/src/cell/__test__/index.test.jsx
index ba0275d39..772d84a30 100644
--- a/src/cell/__test__/index.test.jsx
+++ b/src/cell/__test__/index.test.jsx
@@ -4,7 +4,7 @@ import { describe, it, expect } from 'vitest';
import { ChevronRightIcon, AppIcon } from 'tdesign-icons-vue-next';
import Cell from '../cell';
import CellGroup from '../cell-group';
-import Avatar from '../../avatar/avatar.vue';
+import Avatar from '../../avatar/avatar';
const appIcon = () => h(AppIcon);
const chevronRightIcon = () => h(ChevronRightIcon);