communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
240 lines • 11 kB
JavaScript
// 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