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"