feat: added loading indicator logic in mentions

This commit is contained in:
Palanikannan1437 2024-03-26 16:32:07 +05:30
parent 2f4fc63858
commit 48f913fbe6
3 changed files with 72 additions and 38 deletions

View File

@ -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);
},
};
},

View File

@ -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<IMentionSuggestion[]>;
}
export const MentionList = forwardRef((props: MentionListProps, ref) => {
const { query, mentionSuggestions } = props;
const [items, setItems] = useState<IMentionSuggestion[]>([]);
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<HTMLDivElement>(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 ? (
<div className="mentions absolute max-h-40 w-48 space-y-0.5 overflow-y-auto rounded-md bg-custom-background-100 p-1 text-sm text-custom-text-300 shadow-custom-shadow-sm">
{props.items.length ? (
props.items.map((item, index) => (
return (
<div
ref={commandListContainer}
className="mentions absolute max-h-40 w-48 space-y-0.5 overflow-y-auto rounded-md bg-custom-background-100 p-1 text-sm text-custom-text-300 shadow-custom-shadow-sm"
>
{isLoading ? (
<div className="flex justify-center items-center h-full text-gray-500">Loading...</div>
) : items.length ? (
items.map((item, index) => (
<div
key={item.id}
className={`flex cursor-pointer items-center gap-2 rounded p-1 hover:bg-custom-background-80 ${
@ -92,16 +148,13 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => {
</div>
<div className="flex-grow space-y-1 truncate">
<p className="truncate text-sm font-medium">{item.title}</p>
{/* <p className="text-xs text-gray-400">{item.subtitle}</p> */}
</div>
</div>
))
) : (
<div className="item">No result</div>
<div className="flex justify-center items-center h-full">No results</div>
)}
</div>
) : (
<></>
);
});

View File

@ -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==