import React, { memo, useState, useCallback, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import styled, { css } from 'styled-components';
import { QuickInsertItem } from '@atlaskit/editor-common/provider-factory';
import {
withAnalyticsContext,
withAnalyticsEvents,
WithAnalyticsEventsProps,
} from '@atlaskit/analytics-next';
import {
fireAnalyticsEvent,
EVENT_TYPE,
ACTION_SUBJECT,
ACTION,
} from '../../../plugins/analytics';
import ElementList from './ElementList/ElementList';
import CategoryList from './CategoryList';
import ElementSearch from './ElementSearch';
import {
DEVICE_BREAKPOINT_NUMBERS,
GRID_SIZE,
INLINE_SIDEBAR_HEIGHT,
SIDEBAR_HEADING_PADDING_LEFT,
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 { Category, Modes, SelectedItemProps } from '../types';
export type StatelessElementBrowserProps = {
categories?: Category[];
items: QuickInsertItem[];
onSearch: (searchTerm: string) => void;
onSelectCategory: (category: Category) => void;
onSelectItem?: (item: QuickInsertItem) => void;
onInsertItem: (item: QuickInsertItem) => void;
selectedCategory?: string;
showSearch: boolean;
showCategories: boolean;
mode: keyof typeof Modes;
searchTerm?: string;
} & WithAnalyticsEventsProps;
function StatelessElementBrowser(props: StatelessElementBrowserProps) {
const { items, onSelectItem } = props;
const { containerWidth, ContainerWidthMonitor } = useContainerWidth();
const [columnCount, setColumnCount] = useState(1);
const {
selectedItemIndex,
focusedItemIndex,
setFocusedItemIndex,
focusOnSearch,
onKeyDown,
setFocusOnSearch,
} = useSelectAndFocusOnArrowNavigation(items.length - 1, columnCount);
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 onItemsEnterKeyPress = useCallback(
(e: React.KeyboardEvent) => {
if (e.key !== 'Enter') {
return;
}
props.onInsertItem(items[selectedItemIndex]);
},
[props, items, selectedItemIndex],
);
/**
* On arrow key selection and clicks the selectedItemIndex will change.
* Making sure to update parent component.
*/
const selectedItem = items[selectedItemIndex];
useEffect(() => {
if (onSelectItem && selectedItem != null) {
onSelectItem(selectedItem);
}
}, [onSelectItem, selectedItem]);
return (
{containerWidth < DEVICE_BREAKPOINT_NUMBERS.medium ? (
) : (
)}
);
}
const Wrapper = styled.div`
width: 100%;
max-height: inherit;
overflow: hidden;
`;
Wrapper.displayName = 'Wrapper';
function MobileBrowser({
showCategories,
showSearch,
onSearch,
mode,
categories,
onSelectCategory,
items,
onInsertItem,
selectedCategory,
selectedItemIndex,
focusedItemIndex,
setFocusedItemIndex,
focusOnSearch,
setColumnCount,
setFocusOnSearch,
onKeyPress,
onKeyDown,
searchTerm,
createAnalyticsEvent,
}: StatelessElementBrowserProps &
SelectedItemProps & {
focusOnSearch: boolean;
setFocusOnSearch: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
onKeyDown: (e: React.KeyboardEvent) => void;
setFocusedItemIndex: (index: number) => void;
setColumnCount: (columnCount: number) => void;
}) {
return (
{showSearch && (
)}
{showCategories && (
)}
);
}
function DesktopBrowser({
showCategories,
showSearch,
onSearch,
mode,
categories,
onSelectCategory,
items,
onInsertItem,
selectedCategory,
selectedItemIndex,
focusedItemIndex,
setFocusedItemIndex,
focusOnSearch,
setColumnCount,
setFocusOnSearch,
onKeyPress,
onKeyDown,
searchTerm,
createAnalyticsEvent,
}: StatelessElementBrowserProps &
SelectedItemProps & {
focusOnSearch: boolean;
setFocusOnSearch: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
onKeyDown: (e: React.KeyboardEvent) => void;
setFocusedItemIndex: (index: number) => void;
setColumnCount: (columnCount: number) => void;
}) {
return (
{showCategories && (
)}
{showSearch && (
)}
);
}
const baseBrowserContainerStyles = css`
display: flex;
height: 100%;
/** Needed for Safari to work with current css.
* 100% heights wont work and
* will default to auto if one of the containers doesn't have a specified height in pixels.
* Setting the min-height to fill available fits safari's needs and the above 100% height works on the rest of the browsers.
*/
min-height: -webkit-fill-available;
`;
const MobileElementBrowserContainer = styled.div`
${baseBrowserContainerStyles};
flex-direction: column;
`;
MobileElementBrowserContainer.displayName = 'MobileElementBrowserContainer';
const ElementBrowserContainer = styled.div`
${baseBrowserContainerStyles};
flex-direction: row;
`;
type SideBarType = {
showCategories: boolean;
};
const baseSidebarStyles = css`
display: flex;
flex-direction: column;
overflow-x: auto;
overflow-y: hidden;
`;
const MobileSideBar = styled.div`
${baseSidebarStyles};
flex: 0 0
${({ showCategories }: SideBarType) =>
showCategories ? 'auto' : INLINE_SIDEBAR_HEIGHT};
padding: 12px 12px 0 12px;
`;
const SideBar = styled.div`
${baseSidebarStyles};
flex: 0 0
${({ showCategories }: SideBarType) =>
showCategories ? SIDEBAR_WIDTH : 'auto'};
`;
const SidebarHeading = styled.h2`
flex: 0 0 ${SIDEBAR_HEADING_WRAPPER_HEIGHT};
display: inline-flex;
align-items: center;
padding-left: ${SIDEBAR_HEADING_PADDING_LEFT};
font-weight: 700;
`;
/**
* In enzyme styled components show up as styled.element
* and if we don't wanna export SidebarHeading just for testing.
* https://github.com/styled-components/styled-components/issues/896
*/
SidebarHeading.displayName = 'SidebarHeading';
const MobileMainContent = styled.div`
flex: 1 1 auto;
display: flex;
flex-direction: column;
overflow-y: auto;
height: 100%;
`;
const MainContent = styled(MobileMainContent)`
margin-left: ${GRID_SIZE * 2}px;
// Needed for safari
height: auto;
`;
MainContent.displayName = 'MainContent';
const SearchContainer = styled.div`
padding-bottom: ${GRID_SIZE * 2}px;
`;
const MobileCategoryListWrapper = styled.nav`
display: flex;
overflow-x: auto;
padding: ${GRID_SIZE}px 0 ${GRID_SIZE * 2}px 0;
min-height: ${GRID_SIZE * 4}px;
overflow: -moz-scrollbars-none;
::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
-ms-overflow-style: none;
`;
const CategoryListWrapper = styled(MobileCategoryListWrapper)`
padding: 0;
margin-top: ${GRID_SIZE * 3}px;
flex-direction: column;
`;
const MemoizedElementBrowser = memo(
withAnalyticsContext({
source: 'ElementBrowser',
})(withAnalyticsEvents()(StatelessElementBrowser)),
);
export default MemoizedElementBrowser;