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 <[email protected]> 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"
