@@ -10,12 +10,12 @@ import {
1010 SELECTION_CHANGE_COMMAND ,
1111 type TextFormatType ,
1212} from "lexical" ;
13- import { Bold , Code , Italic , Strikethrough } from "lucide-react" ;
13+ import { Bold , Code , Italic , RemoveFormatting , Strikethrough } from "lucide-react" ;
1414import { AnimatePresence , m } from "motion/react" ;
1515import { type ReactNode , useCallback , useEffect , useEffectEvent , useRef , useState } from "react" ;
1616import { createPortal } from "react-dom" ;
1717
18- import { $getSelectedFormattableTextNodes } from "./codeBlock" ;
18+ import { $getSelectedFormattableTextNodes , $isFormattableTextNode } from "./codeBlock" ;
1919import { type Anchor , firstLineAnchor , placeCenteredAbove } from "./placement" ;
2020
2121// The four inline formats the toolbar toggles, in display order. Each maps to a
@@ -30,6 +30,9 @@ const FORMATS: readonly ToolbarFormat[] = [
3030 { type : "code" , label : "Inline code" , icon : Code } ,
3131] ;
3232
33+ // Separator definition between format toggles and the clear-formatting button.
34+ const CLEAR_FORMAT = { label : "Clear formatting" , icon : RemoveFormatting } as const ;
35+
3336// Initial estimate of the toolbar size, derived from the fixed markup below, so
3437// the first frame can be positioned before the node has been measured (the real
3538// size is cached on mount and takes over — see `measure`). Each button is a
@@ -39,7 +42,7 @@ const BUTTON_SIZE = 24;
3942const BUTTON_GAP = 2 ;
4043const PANEL_INSET = 5 ;
4144const TOOLBAR_WIDTH =
42- FORMATS . length * BUTTON_SIZE + ( FORMATS . length - 1 ) * BUTTON_GAP + 2 * PANEL_INSET ;
45+ ( FORMATS . length + 1 ) * BUTTON_SIZE + FORMATS . length * BUTTON_GAP + 2 * PANEL_INSET ;
4346const TOOLBAR_HEIGHT = BUTTON_SIZE + 2 * PANEL_INSET ;
4447
4548// As the selection is dragged wider, its bounding box — and thus the toolbar's
@@ -254,6 +257,28 @@ export function FloatingFormatToolbarPlugin(): ReactNode {
254257 </ button >
255258 ) ;
256259 } ) }
260+ { /* Clear formatting */ }
261+ < div className = "bg-cork-border/40 mx-0.5 h-4 w-px" />
262+ < button
263+ key = "clear-formatting"
264+ type = "button"
265+ aria-label = { CLEAR_FORMAT . label }
266+ title = { CLEAR_FORMAT . label }
267+ onMouseDown = { ( e ) => e . preventDefault ( ) }
268+ onClick = { ( ) => {
269+ editor . update ( ( ) => {
270+ const selection = $getSelection ( ) ;
271+ if ( ! $isRangeSelection ( selection ) || selection . isCollapsed ( ) ) return ;
272+ for ( const node of selection . extract ( ) . filter ( $isFormattableTextNode ) ) {
273+ node . setFormat ( 0 ) ;
274+ }
275+ selection . format = 0 ;
276+ } ) ;
277+ } }
278+ className = "text-cork-muted hover:bg-cork-border/50 hover:text-cork-text flex cursor-pointer items-center justify-center rounded-md p-1 transition-colors duration-200"
279+ >
280+ < RemoveFormatting className = "size-4" />
281+ </ button >
257282 </ m . div >
258283 ) }
259284 </ AnimatePresence > ,
0 commit comments