UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

323 lines (320 loc) • 10.9 kB
import _extends from "@babel/runtime/helpers/extends"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; var _excluded = ["items", "mode", "selectedItemIndex", "focusedItemIndex", "setColumnCount", "createAnalyticsEvent", "emptyStateHandler", "selectedCategory", "searchTerm"]; /** @jsx jsx */ import React, { Fragment, memo, useCallback, useEffect, useMemo, useState } from 'react'; import { css, jsx } from '@emotion/react'; import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; import { Collection } from 'react-virtualized/dist/commonjs/Collection'; import { withAnalyticsContext } from '@atlaskit/analytics-next'; import { relativeFontSizeToBase16 } from '@atlaskit/editor-shared-styles'; import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut'; import { ButtonItem } from '@atlaskit/menu'; import { B100, N200 } from '@atlaskit/theme/colors'; import { borderRadius } from '@atlaskit/theme/constants'; import Tooltip from '@atlaskit/tooltip'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE, fireAnalyticsEvent } from '../../../analytics'; import { IconFallback } from '../../../quick-insert'; import { ELEMENT_LIST_PADDING, SCROLLBAR_WIDTH } from '../../constants'; import useContainerWidth from '../../hooks/use-container-width'; import useFocus from '../../hooks/use-focus'; import { Modes } from '../../types'; import cellSizeAndPositionGetter from './cellSizeAndPositionGetter'; import EmptyState from './EmptyState'; import { getColumnCount, getScrollbarWidth } from './utils'; export var ICON_HEIGHT = 40; export var ICON_WIDTH = 40; export var itemIcon = css({ width: "".concat(ICON_WIDTH, "px"), height: "".concat(ICON_HEIGHT, "px"), overflow: 'hidden', border: "1px solid ".concat("var(--ds-border, rgba(223, 225, 229, 0.5))"), borderRadius: "".concat(borderRadius(), "px"), boxSizing: 'border-box', display: 'flex', justifyContent: 'center', alignItems: 'center', div: { width: "".concat(ICON_WIDTH, "px"), height: "".concat(ICON_HEIGHT, "px") } }); function ElementList(_ref) { var items = _ref.items, mode = _ref.mode, selectedItemIndex = _ref.selectedItemIndex, focusedItemIndex = _ref.focusedItemIndex, setColumnCount = _ref.setColumnCount, createAnalyticsEvent = _ref.createAnalyticsEvent, emptyStateHandler = _ref.emptyStateHandler, selectedCategory = _ref.selectedCategory, searchTerm = _ref.searchTerm, props = _objectWithoutProperties(_ref, _excluded); var _useContainerWidth = useContainerWidth(), containerWidth = _useContainerWidth.containerWidth, ContainerWidthMonitor = _useContainerWidth.ContainerWidthMonitor; var _useState = useState(SCROLLBAR_WIDTH), _useState2 = _slicedToArray(_useState, 2), scrollbarWidth = _useState2[0], setScrollbarWidth = _useState2[1]; var fullMode = mode === Modes.full; useEffect(function () { /** * More of an optimization condition. * Initially the containerWidths are reported 0 twice. **/ if (fullMode && containerWidth > 0) { setColumnCount(getColumnCount(containerWidth)); var updatedScrollbarWidth = getScrollbarWidth(); if (updatedScrollbarWidth > 0) { setScrollbarWidth(updatedScrollbarWidth); } } }, [fullMode, containerWidth, setColumnCount, scrollbarWidth]); var onExternalLinkClick = useCallback(function () { fireAnalyticsEvent(createAnalyticsEvent)({ payload: { action: ACTION.VISITED, actionSubject: ACTION_SUBJECT.SMART_LINK, eventType: EVENT_TYPE.TRACK } }); }, [createAnalyticsEvent]); var cellRenderer = useMemo(function () { return function (_ref2) { var index = _ref2.index, key = _ref2.key, style = _ref2.style; if (items[index] == null) { return; } return jsx("div", { style: style, key: key, className: "element-item-wrapper", css: elementItemWrapper }, jsx(MemoizedElementItem, _extends({ inlineMode: !fullMode, index: index, item: items[index], selected: selectedItemIndex === index, focus: focusedItemIndex === index }, props))); }; }, [items, fullMode, selectedItemIndex, focusedItemIndex, props]); return jsx(Fragment, null, jsx(ContainerWidthMonitor, null), jsx("div", { css: elementItemsWrapper, "data-testid": "element-items", id: selectedCategory ? "browse-category-".concat(selectedCategory, "-tab") : 'browse-category-tab', "aria-labelledby": selectedCategory ? "browse-category--".concat(selectedCategory, "-button") : 'browse-category-button', role: "tabpanel", tabIndex: items.length === 0 ? 0 : undefined }, !items.length ? emptyStateHandler ? emptyStateHandler({ mode: mode, selectedCategory: selectedCategory, searchTerm: searchTerm }) : jsx(EmptyState, { onExternalLinkClick: onExternalLinkClick }) : jsx(Fragment, null, containerWidth > 0 && jsx(AutoSizer, { disableWidth: true }, function (_ref3) { var height = _ref3.height; return jsx(Collection, { cellCount: items.length, cellRenderer: cellRenderer, cellSizeAndPositionGetter: cellSizeAndPositionGetter(containerWidth - ELEMENT_LIST_PADDING * 2, scrollbarWidth), height: height, width: containerWidth - ELEMENT_LIST_PADDING * 2 // containerWidth - padding on Left/Right (for focus outline) /** * Refresh Collection on WidthObserver value change. * Length of the items used to force re-render to solve Firefox bug with react-virtualized retaining * scroll position after updating the data. If new data has different number of cells, a re-render * is forced to prevent the scroll position render bug. */, key: containerWidth + items.length, scrollToCell: selectedItemIndex }); })))); } var MemoizedElementItem = /*#__PURE__*/memo(ElementItem); MemoizedElementItem.displayName = 'MemoizedElementItem'; export function ElementItem(_ref4) { var inlineMode = _ref4.inlineMode, selected = _ref4.selected, item = _ref4.item, index = _ref4.index, onInsertItem = _ref4.onInsertItem, focus = _ref4.focus, setFocusedItemIndex = _ref4.setFocusedItemIndex; var ref = useFocus(focus); /** * Note: props.onSelectItem(item) is not called here as the StatelessElementBrowser's * useEffect would trigger it on selectedItemIndex change. (Line 106-110) * This implementation was changed for keyboard/click selection to work with `onInsertItem`. */ var onClick = useCallback(function (e) { e.preventDefault(); e.stopPropagation(); setFocusedItemIndex(index); switch (e.nativeEvent.detail) { case 0: onInsertItem(item); break; case 1: if (inlineMode) { onInsertItem(item); } break; case 2: if (!inlineMode) { onInsertItem(item); } break; default: return; } }, [index, inlineMode, item, onInsertItem, setFocusedItemIndex]); var icon = item.icon, title = item.title, description = item.description, keyshortcut = item.keyshortcut; return jsx(Tooltip, { content: description, testId: "element-item-tooltip-".concat(index) }, jsx(ButtonItem, { onClick: onClick, iconBefore: jsx(ElementBefore, { icon: icon, title: title }), isSelected: selected, "aria-describedby": title, ref: ref, testId: "element-item-".concat(index), id: "searched-item-".concat(index) }, jsx(ItemContent, { style: inlineMode ? null : itemStyleOverrides, tabIndex: 0, title: title, description: description, keyshortcut: keyshortcut }))); } /** * Inline mode should use the existing Align-items:center value. */ var itemStyleOverrides = { alignItems: 'flex-start' }; var ElementBefore = /*#__PURE__*/memo(function (_ref5) { var icon = _ref5.icon, title = _ref5.title; return jsx("div", { css: [itemIcon, itemIconStyle] }, icon ? icon() : jsx(IconFallback, null)); }); var ItemContent = /*#__PURE__*/memo(function (_ref6) { var title = _ref6.title, description = _ref6.description, keyshortcut = _ref6.keyshortcut; return jsx("div", { css: itemBody, className: "item-body" }, jsx("div", { css: itemText }, jsx("div", { css: itemTitleWrapper }, jsx("p", { css: itemTitle }, title), jsx("div", { css: itemAfter }, keyshortcut && jsx("div", { css: shortcutStyle }, keyshortcut))), description && jsx("p", { css: itemDescription }, description))); }); var elementItemsWrapper = css({ flex: 1, flexFlow: 'row wrap', alignItems: 'flex-start', justifyContent: 'flex-start', overflow: 'hidden', padding: "var(--ds-space-025, 2px)", '.ReactVirtualized__Collection': { borderRadius: '3px', outline: 'none', ':focus': { boxShadow: "0 0 0 ".concat(ELEMENT_LIST_PADDING, "px ", "var(--ds-border-focused, ".concat(B100, ")")) } }, '.ReactVirtualized__Collection__innerScrollContainer': { "div[class='element-item-wrapper']:last-child": { paddingBottom: "var(--ds-space-050, 4px)" } } }); var elementItemWrapper = css({ div: { button: { height: '75px', alignItems: 'flex-start', padding: "var(--ds-space-150, 12px)".concat(" ", "var(--ds-space-150, 12px)", " 11px") } } }); var itemBody = css({ display: 'flex', flexDirection: 'row', flexWrap: 'nowrap', justifyContent: 'space-between', lineHeight: 1.4, width: '100%', marginTop: "var(--ds-space-negative-025, -2px)" }); /* * -webkit-line-clamp is also supported by firefox 🎉 * https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/68#CSS */ var multilineStyle = css({ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }); var itemDescription = css(multilineStyle, { overflow: 'hidden', fontSize: relativeFontSizeToBase16(11.67), color: "var(--ds-text-subtle, ".concat(N200, ")"), marginTop: "var(--ds-space-025, 2px)" }); var itemText = css({ width: 'inherit', whiteSpace: 'initial' }); var itemTitleWrapper = css({ display: 'flex', justifyContent: 'space-between' }); var itemTitle = css({ width: '100%', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }); var itemAfter = css({ flex: '0 0 auto', paddingTop: "var(--ds-space-025, 2px)", marginBottom: "var(--ds-space-negative-025, -2px)" }); var itemIconStyle = css({ img: { height: '40px', width: '40px', objectFit: 'cover' } }); var MemoizedElementListWithAnalytics = /*#__PURE__*/memo(withAnalyticsContext({ component: 'ElementList' })(ElementList)); export default MemoizedElementListWithAnalytics;