You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2022/10/27 16:32:19 UTC

[superset] 01/01: Add component

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

diegopucci pushed a commit to branch feat/horizontal-filter-bar-gearmenu
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3795f8abc75c4108be9b367e51970e8ee62c59f5
Author: geido <di...@gmail.com>
AuthorDate: Thu Oct 27 19:31:44 2022 +0300

    Add component
---
 .../DropdownSelectableIcon.stories.tsx             |  65 +++++++++++++
 .../DropdownSelectableIcon.test.tsx                |  99 +++++++++++++++++++
 .../components/DropdownSelectableIcon/index.tsx    | 108 +++++++++++++++++++++
 superset-frontend/src/components/Menu/index.tsx    |   3 +
 .../nativeFilters/FilterBar/Header/index.tsx       |  23 +++++
 5 files changed, 298 insertions(+)

diff --git a/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.stories.tsx b/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.stories.tsx
new file mode 100644
index 0000000000..1b5bfbc520
--- /dev/null
+++ b/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.stories.tsx
@@ -0,0 +1,65 @@
+/**
+ * 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 Icons from 'src/components/Icons';
+import DropdownSelectableIcon, { DropDownSelectableProps } from '.';
+
+export default {
+  title: 'DropdownSelectableIcon',
+  component: DropdownSelectableIcon,
+};
+
+export const Component = (props: DropDownSelectableProps) => (
+  <DropdownSelectableIcon
+    {...props}
+    icon={<Icons.Gear name="gear" iconColor="#000000" />}
+  />
+);
+
+Component.story = {
+  parameters: {
+    knobs: {
+      disable: true,
+    },
+  },
+};
+
+Component.args = {
+  info: 'Info go here',
+  selectedKeys: ['vertical'],
+  menuItems: [
+    {
+      key: 'vertical',
+      label: 'Vertical',
+    },
+    {
+      key: 'horizontal',
+      label: 'Horizontal',
+    },
+  ],
+};
+
+Component.argTypes = {
+  onSelect: {
+    action: 'onSelect',
+    table: {
+      disable: true,
+    },
+  },
+};
diff --git a/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.test.tsx b/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.test.tsx
new file mode 100644
index 0000000000..51007d96ff
--- /dev/null
+++ b/superset-frontend/src/components/DropdownSelectableIcon/DropdownSelectableIcon.test.tsx
@@ -0,0 +1,99 @@
+/**
+ * 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 { render, screen, waitFor } from 'spec/helpers/testing-library';
+import Icons from 'src/components/Icons';
+import userEvent from '@testing-library/user-event';
+import DropdownSelectableIcon, { DropDownSelectableProps } from '.';
+
+const mockedProps = {
+  menuItems: [
+    {
+      key: 'vertical',
+      label: 'vertical',
+    },
+    {
+      key: 'horizontal',
+      label: 'horizontal',
+    },
+  ],
+  selectedKeys: [],
+  icon: <Icons.Gear name="gear" />,
+};
+
+const asyncRender = (props: DropDownSelectableProps) =>
+  waitFor(() => render(<DropdownSelectableIcon {...props} />));
+
+const openMenu = () => {
+  userEvent.click(screen.getByRole('img', { name: 'gear' }));
+};
+
+test('should render', async () => {
+  const { container } = await asyncRender(mockedProps);
+  expect(container).toBeInTheDocument();
+});
+
+test('should render the icon', async () => {
+  await asyncRender(mockedProps);
+  expect(screen.getByRole('img', { name: 'gear' })).toBeInTheDocument();
+});
+
+test('should not render the info', async () => {
+  await asyncRender(mockedProps);
+  openMenu();
+  expect(
+    screen.queryByTestId('dropdown-selectable-info'),
+  ).not.toBeInTheDocument();
+});
+
+test('should render the info', async () => {
+  const infoProps = {
+    ...mockedProps,
+    info: 'Test',
+  };
+  await asyncRender(infoProps);
+  openMenu();
+  expect(screen.getByTestId('dropdown-selectable-info')).toBeInTheDocument();
+  expect(screen.getByText('Test')).toBeInTheDocument();
+});
+
+test('should render the menu items', async () => {
+  await asyncRender(mockedProps);
+  openMenu();
+  expect(screen.getAllByRole('menuitem')).toHaveLength(2);
+  expect(screen.getByText('vertical')).toBeInTheDocument();
+  expect(screen.getByText('horizontal')).toBeInTheDocument();
+});
+
+test('should not render any selected menu item', async () => {
+  await asyncRender(mockedProps);
+  openMenu();
+  expect(screen.getAllByRole('menuitem')).toHaveLength(2);
+  expect(screen.queryByRole('img', { name: 'check' })).not.toBeInTheDocument();
+});
+
+test('should render the selected menu items', async () => {
+  const selectedProps = {
+    ...mockedProps,
+    selectedKeys: ['vertical'],
+  };
+  await asyncRender(selectedProps);
+  openMenu();
+  expect(screen.getByRole('img', { name: 'check' })).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
new file mode 100644
index 0000000000..228c6889a2
--- /dev/null
+++ b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
@@ -0,0 +1,108 @@
+/**
+ * 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 { styled, useTheme } from '@superset-ui/core';
+import React, { RefObject, useMemo } from 'react';
+import Icons from 'src/components/Icons';
+import { DropdownButton } from 'src/components/DropdownButton';
+import { DropdownButtonProps } from 'antd/lib/dropdown';
+import { Menu, MenuProps } from 'src/components/Menu';
+
+export interface DropDownSelectableProps extends Pick<MenuProps, 'onSelect'> {
+  ref?: RefObject<HTMLDivElement>;
+  icon: React.ReactNode;
+  info?: string;
+  menuItems: { key: string; label: React.ReactNode }[];
+  selectedKeys?: string[];
+}
+
+const StyledDropdownButton = styled(
+  DropdownButton as React.FC<DropdownButtonProps>,
+)`
+  button.ant-btn:first-of-type {
+    display: none;
+  }
+  > button.ant-btn:nth-child(2) {
+    display: inline-flex;
+    background-color: transparent !important;
+    height: unset;
+    padding: 0;
+    border: none;
+
+    .anticon {
+      line-height: 0;
+    }
+    &:after {
+      box-shadow: none !important;
+    }
+  }
+`;
+
+const StyledMenu = styled(Menu)`
+  ${({ theme }) => `
+    .info {
+      font-size: ${theme.typography.sizes.s}px;
+      color: ${theme.colors.grayscale.base};
+      padding: ${theme.gridUnit}px ${theme.gridUnit * 3}px ${
+    theme.gridUnit
+  }px ${theme.gridUnit * 3}px;
+    }
+    .ant-dropdown-menu-item-selected {
+      color: ${theme.colors.grayscale.dark1};
+      background-color: ${theme.colors.primary.light5};
+    }
+    .ant-dropdown-menu-item > span.anticon {
+      float: right;
+      margin-right: 0;
+      font-size: ${theme.typography.sizes.xl}px;
+    }
+  `}
+`;
+
+export default (props: DropDownSelectableProps) => {
+  const theme = useTheme();
+  const { icon, info, menuItems, selectedKeys, onSelect } = props;
+  const overlayMenu = useMemo(
+    () => (
+      <StyledMenu selectedKeys={selectedKeys} onSelect={onSelect} selectable>
+        {info && (
+          <div className="info" data-test="dropdown-selectable-info">
+            {info}
+          </div>
+        )}
+        {menuItems.map(m => (
+          <Menu.Item key={m.key}>
+            {m.label}
+            {selectedKeys?.includes(m.key) && (
+              <Icons.Check iconColor={theme.colors.primary.base} />
+            )}
+          </Menu.Item>
+        ))}
+      </StyledMenu>
+    ),
+    [info, menuItems],
+  );
+
+  return (
+    <StyledDropdownButton
+      overlay={overlayMenu}
+      trigger={['click']}
+      icon={icon}
+    />
+  );
+};
diff --git a/superset-frontend/src/components/Menu/index.tsx b/superset-frontend/src/components/Menu/index.tsx
index 73858bc92e..a7061b47e1 100644
--- a/superset-frontend/src/components/Menu/index.tsx
+++ b/superset-frontend/src/components/Menu/index.tsx
@@ -18,6 +18,9 @@
  */
 import { styled } from '@superset-ui/core';
 import { Menu as AntdMenu } from 'antd';
+import { MenuProps as AntdMenuProps } from 'antd/lib/menu';
+
+export type MenuProps = AntdMenuProps;
 
 const MenuItem = styled(AntdMenu.Item)`
   > a {
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
index 45019f58cd..bd90660149 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
@@ -25,6 +25,7 @@ import { useSelector } from 'react-redux';
 import FilterConfigurationLink from 'src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink';
 import { useFilters } from 'src/dashboard/components/nativeFilters/FilterBar/state';
 import { RootState } from 'src/dashboard/types';
+import DropdownSelectableIcon from 'src/components/DropdownSelectableIcon';
 import { getFilterBarTestId } from '..';
 
 const TitleArea = styled.h4`
@@ -41,6 +42,10 @@ const TitleArea = styled.h4`
 
 const HeaderButton = styled(Button)`
   padding: 0;
+
+  .anticon {
+    padding-top: ${({ theme }) => `${theme.gridUnit + 2}px`};
+  }
 `;
 
 const Wrapper = styled.div`
@@ -86,6 +91,24 @@ const Header: FC<HeaderProps> = ({ toggleFiltersBar }) => {
     <Wrapper>
       <TitleArea>
         <span>{t('Filters')}</span>
+        <DropdownSelectableIcon
+          onSelect={item => console.log('Selected item', item)}
+          info={t('Placement of filter bar')}
+          icon={
+            <Icons.Gear name="gear" iconColor={theme.colors.grayscale.base} />
+          }
+          menuItems={[
+            {
+              key: 'vertical',
+              label: t('Vertical (Left)'),
+            },
+            {
+              key: 'horizontal',
+              label: t('Horizontal (Top)'),
+            },
+          ]}
+          selectedKeys={['vertical']}
+        />
         <HeaderButton
           {...getFilterBarTestId('collapse-button')}
           buttonStyle="link"