From 0f81e4cf7a6e305be34767d2fe593a3001843f7d Mon Sep 17 00:00:00 2001 From: Betty Date: Thu, 24 Mar 2022 19:54:41 +0800 Subject: [PATCH] feat(comp:comment): add comment component fix #358 --- .../__snapshots__/comment.spec.ts.snap | 18 +++ .../comment/__tests__/comment.spec.ts | 129 ++++++++++++++++++ packages/components/comment/demo/Basic.md | 14 ++ packages/components/comment/demo/Basic.vue | 75 ++++++++++ packages/components/comment/demo/Editor.md | 14 ++ packages/components/comment/demo/Editor.vue | 67 +++++++++ packages/components/comment/demo/List.md | 14 ++ packages/components/comment/demo/List.vue | 53 +++++++ packages/components/comment/demo/Nested.md | 14 ++ packages/components/comment/demo/Nested.vue | 20 +++ packages/components/comment/docs/Design.en.md | 3 + packages/components/comment/docs/Design.zh.md | 15 ++ packages/components/comment/docs/Index.en.md | 23 ++++ packages/components/comment/docs/Index.zh.md | 28 ++++ packages/components/comment/index.ts | 16 +++ packages/components/comment/src/Comment.tsx | 107 +++++++++++++++ packages/components/comment/src/types.ts | 26 ++++ packages/components/comment/style/index.less | 99 ++++++++++++++ .../comment/style/themes/default.less | 3 + .../comment/style/themes/default.ts | 4 + .../style/themes/default.variable.less | 15 ++ packages/components/default.less | 1 + packages/components/index.ts | 2 + .../components/style/variable/prefix.less | 1 + 24 files changed, 761 insertions(+) create mode 100644 packages/components/comment/__tests__/__snapshots__/comment.spec.ts.snap create mode 100644 packages/components/comment/__tests__/comment.spec.ts create mode 100644 packages/components/comment/demo/Basic.md create mode 100644 packages/components/comment/demo/Basic.vue create mode 100644 packages/components/comment/demo/Editor.md create mode 100644 packages/components/comment/demo/Editor.vue create mode 100644 packages/components/comment/demo/List.md create mode 100644 packages/components/comment/demo/List.vue create mode 100644 packages/components/comment/demo/Nested.md create mode 100644 packages/components/comment/demo/Nested.vue create mode 100644 packages/components/comment/docs/Design.en.md create mode 100644 packages/components/comment/docs/Design.zh.md create mode 100644 packages/components/comment/docs/Index.en.md create mode 100644 packages/components/comment/docs/Index.zh.md create mode 100644 packages/components/comment/index.ts create mode 100644 packages/components/comment/src/Comment.tsx create mode 100644 packages/components/comment/src/types.ts create mode 100644 packages/components/comment/style/index.less create mode 100644 packages/components/comment/style/themes/default.less create mode 100644 packages/components/comment/style/themes/default.ts create mode 100644 packages/components/comment/style/themes/default.variable.less diff --git a/packages/components/comment/__tests__/__snapshots__/comment.spec.ts.snap b/packages/components/comment/__tests__/__snapshots__/comment.spec.ts.snap new file mode 100644 index 000000000..95dd8c47f --- /dev/null +++ b/packages/components/comment/__tests__/__snapshots__/comment.spec.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Comment render work 1`] = ` +"
+
+ +
+ +
comment content
+ +
+
+ +
" +`; diff --git a/packages/components/comment/__tests__/comment.spec.ts b/packages/components/comment/__tests__/comment.spec.ts new file mode 100644 index 000000000..339cae8d5 --- /dev/null +++ b/packages/components/comment/__tests__/comment.spec.ts @@ -0,0 +1,129 @@ +import { MountingOptions, mount } from '@vue/test-utils' +import { h } from 'vue' + +import { renderWork } from '@tests' + +import { IxAvatar } from '@idux/components/avatar' +import { IxIcon } from '@idux/components/icon' + +import Comment from '../src/Comment' +import { CommentProps } from '../src/types' + +describe('Comment', () => { + const CommentMount = (options?: MountingOptions>) => mount(Comment, { ...options }) + + renderWork(Comment, { + props: { + content: 'comment content', + }, + }) + const authorText = 'Han Solo' + + test('author work', async () => { + const wrapper = CommentMount({ props: { author: authorText } }) + + expect(wrapper.find('.ix-comment-content-author-name').text()).toBe(authorText) + }) + + test('author slot work', async () => { + const wrapper = CommentMount({ slots: { author: () => authorText } }) + + expect(wrapper.find('.ix-comment-content-author-name').text()).toBe(authorText) + }) + + const avatarSrc = '/images/avatar/0.png' + + test('avatar string work', async () => { + const wrapper = CommentMount({ props: { avatar: avatarSrc } }) + + expect(wrapper.find('.ix-comment-avatar').exists()).toBe(true) + expect(wrapper.find('img').element.src).toContain(avatarSrc) + }) + + const squareShape = 'square' + const circleShape = 'circle' + + test('avatar object work', async () => { + const wrapper = CommentMount({ + props: { + avatar: { src: avatarSrc, shape: squareShape }, + }, + }) + + expect(wrapper.find('.ix-avatar-square').exists()).toBe(true) + expect(wrapper.find('img').element.src).toContain(avatarSrc) + + await wrapper.setProps({ avatar: { src: avatarSrc, shape: circleShape } }) + expect(wrapper.find('.ix-avatar-circle').exists()).toBe(true) + expect(wrapper.find('.ix-avatar-square').exists()).toBe(false) + }) + + test('avatar slot work', async () => { + const wrapper = CommentMount({ + slots: { avatar: () => [h(IxAvatar, { src: avatarSrc })] }, + }) + + expect(wrapper.find('img').element.src).toContain(avatarSrc) + }) + + const commentContent = 'this is comment text !' + + test('content work', async () => { + const wrapper = CommentMount({ + props: { content: commentContent }, + }) + + expect(wrapper.find('.ix-comment-content-detail').text()).toBe(commentContent) + }) + + test('content slot work', async () => { + const wrapper = CommentMount({ + slots: { content: () => [h('p', commentContent)] }, + }) + + expect(wrapper.find('p').text()).toContain(commentContent) + }) + + const datetime1 = '2022/3/24' + const datetime2 = '2022/3/25' + + test('datetime work', async () => { + const wrapper = CommentMount({ + props: { datetime: datetime1 }, + }) + + expect(wrapper.find('.ix-comment-content-author-time').text()).toBe(datetime1) + + await wrapper.setProps({ datetime: datetime2 }) + expect(wrapper.find('.ix-comment-content-author-time').text()).toBe(datetime2) + }) + + test('default slot work', async () => { + const wrapper = CommentMount({ + slots: { + default: () => [ + h(Comment, { + content: commentContent, + }), + ], + }, + }) + + expect(wrapper.find('.ix-comment-nested').text()).toContain(commentContent) + }) + + test('actions slot work', async () => { + const wrapper = CommentMount({ + slots: { + actions: () => [ + h(IxIcon, { + name: 'like', + }), + ], + }, + }) + + const action = wrapper.find('.ix-icon-like') + expect(action.exists()).toBe(true) + }) +}) diff --git a/packages/components/comment/demo/Basic.md b/packages/components/comment/demo/Basic.md new file mode 100644 index 000000000..5416a96ef --- /dev/null +++ b/packages/components/comment/demo/Basic.md @@ -0,0 +1,14 @@ +--- +order: 0 +title: + zh: 基本评论 + en: Basic comment +--- + +## zh + +一个基本的评论组件,带有作者、头像、时间和操作。 + +## en + +A basic comment with author, avatar, time and actions. diff --git a/packages/components/comment/demo/Basic.vue b/packages/components/comment/demo/Basic.vue new file mode 100644 index 000000000..39765d63c --- /dev/null +++ b/packages/components/comment/demo/Basic.vue @@ -0,0 +1,75 @@ + + + diff --git a/packages/components/comment/demo/Editor.md b/packages/components/comment/demo/Editor.md new file mode 100644 index 000000000..5c1dd698c --- /dev/null +++ b/packages/components/comment/demo/Editor.md @@ -0,0 +1,14 @@ +--- +order: 3 +title: + zh: 回复框 + en: Reply Editor +--- + +## zh-CN + +评论编辑器组件提供了相同样式的封装以支持自定义评论编辑器。 + +## en-US + +Comment can be used as an editor, so the user can customize the contents of the component. diff --git a/packages/components/comment/demo/Editor.vue b/packages/components/comment/demo/Editor.vue new file mode 100644 index 000000000..1c7752977 --- /dev/null +++ b/packages/components/comment/demo/Editor.vue @@ -0,0 +1,67 @@ + + + diff --git a/packages/components/comment/demo/List.md b/packages/components/comment/demo/List.md new file mode 100644 index 000000000..d65a864d2 --- /dev/null +++ b/packages/components/comment/demo/List.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh: 配合 List 组件 + en: Usage with list +--- + +## zh-CN + +配合 List 组件展现评论列表。 + +## en-US + +Displaying a series of comments using the `antd` List Component. diff --git a/packages/components/comment/demo/List.vue b/packages/components/comment/demo/List.vue new file mode 100644 index 000000000..1bf1cb235 --- /dev/null +++ b/packages/components/comment/demo/List.vue @@ -0,0 +1,53 @@ + + + diff --git a/packages/components/comment/demo/Nested.md b/packages/components/comment/demo/Nested.md new file mode 100644 index 000000000..79dd680bf --- /dev/null +++ b/packages/components/comment/demo/Nested.md @@ -0,0 +1,14 @@ +--- +order: 2 +title: + zh: 嵌套评论 + en: Nested comments +--- + +## zh-CN + +评论可以嵌套。 + +## en-US + +Comments can be nested. diff --git a/packages/components/comment/demo/Nested.vue b/packages/components/comment/demo/Nested.vue new file mode 100644 index 000000000..04d391103 --- /dev/null +++ b/packages/components/comment/demo/Nested.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/components/comment/docs/Design.en.md b/packages/components/comment/docs/Design.en.md new file mode 100644 index 000000000..d1e713d5e --- /dev/null +++ b/packages/components/comment/docs/Design.en.md @@ -0,0 +1,3 @@ +## Description + +## Usage scenarios diff --git a/packages/components/comment/docs/Design.zh.md b/packages/components/comment/docs/Design.zh.md new file mode 100644 index 000000000..c2efd31e3 --- /dev/null +++ b/packages/components/comment/docs/Design.zh.md @@ -0,0 +1,15 @@ +## 组件定义 + +CommentProps +|属性 |说明 |类型 |默认值 |全局配置 |备注 +|`author` |评论作者名字的元素 |`string \| #author` |- |- |- +|`avatar` |评论头像的元素 |`string \| CommentAvatar \| #avatar` |- |- |- +|`content` |评论的主要内容 |`string \| #content` |- |- |- +|`datetime` |展示时间描述 |`string \| #datetime` |- |- |- + +CommentSlots +|名称 |说明 |参数类型 |备注 +|`default` |嵌套评论的子项 |- |- +|`actions` |在评论内容下面呈现的操作项列表 |`#actions` |- |- - + +## 使用场景 diff --git a/packages/components/comment/docs/Index.en.md b/packages/components/comment/docs/Index.en.md new file mode 100644 index 000000000..50ca422e4 --- /dev/null +++ b/packages/components/comment/docs/Index.en.md @@ -0,0 +1,23 @@ +--- +category: components +type: Data Display +order: 0 +title: Comment +subtitle: +--- + +## API + +### IxComment + +#### CommentProps + +| Name | Description | Type | Default | Global Config | Remark | +| --- | --- | --- | --- | --- | --- | +| - | - | - | - | ✅ | - | + +#### CommentSlots + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | diff --git a/packages/components/comment/docs/Index.zh.md b/packages/components/comment/docs/Index.zh.md new file mode 100644 index 000000000..2c54e835d --- /dev/null +++ b/packages/components/comment/docs/Index.zh.md @@ -0,0 +1,28 @@ +--- +category: components +type: 数据展示 +order: 0 +title: Comment 评论 +subtitle: +--- + +## API + +### IxComment + +#### CommentProps + +| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | +| --- | --- | --- | --- | --- | --- | +|`actions` |在评论内容下面呈现的操作项列表 |`ActionButtonProps[] \| #actions` | - | - | - | +|`author` |评论作者名字的元素 |`string \| #author` | - | - | - | +|`avatar` |评论头像的元素 |`string \| CommentAvatar \| #avatar` | - | - | - | +|`content` |评论的主要内容 |`string \| #content` | - | - | - | +|`datetime` |展示时间描述 |`string \| #datetime` | - | - | - | + +#### CommentSlots + +|名称 |说明 |参数类型 |备注 +| --- | --- | --- | --- | +|`default` |嵌套评论的子项 |- |- +|`actions` |在评论内容下面呈现的操作项列表 |`#actions` |- |- - diff --git a/packages/components/comment/index.ts b/packages/components/comment/index.ts new file mode 100644 index 000000000..b5a586c86 --- /dev/null +++ b/packages/components/comment/index.ts @@ -0,0 +1,16 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { CommentComponent } from './src/types' + +import Comment from './src/Comment' + +const IxComment = Comment as unknown as CommentComponent + +export { IxComment } + +export type { CommentInstance, CommentComponent, CommentPublicProps as CommentProps, CommentAvatar } from './src/types' diff --git a/packages/components/comment/src/Comment.tsx b/packages/components/comment/src/Comment.tsx new file mode 100644 index 000000000..fdee87c35 --- /dev/null +++ b/packages/components/comment/src/Comment.tsx @@ -0,0 +1,107 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { Slots, VNodeTypes, computed, defineComponent } from 'vue' + +import { isString } from 'lodash-es' + +import { IxAvatar } from '@idux/components/avatar' +import { useGlobalConfig } from '@idux/components/config' + +import { CommentProps, commentProps } from './types' + +export default defineComponent({ + name: 'IxComment', + props: commentProps, + setup(props, { slots }) { + const common = useGlobalConfig('common') + const mergedPrefixCls = computed(() => `${common.prefixCls}-comment`) + + return () => { + const prefixCls = mergedPrefixCls.value + return ( +
+
+ {renderAvatar(props, slots, prefixCls)} + {renderContent(props, slots, prefixCls)} +
+ {renderNested(slots, prefixCls)} +
+ ) + } + }, +}) + +const renderAvatar = (props: CommentProps, slots: Slots, prefixCls: string) => { + let avatarNode: VNodeTypes | undefined + if (slots.avatar) { + avatarNode = slots.avatar() + } else if (props.avatar) { + const { avatar } = props + const avatarProps = isString(avatar) ? { src: avatar } : avatar + avatarNode = + } + return avatarNode ?
{avatarNode}
: undefined +} + +const renderAuthor = (props: CommentProps, slots: Slots, prefixCls: string) => { + const author = (slots.author && slots.author()) || props.author + const datetime = (slots.datetime && slots.datetime()) || props.datetime + + return ( + + ) +} + +const renderAction = (props: CommentProps, slots: Slots, prefixCls: string) => { + if (!slots.actions) { + return undefined + } + + let actionNode: VNodeTypes | undefined = slots.actions() + + actionNode = actionNode!.map((action: VNodeTypes, index: number) => { + return
  • {action}
  • + }) + + return
      {actionNode}
    +} + +const renderContent = (props: CommentProps, slots: Slots, prefixCls: string) => { + let contentNode: VNodeTypes | string | undefined + + if (slots.content) { + contentNode = slots.content() + } else if (props.content) { + contentNode = props.content + } + + return contentNode ? ( +
    + {renderAuthor(props, slots, prefixCls)} +
    {contentNode}
    + {renderAction(props, slots, prefixCls)} +
    + ) : ( +
    + {renderAuthor(props, slots, prefixCls)} + {renderAction(props, slots, prefixCls)} +
    + ) +} + +const renderNested = (slots: Slots, prefixCls: string) => { + let childrenNode: VNodeTypes | undefined + if (slots.default) { + childrenNode =
    {slots.default()}
    + } + + return childrenNode +} diff --git a/packages/components/comment/src/types.ts b/packages/components/comment/src/types.ts new file mode 100644 index 000000000..306fa8288 --- /dev/null +++ b/packages/components/comment/src/types.ts @@ -0,0 +1,26 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ExtractInnerPropTypes, ExtractPublicPropTypes } from '@idux/cdk/utils' +import type { AvatarProps } from '@idux/components/avatar' +import type { DefineComponent, HTMLAttributes } from 'vue' + +import { IxPropTypes } from '@idux/cdk/utils' + +export type CommentAvatar = AvatarProps + +export const commentProps = { + author: IxPropTypes.string, + avatar: IxPropTypes.oneOfType([String, IxPropTypes.object()]), + content: IxPropTypes.string, + datetime: IxPropTypes.string, +} + +export type CommentProps = ExtractInnerPropTypes +export type CommentPublicProps = ExtractPublicPropTypes +export type CommentComponent = DefineComponent & CommentPublicProps> +export type CommentInstance = InstanceType> diff --git a/packages/components/comment/style/index.less b/packages/components/comment/style/index.less new file mode 100644 index 000000000..04c4f8401 --- /dev/null +++ b/packages/components/comment/style/index.less @@ -0,0 +1,99 @@ +@import '../../style/mixins/reset.less'; + +.@{comment-prefix} { + .reset-component(); + + &-inner { + display: flex; + padding: @comment-padding-md; + } + + &-avatar { + position: relative; + flex-shrink: 0; + margin-right: @comment-margin-right; + cursor: pointer; + + img { + width: 32px; + height: 32px; + border-radius: 50%; + } + } + + &-content { + position: relative; + flex: 1 1 auto; + min-width: 1px; + font-size: @comment-font-size-md; + word-wrap: break-word; + + &-author { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + margin-bottom: @comment-author-margin-bottom; + font-size: @comment-font-size-md; + + & > a, + & > span { + padding-right: @comment-content-padding-right; + font-size: @comment-font-size-sm; + line-height: 18px; + } + + &-name { + color: @comment-author-name-color; + font-size: @comment-font-size-md; + transition: color 0.3s; + + > * { + color: @comment-author-name-color; + + &:hover { + color: @comment-author-name-color; + } + } + } + + &-time { + color: @comment-author-time-color; + white-space: nowrap; + cursor: auto; + } + } + + &-detail p { + margin-bottom: @comment-content-detail-p-margin-bottom; + white-space: pre-wrap; + } + } + + &-actions { + margin-top: @comment-actions-margin-top; + margin-bottom: @comment-actions-margin-bottom; + padding-left: 0; + + > li { + display: inline-block; + color: @comment-action-color; + + > span { + margin-right: 10px; + color: @comment-action-color; + font-size: @comment-font-size-sm; + cursor: pointer; + transition: color 0.3s; + user-select: none; + + &:hover { + color: @comment-action-hover-color; + } + } + } + } + + &-nested { + margin-left: @comment-nest-indent; + } +} diff --git a/packages/components/comment/style/themes/default.less b/packages/components/comment/style/themes/default.less new file mode 100644 index 000000000..329463be6 --- /dev/null +++ b/packages/components/comment/style/themes/default.less @@ -0,0 +1,3 @@ +@import '../../../style/themes/default.less'; +@import '../index.less'; +@import './default.variable.less'; diff --git a/packages/components/comment/style/themes/default.ts b/packages/components/comment/style/themes/default.ts new file mode 100644 index 000000000..027ca3f89 --- /dev/null +++ b/packages/components/comment/style/themes/default.ts @@ -0,0 +1,4 @@ +// style dependencies +import '@idux/components/style/core/default' + +import './default.less' diff --git a/packages/components/comment/style/themes/default.variable.less b/packages/components/comment/style/themes/default.variable.less new file mode 100644 index 000000000..783522530 --- /dev/null +++ b/packages/components/comment/style/themes/default.variable.less @@ -0,0 +1,15 @@ +@comment-padding-md: @spacing-md 0; +@comment-margin-right: @margin-md; +@comment-font-size-md: @form-font-size-md; +@comment-font-size-sm: @form-font-size-sm; +@comment-author-margin-bottom: @spacing-xs; +@comment-content-padding-right: @spacing-sm; +@comment-author-name-color: @text-color-secondary; +@comment-author-time-color: #ccc; +@comment-actions-margin-top: @margin-md; +@comment-actions-margin-bottom: inherit; +@comment-action-color: @text-color-secondary; +@comment-action-hover-color: #595959; +@comment-content-detail-p-margin-bottom: inherit; +@comment-nest-indent: 44px; + diff --git a/packages/components/default.less b/packages/components/default.less index 9555657e9..73e3c4cd6 100644 --- a/packages/components/default.less +++ b/packages/components/default.less @@ -17,6 +17,7 @@ @import './breadcrumb/style/themes/default.less'; @import './button/style/themes/default.less'; @import './card/style/themes/default.less'; +@import './comment/style/themes/default.less'; @import './carousel/style/themes/default.less'; @import './checkbox/style/themes/default.less'; @import './collapse/style/themes/default.less'; diff --git a/packages/components/index.ts b/packages/components/index.ts index e503afaba..1b4f4ae44 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -19,6 +19,7 @@ import { IxCard, IxCardGrid } from '@idux/components/card' import { IxCarousel } from '@idux/components/carousel' import { IxCheckbox, IxCheckboxGroup } from '@idux/components/checkbox' import { IxCollapse, IxCollapsePanel } from '@idux/components/collapse' +import { IxComment } from '@idux/components/comment' import { IxDatePicker } from '@idux/components/date-picker' import { IxDivider } from '@idux/components/divider' import { IxDrawer, IxDrawerProvider } from '@idux/components/drawer' @@ -81,6 +82,7 @@ const components = [ IxCard, IxCardGrid, IxCarousel, + IxComment, IxCheckbox, IxCheckboxGroup, IxCollapse, diff --git a/packages/components/style/variable/prefix.less b/packages/components/style/variable/prefix.less index 4ef9b96b2..de77e20f4 100644 --- a/packages/components/style/variable/prefix.less +++ b/packages/components/style/variable/prefix.less @@ -26,6 +26,7 @@ @avatar-prefix: ~'@{idux-prefix}-avatar'; @badge-prefix: ~'@{idux-prefix}-badge'; @card-prefix: ~'@{idux-prefix}-card'; +@comment-prefix: ~'@{idux-prefix}-comment'; @list-prefix: ~'@{idux-prefix}-list'; @list-item-prefix: ~'@{idux-prefix}-list-item'; @carousel-prefix: ~'@{idux-prefix}-carousel';