From 48f913fbe683875d6840a8c68ac17264758f5e05 Mon Sep 17 00:00:00 2001 From: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:32:07 +0530 Subject: [PATCH] feat: added loading indicator logic in mentions --- .../editor/core/src/ui/mentions/index.tsx | 27 +------ .../core/src/ui/mentions/mention-list.tsx | 81 +++++++++++++++---- yarn.lock | 2 +- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/packages/editor/core/src/ui/mentions/index.tsx b/packages/editor/core/src/ui/mentions/index.tsx index c97ca03e8..8245fa6fa 100644 --- a/packages/editor/core/src/ui/mentions/index.tsx +++ b/packages/editor/core/src/ui/mentions/index.tsx @@ -23,25 +23,6 @@ export const Mentions = ({ readonly: readonly, mentionHighlights: mentionHighlights, suggestion: { - items: async ({ query }) => { - const suggestions = await mentionSuggestions?.(); - if (!suggestions) { - return []; - } - const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => { - const transactionId = uuidv4(); - return { - ...suggestion, - id: transactionId, - }; - }); - - const filteredSuggestions = mappedSuggestions - .filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())) - .slice(0, 5); - - return filteredSuggestions; - }, // @ts-ignore render: () => { let component: ReactRenderer | null = null; @@ -56,11 +37,11 @@ export const Mentions = ({ return; } component = new ReactRenderer(MentionList, { - props, + props: { ...props, mentionSuggestions }, editor: props.editor, }); props.editor.storage.mentionsOpen = true; - // @ts-ignore + // @ts-expect-error - Tippy types are incorrect popup = tippy("body", { getReferenceClientRect: props.clientRect, appendTo: () => document.body, @@ -70,7 +51,7 @@ export const Mentions = ({ trigger: "manual", placement: "bottom-start", }); - document.addEventListener("scroll", hidePopup, true); + // document.addEventListener("scroll", hidePopup, true); }, onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => { component?.updateProps(props); @@ -107,7 +88,7 @@ export const Mentions = ({ popup?.[0].destroy(); component?.destroy(); - document.removeEventListener("scroll", hidePopup, true); + // document.removeEventListener("scroll", hidePopup, true); }, }; }, diff --git a/packages/editor/core/src/ui/mentions/mention-list.tsx b/packages/editor/core/src/ui/mentions/mention-list.tsx index 477ce6dae..26afb1e86 100644 --- a/packages/editor/core/src/ui/mentions/mention-list.tsx +++ b/packages/editor/core/src/ui/mentions/mention-list.tsx @@ -1,9 +1,9 @@ import { Editor } from "@tiptap/react"; -import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; +import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react"; import { IMentionSuggestion } from "src/types/mention-suggestion"; +import { v4 as uuidv4 } from "uuid"; interface MentionListProps { - items: IMentionSuggestion[]; command: (item: { id: string; label: string; @@ -12,14 +12,42 @@ interface MentionListProps { target: string; redirect_uri: string; }) => void; + query: string; editor: Editor; + mentionSuggestions: () => Promise; } export const MentionList = forwardRef((props: MentionListProps, ref) => { + const { query, mentionSuggestions } = props; + const [items, setItems] = useState([]); const [selectedIndex, setSelectedIndex] = useState(0); + const [isLoading, setIsLoading] = useState(false); // New loading state + + useEffect(() => { + const fetchSuggestions = async () => { + setIsLoading(true); // Start loading + const suggestions = await mentionSuggestions(); + const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => { + const transactionId = uuidv4(); + return { + ...suggestion, + id: transactionId, + }; + }); + + const filteredSuggestions = mappedSuggestions + .filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())) + .slice(0, 5); + + setItems(filteredSuggestions); + setIsLoading(false); // End loading + }; + + fetchSuggestions(); + }, [query, mentionSuggestions]); const selectItem = (index: number) => { - const item = props.items[index]; + const item = items[index]; if (item) { props.command({ @@ -33,12 +61,35 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => { } }; + const commandListContainer = useRef(null); + + useLayoutEffect(() => { + const container = commandListContainer?.current; + + const item = container?.children[selectedIndex] as HTMLElement; + + if (item && container) updateScrollView(container, item); + }, [selectedIndex]); + + const updateScrollView = (container: HTMLElement, item: HTMLElement) => { + const containerHeight = container.offsetHeight; + const itemHeight = item ? item.offsetHeight : 0; + + const top = item.offsetTop; + const bottom = top + itemHeight; + + if (top < container.scrollTop) { + container.scrollTop -= container.scrollTop - top + 5; + } else if (bottom > containerHeight + container.scrollTop) { + container.scrollTop += bottom - containerHeight - container.scrollTop + 5; + } + }; const upHandler = () => { - setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length); + setSelectedIndex((selectedIndex + items.length - 1) % items.length); }; const downHandler = () => { - setSelectedIndex((selectedIndex + 1) % props.items.length); + setSelectedIndex((selectedIndex + 1) % items.length); }; const enterHandler = () => { @@ -47,7 +98,7 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => { useEffect(() => { setSelectedIndex(0); - }, [props.items]); + }, [items]); useImperativeHandle(ref, () => ({ onKeyDown: ({ event }: { event: KeyboardEvent }) => { @@ -70,10 +121,15 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => { }, })); - return props.items && props.items.length !== 0 ? ( -
- {props.items.length ? ( - props.items.map((item, index) => ( + return ( +
+ {isLoading ? ( +
Loading...
+ ) : items.length ? ( + items.map((item, index) => (
{

{item.title}

- {/*

{item.subtitle}

*/}
)) ) : ( -
No result
+
No results
)}
- ) : ( - <> ); }); diff --git a/yarn.lock b/yarn.lock index 6f50b05ac..6b2d0ed1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2722,7 +2722,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.42": +"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42": version "18.2.42" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7" integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==