UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

483 lines (481 loc) 19.1 kB
/** * @jsxRuntime classic * @jsx jsx */ import { useCallback, useEffect, useRef, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports import { css, jsx } from '@emotion/react'; import { imageBorderMessages as messages } from '@atlaskit/editor-common/media'; import { DropdownMenuSharedCssClassName } from '@atlaskit/editor-common/styles'; import { Popup } from '@atlaskit/editor-common/ui'; import { borderColorPalette, borderPaletteTooltipMessages, ColorPalette } from '@atlaskit/editor-common/ui-color'; import { ArrowKeyNavigationProvider, ArrowKeyNavigationType, DropdownMenu, ToolbarButton } from '@atlaskit/editor-common/ui-menu'; import { hexToEditorBorderPaletteColor } from '@atlaskit/editor-palette'; import BorderIcon from '@atlaskit/icon/core/border'; import ChevronDownIcon from '@atlaskit/icon/core/chevron-down'; import StrokeWeightLargeIcon from '@atlaskit/icon/core/stroke-weight-large'; import StrokeWeightMediumIcon from '@atlaskit/icon/core/stroke-weight-medium'; import StrokeWeightSmallIcon from '@atlaskit/icon/core/stroke-weight-small'; import { Text } from '@atlaskit/primitives/compiled'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import Tooltip from '@atlaskit/tooltip'; import { buttonStyle, buttonWrapperStyle, contextualMenuArrow, contextualSubMenu, dropdownOptionButton, dropdownWrapper, itemSpacing, menuItemDimensions, toolbarButtonWrapper } from './styles'; // New padding for border options drop down const dropdownOptionButtonNew = css({ background: 'transparent', borderWidth: "var(--ds-border-width-selected, 2px)", borderStyle: 'solid', borderColor: 'transparent', display: 'flex', width: '100%', alignItems: 'center', justifyContent: 'space-between', padding: 0, '&:focus': { backgroundColor: "var(--ds-background-neutral-subtle-hovered, #0515240F)", borderColor: "var(--ds-border-focused, #4688EC)" } }); const ImageBorder = ({ intl: { formatMessage }, toggleBorder, borderMark, setBorder }) => { const popupTarget = useRef(null); const dropDownColorOptionButton = useRef(null); const dropDownSizeOptionButton = useRef(null); const colorSubmenuRef = useRef(null); const sizeSubmenuRef = useRef(null); const openDropdownButtonRef = useRef(null); const enabled = !!borderMark; const color = borderMark === null || borderMark === void 0 ? void 0 : borderMark.color; const size = borderMark === null || borderMark === void 0 ? void 0 : borderMark.size; const [isOpen, setIsOpen] = useState(false); const [isOpenByKeyboard, setIsOpenedByKeyboard] = useState(false); const [isColorSubmenuOpen, setIsColorSubmenuOpen] = useState(false); const [isSizeSubmenuOpen, setIsSizeSubmenuOpen] = useState(false); const handleColorSubmenuEsc = useCallback(() => { var _dropDownColorOptionB; setIsOpenedByKeyboard(false); setIsColorSubmenuOpen(false); dropDownColorOptionButton === null || dropDownColorOptionButton === void 0 ? void 0 : (_dropDownColorOptionB = dropDownColorOptionButton.current) === null || _dropDownColorOptionB === void 0 ? void 0 : _dropDownColorOptionB.focus(); }, []); const handleSizeSubmenuEsc = useCallback(() => { var _dropDownSizeOptionBu; setIsOpenedByKeyboard(false); setIsSizeSubmenuOpen(false); dropDownSizeOptionButton === null || dropDownSizeOptionButton === void 0 ? void 0 : (_dropDownSizeOptionBu = dropDownSizeOptionButton.current) === null || _dropDownSizeOptionBu === void 0 ? void 0 : _dropDownSizeOptionBu.focus(); }, []); const handleSubMenuRef = ref => { if (!ref) { return; } const rect = ref.getBoundingClientRect(); if (rect.left + rect.width > window.innerWidth) { ref.style.left = `-${rect.width}px`; } }; const handleTriggerByKeyboard = (event, allowedKeys, callback) => { if (!allowedKeys.includes(event.key)) { return; } event.preventDefault(); callback(); setIsOpenedByKeyboard(true); }; const handleTriggerToolbarByKeyboard = (event, callback) => { handleTriggerByKeyboard(event, ['Enter', ' '], callback); }; const handleTriggerSubmenuByKeyboard = (event, callback) => { const keys = expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true) ? ['Enter', 'ArrowRight'] : ['Enter', ' ']; handleTriggerByKeyboard(event, keys, callback); }; useEffect(() => { const focusFirstOption = (submenuRef, isOpen) => { if (!isOpenByKeyboard) { return; } if (isOpen && submenuRef.current) { const firstOption = submenuRef.current.querySelector('button'); if (!firstOption) { return; } firstOption.focus(); const keyboardEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }); firstOption.dispatchEvent(keyboardEvent); } }; focusFirstOption(colorSubmenuRef, isColorSubmenuOpen); focusFirstOption(sizeSubmenuRef, isSizeSubmenuOpen); }, [isColorSubmenuOpen, isSizeSubmenuOpen, isOpenByKeyboard]); const borderSizeOptions = [{ name: formatMessage(messages.borderSizeSubtle), value: 1, icon: StrokeWeightSmallIcon }, { name: formatMessage(messages.borderSizeMedium), value: 2, icon: StrokeWeightMediumIcon }, { name: formatMessage(messages.borderSizeBold), value: 3, icon: StrokeWeightLargeIcon }]; const items = [{ content: jsx("div", null, jsx("button", { ref: dropDownColorOptionButton, type: "button", "aria-label": formatMessage(messages.borderColorDropdownAriaLabel), "data-testid": "image-border-dropdown-button-color", css: expValEquals('platform_editor_fix_media_toolbar_border_dropdown', 'isEnabled', true, false) ? dropdownOptionButtonNew : // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 dropdownOptionButton, "aria-expanded": isColorSubmenuOpen, onKeyDown: e => handleTriggerSubmenuByKeyboard(e, () => { if (expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { setIsColorSubmenuOpen(prev => { const next = !prev; if (next) { setIsSizeSubmenuOpen(false); } return next; }); } else { setIsColorSubmenuOpen(!isColorSubmenuOpen); } }) }, jsx(Text, null, formatMessage(messages.borderColor)), jsx("div", { css: contextualMenuArrow })), jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 className: DropdownMenuSharedCssClassName.SUBMENU, ref: colorSubmenuRef }, isColorSubmenuOpen && // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 jsx("div", { css: contextualSubMenu(0), ref: handleSubMenuRef }, jsx(ArrowKeyNavigationProvider, { type: ArrowKeyNavigationType.MENU // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , handleClose: e => { e.preventDefault(); e.stopPropagation(); handleColorSubmenuEsc(); }, disableCloseOnArrowClick: true }, jsx(ColorPalette // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { onClick: color => { setBorder({ color }); setIsOpen(!isOpen); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onKeyDown: (color, _, event) => { if (event.key === 'Enter' || event.key === ' ') { var _openDropdownButtonRe; setBorder({ color }); setIsOpen(!isOpen); setIsColorSubmenuOpen(false); setIsSizeSubmenuOpen(false); (_openDropdownButtonRe = openDropdownButtonRef.current) === null || _openDropdownButtonRe === void 0 ? void 0 : _openDropdownButtonRe.focus(); } }, selectedColor: color !== null && color !== void 0 ? color : null // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , paletteOptions: { palette: borderColorPalette, paletteColorTooltipMessages: borderPaletteTooltipMessages, hexToPaletteColor: hexToEditorBorderPaletteColor } }))))), 'data-testid': 'dropdown-item__Color', key: 'dropdown-menu-image-border-color-button', value: { name: 'color' }, 'aria-label': '', wrapperTabIndex: null }, { content: jsx("div", null, jsx("button", { type: "button", "aria-label": formatMessage(messages.borderSizeDropdownAriaLabel), "data-testid": "image-border-dropdown-button-size", css: expValEquals('platform_editor_fix_media_toolbar_border_dropdown', 'isEnabled', true, false) ? dropdownOptionButtonNew : // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 dropdownOptionButton, "aria-expanded": isSizeSubmenuOpen, ref: dropDownSizeOptionButton, onKeyDown: e => handleTriggerSubmenuByKeyboard(e, () => { if (expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { setIsSizeSubmenuOpen(prev => { const next = !prev; if (next) { setIsColorSubmenuOpen(false); } return next; }); } else { setIsSizeSubmenuOpen(!isSizeSubmenuOpen); } }) }, jsx(Text, null, formatMessage(messages.borderSize)), jsx("div", { css: contextualMenuArrow })), jsx("div", { //eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 className: DropdownMenuSharedCssClassName.SUBMENU, ref: sizeSubmenuRef }, isSizeSubmenuOpen && jsx(ArrowKeyNavigationProvider, { type: ArrowKeyNavigationType.MENU // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , handleClose: e => { e.preventDefault(); handleSizeSubmenuEsc(); }, disableCloseOnArrowClick: true }, jsx("div", { css: contextualSubMenu(1), ref: handleSubMenuRef }, borderSizeOptions.map(({ name, value, icon }, idx) => { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any const ButtonIcon = icon; return ( // Ignored via go/ees005 // eslint-disable-next-line react/no-array-index-key jsx(Tooltip, { key: idx, content: name }, jsx("span", { css: buttonWrapperStyle }, jsx("button", { type: "button" /* eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 */, css: buttonStyle(value === size), "aria-label": name, role: "radio", "aria-checked": value === size, onClick: () => { setBorder({ size: value }); setIsOpen(false); }, onKeyDown: event => { if (event.key === 'Enter' || event.key === ' ') { var _openDropdownButtonRe2; setBorder({ size: value }); setIsOpen(false); setIsColorSubmenuOpen(false); setIsSizeSubmenuOpen(false); (_openDropdownButtonRe2 = openDropdownButtonRef.current) === null || _openDropdownButtonRe2 === void 0 ? void 0 : _openDropdownButtonRe2.focus(); } }, onMouseDown: e => { e.preventDefault(); } }, jsx(ButtonIcon, { color: value === size ? "var(--ds-icon-inverse, #FFFFFF)" : 'currentColor', spacing: "spacious", label: "" })))) ); }))))), 'data-testid': 'dropdown-item__Size', key: 'dropdown-menu-image-border-size-button', value: { name: 'size' }, 'aria-label': '', wrapperTabIndex: null }]; /** * We want to change direction of our dropdowns a bit early, * not exactly when it hits the boundary. */ const fitTolerance = 10; const fitWidth = menuItemDimensions.width; const fitHeight = items.length * (menuItemDimensions.height + itemSpacing); const isAnySubMenuOpen = isSizeSubmenuOpen || isColorSubmenuOpen; return jsx("div", null, jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 css: toolbarButtonWrapper({ enabled, isOpen }) }, jsx(ToolbarButton // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , { className: "image-border-toolbar-btn", selected: enabled, onClick: toggleBorder, spacing: "compact", "aria-label": enabled ? formatMessage(messages.removeBorder) : formatMessage(messages.addBorder), iconBefore: jsx(BorderIcon, { color: "currentColor", label: "", spacing: "spacious" }), title: enabled ? formatMessage(messages.removeBorder) : formatMessage(messages.addBorder) }), jsx("div", { ref: popupTarget }, jsx(ToolbarButton // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , { className: "image-border-toolbar-dropdown", ref: openDropdownButtonRef, selected: enabled || isOpen, "aria-expanded": isOpen, "aria-label": formatMessage(messages.borderOptions), title: formatMessage(messages.borderOptions), spacing: "compact", iconBefore: jsx(ChevronDownIcon, { color: "currentColor", spacing: "spacious", label: "", size: "small" }) // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClick: () => { setIsOpen(!isOpen); setIsOpenedByKeyboard(false); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onKeyDown: e => handleTriggerToolbarByKeyboard(e, () => setIsOpen(!isOpen)) }))), jsx(Popup, { target: popupTarget.current ? popupTarget.current : undefined, fitWidth: fitWidth + fitTolerance, fitHeight: fitHeight + fitTolerance, forcePlacement: true, stick: true }, jsx("div", { onMouseLeave: expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true) ? undefined : () => { setIsColorSubmenuOpen(false); setIsSizeSubmenuOpen(false); } /* eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 */, css: dropdownWrapper }, jsx(DropdownMenu //This needs be removed when the a11y is completely handled //Disabling key navigation now as it works only partially //Same with packages/editor/editor-plugin-table/src/plugins/table/ui/FloatingContextualMenu/ContextualMenu.tsx // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { arrowKeyNavigationProviderOptions: { type: ArrowKeyNavigationType.MENU, disableArrowKeyNavigation: isAnySubMenuOpen }, allowEnterDefaultBehavior: isAnySubMenuOpen, handleEscapeKeydown: isAnySubMenuOpen ? () => { return; } : () => { var _openDropdownButtonRe3; (_openDropdownButtonRe3 = openDropdownButtonRef.current) === null || _openDropdownButtonRe3 === void 0 ? void 0 : _openDropdownButtonRe3.focus(); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , items: [{ items }], isOpen: isOpen // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , shouldFocusFirstItem: () => isOpenByKeyboard // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onOpenChange: () => { setIsOpen(false); setIsColorSubmenuOpen(false); setIsSizeSubmenuOpen(false); setIsOpenedByKeyboard(false); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onItemActivated: ({ item }) => { if (item.value.name === 'color') { if (expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { setIsColorSubmenuOpen(prev => { const next = !prev; if (next) { setIsSizeSubmenuOpen(false); } return next; }); } else { setIsColorSubmenuOpen(!isColorSubmenuOpen); } } if (item.value.name === 'size') { if (expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { setIsSizeSubmenuOpen(prev => { const next = !prev; if (next) { setIsColorSubmenuOpen(false); } return next; }); } else { setIsSizeSubmenuOpen(!isSizeSubmenuOpen); } } } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onMouseEnter: ({ item }) => { if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { if (item.value.name === 'color') { setIsColorSubmenuOpen(true); setIsOpenedByKeyboard(false); } if (item.value.name === 'size') { setIsSizeSubmenuOpen(true); setIsOpenedByKeyboard(false); } } } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onMouseLeave: ({ item }) => { if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { if (item.value.name === 'color') { setIsColorSubmenuOpen(false); } if (item.value.name === 'size') { setIsSizeSubmenuOpen(false); } } }, fitWidth: fitWidth + fitTolerance, fitHeight: fitHeight + fitTolerance })))); }; export default ImageBorder;