@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
153 lines (149 loc) • 6.98 kB
JavaScript
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React, { 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 rafSchedule from 'raf-schd';
import { messages } from '@atlaskit/editor-common/floating-toolbar';
import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
import ChevronLeftLargeIcon from '@atlaskit/icon/core/chevron-left';
import ChevronRightLargeIcon from '@atlaskit/icon/core/chevron-right';
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
const toolbarScrollButtons = css({
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gridGap: "var(--ds-space-050, 4px)",
padding: `${"var(--ds-space-050, 4px)"} ${"var(--ds-space-100, 8px)"}`,
borderLeft: `solid ${"var(--ds-border, #0B120E24)"} ${"var(--ds-border-width, 1px)"}`,
flexShrink: 0,
alignItems: 'center'
});
const LeftIcon = ChevronLeftLargeIcon;
const RightIcon = ChevronRightLargeIcon;
// Remove this component (replaced by ScrollButton) as part of platform_editor_controls clean up
export const ScrollButtons = ({
intl,
scrollContainerRef,
node,
disabled,
areAnyNewToolbarFlagsEnabled
}) => {
const buttonsContainerRef = useRef(null);
const [needScroll, setNeedScroll] = useState(false);
const [canScrollLeft, setCanScrollLeft] = useState(true);
const [canScrollRight, setCanScrollRight] = useState(true);
const setCanScrollDebounced = rafSchedule(() => {
// Refs are null before mounting and after unmount
if (!scrollContainerRef.current) {
return;
}
const {
scrollLeft,
scrollWidth,
offsetWidth
} = scrollContainerRef.current;
setCanScrollLeft(scrollLeft > 0);
setCanScrollRight(scrollLeft + offsetWidth < scrollWidth - 1); // -1 to account for half pixel
});
const onScroll = () => setCanScrollDebounced();
const scrollLeft = () => {
var _scrollContainerRef$c, _scrollContainerRef$c2, _scrollContainerRef$c3;
const {
width: scrollContainerWidth = 0
} = ((_scrollContainerRef$c = scrollContainerRef.current) === null || _scrollContainerRef$c === void 0 ? void 0 : _scrollContainerRef$c.getBoundingClientRect()) || {};
const scrollLeft = ((_scrollContainerRef$c2 = scrollContainerRef.current) === null || _scrollContainerRef$c2 === void 0 ? void 0 : _scrollContainerRef$c2.scrollLeft) || 0;
// scroll to current position - scroll container width
const scrollTo = scrollLeft - scrollContainerWidth;
(_scrollContainerRef$c3 = scrollContainerRef.current) === null || _scrollContainerRef$c3 === void 0 ? void 0 : _scrollContainerRef$c3.scrollTo({
top: 0,
left: scrollTo,
behavior: 'smooth'
});
};
const scrollRight = () => {
var _scrollContainerRef$c4, _scrollContainerRef$c5, _scrollContainerRef$c6;
const {
width: scrollContainerWidth = 0
} = ((_scrollContainerRef$c4 = scrollContainerRef.current) === null || _scrollContainerRef$c4 === void 0 ? void 0 : _scrollContainerRef$c4.getBoundingClientRect()) || {};
const scrollLeft = ((_scrollContainerRef$c5 = scrollContainerRef.current) === null || _scrollContainerRef$c5 === void 0 ? void 0 : _scrollContainerRef$c5.scrollLeft) || 0;
// scroll to current position + scroll container width
const scrollTo = scrollLeft + scrollContainerWidth;
(_scrollContainerRef$c6 = scrollContainerRef.current) === null || _scrollContainerRef$c6 === void 0 ? void 0 : _scrollContainerRef$c6.scrollTo({
top: 0,
left: scrollTo,
behavior: 'smooth'
});
};
const resizeObserver = new ResizeObserver(t => {
var _scrollContainerRef$c7, _scrollContainerRef$c8, _scrollContainerRef$c9;
const widthNeededToShowAllItems = ((_scrollContainerRef$c7 = scrollContainerRef.current) === null || _scrollContainerRef$c7 === void 0 ? void 0 : _scrollContainerRef$c7.scrollWidth) || 0;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const availableSpace = (_scrollContainerRef$c8 = scrollContainerRef.current) === null || _scrollContainerRef$c8 === void 0 ? void 0 : (_scrollContainerRef$c9 = _scrollContainerRef$c8.parentNode) === null || _scrollContainerRef$c9 === void 0 ? void 0 : _scrollContainerRef$c9.offsetWidth;
if (availableSpace >= widthNeededToShowAllItems) {
setNeedScroll(false);
} else {
setNeedScroll(true);
onScroll();
}
});
useEffect(() => {
onScroll();
const scrollContainerRefCurrent = scrollContainerRef.current;
if (scrollContainerRefCurrent) {
// enable/disable scroll buttons depending on scroll position
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
scrollContainerRefCurrent.addEventListener('scroll', onScroll);
// watch for toolbar resize and show/hide scroll buttons if needed
resizeObserver.observe(scrollContainerRefCurrent);
}
return () => {
if (scrollContainerRefCurrent) {
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
scrollContainerRefCurrent.removeEventListener('scroll', onScroll);
resizeObserver.unobserve(scrollContainerRefCurrent);
}
setCanScrollDebounced.cancel();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const scrollContainerRefCurrent = scrollContainerRef.current;
if (scrollContainerRefCurrent) {
var _scrollContainerRefCu;
// reset scroll position when switching from one node with toolbar to another
// scroll to made optional as it may not be rendered in testing env
(_scrollContainerRefCu = scrollContainerRefCurrent.scrollTo) === null || _scrollContainerRefCu === void 0 ? void 0 : _scrollContainerRefCu.call(scrollContainerRefCurrent, {
left: 0
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [node.type]);
return needScroll ? jsx("div", {
ref: buttonsContainerRef,
css: toolbarScrollButtons
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: "scroll-buttons"
}, jsx(Button, {
title: intl.formatMessage(messages.floatingToolbarScrollLeft),
icon: jsx(LeftIcon, {
label: intl.formatMessage(messages.floatingToolbarScrollLeft),
size: "small"
}),
onClick: scrollLeft,
disabled: !canScrollLeft || disabled,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}), jsx(Button, {
title: intl.formatMessage(messages.floatingToolbarScrollRight),
icon: jsx(RightIcon, {
label: intl.formatMessage(messages.floatingToolbarScrollRight),
size: "small"
}),
onClick: scrollRight,
disabled: !canScrollRight || disabled,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
})) : null;
};