UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

1 lines 12.9 kB
{"version":3,"file":"renderItems.mjs","names":["common","Icon","segmentIndices: number[]","info: MenuInfo","label","labelText","isDanger"],"sources":["../../src/ContextMenu/renderItems.tsx"],"sourcesContent":["import { ContextMenu } from '@base-ui/react/context-menu';\nimport { cx } from 'antd-style';\nimport { Check, ChevronRight } from 'lucide-react';\nimport type { MenuInfo } from 'rc-menu/es/interface';\nimport type {\n Key,\n KeyboardEvent as ReactKeyboardEvent,\n MouseEvent as ReactMouseEvent,\n ReactNode,\n} from 'react';\nimport { isValidElement, memo } from 'react';\n\nimport Icon from '@/Icon';\nimport type { MenuDividerType, MenuItemGroupType, MenuItemType, SubMenuType } from '@/Menu';\nimport common from '@/i18n/resources/en/common';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport { preventDefaultAndStopPropagation } from '@/utils/dom';\n\nimport { styles } from './style';\nimport type { ContextMenuCheckboxItem, ContextMenuItem } from './type';\n\nconst EmptyMenuItem = memo(() => {\n const { t } = useTranslation(common);\n return (\n <ContextMenu.Item className={cx(styles.item, styles.empty)} disabled>\n <div className={styles.itemContent}>\n <span className={styles.label}>{t('common.empty')}</span>\n </div>\n </ContextMenu.Item>\n );\n});\n\nEmptyMenuItem.displayName = 'EmptyMenuItem';\n\ntype KeyableItem = { key?: Key };\n\nconst getItemKey = (item: KeyableItem, fallback: string): Key => {\n if (item && 'key' in item && item.key !== undefined) return item.key;\n return fallback;\n};\n\ntype LabelableItem = {\n key?: Key;\n label?: ReactNode;\n title?: ReactNode;\n};\n\nconst getItemLabel = (\n item: MenuItemType | SubMenuType | ContextMenuCheckboxItem | LabelableItem,\n): ReactNode => {\n if (item.label !== undefined) return item.label;\n if ('title' in item && item.title !== undefined) return item.title;\n return item.key;\n};\n\nconst renderIcon = (icon: MenuItemType['icon']) => {\n if (!icon) return null;\n if (isValidElement(icon)) return icon;\n return <Icon icon={icon} size={'small'} />;\n};\n\nconst getReserveIconSpaceMap = (items: ContextMenuItem[]) => {\n const flags = Array.from({ length: items.length }).fill(false);\n let segmentIndices: number[] = [];\n let segmentHasIcon = false;\n\n const flush = () => {\n if (segmentHasIcon) {\n for (const index of segmentIndices) flags[index] = true;\n }\n segmentIndices = [];\n segmentHasIcon = false;\n };\n\n items.forEach((item, index) => {\n if (!item) return;\n if (\n (item as MenuDividerType).type === 'divider' ||\n (item as MenuItemGroupType).type === 'group'\n ) {\n flush();\n return;\n }\n\n segmentIndices.push(index);\n if ((item as ContextMenuCheckboxItem).type === 'checkbox') {\n segmentHasIcon = true;\n return;\n }\n if ('icon' in item && item.icon) segmentHasIcon = true;\n });\n\n flush();\n return flags;\n};\n\nconst renderItemContent = (\n item: MenuItemType | SubMenuType | ContextMenuCheckboxItem,\n options?: { reserveIconSpace?: boolean; submenu?: boolean },\n iconNode?: ReactNode,\n) => {\n const label = getItemLabel(item);\n const extra = 'extra' in item ? item.extra : undefined;\n const hasCustomIcon = iconNode !== undefined;\n const hasIcon = hasCustomIcon ? Boolean(iconNode) : Boolean(item.icon);\n const shouldRenderIcon = hasCustomIcon\n ? Boolean(options?.reserveIconSpace || iconNode)\n : Boolean(hasIcon || options?.reserveIconSpace);\n\n return (\n <div className={styles.itemContent}>\n {shouldRenderIcon ? (\n <span aria-hidden={!hasIcon} className={styles.icon}>\n {hasCustomIcon ? iconNode : hasIcon ? renderIcon(item.icon) : null}\n </span>\n ) : null}\n <span className={styles.label}>{label}</span>\n {extra ? <span className={styles.extra}>{extra}</span> : null}\n {options?.submenu ? (\n <span className={styles.submenuArrow}>\n <ChevronRight size={16} />\n </span>\n ) : null}\n </div>\n );\n};\n\nconst invokeItemClick = (\n item: MenuItemType,\n keyPath: string[],\n event: ReactMouseEvent<HTMLElement> | ReactKeyboardEvent<HTMLElement>,\n) => {\n if (!item.onClick) return;\n const key = item.key ?? keyPath.at(-1) ?? '';\n const info: MenuInfo = {\n domEvent: event,\n item: event.currentTarget as MenuInfo['item'],\n key: String(key),\n keyPath,\n };\n item.onClick(info);\n};\n\nexport const renderContextMenuItems = (\n items: ContextMenuItem[],\n keyPath: string[] = [],\n options?: { reserveIconSpace?: boolean },\n): ReactNode[] => {\n const reserveIconSpaceMap =\n options?.reserveIconSpace === undefined ? getReserveIconSpaceMap(items) : null;\n\n return items.map((item, index) => {\n if (!item) return null;\n\n const fallbackKey = `${keyPath.join('-') || 'root'}-${index}`;\n const itemKey = getItemKey(item, fallbackKey);\n const nextKeyPath = [...keyPath, String(itemKey)];\n const reserveIconSpace = options?.reserveIconSpace ?? Boolean(reserveIconSpaceMap?.[index]);\n\n if ((item as ContextMenuCheckboxItem).type === 'checkbox') {\n const checkboxItem = item as ContextMenuCheckboxItem;\n const label = getItemLabel(checkboxItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(checkboxItem.danger);\n const indicator = (\n <ContextMenu.CheckboxItemIndicator>\n <Icon icon={Check} size={'small'} />\n </ContextMenu.CheckboxItemIndicator>\n );\n\n return (\n <ContextMenu.CheckboxItem\n checked={checkboxItem.checked}\n className={cx(styles.item, isDanger && styles.danger)}\n closeOnClick={checkboxItem.closeOnClick}\n defaultChecked={checkboxItem.defaultChecked}\n disabled={checkboxItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => checkboxItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(checkboxItem, { reserveIconSpace }, indicator)}\n </ContextMenu.CheckboxItem>\n );\n }\n\n if ((item as MenuDividerType).type === 'divider') {\n return <ContextMenu.Separator className={styles.separator} key={itemKey} />;\n }\n\n if ((item as MenuItemGroupType).type === 'group') {\n const group = item as MenuItemGroupType;\n const groupReserveIconSpace = Boolean(\n group.children?.some((child) => Boolean(child && 'icon' in child && child.icon)),\n );\n return (\n <ContextMenu.Group key={itemKey}>\n {group.label ? (\n <ContextMenu.GroupLabel className={styles.groupLabel}>\n {group.label}\n </ContextMenu.GroupLabel>\n ) : null}\n {group.children\n ? renderContextMenuItems(group.children, nextKeyPath, {\n reserveIconSpace: groupReserveIconSpace,\n })\n : null}\n </ContextMenu.Group>\n );\n }\n\n if (\n (item as SubMenuType).type === 'submenu' ||\n ('children' in item && (item as SubMenuType).children)\n ) {\n const submenu = item as SubMenuType;\n const label = getItemLabel(submenu);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in submenu && Boolean(submenu.danger);\n\n return (\n <ContextMenu.SubmenuRoot key={itemKey}>\n <ContextMenu.SubmenuTrigger\n className={cx(styles.item, isDanger && styles.danger)}\n disabled={submenu.disabled}\n label={labelText}\n >\n {renderItemContent(submenu, {\n reserveIconSpace,\n submenu: true,\n })}\n </ContextMenu.SubmenuTrigger>\n <ContextMenu.Portal>\n <ContextMenu.Positioner\n alignOffset={-4}\n className={styles.positioner}\n onContextMenu={preventDefaultAndStopPropagation}\n sideOffset={-1}\n >\n <ContextMenu.Popup className={styles.popup}>\n {submenu.children && submenu.children.length > 0 ? (\n renderContextMenuItems(submenu.children, nextKeyPath)\n ) : (\n <EmptyMenuItem />\n )}\n </ContextMenu.Popup>\n </ContextMenu.Positioner>\n </ContextMenu.Portal>\n </ContextMenu.SubmenuRoot>\n );\n }\n\n const menuItem = item as MenuItemType;\n const label = getItemLabel(menuItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in menuItem && Boolean(menuItem.danger);\n\n return (\n <ContextMenu.Item\n className={cx(styles.item, isDanger && styles.danger)}\n disabled={menuItem.disabled}\n key={itemKey}\n label={labelText}\n onClick={(event) => invokeItemClick(menuItem, nextKeyPath, event)}\n >\n {renderItemContent(menuItem, { reserveIconSpace })}\n </ContextMenu.Item>\n );\n });\n};\n"],"mappings":"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"}