mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
feat: added loading indicator logic in mentions
This commit is contained in:
parent
2f4fc63858
commit
48f913fbe6
@ -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);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -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>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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==
|
||||
|
Loading…
Reference in New Issue
Block a user