Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.
Merged
5 changes: 5 additions & 0 deletions server/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ router.route('/discovery/:category').get((req, res) => {
return renderAndCache({ req, res, path: '/discovery' })
})

// 帮助中心
router.route('/:community/help-center').get((req, res) => {
return renderAndCache({ req, res, path: '/help-center' })
})

// 社区主页
router.route('/:community/:thread').get((req, res) => {
if (
Expand Down
4 changes: 4 additions & 0 deletions size-limit.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
{
"path": ".next/server/pages/create/article.js",
"maxSize": "280 kB"
},
{
"path": ".next/server/pages/help-center.js",
"maxSize": "280 kB"
}
],
"ci": {
Expand Down
139 changes: 139 additions & 0 deletions src/components/CollapseMenu/Group.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useState, useRef, useEffect } from 'react'
import T from 'prop-types'

import { findIndex } from 'ramda'

import { ICON } from '@/config'
import { buildLog } from '@/utils'

import Item from './Item'

import {
Wrapper,
TagsWrapper,
Header,
ArrowIcon,
Title,
Content,
SubToggle,
SubToggleTitle,
SubTogglePrefixIcon,
} from './styles/group'

/* eslint-disable-next-line */
const log = buildLog('c:CollapseMenu:Group')

const Group = ({
title,
groupItems,
items,
activeItem,
onSelect,
maxDisplayCount,
totalToggleThrold,
}) => {
// 决定是否显示 '展示更多' 的时候参考标签总数
const needSubToggle =
items?.length > totalToggleThrold && groupItems.length > maxDisplayCount

const initDisplayCount = needSubToggle ? maxDisplayCount : groupItems.length

const [isFolderOpen, toggleFolder] = useState(true)
const [curDisplayCount, setCurDisplayCount] = useState(initDisplayCount)

const sortedItems = groupItems // sortByColor(groupItems)

const isActiveTagInFolder =
findIndex((item) => item.id === activeItem.id, groupItems) >= 0

const subToggleRef = useRef(null)
// 当选中的 Tag 被折叠在展示更多里面时,将其展开
useEffect(() => {
if (subToggleRef && isActiveTagInFolder) {
setCurDisplayCount(groupItems.length)
}
}, [subToggleRef, isActiveTagInFolder, groupItems])

return (
<Wrapper>
<Header
onClick={() => {
toggleFolder(!isFolderOpen)

// 当关闭 Folder 的时候,如果当前 Folder 没有被激活的 Tag, 那么就回到折叠状态
// 如果有,那么保持原来的状态
if (isFolderOpen && !isActiveTagInFolder) {
setCurDisplayCount(maxDisplayCount)
}
}}
>
<ArrowIcon
isOpen={isFolderOpen}
src={`${ICON}/shape/arrow-simple.svg`}
/>
<Title>{title}</Title>
</Header>

<Content isOpen={isFolderOpen}>
<TagsWrapper>
{sortedItems.slice(0, curDisplayCount).map((item) => (
<Item
key={item.id}
item={item}
active={activeItem.id === item.id}
activeId={activeItem.id}
onSelect={onSelect}
/>
))}
</TagsWrapper>
{needSubToggle && (
<SubToggle
ref={subToggleRef}
onClick={() => {
setCurDisplayCount(
curDisplayCount === maxDisplayCount
? groupItems.length
: maxDisplayCount,
)
}}
>
<SubTogglePrefixIcon src={`${ICON}/shape/more.svg`} />
<SubToggleTitle>
{curDisplayCount === maxDisplayCount ? '展开更多' : '收起'}
</SubToggleTitle>
</SubToggle>
)}
</Content>
</Wrapper>
)
}

Group.propTypes = {
// title, groupItems, items, activeItem, onSelect
title: T.string,
groupItems: T.arrayOf(
T.shape({
id: T.number,
title: T.string,
}),
).isRequired,
items: T.arrayOf(
T.shape({
id: T.number,
title: T.string,
}),
).isRequired,
activeItem: T.shape({
id: T.number,
title: T.string,
}).isRequired,
maxDisplayCount: T.number.isRequired,
totalToggleThrold: T.number.isRequired,
onSelect: T.func.isRequired,
}

Group.defaultProps = {
title: '',
}

export default React.memo(Group)
16 changes: 16 additions & 0 deletions src/components/CollapseMenu/Item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'

import { Wrapper, Title } from './styles/item'

// const Item = ({ item, active, activeId, onSelect }) => {
const Item = ({ item, active, onSelect }) => {
return (
<Wrapper active={active}>
<Title active={active} onClick={() => onSelect(item)}>
{item.title}
</Title>
</Wrapper>
)
}

export default Item
137 changes: 137 additions & 0 deletions src/components/CollapseMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
*
* CollapseMenu
*
*/

import React from 'react'
import T from 'prop-types'
import { keys } from 'ramda'

import { buildLog, groupByKey } from '@/utils'

import Group from './Group'

import { Wrapper } from './styles'

const MAX_DISPLAY_COUNT = 5
const TOTAL_TOGGLE_THROLD = 8 // 15

/* eslint-disable-next-line */
const log = buildLog('c:CollapseMenu:index')

const defaultActiveItem = { id: 2 }
const defaultItems = [
{
id: 1,
title: 'coderplanets 是什么?',
group: '基础问答',
},
{
id: 2,
title: '持续部署项目实践',
group: '基础问答',
},
{
id: 3,
title: 'coderplanets 是什么 3?',
group: '基础问答',
},

{
id: 4,
title: 'coderplanets 是什么 4?',
group: '进阶问答',
},
{
id: 5,
title: 'coderplanets 是什么 5?',
group: '进阶问答',
},
{
id: 6,
title: 'coderplanets 是什么 6?',
group: '进阶问答',
},
{
id: 7,
title: 'coderplanets 是什么 7?',
group: '进阶问答',
},
{
id: 8,
title: 'coderplanets 是什么 8?',
group: '进阶问答',
},
{
id: 9,
title: 'coderplanets 是什么 9?',
group: '进阶问答',
},
{
id: 10,
title: 'coderplanets 是什么 10?',
group: '进阶问答',
},
]

const CollapseMenu = ({
testId,
items,
activeItem,
onSelect,
maxDisplayCount,
totalToggleThrold,
}) => {
const groupedItems = groupByKey(items, 'group')
const groupsKeys = keys(groupedItems)

return (
<Wrapper testId={testId}>
{groupsKeys.map((groupKey) => (
<Group
key={groupKey}
title={groupKey}
items={items}
groupItems={groupedItems[groupKey]}
activeItem={activeItem}
maxDisplayCount={maxDisplayCount}
totalToggleThrold={totalToggleThrold}
onSelect={onSelect}
/>
))}
</Wrapper>
)
}

CollapseMenu.propTypes = {
testId: T.string,
items: T.arrayOf(
T.shape({
id: T.number,
title: T.string,
group: T.string,
}),
), // .isRequired,
activeItem: T.shape({
id: T.number,
title: T.string,
group: T.string,
}), // .isRequired,
maxDisplayCount: T.number,
totalToggleThrold: T.number,
onSelect: T.func,
}

CollapseMenu.defaultProps = {
testId: 'collapse-menu',
items: defaultItems,
activeItem: defaultActiveItem,
// default display count in each group, the remaining part will be folded
maxDisplayCount: MAX_DISPLAY_COUNT,
// if items count < than this, will not be folded in each group
totalToggleThrold: TOTAL_TOGGLE_THROLD,
onSelect: console.log,
}

export default React.memo(CollapseMenu)
67 changes: 67 additions & 0 deletions src/components/CollapseMenu/styles/group.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import styled from 'styled-components'

import Img from '@/Img'
import { css, theme } from '@/utils'

export const Wrapper = styled.div``

export const TagsWrapper = styled.div``

export const Header = styled.div`
${css.flex('align-center')};
margin-bottom: 8px;
margin-left: 3px;
&:hover {
cursor: pointer;
/* opacity: 0.65; */
}
`
export const ArrowIcon = styled(Img)`
fill: ${theme('tags.text')};
${css.size(16)};
opacity: 0.5;
transform: ${({ isOpen }) => (isOpen ? 'rotate(270deg)' : 'rotate(180deg)')};
transition: transform 0.5s;
${Header}:hover & {
opacity: 0.65;
}
`
export const Title = styled.div`
color: ${theme('tags.text')};
opacity: 0.5;
margin-left: 4px;
font-size: 14px;
margin-right: 8px;
${css.cutFrom('85px')};

${Header}:hover & {
opacity: 0.65;
}
`
export const Content = styled.div`
display: ${({ isOpen }) => (isOpen ? 'block' : 'none')};
width: 100%;
margin-bottom: 15px;
`
export const SubToggle = styled.div`
${css.flex('align-center')};
margin-left: 5px;
opacity: 0.5;

&:hover {
opacity: 0.8;
cursor: pointer;
}
`
export const SubToggleTitle = styled.div`
color: ${theme('tags.text')};
font-size: 12px;
margin-left: 5px;
padding: 2px;
border-radius: 5px;
`
export const SubTogglePrefixIcon = styled(Img)`
fill: ${theme('tags.text')};
${css.size(14)};
transform: rotate(90deg);
`
Loading