import * as React from "react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Command, MenuListProps } from "./types" import { getShortcutKeys } from "../../lib/utils" import { Icon } from "../../components/ui/icon" import { PopoverWrapper } from "../../components/ui/popover-wrapper" import { Shortcut } from "../../components/ui/shortcut" export const MenuList = React.forwardRef((props: MenuListProps, ref) => { const scrollContainer = React.useRef(null) const activeItem = React.useRef(null) const [selectedGroupIndex, setSelectedGroupIndex] = React.useState(0) const [selectedCommandIndex, setSelectedCommandIndex] = React.useState(0) // Anytime the groups change, i.e. the user types to narrow it down, we want to // reset the current selection to the first menu item React.useEffect(() => { setSelectedGroupIndex(0) setSelectedCommandIndex(0) }, [props.items]) const selectItem = React.useCallback( (groupIndex: number, commandIndex: number) => { const command = props.items[groupIndex].commands[commandIndex] props.command(command) }, [props] ) React.useImperativeHandle(ref, () => ({ onKeyDown: ({ event }: { event: React.KeyboardEvent }) => { if (event.key === "ArrowDown") { if (!props.items.length) { return false } const commands = props.items[selectedGroupIndex].commands let newCommandIndex = selectedCommandIndex + 1 let newGroupIndex = selectedGroupIndex if (commands.length - 1 < newCommandIndex) { newCommandIndex = 0 newGroupIndex = selectedGroupIndex + 1 } if (props.items.length - 1 < newGroupIndex) { newGroupIndex = 0 } setSelectedCommandIndex(newCommandIndex) setSelectedGroupIndex(newGroupIndex) return true } if (event.key === "ArrowUp") { if (!props.items.length) { return false } let newCommandIndex = selectedCommandIndex - 1 let newGroupIndex = selectedGroupIndex if (newCommandIndex < 0) { newGroupIndex = selectedGroupIndex - 1 newCommandIndex = props.items[newGroupIndex]?.commands.length - 1 || 0 } if (newGroupIndex < 0) { newGroupIndex = props.items.length - 1 newCommandIndex = props.items[newGroupIndex].commands.length - 1 } setSelectedCommandIndex(newCommandIndex) setSelectedGroupIndex(newGroupIndex) return true } if (event.key === "Enter") { if (!props.items.length || selectedGroupIndex === -1 || selectedCommandIndex === -1) { return false } selectItem(selectedGroupIndex, selectedCommandIndex) return true } return false } })) React.useEffect(() => { if (activeItem.current && scrollContainer.current) { const offsetTop = activeItem.current.offsetTop const offsetHeight = activeItem.current.offsetHeight scrollContainer.current.scrollTop = offsetTop - offsetHeight } }, [selectedCommandIndex, selectedGroupIndex]) const createCommandClickHandler = React.useCallback( (groupIndex: number, commandIndex: number) => { return () => { selectItem(groupIndex, commandIndex) } }, [selectItem] ) if (!props.items.length) { return null } return ( {props.items.map((group, groupIndex: number) => ( {group.commands.map((command: Command, commandIndex: number) => ( ))} {groupIndex !== props.items.length - 1 && } ))} ) }) MenuList.displayName = "MenuList" export default MenuList