diff --git a/packages/ui/components/editor/plugins/AddVariablesDropdown.tsx b/packages/ui/components/editor/plugins/AddVariablesDropdown.tsx index 343418dda1..ea88dd00e3 100644 --- a/packages/ui/components/editor/plugins/AddVariablesDropdown.tsx +++ b/packages/ui/components/editor/plugins/AddVariablesDropdown.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useMemo, useState, useRef, useEffect } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; @@ -20,6 +20,28 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { const { t } = useLocale(); const [query, setQuery] = useState(""); const isMobile = useMediaQuery("(max-width: 640px)"); + const [isOpen, setisOpen] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(-1); + const itemRefs = useRef<(HTMLButtonElement | null)[]>([]); + const dropdownContainerRef = useRef(null); + + useEffect(() => { + if (selectedIndex >= 0 && dropdownContainerRef.current && itemRefs.current[selectedIndex]) { + const container = dropdownContainerRef.current; + const selectedItem = itemRefs.current[selectedIndex]; + + if (selectedItem) { + const containerRect = container.getBoundingClientRect(); + const itemRect = selectedItem.getBoundingClientRect(); + + if (itemRect.bottom > containerRect.bottom) { + container.scrollTop += itemRect.bottom - containerRect.bottom; + } else if (itemRect.top < containerRect.top) { + container.scrollTop -= containerRect.top - itemRect.top; + } + } + } + }, [selectedIndex]); const filteredVariables = useMemo(() => { const q = query.trim().toLowerCase(); @@ -32,12 +54,55 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { }); }, [props.variables, query, t]); + const handleOnOpen = (open: boolean) => { + setisOpen(open); + if (!open) setQuery(""); + setSelectedIndex(open && filteredVariables.length > 0 ? 0 : -1); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (filteredVariables.length === 0 || !isOpen) return; + + switch (e.key) { + case "Enter": + e.preventDefault(); + if (selectedIndex >= 0 && selectedIndex < filteredVariables.length) { + props.addVariable(t(`${filteredVariables[selectedIndex]}_variable`)); + } + setisOpen(false); + setQuery(""); + setSelectedIndex(-1); + break; + case "ArrowUp": + e.preventDefault(); + setSelectedIndex((prev) => { + if (filteredVariables.length === 0) return -1; + if (prev <= 0 || prev === -1) return filteredVariables.length - 1; + return prev - 1; + }); + break; + case "ArrowDown": + e.preventDefault(); + setSelectedIndex((prev) => { + if (filteredVariables.length === 0) return -1; + if (prev >= filteredVariables.length - 1) return 0; + return prev + 1; + }); + break; + case "Escape": + e.preventDefault(); + setisOpen(false); + setQuery(""); + setSelectedIndex(-1); + break; + } + }; + return ( - { - if (!open) setQuery(""); - }}> - + +
{props.isTextEditor ? ( <> @@ -64,7 +129,7 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { )}
- +
{t("add_dynamic_variables")} @@ -80,15 +145,21 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { className="border-subtle bg-default focus:ring-brand-800 w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-1" />
-
+
{filteredVariables.length === 0 ? (
{t("no_variables_found")}
) : ( - filteredVariables.map((variable) => ( - -
( + +
{t(`${variable}_info`)}
-
+ )) )}