1- import { FC , useEffect , useState } from "react" ;
2- import { BubbleMenu , BubbleMenuProps , Editor , isNodeSelection } from "@tiptap/ react" ;
1+ import { BubbleMenu , BubbleMenuProps , Editor , isNodeSelection , useEditorState } from "@tiptap/ react" ;
2+ import { FC , useEffect , useState , useRef } from "react" ;
33// plane utils
44import { cn } from "@plane/utils" ;
55// components
66import {
7+ BackgroundColorItem ,
78 BoldItem ,
89 BubbleMenuColorSelector ,
910 BubbleMenuLinkSelector ,
1011 BubbleMenuNodeSelector ,
1112 CodeItem ,
1213 ItalicItem ,
1314 StrikeThroughItem ,
15+ TextAlignItem ,
16+ TextColorItem ,
1417 UnderLineItem ,
1518} from "@/components/menus" ;
19+ // constants
20+ import { COLORS_LIST } from "@/constants/common" ;
1621// extensions
1722import { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection" ;
1823// local components
1924import { TextAlignmentSelector } from "./alignment-selector" ;
2025
2126type EditorBubbleMenuProps = Omit < BubbleMenuProps , "children" > ;
2227
23- export const EditorBubbleMenu : FC < EditorBubbleMenuProps > = ( props : any ) => {
24- // states
28+ export interface EditorStateType {
29+ code : boolean ;
30+ bold : boolean ;
31+ italic : boolean ;
32+ underline : boolean ;
33+ strike : boolean ;
34+ left : boolean ;
35+ right : boolean ;
36+ center : boolean ;
37+ color : { key : string ; label : string ; textColor : string ; backgroundColor : string } | undefined ;
38+ backgroundColor :
39+ | {
40+ key : string ;
41+ label : string ;
42+ textColor : string ;
43+ backgroundColor : string ;
44+ }
45+ | undefined ;
46+ }
47+
48+ export const EditorBubbleMenu : FC < EditorBubbleMenuProps > = ( props : { editor : Editor } ) => {
49+ const menuRef = useRef < HTMLDivElement > ( null ) ;
2550 const [ isNodeSelectorOpen , setIsNodeSelectorOpen ] = useState ( false ) ;
2651 const [ isLinkSelectorOpen , setIsLinkSelectorOpen ] = useState ( false ) ;
2752 const [ isColorSelectorOpen , setIsColorSelectorOpen ] = useState ( false ) ;
2853 const [ isSelecting , setIsSelecting ] = useState ( false ) ;
2954
30- const basicFormattingOptions = props . editor . isActive ( "code" )
31- ? [ CodeItem ( props . editor ) ]
32- : [ BoldItem ( props . editor ) , ItalicItem ( props . editor ) , UnderLineItem ( props . editor ) , StrikeThroughItem ( props . editor ) ] ;
55+ const formattingItems = {
56+ code : CodeItem ( props . editor ) ,
57+ bold : BoldItem ( props . editor ) ,
58+ italic : ItalicItem ( props . editor ) ,
59+ underline : UnderLineItem ( props . editor ) ,
60+ strike : StrikeThroughItem ( props . editor ) ,
61+ textAlign : TextAlignItem ( props . editor ) ,
62+ } ;
63+
64+ const editorState : EditorStateType = useEditorState ( {
65+ editor : props . editor ,
66+ selector : ( { editor } : { editor : Editor } ) => ( {
67+ code : formattingItems . code . isActive ( ) ,
68+ bold : formattingItems . bold . isActive ( ) ,
69+ italic : formattingItems . italic . isActive ( ) ,
70+ underline : formattingItems . underline . isActive ( ) ,
71+ strike : formattingItems . strike . isActive ( ) ,
72+ left : formattingItems . textAlign . isActive ( { alignment : "left" } ) ,
73+ right : formattingItems . textAlign . isActive ( { alignment : "right" } ) ,
74+ center : formattingItems . textAlign . isActive ( { alignment : "center" } ) ,
75+ color : COLORS_LIST . find ( ( c ) => TextColorItem ( editor ) . isActive ( { color : c . key } ) ) ,
76+ backgroundColor : COLORS_LIST . find ( ( c ) => BackgroundColorItem ( editor ) . isActive ( { color : c . key } ) ) ,
77+ } ) ,
78+ } ) ;
79+
80+ const basicFormattingOptions = editorState . code
81+ ? [ formattingItems . code ]
82+ : [ formattingItems . bold , formattingItems . italic , formattingItems . underline , formattingItems . strike ] ;
3383
3484 const bubbleMenuProps : EditorBubbleMenuProps = {
3585 ...props ,
@@ -51,6 +101,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
51101 } ,
52102 tippyOptions : {
53103 moveTransition : "transform 0.15s ease-out" ,
104+ duration : [ 300 , 0 ] ,
54105 onHidden : ( ) => {
55106 setIsNodeSelectorOpen ( false ) ;
56107 setIsLinkSelectorOpen ( false ) ;
@@ -60,7 +111,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
60111 } ;
61112
62113 useEffect ( ( ) => {
63- function handleMouseDown ( ) {
114+ function handleMouseDown ( e : MouseEvent ) {
115+ if ( menuRef . current ?. contains ( e . target as Node ) ) return ;
116+
64117 function handleMouseMove ( ) {
65118 if ( ! props . editor . state . selection . empty ) {
66119 setIsSelecting ( true ) ;
@@ -70,7 +123,6 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
70123
71124 function handleMouseUp ( ) {
72125 setIsSelecting ( false ) ;
73-
74126 document . removeEventListener ( "mousemove" , handleMouseMove ) ;
75127 document . removeEventListener ( "mouseup" , handleMouseUp ) ;
76128 }
@@ -84,27 +136,28 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
84136 return ( ) => {
85137 document . removeEventListener ( "mousedown" , handleMouseDown ) ;
86138 } ;
87- } , [ ] ) ;
139+ } , [ props . editor ] ) ;
88140
89141 return (
90142 < BubbleMenu { ...bubbleMenuProps } >
91143 { ! isSelecting && (
92- < div className = "flex py-2 divide-x divide-custom-border-200 rounded-lg border border-custom-border-200 bg-custom-background-100 shadow-custom-shadow-rg" >
144+ < div
145+ ref = { menuRef }
146+ className = "flex py-2 divide-x divide-custom-border-200 rounded-lg border border-custom-border-200 bg-custom-background-100 shadow-custom-shadow-rg"
147+ >
93148 < div className = "px-2" >
94- { ! props . editor . isActive ( "table" ) && (
95- < BubbleMenuNodeSelector
96- editor = { props . editor ! }
97- isOpen = { isNodeSelectorOpen }
98- setIsOpen = { ( ) => {
99- setIsNodeSelectorOpen ( ( prev ) => ! prev ) ;
100- setIsLinkSelectorOpen ( false ) ;
101- setIsColorSelectorOpen ( false ) ;
102- } }
103- />
104- ) }
149+ < BubbleMenuNodeSelector
150+ editor = { props . editor ! }
151+ isOpen = { isNodeSelectorOpen }
152+ setIsOpen = { ( ) => {
153+ setIsNodeSelectorOpen ( ( prev ) => ! prev ) ;
154+ setIsLinkSelectorOpen ( false ) ;
155+ setIsColorSelectorOpen ( false ) ;
156+ } }
157+ />
105158 </ div >
106- < div className = "px-2" >
107- { ! props . editor . isActive ( "code" ) && (
159+ { ! editorState . code && (
160+ < div className = "px-2" >
108161 < BubbleMenuLinkSelector
109162 editor = { props . editor }
110163 isOpen = { isLinkSelectorOpen }
@@ -114,21 +167,22 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
114167 setIsColorSelectorOpen ( false ) ;
115168 } }
116169 />
117- ) }
118- </ div >
119- < div className = "px-2" >
120- { ! props . editor . isActive ( "code" ) && (
170+ </ div >
171+ ) }
172+ { ! editorState . code && (
173+ < div className = "px-2" >
121174 < BubbleMenuColorSelector
122175 editor = { props . editor }
123176 isOpen = { isColorSelectorOpen }
177+ editorState = { editorState }
124178 setIsOpen = { ( ) => {
125179 setIsColorSelectorOpen ( ( prev ) => ! prev ) ;
126180 setIsNodeSelectorOpen ( false ) ;
127181 setIsLinkSelectorOpen ( false ) ;
128182 } }
129183 />
130- ) }
131- </ div >
184+ </ div >
185+ ) }
132186 < div className = "flex gap-0.5 px-2" >
133187 { basicFormattingOptions . map ( ( item ) => (
134188 < button
@@ -141,23 +195,15 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
141195 className = { cn (
142196 "size-7 grid place-items-center rounded text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 transition-colors" ,
143197 {
144- "bg-custom-background-80 text-custom-text-100" : item . isActive ( "" ) ,
198+ "bg-custom-background-80 text-custom-text-100" : editorState [ item . key ] ,
145199 }
146200 ) }
147201 >
148202 < item . icon className = "size-4" />
149203 </ button >
150204 ) ) }
151205 </ div >
152- < TextAlignmentSelector
153- editor = { props . editor }
154- onClose = { ( ) => {
155- const editor = props . editor as Editor ;
156- if ( ! editor ) return ;
157- const pos = editor . state . selection . to ;
158- editor . commands . setTextSelection ( pos ?? 0 ) ;
159- } }
160- />
206+ < TextAlignmentSelector editor = { props . editor } editorState = { editorState } />
161207 </ div >
162208 ) }
163209 </ BubbleMenu >
0 commit comments