mirror of
https://github.com/apache/superset.git
synced 2024-09-06 13:57:40 -04:00
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:
parent
93158ea649
commit
435926b89e
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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 &&
|
||||||
|
@ -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;
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user