🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@fc-components/menu

Package Overview
Dependencies
Maintainers
3
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fc-components/menu - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2
+280
src/Collapse/index.less
.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",

# @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';
```

@@ -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 };
.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>
);
}
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;
}
};