mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
chore: implement new mockup to the new viz gallery (2nd iteration) (#15868)
* chore: implement new mockup to the new viz gallery * fix: update package-lock * fix: add license * fix: reduce duplication and fit within the sidebar * fix: ut
This commit is contained in:
22
superset-frontend/images/icons/ballot.svg
Normal file
22
superset-frontend/images/icons/ballot.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.66667 5H12V6.33333H8.66667V5ZM8.66667 9.66667H12V11H8.66667V9.66667ZM12.6667 2H3.33333C2.6 2 2 2.6 2 3.33333V12.6667C2 13.4 2.6 14 3.33333 14H12.6667C13.4 14 14 13.4 14 12.6667V3.33333C14 2.6 13.4 2 12.6667 2ZM12.6667 12.6667H3.33333V3.33333H12.6667V12.6667ZM7.33333 4H4V7.33333H7.33333V4ZM6.66667 6.66667H4.66667V4.66667H6.66667V6.66667ZM7.33333 8.66667H4V12H7.33333V8.66667ZM6.66667 11.3333H4.66667V9.33333H6.66667V11.3333Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
22
superset-frontend/images/icons/category.svg
Normal file
22
superset-frontend/images/icons/category.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.73967 1.7002L4.04888 7.79629H11.4305L7.73967 1.7002ZM7.73967 4.3012L9.0348 6.4416H6.43783L7.73967 4.3012ZM11.4305 9.15098C9.75954 9.15098 8.41072 10.5124 8.41072 12.199C8.41072 13.8856 9.75954 15.2471 11.4305 15.2471C13.1014 15.2471 14.4502 13.8856 14.4502 12.199C14.4502 10.5124 13.1014 9.15098 11.4305 9.15098ZM11.4305 13.8924C10.5044 13.8924 9.75283 13.1338 9.75283 12.199C9.75283 11.2643 10.5044 10.5057 11.4305 10.5057C12.3565 10.5057 13.1081 11.2643 13.1081 12.199C13.1081 13.1338 12.3565 13.8924 11.4305 13.8924ZM1.7002 14.9084H7.06862V9.48965H1.7002V14.9084ZM3.0423 10.8443H5.72651V13.5537H3.0423V10.8443Z" fill="currentColor" fill-opacity="0.85"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
22
superset-frontend/images/icons/tags.svg
Normal file
22
superset-frontend/images/icons/tags.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.1668 7.0835V5.66683H11.3335V2.8335H9.91683V5.66683H7.0835V2.8335H5.66683V5.66683H2.8335V7.0835H5.66683V9.91683H2.8335V11.3335H5.66683V14.1668H7.0835V11.3335H9.91683V14.1668H11.3335V11.3335H14.1668V9.91683H11.3335V7.0835H14.1668ZM9.91683 9.91683H7.0835V7.0835H9.91683V9.91683Z" fill="currentColor" fill-opacity="0.85"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
18
superset-frontend/package-lock.json
generated
18
superset-frontend/package-lock.json
generated
@@ -67596,7 +67596,8 @@
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.16",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz",
|
||||
"integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ=="
|
||||
"integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
@@ -87305,11 +87306,18 @@
|
||||
}
|
||||
},
|
||||
"scroll-into-view-if-needed": {
|
||||
"version": "2.2.26",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz",
|
||||
"integrity": "sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw==",
|
||||
"version": "2.2.28",
|
||||
"resolved": "https://registry.npm.taobao.org/scroll-into-view-if-needed/download/scroll-into-view-if-needed-2.2.28.tgz",
|
||||
"integrity": "sha1-WhWy9YpSZCyIyOylhGROAXA9ZFo=",
|
||||
"requires": {
|
||||
"compute-scroll-into-view": "^1.0.16"
|
||||
"compute-scroll-into-view": "^1.0.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npm.taobao.org/compute-scroll-into-view/download/compute-scroll-into-view-1.0.17.tgz?cache=0&sync_timestamp=1614042424875&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompute-scroll-into-view%2Fdownload%2Fcompute-scroll-into-view-1.0.17.tgz",
|
||||
"integrity": "sha1-aojxis2dQunPS6pr7H4FImB6t6s="
|
||||
}
|
||||
}
|
||||
},
|
||||
"seedrandom": {
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
"redux-undo": "^1.0.0-beta9-9-7",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"rison": "^0.1.1",
|
||||
"scroll-into-view-if-needed": "^2.2.28",
|
||||
"shortid": "^2.2.6",
|
||||
"urijs": "^1.19.6",
|
||||
"use-immer": "^0.4.2",
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('VizTypeControl', () => {
|
||||
const thumbnails = screen.getByTestId('viztype-selector-container');
|
||||
expect(thumbnails).toBeInTheDocument();
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search');
|
||||
const searchInput = screen.getByPlaceholderText('Search all charts');
|
||||
userEvent.type(searchInput, 'foo');
|
||||
await waitForEffects();
|
||||
|
||||
|
||||
@@ -150,6 +150,9 @@ const IconFileNames = [
|
||||
'warning_solid',
|
||||
'x-large',
|
||||
'x-small',
|
||||
'tags',
|
||||
'ballot',
|
||||
'category',
|
||||
];
|
||||
|
||||
const iconOverrides: Record<string, React.FC> = {};
|
||||
|
||||
@@ -105,7 +105,9 @@ describe('VizTypeControl', () => {
|
||||
|
||||
const visualizations = screen.getByTestId(getTestId('viz-row'));
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: 'Table' }));
|
||||
userEvent.click(
|
||||
screen.getByRole('button', { name: 'category Table close' }),
|
||||
);
|
||||
|
||||
expect(visualizations).toHaveTextContent(/Time-series Table/);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import React, {
|
||||
ChangeEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -32,11 +33,13 @@ import {
|
||||
SupersetTheme,
|
||||
useTheme,
|
||||
} from '@superset-ui/core';
|
||||
import { Input } from 'src/common/components';
|
||||
import { Collapse, Input } from 'src/common/components';
|
||||
import Label from 'src/components/Label';
|
||||
import { usePluginContext } from 'src/components/DynamicPlugins';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
|
||||
interface VizTypeGalleryProps {
|
||||
onChange: (vizType: string | null) => void;
|
||||
@@ -96,8 +99,6 @@ const DEFAULT_ORDER = [
|
||||
'country_map',
|
||||
];
|
||||
|
||||
const ALL_TAGS = [t('Highly-used'), t('Text'), t('Trend'), t('Formattable')];
|
||||
|
||||
const typesWithDefaultOrder = new Set(DEFAULT_ORDER);
|
||||
|
||||
const THUMBNAIL_GRID_UNITS = 24;
|
||||
@@ -106,16 +107,22 @@ export const MAX_ADVISABLE_VIZ_GALLERY_WIDTH = 1090;
|
||||
|
||||
const OTHER_CATEGORY = t('Other');
|
||||
|
||||
const DEFAULT_SEARCH_INPUT_VALUE = t('Highly-used');
|
||||
const ALL_CHARTS = t('All charts');
|
||||
|
||||
const RECOMMENDED_TAGS = [
|
||||
t('Highly-used'),
|
||||
t('ECharts'),
|
||||
t('Advanced-Analytics'),
|
||||
];
|
||||
|
||||
export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control';
|
||||
|
||||
const VizPickerLayout = styled.div`
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(100px, 1fr) minmax(200px, 35%);
|
||||
grid-template-columns: 1fr 5fr;
|
||||
grid-template-columns: auto 5fr;
|
||||
grid-template-areas:
|
||||
'sidebar tags'
|
||||
'sidebar search'
|
||||
'sidebar main'
|
||||
'details details';
|
||||
height: 70vh;
|
||||
@@ -135,8 +142,21 @@ const LeftPane = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
|
||||
padding: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
|
||||
.ant-collapse .ant-collapse-item {
|
||||
.ant-collapse-header {
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
padding-left: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
padding-bottom: ${({ theme }) => theme.gridUnit}px;
|
||||
}
|
||||
.ant-collapse-content .ant-collapse-content-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 ${({ theme }) => theme.gridUnit * 2}px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const RightPane = styled.div`
|
||||
@@ -144,25 +164,10 @@ const RightPane = styled.div`
|
||||
overflow-y: scroll;
|
||||
`;
|
||||
|
||||
const AllTagsWrapper = styled.div`
|
||||
${({ theme }) => `
|
||||
grid-area: tags;
|
||||
margin: ${theme.gridUnit * 4}px ${theme.gridUnit * 2}px 0;
|
||||
input {
|
||||
font-size: ${theme.typography.sizes.s};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const CategoriesWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const SearchWrapper = styled.div`
|
||||
${({ theme }) => `
|
||||
margin-bottom: ${theme.gridUnit * 2}px;
|
||||
grid-area: search;
|
||||
margin: ${theme.gridUnit * 2}px ${theme.gridUnit * 3}px;
|
||||
input {
|
||||
font-size: ${theme.typography.sizes.s};
|
||||
}
|
||||
@@ -180,21 +185,48 @@ const InputIconAlignment = styled.div`
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
`;
|
||||
|
||||
const CategoryLabel = styled.button`
|
||||
const SelectorLabel = styled.button`
|
||||
${({ theme }) => `
|
||||
all: unset; // remove default button styles
|
||||
cursor: pointer;
|
||||
padding: ${theme.gridUnit}px;
|
||||
margin: ${theme.gridUnit}px 0;
|
||||
padding: 0 ${theme.gridUnit * 6}px 0 ${theme.gridUnit}px;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
line-height: 2em;
|
||||
font-size: ${theme.typography.sizes.s};
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
outline: initial;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: ${theme.colors.secondary.light4};
|
||||
background-color: ${theme.colors.primary.dark1};
|
||||
color: ${theme.colors.primary.light5};
|
||||
|
||||
svg {
|
||||
color: ${theme.colors.primary.light5};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.cancel {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: ${theme.gridUnit * 2}px
|
||||
}
|
||||
|
||||
.cancel {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
right: ${theme.gridUnit * 2}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
@@ -362,32 +394,54 @@ const ThumbnailGallery: React.FC<ThumbnailGalleryProps> = ({
|
||||
</IconsPane>
|
||||
);
|
||||
|
||||
const CategorySelector: React.FC<{
|
||||
category: string;
|
||||
const Selector: React.FC<{
|
||||
selector: string;
|
||||
icon: JSX.Element;
|
||||
isSelected: boolean;
|
||||
onClick: (category: string) => void;
|
||||
}> = ({ category, isSelected, onClick }) => (
|
||||
<CategoryLabel
|
||||
key={category}
|
||||
name={category}
|
||||
className={isSelected ? 'selected' : ''}
|
||||
onClick={() => onClick(category)}
|
||||
>
|
||||
{category}
|
||||
</CategoryLabel>
|
||||
);
|
||||
onClick: (selector: string) => void;
|
||||
onClear: (e: React.MouseEvent) => void;
|
||||
}> = ({ selector, icon, isSelected, onClick, onClear }) => {
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const doesVizMatchCategory = (viz: ChartMetadata, category: string) =>
|
||||
category === viz.category ||
|
||||
(category === OTHER_CATEGORY && viz.category == null);
|
||||
// see Element.scrollIntoViewIfNeeded()
|
||||
// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
|
||||
useEffect(() => {
|
||||
if (isSelected) {
|
||||
// We need to wait for the modal to open and then scroll, so we put it in the microtask queue
|
||||
queueMicrotask(() =>
|
||||
scrollIntoView(btnRef.current as HTMLButtonElement, {
|
||||
behavior: 'smooth',
|
||||
scrollMode: 'if-needed',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SelectorLabel
|
||||
ref={btnRef}
|
||||
key={selector}
|
||||
name={selector}
|
||||
className={isSelected ? 'selected' : ''}
|
||||
onClick={() => onClick(selector)}
|
||||
>
|
||||
{icon}
|
||||
{selector}
|
||||
<CloseOutlined className="cancel" onClick={onClear} />
|
||||
</SelectorLabel>
|
||||
);
|
||||
};
|
||||
|
||||
const doesVizMatchSelector = (viz: ChartMetadata, selector: string) =>
|
||||
selector === viz.category ||
|
||||
(selector === OTHER_CATEGORY && viz.category == null) ||
|
||||
(viz.tags || []).indexOf(selector) > -1;
|
||||
|
||||
export default function VizTypeGallery(props: VizTypeGalleryProps) {
|
||||
const { selectedViz, onChange, className } = props;
|
||||
const { mountedPluginMetadata } = usePluginContext();
|
||||
const searchInputRef = useRef<HTMLInputElement>();
|
||||
const [searchInputValue, setSearchInputValue] = useState(
|
||||
DEFAULT_SEARCH_INPUT_VALUE,
|
||||
);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const [isSearchFocused, setIsSearchFocused] = useState(true);
|
||||
const isActivelySearching = isSearchFocused && !!searchInputValue;
|
||||
|
||||
@@ -430,8 +484,38 @@ export default function VizTypeGallery(props: VizTypeGalleryProps) {
|
||||
[chartsByCategory],
|
||||
);
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<string>(
|
||||
() => selectedVizMetadata?.category || categories[0],
|
||||
const chartsByTags = useMemo(() => {
|
||||
const result: Record<string, VizEntry[]> = {};
|
||||
chartMetadata.forEach(entry => {
|
||||
const tags = entry.value.tags || [];
|
||||
tags.forEach(tag => {
|
||||
if (!result[tag]) {
|
||||
result[tag] = [];
|
||||
}
|
||||
result[tag].push(entry);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, [chartMetadata]);
|
||||
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
Object.keys(chartsByTags)
|
||||
.sort((a, b) =>
|
||||
// sort alphabetically
|
||||
a.localeCompare(b),
|
||||
)
|
||||
.filter(tag => RECOMMENDED_TAGS.indexOf(tag) === -1),
|
||||
[chartsByCategory],
|
||||
);
|
||||
|
||||
const sortedMetadata = useMemo(
|
||||
() => chartMetadata.sort((a, b) => a.key.localeCompare(b.key)),
|
||||
[chartMetadata],
|
||||
);
|
||||
|
||||
const [activeSelector, setActiveSelector] = useState<string>(
|
||||
() => selectedVizMetadata?.category || RECOMMENDED_TAGS[0],
|
||||
);
|
||||
|
||||
// get a fuse instance for fuzzy search
|
||||
@@ -472,83 +556,125 @@ export default function VizTypeGallery(props: VizTypeGalleryProps) {
|
||||
searchInputRef.current!.blur();
|
||||
}, []);
|
||||
|
||||
const selectCategory = useCallback(
|
||||
const clickSelector = useCallback(
|
||||
(key: string) => {
|
||||
if (isSearchFocused) {
|
||||
stopSearching();
|
||||
}
|
||||
setActiveCategory(key);
|
||||
// clear the selected viz if it is not present in the new category
|
||||
setActiveSelector(key);
|
||||
// clear the selected viz if it is not present in the new category or tags
|
||||
const isSelectedVizCompatible =
|
||||
selectedVizMetadata && doesVizMatchCategory(selectedVizMetadata, key);
|
||||
if (key !== activeCategory && !isSelectedVizCompatible) {
|
||||
selectedVizMetadata && doesVizMatchSelector(selectedVizMetadata, key);
|
||||
if (key !== activeSelector && !isSelectedVizCompatible) {
|
||||
onChange(null);
|
||||
}
|
||||
},
|
||||
[
|
||||
stopSearching,
|
||||
isSearchFocused,
|
||||
activeCategory,
|
||||
activeSelector,
|
||||
selectedVizMetadata,
|
||||
onChange,
|
||||
],
|
||||
);
|
||||
|
||||
const clearSelector = useCallback(e => {
|
||||
e.stopPropagation();
|
||||
if (isSearchFocused) {
|
||||
stopSearching();
|
||||
}
|
||||
// clear current selector and set all charts
|
||||
setActiveSelector(ALL_CHARTS);
|
||||
}, []);
|
||||
|
||||
const sectionMap = useMemo(
|
||||
() => ({
|
||||
RECOMMENDED_TAGS: {
|
||||
title: t('Recommended tags'),
|
||||
icon: <Icons.Tags />,
|
||||
selectors: RECOMMENDED_TAGS,
|
||||
},
|
||||
ALL: {
|
||||
title: t('All'),
|
||||
icon: <Icons.Ballot />,
|
||||
selectors: [ALL_CHARTS],
|
||||
},
|
||||
CATEGORY: {
|
||||
title: t('Category'),
|
||||
icon: <Icons.Category />,
|
||||
selectors: categories,
|
||||
},
|
||||
TAGS: {
|
||||
title: t('Tags'),
|
||||
icon: <Icons.Tags />,
|
||||
selectors: tags,
|
||||
},
|
||||
}),
|
||||
[categories, tags],
|
||||
);
|
||||
|
||||
const vizEntriesToDisplay = isActivelySearching
|
||||
? searchResults
|
||||
: chartsByCategory[activeCategory] || [];
|
||||
: activeSelector === ALL_CHARTS
|
||||
? sortedMetadata
|
||||
: chartsByCategory[activeSelector] || chartsByTags[activeSelector] || [];
|
||||
|
||||
return (
|
||||
<VizPickerLayout className={className}>
|
||||
<LeftPane>
|
||||
<SearchWrapper>
|
||||
<Input
|
||||
type="text"
|
||||
ref={searchInputRef as any /* cast required because emotion */}
|
||||
value={searchInputValue}
|
||||
placeholder={t('Search')}
|
||||
onChange={changeSearch}
|
||||
onFocus={focusSearch}
|
||||
data-test={`${VIZ_TYPE_CONTROL_TEST_ID}__search-input`}
|
||||
prefix={
|
||||
<InputIconAlignment>
|
||||
<Icons.Search iconSize="m" />
|
||||
</InputIconAlignment>
|
||||
}
|
||||
suffix={
|
||||
<InputIconAlignment>
|
||||
{searchInputValue && (
|
||||
<Icons.XLarge iconSize="m" onClick={stopSearching} />
|
||||
)}
|
||||
</InputIconAlignment>
|
||||
}
|
||||
/>
|
||||
</SearchWrapper>
|
||||
<CategoriesWrapper>
|
||||
{categories.map(category => (
|
||||
<CategorySelector
|
||||
key={category}
|
||||
category={category}
|
||||
isSelected={!isActivelySearching && category === activeCategory}
|
||||
onClick={selectCategory}
|
||||
/>
|
||||
))}
|
||||
</CategoriesWrapper>
|
||||
<Collapse
|
||||
expandIconPosition="right"
|
||||
ghost
|
||||
defaultActiveKey={Object.keys(sectionMap)}
|
||||
>
|
||||
{Object.keys(sectionMap).map(key => {
|
||||
const section = sectionMap[key];
|
||||
|
||||
return (
|
||||
<Collapse.Panel
|
||||
header={<span className="header">{section.title}</span>}
|
||||
key={key}
|
||||
>
|
||||
{section.selectors.map((selector: string) => (
|
||||
<Selector
|
||||
selector={selector}
|
||||
icon={section.icon}
|
||||
isSelected={
|
||||
!isActivelySearching && selector === activeSelector
|
||||
}
|
||||
onClick={clickSelector}
|
||||
onClear={clearSelector}
|
||||
/>
|
||||
))}
|
||||
</Collapse.Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</LeftPane>
|
||||
|
||||
<AllTagsWrapper>
|
||||
{ALL_TAGS.map(tag => (
|
||||
<Label
|
||||
key={tag}
|
||||
onClick={() => {
|
||||
focusSearch();
|
||||
setSearchInputValue(tag);
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</Label>
|
||||
))}
|
||||
</AllTagsWrapper>
|
||||
<SearchWrapper>
|
||||
<Input
|
||||
type="text"
|
||||
ref={searchInputRef as any /* cast required because emotion */}
|
||||
value={searchInputValue}
|
||||
placeholder={t('Search all charts')}
|
||||
onChange={changeSearch}
|
||||
onFocus={focusSearch}
|
||||
data-test={`${VIZ_TYPE_CONTROL_TEST_ID}__search-input`}
|
||||
prefix={
|
||||
<InputIconAlignment>
|
||||
<Icons.Search iconSize="m" />
|
||||
</InputIconAlignment>
|
||||
}
|
||||
suffix={
|
||||
<InputIconAlignment>
|
||||
{searchInputValue && (
|
||||
<Icons.XLarge iconSize="m" onClick={stopSearching} />
|
||||
)}
|
||||
</InputIconAlignment>
|
||||
}
|
||||
/>
|
||||
</SearchWrapper>
|
||||
|
||||
<RightPane>
|
||||
<ThumbnailGallery
|
||||
|
||||
Reference in New Issue
Block a user