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: popover component & new topic actions layout #580

Merged
merged 10 commits into from
May 14, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/design/components/Popover/Popover.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Meta, StoryFn } from '@storybook/react';
import React from 'react';

import Button from '../Button';
import type { PopoverProps } from '.';
import Popover from '.';

const storyMeta: Meta<typeof Popover> = {
title: 'modern/Popover',
component: Popover,
};

export default storyMeta;

const Template: StoryFn<PopoverProps> = (args) => {
return <Popover {...args} />;
};

export const Default = Template.bind({});
Default.args = {
content: <div style={{ padding: 30 }}>Popover content</div>,
children: <Button>Popover</Button>,
};
24 changes: 24 additions & 0 deletions packages/design/components/Popover/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import './style';

import classNames from 'classnames';
import React from 'react';

export interface PopoverProps {
content: React.ReactNode;
children: React.ReactNode;
className?: string;
}

const Popover = ({ children, content, className }: PopoverProps) => {
return (
<div className={classNames('bgm-popover', className)}>
{children}
{/* 添加一个wrapper使绝对定位元素能够水平居中 */}
<div className='bgm-popover__container'>
<div className='bgm-popover__content'>{content}</div>
</div>
</div>
);
};

export default Popover;
29 changes: 29 additions & 0 deletions packages/design/components/Popover/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@import '../../../theme/base.less';

.bgm-popover {
display: inline-block;

&__container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}

&__content {
border: 1px solid @gray-10;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
background-color: white;
border-radius: 17px;
position: absolute;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.15s linear;
z-index: 99;
}

&:hover &__content {
visibility: visible;
opacity: 1;
}
}
1 change: 1 addition & 0 deletions packages/design/components/Popover/style/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './index.less';
y-young marked this conversation as resolved.
Show resolved Hide resolved
72 changes: 29 additions & 43 deletions packages/design/components/Topic/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { Friend, OriginalPoster, TopicClosed, TopicReopen, TopicSilent } from '@
import { getUserProfileLink } from '@bangumi/utils/pages';

import Avatar from '../../components/Avatar';
import Button from '../../components/Button';
import RichContent from '../../components/RichContent';
import Typography from '../../components/Typography';
import { toast } from '../Toast';
import CommentActions from './CommentActions';
import CommentInfo from './CommentInfo';
import ReplyForm from './ReplyForm';

Expand Down Expand Up @@ -161,46 +161,6 @@ const Comment: FC<CommentProps> = ({
}
};

const commentActions = user && !isDeleted && (
<div className='bgm-comment__opinions'>
{showReplyEditor ? (
<ReplyForm
autoFocus
topicId={topicId}
replyTo={props.id}
placeholder={`回复 @${creator.nickname}:`}
content={replyContent}
onChange={setReplyContent}
onCancel={() => {
setShowReplyEditor(false);
}}
onSuccess={handleReplySuccess}
/>
) : (
<>
<Button type='secondary' size='small' onClick={startReply}>
回复
</Button>
<Button type='secondary' size='small'>
+1
</Button>
{user.id === creator.id && (
<>
{!replies?.length && (
<Button.Link type='text' size='small' to={`/group/reply/${props.id}/edit`}>
编辑
</Button.Link>
)}
<Button type='text' size='small' onClick={handleDeleteReply}>
删除
</Button>
</>
)}
</>
)}
</div>
);

return (
<div>
<div className={headerClassName} id={`post_${props.id}`}>
Expand All @@ -219,11 +179,37 @@ const Comment: FC<CommentProps> = ({
{isFriend ? <Friend /> : null}
{!isReply && creator.sign ? <span>{`(${creator.sign})`}</span> : null}
</div>
<CommentInfo createdAt={createdAt} floor={floor} id={`post_${props.id}`} />
<div className='comment-info'>
<CommentInfo createdAt={createdAt} floor={floor} id={props.id} />
{user && !isDeleted && (
<CommentActions
id={props.id}
onReply={startReply}
onDelete={handleDeleteReply}
isAuthor={user?.id === creator.id}
editable={!replies?.length}
/>
)}
</div>
</span>
<RenderContent state={state} text={text} />
</div>
{commentActions}
{showReplyEditor && (
<div className='bgm-comment__opinions'>
<ReplyForm
autoFocus
topicId={topicId}
replyTo={props.id}
placeholder={`回复 @${creator.nickname}:`}
content={replyContent}
onChange={setReplyContent}
onCancel={() => {
setShowReplyEditor(false);
}}
onSuccess={handleReplySuccess}
/>
</div>
)}
</div>
</div>
{replies?.map((reply, idx) => (
Expand Down
51 changes: 51 additions & 0 deletions packages/design/components/Topic/CommentActions.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { StoryFn } from '@storybook/react';
import React from 'react';
import { BrowserRouter } from 'react-router-dom';

import type { CommentActionsProps } from './CommentActions';
import CommentActions from './CommentActions';

export default {
title: 'Topic/CommentActions',
component: CommentActions,
};

const Template: StoryFn<CommentActionsProps> = (args) => {
return (
<BrowserRouter>
<CommentActions {...args} id={375793} />
</BrowserRouter>
);
};

export const Basic = Template.bind({});

export const WithText = Template.bind({});
WithText.args = {
showText: true,
};

export const IsAuthor = Template.bind({});
IsAuthor.args = {
isAuthor: true,
};
IsAuthor.parameters = {
docs: {
description: {
story: '显示编辑和删除按钮',
},
},
};

export const NonEditable = Template.bind({});
NonEditable.args = {
isAuthor: true,
editable: false,
};
NonEditable.parameters = {
docs: {
description: {
story: '显示删除按钮,但隐藏编辑按钮,例如有子回复时',
},
},
};
62 changes: 62 additions & 0 deletions packages/design/components/Topic/CommentActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';

import { Comment as CommentIcon, More } from '@bangumi/icons';

import Button from '../../components/Button';
import Popover from '../Popover';

export interface CommentActionsProps {
id: number;
onReply?: () => void;
onDelete?: () => void;
isAuthor?: boolean;
editable?: boolean;
showText?: boolean;
}

const CommentActions = ({
id,
onReply,
onDelete,
isAuthor = false,
editable = true,
showText = false,
}: CommentActionsProps) => {
return (
<div className='bgm-comment-actions'>
<Button type='plain' size='small' onClick={onReply} title='回复'>
<CommentIcon />
{showText && '回复'}
</Button>
{/* TODO: 实现贴贴功能 */}
<Popover
content={
<div className='bgm-comment-actions__popover'>
{isAuthor && (
<>
{editable && (
<Button.Link type='text' size='small' to={`/group/reply/${id}/edit`}>
编辑
</Button.Link>
)}
<Button type='text' size='small' onClick={onDelete}>
删除
</Button>
</>
)}
{/* TODO: 实现绝交和报告疑虑功能 */}
<Button type='text'>绝交</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

绝交是啥功能 (bgm38) (不是代码问题,纯好奇)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

啊?就是原站的拉黑功能啊

https://bgm.tv/settings/privacy

与以下用户绝交 · 不再看到用户的所有话题、评论、日志、私信、提醒

<Button type='text'>报告疑虑</Button>
</div>
}
>
<Button type='plain' size='small' className='bgm-comment-actions__more' title='其他'>
<More />
{showText && '其他'}
</Button>
</Popover>
</div>
);
};

export default CommentActions;
6 changes: 3 additions & 3 deletions packages/design/components/Topic/CommentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface CommentInfoProps {
floor: string | number;
isSpecial?: boolean;
createdAt: number | string | Date;
id?: string;
id?: number;
}

const spaces = '\u00A0'.repeat(2);
Expand All @@ -24,10 +24,10 @@ const CommentInfo: FC<CommentInfoProps> = ({ floor, createdAt, isSpecial = false

return !isSpecial ? (
<span className='bgm-topic__commentInfo'>
<a href={`#${id}`}>#{floor}</a>
<a href={`#post_${id}`}>#{floor}</a>
{spaces}|{spaces}
{date}
{spaces}|{spaces}!
{spaces}|{spaces}
</span>
) : (
<span className='bgm-topic__commentInfo'>{date}</span>
Expand Down
12 changes: 5 additions & 7 deletions packages/design/components/Topic/__test__/Comment.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ describe('Normal Comment', () => {
const props = buildProps(false, repliesComment, '233', 233, user);
const { container } = render(<Comment {...props} />);
// 选取主评论的操作区域
const actions = container.querySelector(
'.bgm-comment__box > .bgm-comment__opinions',
)?.textContent;
const actions = container.querySelector('.bgm-comment__box .bgm-comment-actions')?.textContent;
expect(actions?.includes('编辑')).toBeFalsy();
expect(actions?.includes('删除')).toBeTruthy();
});
Expand All @@ -119,10 +117,10 @@ describe('Normal Comment', () => {

it('click reply button should show editor form', () => {
const props = buildProps(false);
const { getByText, container } = render(<Comment {...props} />);
const { getByText, container, getByTitle } = render(<Comment {...props} />);
expect(container.getElementsByClassName('bgm-editor__form').length).toBe(0);

fireEvent.click(getByText('回复'));
fireEvent.click(getByTitle('回复'));
expect(container.getElementsByClassName('bgm-editor__form').length).toBe(1);

fireEvent.click(getByText('取消'));
Expand All @@ -141,11 +139,11 @@ describe('Normal Comment', () => {

const onSuccess = vi.fn();
const props = buildProps(false);
const { getByText, container } = render(
const { getByText, container, getByTitle } = render(
<Comment {...props} onCommentUpdate={onSuccess} topicId={1} />,
);
const fillAndSubmit = () => {
fireEvent.click(getByText('回复'));
fireEvent.click(getByTitle('回复'));
fireEvent.change(container.querySelector('textarea')!, { target: { value: '233' } });
fireEvent.click(getByText('写好了'));
};
Expand Down
37 changes: 37 additions & 0 deletions packages/design/components/Topic/__test__/CommentActions.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render as _render } from '@testing-library/react';
import React from 'react';
import { BrowserRouter } from 'react-router-dom';

import type { CommentActionsProps } from '../CommentActions';
import CommentActions from '../CommentActions';

const render = (props: CommentActionsProps) =>
_render(
<BrowserRouter>
<CommentActions {...props} />
</BrowserRouter>,
);

describe('Basic Usage', () => {
it('should render', () => {
expect(render({ id: 233 })).toMatchSnapshot();
});
});

describe('With Text', () => {
it('should render', () => {
expect(render({ id: 233, showText: true })).toMatchSnapshot();
});
});

describe('Is Author', () => {
it('should render', () => {
expect(render({ id: 233, isAuthor: true })).toMatchSnapshot();
});
});

describe('Non-editable', () => {
it('should render', () => {
expect(render({ id: 233, isAuthor: true, editable: false })).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ it('normal comment should render floor', () => {
const createdAt = Math.trunc(new Date('2022-09-22T06:03:21Z').getTime() / 1000);
const { container } = render(<CommentInfo floor={1} createdAt={createdAt} />);
const el = container.getElementsByClassName('bgm-topic__commentInfo')[0];
expect(el!.textContent).toBe('#1  |  2022-9-22 06:03  |  !');
expect(el!.textContent).toBe('#1  |  2022-9-22 06:03  |  ');
});
Loading