You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2022/12/01 20:41:26 UTC

[superset] branch master updated: feat(native-filters): Adjust filter components for horizontal mode (#22273)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new eb6045adfa feat(native-filters): Adjust filter components for horizontal mode (#22273)
eb6045adfa is described below

commit eb6045adfa77e06c8aaf3de217719ca59d4328e1
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Thu Dec 1 21:41:19 2022 +0100

    feat(native-filters): Adjust filter components for horizontal mode (#22273)
---
 .../src/chart/models/ChartProps.ts                 |  9 +++
 .../src/components/Select/CustomTag.tsx            | 77 ++++++++++++++++++++++
 .../src/components/Select/Select.stories.tsx       | 10 +++
 .../src/components/Select/Select.test.tsx          | 48 ++++++++++++++
 superset-frontend/src/components/Select/Select.tsx | 17 ++++-
 superset-frontend/src/components/Select/styles.tsx | 25 ++++++-
 superset-frontend/src/components/Select/types.ts   | 13 ++++
 .../FilterBar/FilterControls/FilterControl.tsx     |  2 +-
 .../FilterBar/FilterControls/FilterValue.tsx       | 12 ++--
 .../filters/components/Range/RangeFilterPlugin.tsx | 33 ++++++++--
 .../src/filters/components/Range/transformProps.ts |  3 +
 .../src/filters/components/Range/types.ts          |  3 +
 .../components/Select/SelectFilterPlugin.tsx       | 15 +++--
 .../filters/components/Select/transformProps.ts    |  3 +
 .../src/filters/components/Select/types.ts         |  3 +
 .../filters/components/Time/TimeFilterPlugin.tsx   |  9 +++
 .../src/filters/components/Time/transformProps.ts  |  2 +
 17 files changed, 265 insertions(+), 19 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
index 324c4dfc45..5e4f044942 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
+++ b/superset-frontend/packages/superset-ui-core/src/chart/models/ChartProps.ts
@@ -90,6 +90,8 @@ export interface ChartPropsConfig {
   filterState?: FilterState;
   /** Set of actual behaviors that this instance of chart should use */
   behaviors?: Behavior[];
+  /** Chart display settings related to current view context */
+  displaySettings?: JsonObject;
   /** Application section of the chart on the screen (in what components/screen it placed) */
   appSection?: AppSection;
   /** is the chart refreshing its contents */
@@ -132,6 +134,8 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
 
   behaviors: Behavior[];
 
+  displaySettings?: JsonObject;
+
   appSection?: AppSection;
 
   isRefreshing?: boolean;
@@ -153,6 +157,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
       initialValues = {},
       queriesData = [],
       behaviors = [],
+      displaySettings = {},
       width = DEFAULT_WIDTH,
       height = DEFAULT_HEIGHT,
       appSection,
@@ -174,6 +179,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
     this.ownState = ownState;
     this.filterState = filterState;
     this.behaviors = behaviors;
+    this.displaySettings = displaySettings;
     this.appSection = appSection;
     this.isRefreshing = isRefreshing;
     this.inputRef = inputRef;
@@ -196,6 +202,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
     input => input.ownState,
     input => input.filterState,
     input => input.behaviors,
+    input => input.displaySettings,
     input => input.appSection,
     input => input.isRefreshing,
     input => input.inputRef,
@@ -213,6 +220,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
       ownState,
       filterState,
       behaviors,
+      displaySettings,
       appSection,
       isRefreshing,
       inputRef,
@@ -231,6 +239,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
         filterState,
         width,
         behaviors,
+        displaySettings,
         appSection,
         isRefreshing,
         inputRef,
diff --git a/superset-frontend/src/components/Select/CustomTag.tsx b/superset-frontend/src/components/Select/CustomTag.tsx
new file mode 100644
index 0000000000..57aa37c81b
--- /dev/null
+++ b/superset-frontend/src/components/Select/CustomTag.tsx
@@ -0,0 +1,77 @@
+/**
+ * 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.
+ */
+import React from 'react';
+import { Tag as AntdTag } from 'antd';
+import { styled } from '@superset-ui/core';
+import { useCSSTextTruncation } from 'src/hooks/useTruncation';
+import { Tooltip } from '../Tooltip';
+import { CustomTagProps } from './types';
+
+const StyledTag = styled(AntdTag)`
+  & .ant-tag-close-icon {
+    display: inline-flex;
+    align-items: center;
+    margin-left: ${({ theme }) => theme.gridUnit}px;
+  }
+
+  & .tag-content {
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+`;
+
+// TODO: use antd Tag props instead of any. Currently it's causing a typescript error
+const Tag = (props: any) => {
+  const [tagRef, tagIsTruncated] = useCSSTextTruncation<HTMLSpanElement>();
+  return (
+    <Tooltip title={tagIsTruncated ? props.children : null}>
+      <StyledTag {...props} className="ant-select-selection-item">
+        <span className="tag-content" ref={tagRef}>
+          {props.children}
+        </span>
+      </StyledTag>
+    </Tooltip>
+  );
+};
+
+/**
+ * Custom tag renderer dedicated for oneLine mode
+ */
+export const oneLineTagRender = (props: CustomTagProps) => {
+  const { label } = props;
+
+  const onPreventMouseDown = (event: React.MouseEvent<HTMLElement>) => {
+    // if close icon is clicked, stop propagation to avoid opening the dropdown
+    const target = event.target as HTMLElement;
+    if (
+      target.tagName === 'svg' ||
+      target.tagName === 'path' ||
+      (target.tagName === 'span' &&
+        target.className.includes('ant-tag-close-icon'))
+    ) {
+      event.stopPropagation();
+    }
+  };
+
+  return (
+    <Tag onMouseDown={onPreventMouseDown} {...props}>
+      {label}
+    </Tag>
+  );
+};
diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index c4802a45e0..4a5d3551f0 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -132,6 +132,15 @@ const ARG_TYPES = {
       By default label and value.
     `,
   },
+  oneLine: {
+    defaultValue: false,
+    description: `Sets maxTagCount to 1. The overflow tag is always displayed in
+       the same line, line wrapping is disabled.
+       When the dropdown is open, sets maxTagCount to 0,
+       displays only the overflow tag.
+       Requires '"mode=multiple"'.
+     `,
+  },
 };
 
 const mountHeader = (type: String) => {
@@ -197,6 +206,7 @@ InteractiveSelect.args = {
   invertSelection: false,
   placeholder: 'Select ...',
   optionFilterProps: ['value', 'label', 'custom'],
+  oneLine: false,
 };
 
 InteractiveSelect.argTypes = {
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 06f5f7b8e5..cb4548633e 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -566,6 +566,54 @@ test('finds an element with a numeric value and does not duplicate the options',
   expect(await querySelectOption('11')).not.toBeInTheDocument();
 });
 
+test('Renders only 1 tag and an overflow tag in oneLine mode', () => {
+  render(
+    <Select
+      {...defaultProps}
+      value={[OPTIONS[0], OPTIONS[1], OPTIONS[2]]}
+      mode="multiple"
+      oneLine
+    />,
+  );
+  expect(screen.getByText(OPTIONS[0].label)).toBeVisible();
+  expect(screen.queryByText(OPTIONS[1].label)).not.toBeInTheDocument();
+  expect(screen.queryByText(OPTIONS[2].label)).not.toBeInTheDocument();
+  expect(screen.getByText('+ 2 ...')).toBeVisible();
+});
+
+test('Renders only an overflow tag if dropdown is open in oneLine mode', async () => {
+  render(
+    <Select
+      {...defaultProps}
+      value={[OPTIONS[0], OPTIONS[1], OPTIONS[2]]}
+      mode="multiple"
+      oneLine
+    />,
+  );
+  await open();
+
+  const withinSelector = within(getElementByClassName('.ant-select-selector'));
+  await waitFor(() => {
+    expect(
+      withinSelector.queryByText(OPTIONS[0].label),
+    ).not.toBeInTheDocument();
+    expect(
+      withinSelector.queryByText(OPTIONS[1].label),
+    ).not.toBeInTheDocument();
+    expect(
+      withinSelector.queryByText(OPTIONS[2].label),
+    ).not.toBeInTheDocument();
+    expect(withinSelector.getByText('+ 3 ...')).toBeVisible();
+  });
+
+  await type('{esc}');
+
+  expect(await withinSelector.findByText(OPTIONS[0].label)).toBeVisible();
+  expect(withinSelector.queryByText(OPTIONS[1].label)).not.toBeInTheDocument();
+  expect(withinSelector.queryByText(OPTIONS[2].label)).not.toBeInTheDocument();
+  expect(withinSelector.getByText('+ 2 ...')).toBeVisible();
+});
+
 /*
  TODO: Add tests that require scroll interaction. Needs further investigation.
  - Fetches more data when scrolling and more data is available
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 0e490013cf..5550463492 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -54,6 +54,7 @@ import {
   TOKEN_SEPARATORS,
   DEFAULT_SORT_COMPARATOR,
 } from './constants';
+import { oneLineTagRender } from './CustomTag';
 
 /**
  * This component is a customized version of the Antdesign 4.X Select component
@@ -96,6 +97,8 @@ const Select = forwardRef(
       tokenSeparators,
       value,
       getPopupContainer,
+      oneLine,
+      maxTagCount: propsMaxTagCount,
       ...props
     }: SelectProps,
     ref: RefObject<HTMLInputElement>,
@@ -106,6 +109,16 @@ const Select = forwardRef(
     const [inputValue, setInputValue] = useState('');
     const [isLoading, setIsLoading] = useState(loading);
     const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+    const [maxTagCount, setMaxTagCount] = useState(
+      propsMaxTagCount ?? MAX_TAG_COUNT,
+    );
+
+    useEffect(() => {
+      if (oneLine) {
+        setMaxTagCount(isDropdownVisible ? 0 : 1);
+      }
+    }, [isDropdownVisible, oneLine]);
+
     const mappedMode = isSingleMode
       ? undefined
       : allowNewOptions
@@ -280,7 +293,7 @@ const Select = forwardRef(
           }
           headerPosition={headerPosition}
           labelInValue={labelInValue}
-          maxTagCount={MAX_TAG_COUNT}
+          maxTagCount={maxTagCount}
           mode={mappedMode}
           notFoundContent={isLoading ? t('Loading...') : notFoundContent}
           onDeselect={handleOnDeselect}
@@ -308,6 +321,8 @@ const Select = forwardRef(
               <StyledCheckOutlined iconSize="m" />
             )
           }
+          oneLine={oneLine}
+          tagRender={oneLine ? oneLineTagRender : undefined}
           {...props}
           ref={ref}
         >
diff --git a/superset-frontend/src/components/Select/styles.tsx b/superset-frontend/src/components/Select/styles.tsx
index 2da49d7d6e..784bdc8ae7 100644
--- a/superset-frontend/src/components/Select/styles.tsx
+++ b/superset-frontend/src/components/Select/styles.tsx
@@ -40,9 +40,9 @@ export const StyledContainer = styled.div<{ headerPosition: string }>`
 `;
 
 export const StyledSelect = styled(AntdSelect, {
-  shouldForwardProp: prop => prop !== 'headerPosition',
-})<{ headerPosition: string }>`
-  ${({ theme, headerPosition }) => `
+  shouldForwardProp: prop => prop !== 'headerPosition' && prop !== 'oneLine',
+})<{ headerPosition: string; oneLine?: boolean }>`
+  ${({ theme, headerPosition, oneLine }) => `
     flex: ${headerPosition === 'left' ? 1 : 0};
     && .ant-select-selector {
       border-radius: ${theme.gridUnit}px;
@@ -52,6 +52,25 @@ export const StyledSelect = styled(AntdSelect, {
     .ant-select-arrow .anticon:not(.ant-select-suffix) {
       pointer-events: none;
     }
+
+    ${
+      oneLine &&
+      `
+        .ant-select-selection-overflow {
+          flex-wrap: nowrap;
+        }
+
+        .ant-select-selection-overflow-item:not(.ant-select-selection-overflow-item-rest):not(.ant-select-selection-overflow-item-suffix) {
+          flex-shrink: 1;
+          min-width: ${theme.gridUnit * 13}px;
+        }
+
+        .ant-select-selection-overflow-item-suffix {
+          flex: unset;
+          min-width: 0px;
+        }
+      `
+    }
  `}
 `;
 
diff --git a/superset-frontend/src/components/Select/types.ts b/superset-frontend/src/components/Select/types.ts
index 2c4ccad394..76f7acd0a2 100644
--- a/superset-frontend/src/components/Select/types.ts
+++ b/superset-frontend/src/components/Select/types.ts
@@ -27,6 +27,7 @@ import {
   SelectValue as AntdSelectValue,
   LabeledValue as AntdLabeledValue,
 } from 'antd/lib/select';
+import { TagProps } from 'antd/lib/tag';
 
 export type RawValue = string | number;
 
@@ -140,6 +141,13 @@ export interface BaseSelectProps extends AntdExposedProps {
     b: AntdLabeledValue,
     search?: string,
   ) => number;
+  /**
+   * Sets maxTagCount to 1. The overflow tag is always displayed in
+   * the same line, line wrapping is disabled.
+   * When the dropdown is open, sets maxTagCount to 0,
+   * displays only the overflow tag.
+   */
+  oneLine?: boolean;
 
   suffixIcon?: ReactNode;
 
@@ -203,3 +211,8 @@ export interface AsyncSelectProps extends BaseSelectProps {
    */
   onError?: (error: string) => void;
 }
+
+export type CustomTagProps = HTMLSpanElement &
+  TagProps & {
+    label: ReactNode;
+  };
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
index 47ca4b4a5b..1206755f93 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControl.tsx
@@ -136,7 +136,7 @@ const HorizontalFormItem = styled(StyledFormItem)`
   }
 
   .ant-form-item-control {
-    width: ${({ theme }) => theme.gridUnit * 40}px;
+    width: ${({ theme }) => theme.gridUnit * 41}px;
   }
 `;
 
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index a08200d83b..5cc9d804e1 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -253,9 +253,12 @@ const FilterValue: React.FC<FilterControlProps> = ({
     [filter.dataMask?.filterState, isMissingRequiredValue],
   );
 
-  const formDataWithDisplayParams = useMemo(
-    () => ({ ...formData, orientation, overflow }),
-    [formData, orientation, overflow],
+  const displaySettings = useMemo(
+    () => ({
+      filterBarOrientation: orientation,
+      isOverflowingFilterBar: overflow,
+    }),
+    [orientation, overflow],
   );
 
   if (error) {
@@ -277,7 +280,8 @@ const FilterValue: React.FC<FilterControlProps> = ({
           height={HEIGHT}
           width="100%"
           showOverflow={showOverflow}
-          formData={formDataWithDisplayParams}
+          formData={formData}
+          displaySettings={displaySettings}
           parentRef={parentRef}
           inputRef={inputRef}
           // For charts that don't have datasource we need workaround for empty placeholder
diff --git a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
index f3673f223e..9c60e60814 100644
--- a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
@@ -25,8 +25,9 @@ import {
   t,
 } from '@superset-ui/core';
 import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { AntdSlider } from 'src/components';
 import { rgba } from 'emotion-rgba';
+import { AntdSlider } from 'src/components';
+import { FilterBarOrientation } from 'src/dashboard/types';
 import { PluginFilterRangeProps } from './types';
 import { StatusMessage, StyledFormItem, FilterPluginStyle } from '../common';
 import { getRangeExtraFormData } from '../../utils';
@@ -65,8 +66,12 @@ const StyledMinSlider = styled(AntdSlider)<{
   `}
 `;
 
-const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>`
-  ${({ theme, validateStatus }) => `
+const Wrapper = styled.div<{
+  validateStatus?: 'error' | 'warning' | 'info';
+  orientation?: FilterBarOrientation;
+  isOverflowing?: boolean;
+}>`
+  ${({ theme, validateStatus, orientation, isOverflowing }) => `
     border: 1px solid transparent;
     &:focus {
       border: 1px solid
@@ -76,8 +81,18 @@ const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>`
         ${rgba(theme.colors[validateStatus || 'primary']?.base, 0.2)};
     }
     & .ant-slider {
-      margin-top: ${theme.gridUnit}px;
-      margin-bottom: ${theme.gridUnit * 5}px;
+      margin-top: ${
+        orientation === FilterBarOrientation.HORIZONTAL ? 0 : theme.gridUnit
+      }px;
+      margin-bottom: ${
+        orientation === FilterBarOrientation.HORIZONTAL ? 0 : theme.gridUnit * 5
+      }px;
+
+      ${
+        orientation === FilterBarOrientation.HORIZONTAL &&
+        !isOverflowing &&
+        `line-height: 1.2;`
+      }
 
       & .ant-slider-track {
         background-color: ${
@@ -93,6 +108,10 @@ const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>`
             ${rgba(theme.colors[validateStatus || 'primary']?.base, 0.2)};
         }
       }
+      & .ant-slider-mark {
+        font-size: ${theme.typography.sizes.s}px;
+      }
+
       &:hover {
         & .ant-slider-track {
           background-color: ${
@@ -155,6 +174,8 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
     setFilterActive,
     filterState,
     inputRef,
+    filterBarOrientation,
+    isOverflowingFilterBar,
   } = props;
   const [row] = data;
   // @ts-ignore
@@ -287,6 +308,8 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
             tabIndex={-1}
             ref={inputRef}
             validateStatus={filterState.validateStatus}
+            orientation={filterBarOrientation}
+            isOverflowing={isOverflowingFilterBar}
             onFocus={setFocusedFilter}
             onBlur={unsetFocusedFilter}
             onMouseEnter={setFocusedFilter}
diff --git a/superset-frontend/src/filters/components/Range/transformProps.ts b/superset-frontend/src/filters/components/Range/transformProps.ts
index f4c47cec26..48def72cad 100644
--- a/superset-frontend/src/filters/components/Range/transformProps.ts
+++ b/superset-frontend/src/filters/components/Range/transformProps.ts
@@ -29,6 +29,7 @@ export default function transformProps(chartProps: ChartProps) {
     behaviors,
     filterState,
     inputRef,
+    displaySettings,
   } = chartProps;
   const {
     setDataMask = noOp,
@@ -50,5 +51,7 @@ export default function transformProps(chartProps: ChartProps) {
     unsetFocusedFilter,
     setFilterActive,
     inputRef,
+    isOverflowingFilterBar: displaySettings?.isOverflowingFilterBar,
+    filterBarOrientation: displaySettings?.filterBarOrientation,
   };
 }
diff --git a/superset-frontend/src/filters/components/Range/types.ts b/superset-frontend/src/filters/components/Range/types.ts
index 69ad4d53da..25a18bb3e8 100644
--- a/superset-frontend/src/filters/components/Range/types.ts
+++ b/superset-frontend/src/filters/components/Range/types.ts
@@ -24,6 +24,7 @@ import {
 } from '@superset-ui/core';
 import { RefObject } from 'react';
 import { PluginFilterHooks, PluginFilterStylesProps } from '../types';
+import { FilterBarOrientation } from '../../../dashboard/types';
 
 interface PluginFilterSelectCustomizeProps {
   max?: number;
@@ -40,4 +41,6 @@ export type PluginFilterRangeProps = PluginFilterStylesProps & {
   filterState: FilterState;
   behaviors: Behavior[];
   inputRef: RefObject<any>;
+  filterBarOrientation?: FilterBarOrientation;
+  isOverflowingFilterBar?: boolean;
 } & PluginFilterHooks;
diff --git a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
index 9cea856d47..7337f51689 100644
--- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 /* eslint-disable no-param-reassign */
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import {
   AppSection,
   DataMask,
@@ -31,14 +32,14 @@ import {
   tn,
 } from '@superset-ui/core';
 import { LabeledValue as AntdLabeledValue } from 'antd/lib/select';
-import React, { useCallback, useEffect, useState, useMemo } from 'react';
-import { Select } from 'src/components';
 import debounce from 'lodash/debounce';
-import { SLOW_DEBOUNCE } from 'src/constants';
 import { useImmerReducer } from 'use-immer';
+import { Select } from 'src/components';
+import { SLOW_DEBOUNCE } from 'src/constants';
 import { propertyComparator } from 'src/components/Select/utils';
+import { FilterBarOrientation } from 'src/dashboard/types';
 import { PluginFilterSelectProps, SelectValue } from './types';
-import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common';
+import { FilterPluginStyle, StatusMessage, StyledFormItem } from '../common';
 import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
 
 type DataMaskAction =
@@ -89,6 +90,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
     showOverflow,
     parentRef,
     inputRef,
+    filterBarOrientation,
   } = props;
   const {
     enableEmptyFilter,
@@ -323,11 +325,14 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
           onChange={handleChange}
           ref={inputRef}
           loading={isRefreshing}
-          maxTagCount={5}
+          oneLine={filterBarOrientation === FilterBarOrientation.HORIZONTAL}
           invertSelection={inverseSelection}
           // @ts-ignore
           options={options}
           sortComparator={sortComparator}
+          maxTagPlaceholder={(val: AntdLabeledValue[]) => (
+            <span>+{val.length}</span>
+          )}
           onDropdownVisibleChange={setFilterActive}
         />
       </StyledFormItem>
diff --git a/superset-frontend/src/filters/components/Select/transformProps.ts b/superset-frontend/src/filters/components/Select/transformProps.ts
index 59d9adc57e..a0fafa4a45 100644
--- a/superset-frontend/src/filters/components/Select/transformProps.ts
+++ b/superset-frontend/src/filters/components/Select/transformProps.ts
@@ -29,6 +29,7 @@ export default function transformProps(
     hooks,
     queriesData,
     width,
+    displaySettings,
     behaviors,
     appSection,
     filterState,
@@ -64,5 +65,7 @@ export default function transformProps(
     unsetFocusedFilter,
     setFilterActive,
     inputRef,
+    filterBarOrientation: displaySettings?.filterBarOrientation,
+    isOverflowingFilterBar: displaySettings?.isOverflowingFilterBar,
   };
 }
diff --git a/superset-frontend/src/filters/components/Select/types.ts b/superset-frontend/src/filters/components/Select/types.ts
index 0497b58e55..e608f59640 100644
--- a/superset-frontend/src/filters/components/Select/types.ts
+++ b/superset-frontend/src/filters/components/Select/types.ts
@@ -27,6 +27,7 @@ import {
   ChartDataResponseResult,
 } from '@superset-ui/core';
 import { RefObject } from 'react';
+import { FilterBarOrientation } from 'src/dashboard/types';
 import { PluginFilterHooks, PluginFilterStylesProps } from '../types';
 
 export type SelectValue = (number | string | null)[] | null | undefined;
@@ -61,6 +62,8 @@ export type PluginFilterSelectProps = PluginFilterStylesProps & {
   showOverflow: boolean;
   parentRef?: RefObject<any>;
   inputRef?: RefObject<any>;
+  filterBarOrientation?: FilterBarOrientation;
+  isOverflowingFilterBar?: boolean;
 } & PluginFilterHooks;
 
 export const DEFAULT_FORM_DATA: PluginFilterSelectCustomizeProps = {
diff --git a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx
index 82bfc678f0..0d4185b759 100644
--- a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx
@@ -23,12 +23,21 @@ import { PluginFilterTimeProps } from './types';
 import { FilterPluginStyle } from '../common';
 
 const TimeFilterStyles = styled(FilterPluginStyle)`
+  display: flex;
+  align-items: center;
   overflow-x: auto;
+
+  & .ant-tag {
+    margin-right: 0;
+  }
 `;
 
 const ControlContainer = styled.div<{
   validateStatus?: 'error' | 'warning' | 'info';
 }>`
+  display: flex;
+  height: 100%;
+  max-width: 100%;
   padding: 2px;
   & > span,
   & > span:hover {
diff --git a/superset-frontend/src/filters/components/Time/transformProps.ts b/superset-frontend/src/filters/components/Time/transformProps.ts
index 883b6002d8..8262b8f63f 100644
--- a/superset-frontend/src/filters/components/Time/transformProps.ts
+++ b/superset-frontend/src/filters/components/Time/transformProps.ts
@@ -30,6 +30,7 @@ export default function transformProps(chartProps: ChartProps) {
     behaviors,
     filterState,
     inputRef,
+    displaySettings,
   } = chartProps;
   const {
     setDataMask = noOp,
@@ -54,5 +55,6 @@ export default function transformProps(chartProps: ChartProps) {
     setFilterActive,
     width,
     inputRef,
+    filterBarOrientation: displaySettings?.filterBarOrientation,
   };
 }