Files
FastGPT/packages/web/components/common/MyMenu/index.tsx
Archer 3a4b4a866b Team group (#2864)
* feat(member-group): Team (#2616)

* feat: member-group schema define

* feat(fe): create group

* feat: add group edit modal

* feat(fe): add avatar group component

* feat: edit group
fix: permission select menu style

* feat: bio-mode support for select-member component

* fix: avatar group key unique

* feat: group manage

* feat: divide member into group and clbs

* feat: finish team permission

* chore: adjust

* fix: get clbs

* perf: groups code

* pref: member group for team (#2706)

* chore: fe adjust
fix: remove the member from groups when removing from team
feat: change the groups avatar when updating the team's avatar

* chore: DefaultGroupName as a constant string ''

* fix: create default group when create team for root

* feat: comment

* feat: 4811 init

* pref: member group for team (#2732)

* chore: default group name

* feat: get default group when get by tmbid

* feat(fe): adjust

* member ui

* fix: delete group (#2736)

* perf: init4811

* pref: member group (#2818)

* fix: update clb per then refetch clb list

* fix: calculate group permission

* feat(fe): group tag

* refactor(fe): team and group manage

* feat: manage group member

* feat: add group transfer owner modal

* feat: group manage member

* chore: adjust the file structure

* pref: member group

* chore: adjust fe style

* fix: ts error

* chore: fe adjust

* chore: fe adjust

* chore: adjust

* chore: adjust the code

* perf: i18n and schema name

* pref: member-group (#2862)

* feat: group list ordered by updateTime

* fix: transfer ownership of group when deleting member

* fix: i18n fix

* feat: can not set member as admin/owner when user is not active

* fix: GroupInfoModal hover input do not change color

* fix(fe): searchinput do not scroll

* perf: team group ui

* doc

* remove enum

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
2024-10-09 18:32:10 +08:00

226 lines
5.7 KiB
TypeScript

import React, { useMemo, useRef, useState } from 'react';
import {
Menu,
MenuList,
MenuItem,
Box,
useOutsideClick,
MenuButton,
MenuItemProps,
PlacementWithLogical
} from '@chakra-ui/react';
import MyDivider from '../MyDivider';
import type { IconNameType } from '../Icon/type';
import { useSystem } from '../../../hooks/useSystem';
import Avatar from '../Avatar';
export type MenuItemType = 'primary' | 'danger';
export type Props = {
width?: number | string;
offset?: [number, number];
Button: React.ReactNode;
trigger?: 'hover' | 'click';
iconSize?: string;
iconRadius?: string;
placement?: PlacementWithLogical;
menuList: {
label?: string;
children: {
isActive?: boolean;
type?: MenuItemType;
icon?: IconNameType | string;
label: string | React.ReactNode;
description?: string;
onClick?: () => any;
menuItemStyles?: MenuItemProps;
}[];
}[];
};
const MyMenu = ({
width = 'auto',
trigger = 'hover',
offset,
iconSize = '1rem',
Button,
menuList,
iconRadius,
placement = 'bottom-start'
}: Props) => {
const typeMapStyle: Record<MenuItemType, MenuItemProps> = {
primary: {
_hover: {
backgroundColor: 'primary.50',
color: 'primary.600'
},
_focus: {
backgroundColor: 'primary.50',
color: 'primary.600'
},
_active: {
backgroundColor: 'primary.50',
color: 'primary.600'
}
},
danger: {
color: 'red.600',
_hover: {
background: 'red.1'
},
_focus: {
background: 'red.1'
},
_active: {
background: 'red.1'
}
}
};
const { isPc } = useSystem();
const ref = useRef<HTMLDivElement>(null);
const closeTimer = useRef<any>();
const [isOpen, setIsOpen] = useState(false);
const formatTrigger = !isPc ? 'click' : trigger;
useOutsideClick({
ref: ref,
handler: () => {
setIsOpen(false);
}
});
const computeOffset = useMemo<[number, number]>(() => {
if (offset) return offset;
if (typeof width === 'number') return [-width / 2, 5];
return [0, 5];
}, [offset]);
return (
<Menu
offset={computeOffset}
isOpen={isOpen}
autoSelect={false}
direction={'ltr'}
isLazy
lazyBehavior={'keepMounted'}
placement={placement}
computePositionOnMount
>
<Box
ref={ref}
onMouseEnter={() => {
if (formatTrigger === 'hover') {
setIsOpen(true);
}
clearTimeout(closeTimer.current);
}}
onMouseLeave={() => {
if (formatTrigger === 'hover') {
closeTimer.current = setTimeout(() => {
setIsOpen(false);
}, 100);
}
}}
>
<Box
position={'relative'}
onClickCapture={(e) => {
e.stopPropagation();
if (formatTrigger === 'click') {
setIsOpen(!isOpen);
}
}}
>
<MenuButton
w={'100%'}
h={'100%'}
position={'absolute'}
top={0}
right={0}
bottom={0}
left={0}
/>
<Box
position={'relative'}
color={isOpen ? 'primary.600' : ''}
w="fit-content"
p="1"
bgColor={isOpen ? 'myGray.50' : ''}
h="fit-content"
borderRadius="sm"
>
{Button}
</Box>
</Box>
<MenuList
minW={isOpen ? `${width}px !important` : '80px'}
zIndex={100}
maxW={'300px'}
p={'6px'}
border={'1px solid #fff'}
boxShadow={'3'}
>
{menuList.map((item, i) => {
return (
<Box key={i}>
{item.label && <Box fontSize={'sm'}>{item.label}</Box>}
{i !== 0 && <MyDivider h={'1.5px'} my={1} />}
{item.children.map((child, index) => (
<MenuItem
key={index}
borderRadius={'sm'}
onClick={(e) => {
e.stopPropagation();
if (child.onClick) {
setIsOpen(false);
child.onClick();
}
}}
py={2}
px={3}
alignItems={'center'}
fontSize={'sm'}
color={child.isActive ? 'primary.700' : 'myGray.600'}
whiteSpace={'pre-wrap'}
_notLast={{ mb: 0.5 }}
{...typeMapStyle[child.type || 'primary']}
{...child.menuItemStyles}
>
{!!child.icon && (
<Avatar
src={child.icon as any}
borderRadius={iconRadius}
w={iconSize}
mr={3}
/>
)}
<Box w={'100%'}>
<Box
w={'100%'}
color={child.description ? 'myGray.900' : 'inherit'}
fontSize={'sm'}
>
{child.label}
</Box>
{child.description && (
<Box color={'myGray.500'} fontSize={'mini'} w={'100%'}>
{child.description}
</Box>
)}
</Box>
</MenuItem>
))}
</Box>
);
})}
</MenuList>
</Box>
</Menu>
);
};
export default React.memo(MyMenu);