fix(dashboard): Add correct icon, label and badge to horizontal native filters dropdown button (#22211)

Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
This commit is contained in:
Cody Leff 2022-11-28 06:12:57 -07:00 committed by GitHub
parent 93158ea649
commit 435926b89e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 81 deletions

View File

@ -91,6 +91,8 @@ export type Filter = {
description: string; description: string;
}; };
export type FilterWithDataMask = Filter & { dataMask: DataMaskWithId };
export type Divider = Partial<Omit<Filter, 'id' | 'type'>> & { export type Divider = Partial<Omit<Filter, 'id' | 'type'>> & {
id: string; id: string;
title: string; title: string;

View File

@ -104,7 +104,7 @@ const DropdownContainer = forwardRef(
{ {
items, items,
onOverflowingStateChange, onOverflowingStateChange,
dropdownContent: popoverContent, dropdownContent: getPopoverContent,
dropdownRef: popoverRef, dropdownRef: popoverRef,
dropdownStyle: popoverStyle = {}, dropdownStyle: popoverStyle = {},
dropdownTriggerCount: popoverTriggerCount, dropdownTriggerCount: popoverTriggerCount,
@ -209,8 +209,12 @@ const DropdownContainer = forwardRef(
} }
}, [notOverflowedIds, onOverflowingStateChange, overflowedIds]); }, [notOverflowedIds, onOverflowingStateChange, overflowedIds]);
const content = useMemo( const overflowingCount =
() => ( overflowingIndex !== -1 ? items.length - overflowingIndex : 0;
const popoverContent = useMemo(
() =>
getPopoverContent || overflowingCount ? (
<div <div
css={css` css={css`
display: flex; display: flex;
@ -221,14 +225,15 @@ const DropdownContainer = forwardRef(
style={popoverStyle} style={popoverStyle}
ref={popoverRef} ref={popoverRef}
> >
{popoverContent {getPopoverContent
? popoverContent(overflowedItems) ? getPopoverContent(overflowedItems)
: overflowedItems.map(item => item.element)} : overflowedItems.map(item => item.element)}
</div> </div>
), ) : null,
[ [
getPopoverContent,
overflowedItems, overflowedItems,
popoverContent, overflowingCount,
popoverRef, popoverRef,
popoverStyle, popoverStyle,
theme.gridUnit, theme.gridUnit,
@ -244,9 +249,6 @@ const DropdownContainer = forwardRef(
[ref], [ref],
); );
const overflowingCount =
overflowingIndex !== -1 ? items.length - overflowingIndex : 0;
return ( return (
<div <div
ref={ref} ref={ref}
@ -268,20 +270,33 @@ const DropdownContainer = forwardRef(
> >
{notOverflowedItems.map(item => item.element)} {notOverflowedItems.map(item => item.element)}
</div> </div>
{overflowingCount > 0 && ( {popoverContent && (
<Popover <Popover
content={content} content={popoverContent}
trigger="click" trigger="click"
visible={popoverVisible} visible={popoverVisible}
onVisibleChange={visible => setPopoverVisible(visible)} onVisibleChange={visible => setPopoverVisible(visible)}
placement="bottom"
> >
<Button buttonStyle="secondary"> <Button buttonStyle="secondary">
{popoverTriggerIcon} {popoverTriggerIcon}
{popoverTriggerText} {popoverTriggerText}
<Badge count={popoverTriggerCount || overflowingCount} /> <Badge
count={popoverTriggerCount ?? overflowingCount}
css={css`
margin-left: ${popoverTriggerCount ?? overflowingCount
? '8px'
: '0'};
`}
/>
<Icons.DownOutlined <Icons.DownOutlined
iconSize="m" iconSize="m"
iconColor={theme.colors.grayscale.base} iconColor={theme.colors.grayscale.light1}
css={css`
.anticon {
display: flex;
}
`}
/> />
</Button> </Button>
</Popover> </Popover>

View File

@ -24,6 +24,8 @@ import {
Divider, Divider,
css, css,
SupersetTheme, SupersetTheme,
t,
isNativeFilter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { import {
createHtmlPortalNode, createHtmlPortalNode,
@ -37,6 +39,7 @@ import {
} from 'src/dashboard/components/nativeFilters/state'; } from 'src/dashboard/components/nativeFilters/state';
import { FilterBarOrientation, RootState } from 'src/dashboard/types'; import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import DropdownContainer from 'src/components/DropdownContainer'; import DropdownContainer from 'src/components/DropdownContainer';
import Icons from 'src/components/Icons';
import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible'; import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible';
import { useFilterControlFactory } from '../useFilterControlFactory'; import { useFilterControlFactory } from '../useFilterControlFactory';
import { FiltersDropdownContent } from '../FiltersDropdownContent'; import { FiltersDropdownContent } from '../FiltersDropdownContent';
@ -56,7 +59,7 @@ const FilterControls: FC<FilterControlsProps> = ({
state => state.dashboardInfo.filterBarOrientation, state => state.dashboardInfo.filterBarOrientation,
); );
const [overflowIndex, setOverflowIndex] = useState(0); const [overflowedIds, setOverflowedIds] = useState<string[]>([]);
const { filterControlFactory, filtersWithValues } = useFilterControlFactory( const { filterControlFactory, filtersWithValues } = useFilterControlFactory(
dataMaskSelected, dataMaskSelected,
@ -100,8 +103,9 @@ const FilterControls: FC<FilterControlsProps> = ({
</> </>
); );
const renderHorizontalContent = () => { const items = useMemo(
const items = filtersInScope.map(filter => ({ () =>
filtersInScope.map(filter => ({
id: filter.id, id: filter.id,
element: ( element: (
<div <div
@ -112,8 +116,24 @@ const FilterControls: FC<FilterControlsProps> = ({
{renderer(filter)} {renderer(filter)}
</div> </div>
), ),
})); })),
return ( [filtersInScope, renderer],
);
const overflowedFiltersInScope = useMemo(
() => filtersInScope.filter(({ id }) => overflowedIds?.includes(id)),
[filtersInScope, overflowedIds],
);
const activeOverflowedFiltersInScope = useMemo(
() =>
overflowedFiltersInScope.filter(
filter => isNativeFilter(filter) && filter.dataMask.filterState?.value,
).length,
[overflowedFiltersInScope],
);
const renderHorizontalContent = () => (
<div <div
css={(theme: SupersetTheme) => css={(theme: SupersetTheme) =>
css` css`
@ -124,28 +144,58 @@ const FilterControls: FC<FilterControlsProps> = ({
> >
<DropdownContainer <DropdownContainer
items={items} items={items}
dropdownContent={overflowedItems => { dropdownTriggerIcon={
const overflowedItemIds = new Set( <Icons.FilterSmall
overflowedItems.map(({ id }) => id), css={css`
); && {
return ( margin-right: -4px;
display: flex;
}
`}
/>
}
dropdownTriggerText={t('More filters')}
dropdownTriggerCount={activeOverflowedFiltersInScope}
dropdownContent={
overflowedFiltersInScope.length ||
(filtersOutOfScope.length && showCollapsePanel)
? () => (
<FiltersDropdownContent <FiltersDropdownContent
filtersInScope={filtersInScope.filter(({ id }) => filtersInScope={overflowedFiltersInScope}
overflowedItemIds.has(id),
)}
filtersOutOfScope={filtersOutOfScope} filtersOutOfScope={filtersOutOfScope}
renderer={renderer} renderer={renderer}
showCollapsePanel={showCollapsePanel} showCollapsePanel={showCollapsePanel}
/> />
); )
}} : undefined
onOverflowingStateChange={overflowingState =>
setOverflowIndex(overflowingState.notOverflowed.length)
} }
onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
if (
nextOverflowedIds.length !== overflowedIds.length ||
overflowedIds.reduce(
(a, b, i) => a || b !== nextOverflowedIds[i],
false,
)
) {
setOverflowedIds(nextOverflowedIds);
}
}}
/> />
</div> </div>
); );
};
const overflowedByIndex = useMemo(() => {
const filtersOutOfScopeIds = new Set(filtersOutOfScope.map(({ id }) => id));
const overflowedFiltersInScopeIds = new Set(
overflowedFiltersInScope.map(({ id }) => id),
);
return filtersWithValues.map(
filter =>
filtersOutOfScopeIds.has(filter.id) ||
overflowedFiltersInScopeIds.has(filter.id),
);
}, [filtersOutOfScope, filtersWithValues, overflowedFiltersInScope]);
return ( return (
<> <>
@ -153,7 +203,11 @@ const FilterControls: FC<FilterControlsProps> = ({
.filter((node, index) => filterIds.has(filtersWithValues[index].id)) .filter((node, index) => filterIds.has(filtersWithValues[index].id))
.map((node, index) => ( .map((node, index) => (
<InPortal node={node}> <InPortal node={node}>
{filterControlFactory(index, filterBarOrientation, overflowIndex)} {filterControlFactory(
index,
filterBarOrientation,
overflowedByIndex[index],
)}
</InPortal> </InPortal>
))} ))}
{filterBarOrientation === FilterBarOrientation.VERTICAL && {filterBarOrientation === FilterBarOrientation.VERTICAL &&

View File

@ -74,9 +74,6 @@ const FiltersLinkContainer = styled.div<{ hasFilters: boolean }>`
button { button {
display: flex; display: flex;
align-items: center; align-items: center;
text-transform: capitalize;
font-weight: ${theme.typography.weights.normal};
color: ${theme.colors.primary.base};
> .anticon { > .anticon {
height: 24px; height: 24px;
padding-right: ${theme.gridUnit}px; padding-right: ${theme.gridUnit}px;

View File

@ -23,6 +23,7 @@ import {
DataMaskStateWithId, DataMaskStateWithId,
Divider, Divider,
Filter, Filter,
FilterWithDataMask,
isFilterDivider, isFilterDivider,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { FilterBarOrientation } from 'src/dashboard/types'; import { FilterBarOrientation } from 'src/dashboard/types';
@ -37,7 +38,7 @@ export const useFilterControlFactory = (
) => { ) => {
const filters = useFilters(); const filters = useFilters();
const filterValues = useMemo(() => Object.values(filters), [filters]); const filterValues = useMemo(() => Object.values(filters), [filters]);
const filtersWithValues: (Filter | Divider)[] = useMemo( const filtersWithValues: (FilterWithDataMask | Divider)[] = useMemo(
() => () =>
filterValues.map(filter => ({ filterValues.map(filter => ({
...filter, ...filter,
@ -50,7 +51,7 @@ export const useFilterControlFactory = (
( (
index: number, index: number,
filterBarOrientation: FilterBarOrientation, filterBarOrientation: FilterBarOrientation,
overflowIndex: number, overflow: boolean,
) => { ) => {
const filter = filtersWithValues[index]; const filter = filtersWithValues[index];
if (isFilterDivider(filter)) { if (isFilterDivider(filter)) {
@ -59,7 +60,7 @@ export const useFilterControlFactory = (
title={filter.title} title={filter.title}
description={filter.description} description={filter.description}
orientation={filterBarOrientation} orientation={filterBarOrientation}
overflow={index >= overflowIndex} overflow={overflow}
/> />
); );
} }
@ -71,7 +72,7 @@ export const useFilterControlFactory = (
onFilterSelectionChange={onFilterSelectionChange} onFilterSelectionChange={onFilterSelectionChange}
inView={false} inView={false}
orientation={filterBarOrientation} orientation={filterBarOrientation}
overflow={index >= overflowIndex} overflow={overflow}
/> />
); );
}, },

View File

@ -23,6 +23,7 @@ import {
FilterConfiguration, FilterConfiguration,
Divider, Divider,
isFilterDivider, isFilterDivider,
FilterWithDataMask,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { ActiveTabs, DashboardLayout, RootState } from '../../types'; import { ActiveTabs, DashboardLayout, RootState } from '../../types';
import { TAB_TYPE } from '../../util/componentTypes'; import { TAB_TYPE } from '../../util/componentTypes';
@ -109,13 +110,15 @@ function useIsFilterInScope() {
})); }));
} }
export function useSelectFiltersInScope(filters: (Filter | Divider)[]) { export function useSelectFiltersInScope(
filters: (FilterWithDataMask | Divider)[],
) {
const dashboardHasTabs = useDashboardHasTabs(); const dashboardHasTabs = useDashboardHasTabs();
const isFilterInScope = useIsFilterInScope(); const isFilterInScope = useIsFilterInScope();
return useMemo(() => { return useMemo(() => {
let filtersInScope: (Filter | Divider)[] = []; let filtersInScope: (FilterWithDataMask | Divider)[] = [];
const filtersOutOfScope: (Filter | Divider)[] = []; const filtersOutOfScope: (FilterWithDataMask | Divider)[] = [];
// we check native filters scopes only on dashboards with tabs // we check native filters scopes only on dashboards with tabs
if (!dashboardHasTabs) { if (!dashboardHasTabs) {