@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
370 lines (364 loc) • 13.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
/* eslint-disable jsdoc/check-tag-names */
/**
* @jsxRuntime classic
* @jsx jsx
*/
import { useCallback, useEffect, useMemo, useState, useRef, Fragment } from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports
import { jsx, css } from '@emotion/react';
import Button from '@atlaskit/button';
import { IconButton } from '@atlaskit/button/new';
import { pixelEntryMessages as messages } from '@atlaskit/editor-common/media';
import Form, { Field } from '@atlaskit/form';
import CrossIcon from '@atlaskit/icon/core/cross';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives, @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Inline, Box, Text, xcss } from '@atlaskit/primitives';
import Textfield from '@atlaskit/textfield';
import Tooltip from '@atlaskit/tooltip';
import { PIXEL_RESIZING_TOOLBAR_WIDTH, PIXEL_VIEW_MODE_TOOLBAR_WIDTH, PIXELENTRY_MIGRATION_BUTTON_TESTID } from './constants';
import { pixelEntryForm, pixelEntryHiddenSubmit, pixelSizingHeightInput, pixelSizingInput, pixelSizingWidthInput, pixelSizingWrapper } from './styles';
const pixelSizingLabel = xcss({
gridArea: 'label',
lineHeight: "var(--ds-space-300, 24px)"
});
export const PixelEntryComponent = ({
width,
mediaWidth,
mediaHeight,
onSubmit,
minWidth,
maxWidth,
onChange,
intl: {
formatMessage
},
showMigration,
onMigrate,
onCloseAndSave,
isViewMode,
areAnyNewToolbarFlagsEnabled
}) => {
const ratioWidth = useMemo(() => {
return mediaHeight / mediaWidth;
}, [mediaHeight, mediaWidth]);
const ratioHeight = useMemo(() => {
return mediaWidth / mediaHeight;
}, [mediaHeight, mediaWidth]);
const [computedWidth, setComputedWidth] = useState(width);
const [computedHeight, setComputedHeight] = useState(Math.round(ratioWidth * width));
// Handle update to width from outside component
useEffect(() => {
setComputedWidth(width);
setComputedHeight(Math.round(ratioWidth * width));
}, [width, ratioWidth]);
// Handle submit when user presses enter in form
const handleOnSubmit = data => {
if (data.inputWidth === '' || data.inputHeight === '') {
return;
}
if (onSubmit) {
let widthToBeSumitted = data.inputWidth;
let validation = 'valid';
if (data.inputWidth < minWidth) {
widthToBeSumitted = minWidth;
validation = 'less-than-min';
}
if (data.inputWidth > maxWidth) {
widthToBeSumitted = maxWidth;
validation = 'greater-than-max';
}
// If user keeps submitting an invalid input, node width attribute will be updated with the same value
// and won't upadte the state in useEffect (since width is the same)
// Thus, we set the state here to always display the correct dimension
if (validation !== 'valid') {
setComputedWidth(widthToBeSumitted);
setComputedHeight(Math.round(ratioWidth * widthToBeSumitted));
}
onSubmit({
width: widthToBeSumitted,
validation
});
}
};
// Handle submit when user presses enter or click close button in PixelEntryComponentNext
const handleCloseAndSave = useCallback((data, setFocus) => {
if (data.inputWidth === '' || data.inputHeight === '') {
return;
}
if (onCloseAndSave) {
let widthToBeSubmitted = data.inputWidth;
let validation = 'valid';
if (data.inputWidth < minWidth) {
widthToBeSubmitted = minWidth;
validation = 'less-than-min';
}
if (data.inputWidth > maxWidth) {
widthToBeSubmitted = maxWidth;
validation = 'greater-than-max';
}
// If user keeps submitting an invalid input, node width attribute will be updated with the same value
// and won't upadte the state in useEffect (since width is the same)
// Thus, we set the state here to always display the correct dimension
if (validation !== 'valid') {
setComputedWidth(widthToBeSubmitted);
setComputedHeight(Math.round(ratioWidth * widthToBeSubmitted));
}
onCloseAndSave({
width: widthToBeSubmitted,
validation
}, setFocus);
}
}, [maxWidth, minWidth, onCloseAndSave, ratioWidth]);
// Handle updating computed fields based on
const handleOnChange = useCallback(type => event => {
const value = parseInt(event.currentTarget.value);
const newInputValue = isNaN(value) ? '' : value;
let newWidth = '';
switch (type) {
case 'inputWidth':
{
newWidth = newInputValue;
setComputedWidth(newInputValue);
const newHeight = newInputValue !== '' ? Math.round(ratioWidth * newInputValue) : '';
setComputedHeight(newHeight);
break;
}
case 'inputHeight':
{
setComputedHeight(newInputValue);
newWidth = newInputValue !== '' ? Math.round(ratioHeight * newInputValue) : '';
setComputedWidth(newWidth);
break;
}
}
const isInvalidInputValid = newWidth !== '' && (newWidth < minWidth || newWidth > maxWidth);
onChange && onChange(isInvalidInputValid);
}, [minWidth, maxWidth, onChange, ratioWidth, ratioHeight]);
if (showMigration) {
return jsx(Tooltip, {
content: formatMessage(messages.migrationButtonTooltip)
}, jsx(Button, {
appearance: "warning",
spacing: "compact",
onClick: onMigrate,
testId: PIXELENTRY_MIGRATION_BUTTON_TESTID
}, formatMessage(messages.migrationButtonText)));
}
if (areAnyNewToolbarFlagsEnabled) {
return jsx(PixelEntryComponentNext, {
maxWidth: maxWidth,
formatMessage: formatMessage,
handleFieldChange: handleOnChange,
computedWidth: computedWidth,
computedHeight: computedHeight,
handleCloseAndSave: handleCloseAndSave,
isViewMode: isViewMode
});
}
return (
// 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: pixelEntryForm
}, jsx(Form, {
onSubmit: handleOnSubmit
}, ({
formProps
}) => {
return (
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
jsx("form", formProps, jsx("div", {
css: pixelSizingWrapper
}, jsx(Field, {
key: "inputWidth",
name: "inputWidth",
defaultValue: computedWidth
}, ({
fieldProps
}) => jsx(Tooltip, {
hideTooltipOnMouseDown: true,
content: formatMessage(messages.inputWidthTooltip, {
maxWidth
}),
position: "top"
}, jsx(Textfield
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, _extends({}, fieldProps, {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/no-unsafe-style-overrides, @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
css: [pixelSizingWidthInput, pixelSizingInput],
appearance: "none",
isCompact: true,
onChange: handleOnChange('inputWidth'),
pattern: "\\d*",
"aria-label": formatMessage(messages.inputWidthAriaLabel, {
maxWidth
})
})))), jsx(Box, {
as: "span",
xcss: pixelSizingLabel
}, "\xD7"), jsx(Field, {
key: "inputHeight",
name: "inputHeight",
defaultValue: computedHeight
}, ({
fieldProps
}) => jsx(Tooltip, {
hideTooltipOnMouseDown: true,
content: formatMessage(messages.inputHeightTooltip),
position: "top"
}, jsx(Textfield
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, _extends({}, fieldProps, {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/no-unsafe-style-overrides, @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
css: [pixelSizingHeightInput, pixelSizingInput],
appearance: "none",
isCompact: true,
onChange: handleOnChange('inputHeight'),
pattern: "\\d*",
"aria-label": formatMessage(messages.inputHeightAriaLabel)
})))), jsx(Button, {
css: pixelEntryHiddenSubmit,
type: "submit"
}, formatMessage(messages.submitButtonText))))
);
}))
);
};
const pixelEntryWrapperStyles = xcss({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
width: `${PIXEL_RESIZING_TOOLBAR_WIDTH}px`
});
const pixelEntryWrapperViewModeStyles = xcss({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
width: `${PIXEL_VIEW_MODE_TOOLBAR_WIDTH}px`
});
const fieldStyles = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'[data-ds--text-field--input]': {
paddingBlock: "var(--ds-space-0, 0px)",
paddingInline: "var(--ds-space-100, 8px)"
},
flex: '1'
});
const dividerStyles = xcss({
width: '1px',
height: "var(--ds-space-500, 40px)",
background: "var(--ds-border, #0B120E24)",
marginInlineEnd: 'space.050'
});
export const PixelEntryComponentNext = ({
maxWidth,
formatMessage,
handleFieldChange,
computedWidth,
computedHeight,
handleCloseAndSave,
isViewMode
}) => {
const widthInputRef = useRef(null);
useEffect(() => {
if (widthInputRef.current) {
if (isViewMode) {
widthInputRef.current.blur();
} else {
widthInputRef.current.focus();
}
}
}, [isViewMode, widthInputRef]);
const handleKeyDown = useCallback(event => {
if (event.key === 'Enter') {
const shouldSetFocus = true;
handleCloseAndSave({
inputWidth: computedWidth,
inputHeight: computedHeight
}, shouldSetFocus);
}
}, [computedWidth, computedHeight, handleCloseAndSave]);
const handleCloseButtonKeyDown = useCallback(event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const shouldSetFocus = true;
handleCloseAndSave({
inputWidth: computedWidth,
inputHeight: computedHeight
}, shouldSetFocus);
}
}, [computedWidth, computedHeight, handleCloseAndSave]);
return (
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
jsx(Box, {
xcss: [pixelEntryWrapperStyles, isViewMode && pixelEntryWrapperViewModeStyles]
}, jsx(Inline, {
alignBlock: "center",
spread: "space-between"
}, jsx(Box, {
paddingInlineStart: "space.100"
}, jsx(Text, {
color: "color.text.subtlest"
}, formatMessage(messages.inputWidthLabel))), jsx("div", {
css: fieldStyles
}, jsx(Tooltip, {
hideTooltipOnMouseDown: true,
hideTooltipOnClick: true,
position: "top",
content: formatMessage(messages.inputWidthTooltip, {
maxWidth
})
}, jsx(Textfield, {
name: "inputWidth",
value: computedWidth,
ref: widthInputRef,
height: "var(--ds-space-250, 20px)",
appearance: "none",
pattern: "\\d*",
"aria-label": formatMessage(messages.inputWidthAriaLabel, {
maxWidth
}),
onChange: handleFieldChange('inputWidth'),
onKeyDown: handleKeyDown
}))), jsx(Box, {
paddingInlineStart: "space.100"
}, jsx(Text, {
color: "color.text.subtlest"
}, formatMessage(messages.inputHeightTooltip))), jsx("div", {
css: fieldStyles
}, jsx(Tooltip, {
hideTooltipOnMouseDown: true,
hideTooltipOnClick: true,
content: formatMessage(messages.inputHeightTooltip),
position: "top"
}, jsx(Textfield, {
name: "inputHeight",
value: computedHeight,
height: "var(--ds-space-250, 20px)",
appearance: "none",
pattern: "\\d*",
"aria-label": formatMessage(messages.inputHeightAriaLabel),
onChange: handleFieldChange('inputHeight'),
onKeyDown: handleKeyDown
}))), !isViewMode && jsx(Fragment, null, jsx(Box, {
xcss: dividerStyles
}), jsx(IconButton
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
, {
icon: () => jsx(CrossIcon, {
label: "",
color: "var(--ds-icon-subtlest, #6B6E76)"
}),
label: formatMessage(messages.closePixelEntry),
appearance: "subtle"
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onClick: () => {
handleCloseAndSave({
inputWidth: computedWidth,
inputHeight: computedHeight
});
},
onKeyDown: handleCloseButtonKeyDown
}))))
);
};