UNPKG

@atlaskit/editor-plugin-floating-toolbar

Version:

Floating toolbar plugin for @atlaskit/editor-core

143 lines (142 loc) 5.97 kB
import React, { useEffect, useState } from 'react'; import { bind } from 'bind-event-listener'; import rafSchedule from 'raf-schd'; import { IconButton } from '@atlaskit/button/new'; import { messages } from '@atlaskit/editor-common/floating-toolbar'; import ChevronLeftLargeIcon from '@atlaskit/icon/core/chevron-left'; import ChevronRightLargeIcon from '@atlaskit/icon/core/chevron-right'; // eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss import { Box, xcss } from '@atlaskit/primitives'; const rightSideStyles = xcss({ borderLeft: `solid ${"var(--ds-border, #0B120E24)"} 1px`, right: 'space.0', borderTopRightRadius: 'radius.small', borderBottomRightRadius: 'radius.small' }); const leftSideStyles = xcss({ borderRight: `solid ${"var(--ds-border, #0B120E24)"} 1px`, left: 'space.0', borderTopLeftRadius: 'radius.small', borderBottomLeftRadius: 'radius.small' }); const buttonCommonStyles = xcss({ backgroundColor: 'elevation.surface.overlay', zIndex: '1', position: 'absolute' }); export const ScrollButton = ({ intl, scrollContainerRef, node, disabled, side }) => { const [needScroll, setNeedScroll] = useState(false); const [canScrollToSide, setCanScrollToSide] = useState(true); const setCanScrollDebounced = rafSchedule(() => { // Refs are null before mounting and after unmount if (!scrollContainerRef.current) { return; } const { scrollLeft, scrollWidth, offsetWidth } = scrollContainerRef.current; setCanScrollToSide( // -1 to account for pixel rounding error side === 'left' ? scrollLeft > 0 : scrollLeft < scrollWidth - offsetWidth - 1); }); const onScroll = () => { setCanScrollDebounced(); }; const onClick = () => { 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; const scrollTo = side === 'left' ? scrollLeft - scrollContainerWidth : scrollLeft + scrollContainerWidth; (_scrollContainerRef$c3 = scrollContainerRef.current) === null || _scrollContainerRef$c3 === void 0 ? void 0 : _scrollContainerRef$c3.scrollTo({ top: 0, left: scrollTo, behavior: 'smooth' }); }; const resizeObserver = new ResizeObserver(_t => { var _scrollContainerRef$c4, _scrollContainerRef$c5; const widthNeededToShowAllItems = ((_scrollContainerRef$c4 = scrollContainerRef.current) === null || _scrollContainerRef$c4 === void 0 ? void 0 : _scrollContainerRef$c4.scrollWidth) || 0; const parentNode = (_scrollContainerRef$c5 = scrollContainerRef.current) === null || _scrollContainerRef$c5 === void 0 ? void 0 : _scrollContainerRef$c5.parentNode; let availableSpace = -1; if (parentNode instanceof HTMLElement) { availableSpace = parentNode.offsetWidth; } if (availableSpace === -1) { return; } if (availableSpace >= widthNeededToShowAllItems) { setNeedScroll(false); } else { setNeedScroll(true); onScroll(); } }); useEffect(() => { onScroll(); const scrollContainerRefCurrent = scrollContainerRef.current; let unbind; if (scrollContainerRefCurrent) { // Adding/removing scroll button depending on scroll position unbind = bind(scrollContainerRefCurrent, { type: 'scroll', listener: onScroll }); // watch for toolbar resize and show/hide scroll buttons if needed resizeObserver.observe(scrollContainerRefCurrent); } return () => { if (scrollContainerRefCurrent) { var _unbind; (_unbind = unbind) === null || _unbind === void 0 ? void 0 : _unbind(); 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 }); } }, [node.type, scrollContainerRef]); const Icon = side === 'left' ? ChevronLeftLargeIcon : ChevronRightLargeIcon; return needScroll && (side === 'left' && canScrollToSide || side === 'right' && canScrollToSide) && /*#__PURE__*/React.createElement(Box, { padding: "space.050" // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , xcss: [side === 'left' ? leftSideStyles : rightSideStyles, buttonCommonStyles] }, /*#__PURE__*/React.createElement(IconButton, { appearance: "subtle", label: intl.formatMessage(side === 'left' ? messages.floatingToolbarScrollLeft : messages.floatingToolbarScrollRight), onClick: onClick, isDisabled: disabled // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , icon: iconProps => /*#__PURE__*/React.createElement(Icon, { label: iconProps.label, size: "small" }), isTooltipDisabled: false // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , tooltip: { position: 'top' } })); };