@fc-components/menu
Advanced tools
| .fc-menu-icon { | ||
| display: flex; | ||
| width: 48px; | ||
| // height: 48px; | ||
| align-items: center; | ||
| justify-content: center; | ||
| z-index: 1; | ||
| .iconpark-icon { | ||
| width: 16px; | ||
| } | ||
| } | ||
| .fc-menu-label { | ||
| padding-left: 8px; | ||
| font-size: 14px; | ||
| } | ||
| .fc-menu-bay { | ||
| background-color: #f9f9f9; | ||
| padding-bottom: 10px; | ||
| & > .fc-menu-sub-top { | ||
| position: absolute; | ||
| width: 100%; | ||
| top: 0px; | ||
| height: 15px; | ||
| border-radius: 0 0 15px 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-bottom { | ||
| position: absolute; | ||
| width: 100%; | ||
| bottom: 0px; | ||
| height: 15px; | ||
| z-index: 1; | ||
| border-radius: 0 15px 0 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-left { | ||
| position: absolute; | ||
| height: 100%; | ||
| width: 8px; | ||
| top: 0px; | ||
| left: 0px; | ||
| background-color: @primary-color; | ||
| z-index: 1; | ||
| } | ||
| & > .fc-menu-sub-topleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| top: 15px; | ||
| left: 8px; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 16px 0 0 0; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| .fc-menu-sub-bottomleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| bottom: 15px; | ||
| left: 8px; | ||
| z-index: 1; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 0 0 0 16px; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| } | ||
| .fc-menu { | ||
| border-radius: 0 0 8px 8px; | ||
| &.collapsed { | ||
| width: 48px; | ||
| .fc-menu-item-icon, | ||
| .fc-menu-sub-title-icon { | ||
| height: 48px; | ||
| } | ||
| .fc-menu-sub, | ||
| .fc-menu-item { | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| } | ||
| } | ||
| &.hide { | ||
| width: 0px; | ||
| } | ||
| &.hoverMode { | ||
| .fc-menu-sub-selected { | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: #666; | ||
| .fc-menu-bay(); | ||
| .fc-menu-sub-title, | ||
| .fc-menu-item-selected { | ||
| color: @primary-color; | ||
| padding: 0; | ||
| } | ||
| } | ||
| } | ||
| flex: 1; | ||
| width: 152px; | ||
| background-color: @primary-color; | ||
| color: #fff; | ||
| cursor: pointer; | ||
| &-item { | ||
| line-height: 48px; | ||
| display: flex; | ||
| position: relative; | ||
| &-selected { | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| &:hover { | ||
| color: #bba6fa; | ||
| } | ||
| &-icon { | ||
| .fc-menu-icon(); | ||
| } | ||
| &-label { | ||
| .fc-menu-label(); | ||
| } | ||
| } | ||
| &-sub { | ||
| position: relative; | ||
| overflow: hidden; | ||
| &-selected { | ||
| height: auto; | ||
| &:not(.fc-menu-sub-open) { | ||
| .fc-menu-sub-title { | ||
| color: #bba6fa; | ||
| } | ||
| } | ||
| &.fc-menu-sub-open { | ||
| .fc-menu-sub-title, | ||
| .fc-menu-item-selected { | ||
| color: @primary-color; | ||
| padding: 0; | ||
| } | ||
| } | ||
| .fc-menu-item-selected { | ||
| line-height: 45px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| } | ||
| &-open { | ||
| height: auto; | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: #666; | ||
| .fc-menu-bay(); | ||
| .fc-menu-sub-title { | ||
| &:hover { | ||
| color: @primary-color; | ||
| } | ||
| } | ||
| .fc-menu-item-selected { | ||
| line-height: 45px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| } | ||
| &-title { | ||
| &:hover { | ||
| color: #bba6fa; | ||
| } | ||
| font-size: 14px; | ||
| line-height: 48px; | ||
| display: flex; | ||
| &-icon { | ||
| .fc-menu-icon(); | ||
| } | ||
| &-label { | ||
| .fc-menu-label(); | ||
| } | ||
| } | ||
| .fc-menu-item { | ||
| height: 45px; | ||
| line-height: 34px; | ||
| &-label { | ||
| .fc-menu-label(); | ||
| font-size: 12px; | ||
| } | ||
| } | ||
| } | ||
| .collapse-btn { | ||
| &:hover { | ||
| background: fade(@primary-color, 80%); | ||
| } | ||
| width: 24px; | ||
| height: 24px; | ||
| position: fixed; | ||
| left: 0px; | ||
| top: 0px; | ||
| color: #fff; | ||
| background: fade(@primary-color, 30%); | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: 5px; | ||
| } | ||
| } | ||
| .fc-menu-sub-tooltip, | ||
| .fc-skim-menu-tooltip { | ||
| .ant-tooltip-inner { | ||
| padding: 0px; | ||
| border-radius: 7px; | ||
| } | ||
| .ant-tooltip-arrow { | ||
| display: none; | ||
| } | ||
| } | ||
| .fc-skim-menu-tooltip { | ||
| left: 10px !important; | ||
| top: 18px !important; | ||
| } | ||
| .fc-skim-menu { | ||
| width: 218px; | ||
| border-radius: 7px; | ||
| padding: 16px 24px; | ||
| background: @primary-color; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| cursor: pointer; | ||
| &-primary { | ||
| & > div { | ||
| &:hover { | ||
| font-size: 14px; | ||
| background: #310094; | ||
| border-radius: 7px; | ||
| } | ||
| padding: 0px 8px; | ||
| height: 34px; | ||
| line-height: 34px; | ||
| } | ||
| } | ||
| &-secondary { | ||
| margin-left: 20px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| &-wrap { | ||
| border-left: 2px solid #310094; | ||
| & > div { | ||
| position: relative; | ||
| color: lightgray; | ||
| &:hover { | ||
| color: @primary-background; | ||
| &::before { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 0px; | ||
| left: -2px; | ||
| height: 30px; | ||
| width: 2px; | ||
| background: #9470ff; | ||
| } | ||
| } | ||
| padding: 6px; | ||
| } | ||
| } | ||
| } | ||
| } |
| import React, { useState, useEffect, createContext } from 'react'; | ||
| import { Tooltip } from 'antd'; | ||
| import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; | ||
| import { IMenuProps, isSubMenu, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import Item from './menuItem'; | ||
| import SubMenu from './subMenu'; | ||
| import createGlobalState from 'react-use/esm/factory/createGlobalState'; | ||
| import className from 'classnames'; | ||
| import SkimMenu from './skimMenu'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| onClick: (key: string) => void; | ||
| collapsed: string | null; | ||
| switchCollapsed: () => void; | ||
| defaultSelectedKeys?: string[]; | ||
| activeMode?: 'click' | 'hover'; | ||
| } | ||
| export type { IMenuProps }; | ||
| export const useMenuSelect = createGlobalState<string[]>([]); | ||
| export const useMenuActive = createGlobalState<string[]>([]); | ||
| export const Context = createContext<{ activeMode: 'click' | 'hover' }>({ | ||
| activeMode: 'hover', | ||
| }); | ||
| export default function FcMenu(props: Props) { | ||
| const { items, onClick, collapsed, switchCollapsed, defaultSelectedKeys, activeMode = 'hover' } = props; | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [inited, setInited] = useState(false); | ||
| useEffect(() => { | ||
| if (inited) { | ||
| if (menuSelect.length > 0) { | ||
| onClick(menuSelect[menuSelect.length - 1]); | ||
| } | ||
| } else { | ||
| defaultSelectedKeys && setMenuActive(defaultSelectedKeys); | ||
| defaultSelectedKeys && setMenuSelect(defaultSelectedKeys); | ||
| setTimeout(() => { | ||
| setInited(true); | ||
| }, 0); | ||
| } | ||
| }, [menuSelect]); | ||
| return ( | ||
| <Context.Provider value={{ activeMode }}> | ||
| <div | ||
| className={className({ 'fc-menu': true, collapsed: collapsed === '1', hide: collapsed === '2', hoverMode: activeMode === 'hover' })} | ||
| onMouseLeave={() => { | ||
| if (activeMode === 'hover') { | ||
| setMenuActive([]); | ||
| } | ||
| }} | ||
| > | ||
| <div className='collapse-btn'> | ||
| {collapsed === '2' ? ( | ||
| <Tooltip title={<SkimMenu items={items} />} placement='rightTop' overlayClassName='fc-skim-menu-tooltip'> | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| </Tooltip> | ||
| ) : collapsed === '0' ? ( | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| ) : ( | ||
| <MenuUnfoldOutlined onClick={() => switchCollapsed()} /> | ||
| )} | ||
| </div> | ||
| {collapsed !== '2' && | ||
| items | ||
| .filter((item) => !!item) | ||
| .map((item) => (isSubMenu(item) ? <SubMenu item={item as SubMenuProps} collapsed={collapsed === '1'} /> : item && <Item item={item} collapsed={collapsed === '1'} />))} | ||
| </div> | ||
| </Context.Provider> | ||
| ); | ||
| } |
| import React, { ReactNode, useContext } from 'react'; | ||
| import { Tooltip } from 'antd'; | ||
| import { MenuItemProps, isSubMenu } from './types'; | ||
| import './index.less'; | ||
| import { useMenuSelect, useMenuActive, Context } from './index'; | ||
| import classNames from 'classnames'; | ||
| interface Props { | ||
| item: MenuItemProps; | ||
| subMenuKey?: string; | ||
| collapsed?: boolean; | ||
| } | ||
| export default function FcMenuItem(props: Props) { | ||
| const { item, subMenuKey, collapsed } = props; | ||
| const { activeMode } = useContext(Context); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const selected = menuSelect.includes(item.key) | ||
| const showHoverModeSelected = menuActive.length > 0 ? menuActive.includes(item.key) : selected; | ||
| const handleClick = () => { | ||
| const keys = [item.key]; | ||
| subMenuKey && keys.unshift(subMenuKey); | ||
| setMenuSelect(keys); | ||
| }; | ||
| const handleHover = () => { | ||
| if (activeMode === 'hover') { | ||
| const keys = [item.key]; | ||
| subMenuKey && keys.unshift(subMenuKey); | ||
| setMenuActive(keys); | ||
| } | ||
| }; | ||
| return ( | ||
| <div | ||
| className={classNames({ | ||
| 'fc-menu-item': true, | ||
| 'fc-menu-item-selected': activeMode === 'hover' ? showHoverModeSelected : selected, | ||
| })} | ||
| onClick={handleClick} | ||
| onMouseEnter={handleHover} | ||
| > | ||
| {!subMenuKey && (activeMode === 'hover' ? showHoverModeSelected : selected) && ( | ||
| <> | ||
| <div className='fc-menu-sub-top'></div> | ||
| <div className='fc-menu-sub-topleft-corner'></div> | ||
| <div className='fc-menu-sub-bottomleft-corner'></div> | ||
| <div className='fc-menu-sub-left'></div> | ||
| <div className='fc-menu-sub-bottom'></div> | ||
| </> | ||
| )} | ||
| {collapsed ? ( | ||
| <Tooltip title={item.label} placement='right'> | ||
| <span className='fc-menu-item-icon'>{item.icon}</span> | ||
| </Tooltip> | ||
| ) : ( | ||
| <span className='fc-menu-item-icon'>{item.icon}</span> | ||
| )} | ||
| {!collapsed && <span className='fc-menu-item-label'>{item.label}</span>} | ||
| </div> | ||
| ); | ||
| } |
| import React, { ReactNode, useState } from 'react'; | ||
| import { IMenuProps, MenuItemProps, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import { useMenuSelect } from './index'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| } | ||
| export default function FcMenuItem(props: Props) { | ||
| const { items } = props; | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [hoverSecondMenus, setHoverSecondMenus] = useState<SubMenuProps>(); | ||
| return ( | ||
| <div className={'fc-skim-menu'}> | ||
| <div className='fc-skim-menu-primary'> | ||
| {items | ||
| .filter((item) => !!item) | ||
| .map((item: SubMenuProps) => ( | ||
| <div key={item.key} onMouseEnter={() => setHoverSecondMenus(item)} onClick={() => !item.children && setMenuSelect([item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <div className='fc-skim-menu-secondary'> | ||
| <div className='fc-skim-menu-secondary-wrap'> | ||
| {hoverSecondMenus && | ||
| hoverSecondMenus.children && | ||
| hoverSecondMenus.children | ||
| .filter((item) => !!item) | ||
| .map((item: MenuItemProps) => ( | ||
| <div key={item.key} onClick={() => setMenuSelect([hoverSecondMenus.key, item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| import React, { ReactNode, useState, useContext, useEffect } from 'react'; | ||
| import { Tooltip, Menu } from 'antd'; | ||
| import { SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import Item from './menuItem'; | ||
| import classNames from 'classnames'; | ||
| import { useMenuSelect, useMenuActive, Context } from './index'; | ||
| interface Props { | ||
| item: SubMenuProps; | ||
| collapsed: boolean; | ||
| } | ||
| export default function FcSubMenu(props: Props) { | ||
| const { item, collapsed } = props; | ||
| const { activeMode } = useContext(Context); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const [collapse, setCollapse] = useState(false); | ||
| const selected = menuSelect.includes(item.key) && menuSelect.length > 1; | ||
| const showHoverModeSelected = menuActive.length > 0 ? menuActive.includes(item.key) : selected; | ||
| useEffect(() => { | ||
| if (collapsed) { | ||
| setCollapse(selected); | ||
| } | ||
| }, [menuSelect, collapsed]); | ||
| const menu = ( | ||
| <Menu onClick={({ key }) => setMenuSelect([item.key, key])}> | ||
| {item.children | ||
| .filter((i) => !!i) | ||
| .map((item) => ( | ||
| <Menu.Item key={item.key}>{item.label}</Menu.Item> | ||
| ))} | ||
| </Menu> | ||
| ); | ||
| return ( | ||
| <div | ||
| className={classNames({ | ||
| 'fc-menu-sub': true, | ||
| 'fc-menu-sub-open': collapse, | ||
| 'fc-menu-sub-selected': activeMode === 'hover' ? showHoverModeSelected : selected, //hover和click交互不同,click可以有多个选中,而hover只有一个选中 | ||
| })} | ||
| onMouseEnter={() => { | ||
| if (activeMode === 'hover') { | ||
| setCollapse(true); | ||
| setMenuActive([item.key]); | ||
| } | ||
| }} | ||
| onMouseLeave={() => { | ||
| if (activeMode === 'hover') { | ||
| setCollapse(false); | ||
| } | ||
| }} | ||
| > | ||
| {(collapse || (activeMode === 'hover' ? showHoverModeSelected : selected)) && ( | ||
| <> | ||
| <div className='fc-menu-sub-top'></div> | ||
| <div className='fc-menu-sub-topleft-corner'></div> | ||
| <div className='fc-menu-sub-bottomleft-corner'></div> | ||
| <div className='fc-menu-sub-left'></div> | ||
| <div className='fc-menu-sub-bottom'></div> | ||
| </> | ||
| )} | ||
| <div | ||
| className='fc-menu-sub-title' | ||
| onClick={() => { | ||
| if (activeMode === 'click') { | ||
| if (!collapsed) { | ||
| setCollapse(!collapse); | ||
| } | ||
| } | ||
| }} | ||
| onMouseEnter={() => { | ||
| if (activeMode === 'hover') { | ||
| setMenuActive([item.key]); | ||
| } | ||
| }} | ||
| > | ||
| {collapsed ? ( | ||
| <Tooltip title={menu} placement='right' color='white' overlayClassName='fc-menu-sub-tooltip'> | ||
| <span className='fc-menu-sub-title-icon'>{item.icon}</span> | ||
| </Tooltip> | ||
| ) : ( | ||
| <span className='fc-menu-sub-title-icon'>{item.icon}</span> | ||
| )} | ||
| {!collapsed && <span className='fc-menu-sub-title-label'>{item.label}</span>} | ||
| </div> | ||
| {activeMode === 'click' && !collapsed && collapse && item.children.filter((i) => !!i).map((i) => <Item key={i.key} item={i} subMenuKey={item.key} collapsed={collapsed} />)} | ||
| {activeMode === 'hover' && | ||
| !collapsed && | ||
| (collapse || showHoverModeSelected) && | ||
| item.children.filter((i) => !!i).map((i) => <Item key={i.key} item={i} subMenuKey={item.key} collapsed={collapsed} />)} | ||
| </div> | ||
| ); | ||
| } |
| export interface MenuItemProps { | ||
| key: string; | ||
| label: string; | ||
| icon?: React.ReactNode; | ||
| } | ||
| export interface SubMenuProps extends MenuItemProps { | ||
| children: MenuItemProps[]; | ||
| } | ||
| export type IMenuProps = MenuItemProps | SubMenuProps | undefined; | ||
| export const isSubMenu = (menu: IMenuProps): boolean => { | ||
| if (menu && menu['children']) { | ||
| return true; | ||
| } else { | ||
| return false; | ||
| } | ||
| }; |
| .fc-menu-icon { | ||
| display: flex; | ||
| width: 48px; | ||
| // height: 48px; | ||
| align-items: center; | ||
| justify-content: center; | ||
| z-index: 1; | ||
| .iconpark-icon { | ||
| width: 16px; | ||
| } | ||
| } | ||
| .fc-menu-label { | ||
| padding-left: 8px; | ||
| font-size: 14px; | ||
| } | ||
| .fc-menu-bay { | ||
| background-color: #f9f9f9; | ||
| padding-bottom: 10px; | ||
| & > .fc-menu-sub-top { | ||
| position: absolute; | ||
| width: 100%; | ||
| top: 0px; | ||
| height: 15px; | ||
| border-radius: 0 0 15px 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-bottom { | ||
| position: absolute; | ||
| width: 100%; | ||
| bottom: 0px; | ||
| height: 15px; | ||
| z-index: 1; | ||
| border-radius: 0 15px 0 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-left { | ||
| position: absolute; | ||
| height: 100%; | ||
| width: 8px; | ||
| top: 0px; | ||
| left: 0px; | ||
| background-color: @primary-color; | ||
| z-index: 1; | ||
| } | ||
| & > .fc-menu-sub-topleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| top: 15px; | ||
| left: 8px; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 16px 0 0 0; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| .fc-menu-sub-bottomleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| bottom: 15px; | ||
| left: 8px; | ||
| z-index: 1; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 0 0 0 16px; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| } | ||
| .float-fc-menu { | ||
| flex: 1; | ||
| width: 152px; | ||
| background-color: @primary-color; | ||
| color: #fff; | ||
| cursor: pointer; | ||
| border-radius: 0 0 8px 8px; | ||
| &.collapsed { | ||
| width: 48px; | ||
| .fc-menu-item-icon, | ||
| .fc-menu-sub-title-icon { | ||
| height: 48px; | ||
| } | ||
| .fc-menu-sub, | ||
| .fc-menu-item { | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| } | ||
| } | ||
| &.hide { | ||
| width: 0px; | ||
| } | ||
| &-sub { | ||
| position: relative; | ||
| overflow: hidden; | ||
| &-hover { | ||
| height: auto; | ||
| .float-fc-menu-sub-title { | ||
| color: @primary-color; | ||
| } | ||
| height: auto; | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: #666; | ||
| .fc-menu-bay(); | ||
| .fc-menu-sub-title { | ||
| &:hover { | ||
| color: @primary-color; | ||
| } | ||
| } | ||
| } | ||
| &-selected { | ||
| svg.selected { | ||
| position: absolute; | ||
| top: 5px; | ||
| right: 0px; | ||
| } | ||
| } | ||
| &-title { | ||
| &:hover { | ||
| color: @primary-color; | ||
| } | ||
| font-size: 14px; | ||
| line-height: 48px; | ||
| display: flex; | ||
| &-icon { | ||
| .fc-menu-icon(); | ||
| } | ||
| &-label { | ||
| .fc-menu-label(); | ||
| } | ||
| } | ||
| .fc-menu-item { | ||
| height: 45px; | ||
| line-height: 34px; | ||
| &-label { | ||
| flex: 1; | ||
| .fc-menu-label(); | ||
| font-size: 12px; | ||
| } | ||
| } | ||
| } | ||
| .collapse-btn { | ||
| &:hover { | ||
| background: fade(@primary-color, 80%); | ||
| } | ||
| width: 24px; | ||
| height: 24px; | ||
| position: fixed; | ||
| left: 0px; | ||
| top: 0px; | ||
| color: #fff; | ||
| background: fade(@primary-color, 30%); | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: 5px; | ||
| } | ||
| } | ||
| .fc-menu-sub-tooltip, | ||
| .fc-skim-menu-tooltip { | ||
| &.ant-tooltip-placement-right { | ||
| padding-left: 0px; | ||
| } | ||
| .ant-tooltip-inner { | ||
| padding: 0px; | ||
| border-radius: 20px; | ||
| padding: 8px; | ||
| background: none; | ||
| margin-left: -4px; | ||
| .ant-menu { | ||
| width: 112px; | ||
| border-right: none; | ||
| background: none; | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| width: 100%; | ||
| height: 100%; | ||
| z-index: -1; | ||
| background: #f9f9f980; | ||
| backdrop-filter: blur(3px); | ||
| } | ||
| &-item { | ||
| text-align: center; | ||
| color: #333; | ||
| &-active { | ||
| color: @primary-color; | ||
| } | ||
| &-selected { | ||
| color: #fff; | ||
| border-radius: 8px; | ||
| background-color: fade(@primary-color, 80%); | ||
| } | ||
| } | ||
| } | ||
| // .ant-menu-item-selected { | ||
| // color: #fff; | ||
| // border-radius: 8px; | ||
| // background-color: fade(@primary-color, 80%); | ||
| // } | ||
| } | ||
| .ant-tooltip-arrow { | ||
| display: none; | ||
| } | ||
| } | ||
| .fc-skim-menu-tooltip { | ||
| left: 10px !important; | ||
| top: 18px !important; | ||
| } | ||
| .fc-skim-menu { | ||
| width: 218px; | ||
| border-radius: 7px; | ||
| padding: 16px 24px; | ||
| background: @primary-color; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| cursor: pointer; | ||
| &-primary { | ||
| & > div { | ||
| &:hover { | ||
| font-size: 14px; | ||
| background: #310094; | ||
| border-radius: 7px; | ||
| } | ||
| padding: 0px 8px; | ||
| height: 34px; | ||
| line-height: 34px; | ||
| } | ||
| } | ||
| &-secondary { | ||
| margin-left: 20px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| &-wrap { | ||
| border-left: 2px solid #310094; | ||
| & > div { | ||
| position: relative; | ||
| color: lightgray; | ||
| &:hover { | ||
| color: @primary-background; | ||
| &::before { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 0px; | ||
| left: -2px; | ||
| height: 30px; | ||
| width: 2px; | ||
| background: #9470ff; | ||
| } | ||
| } | ||
| padding: 6px; | ||
| } | ||
| } | ||
| } | ||
| } |
| import React, { useState, useEffect, createContext } from 'react'; | ||
| import { Tooltip } from 'antd'; | ||
| import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; | ||
| import { IMenuProps, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import SubMenu from './subMenu'; | ||
| import createGlobalState from 'react-use/esm/factory/createGlobalState'; | ||
| import className from 'classnames'; | ||
| import SkimMenu from './skimMenu'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| onClick: (key: string) => void; | ||
| collapsed: string | null; | ||
| switchCollapsed: () => void; | ||
| defaultSelectedKeys?: string[]; | ||
| } | ||
| export type { IMenuProps }; | ||
| export const useMenuSelect = createGlobalState<string[]>([]); | ||
| export const useMenuActive = createGlobalState<string[]>([]); | ||
| export default function FcMenu(props: Props) { | ||
| const { items, onClick, collapsed, switchCollapsed, defaultSelectedKeys } = props; | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [inited, setInited] = useState(false); | ||
| useEffect(() => { | ||
| if (inited) { | ||
| if (menuSelect.length > 0) { | ||
| onClick(menuSelect[menuSelect.length - 1]); | ||
| } | ||
| } else { | ||
| defaultSelectedKeys && setMenuSelect(defaultSelectedKeys); | ||
| setTimeout(() => { | ||
| setInited(true); | ||
| }, 0); | ||
| } | ||
| }, [menuSelect]); | ||
| return ( | ||
| <div | ||
| className={className({ | ||
| 'float-fc-menu': true, | ||
| collapsed: collapsed === '1', | ||
| hide: collapsed === '2', | ||
| })} | ||
| onMouseLeave={() => { | ||
| setMenuActive([]); | ||
| }} | ||
| > | ||
| <div className='collapse-btn'> | ||
| {collapsed === '2' ? ( | ||
| <Tooltip title={<SkimMenu items={items} />} placement='rightTop' overlayClassName='fc-skim-menu-tooltip'> | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| </Tooltip> | ||
| ) : collapsed === '0' ? ( | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| ) : ( | ||
| <MenuUnfoldOutlined onClick={() => switchCollapsed()} /> | ||
| )} | ||
| </div> | ||
| {collapsed !== '2' && | ||
| items | ||
| .filter((item) => !!item) | ||
| .map((item) => <SubMenu item={item as SubMenuProps} collapsed={collapsed === '1'} key={item.key} />)} | ||
| </div> | ||
| ); | ||
| } |
| import React, { ReactNode, useState } from 'react'; | ||
| import { IMenuProps, MenuItemProps, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import { useMenuSelect } from './index'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| } | ||
| export default function FcMenuItem(props: Props) { | ||
| const { items } = props; | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [hoverSecondMenus, setHoverSecondMenus] = useState<SubMenuProps>(); | ||
| return ( | ||
| <div className={'fc-skim-menu'}> | ||
| <div className='fc-skim-menu-primary'> | ||
| {items | ||
| .filter((item) => !!item) | ||
| .map((item: SubMenuProps) => ( | ||
| <div key={item.key} onMouseEnter={() => setHoverSecondMenus(item)} onClick={() => !item.children && setMenuSelect([item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <div className='fc-skim-menu-secondary'> | ||
| <div className='fc-skim-menu-secondary-wrap'> | ||
| {hoverSecondMenus && | ||
| hoverSecondMenus.children && | ||
| hoverSecondMenus.children | ||
| .filter((item) => !!item) | ||
| .map((item: MenuItemProps) => ( | ||
| <div key={item.key} onClick={() => setMenuSelect([hoverSecondMenus.key, item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| import React from 'react'; | ||
| import { Tooltip, Menu } from 'antd'; | ||
| import { SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import classNames from 'classnames'; | ||
| import { useMenuSelect, useMenuActive } from './index'; | ||
| const selectedSvg = ( | ||
| <svg | ||
| width='8px' | ||
| height='38px' | ||
| viewBox='0 0 8 38' | ||
| version='1.1' | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| className='selected' | ||
| > | ||
| <title>selected</title> | ||
| <g id='控件' stroke='none' strokeWidth='1' fill='none'> | ||
| <g | ||
| id='✦-Navigation/01.Left-Menu/03.Elements-01.L1/02.Parent/03.Selected/01.Purple_short' | ||
| transform='translate(-144.000000, 0.000000)' | ||
| fill='#F9F9F9' | ||
| > | ||
| <path | ||
| d='M144,38 C144,28 152,27 152,19 C152,11 144,10 144,0 C144,0 144,6.33333333 144,19 C144,31.6666667 144,38 144,38 Z' | ||
| id='selected' | ||
| transform='translate(148.000000, 19.000000) scale(-1, 1) translate(-148.000000, -19.000000) ' | ||
| ></path> | ||
| </g> | ||
| </g> | ||
| </svg> | ||
| ); | ||
| interface Props { | ||
| item: SubMenuProps; | ||
| collapsed: boolean; | ||
| } | ||
| export default function FcSubMenu(props: Props) { | ||
| const { item, collapsed } = props; | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const selected = menuSelect.includes(item.key) && menuSelect.length > 1; | ||
| const hovering = menuActive.includes(item.key); | ||
| const children = item.children || [item]; | ||
| const menu = ( | ||
| <Menu | ||
| onClick={({ key, domEvent }) => { | ||
| domEvent.stopPropagation(); | ||
| setMenuSelect([item.key, key]); | ||
| }} | ||
| selectedKeys={menuSelect} | ||
| > | ||
| {children | ||
| .filter((i) => !!i) | ||
| .map((item) => ( | ||
| <Menu.Item key={item.key}>{item.label}</Menu.Item> | ||
| ))} | ||
| </Menu> | ||
| ); | ||
| return ( | ||
| <div | ||
| className={classNames({ | ||
| 'float-fc-menu-sub': true, | ||
| 'float-fc-menu-sub-hover': hovering, | ||
| 'float-fc-menu-sub-selected': selected, | ||
| })} | ||
| > | ||
| {hovering && ( | ||
| <> | ||
| <div className='fc-menu-sub-top'></div> | ||
| <div className='fc-menu-sub-topleft-corner'></div> | ||
| <div className='fc-menu-sub-bottomleft-corner'></div> | ||
| <div className='fc-menu-sub-left'></div> | ||
| <div className='fc-menu-sub-bottom'></div> | ||
| </> | ||
| )} | ||
| <div | ||
| className='fc-menu-sub-title' | ||
| onClick={(e) => setMenuSelect([item.key, children[0].key])} | ||
| onMouseEnter={() => { | ||
| setMenuActive([item.key]); | ||
| }} | ||
| > | ||
| {selected && selectedSvg} | ||
| <Tooltip title={menu} placement='right' overlayClassName='fc-menu-sub-tooltip'> | ||
| <div style={{ display: 'flex', flex: 1 }}> | ||
| <span className='fc-menu-sub-title-icon'>{item.icon}</span> | ||
| {!collapsed && <span className='fc-menu-sub-title-label'>{item.label}</span>} | ||
| </div> | ||
| </Tooltip> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| export interface MenuItemProps { | ||
| key: string; | ||
| label: string; | ||
| icon?: React.ReactNode; | ||
| } | ||
| export interface SubMenuProps extends MenuItemProps { | ||
| children: MenuItemProps[]; | ||
| } | ||
| export type IMenuProps = SubMenuProps | undefined; |
+1
-1
| { | ||
| "name": "@fc-components/menu", | ||
| "version": "1.0.1", | ||
| "version": "1.0.2", | ||
| "description": "The Menu component used for flashcat.", | ||
@@ -5,0 +5,0 @@ "main": "src/index.tsx", |
+17
-1
| # @fc-components/menu | ||
| The menu component used for [flashcat](https://flashcat.cloud) and [n9e](https://github.com/didi/nightingale). | ||
| ## Usage | ||
| https://github.com/n9e/fe-v5/blob/master/src/components/menu/index.tsx#L306 | ||
| ## Type | ||
| 目前有两种菜单模式,CollapseFcMenu(折叠菜单), FloatFcMenu(浮动菜单) | ||
| ## Publish | ||
| ``` | ||
@@ -13,2 +20,5 @@ npm publish --access public | ||
| ## how to debugger | ||
| 方法 1:link 进去 | ||
| ``` | ||
@@ -19,2 +29,8 @@ //在本项目中 | ||
| npm link @fc-components/menu | ||
| ``` | ||
| ``` | ||
| 方法 2:相对路径引入 | ||
| ``` | ||
| import { FloatFcMenu, CollapseFcMenu } from '../../../../fc-menu/src'; | ||
| ``` |
+3
-77
@@ -1,77 +0,3 @@ | ||
| import React, { useState, useEffect, createContext } from 'react'; | ||
| import { Tooltip } from 'antd'; | ||
| import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; | ||
| import { IMenuProps, isSubMenu, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import Item from './menuItem'; | ||
| import SubMenu from './subMenu'; | ||
| import createGlobalState from 'react-use/esm/factory/createGlobalState'; | ||
| import className from 'classnames'; | ||
| import SkimMenu from './skimMenu'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| onClick: (key: string) => void; | ||
| collapsed: string | null; | ||
| switchCollapsed: () => void; | ||
| defaultSelectedKeys?: string[]; | ||
| activeMode?: 'click' | 'hover'; | ||
| } | ||
| export type { IMenuProps }; | ||
| export const useMenuSelect = createGlobalState<string[]>([]); | ||
| export const useMenuActive = createGlobalState<string[]>([]); | ||
| export const Context = createContext<{ activeMode: 'click' | 'hover' }>({ | ||
| activeMode: 'hover', | ||
| }); | ||
| export default function FcMenu(props: Props) { | ||
| const { items, onClick, collapsed, switchCollapsed, defaultSelectedKeys, activeMode = 'hover' } = props; | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [inited, setInited] = useState(false); | ||
| useEffect(() => { | ||
| if (inited) { | ||
| if (menuSelect.length > 0) { | ||
| onClick(menuSelect[menuSelect.length - 1]); | ||
| } | ||
| } else { | ||
| defaultSelectedKeys && setMenuActive(defaultSelectedKeys); | ||
| defaultSelectedKeys && setMenuSelect(defaultSelectedKeys); | ||
| setTimeout(() => { | ||
| setInited(true); | ||
| }, 0); | ||
| } | ||
| }, [menuSelect]); | ||
| return ( | ||
| <Context.Provider value={{ activeMode }}> | ||
| <div | ||
| className={className({ 'fc-menu': true, collapsed: collapsed === '1', hide: collapsed === '2', hoverMode: activeMode === 'hover' })} | ||
| onMouseLeave={() => { | ||
| if (activeMode === 'hover') { | ||
| setMenuActive([]); | ||
| } | ||
| }} | ||
| > | ||
| <div className='collapse-btn'> | ||
| {collapsed === '2' ? ( | ||
| <Tooltip title={<SkimMenu items={items} />} placement='rightTop' overlayClassName='fc-skim-menu-tooltip'> | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| </Tooltip> | ||
| ) : collapsed === '0' ? ( | ||
| <MenuFoldOutlined onClick={() => switchCollapsed()} /> | ||
| ) : ( | ||
| <MenuUnfoldOutlined onClick={() => switchCollapsed()} /> | ||
| )} | ||
| </div> | ||
| {collapsed !== '2' && | ||
| items | ||
| .filter((item) => !!item) | ||
| .map((item) => (isSubMenu(item) ? <SubMenu item={item as SubMenuProps} collapsed={collapsed === '1'} /> : item && <Item item={item} collapsed={collapsed === '1'} />))} | ||
| </div> | ||
| </Context.Provider> | ||
| ); | ||
| } | ||
| import CollapseFcMenu from './Collapse'; | ||
| import FloatFcMenu from './Float'; | ||
| export { CollapseFcMenu, FloatFcMenu }; |
-280
| .fc-menu-icon { | ||
| display: flex; | ||
| width: 48px; | ||
| // height: 48px; | ||
| align-items: center; | ||
| justify-content: center; | ||
| z-index: 1; | ||
| .iconpark-icon { | ||
| width: 16px; | ||
| } | ||
| } | ||
| .fc-menu-label { | ||
| padding-left: 8px; | ||
| font-size: 14px; | ||
| } | ||
| .fc-menu-bay { | ||
| background-color: #f9f9f9; | ||
| padding-bottom: 10px; | ||
| & > .fc-menu-sub-top { | ||
| position: absolute; | ||
| width: 100%; | ||
| top: 0px; | ||
| height: 15px; | ||
| border-radius: 0 0 15px 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-bottom { | ||
| position: absolute; | ||
| width: 100%; | ||
| bottom: 0px; | ||
| height: 15px; | ||
| z-index: 1; | ||
| border-radius: 0 15px 0 0; | ||
| background-color: @primary-color; | ||
| } | ||
| & > .fc-menu-sub-left { | ||
| position: absolute; | ||
| height: 100%; | ||
| width: 8px; | ||
| top: 0px; | ||
| left: 0px; | ||
| background-color: @primary-color; | ||
| z-index: 1; | ||
| } | ||
| & > .fc-menu-sub-topleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| top: 15px; | ||
| left: 8px; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 16px 0 0 0; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| .fc-menu-sub-bottomleft-corner { | ||
| position: absolute; | ||
| height: 16px; | ||
| width: 16px; | ||
| bottom: 15px; | ||
| left: 8px; | ||
| z-index: 1; | ||
| background-color: @primary-color; | ||
| &::before { | ||
| display: block; | ||
| content: ''; | ||
| width: 100%; | ||
| height: 100%; | ||
| border-radius: 0 0 0 16px; | ||
| background: #f9f9f9; | ||
| } | ||
| } | ||
| } | ||
| .fc-menu { | ||
| border-radius: 0 0 8px 8px; | ||
| &.collapsed { | ||
| width: 48px; | ||
| .fc-menu-item-icon, | ||
| .fc-menu-sub-title-icon { | ||
| height: 48px; | ||
| } | ||
| .fc-menu-sub, | ||
| .fc-menu-item { | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| } | ||
| } | ||
| &.hide { | ||
| width: 0px; | ||
| } | ||
| &.hoverMode { | ||
| .fc-menu-sub-selected { | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: #666; | ||
| .fc-menu-bay(); | ||
| .fc-menu-sub-title, | ||
| .fc-menu-item-selected { | ||
| color: @primary-color; | ||
| padding: 0; | ||
| } | ||
| } | ||
| } | ||
| flex: 1; | ||
| width: 152px; | ||
| background-color: @primary-color; | ||
| color: #fff; | ||
| cursor: pointer; | ||
| &-item { | ||
| line-height: 48px; | ||
| display: flex; | ||
| position: relative; | ||
| &-selected { | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| &:hover { | ||
| color: #bba6fa; | ||
| } | ||
| &-icon { | ||
| .fc-menu-icon(); | ||
| } | ||
| &-label { | ||
| .fc-menu-label(); | ||
| } | ||
| } | ||
| &-sub { | ||
| position: relative; | ||
| overflow: hidden; | ||
| &-selected { | ||
| height: auto; | ||
| &:not(.fc-menu-sub-open) { | ||
| .fc-menu-sub-title { | ||
| color: #bba6fa; | ||
| } | ||
| } | ||
| &.fc-menu-sub-open { | ||
| .fc-menu-sub-title, | ||
| .fc-menu-item-selected { | ||
| color: @primary-color; | ||
| padding: 0; | ||
| } | ||
| } | ||
| .fc-menu-item-selected { | ||
| line-height: 45px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| } | ||
| &-open { | ||
| height: auto; | ||
| background-color: #f9f9f9; | ||
| padding-top: 10px; | ||
| padding-bottom: 10px; | ||
| color: #666; | ||
| .fc-menu-bay(); | ||
| .fc-menu-sub-title { | ||
| &:hover { | ||
| color: @primary-color; | ||
| } | ||
| } | ||
| .fc-menu-item-selected { | ||
| line-height: 45px; | ||
| color: @primary-color; | ||
| .fc-menu-bay(); | ||
| } | ||
| } | ||
| &-title { | ||
| &:hover { | ||
| color: #bba6fa; | ||
| } | ||
| font-size: 14px; | ||
| line-height: 48px; | ||
| display: flex; | ||
| &-icon { | ||
| .fc-menu-icon(); | ||
| } | ||
| &-label { | ||
| .fc-menu-label(); | ||
| } | ||
| } | ||
| .fc-menu-item { | ||
| height: 45px; | ||
| line-height: 34px; | ||
| &-label { | ||
| .fc-menu-label(); | ||
| font-size: 12px; | ||
| } | ||
| } | ||
| } | ||
| .collapse-btn { | ||
| &:hover { | ||
| background: fade(@primary-color, 80%); | ||
| } | ||
| width: 24px; | ||
| height: 24px; | ||
| position: fixed; | ||
| left: 0px; | ||
| top: 0px; | ||
| color: #fff; | ||
| background: fade(@primary-color, 30%); | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: 5px; | ||
| } | ||
| } | ||
| .fc-menu-sub-tooltip, | ||
| .fc-skim-menu-tooltip { | ||
| .ant-tooltip-inner { | ||
| padding: 0px; | ||
| border-radius: 7px; | ||
| } | ||
| .ant-tooltip-arrow { | ||
| display: none; | ||
| } | ||
| } | ||
| .fc-skim-menu-tooltip { | ||
| left: 10px !important; | ||
| top: 18px !important; | ||
| } | ||
| .fc-skim-menu { | ||
| width: 218px; | ||
| border-radius: 7px; | ||
| padding: 16px 24px; | ||
| background: @primary-color; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| cursor: pointer; | ||
| &-primary { | ||
| & > div { | ||
| &:hover { | ||
| font-size: 14px; | ||
| background: #310094; | ||
| border-radius: 7px; | ||
| } | ||
| padding: 0px 8px; | ||
| height: 34px; | ||
| line-height: 34px; | ||
| } | ||
| } | ||
| &-secondary { | ||
| margin-left: 20px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| &-wrap { | ||
| border-left: 2px solid #310094; | ||
| & > div { | ||
| position: relative; | ||
| color: lightgray; | ||
| &:hover { | ||
| color: @primary-background; | ||
| &::before { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 0px; | ||
| left: -2px; | ||
| height: 30px; | ||
| width: 2px; | ||
| background: #9470ff; | ||
| } | ||
| } | ||
| padding: 6px; | ||
| } | ||
| } | ||
| } | ||
| } |
| import React, { ReactNode, useContext } from 'react'; | ||
| import { Tooltip } from 'antd'; | ||
| import { MenuItemProps, isSubMenu } from './types'; | ||
| import './index.less'; | ||
| import { useMenuSelect, useMenuActive, Context } from './index'; | ||
| import classNames from 'classnames'; | ||
| interface Props { | ||
| item: MenuItemProps; | ||
| subMenuKey?: string; | ||
| collapsed?: boolean; | ||
| } | ||
| export default function FcMenuItem(props: Props) { | ||
| const { item, subMenuKey, collapsed } = props; | ||
| const { activeMode } = useContext(Context); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const selected = menuSelect.includes(item.key) | ||
| const showHoverModeSelected = menuActive.length > 0 ? menuActive.includes(item.key) : selected; | ||
| const handleClick = () => { | ||
| const keys = [item.key]; | ||
| subMenuKey && keys.unshift(subMenuKey); | ||
| setMenuSelect(keys); | ||
| }; | ||
| const handleHover = () => { | ||
| if (activeMode === 'hover') { | ||
| const keys = [item.key]; | ||
| subMenuKey && keys.unshift(subMenuKey); | ||
| setMenuActive(keys); | ||
| } | ||
| }; | ||
| return ( | ||
| <div | ||
| className={classNames({ | ||
| 'fc-menu-item': true, | ||
| 'fc-menu-item-selected': activeMode === 'hover' ? showHoverModeSelected : selected, | ||
| })} | ||
| onClick={handleClick} | ||
| onMouseEnter={handleHover} | ||
| > | ||
| {!subMenuKey && (activeMode === 'hover' ? showHoverModeSelected : selected) && ( | ||
| <> | ||
| <div className='fc-menu-sub-top'></div> | ||
| <div className='fc-menu-sub-topleft-corner'></div> | ||
| <div className='fc-menu-sub-bottomleft-corner'></div> | ||
| <div className='fc-menu-sub-left'></div> | ||
| <div className='fc-menu-sub-bottom'></div> | ||
| </> | ||
| )} | ||
| {collapsed ? ( | ||
| <Tooltip title={item.label} placement='right'> | ||
| <span className='fc-menu-item-icon'>{item.icon}</span> | ||
| </Tooltip> | ||
| ) : ( | ||
| <span className='fc-menu-item-icon'>{item.icon}</span> | ||
| )} | ||
| {!collapsed && <span className='fc-menu-item-label'>{item.label}</span>} | ||
| </div> | ||
| ); | ||
| } |
| import React, { ReactNode, useState } from 'react'; | ||
| import { IMenuProps, MenuItemProps, SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import { useMenuSelect } from './index'; | ||
| interface Props { | ||
| items: IMenuProps[]; | ||
| } | ||
| export default function FcMenuItem(props: Props) { | ||
| const { items } = props; | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [hoverSecondMenus, setHoverSecondMenus] = useState<SubMenuProps>(); | ||
| return ( | ||
| <div className={'fc-skim-menu'}> | ||
| <div className='fc-skim-menu-primary'> | ||
| {items | ||
| .filter((item) => !!item) | ||
| .map((item: SubMenuProps) => ( | ||
| <div key={item.key} onMouseEnter={() => setHoverSecondMenus(item)} onClick={() => !item.children && setMenuSelect([item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <div className='fc-skim-menu-secondary'> | ||
| <div className='fc-skim-menu-secondary-wrap'> | ||
| {hoverSecondMenus && | ||
| hoverSecondMenus.children && | ||
| hoverSecondMenus.children | ||
| .filter((item) => !!item) | ||
| .map((item: MenuItemProps) => ( | ||
| <div key={item.key} onClick={() => setMenuSelect([hoverSecondMenus.key, item.key])}> | ||
| {item.label} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| import React, { ReactNode, useState, useContext, useEffect } from 'react'; | ||
| import { Tooltip, Menu } from 'antd'; | ||
| import { SubMenuProps } from './types'; | ||
| import './index.less'; | ||
| import Item from './menuItem'; | ||
| import classNames from 'classnames'; | ||
| import { useMenuSelect, useMenuActive, Context } from './index'; | ||
| interface Props { | ||
| item: SubMenuProps; | ||
| collapsed: boolean; | ||
| } | ||
| export default function FcSubMenu(props: Props) { | ||
| const { item, collapsed } = props; | ||
| const { activeMode } = useContext(Context); | ||
| const [menuSelect, setMenuSelect] = useMenuSelect(); | ||
| const [menuActive, setMenuActive] = useMenuActive(); | ||
| const [collapse, setCollapse] = useState(false); | ||
| const selected = menuSelect.includes(item.key) && menuSelect.length > 1; | ||
| const showHoverModeSelected = menuActive.length > 0 ? menuActive.includes(item.key) : selected; | ||
| useEffect(() => { | ||
| if (collapsed) { | ||
| setCollapse(selected); | ||
| } | ||
| }, [menuSelect, collapsed]); | ||
| const menu = ( | ||
| <Menu onClick={({ key }) => setMenuSelect([item.key, key])}> | ||
| {item.children | ||
| .filter((i) => !!i) | ||
| .map((item) => ( | ||
| <Menu.Item key={item.key}>{item.label}</Menu.Item> | ||
| ))} | ||
| </Menu> | ||
| ); | ||
| return ( | ||
| <div | ||
| className={classNames({ | ||
| 'fc-menu-sub': true, | ||
| 'fc-menu-sub-open': collapse, | ||
| 'fc-menu-sub-selected': activeMode === 'hover' ? showHoverModeSelected : selected, //hover和click交互不同,click可以有多个选中,而hover只有一个选中 | ||
| })} | ||
| onMouseEnter={() => { | ||
| if (activeMode === 'hover') { | ||
| setCollapse(true); | ||
| setMenuActive([item.key]); | ||
| } | ||
| }} | ||
| onMouseLeave={() => { | ||
| if (activeMode === 'hover') { | ||
| setCollapse(false); | ||
| } | ||
| }} | ||
| > | ||
| {(collapse || (activeMode === 'hover' ? showHoverModeSelected : selected)) && ( | ||
| <> | ||
| <div className='fc-menu-sub-top'></div> | ||
| <div className='fc-menu-sub-topleft-corner'></div> | ||
| <div className='fc-menu-sub-bottomleft-corner'></div> | ||
| <div className='fc-menu-sub-left'></div> | ||
| <div className='fc-menu-sub-bottom'></div> | ||
| </> | ||
| )} | ||
| <div | ||
| className='fc-menu-sub-title' | ||
| onClick={() => { | ||
| if (activeMode === 'click') { | ||
| if (!collapsed) { | ||
| setCollapse(!collapse); | ||
| } | ||
| } | ||
| }} | ||
| onMouseEnter={() => { | ||
| if (activeMode === 'hover') { | ||
| setMenuActive([item.key]); | ||
| } | ||
| }} | ||
| > | ||
| {collapsed ? ( | ||
| <Tooltip title={menu} placement='right' color='white' overlayClassName='fc-menu-sub-tooltip'> | ||
| <span className='fc-menu-sub-title-icon'>{item.icon}</span> | ||
| </Tooltip> | ||
| ) : ( | ||
| <span className='fc-menu-sub-title-icon'>{item.icon}</span> | ||
| )} | ||
| {!collapsed && <span className='fc-menu-sub-title-label'>{item.label}</span>} | ||
| </div> | ||
| {activeMode === 'click' && !collapsed && collapse && item.children.filter((i) => !!i).map((i) => <Item key={i.key} item={i} subMenuKey={item.key} collapsed={collapsed} />)} | ||
| {activeMode === 'hover' && | ||
| !collapsed && | ||
| (collapse || showHoverModeSelected) && | ||
| item.children.filter((i) => !!i).map((i) => <Item key={i.key} item={i} subMenuKey={item.key} collapsed={collapsed} />)} | ||
| </div> | ||
| ); | ||
| } |
-19
| export interface MenuItemProps { | ||
| key: string; | ||
| label: string; | ||
| icon?: React.ReactNode; | ||
| } | ||
| export interface SubMenuProps extends MenuItemProps { | ||
| children: MenuItemProps[]; | ||
| } | ||
| export type IMenuProps = MenuItemProps | SubMenuProps | undefined; | ||
| export const isSubMenu = (menu: IMenuProps): boolean => { | ||
| if (menu && menu['children']) { | ||
| return true; | ||
| } else { | ||
| return false; | ||
| } | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
28920
74.43%16
60%499
70.89%35
94.44%2
100%1
Infinity%