diff --git a/src/spec/menu.d.ts b/src/spec/menu.d.ts index 3ba70483b..4ba890404 100644 --- a/src/spec/menu.d.ts +++ b/src/spec/menu.d.ts @@ -9,3 +9,13 @@ export type TMenu = | 'ARROW_TO_BOTTOM' | 'SETTING' | 'DELETE' + // commont + | 'TODO' + | 'WIP' + | 'DONE' + | 'FEATURE' + | 'BUG' + | 'HELP' + | 'OTHER' + | 'ALL' + | 'TOOL' diff --git a/src/widgets/ArticlesFilter/DesktopView.tsx b/src/widgets/ArticlesFilter/DesktopView.tsx index 2d0c92956..65ca8870f 100755 --- a/src/widgets/ArticlesFilter/DesktopView.tsx +++ b/src/widgets/ArticlesFilter/DesktopView.tsx @@ -50,14 +50,13 @@ const ArticlesFilter: FC = ({ {!searchMode && ( - - + {resState === TYPE.RES_STATE.LOADING && } diff --git a/src/widgets/ArticlesFilter/ModelineView.tsx b/src/widgets/ArticlesFilter/ModelineView.tsx index 8ce79e8c2..37e08c9dd 100644 --- a/src/widgets/ArticlesFilter/ModelineView.tsx +++ b/src/widgets/ArticlesFilter/ModelineView.tsx @@ -43,12 +43,10 @@ const ArticlesFilter: FC = ({ - - + + - {modelineExpand && ( - - )} + {modelineExpand && } {/* */} diff --git a/src/widgets/ArticlesFilter/SortFilter.tsx b/src/widgets/ArticlesFilter/SortFilter.tsx index b7024be97..9b30ca4d4 100644 --- a/src/widgets/ArticlesFilter/SortFilter.tsx +++ b/src/widgets/ArticlesFilter/SortFilter.tsx @@ -11,21 +11,21 @@ type TProps = { activeFilter: TArticleFilter onSelect: (filter: TArticleFilter) => void noArrow?: boolean - tooltipPlacement?: TTooltipPlacement + placement?: TTooltipPlacement } const SortFilter: FC = ({ onSelect, activeFilter, noArrow = false, - tooltipPlacement = 'bottom-start', + placement = 'bottom-start', }) => { const [menuOpen, setMenuOpen] = useState(false) return ( setMenuOpen(true)} onHide={() => setMenuOpen(false)} offset={[-30, 5]} diff --git a/src/widgets/Buttons/DropdownButton/index.tsx b/src/widgets/Buttons/DropdownButton/index.tsx index c6b29b50d..8ca42fc13 100755 --- a/src/widgets/Buttons/DropdownButton/index.tsx +++ b/src/widgets/Buttons/DropdownButton/index.tsx @@ -7,7 +7,14 @@ import usePrimaryColor from '@/hooks/usePrimaryColor' import { buildLog } from '@/logger' -import { Wrapper, ButtonWrapper, InnerBtnWrapper, FilterIcon } from '../styles/dropdown_button' +import { + Wrapper, + ButtonWrapper, + InnerBtnWrapper, + FilterIcon, + CloseWrapper, + CloseIcon, +} from '../styles/dropdown_button' const log = buildLog('C:DropdownButton') @@ -16,8 +23,10 @@ type TProps = { size?: TSizeTS withBorder?: boolean onClick?: () => void + onClear?: () => void noArrow?: boolean selected?: boolean + closable?: boolean } & TSpace & TActive @@ -26,9 +35,11 @@ const DropdownButton: FC = ({ size = SIZE.SMALL, withBorder = false, onClick = log, + onClear = log, noArrow = false, $active = false, selected = false, + closable = false, ...restProps }) => { const primaryColor = usePrimaryColor() @@ -46,9 +57,21 @@ const DropdownButton: FC = ({ <>{children} - {!noArrow && ( + {!noArrow && !(closable && selected) && ( )} + + {closable && selected && ( + { + e.preventDefault() + onClear() + }} + > + + + )} diff --git a/src/widgets/Buttons/PublishButton/index.tsx b/src/widgets/Buttons/PublishButton/index.tsx index 9e1444a8c..edf709356 100755 --- a/src/widgets/Buttons/PublishButton/index.tsx +++ b/src/widgets/Buttons/PublishButton/index.tsx @@ -10,11 +10,11 @@ import { buildLog } from '@/logger' import usePrimaryColor from '@/hooks/usePrimaryColor' import useViewingThread from '@/hooks/useViewingThread' -import Tooltip from '@/widgets/Tooltip' -import FullPanel from '@/widgets/CatSelector/FullPanel' +import { POST_CAT_MENU_ITEMS } from '@/constant/menu' -import { MORE_MENU } from './constant' +import Menu from '@/widgets/Menu' +import { MORE_MENU } from './constant' import PostLayout from './PostLayout' import SidebarHeaderLayout from './SidebarHeaderLayout' @@ -55,12 +55,16 @@ const PublishButton: FC = ({ return ( - setShow(true)} + {show && }} + placement={placement} + items={POST_CAT_MENU_ITEMS} + // onSelect={(item) => handleSelect(item.key as TArticleState)} + // onShow={() => setMenuOpen(true)} + // onHide={() => setMenuOpen(false)} + // activeKey={activeCat} + popWidth={192} + withDesc > = ({ {mode === PUBLISH_MODE.DEFAULT && } {mode === PUBLISH_MODE.SIDEBAR_LAYOUT_HEADER && } - + ) } diff --git a/src/widgets/Buttons/styles/dropdown_button/index.ts b/src/widgets/Buttons/styles/dropdown_button/index.ts index 1f55dc253..3aa30d917 100755 --- a/src/widgets/Buttons/styles/dropdown_button/index.ts +++ b/src/widgets/Buttons/styles/dropdown_button/index.ts @@ -6,6 +6,7 @@ import css, { theme, primaryTheme, primaryLightTheme } from '@/css' import Button from '@/widgets/Buttons/Button' import ArrowSVG from '@/icons/ArrowSimple' +import CloseSVG from '@/icons/CloseCross' type TWrapper = { withBorder: boolean; size: TSizeTS; selected: boolean } & TSpace & TActive & @@ -82,3 +83,31 @@ export const FilterIcon = styled(ArrowSVG)` opacity: 0.6; `}; ` + +export const CloseWrapper = styled.div` + ${css.circle(16)}; + ${css.row('align-both')}; + margin-left: 4px; + + color: ${({ primaryColor }) => primaryTheme(primaryColor)}; + + &:hover { + color: ${theme('button.fg')}; + background: ${({ primaryColor }) => primaryTheme(primaryColor)}; + cursor: pointer; + } + + transition: all 0.2s; +` +export const CloseIcon = styled(CloseSVG)` + fill: ${({ primaryColor }) => primaryTheme(primaryColor)}; + ${css.size(12)}; + transform: rotate(-90deg); + opacity: 0.8; + + ${CloseWrapper}:hover & { + fill: ${theme('button.fg')}; + opacity: 1; + } + transition: all 0.2s; +` diff --git a/src/widgets/CatSelector/ActiveLabel.tsx b/src/widgets/CatSelector/ActiveLabel.tsx index da08de3a0..413486914 100644 --- a/src/widgets/CatSelector/ActiveLabel.tsx +++ b/src/widgets/CatSelector/ActiveLabel.tsx @@ -5,7 +5,7 @@ import type { TArticleCat } from '@/spec' import { Trans } from '@/i18n' import usePrimaryColor from '@/hooks/usePrimaryColor' -import { Wrapper, Icon } from './styles/active_label' +import { Wrapper, Icon, Title } from './styles/active_label' type TProps = { cat: TArticleCat @@ -18,7 +18,7 @@ const ActiveLabel: FC = ({ cat }) => { return ( - {Trans(cat)} + {Trans(cat)} ) } diff --git a/src/widgets/CatSelector/FilterPanel.tsx b/src/widgets/CatSelector/FilterPanel.tsx deleted file mode 100644 index aba4881a6..000000000 --- a/src/widgets/CatSelector/FilterPanel.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { FC, memo } from 'react' - -import type { TArticleCat } from '@/spec' -import { ARTICLE_CAT } from '@/constant/gtd' - -import { Trans } from '@/i18n' - -import { Wrapper, SelectItem, IconWrapper, Icon } from './styles/filter_panel' - -type TProps = { - activeCat: TArticleCat - onSelect: (cat: TArticleCat) => void -} - -const FilterPanel: FC = ({ activeCat, onSelect }) => { - const OPTIONS = [ - ARTICLE_CAT.ALL, - ARTICLE_CAT.FEATURE, - ARTICLE_CAT.BUG, - ARTICLE_CAT.QUESTION, - ARTICLE_CAT.OTHER, - ] - - return ( - - {OPTIONS.map((cat) => { - const OptIcon = Icon[cat] - - return ( - onSelect(cat)}> - - - - {Trans(cat)} - - ) - })} - - ) -} - -export default memo(FilterPanel) diff --git a/src/widgets/CatSelector/FullPanel.tsx b/src/widgets/CatSelector/FullPanel.tsx deleted file mode 100644 index 0e7300dee..000000000 --- a/src/widgets/CatSelector/FullPanel.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { FC, memo } from 'react' - -import type { TArticleCat } from '@/spec' -import { ARTICLE_CAT } from '@/constant/gtd' - -import { Trans } from '@/i18n' - -import { Wrapper, SelectItem, Icon, RightPart, Title, Desc } from './styles/full_panel' - -type TProps = { - activeCat: TArticleCat - onSelect: (cat: TArticleCat) => void -} - -const DESC = { - [ARTICLE_CAT.FEATURE]: '提需求,功能建议等', - [ARTICLE_CAT.BUG]: '使用中遇到的错误,问题等', - [ARTICLE_CAT.QUESTION]: '请求帮助,使用疑惑等', - [ARTICLE_CAT.OTHER]: '一般讨论,其他话题', -} - -const FullPanel: FC = ({ activeCat, onSelect }) => { - const OPTIONS = [ARTICLE_CAT.FEATURE, ARTICLE_CAT.BUG, ARTICLE_CAT.QUESTION, ARTICLE_CAT.OTHER] - - return ( - - {OPTIONS.map((cat) => { - const OptIcon = Icon[cat] - - return ( - onSelect(cat)}> - - - {Trans(cat)} - {DESC[cat]} - - - ) - })} - - ) -} - -export default memo(FullPanel) diff --git a/src/widgets/CatSelector/index.tsx b/src/widgets/CatSelector/index.tsx index 2b18f4c1c..fe1c0b807 100644 --- a/src/widgets/CatSelector/index.tsx +++ b/src/widgets/CatSelector/index.tsx @@ -1,24 +1,20 @@ -import { FC, memo, useState, Fragment } from 'react' -import dynamic from 'next/dynamic' +import { FC, memo, useState, useRef } from 'react' -import type { TArticleCatMode, TArticleCat, TTooltipPlacement, TSpace } from '@/spec' +import type { TArticleCatMode, TArticleCat, TSpace, TTooltipPlacement } from '@/spec' import { ARTICLE_CAT, ARTICLE_CAT_MODE } from '@/constant/gtd' +import { POST_CAT_MENU_ITEMS } from '@/constant/menu' -import Tooltip from '@/widgets/Tooltip' import DropdownButton from '@/widgets/Buttons/DropdownButton' +import Menu from '@/widgets/Menu' import { FilterWrapper, FullWrapper, Label } from './styles' - import ActiveLabel from './ActiveLabel' -const FilterPanel = dynamic(() => import('./FilterPanel')) -const FullPanel = dynamic(() => import('./FullPanel')) - type TProps = { mode?: TArticleCatMode activeCat: TArticleCat + placement?: TTooltipPlacement onSelect: (cat: TArticleCat) => void - tooltipPlacement?: TTooltipPlacement selected?: boolean } & TSpace @@ -26,12 +22,13 @@ const CatSelector: FC = ({ mode = ARTICLE_CAT_MODE.FILTER, activeCat, onSelect, - tooltipPlacement = 'bottom-end', selected = false, + placement = 'bottom', ...restProps }) => { - const [show, setShow] = useState(false) + const [offset, setOffset] = useState([30, 5]) const [menuOpen, setMenuOpen] = useState(false) + const ref = useRef(null) const Wrapper = mode === ARTICLE_CAT_MODE.FILTER ? FilterWrapper : FullWrapper @@ -39,35 +36,43 @@ const CatSelector: FC = ({ onSelect(cat) } - const offset = mode === ARTICLE_CAT_MODE.FILTER ? [30, 5] : [-44, 5] + const popWidth = 120 return ( - + {mode === ARTICLE_CAT_MODE.FULL && } - handleSelect(item.key as TArticleCat)} onShow={() => { - setShow(true) + if (selected) { + setOffset([10, 5]) + } setMenuOpen(true) }} - onHide={() => setMenuOpen(false)} - offset={offset as [number, number]} - content={ - - {show && mode === ARTICLE_CAT_MODE.FULL && ( - - )} - {show && mode === ARTICLE_CAT_MODE.FILTER && ( - - )} - - } + onHide={() => { + setOffset([30, 5]) + setMenuOpen(false) + }} + activeKey={activeCat} + placement={placement} + popWidth={popWidth} > - + { + // simulate click to avoid menu pop again + ref.current.click() + onSelect(ARTICLE_CAT.ALL) + }} + > {activeCat === ARTICLE_CAT.ALL ? '类别' : } - + ) } diff --git a/src/widgets/CatSelector/styles/active_label.ts b/src/widgets/CatSelector/styles/active_label.ts index c736a6491..1f59385df 100644 --- a/src/widgets/CatSelector/styles/active_label.ts +++ b/src/widgets/CatSelector/styles/active_label.ts @@ -12,10 +12,13 @@ import OtherSVG from '@/icons/menu/Feedback' export const Wrapper = styled.div` ${css.row('align-center')}; color: ${({ primaryColor }) => primaryTheme(primaryColor)}; +` +export const Title = styled.div` + font-size: 13px; font-weight: 550; ` const iconBase = ` - ${css.size(14)}; + ${css.size(13)}; margin-right: 5px; ` diff --git a/src/widgets/CatSelector/styles/filter_panel.ts b/src/widgets/CatSelector/styles/filter_panel.ts deleted file mode 100644 index 930bde284..000000000 --- a/src/widgets/CatSelector/styles/filter_panel.ts +++ /dev/null @@ -1,68 +0,0 @@ -import styled from 'styled-components' - -import css, { theme } from '@/css' - -import LightSVG from '@/icons/Light' -import QuestionSVG from '@/icons/Question' -import BugSVG from '@/icons/Bug' -import AllSVG from '@/icons/menu/Dots' -import OtherSVG from '@/icons/menu/Feedback' - -import { SelectItem as SelectItemBase } from '.' - -export const Wrapper = styled.div` - ${css.column()}; - min-width: 120px; - margin-top: 2px; - font-size: 14px; - cursor: auto; -` -export const IconWrapper = styled.div` - ${css.circle(16)}; - ${css.row('align-both')}; - margin-right: 10px; -` - -const iconBase = ` - ${css.size(14)}; - opacity: 0.8; -` -export const AllIcon = styled(AllSVG)` - ${iconBase}; - ${css.size(16)}; - fill: ${theme('article.digest')}; -` -export const LightIcon = styled(LightSVG)` - ${iconBase}; - fill: ${theme('article.digest')}; - margin-top: 1px; -` -export const QuestionIcon = styled(QuestionSVG)` - ${iconBase}; - ${css.size(10)}; - fill: ${theme('article.digest')}; -` -export const BugIcon = styled(BugSVG)` - ${iconBase}; - ${css.size(12)}; - fill: ${theme('article.digest')}; -` -export const OtherIcon = styled(OtherSVG)` - ${iconBase}; - ${css.size(13)}; - fill: ${theme('article.digest')}; -` -export const SelectItem = styled(SelectItemBase)` - ${css.row('align-center')}; - padding: 7px 5px; - border-radius: 5px; -` -export const Icon = { - ALL: AllIcon, - FEATURE: LightIcon, - BUG: BugIcon, - QUESTION: QuestionIcon, - OTHER: OtherIcon, -} - -export const Title = styled.div`` diff --git a/src/widgets/CatSelector/styles/full_panel.ts b/src/widgets/CatSelector/styles/full_panel.ts deleted file mode 100644 index e016dceda..000000000 --- a/src/widgets/CatSelector/styles/full_panel.ts +++ /dev/null @@ -1,69 +0,0 @@ -import styled from 'styled-components' - -import css, { theme } from '@/css' - -import LightSVG from '@/icons/Light' -import QuestionSVG from '@/icons/Question' -import BugSVG from '@/icons/Bug' -import AllSVG from '@/icons/menu/MoreL' -import OtherSVG from '@/icons/menu/Feedback' - -export { SelectItem } from '.' - -export const Wrapper = styled.div` - ${css.column()}; - min-width: 190px; - cursor: auto; -` -const iconBase = ` - ${css.size(18)}; - margin-right: 12px; - opacity: 0.8; - margin-top: 5px; -` -export const AllIcon = styled(AllSVG)` - ${iconBase}; - fill: ${theme('article.digest')}; -` -export const LightIcon = styled(LightSVG)` - ${iconBase}; - fill: ${theme('article.digest')}; -` -export const QuestionIcon = styled(QuestionSVG)` - ${iconBase}; - ${css.size(14)}; - margin-right: 13px; - margin-left: 1px; - fill: ${theme('article.digest')}; -` -export const BugIcon = styled(BugSVG)` - ${iconBase}; - ${css.size(16)}; - margin-left: 1px; - fill: ${theme('article.digest')}; -` -export const OtherIcon = styled(OtherSVG)` - ${iconBase}; - margin-left: 1px; - margin-right: 10px; - fill: ${theme('article.digest')}; -` - -export const Icon = { - FEATURE: LightIcon, - BUG: BugIcon, - QUESTION: QuestionIcon, - OTHER: OtherIcon, -} - -export const RightPart = styled.div`` -export const Title = styled.div` - font-size: 13px; - color: ${theme('article.title')}; - font-weight: 500; - margin-bottom: 2px; -` -export const Desc = styled.div` - font-size: 12px; - color: ${theme('hint')}; -` diff --git a/src/widgets/CatSelector/styles/index.ts b/src/widgets/CatSelector/styles/index.ts index 20eabd2ea..2a3cf0f0c 100644 --- a/src/widgets/CatSelector/styles/index.ts +++ b/src/widgets/CatSelector/styles/index.ts @@ -1,7 +1,7 @@ import styled from 'styled-components' -import type { TActive, TPrimaryColor, TSpace } from '@/spec' -import css, { theme, primaryTheme } from '@/css' +import type { TActive, TSpace } from '@/spec' +import css, { theme } from '@/css' type TWrapper = { menuOpen?: boolean } & TSpace export const FilterWrapper = styled.div` diff --git a/src/widgets/Menu/Icon.tsx b/src/widgets/Menu/Icon.tsx new file mode 100644 index 000000000..7d26f0d26 --- /dev/null +++ b/src/widgets/Menu/Icon.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react' +import { observer } from 'mobx-react' + +import type { TActive, TMenu } from '@/spec' +import usePrimaryColor from '@/hooks/usePrimaryColor' + +import { Wrapper, ICONS } from './styles/icon' + +type TProps = { + type: TMenu +} & TActive + +const Icon: FC = ({ type, $active }) => { + const IconComp = ICONS[type] || ICONS.OTHER + + const primaryColor = usePrimaryColor() + + return ( + + + + ) +} + +export default observer(Icon) diff --git a/src/widgets/Menu/List.tsx b/src/widgets/Menu/List.tsx new file mode 100644 index 000000000..b451c6779 --- /dev/null +++ b/src/widgets/Menu/List.tsx @@ -0,0 +1,68 @@ +import { FC } from 'react' +import { observer } from 'mobx-react' + +import type { TMenu } from '@/spec' +import usePrimaryColor from '@/hooks/usePrimaryColor' + +import type { TMenuItem } from './spec' +import Icon from './Icon' +import { Wrapper, Item, FullIcon, FullItem, Main, Title, FullTitle, Desc } from './styles/list' + +type TProps = { + items: TMenuItem[] + activeKey: string + onSelect: (item: TMenuItem) => void + popWidth: number + withDesc: boolean +} + +const List: FC = ({ items, activeKey, onSelect, popWidth, withDesc }) => { + const primaryColor = usePrimaryColor() + + if (withDesc) { + return ( + + {items.map((item) => ( + onSelect(item)} + > + + + +
+ {item.title} + {item.desc || '--'} +
+
+ ))} +
+ ) + } + + return ( + + {items.map((item) => { + const $active = activeKey === item.key + + return ( + onSelect(item)} + > + +
+ {item.title} +
+
+ ) + })} +
+ ) +} + +export default observer(List) diff --git a/src/widgets/Menu/index.tsx b/src/widgets/Menu/index.tsx new file mode 100644 index 000000000..cca2a290d --- /dev/null +++ b/src/widgets/Menu/index.tsx @@ -0,0 +1,95 @@ +/* + * + * Menu + * + */ + +import { ReactNode, FC, memo } from 'react' + +import type { TTooltipPlacement } from '@/spec' +import { buildLog } from '@/logger' +import Tooltip from '@/widgets/Tooltip' +import MENU from '@/constant/menu' + +import type { TMenuItem } from './spec' +import List from './List' + +import { Wrapper } from './styles' + +/* eslint-disable-next-line */ +const log = buildLog('c:Menu:index') + +type TProps = { + testid?: string + activeKey?: string + items?: TMenuItem[] + onSelect?: (item: TMenuItem) => void + children?: ReactNode + offset?: [number, number] + placement?: TTooltipPlacement + onShow?: () => void + onHide?: () => void + popWidth?: number + withDesc?: boolean +} + +// todo constant for MENU_ICON +const DEMO_ITEMS = [ + { + key: 'all', + title: '全部', + icon: MENU.ALL, + }, + { + key: 'todo', + title: '计划中', + icon: MENU.TODO, + }, + { + key: 'wip', + title: '进行中', + icon: MENU.WIP, + }, + { + key: 'done', + title: '已完成', + icon: MENU.DONE, + }, +] + +const Menu: FC = ({ + testid = 'menu', + activeKey = '', + onSelect = log, + items = DEMO_ITEMS, + children = 'menu', + onShow = log, + onHide = log, + placement = 'bottom', + offset = [-5, 5], + popWidth = 120, + withDesc = false, +}) => { + return ( + onShow && onShow()} + onHide={() => onHide && onHide()} + offset={offset as [number, number]} + content={ + + } + > + {children} + + ) +} + +export default memo(Menu) diff --git a/src/widgets/Menu/spec.d.ts b/src/widgets/Menu/spec.d.ts new file mode 100644 index 000000000..df85abfed --- /dev/null +++ b/src/widgets/Menu/spec.d.ts @@ -0,0 +1,6 @@ +export type TMenuItem = { + title: string + icon?: string + desc?: string + key: string +} diff --git a/src/widgets/Menu/styles/icon.ts b/src/widgets/Menu/styles/icon.ts new file mode 100644 index 000000000..ddc6622a5 --- /dev/null +++ b/src/widgets/Menu/styles/icon.ts @@ -0,0 +1,63 @@ +import { FC } from 'react' +import styled from 'styled-components' + +import type { TActive, TPrimaryColor } from '@/spec' + +import MENU from '@/constant/menu' + +import AllSVG from '@/icons/menu/Dots' +import GtdTodoSVG from '@/icons/GtdTodo' +import GtdWipSVG from '@/icons/GtdWip' +import GtdDoneSVG from '@/icons/CheckBold' + +import LightSVG from '@/icons/Light' +import BugSVG from '@/icons/Bug' +import QuestionSVG from '@/icons/Question' + +import OtherSVG from '@/icons/menu/MoreL' + +// import Img from '@/Img' +import css, { theme, primaryTheme } from '@/css' +import { Item } from './list' + +export const Wrapper = styled.div` + ${css.row('align-both')} + ${css.size(15)}; + margin-right: 8px; + /* $active state hot work on icon's hover, put it in here instead */ + filter: ${({ $active }) => ($active ? 'saturate(1)' : 'saturate(0)')}; +` + +type TIcon = TActive & TPrimaryColor +const commonIcon = (comp): FC => { + // @ts-ignore + return styled(comp)` + fill: ${({ $active, primaryColor }) => ($active ? primaryTheme(primaryColor) : theme('hint'))}; + width: ${({ $small }: { $small: boolean }) => ($small ? '12px' : '13px')}; + height: ${({ $small }) => ($small ? '12px' : '13px')}; + display: block; + + ${Item}:hover & { + fill: ${({ primaryColor, $active }) => + $active ? primaryTheme(primaryColor) : theme('hint')}; + } + ` +} + +export const ICONS = { + [MENU.ALL]: styled(commonIcon(AllSVG))` + ${css.size(15)}; + margin-left: -1px; + `, + [MENU.TODO]: commonIcon(GtdTodoSVG), + [MENU.WIP]: commonIcon(GtdWipSVG), + [MENU.DONE]: commonIcon(GtdDoneSVG), + + [MENU.FEATURE]: commonIcon(LightSVG), + [MENU.BUG]: commonIcon(BugSVG), + [MENU.HELP]: commonIcon(QuestionSVG), + + [MENU.OTHER]: styled(commonIcon(OtherSVG))` + margin-left: 1px; + `, +} diff --git a/src/widgets/Menu/styles/index.ts b/src/widgets/Menu/styles/index.ts new file mode 100644 index 000000000..c9a6da238 --- /dev/null +++ b/src/widgets/Menu/styles/index.ts @@ -0,0 +1,16 @@ +import styled from 'styled-components' + +import type { TTestable } from '@/spec' + +// import Img from '@/Img' +// import { theme } from '@/css' + +export const Wrapper = styled.div.attrs(({ testid }: TTestable) => ({ + 'data-test-id': testid, +}))` + /* line-break: anywhere; + word-wrap: break-word; */ + width: auto; +` + +export const Title = styled.div`` diff --git a/src/widgets/Menu/styles/list.ts b/src/widgets/Menu/styles/list.ts new file mode 100644 index 000000000..7e372d8c0 --- /dev/null +++ b/src/widgets/Menu/styles/list.ts @@ -0,0 +1,59 @@ +import styled from 'styled-components' + +import type { TActive, TPrimaryColor } from '@/spec' + +// import Img from '@/Img' +import css, { theme, primaryLightTheme, primaryTheme } from '@/css' + +type TWrapper = { popWidth: number } +export const Wrapper = styled.div` + width: ${({ popWidth }) => `${popWidth}px`}; +` +type TItem = TPrimaryColor & TActive +export const Item = styled.div` + ${css.row('align-center')}; + padding: 2px 8px; + border-radius: 5px; + line-height: 32px; + + font-weight: ${({ $active }) => ($active ? 500 : 400)}; + + background: ${({ $active, primaryColor }) => + $active ? primaryLightTheme(primaryColor) : 'transparent'}; + + color: ${({ $active, primaryColor }) => + $active ? primaryTheme(primaryColor) : theme('article.digest')}; + + &:hover { + font-weight: 500; + background: ${({ $active, primaryColor }) => + $active ? primaryLightTheme(primaryColor) : theme('hoverBg')}; + cursor: pointer; + } + + transition: all 0.2s; +` + +export const Main = styled.div`` + +export const Title = styled.div` + font-size: 14px; +` +// full version + +export const FullItem = styled(Item)` + ${css.row('align-start')}; + padding: 2px 8px; +` +export const FullIcon = styled.div` + margin-top: 10px; + margin-right: 5px; +` +export const FullTitle = styled(Title)` + font-weight: bold; +` +export const Desc = styled.div` + font-size: 12px; + opacity: 0.8; + margin-top: -8px; +` diff --git a/src/widgets/Menu/tests/index.test.ts b/src/widgets/Menu/tests/index.test.ts new file mode 100644 index 000000000..54b582781 --- /dev/null +++ b/src/widgets/Menu/tests/index.test.ts @@ -0,0 +1,10 @@ +// import React from 'react' +// import { shallow } from 'enzyme' + +// import Menu from '..' + +describe('TODO ', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(true) + }) +}) diff --git a/src/widgets/StateSelector/constant.ts b/src/widgets/StateSelector/constant.ts new file mode 100644 index 000000000..f6e3bab12 --- /dev/null +++ b/src/widgets/StateSelector/constant.ts @@ -0,0 +1,22 @@ +import { ARTICLE_STATE } from '@/constant/gtd' +import MENU from '@/constant/menu' + +export const MENU_ITEMS = [ + { + key: ARTICLE_STATE.TODO, + title: '计划中', + icon: MENU.TODO, + }, + { + key: ARTICLE_STATE.WIP, + title: '进行中', + icon: MENU.WIP, + }, + { + key: ARTICLE_STATE.DONE, + title: '已完成', + icon: MENU.DONE, + }, +] + +export const holder = 1 diff --git a/src/widgets/StateSelector/index.back.tsx b/src/widgets/StateSelector/index.back.tsx new file mode 100644 index 000000000..2361c5940 --- /dev/null +++ b/src/widgets/StateSelector/index.back.tsx @@ -0,0 +1,79 @@ +import { FC, memo, useState } from 'react' +import dynamic from 'next/dynamic' + +import type { TArticleState, TArticleCatMode, TTooltipPlacement, TSpace } from '@/spec' +import { ARTICLE_STATE, ARTICLE_STATE_MODE } from '@/constant/gtd' + +import Tooltip from '@/widgets/Tooltip' +import DropdownButton from '@/widgets/Buttons/DropdownButton' + +import ActiveState from './ActiveState' + +import { FilterWrapper, FullWrapper, Label } from './styles' + +const FilterPanel = dynamic(() => import('./FilterPanel')) +const FullPanel = dynamic(() => import('./FullPanel')) + +type TProps = { + mode?: TArticleCatMode + state?: string + tooltipPlacement?: TTooltipPlacement +} & TSpace + +const StateSelector: FC = ({ + mode = ARTICLE_STATE_MODE.FULL, + state = 'todo', + tooltipPlacement = 'bottom-end', + ...restProps +}) => { + const [show, setShow] = useState(false) + const [menuOpen, setMenuOpen] = useState(false) + const [activeState, setActiveState] = useState(null) + + const handleSelect = (state: TArticleState) => { + setActiveState(state) + } + + const Wrapper = mode === ARTICLE_STATE_MODE.FILTER ? FilterWrapper : FullWrapper + const offset = mode === ARTICLE_STATE_MODE.FILTER ? [20, 5] : [-42, 5] + + return ( + + {mode === ARTICLE_STATE_MODE.FULL && } + { + setShow(true) + setMenuOpen(true) + }} + onHide={() => setMenuOpen(false)} + offset={offset as [number, number]} + content={ + <> + {show && mode === ARTICLE_STATE_MODE.FILTER && ( + + )} + {show && mode === ARTICLE_STATE_MODE.FULL && ( + + )} + + } + noPadding + > + + {!activeState || activeState === ARTICLE_STATE.ALL ? ( + '状态' + ) : ( + + )} + + + + ) +} + +export default memo(StateSelector) diff --git a/src/widgets/StateSelector/index.tsx b/src/widgets/StateSelector/index.tsx index 2361c5940..1d1cc00a7 100644 --- a/src/widgets/StateSelector/index.tsx +++ b/src/widgets/StateSelector/index.tsx @@ -1,69 +1,70 @@ -import { FC, memo, useState } from 'react' -import dynamic from 'next/dynamic' +import { FC, memo, useState, useRef } from 'react' -import type { TArticleState, TArticleCatMode, TTooltipPlacement, TSpace } from '@/spec' +import type { TArticleState, TArticleCatMode, TSpace, TTooltipPlacement } from '@/spec' import { ARTICLE_STATE, ARTICLE_STATE_MODE } from '@/constant/gtd' -import Tooltip from '@/widgets/Tooltip' import DropdownButton from '@/widgets/Buttons/DropdownButton' +import Menu from '@/widgets/Menu' +import { MENU_ITEMS } from './constant' import ActiveState from './ActiveState' - import { FilterWrapper, FullWrapper, Label } from './styles' -const FilterPanel = dynamic(() => import('./FilterPanel')) -const FullPanel = dynamic(() => import('./FullPanel')) - type TProps = { mode?: TArticleCatMode state?: string - tooltipPlacement?: TTooltipPlacement + placement?: TTooltipPlacement } & TSpace const StateSelector: FC = ({ mode = ARTICLE_STATE_MODE.FULL, state = 'todo', - tooltipPlacement = 'bottom-end', + placement = 'bottom', ...restProps }) => { - const [show, setShow] = useState(false) + const [offset, setOffset] = useState([30, 5]) const [menuOpen, setMenuOpen] = useState(false) const [activeState, setActiveState] = useState(null) + const ref = useRef(null) const handleSelect = (state: TArticleState) => { setActiveState(state) } const Wrapper = mode === ARTICLE_STATE_MODE.FILTER ? FilterWrapper : FullWrapper - const offset = mode === ARTICLE_STATE_MODE.FILTER ? [20, 5] : [-42, 5] return ( - + {mode === ARTICLE_STATE_MODE.FULL && } - handleSelect(item.key as TArticleState)} + activeKey={activeState} + popWidth={120} + placement={placement} onShow={() => { - setShow(true) + if (activeState !== ARTICLE_STATE.ALL) { + setOffset([10, 5]) + } else { + setOffset([30, 5]) + } setMenuOpen(true) }} - onHide={() => setMenuOpen(false)} - offset={offset as [number, number]} - content={ - <> - {show && mode === ARTICLE_STATE_MODE.FILTER && ( - - )} - {show && mode === ARTICLE_STATE_MODE.FULL && ( - - )} - - } - noPadding + onHide={() => { + setOffset([22, 5]) + setMenuOpen(false) + }} > { + // simulate click to avoid menu pop again + ref.current.click() + setActiveState(ARTICLE_STATE.ALL) + }} > {!activeState || activeState === ARTICLE_STATE.ALL ? ( '状态' @@ -71,7 +72,7 @@ const StateSelector: FC = ({ )} - +
) } diff --git a/src/widgets/StateSelector/styles/active_state.ts b/src/widgets/StateSelector/styles/active_state.ts index e290c7b48..e61938290 100644 --- a/src/widgets/StateSelector/styles/active_state.ts +++ b/src/widgets/StateSelector/styles/active_state.ts @@ -10,7 +10,6 @@ export { WipIcon, TodoIcon, DoneIcon } from './filter_panel' export const Wrapper = styled.div` color: ${theme('article.title')}; font-weight: 400; - opacity: 0.8; ` export const Item = styled.div` ${css.row('align-center')}; @@ -19,7 +18,8 @@ type TTitle = TActive & TPrimaryColor export const StateTitle = styled(Title)` color: ${({ $active, primaryColor }) => $active ? primaryTheme(primaryColor) : theme('article.title')}; - font-weight: 600; + font-weight: 500; + white-space: nowrap; ` export const IconWrapper = styled.div` transform: scale(0.9); diff --git a/utils/constant/menu.ts b/utils/constant/menu.ts index 3124c4e17..6ab544e18 100644 --- a/utils/constant/menu.ts +++ b/utils/constant/menu.ts @@ -1,3 +1,4 @@ +import { ARTICLE_CAT } from '@/constant/gtd' import type { TMenu } from '@/spec' const MENU = { @@ -13,6 +14,43 @@ const MENU = { ARROW_TO_BOTTOM: 'ARROW_TO_BOTTOM', SETTING: 'SETTING', DELETE: 'DELETE', + + TODO: 'TODO', + WIP: 'WIP', + DONE: 'DONE', + FEATURE: 'FEATURE', + BUG: 'BUG', + HELP: 'HELP', + ALL: 'ALL', + OTHER: 'OTHER', + TOOL: 'TOOL', } as Record, Uppercase> +export const POST_CAT_MENU_ITEMS = [ + { + key: ARTICLE_CAT.FEATURE, + title: '功能建议', + desc: '提需求,功能建议等', + icon: MENU.FEATURE, + }, + { + key: ARTICLE_CAT.BUG, + title: '问题报告', + desc: '使用中遇到的错误,问题等', + icon: MENU.BUG, + }, + { + key: ARTICLE_CAT.QUESTION, + title: '求助/疑问', + desc: '请求帮助,使用疑惑等', + icon: MENU.HELP, + }, + { + key: ARTICLE_CAT.OTHER, + title: '其他讨论', + desc: '一般讨论,其他话题', + icon: MENU.OTHER, + }, +] + export default MENU diff --git a/utils/i18n/index.js b/utils/i18n/index.js index 516a2a789..9e260ff4c 100755 --- a/utils/i18n/index.js +++ b/utils/i18n/index.js @@ -27,7 +27,7 @@ const I18nDict = { ALL: '全部', FEATURE: '功能建议', BUG: '问题反馈', - QUESTION: '求助', + QUESTION: '求助/疑问', OTHER: '其他', RESOLVED: '问题解决', // articleState;