UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

240 lines 11 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { CommandBar, ContextualMenuItemType, Icon } from '@fluentui/react'; import { toolbarButtonStyle, ribbonDividerStyle, ribbonOverflowButtonStyle, richTextToolbarStyle } from '../../styles/RichTextEditor.styles'; import { useTheme } from '../../../theming'; import { toggleBold, toggleItalic, toggleUnderline, toggleBullet, toggleNumbering, setIndentation, insertTable } from 'roosterjs-content-model-api'; import { richTextInsertTableCommandBarItem } from './Table/RichTextInsertTableCommandBarItem'; const MaxRowsNumber = 5; const MaxColumnsNumber = 5; /** * A component to display rich text toolbar. * * @beta */ export const RichTextToolbar = (props) => { const { plugin, strings } = props; const theme = useTheme(); // need to re-render the buttons when format state changes const [formatState, setFormatState] = React.useState(undefined); const commandBarRef = useRef(null); useEffect(() => { // update the format state on editor events plugin.onFormatChanged = setFormatState; // plugin editor ready event may happen before onFormatChanged is set // call update format function to ensure the format state is set plugin.updateFormat(); }, [plugin]); const boldButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-bold-button', key: 'RichTextToolbarBoldButton', icon: 'RichTextBoldButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { toggleBold(editor); }); }, text: strings.richTextBoldTooltip, checked: formatState !== undefined && formatState.isBold === true, theme: theme }); }, [formatState, plugin, strings.richTextBoldTooltip, theme]); const italicButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-italic-button', key: 'RichTextToolbarItalicButton', icon: 'RichTextItalicButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { toggleItalic(editor); }); }, text: strings.richTextItalicTooltip, checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isItalic) === true, theme: theme }); }, [formatState, plugin, strings.richTextItalicTooltip, theme]); const underlineButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-underline-button', key: 'RichTextToolbarUnderlineButton', icon: 'RichTextUnderlineButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { toggleUnderline(editor); }); }, text: strings.richTextUnderlineTooltip, checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isUnderline) === true, theme: theme }); }, [formatState, plugin, strings.richTextUnderlineTooltip, theme]); const bulletListButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-bullet-list-button', key: 'RichTextToolbarBulletListButton', icon: 'RichTextBulletListButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { // check the format state to see if the bulleted list is already applied const isBullet = formatState === null || formatState === void 0 ? void 0 : formatState.isBullet; toggleBullet(editor); // the bulleted list was added if (!isBullet) { setTimeout(() => { // a small delay and polite aria live are needed for MacOS VoiceOver to announce the change editor.announce({ ariaLiveMode: 'polite', text: strings.richTextBulletedListAppliedAnnouncement }); }, 50); } }); }, text: strings.richTextBulletListTooltip, checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isBullet) === true, theme: theme }); }, [formatState, plugin, strings.richTextBulletListTooltip, strings.richTextBulletedListAppliedAnnouncement, theme]); const numberListButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-number-list-button', key: 'RichTextToolbarNumberListButton', icon: 'RichTextNumberListButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { // check the format state to see if the numbered list is already applied const isNumbering = formatState === null || formatState === void 0 ? void 0 : formatState.isNumbering; toggleNumbering(editor); // the numbered list was added if (!isNumbering) { // a small delay and polite aria live are needed for MacOS VoiceOver to announce the change setTimeout(() => { editor.announce({ ariaLiveMode: 'polite', text: strings.richTextNumberedListAppliedAnnouncement }); }, 50); } }); }, text: strings.richTextNumberListTooltip, checked: formatState !== undefined && (formatState === null || formatState === void 0 ? void 0 : formatState.isNumbering) === true, theme: theme }); }, [formatState, plugin, strings.richTextNumberListTooltip, strings.richTextNumberedListAppliedAnnouncement, theme]); const indentDecreaseButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-indent-decrease-button', key: 'RichTextToolbarIndentDecreaseButton', icon: 'RichTextIndentDecreaseButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { setIndentation(editor, 'outdent'); }); }, text: strings.richTextDecreaseIndentTooltip, canCheck: false, theme: theme }); }, [plugin, strings.richTextDecreaseIndentTooltip, theme]); const indentIncreaseButton = useMemo(() => { return getCommandBarItem({ dataTestId: 'rich-text-toolbar-indent-increase-button', key: 'RichTextToolbarIndentIncreaseButton', icon: 'RichTextIndentIncreaseButtonIcon', onClick: () => { plugin.onToolbarButtonClick((editor) => { setIndentation(editor, 'indent'); }); }, text: strings.richTextIncreaseIndentTooltip, canCheck: false, theme: theme }); }, [plugin, strings.richTextIncreaseIndentTooltip, theme]); const divider = useCallback((key) => { return dividerCommandBarItem(theme, key); }, [theme]); const tableButton = useMemo(() => { return richTextInsertTableCommandBarItem(theme, MaxRowsNumber, MaxColumnsNumber, strings, (column, row) => { plugin.onToolbarButtonClick((editor) => { //add format insertTable(editor, column, row); // when subMenuProps is used and the menu is dismissed, focus is set to the command bar item that opened the menu // set focus to editor on next re-render setTimeout(() => { editor.focus(); }); }); }); }, [plugin, strings, theme]); const buttons = useMemo(() => { return [ boldButton, italicButton, underlineButton, divider('RichTextRibbonTextFormatDivider'), bulletListButton, numberListButton, indentDecreaseButton, indentIncreaseButton, divider('RichTextRibbonTableDivider'), tableButton ]; }, [ boldButton, italicButton, underlineButton, divider, bulletListButton, numberListButton, indentDecreaseButton, indentIncreaseButton, tableButton ]); const overflowButtonProps = useMemo(() => { return { ariaLabel: strings.richTextToolbarMoreButtonAriaLabel, styles: toolbarButtonStyle(theme), menuProps: { items: [], // CommandBar will determine items rendered in overflow isBeakVisible: false, styles: ribbonOverflowButtonStyle(theme) } }; }, [strings.richTextToolbarMoreButtonAriaLabel, theme]); useEffect(() => { // delay focus to ensure the command bar is rendered setTimeout(() => { var _a; (_a = commandBarRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 25); }, []); return (React.createElement(CommandBar, { items: buttons, "data-testid": 'rich-text-editor-toolbar', styles: richTextToolbarStyle, overflowButtonProps: overflowButtonProps, componentRef: commandBarRef, "aria-label": strings.richTextToolbarAriaLabel })); }; const getCommandBarItem = ({ key, icon, onClick, text, canCheck = true, checked = false, disabled = false, theme, dataTestId }) => { return { role: canCheck ? 'menuitemcheckbox' : 'menuitem', 'aria-checked': canCheck ? checked : undefined, // `menuitem` role doesn't support `aria-checked` 'data-testid': dataTestId, key: key, iconProps: { iconName: icon }, onClick: onClick, text: text, ariaLabel: text, iconOnly: true, canCheck: canCheck, buttonStyles: Object.assign({}, toolbarButtonStyle(theme)), checked: checked, disabled: disabled }; }; const dividerCommandBarItem = (theme, key) => { return { key: key, disabled: true, // show the item correctly for the overflow menu itemType: ContextualMenuItemType.Divider, // this is still needed to remove checkmark icon space even though it is a divider canCheck: false, onRender: () => React.createElement(Icon, { iconName: "RichTextDividerIcon", className: ribbonDividerStyle(theme) }) }; }; //# sourceMappingURL=RichTextToolbar.js.map