@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
272 lines (268 loc) • 11.5 kB
JavaScript
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React, { useCallback, useMemo } from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { css, jsx } from '@emotion/react';
import { useIntl } from 'react-intl';
import withAnalyticsContext from '@atlaskit/analytics-next/withAnalyticsContext';
import withAnalyticsEvents from '@atlaskit/analytics-next/withAnalyticsEvents';
import Button from '@atlaskit/button/new';
import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
import { fg } from '@atlaskit/platform-feature-flags';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Box, xcss, Inline } from '@atlaskit/primitives';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import Tooltip from '@atlaskit/tooltip';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, editorAnalyticsChannel, EVENT_TYPE } from '../../analytics';
import { colorPickerButtonMessages } from '../../messages/color-picker-button';
import { ColorPalette, DEFAULT_BORDER_COLOR, getSelectedRowAndColumnFromPalette } from '../../ui-color';
import { withReactEditorViewOuterListeners } from '../../ui-react';
import { default as Popup } from '../../ui/Popup';
import { ArrowKeyNavigationProvider } from '../ArrowKeyNavigationProvider';
import { ArrowKeyNavigationType } from '../ArrowKeyNavigationProvider/types';
// helps adjusts position of popup
const colorPickerButtonWrapper = css({
position: 'relative'
});
const colorPickerExpandContainerVisualRefresh = xcss({
marginTop: 'space.negative.025',
marginRight: 'space.negative.050'
});
const colorPickerButtonStyle = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
button: {
'&::after': {
border: 'none'
},
'&:hover': {
backgroundColor: `${"var(--ds-background-selected, #E9F2FE)"}`
}
}
});
// Control the size of color picker buttons and preview
// TODO: DSP-4134 - Color picking UI
/* eslint-disable @atlaskit/design-system/ensure-design-token-usage */
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766
const colorPickerWrapper = () => css({
borderRadius: "var(--ds-radius-small, 3px)",
backgroundColor: "var(--ds-surface-overlay, #FFFFFF)",
boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)",
padding: `${"var(--ds-space-100, 8px)"} 0px`
});
/* eslint-enable @atlaskit/design-system/ensure-design-token-usage */
const ColorPaletteWithReactViewListeners = withReactEditorViewOuterListeners(ColorPalette);
const COLOR_PICKER_POPUP_OFFSET = [0, 10];
const ColorPickerButton = props => {
const buttonRef = React.useRef(null);
const [isPopupOpen, setIsPopupOpen] = React.useState(false);
const [isPopupPositioned, setIsPopupPositioned] = React.useState(false);
const [isOpenedByKeyboard, setIsOpenedByKeyboard] = React.useState(false);
const {
formatMessage
} = useIntl();
const memoizedHandleClose = useCallback(() => setIsPopupOpen(false), [setIsPopupOpen]);
const handleClose = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? memoizedHandleClose : () => setIsPopupOpen(false);
const memoizedTogglePopup = useCallback(() => {
setIsPopupOpen(prevIsOpen => {
if (!prevIsOpen) {
setIsOpenedByKeyboard(false);
setIsPopupPositioned(false);
}
return !prevIsOpen;
});
}, []);
const togglePopup = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? memoizedTogglePopup : () => {
setIsPopupOpen(!isPopupOpen);
if (!isPopupOpen) {
setIsOpenedByKeyboard(false);
setIsPopupPositioned(false);
}
};
const memoizedOnKeyDown = useCallback(event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
memoizedTogglePopup();
setIsOpenedByKeyboard(true);
}
}, [memoizedTogglePopup]);
const onKeyDown = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? memoizedOnKeyDown : event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
togglePopup();
setIsOpenedByKeyboard(true);
}
};
const memoizedPaletteOptions = useMemo(() => ({
palette: props.colorPalette,
hexToPaletteColor: props.hexToPaletteColor,
paletteColorTooltipMessages: props.paletteColorTooltipMessages
}), [props.colorPalette, props.hexToPaletteColor, props.paletteColorTooltipMessages]);
const paletteOptions = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? memoizedPaletteOptions : {
palette: props.colorPalette,
hexToPaletteColor: props.hexToPaletteColor,
paletteColorTooltipMessages: props.paletteColorTooltipMessages
};
React.useEffect(() => {
if (props.setDisableParentScroll) {
props.setDisableParentScroll(isPopupOpen);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPopupOpen]);
const focusButton = () => {
var _buttonRef$current;
(_buttonRef$current = buttonRef.current) === null || _buttonRef$current === void 0 ? void 0 : _buttonRef$current.focus();
};
const handleEsc = React.useCallback(() => {
setIsOpenedByKeyboard(false);
setIsPopupPositioned(false);
setIsPopupOpen(false);
focusButton();
}, []);
const onPositionCalculated = React.useCallback(position => {
setIsPopupPositioned(true);
return position;
}, []);
const {
onChange,
createAnalyticsEvent,
colorPalette,
placement,
skipFocusButtonAfterPick
} = props;
const onColorSelected = React.useCallback((color, label) => {
setIsOpenedByKeyboard(false);
setIsPopupOpen(false);
setIsPopupPositioned(false);
if (onChange) {
if (createAnalyticsEvent) {
// fire analytics
const payload = {
action: ACTION.UPDATED,
actionSubject: ACTION_SUBJECT.PICKER,
actionSubjectId: ACTION_SUBJECT_ID.PICKER_COLOR,
attributes: {
color,
label,
placement: placement
},
eventType: EVENT_TYPE.TRACK
};
createAnalyticsEvent(payload).fire(editorAnalyticsChannel);
}
const newPalette = colorPalette.find(colorPalette => colorPalette.value === color);
newPalette && onChange(newPalette);
}
if (!skipFocusButtonAfterPick) {
focusButton();
}
}, [colorPalette, onChange, createAnalyticsEvent, placement, skipFocusButtonAfterPick]);
const renderPopup = () => {
if (!isPopupOpen || !buttonRef.current) {
return;
}
const selectedColor = props.currentColor || null;
const {
selectedRowIndex,
selectedColumnIndex
} = getSelectedRowAndColumnFromPalette(props.colorPalette, selectedColor, props.cols);
return jsx(Popup, {
target: buttonRef.current,
fitHeight: 350,
fitWidth: 350,
offset: expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? COLOR_PICKER_POPUP_OFFSET : [0, 10],
alignX: props.alignX,
mountTo: props.setDisableParentScroll ? props.mountPoint : undefined,
absoluteOffset: props.absoluteOffset
// Confluence inline comment editor has z-index: 500
// if the toolbar is scrollable, this will be mounted in the root editor
// we need an index of > 500 to display over it
,
zIndex: props.setDisableParentScroll ? 600 : undefined,
ariaLabel: fg('_editor_a11y_aria_label_removal_popup') ? formatMessage(colorPickerButtonMessages.colorPickerMenuLabel) : 'Color picker popup',
onPositionCalculated: onPositionCalculated
}, jsx("div", {
css: colorPickerWrapper,
"data-test-id": "color-picker-menu"
}, jsx(ArrowKeyNavigationProvider, {
type: ArrowKeyNavigationType.COLOR,
selectedRowIndex: selectedRowIndex,
selectedColumnIndex: selectedColumnIndex,
closeOnTab: true,
handleClose: handleClose,
isOpenedByKeyboard: isOpenedByKeyboard,
isPopupPositioned: isPopupPositioned,
ignoreEscapeKey: props.returnEscToButton
}, jsx(ColorPaletteWithReactViewListeners, {
cols: props.cols,
selectedColor: selectedColor,
onClick: onColorSelected,
handleClickOutside: togglePopup,
handleEscapeKeydown: handleEsc,
paletteOptions: paletteOptions
}))));
};
const title = props.title || '';
const currentColor = props.currentColor && props.hexToPaletteColor ? props.hexToPaletteColor(props.currentColor) : props.currentColor;
const buttonStyleVisualRefresh = () => {
var _props$size, _props$size2, _props$size3;
return css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
height: `${!!((_props$size = props.size) !== null && _props$size !== void 0 && _props$size.height) ? 'inherit' : ''}`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
'&:before': {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
content: "''",
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
border: `${"var(--ds-border-width, 1px)"} solid ${DEFAULT_BORDER_COLOR}`,
borderRadius: "var(--ds-radius-small, 3px)",
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
backgroundColor: currentColor || 'transparent',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
width: ((_props$size2 = props.size) === null || _props$size2 === void 0 ? void 0 : _props$size2.width) || '14px',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
height: ((_props$size3 = props.size) === null || _props$size3 === void 0 ? void 0 : _props$size3.height) || '14px',
marginTop: `${"var(--ds-space-025, 2px)"}`
}
});
};
return jsx("div", {
css: colorPickerButtonWrapper
}, jsx(Tooltip, {
content: title,
position: "top"
}, jsx("div", {
css: colorPickerButtonStyle
}, jsx(Button, {
appearance: 'subtle',
ref: buttonRef,
"aria-label": title,
"aria-expanded": props.isAriaExpanded ? isPopupOpen : undefined,
spacing: editorExperiment('platform_editor_controls', 'variant1') ? 'default' : 'compact',
onClick: togglePopup,
onKeyDown: onKeyDown,
"data-selected-color": props.currentColor,
isSelected: isPopupOpen
}, jsx(Inline, {
alignBlock: "start"
}, jsx("span", {
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
css: buttonStyleVisualRefresh
}), jsx(Box, {
xcss: colorPickerExpandContainerVisualRefresh
}, jsx(ChevronDownIcon, {
color: "currentColor",
spacing: "spacious",
label: "color-picker-chevron-down",
size: "small"
})))))), renderPopup());
};
const _default_1 = withAnalyticsContext({
source: 'ConfigPanel'
})(withAnalyticsEvents()(ColorPickerButton));
export default _default_1;