module import type { MouseEvent as ReactMouseEvent , ReactNode } from 'react' ; import { useCallback } from 'react' ; import { Box } from 'reakit/Box' ; import { Button } from 'reakit/Button' ; import { Group } from 'reakit/Group' ; import { Toolbar as ReakitToolbar , ToolbarItem as ReakitToolbarItem , ToolbarSeparator as ReakitToolbarSeparator , ToolbarStateReturn , useToolbarState , } from 'reakit/Toolbar' ; import { Tooltip , TooltipReference , useTooltipState } from 'reakit/Tooltip' ; import { VisuallyHidden } from 'reakit/VisuallyHidden' ; import { AnyExtension , CommandDecoratorMessageProps , cx , ErrorConstant , Except , includes , invariant , } from '@remirror/core' ; import { useActive , useChainedCommands , useCommands , useHelpers , useI18n , useRemirrorContext , } from '@remirror/react-core' ; import { ComponentsTheme } from '@remirror/theme' ; import { CommandIconComponent } from '../components' ; import { DropdownComponent } from '../menu' ; import { useTheme } from '../providers' ; import { BaseButtonItem , ComponentItem , ToolbarButtonItem , ToolbarCommandButtonItem , ToolbarGroupItem , ToolbarItem , ToolbarItemUnion , ToolbarMenuItem , } from '../react-component-types' ; import { getCommandOptionValue , getShortcutString , getUiShortcutString , } from '../react-component-utils' ; interface BaseToolbarProps extends BaseButtonItem { /**
     * The toolbar state generated by `Reakit` (the underlying UI library).
     */ toolbarState : ToolbarStateReturn ; } interface ToolbarProps extends Except < ToolbarItem , 'type' > { } /**
 * This is the menu that can be placed at the top of the editor.
 */ export const Toolbar = ( props ) : JSX . Element => { const { items , loop , label , refocusEditor , focusable } = props ; const toolbarState = useToolbarState ( { loop } ) ; const focusableProps = focusable === false ? { focusable : false , tabIndex : - 1 } : { } ; return ( < ReakitToolbar { ... toolbarState } { ... focusableProps } aria - label = { label } > { items . map ( getToolbarComponent ( { toolbarState , refocusEditor } ) ) } < /ReakitToolbar>); } ; interface ToolbarCommandButtonProps extends BaseToolbarProps { /**
     * The command button item.
     */ item : ToolbarCommandButtonItem ; } const ToolbarCommandButton = ( props ) => { const { toolbarState , item } = props ; const commands = useCommands ( ) ; const chain = useChainedCommands ( ) ; const { getCommandOptions } = useHelpers ( ) ; const context = useRemirrorContext ( ) ; const { t } = useI18n ( ) ; const refocusEditor = item . refocusEditor ?? props . refocusEditor ?? false ; const active = useActive < AnyExtension > ( ) ; const { display , commandName : name , attrs , displayShortcut = true } = item ; const options = getCommandOptions ( name ) ; if ( ! options ) { return < > < />; } const enabled = commands [ name ] ?. isEnabled ( attrs ) ?? false ; const isActive = active [ options . name ] ?. ( attrs ) ?? false ; const commandProps = { active : isActive , attrs , enabled , t } ; const description = getCommandOptionValue ( options . description , commandProps ) ; const label = getCommandOptionValue ( options . label , commandProps ) ; const icon = getCommandOptionValue ( options . icon , commandProps ) ; const shortcutString = displayShortcut && options . shortcut ? ` (${ getShortcutString ( getUiShortcutString ( options . shortcut , attrs ?? { } ) , { t } ) })` : '' ; const onClick = ( event ) => { if ( item . onClick ) { item . onClick ( event , context ) ; return ; } if ( options . disableChaining ) { commands [ name ] ?. ( attrs ) ; return ; } const command = chain [ name ] ?. ( attrs ) ; if ( refocusEditor ) { command ?. focus ?. ( ) ; } command ?. run ( ) ; } ; switch ( display ) { case 'icon' : { invariant ( label && icon , { code : ErrorConstant . REACT_CONTROLLED , message : `Both label and icon are needed for the command: ${ name } in extension: ${ options . name }` , } ) ; return ( < BaseToolbarButton toolbarState = { toolbarState } disabled = { ! enabled } focusable = { enabled } tooltip = { `${ label }${ shortcutString }` } onClick = { onClick } active = { isActive } > < VisuallyHidden > { label } < /VisuallyHidden> < CommandIconComponent icon = { icon } / > < /BaseToolbarButton>); } case 'label' : { invariant ( label && description , { code : ErrorConstant . REACT_CONTROLLED , message : `Both label and description are needed for the command: ${ name } in extension: ${ options . name }` , } ) ; return ( < BaseToolbarButton toolbarState = { toolbarState } disabled = { ! enabled } focusable = { enabled } tooltip = { `${ description }${ shortcutString }` } onClick = { onClick } active = { isActive } > { label } < /BaseToolbarButton>); } default : { invariant ( label && icon , { code : ErrorConstant . REACT_CONTROLLED , message : `Icon, label and description are needed for the command: ${ name } in extension: ${ options . name }` , } ) ; return ( < BaseToolbarButton toolbarState = { toolbarState } disabled = { ! enabled } focusable = { enabled } tooltip = { `${ label }${ shortcutString }` } onClick = { onClick } active = { isActive } > { display === 'label-icon' && < Box as = 'span' > { label } < /Box>} < CommandIconComponent icon = { icon } / > { ' ' } { display === 'icon-label' && < Box as = 'span' > { label } < /Box>} < /BaseToolbarButton>); } } } ; interface BaseToolbarButtonProps extends BaseToolbarProps { children : ReactNode ; active : boolean ; tooltip ? : string ; tooltipClass ? : string ; disabled ? : boolean ; focusable ? : boolean ; className ? : string ; onClick : ( event : ReactMouseEvent < HTMLButtonElement , MouseEvent > ) => void ; } const BaseToolbarButton = ( props ) => { const { toolbarState , children , tooltip , disabled , focusable , onClick , tooltipClass , active , className , } = props ; const tooltipState = useTooltipState ( { gutter : 5 } ) ; const themeProps = useTheme ( { className : tooltipClass } ) ; return ( < > < TooltipReference { ... tooltipState } > { ( props ) => ( < ReakitToolbarItem { ... props } { ... toolbarState } as = { Button } disabled = { disabled } focusable = { focusable } onClick = { onClick } className = { cx ( className , active && ComponentsTheme . BUTTON_ACTIVE ) } > { children } < /ReakitToolbarItem>)} < /TooltipReference> { tooltip && ( < Tooltip { ... tooltipState } style = { themeProps . style } className = { cx ( themeProps . className ) } > { tooltip } < /Tooltip>)} < />); } ; interface ToolbarSeparatorProps extends BaseToolbarProps { } /**
 * The divider component
 */ const ToolbarSeparator = ( props ) => { return < ReakitToolbarSeparator { ... props . toolbarState } / > ; } ; interface ToolbarGroupProps extends BaseToolbarProps { /**
     * The group item configuration.
     */ item : ToolbarGroupItem ; } /**
 * The [[`Group`]] component
 */ const ToolbarGroup = ( props ) => { const { toolbarState , refocusEditor , item : group } = props ; const startSeparator = includes ( [ 'start' , 'both' ] , group . separator ) && ( < ToolbarSeparator toolbarState = { toolbarState } />); const endSeparator = includes ( [ 'end' , 'both' ] , group . separator ) && ( < ToolbarSeparator toolbarState = { toolbarState } />); return ( < > { startSeparator } < Group aria - label = { group . label } > { group . items . map ( getToolbarComponent ( { toolbarState , refocusEditor } ) ) } < /Group> { endSeparator } < />); } ; interface ToolbarButtonProps extends BaseToolbarProps { /**
     * The command button item.
     */ item : ToolbarButtonItem ; } const ToolbarButton = ( props ) => { const { toolbarState , focusable , item } = props ; const context = useRemirrorContext ( ) ; const refocusEditor = item . refocusEditor ?? props . refocusEditor ?? false ; const { disabled , label , icon , active = false } = item ; const onClick = useCallback ( ( event ) => { item . onClick ( event , context ) ; if ( refocusEditor ) { requestAnimationFrame ( ( ) => context . commands . focus ( ) ) ; } } , [ refocusEditor , context , item ] ) ; return ( < BaseToolbarButton toolbarState = { toolbarState } disabled = { disabled } focusable = { focusable } onClick = { onClick } active = { active } > < Box as = 'span' > { label } < /Box> { icon && < CommandIconComponent icon = { icon } />} < /BaseToolbarButton>); } ; /**
 * Create a mapping function for generating a toolbar component
 */ function getToolbarComponent ( props ) { return ( item , index , _ ) => { const { toolbarState , refocusEditor } = props ; switch ( item . type ) { case ComponentItem . ToolbarCommandButton : return ( < ToolbarCommandButton item = { item } toolbarState = { toolbarState } refocusEditor = { refocusEditor } key = { item . key ?? index } / > ) ; case ComponentItem . ToolbarButton : return ( < ToolbarButton item = { item } toolbarState = { toolbarState } refocusEditor = { refocusEditor } key = { item . key ?? index } / > ) ; case ComponentItem . ToolbarGroup : return ( < ToolbarGroup item = { item } toolbarState = { toolbarState } refocusEditor = { refocusEditor } key = { item . key ?? index } / > ) ; case ComponentItem . ToolbarSeparator : return < ToolbarSeparator toolbarState = { toolbarState } key = { item . key ?? index } / > ; case ComponentItem . ToolbarElement : return item . element ; case ComponentItem . ToolbarComponent : return ( < item . Component Wrapper = { ReakitToolbarItem } toolbarState = { toolbarState } key = { item . key ?? index } / > ) ; case ComponentItem . ToolbarMenu : return < ToolbarMenu item = { item } toolbarState = { toolbarState } key = { item . key ?? index } / > ; } } ; } interface ToolbarMenuProps extends BaseToolbarProps { item : ToolbarMenuItem ; } const ToolbarMenu = ( props ) : JSX . Element => { const { item , toolbarState } = props ; const { items , label , menuLabel } = item ; return ( < ReakitToolbarItem { ... toolbarState } as = { DropdownComponent } menuItems = { items } label = { label } menuLabel = { menuLabel } / > ) ; } ;