mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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:
parent
a9b72fa1d2
commit
da391064aa
@ -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;
|
||||||
|
@ -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>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user