style: lite text editor editor toolbar (#2601)

* style: comment editor toolbar

* style: updated icon styling
This commit is contained in:
Aaryan Khandelwal 2023-11-02 16:26:57 +05:30 committed by GitHub
parent 5b808571e5
commit c394a4f64e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 142 deletions

View File

@ -7,7 +7,11 @@ interface EditorContainerProps {
children: ReactNode;
}
export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => (
export const EditorContainer = ({
editor,
editorClassNames,
children,
}: EditorContainerProps) => (
<div
id="editor-container"
onClick={() => {

View File

@ -1,14 +1,14 @@
"use client"
import * as React from 'react';
"use client";
import * as React from "react";
import { Extension } from "@tiptap/react";
import { UploadImage } from '../types/upload-image';
import { DeleteImage } from '../types/delete-image';
import { getEditorClassNames } from '../lib/utils';
import { EditorProps } from '@tiptap/pm/view';
import { useEditor } from './hooks/useEditor';
import { EditorContainer } from '../ui/components/editor-container';
import { EditorContentWrapper } from '../ui/components/editor-content';
import { IMentionSuggestion } from '../types/mention-suggestion';
import { UploadImage } from "../types/upload-image";
import { DeleteImage } from "../types/delete-image";
import { getEditorClassNames } from "../lib/utils";
import { EditorProps } from "@tiptap/pm/view";
import { useEditor } from "./hooks/useEditor";
import { EditorContainer } from "../ui/components/editor-container";
import { EditorContentWrapper } from "../ui/components/editor-content";
import { IMentionSuggestion } from "../types/mention-suggestion";
interface ICoreEditor {
value: string;
@ -19,7 +19,9 @@ interface ICoreEditor {
customClassName?: string;
editorContentCustomClassNames?: string;
onChange?: (json: any, html: string) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setShouldShowAlert?: (showAlert: boolean) => void;
editable?: boolean;
forwardedRef?: any;
@ -72,22 +74,29 @@ const CoreEditor = ({
forwardedRef,
});
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
const editorClassNames = getEditorClassNames({
noBorder,
borderOnFocus,
customClassName,
});
if (!editor) return null;
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
</div>
</EditorContainer >
</EditorContainer>
);
};
const CoreEditorWithRef = React.forwardRef<EditorHandle, ICoreEditor>((props, ref) => (
<CoreEditor {...props} forwardedRef={ref} />
));
const CoreEditorWithRef = React.forwardRef<EditorHandle, ICoreEditor>(
(props, ref) => <CoreEditor {...props} forwardedRef={ref} />,
);
CoreEditorWithRef.displayName = "CoreEditorWithRef";

View File

@ -18,9 +18,9 @@ export type IMentionSuggestion = {
title: string;
subtitle: string;
redirect_uri: string;
}
};
export type IMentionHighlight = string
export type IMentionHighlight = string;
interface ILiteTextEditor {
value: string;
@ -32,7 +32,7 @@ interface ILiteTextEditor {
editorContentCustomClassNames?: string;
onChange?: (json: any, html: string) => void;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved"
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setShouldShowAlert?: (showAlert: boolean) => void;
forwardedRef?: any;
@ -50,6 +50,7 @@ interface ILiteTextEditor {
onEnterKeyPress?: (e?: any) => void;
mentionHighlights?: string[];
mentionSuggestions?: IMentionSuggestion[];
submitButton?: React.ReactNode;
}
interface LiteTextEditorProps extends ILiteTextEditor {
@ -61,7 +62,8 @@ interface EditorHandle {
setEditorValue: (content: string) => void;
}
const LiteTextEditor = ({
const LiteTextEditor = (props: LiteTextEditorProps) => {
const {
onChange,
debouncedUpdatesEnabled,
setIsSubmitting,
@ -77,8 +79,10 @@ const LiteTextEditor = ({
commentAccessSpecifier,
onEnterKeyPress,
mentionHighlights,
mentionSuggestions
}: LiteTextEditorProps) => {
mentionSuggestions,
submitButton,
} = props;
const editor = useEditor({
onChange,
debouncedUpdatesEnabled,
@ -90,7 +94,7 @@ const LiteTextEditor = ({
forwardedRef,
extensions: LiteTextEditorExtensions(onEnterKeyPress),
mentionHighlights,
mentionSuggestions
mentionSuggestions,
});
const editorClassNames = getEditorClassNames({
@ -114,6 +118,7 @@ const LiteTextEditor = ({
uploadFile={uploadFile}
setIsSubmitting={setIsSubmitting}
commentAccessSpecifier={commentAccessSpecifier}
submitButton={submitButton}
/>
</div>
</div>
@ -122,7 +127,7 @@ const LiteTextEditor = ({
};
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>(
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />,
);
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";

View File

@ -1,5 +1,5 @@
import { Editor } from "@tiptap/react";
import { BoldIcon, LucideIcon } from "lucide-react";
import { BoldIcon } from "lucide-react";
import {
BoldItem,
@ -14,7 +14,6 @@ import {
TableItem,
UnderLineItem,
} from "@plane/editor-core";
import { Icon } from "./icon";
import { Tooltip } from "../../tooltip";
import { UploadImage } from "../..";
@ -41,8 +40,9 @@ type EditorBubbleMenuProps = {
};
uploadFile: UploadImage;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved"
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
submitButton: React.ReactNode;
};
export const FixedMenu = (props: EditorBubbleMenuProps) => {
@ -72,117 +72,133 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
props.commentAccessSpecifier?.onAccessChange(accessKey);
};
console.log(complexItems);
return (
<div className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl">
<div className="flex items-stretch gap-1.5 w-full h-9">
{props.commentAccessSpecifier && (
<div className="flex border border-custom-border-300 mt-0 divide-x divide-custom-border-300 rounded overflow-hidden">
<div className="flex-shrink-0 flex items-stretch gap-0.5 border border-custom-border-200 rounded p-1">
{props?.commentAccessSpecifier.commentAccess?.map((access) => (
<Tooltip key={access.key} tooltipContent={access.label}>
<button
type="button"
onClick={() => handleAccessChange(access.key)}
className={`grid place-basicMarkItems-center p-1 hover:bg-custom-background-80 ${
className={`aspect-square grid place-items-center p-1 rounded-sm hover:bg-custom-background-90 ${
props.commentAccessSpecifier?.accessValue === access.key
? "bg-custom-background-80"
? "bg-custom-background-90"
: ""
}`}
>
<access.icon
className={`w-4 h-4 ${
className={`w-3.5 h-3.5 ${
props.commentAccessSpecifier?.accessValue === access.key
? "!text-custom-text-100"
: "!text-custom-text-400"
? "text-custom-text-100"
: "text-custom-text-400"
}`}
strokeWidth={2}
/>
</button>
</Tooltip>
))}
</div>
)}
<div className="flex">
<div className="flex items-stretch justify-between gap-2 w-full border border-custom-border-200 bg-custom-background-90 rounded p-1">
<div className="flex items-stretch">
<div className="flex items-stretch gap-0.5 pr-2.5 border-r border-custom-border-200">
{basicMarkItems.map((item, index) => (
<button
key={index}
type="button"
onClick={item.command}
className={cn(
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
{
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
}
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
)}
>
<item.icon
className={cn("h-4 w-4", {
className={cn("h-3.5 w-3.5", {
"text-custom-text-100": item.isActive(),
})}
strokeWidth={2.5}
/>
</button>
))}
</div>
<div className="flex">
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
{listItems.map((item, index) => (
<button
key={index}
type="button"
onClick={item.command}
className={cn(
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
{
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
}
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
)}
>
<item.icon
className={cn("h-4 w-4", {
className={cn("h-3.5 w-3.5", {
"text-custom-text-100": item.isActive(),
})}
strokeWidth={2.5}
/>
</button>
))}
</div>
<div className="flex">
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
{userActionItems.map((item, index) => (
<button
key={index}
type="button"
onClick={item.command}
className={cn(
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
{
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
}
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
)}
>
<item.icon
className={cn("h-4 w-4", {
className={cn("h-3.5 w-3.5", {
"text-custom-text-100": item.isActive(),
})}
strokeWidth={2.5}
/>
</button>
))}
</div>
<div className="flex">
<div className="flex items-stretch gap-0.5 pl-2.5">
{complexItems.map((item, index) => (
<button
key={index}
type="button"
onClick={item.command}
className={cn(
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
{
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
}
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
)}
>
<item.icon
className={cn("h-4 w-4", {
className={cn("h-3.5 w-3.5", {
"text-custom-text-100": item.isActive(),
})}
strokeWidth={2.5}
/>
</button>
))}
</div>
</div>
{props.submitButton}
</div>
</div>
);
};

View File

@ -49,7 +49,7 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
const [isEditing, setIsEditing] = useState(false);
const editorSuggestions = useEditorSuggestions(workspaceSlug, projectId)
const editorSuggestions = useEditorSuggestions(workspaceSlug, projectId);
const {
formState: { isSubmitting },

View File

@ -54,7 +54,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined)
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined);
const {
control,
@ -75,7 +75,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
return (
<form onSubmit={handleSubmit(handleAddComment)}>
<div className="space-y-2">
<div className="relative">
<div className="relative h-full">
{showAccessSpecifier && (
<div className="absolute bottom-2 left-3 z-[1]">
<Controller
@ -119,22 +119,28 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
deleteFile={fileService.deleteImage}
ref={editorRef}
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
customClassName="p-3 min-h-[100px] shadow-sm"
customClassName="p-2 h-full"
debouncedUpdatesEnabled={false}
mentionSuggestions={editorSuggestions.mentionSuggestions}
mentionHighlights={editorSuggestions.mentionHighlights}
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }}
submitButton={
<Button
variant="primary"
type="submit"
className="!px-2.5 !py-1.5 !text-xs"
disabled={isSubmitting || disabled}
>
{isSubmitting ? "Adding..." : "Comment"}
</Button>
}
/>
)}
/>
)}
/>
</div>
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
{isSubmitting ? "Adding..." : "Comment"}
</Button>
</div>
</form>
);