[FIX] Minor bug fixes in MentionList and MentionNode UI (#2600)

* fix: removed text color in peek view

* fix: fixed list view UI bugs and node view colors

* feat: update imports in suggestions for mentionSuggestion type

* fix: updated mention list css

* fix: updated mention node UI according to the design provided

* style: update the mentions dropdown UI

* style: mentioned users UI in the editor

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
Henit Chobisa 2023-11-02 19:31:25 +05:30 committed by GitHub
parent a9b72fa1d2
commit da391064aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 119 deletions

View File

@ -1,111 +1,122 @@
import { Editor } from '@tiptap/react'; import { Editor } from "@tiptap/react";
import React, { import React, {
forwardRef, forwardRef,
useCallback,
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useState, useState,
} from 'react' } from "react";
import { IMentionSuggestion } from '../../types/mention-suggestion'; import { IMentionSuggestion } from "../../types/mention-suggestion";
interface MentionListProps { interface MentionListProps {
items: IMentionSuggestion[]; items: IMentionSuggestion[];
command: (item: { id: string, label: string, target: string, redirect_uri: string }) => void; command: (item: {
id: string;
label: string;
target: string;
redirect_uri: string;
}) => void;
editor: Editor; editor: Editor;
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const MentionList = forwardRef((props: MentionListProps, ref) => { const MentionList = forwardRef((props: MentionListProps, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0) const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = (index: number) => { const selectItem = (index: number) => {
const item = props.items[index] const item = props.items[index];
console.log(props.command);
if (item) { if (item) {
props.command({ id: item.id, label: item.title, target: "users", redirect_uri: item.redirect_uri }) props.command({
id: item.id,
label: item.title,
target: "users",
redirect_uri: item.redirect_uri,
});
} }
} };
const upHandler = () => { const upHandler = () => {
setSelectedIndex(((selectedIndex + props.items.length) - 1) % props.items.length) setSelectedIndex(
} (selectedIndex + props.items.length - 1) % props.items.length,
);
};
const downHandler = () => { const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % props.items.length) setSelectedIndex((selectedIndex + 1) % props.items.length);
} };
const enterHandler = () => { const enterHandler = () => {
selectItem(selectedIndex) selectItem(selectedIndex);
} };
useEffect(() => { useEffect(() => {
setSelectedIndex(0) setSelectedIndex(0);
}, [props.items]) }, [props.items]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
onKeyDown: ({ event }: { event: KeyboardEvent }) => { onKeyDown: ({ event }: { event: KeyboardEvent }) => {
if (event.key === 'ArrowUp') { if (event.key === "ArrowUp") {
upHandler() upHandler();
return true return true;
} }
if (event.key === 'ArrowDown') { if (event.key === "ArrowDown") {
downHandler() downHandler();
return true return true;
} }
if (event.key === 'Enter') { if (event.key === "Enter") {
enterHandler() enterHandler();
return true return false;
} }
return false return false;
}, },
})) }));
return ( return props.items && props.items.length !== 0 ? (
props.items && props.items.length !== 0 ? <div className="items"> <div className="absolute max-h-40 bg-custom-background-100 rounded-md shadow-custom-shadow-sm text-custom-text-300 text-sm overflow-y-auto w-48 p-1 space-y-0.5">
{ props.items.length ? props.items.map((item, index) => ( {props.items.length ? (
<div className={`item ${index === selectedIndex ? 'is-selected' : ''} w-72 flex items-center p-3 rounded shadow-md`} onClick={() => selectItem(index)}> props.items.map((item, index) => (
{item.avatar ? <div <div
className={`rounded border-[0.5px] ${index ? "border-custom-border-200 bg-custom-background-100" : "border-transparent" key={item.id}
}`} className={`flex items-center gap-2 rounded p-1 hover:bg-custom-background-80 cursor-pointer ${
style={{ index === selectedIndex ? "bg-custom-background-80" : ""
height: "24px", }`}
width: "24px", onClick={() => selectItem(index)}
}} >
> <div className="flex-shrink-0 h-4 w-4 grid place-items-center overflow-hidden">
<img {item.avatar && item.avatar.trim() !== "" ? (
src={item.avatar} <img
className="absolute top-0 left-0 h-full w-full object-cover rounded" src={item.avatar}
alt={item.title} className="h-full w-full object-cover rounded-sm"
/> alt={item.title}
</div> : />
<div ) : (
className="grid place-items-center text-xs capitalize text-white rounded bg-gray-700 border-[0.5px] border-custom-border-200" <div className="h-full w-full grid place-items-center text-xs capitalize text-white rounded-sm bg-gray-700">
style={{ {item.title[0]}
height: "24px", </div>
width: "24px", )}
fontSize: "12px",
}}
>
{item.title.charAt(0)}
</div>
}
<div className="ml-7 space-y-1">
<p className="text-sm font-medium leading-none">{item.title}</p>
<p className="text-xs text-gray-400">
{item.subtitle}
</p>
</div>
</div> </div>
) <div className="flex-grow space-y-1 truncate">
) <p className="text-sm font-medium truncate">{item.title}</p>
: <div className="item">No result</div> {/* <p className="text-xs text-gray-400">{item.subtitle}</p> */}
} </div>
</div> : <></> </div>
) ))
}) ) : (
<div className="item">No result</div>
)}
</div>
) : (
<></>
);
});
MentionList.displayName = "MentionList" MentionList.displayName = "MentionList";
export default MentionList export default MentionList;

View File

@ -1,32 +1,41 @@
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
// @ts-nocheck // @ts-nocheck
import { NodeViewWrapper } from '@tiptap/react' import { NodeViewWrapper } from "@tiptap/react";
import { cn } from '../../lib/utils' import { cn } from "../../lib/utils";
import React from 'react' import { useRouter } from "next/router";
import { useRouter } from 'next/router' import { IMentionHighlight } from "../../types/mention-suggestion";
import { IMentionHighlight } from '../../types/mention-suggestion'
// eslint-disable-next-line import/no-anonymous-default-export // eslint-disable-next-line import/no-anonymous-default-export
export default props => { export default (props) => {
const router = useRouter();
const router = useRouter() const highlights = props.extension.options
const highlights = props.extension.options.mentionHighlights as IMentionHighlight[] .mentionHighlights as IMentionHighlight[];
const handleClick = () => { const handleClick = () => {
if (!props.extension.options.readonly){ if (!props.extension.options.readonly) {
router.push(props.node.attrs.redirect_uri) router.push(props.node.attrs.redirect_uri);
} }
} };
return ( return (
<NodeViewWrapper className="w-fit inline mention-component" > <NodeViewWrapper className="w-fit inline mention-component">
<span className={cn("px-1 py-0.5 inline rounded-md font-bold bg-custom-primary-500 mention", { <span
"text-[#D9C942] bg-[#544D3B] hover:bg-[#544D3B]" : highlights ? highlights.includes(props.node.attrs.id) : false, className={cn(
"cursor-pointer" : !props.extension.options.readonly, "px-1 py-0.5 bg-custom-primary-100/20 text-custom-primary-100 rounded font-medium mention",
"hover:bg-custom-primary-300" : !props.extension.options.readonly && !highlights.includes(props.node.attrs.id) {
})} onClick={handleClick} data-mention-target={props.node.attrs.target} data-mention-id={props.node.attrs.id}>@{ props.node.attrs.label }</span> "text-yellow-500 bg-yellow-500/20": highlights
? highlights.includes(props.node.attrs.id)
: false,
"cursor-pointer": !props.extension.options.readonly,
// "hover:bg-custom-primary-300" : !props.extension.options.readonly && !highlights.includes(props.node.attrs.id)
},
)}
onClick={handleClick}
data-mention-target={props.node.attrs.target}
data-mention-id={props.node.attrs.id}
>
@{props.node.attrs.label}
</span>
</NodeViewWrapper> </NodeViewWrapper>
) );
} };

View File

@ -3,7 +3,7 @@ import { Editor } from "@tiptap/core";
import tippy from 'tippy.js' import tippy from 'tippy.js'
import MentionList from './MentionList' import MentionList from './MentionList'
import { IMentionSuggestion } from './mentions'; import { IMentionSuggestion } from '../../types/mention-suggestion';
const Suggestion = (suggestions: IMentionSuggestion[]) => ({ const Suggestion = (suggestions: IMentionSuggestion[]) => ({
items: ({ query }: { query: string }) => suggestions.filter(suggestion => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5), items: ({ query }: { query: string }) => suggestions.filter(suggestion => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5),

View File

@ -140,7 +140,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
</div> </div>
<span>{errors.name ? errors.name.message : null}</span> <span>{errors.name ? errors.name.message : null}</span>
<span className="text-black"> <span className="">
<RichTextEditor <RichTextEditor
uploadFile={fileService.getUploadFileFunction(workspaceSlug)} uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage} deleteFile={fileService.deleteImage}

View File

@ -229,27 +229,3 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
.ProseMirror table * .is-empty::before { .ProseMirror table * .is-empty::before {
opacity: 0; opacity: 0;
} }
.items {
position: absolute;
max-height: 40vh;
background: rgb(var(--color-background-100));
border-radius: 0.5rem;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1);
color: rgb(var(--color-text-100));
font-size: 0.9rem;
overflow: auto;
}
.item {
background: transparent;
border: 1px solid transparent;
border-radius: 0.4rem;
text-align: left;
cursor: pointer;
}
.item.is-selected {
border-color: rgb(var(--color-border-200));
}