Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Commit 1b5fd6e

Browse files
authored
feat(comment): actions menu button (#1127)
* fix(copy-button): avoid un-expected animation with value change * fix(tooltip): z-index edge case * fix(switcher-icon): active style adjust * fix(report): reset view on close * feat(comment): basic fold/extand UI/UX * feat(comment): range bar wip * refactor(fold): UX & re-design looks * fix: mini * feat(comment): put action into menu
1 parent cb39a54 commit 1b5fd6e

File tree

25 files changed

+328
-68
lines changed

25 files changed

+328
-68
lines changed

src/components/Buttons/DropdownButton/OptionPanel.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { FC, memo } from 'react'
2-
import T from 'prop-types'
32

43
import { ICON } from '@/config'
54
import { cutRest } from '@/utils'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { FC, memo } from 'react'
2+
import { isEmpty } from 'ramda'
3+
4+
import { ICON } from '@/config'
5+
import { cutRest } from '@/utils'
6+
import type { TOption } from './index'
7+
8+
import {
9+
Wrapper,
10+
Block,
11+
BlockA,
12+
Item,
13+
Icon,
14+
Title,
15+
LinkIcon,
16+
Divider,
17+
} from '../styles/menu_button/menu'
18+
19+
// there is two types of block, normal block and link
20+
const OptionBlock = ({ item, onClick }) => {
21+
if (item.link) {
22+
return (
23+
<BlockA as="a" href={item.link}>
24+
<Item>
25+
<Icon src={item.icon} />
26+
<Title>{cutRest(item.title, 50)}</Title>
27+
<LinkIcon src={`${ICON}/shape/link-hint.svg`} />
28+
</Item>
29+
</BlockA>
30+
)
31+
}
32+
return (
33+
<Block onClick={onClick}>
34+
<Item>
35+
<Icon src={item.icon} />
36+
<Title>{cutRest(item.title, 50)}</Title>
37+
</Item>
38+
</Block>
39+
)
40+
}
41+
42+
type TProps = {
43+
options: TOption[]
44+
extraOptions: TOption[]
45+
panelMinWidth: string
46+
onClick?: (key?: string) => void
47+
}
48+
49+
const Menu: FC<TProps> = ({
50+
options,
51+
extraOptions,
52+
onClick,
53+
panelMinWidth,
54+
}) => {
55+
return (
56+
<Wrapper panelMinWidth={panelMinWidth}>
57+
{options.map((item) => (
58+
<OptionBlock
59+
key={item.key}
60+
item={item}
61+
onClick={() => onClick(item.key)}
62+
/>
63+
))}
64+
{!isEmpty(extraOptions) && <Divider />}
65+
{extraOptions.map((item) => (
66+
<OptionBlock
67+
key={item.key}
68+
item={item}
69+
onClick={() => onClick(item.key)}
70+
/>
71+
))}
72+
</Wrapper>
73+
)
74+
}
75+
76+
export default memo(Menu)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { FC, ReactNode, memo } from 'react'
2+
3+
import { buildLog } from '@/utils'
4+
5+
import type { TTooltipPlacement } from '@/spec'
6+
7+
import Tooltip from '@/components/Tooltip'
8+
9+
import Menu from './Menu'
10+
11+
// import { Wrapper } from '../styles/menu_button'
12+
13+
const log = buildLog('C:MenuButton')
14+
15+
export type TOption = {
16+
title: string
17+
key: string
18+
icon?: string
19+
link?: string
20+
}
21+
22+
type TProps = {
23+
children: ReactNode
24+
options: TOption[]
25+
extraOptions: TOption[]
26+
placement?: TTooltipPlacement
27+
panelMinWidth?: string
28+
onClick: (key?: string) => void
29+
}
30+
31+
const MenuButton: FC<TProps> = ({
32+
children,
33+
options,
34+
extraOptions = [],
35+
onClick = log,
36+
placement = 'top-end',
37+
panelMinWidth = '100px',
38+
}) => {
39+
return (
40+
<Tooltip
41+
placement={placement}
42+
trigger="click"
43+
hideOnClick
44+
content={
45+
<Menu
46+
options={options}
47+
extraOptions={extraOptions}
48+
onClick={onClick}
49+
panelMinWidth={panelMinWidth}
50+
/>
51+
}
52+
noPadding
53+
>
54+
{children}
55+
</Tooltip>
56+
)
57+
}
58+
59+
export default memo(MenuButton)

src/components/Buttons/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { default as ArrowLink } from './ArrowLink'
55
export { default as PublishButton } from './PublishButton'
66
export { default as OrButton } from './OrButton'
77
export { default as DropdownButton } from './DropdownButton'
8+
export { default as MenuButton } from './MenuButton'
89
export { default as NotifyButton } from './NotifyButton'
910
export { default as FollowButton } from './FollowButton'
1011
export { default as YesOrNoButtons } from './YesOrNoButtons'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import styled from 'styled-components'
2+
3+
import { css } from '@/utils'
4+
5+
export const Wrapper = styled.div`
6+
${css.flex('align-center')};
7+
width: 100%;
8+
position: relative;
9+
`
10+
export const Holder = 1
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import styled from 'styled-components'
2+
3+
import Img from '@/Img'
4+
import { css, theme } from '@/utils'
5+
6+
export const Wrapper = styled.div<{ panelMinWidth: string }>`
7+
${css.flexColumn('align-center')};
8+
width: 100%;
9+
min-width: ${({ panelMinWidth }) => panelMinWidth};
10+
max-height: 300px;
11+
overflow: hidden;
12+
`
13+
export const Block = styled.div`
14+
${css.flex('align-start')};
15+
width: 100%;
16+
padding: 6px 10px;
17+
padding-left: 15px;
18+
19+
&:hover {
20+
background: #0d3e4e;
21+
cursor: pointer;
22+
}
23+
`
24+
export const Divider = styled.div`
25+
width: 100%;
26+
height: 1px;
27+
background: #0d3e4e;
28+
margin-top: 3px;
29+
margin-bottom: 3px;
30+
`
31+
export const BlockA = styled(Block)`
32+
text-decoration: none;
33+
`
34+
export const Item = styled.div`
35+
${css.flex('align-center')};
36+
`
37+
export const Icon = styled(Img)`
38+
fill: ${theme('thread.articleDigest')};
39+
${css.size(12)};
40+
margin-right: 10px;
41+
opacity: 0.8;
42+
43+
${Item}:hover & {
44+
opacity: 1;
45+
}
46+
`
47+
export const Title = styled.div`
48+
color: ${theme('thread.articleTitle')};
49+
font-size: 13px;
50+
`
51+
export const LinkIcon = styled(Img)`
52+
${css.size(10)};
53+
fill: ${theme('thread.articleDigest')};
54+
margin-left: 7px;
55+
`
56+
export const Desc = styled.div`
57+
color: ${theme('thread.articleDigest')};
58+
font-size: 11px;
59+
margin-top: 4px;
60+
`

src/containers/unit/Comments/Comment/Actions.tsx

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { FC, memo } from 'react'
1+
import { FC, memo, useCallback } from 'react'
22

33
import type { TAccount, TComment } from '@/spec'
4+
import { ICON } from '@/config'
45

5-
import { IconButton } from '@/components/Buttons'
6+
import { IconButton, MenuButton } from '@/components/Buttons'
67
import { SpaceGrow } from '@/components/Common'
78

89
import { Wrapper, ReplyAction } from '../styles/comment/actions'
@@ -13,23 +14,80 @@ type TProps = {
1314
accountInfo: TAccount
1415
}
1516

17+
const menuOptions = [
18+
{
19+
key: 'quote',
20+
icon: `${ICON}/shape/quote.svg`,
21+
title: '引用',
22+
},
23+
{
24+
key: 'share',
25+
icon: `${ICON}/article/share.svg`,
26+
title: '分享',
27+
},
28+
{
29+
key: 'report',
30+
icon: `${ICON}/article/report.svg`,
31+
title: '举报',
32+
},
33+
]
34+
1635
const Actions: FC<TProps> = ({ data, accountInfo }) => {
36+
let extraOptions = []
37+
1738
if (String(data.author.id) === accountInfo.id) {
18-
return (
19-
<Wrapper>
20-
<ReplyAction onClick={() => openUpdateEditor(data)}>编辑</ReplyAction>
21-
<ReplyAction onClick={() => onDelete(data)}>删除</ReplyAction>
22-
</Wrapper>
23-
)
39+
extraOptions = [
40+
{
41+
key: 'edit',
42+
icon: `${ICON}/edit/publish-pen.svg`,
43+
title: '编辑',
44+
},
45+
{
46+
key: 'delete',
47+
icon: `${ICON}/shape/delete.svg`,
48+
title: '删除',
49+
},
50+
]
2451
}
2552

53+
const handleAction = useCallback(
54+
(key) => {
55+
switch (key) {
56+
case 'share': {
57+
return console.log('todo: share')
58+
}
59+
case 'quote': {
60+
return console.log('todo: quote')
61+
}
62+
case 'report': {
63+
return console.log('todo: report')
64+
}
65+
case 'edit': {
66+
return openUpdateEditor(data)
67+
}
68+
case 'delete': {
69+
return onDelete(data)
70+
}
71+
default: {
72+
// eslint-disable-next-line no-useless-return
73+
return
74+
}
75+
}
76+
},
77+
[data],
78+
)
79+
2680
return (
2781
<Wrapper>
2882
<ReplyAction onClick={() => openReplyEditor(data)}>回复</ReplyAction>
2983
<SpaceGrow />
30-
<IconButton path="article/share.svg" size={13} />
31-
<IconButton path="shape/quote.svg" size={13} />
32-
<IconButton path="article/report.svg" size={13} />
84+
<MenuButton
85+
options={menuOptions}
86+
extraOptions={extraOptions}
87+
onClick={handleAction}
88+
>
89+
<IconButton path="shape/more.svg" size={16} />
90+
</MenuButton>
3391
</Wrapper>
3492
)
3593
}

src/containers/unit/Comments/Comment/DesktopView/DefaultLayout.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
AuthorUpvotedIcon,
2727
SolutionIcon,
2828
BadgePopContent,
29-
RangeLine,
29+
IndentLine,
3030
} from '../../styles/comment/desktop_view'
3131
import { foldComment } from '../../logic'
3232

@@ -41,17 +41,13 @@ type TProps = {
4141
data: TComment
4242
accountInfo: TAccount
4343
tobeDeleteId: string
44-
hasReplies?: boolean
45-
withoutBottomDivider?: boolean
4644
isReply?: boolean
4745
}
4846

4947
const DefaultLayout: FC<TProps> = ({
5048
data,
5149
tobeDeleteId,
5250
accountInfo,
53-
hasReplies = false,
54-
withoutBottomDivider = false,
5551
isReply = false,
5652
}) => {
5753
const pined = data.id === '360' || data.id === '377'
@@ -92,7 +88,7 @@ const DefaultLayout: FC<TProps> = ({
9288
/>
9389
</Tooltip>
9490
)}
95-
{isReply && <RangeLine onClick={() => foldComment(data.id)} />}
91+
{isReply && <IndentLine onClick={() => foldComment(data.id)} />}
9692
</SidebarWrapper>
9793

9894
<CommentBodyInfo onMouseUp={getSelection}>
@@ -101,12 +97,7 @@ const DefaultLayout: FC<TProps> = ({
10197
{data.replyTo && <ReplyBar data={data.replyTo} />}
10298
<MarkDownRender body={data.body} />
10399
</CommentContent>
104-
<Footer
105-
data={data}
106-
accountInfo={accountInfo}
107-
withoutBottomDivider={withoutBottomDivider}
108-
hasReplies={hasReplies}
109-
/>
100+
<Footer data={data} accountInfo={accountInfo} />
110101
</CommentBodyInfo>
111102
</CommentWrapper>
112103
</Wrapper>

src/containers/unit/Comments/Comment/DesktopView/FoldLayout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ type TProps = {
2323
}
2424

2525
const FoldLayout: FC<TProps> = ({ data }) => {
26-
const pined = data.id === '360' || data.id === '377'
2726
const isAuthorUpvoted =
2827
data.id === '377' || data.id === '355' || data.id === '359'
2928
const isSolution = data.id === '358' || data.id === '355'
3029

3130
return (
32-
<Wrapper pined={pined}>
31+
<Wrapper>
3332
<IconButton
3433
path="shape/expand-all.svg"
3534
hint="展开评论"

src/containers/unit/Comments/Comment/DesktopView/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ type TProps = {
1212
accountInfo: TAccount
1313
tobeDeleteId: string
1414
hasReplies?: boolean
15-
withoutBottomDivider?: boolean
1615
foldedIds: TID[]
1716
}
1817

0 commit comments

Comments
 (0)