Repository navigation
feat(comment): add mobile comment component #978
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
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
0ef131a
feat(comment): import comment type & desc
slatejack 6a28681
feat(comment): update comment type, create comment component, import …
slatejack 5f78e8e
feat(comment): add onActions and onFolds operations
slatejack d21bfbd
feat(comment): add comment demo page
slatejack d424582
style(comment): update submodule branch
slatejack 5e2789f
test(comment): update test snapshots
slatejack 90462bb
fix(comment): fix comment folds position
slatejack 3497b84
feat(comment): update comment demo, align figma
slatejack 312075f
feat(comment): update submodule
slatejack 9008233
test(comment): update snapshots
slatejack c450f83
docs(comment): update comment component overview style in overview page
slatejack bd27bf0
fix(comment): update comment component style, fix the issue where def…
slatejack f20bde5
fix(comment): fix comment reply style, fix hidden reply in collapse s…
slatejack ee1bdb7
feat(comment): update comment demo, enable reply in demo
slatejack 2a9441b
test(comment): update snapshots
slatejack 41e2378
chore: update common
github-actions[bot] 5a18327
chore: merge develop
github-actions[bot] e7b5eba
chore: update snapshot
github-actions[bot] 62f5de3
fix(comment): fix comment style, alignment figma page
slatejack 48abd5f
fix(comment): fix comment component style, alignment figma page
slatejack 01956f9
fix(comment): fix comment demo style
slatejack 5acf95c
fix(comment): fix comment demo style, use css token
slatejack 88d79a7
chore: update common
github-actions[bot] 92fd253
fix: fix cr
anlyyao 00963a1
fix(comment): fix demo style
slatejack ed30c61
fix(comment): update submodule comment style
slatejack c93c8e0
chore: update common
github-actions[bot] 9e6fb73
chore: merge develop
github-actions[bot] 0d21408
test: update snapshots
slatejack 53c3388
Merge branch 'develop' into feature/comment
anlyyao 5509735
Merge branch 'develop' into feature/comment
anlyyao File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| import React, { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react'; | ||
| import { isObject } from 'lodash-es'; | ||
| import { ChevronDownSIcon, ChevronUpSIcon } from 'tdesign-icons-react'; | ||
| import { CommentActionItem, CommentFold, CommentFoldState, TdCommentProps } from './type'; | ||
| import { usePrefixClass } from '../hooks/useClass'; | ||
| import Avatar, { AvatarProps } from '../avatar'; | ||
| import parseTNode, { parseContentTNode } from '../_util/parseTNode'; | ||
| import { StyledProps, TNode } from '../common'; | ||
|
|
||
| export interface CommentProps extends TdCommentProps, StyledProps {} | ||
|
|
||
| const DEFAULT_COMMENT_FOLDS: CommentFold = { | ||
| state: 'collapsed', | ||
| total: 0, | ||
| step: 3, | ||
| content: { | ||
| collapsed: '展开回复', | ||
| partial: ['展开更多回复', '收起'], // [主文案, 副文案] | ||
| expanded: '收起', | ||
| }, | ||
| }; | ||
| const Comment: React.FC<TdCommentProps> = (props) => { | ||
| const { actions, author, avatar, content, datetime, folds, defaultFolds, reply, children, onActions, onFolds } = | ||
| props; | ||
| const [innerFoldsState, setInnerFoldsState] = useState<CommentFoldState>(defaultFolds?.state || 'collapsed'); | ||
| const foldSteps = useRef(0); | ||
| const replyBodyRef = useRef<HTMLDivElement>(null); | ||
| const replyInnerRef = useRef<HTMLDivElement>(null); | ||
| const rootClassName = usePrefixClass('comment'); | ||
|
|
||
| const isControlled = folds !== undefined; | ||
| const commentFolds = folds || defaultFolds || DEFAULT_COMMENT_FOLDS; | ||
|
|
||
| // 受控模式下,当外部 folds.state 变化时同步 foldSteps | ||
| useEffect(() => { | ||
| if (!isControlled) { | ||
| return; | ||
| } | ||
| if (folds.state === 'collapsed') { | ||
| foldSteps.current = 0; | ||
| } else if (folds.state === 'expanded') { | ||
| foldSteps.current = (commentFolds.step || 3) - 1; | ||
| } | ||
| }, [isControlled, folds?.state, commentFolds.step]); | ||
|
|
||
| // 当前实际使用的折叠状态 | ||
| const curFoldsState: CommentFoldState = isControlled ? folds.state || 'collapsed' : innerFoldsState; | ||
|
|
||
| // 首次渲染前同步设置高度,避免闪烁(无动画) | ||
| useLayoutEffect(() => { | ||
| const body = replyBodyRef.current; | ||
| const inner = replyInnerRef.current; | ||
| if (body && inner) { | ||
| body.style.height = `${inner.scrollHeight}px`; | ||
| } | ||
| }, []); | ||
|
|
||
| // 监听回复区内容高度变化,驱动过渡动画 | ||
| useEffect(() => { | ||
| const body = replyBodyRef.current; | ||
| const inner = replyInnerRef.current; | ||
| if (!body || !inner) return; | ||
|
|
||
| const observer = new ResizeObserver(() => { | ||
| body.style.height = `${inner.scrollHeight}px`; | ||
| }); | ||
|
|
||
| observer.observe(inner); | ||
| return () => observer.disconnect(); | ||
| }, []); | ||
|
|
||
| const handleClickActions = (event: MouseEvent, action: CommentActionItem | TNode) => { | ||
| onActions?.({ action, e: event }); | ||
| }; | ||
|
|
||
| const handleClickFold = (event: MouseEvent, isExpand: boolean) => { | ||
| if (!isExpand) { | ||
| // 收起:重置为 collapsed | ||
| foldSteps.current = 0; | ||
| if (!isControlled) { | ||
| setInnerFoldsState('collapsed'); | ||
| } | ||
| onFolds?.({ | ||
| fold: { ...commentFolds, state: 'collapsed' }, | ||
| e: event, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| // 展开:递增步进并计算下一状态 | ||
| foldSteps.current += 1; | ||
| const nextState: CommentFoldState = foldSteps.current >= (commentFolds.step || 3) - 1 ? 'expanded' : 'partial'; | ||
|
|
||
| if (!isControlled) { | ||
| setInnerFoldsState(nextState); | ||
| } | ||
| onFolds?.({ | ||
| fold: { ...commentFolds, state: nextState }, | ||
| e: event, | ||
| }); | ||
| }; | ||
|
|
||
| const renderAvatar = () => { | ||
| if (isObject(avatar) && !React.isValidElement(avatar) && typeof avatar !== 'function') { | ||
| return <Avatar className={`${rootClassName}__avatar`} {...(avatar as AvatarProps)} />; | ||
| } | ||
| return parseTNode(avatar); | ||
| }; | ||
|
|
||
| const renderAuthor = () => <div className={`${rootClassName}__author`}>{parseTNode(author)}</div>; | ||
| const renderContent = () => <div className={`${rootClassName}__content`}>{parseTNode(content)}</div>; | ||
| const renderFooter = () => { | ||
| const renderActions = () => { | ||
| if (!actions) { | ||
| return null; | ||
| } | ||
| if (!Array.isArray(actions)) { | ||
| return ( | ||
| <div onClick={(e) => handleClickActions(e as unknown as MouseEvent, actions)}>{parseTNode(actions)}</div> | ||
| ); | ||
| } | ||
| const renderLeftPlacement = () => | ||
| actions | ||
| .filter((item) => item.placement === 'start') | ||
| .map((item) => ( | ||
| <div key={item.key} onClick={(e) => handleClickActions(e as unknown as MouseEvent, item)}> | ||
| {parseContentTNode(item.content, { disabled: item.disabled })} | ||
| </div> | ||
| )); | ||
| const renderRightPlacement = () => | ||
| actions | ||
| .filter((item) => item.placement === 'end') | ||
| .map((item) => ( | ||
| <div key={item.key} onClick={(e) => handleClickActions(e as unknown as MouseEvent, item)}> | ||
| {parseContentTNode(item.content, { disabled: item.disabled })} | ||
| </div> | ||
| )); | ||
| return ( | ||
| <> | ||
| <div className={`${rootClassName}-block`}>{renderLeftPlacement()}</div> | ||
| <div className={`${rootClassName}-block`}>{renderRightPlacement()}</div> | ||
| </> | ||
| ); | ||
| }; | ||
| return ( | ||
| <div className={`${rootClassName}__bottom`}> | ||
| <div className={`${rootClassName}__date`}>{parseTNode(datetime)}</div> | ||
| <div className={`${rootClassName}__actions`}>{renderActions()}</div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| const renderFolds = () => { | ||
| const renderFoldsContent = () => { | ||
| if (curFoldsState === 'collapsed') { | ||
| // 全收起 | ||
| return ( | ||
| <div | ||
| className={`${rootClassName}__folds-item`} | ||
| onClick={(e) => handleClickFold(e as unknown as MouseEvent, true)} | ||
| > | ||
| {commentFolds.content.collapsed as ReactNode} | ||
| <ChevronDownSIcon size="16px" /> | ||
| </div> | ||
| ); | ||
| } | ||
| if (curFoldsState === 'partial') { | ||
| // 半展开 | ||
| return ( | ||
| <> | ||
| <div | ||
| className={`${rootClassName}__folds-item`} | ||
| onClick={(e) => handleClickFold(e as unknown as MouseEvent, true)} | ||
| > | ||
| {commentFolds.content.partial[0] as ReactNode} | ||
| <ChevronDownSIcon size="16px" /> | ||
| </div> | ||
| <div | ||
| className={`${rootClassName}__folds-item`} | ||
| onClick={(e) => handleClickFold(e as unknown as MouseEvent, false)} | ||
| > | ||
| {commentFolds.content.partial[1] as ReactNode} | ||
| <ChevronUpSIcon size="16px" /> | ||
| </div> | ||
| </> | ||
| ); | ||
| } | ||
| // 全展开 | ||
| return ( | ||
| <div | ||
| className={`${rootClassName}__folds-item`} | ||
| onClick={(e) => handleClickFold(e as unknown as MouseEvent, false)} | ||
| > | ||
| {commentFolds.content.expanded as ReactNode} | ||
| <ChevronUpSIcon size="16px" /> | ||
| </div> | ||
| ); | ||
| }; | ||
| if (!commentFolds.total || commentFolds.total <= 1) { | ||
| return null; | ||
| } | ||
| return <div className={`${rootClassName}__folds`}>{renderFoldsContent()}</div>; | ||
| }; | ||
|
|
||
| const renderReply = () => { | ||
| const replyContent = reply || children; | ||
| return ( | ||
| <div className={`${rootClassName}__reply`}> | ||
| <div ref={replyBodyRef} className={`${rootClassName}__reply-body`}> | ||
| <div ref={replyInnerRef} className={`${rootClassName}__reply-inner`}> | ||
| {parseTNode(replyContent)} | ||
| </div> | ||
| </div> | ||
| {renderFolds()} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className={`${rootClassName}`}> | ||
|
slatejack marked this conversation as resolved.
|
||
| <div className={`${rootClassName}__inner`}> | ||
| {renderAvatar()} | ||
| <div className={`${rootClassName}__detail`}> | ||
| {renderAuthor()} | ||
| {renderContent()} | ||
| {renderFooter()} | ||
| </div> | ||
| </div> | ||
| {renderReply()} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Comment; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import React from 'react'; | ||
| import { Button, Comment, Tag } from 'tdesign-mobile-react'; | ||
| import { ThumbUpIcon, Uncomfortable1Icon } from 'tdesign-icons-react'; | ||
|
|
||
| export default function () { | ||
| return ( | ||
| <div className="mobile-comment"> | ||
| <Comment | ||
| avatar={{ shape: 'circle', size: '32px', image: 'https://tdesign.gtimg.com/mobile/demos/avatar1.png' }} | ||
| author={ | ||
| <div className="mobile-comment__author"> | ||
| <div>Name1 名称</div> | ||
| <Tag className="mobile-comment__author-tag" variant="light" theme="primary" shape="round"> | ||
| 作者 | ||
| </Tag> | ||
| </div> | ||
| } | ||
| actions={[ | ||
| { placement: 'start', content: <div className="mobile-comment__reply-button">回复</div>, key: 'reply' }, | ||
| { | ||
| placement: 'end', | ||
| content: ( | ||
| <div className="mobile-comment__actions"> | ||
| <Button className="mobile-comment__actions-button" icon={<ThumbUpIcon size="14px" />} variant="text"> | ||
|
slatejack marked this conversation as resolved.
|
||
| 6 | ||
| </Button> | ||
| <Button | ||
| icon={<Uncomfortable1Icon size="14px" />} | ||
| className="mobile-comment__actions-button" | ||
| variant="text" | ||
| /> | ||
| </div> | ||
| ), | ||
| key: 'actions', | ||
| }, | ||
| ]} | ||
| content="这是一段很长很长很长很长很长很长的评论内容。" | ||
| datetime="今天16:38·广东" | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.