UNPKG

@atlaskit/editor-common

Version:

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

487 lines (477 loc) • 18 kB
import _extends from "@babel/runtime/helpers/extends"; /** * @jsxRuntime classic * @jsx jsx */ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766 import { css, jsx } from '@emotion/react'; import { FormattedMessage } from 'react-intl'; import withAnalyticsContext from '@atlaskit/analytics-next/withAnalyticsContext'; import withAnalyticsEvents from '@atlaskit/analytics-next/withAnalyticsEvents'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE, fireAnalyticsEvent } from '../../analytics'; import editorUGCToken from '../../ugc-tokens/get-editor-ugc-token'; import { ViewMore } from '../components/ViewMore'; import { DEVICE_BREAKPOINT_NUMBERS, ELEMENT_BROWSER_ID, ELEMENT_BROWSER_LIST_ID, GRID_SIZE, INLINE_SIDEBAR_HEIGHT, SIDEBAR_HEADING_WRAPPER_HEIGHT, SIDEBAR_WIDTH } from '../constants'; import useContainerWidth from '../hooks/use-container-width'; import useSelectAndFocusOnArrowNavigation from '../hooks/use-select-and-focus-on-arrow-navigation'; import CategoryList from './CategoryList'; import ElementList from './ElementList/ElementList'; import ElementSearch from './ElementSearch'; const wrapper = css({ width: '100%', maxHeight: 'inherit', overflow: 'hidden' }); const baseBrowserContainerStyles = css({ display: 'flex', height: '100%', minHeight: '-webkit-fill-available' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const mobileElementBrowserContainer = css(baseBrowserContainerStyles, { flexDirection: 'column' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const elementBrowserContainer = css(baseBrowserContainerStyles, { flexDirection: 'row' }); const baseSidebarStyles = css({ display: 'flex', flexDirection: 'column', overflowX: 'auto', overflowY: 'hidden' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const mobileSideBar = css(baseSidebarStyles, { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766 flex: `0 0 ${INLINE_SIDEBAR_HEIGHT}`, padding: `${"var(--ds-space-150, 12px)"} ${"var(--ds-space-150, 12px)"} 0 ${"var(--ds-space-150, 12px)"}` }); const mobileSideBarShowCategories = css({ flex: '0 0 auto' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const sideBar = css(baseSidebarStyles, { flex: "0 0 'auto'" }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const sideBarShowCategories = css(baseSidebarStyles, { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766 flex: `0 0 ${SIDEBAR_WIDTH}` }); const sidebarHeading = css({ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766 flex: `0 0 ${SIDEBAR_HEADING_WRAPPER_HEIGHT}`, display: 'inline-flex', alignItems: 'center', paddingLeft: "var(--ds-space-150, 12px)", // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values font: editorUGCToken('editor.font.heading.h1') }); const mobileMainContent = css({ flex: '1 1 auto', display: 'flex', flexDirection: 'column', overflowY: 'auto', height: '100%' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const mainContent = css(mobileMainContent, { marginLeft: "var(--ds-space-200, 16px)", height: 'auto' }); const searchContainer = css({ paddingBottom: "var(--ds-space-200, 16px)" }); const mobileCategoryListWrapper = css({ display: 'flex', overflowX: 'auto', padding: `${"var(--ds-space-200, 16px)"} 0 ${"var(--ds-space-200, 16px)"} 0`, // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766 minHeight: `${GRID_SIZE * 4}px`, overflow: '-moz-scrollbars-none', // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766 '&::-webkit-scrollbar': { display: 'none' }, scrollbarWidth: 'none', MsOverflowStyle: 'none' }); // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 const categoryListWrapper = css(mobileCategoryListWrapper, { padding: 0, marginTop: "var(--ds-space-200, 16px)", flexDirection: 'column' }); function StatelessElementBrowser(props) { var _props$categories; const { items, onSelectItem, onInsertItem, onViewMore, selectedCategory, onSelectCategory, searchTerm, showCategories, cache, autoFocusSearch = true } = props; const { containerWidth, ContainerWidthMonitor } = useContainerWidth(); const categoryBeenChosen = useRef(false); const [columnCount, setColumnCount] = useState(1); let isFocusSearch; const selectedCategoryIndex = (_props$categories = props.categories) === null || _props$categories === void 0 ? void 0 : _props$categories.findIndex(category => { return category.name === selectedCategory; }); const [canFocusSearch, setCanFocusSearch] = useState(autoFocusSearch); if (showCategories) { const isEmptySearchTerm = !searchTerm || (searchTerm === null || searchTerm === void 0 ? void 0 : searchTerm.length) === 0; if (!isEmptySearchTerm) { // clear the flag if the search happens after a user has chosen the category categoryBeenChosen.current = false; } // A11Y: if categories exists and search can be focused, on the initial render it should receive focus. // After user pick some category the category should stay focused. isFocusSearch = canFocusSearch && (!categoryBeenChosen.current || !isEmptySearchTerm); } const itemIsDisabled = useCallback(index => { var _items$index$isDisabl, _items$index; return (_items$index$isDisabl = (_items$index = items[index]) === null || _items$index === void 0 ? void 0 : _items$index.isDisabled) !== null && _items$index$isDisabl !== void 0 ? _items$index$isDisabl : false; }, [items]); const { selectedItemIndex, focusedItemIndex, setFocusedItemIndex, setFocusedCategoryIndex, focusOnEmptyStateButton, focusedCategoryIndex, focusOnSearch, focusOnViewMore, onKeyDown, setFocusOnSearch } = useSelectAndFocusOnArrowNavigation(items.length - 1, columnCount, !!onViewMore, itemIsDisabled, isFocusSearch, autoFocusSearch); useEffect(() => { fireAnalyticsEvent(props.createAnalyticsEvent)({ payload: { action: ACTION.OPENED, actionSubject: ACTION_SUBJECT.ELEMENT_BROWSER, eventType: EVENT_TYPE.UI, attributes: { mode: props.mode } } }); return () => { fireAnalyticsEvent(props.createAnalyticsEvent)({ payload: { action: ACTION.CLOSED, actionSubject: ACTION_SUBJECT.ELEMENT_BROWSER, eventType: EVENT_TYPE.UI, attributes: { mode: props.mode } } }); }; }, [props.createAnalyticsEvent, props.mode]); /* Only for hitting enter to select item when focused on search bar, * The actual enter key press is handled on individual items level. */ const selectedItem = selectedItemIndex !== undefined ? items[selectedItemIndex] : null; const onItemsEnterTabKeyPress = useCallback(e => { var _selectedItem$isDisab; if (e.key !== 'Enter' && (e.key !== 'Tab' || !showCategories)) { return; } if (showCategories && e.key === 'Tab' && selectedCategoryIndex !== undefined) { // A11Y: Set focus on first category if tab pressed on search setFocusedCategoryIndex(selectedCategoryIndex); e.preventDefault(); return; } if (onInsertItem && selectedItem != null && !((_selectedItem$isDisab = selectedItem.isDisabled) !== null && _selectedItem$isDisab !== void 0 ? _selectedItem$isDisab : false)) { onInsertItem(selectedItem); } e.preventDefault(); }, [onInsertItem, selectedItem, setFocusedCategoryIndex, showCategories, selectedCategoryIndex]); /** * On arrow key selection and clicks the selectedItemIndex will change. * Making sure to update parent component. */ useEffect(() => { if (onSelectItem && selectedItem != null) { onSelectItem(selectedItem); } }, [onSelectItem, selectedItem]); const onSelectCategoryCB = useCallback(category => { onSelectCategory(category); categoryBeenChosen.current = true; }, [categoryBeenChosen, onSelectCategory]); const handleKeyPress = e => { if (e.key === 'Tab') { // only Tab key can change focus from close button (if present) setCanFocusSearch(true); } }; const handleClick = () => { setCanFocusSearch(true); }; const browserContent = jsx(React.Fragment, null, jsx(ContainerWidthMonitor, null), containerWidth < DEVICE_BREAKPOINT_NUMBERS.medium ? jsx(MobileBrowser // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, props, { selectedItemIndex: selectedItemIndex, focusOnEmptyStateButton: focusOnEmptyStateButton, focusedItemIndex: focusedItemIndex, setFocusedItemIndex: setFocusedItemIndex, focusedCategoryIndex: focusedCategoryIndex, setFocusedCategoryIndex: setFocusedCategoryIndex, focusOnSearch: focusOnSearch, columnCount: columnCount, setColumnCount: setColumnCount, setFocusOnSearch: setFocusOnSearch, onKeyPress: onItemsEnterTabKeyPress, onKeyDown: onKeyDown, onViewMore: onViewMore, focusOnViewMore: focusOnViewMore, cache: cache })) : jsx(DesktopBrowser // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, props, { selectedItemIndex: selectedItemIndex, focusOnEmptyStateButton: focusOnEmptyStateButton, focusedItemIndex: focusedItemIndex, setFocusedItemIndex: setFocusedItemIndex, focusOnSearch: focusOnSearch, setColumnCount: setColumnCount, columnCount: columnCount, setFocusOnSearch: setFocusOnSearch, onKeyPress: onItemsEnterTabKeyPress, onKeyDown: onKeyDown, focusedCategoryIndex: focusedCategoryIndex, setFocusedCategoryIndex: setFocusedCategoryIndex, selectedCategoryIndex: selectedCategoryIndex, onSelectCategory: onSelectCategoryCB, cache: cache }))); if (expValEquals('editor_a11y__enghealth-46814_fy26', 'isEnabled', true)) { return jsx("div", { css: wrapper, "data-testid": "element-browser", id: ELEMENT_BROWSER_ID, role: "none", onKeyUp: canFocusSearch ? undefined : handleKeyPress, onClick: canFocusSearch ? undefined : handleClick }, browserContent); } return ( // eslint-disable-next-line @atlassian/a11y/interactive-element-not-keyboard-focusable jsx("div", { css: wrapper, "data-testid": "element-browser", id: ELEMENT_BROWSER_ID, onKeyUp: canFocusSearch ? undefined : handleKeyPress, onClick: canFocusSearch ? undefined : handleClick }, browserContent) ); } function MobileBrowser({ showCategories, showSearch, onSearch, mode, categories, onSelectCategory, items, onInsertItem, selectedCategory, selectedItemIndex, focusedItemIndex, setFocusedItemIndex, focusedCategoryIndex, setFocusedCategoryIndex, focusOnSearch, focusOnViewMore, columnCount, setColumnCount, setFocusOnSearch, onKeyPress, onKeyDown, searchTerm, createAnalyticsEvent, emptyStateHandler, onViewMore, cache, focusOnEmptyStateButton = false }) { return jsx("div", { css: mobileElementBrowserContainer, role: expValEquals('editor_a11y__enghealth-46814_fy26', 'isEnabled', true) ? 'none' : undefined, onKeyDown: onKeyDown, "data-testid": "mobile__element-browser" }, jsx("div", { css: showCategories ? [mobileSideBar, mobileSideBarShowCategories] : mobileSideBar }, showSearch && jsx(ElementSearch, { onSearch: onSearch, onKeyDown: onKeyPress, mode: mode, focus: focusOnSearch, onClick: setFocusOnSearch, searchTerm: searchTerm, items: items, selectedItemIndex: selectedItemIndex, ariaControlsId: expValEquals('platform_editor_august_a11y', 'isEnabled', true) ? ELEMENT_BROWSER_LIST_ID : undefined }), showCategories && jsx("nav", { css: mobileCategoryListWrapper, tabIndex: -1 }, jsx(CategoryList, { categories: categories, onSelectCategory: onSelectCategory, selectedCategory: selectedCategory, focusedCategoryIndex: focusedCategoryIndex, setFocusedCategoryIndex: setFocusedCategoryIndex, setFocusedItemIndex: setFocusedItemIndex, setFocusOnSearch: setFocusOnSearch }))), jsx("div", { css: mobileMainContent, id: expValEquals('platform_editor_august_a11y', 'isEnabled', true) ? ELEMENT_BROWSER_LIST_ID : undefined }, jsx(ElementList, { items: items, mode: mode, onInsertItem: onInsertItem, selectedItemIndex: selectedItemIndex, focusedItemIndex: focusedItemIndex, focusOnEmptyStateButton: focusOnEmptyStateButton, setFocusedItemIndex: setFocusedItemIndex, columnCount: columnCount, setColumnCount: setColumnCount, createAnalyticsEvent: createAnalyticsEvent, emptyStateHandler: emptyStateHandler, selectedCategory: selectedCategory, searchTerm: searchTerm, cache: cache, hasTabListContext: false })), onViewMore && jsx(ViewMore, { onViewMore: onViewMore, focus: focusOnViewMore })); } function DesktopBrowser({ showCategories, showSearch, onSearch, mode, categories, onSelectCategory, items, onInsertItem, selectedCategory, selectedItemIndex, focusedItemIndex, setFocusedItemIndex, focusedCategoryIndex, setFocusedCategoryIndex, selectedCategoryIndex, focusOnSearch, columnCount, setColumnCount, setFocusOnSearch, onKeyPress, onKeyDown, searchTerm, createAnalyticsEvent, emptyStateHandler, cache, focusOnEmptyStateButton = false }) { return jsx("div", { css: elementBrowserContainer, "data-testid": "desktop__element-browser" }, showCategories && jsx("div", { css: showCategories ? sideBarShowCategories : sideBar }, jsx("div", { css: sidebarHeading, "data-testid": "sidebar-heading", id: "sidebar-heading" }, jsx(FormattedMessage, { id: "fabric.editor.elementbrowser.sidebar.heading", defaultMessage: "Browse", description: "Sidebar heading" })), expValEquals('editor_a11y__enghealth-46814_fy26', 'isEnabled', true) ? jsx("div", { role: "tablist", "aria-labelledby": "sidebar-heading", css: categoryListWrapper }, jsx(CategoryList, { categories: categories, onSelectCategory: onSelectCategory, selectedCategory: selectedCategory, createAnalyticsEvent: createAnalyticsEvent, focusedCategoryIndex: focusedCategoryIndex, setFocusedCategoryIndex: setFocusedCategoryIndex, setFocusedItemIndex: setFocusedItemIndex, setFocusOnSearch: setFocusOnSearch })) : // eslint-disable-next-line @atlassian/a11y/no-noninteractive-element-to-interactive-role jsx("nav", { role: "tablist", "aria-labelledby": "sidebar-heading", css: categoryListWrapper }, jsx(CategoryList, { categories: categories, onSelectCategory: onSelectCategory, selectedCategory: selectedCategory, createAnalyticsEvent: createAnalyticsEvent, focusedCategoryIndex: focusedCategoryIndex, setFocusedCategoryIndex: setFocusedCategoryIndex, setFocusedItemIndex: setFocusedItemIndex, setFocusOnSearch: setFocusOnSearch }))), jsx("div", { css: mainContent, role: expValEquals('editor_a11y__enghealth-46814_fy26', 'isEnabled', true) ? 'none' : undefined, onKeyDown: onKeyDown, "data-testid": "main-content" }, showSearch && // eslint-disable-next-line jsx("div", { css: searchContainer }, jsx(ElementSearch, { onSearch: onSearch, onKeyDown: onKeyPress, mode: mode, focus: focusOnSearch, onClick: setFocusOnSearch, searchTerm: searchTerm, items: items, selectedItemIndex: selectedItemIndex, ariaControlsId: selectedCategory ? `browse-category-${selectedCategory}-tab` : 'browse-category-tab' })), jsx(ElementList, { items: items, mode: mode, onInsertItem: onInsertItem, selectedItemIndex: selectedItemIndex, focusedItemIndex: focusedItemIndex, focusOnEmptyStateButton: focusOnEmptyStateButton, setFocusedItemIndex: setFocusedItemIndex, columnCount: columnCount, setColumnCount: setColumnCount, createAnalyticsEvent: createAnalyticsEvent, emptyStateHandler: emptyStateHandler, selectedCategory: selectedCategory, selectedCategoryIndex: selectedCategoryIndex, searchTerm: searchTerm, setFocusedCategoryIndex: showCategories ? setFocusedCategoryIndex : undefined, cache: cache, hasTabListContext: showCategories }))); } const MemoizedElementBrowser = /*#__PURE__*/memo(withAnalyticsContext({ source: 'ElementBrowser' })(withAnalyticsEvents()(StatelessElementBrowser))); export default MemoizedElementBrowser;