From c1f8d5b0324c050a7d1b9887d64d72e8bb477705 Mon Sep 17 00:00:00 2001 From: dreamer6680 <1468683855@qq.com> Date: Tue, 3 Jun 2025 22:05:07 +0800 Subject: [PATCH] add secondary.tsx (#4946) * add secondary.tsx * fix --------- Co-authored-by: dreamer6680 <146868355@qq.com> --- .../components/common/MyMenu/secondary.tsx | 350 ++++++++++++++++++ projects/app/src/pages/dataset/list/index.tsx | 63 ++-- 2 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 packages/web/components/common/MyMenu/secondary.tsx diff --git a/packages/web/components/common/MyMenu/secondary.tsx b/packages/web/components/common/MyMenu/secondary.tsx new file mode 100644 index 000000000..2883627c8 --- /dev/null +++ b/packages/web/components/common/MyMenu/secondary.tsx @@ -0,0 +1,350 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { + Box, + Flex, + type MenuItemProps, + type PlacementWithLogical, + type AvatarProps, + type BoxProps, + type DividerProps +} from '@chakra-ui/react'; +import MyDivider from '../MyDivider'; +import type { IconNameType } from '../Icon/type'; +import { useSystem } from '../../../hooks/useSystem'; +import Avatar from '../Avatar'; +import MyPopover from '../MyPopover'; + +export type MenuItemType = 'primary' | 'danger' | 'gray' | 'grayBg'; + +export type MenuSizeType = 'sm' | 'md' | 'xs' | 'mini'; + +export type MenuItemData = { + label?: string; + children: Array<{ + isActive?: boolean; + type?: MenuItemType; + icon?: IconNameType | string; + label: string | React.ReactNode; + description?: string; + onClick?: () => any; + menuItemStyles?: MenuItemProps; + menuList?: MenuItemData[]; + }>; +}; + +export type Props = { + label?: string; + width?: number | string; + offset?: [number, number]; + Button: React.ReactNode; + trigger?: 'hover' | 'click'; + size?: MenuSizeType; + placement?: PlacementWithLogical; + menuList: MenuItemData[]; +}; + +const typeMapStyle: Record = { + primary: { + styles: { + _hover: { + backgroundColor: 'primary.50', + color: 'primary.600' + }, + _focus: { + backgroundColor: 'primary.50', + color: 'primary.600' + }, + _active: { + backgroundColor: 'primary.50', + color: 'primary.600' + } + }, + iconColor: 'myGray.600' + }, + gray: { + styles: { + _hover: { + backgroundColor: 'myGray.05', + color: 'primary.600' + }, + _focus: { + backgroundColor: 'myGray.05', + color: 'primary.600' + }, + _active: { + backgroundColor: 'myGray.05', + color: 'primary.600' + } + }, + iconColor: 'myGray.400' + }, + grayBg: { + styles: { + _hover: { + backgroundColor: 'myGray.05', + color: 'primary.600' + }, + _focus: { + backgroundColor: 'myGray.05', + color: 'primary.600' + }, + _active: { + backgroundColor: 'myGray.05', + color: 'primary.600' + } + }, + iconColor: 'myGray.600' + }, + danger: { + styles: { + color: 'red.600', + _hover: { + background: 'red.1' + }, + _focus: { + background: 'red.1' + }, + _active: { + background: 'red.1' + } + }, + iconColor: 'red.600' + } +}; +const sizeMapStyle: Record< + MenuSizeType, + { + iconStyle: AvatarProps; + labelStyle: BoxProps; + dividerStyle: DividerProps; + menuItemStyle: MenuItemProps; + } +> = { + mini: { + iconStyle: { + w: '14px' + }, + labelStyle: { + fontSize: 'mini' + }, + dividerStyle: { + my: 0.5 + }, + menuItemStyle: { + py: 1.5, + px: 2 + } + }, + xs: { + iconStyle: { + w: '14px' + }, + labelStyle: { + fontSize: 'sm' + }, + dividerStyle: { + my: 0.5 + }, + menuItemStyle: { + py: 1.5, + px: 2 + } + }, + sm: { + iconStyle: { + w: '1rem' + }, + labelStyle: { + fontSize: 'sm' + }, + dividerStyle: { + my: 1 + }, + menuItemStyle: { + py: 2, + px: 3, + _notLast: { + mb: 0.5 + } + } + }, + md: { + iconStyle: { + w: '2rem', + borderRadius: '6px' + }, + labelStyle: { + fontSize: 'sm' + }, + dividerStyle: { + my: 1 + }, + menuItemStyle: { + py: 2, + px: 3, + _notLast: { + mb: 0.5 + } + } + } +}; + +const MenuItem = React.forwardRef< + HTMLDivElement, + { + item: MenuItemData['children'][number]; + size: MenuSizeType; + onClose: () => void; + } +>((props, ref) => { + const { item, size, onClose } = props; + + return ( + { + e.stopPropagation(); + if (item.onClick && !item.menuList) { + onClose(); + item.onClick(); + } + }} + > + + {!!item.icon && ( + + )} + + + {item.label} + + {item.description && ( + + {item.description} + + )} + + + + ); +}); + +MenuItem.displayName = 'MenuItem'; + +const MyMenu = ({ + width = 'auto', + trigger = 'hover', + size = 'sm', + offset, + Button, + menuList, + placement = 'bottom-start' +}: Props) => { + const { isPc } = useSystem(); + const ref = useRef(null); + const formatTrigger = !isPc ? 'click' : trigger; + + return ( + + + {({ onClose }) => ( + + {menuList.map((group, i) => ( + + {i !== 0 && } + {group.label && ( + + {group.label} + + )} + {group.children.map((item, index) => { + return ( + + {item.menuList ? ( + + + + } + > + {({ onClose: onCloseSubmenu }) => { + return ( + + {item.menuList?.map((subGroup, subI) => ( + + {subGroup.children.map((subItem, subIndex) => ( + { + onClose(); + onCloseSubmenu(); + }} + /> + ))} + + ))} + + ); + }} + + ) : ( + + )} + + ); + })} + + ))} + + )} + + + ); +}; + +export default React.memo(MyMenu); diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index 8468ea96b..0187ddeaa 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -9,7 +9,7 @@ import List from '@/pageComponents/dataset/list/List'; import { DatasetsContext } from './context'; import DatasetContextProvider from './context'; import { useContextSelector } from 'use-context-selector'; -import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import MyMenu from '@fastgpt/web/components/common/MyMenu/secondary'; import { AddIcon } from '@chakra-ui/icons'; import { useUserStore } from '@/web/support/user/useUserStore'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -169,31 +169,42 @@ const Dataset = () => { { children: [ { - icon: 'core/dataset/externalDatasetColor', - label: t('dataset:api_file'), - description: t('dataset:external_file_dataset_desc'), - onClick: () => onSelectDatasetType(DatasetTypeEnum.apiDataset) - }, - ...(feConfigs?.show_dataset_feishu !== false - ? [ - { - icon: 'core/dataset/feishuDatasetColor', - label: t('dataset:feishu_dataset'), - description: t('dataset:feishu_dataset_desc'), - onClick: () => onSelectDatasetType(DatasetTypeEnum.feishu) - } - ] - : []), - ...(feConfigs?.show_dataset_yuque !== false - ? [ - { - icon: 'core/dataset/yuqueDatasetColor', - label: t('dataset:yuque_dataset'), - description: t('dataset:yuque_dataset_desc'), - onClick: () => onSelectDatasetType(DatasetTypeEnum.yuque) - } - ] - : []) + icon: 'core/dataset/otherDataset', + label: t('dataset:other_dataset'), + description: t('dataset:external_other_dataset_desc'), + menuList: [ + { + children: [ + { + icon: 'core/dataset/externalDatasetColor', + label: t('dataset:api_file'), + description: t('dataset:external_file_dataset_desc'), + onClick: () => onSelectDatasetType(DatasetTypeEnum.apiDataset) + }, + ...(feConfigs?.show_dataset_feishu !== false + ? [ + { + icon: 'core/dataset/feishuDatasetColor', + label: t('dataset:feishu_dataset'), + description: t('dataset:feishu_dataset_desc'), + onClick: () => onSelectDatasetType(DatasetTypeEnum.feishu) + } + ] + : []), + ...(feConfigs?.show_dataset_yuque !== false + ? [ + { + icon: 'core/dataset/yuqueDatasetColor', + label: t('dataset:yuque_dataset'), + description: t('dataset:yuque_dataset_desc'), + onClick: () => onSelectDatasetType(DatasetTypeEnum.yuque) + } + ] + : []) + ] + } + ] + } ] }, {