This is an automated email from the ASF dual-hosted git repository.
rusackas 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 e4d2424 refactor: Bootstrap to AntD - NavDropdown (#14557)
e4d2424 is described below
commit e4d24242b4ded30f7a80488b4628d0832b30561f
Author: Michael S. Molina <[email protected]>
AuthorDate: Thu May 13 18:54:09 2021 -0300
refactor: Bootstrap to AntD - NavDropdown (#14557)
---
.../src/components/EditableTitle/index.tsx | 9 +-
.../src/components/Menu/LanguagePicker.test.tsx | 13 +-
.../src/components/Menu/LanguagePicker.tsx | 105 ++++++----
.../src/components/Menu/Menu.test.tsx | 8 +-
superset-frontend/src/components/Menu/Menu.tsx | 6 +-
.../src/components/Menu/MenuRight.tsx | 214 +++++++++++----------
.../src/components/NavDropdown/index.tsx | 63 ------
.../DashboardBuilder/DashboardBuilder.tsx | 7 +-
8 files changed, 206 insertions(+), 219 deletions(-)
diff --git a/superset-frontend/src/components/EditableTitle/index.tsx
b/superset-frontend/src/components/EditableTitle/index.tsx
index f1685da..ec2245b 100644
--- a/superset-frontend/src/components/EditableTitle/index.tsx
+++ b/superset-frontend/src/components/EditableTitle/index.tsx
@@ -110,12 +110,9 @@ export default function EditableTitle({
}
}
- // this entire method exists to support using EditableTitle as the title of a
- // react-bootstrap Tab, as a workaround for this line in react-bootstrap
https://goo.gl/ZVLmv4
- //
- // tl;dr when a Tab EditableTitle is being edited, typically the Tab it's
within has been
- // clicked and is focused/active. for accessibility, when focused the Tab <a
/> intercepts
- // the ' ' key (among others, including all arrows) and onChange() doesn't
fire. somehow
+ // tl;dr when a EditableTitle is being edited, typically the Tab that wraps
it has been
+ // clicked and is focused/active. For accessibility, when the focused tab
anchor intercepts
+ // the ' ' key (among others, including all arrows) the onChange() doesn't
fire. Somehow
// keydown is still called so we can detect this and manually add a ' ' to
the current title
function handleKeyDown(event: any) {
if (event.key === ' ') {
diff --git a/superset-frontend/src/components/Menu/LanguagePicker.test.tsx
b/superset-frontend/src/components/Menu/LanguagePicker.test.tsx
index ad494d6..c82cd50 100644
--- a/superset-frontend/src/components/Menu/LanguagePicker.test.tsx
+++ b/superset-frontend/src/components/Menu/LanguagePicker.test.tsx
@@ -18,6 +18,7 @@
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
import LanguagePicker from './LanguagePicker';
const mockedProps = {
@@ -41,14 +42,14 @@ test('should render', () => {
expect(container).toBeInTheDocument();
});
-test('should render the button', () => {
+test('should render the combobox', () => {
render(<LanguagePicker {...mockedProps} />);
- const button = screen.getByRole('button');
- expect(button).toHaveAttribute('href', '#');
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
});
-test('should render the menuitem', () => {
+test('should render the items', async () => {
render(<LanguagePicker {...mockedProps} />);
- const menuitem = screen.getByRole('menuitem');
- expect(menuitem).toHaveTextContent('Italian');
+ userEvent.click(screen.getByRole('combobox'));
+ expect(await screen.findByText('English')).toBeInTheDocument();
+ expect(await screen.findByText('Italian')).toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/Menu/LanguagePicker.tsx
b/superset-frontend/src/components/Menu/LanguagePicker.tsx
index 63f6e6b..ec271d0 100644
--- a/superset-frontend/src/components/Menu/LanguagePicker.tsx
+++ b/superset-frontend/src/components/Menu/LanguagePicker.tsx
@@ -17,8 +17,9 @@
* under the License.
*/
import React, { useState } from 'react';
-import { Menu } from 'src/common/components';
-import NavDropdown from 'src/components/NavDropdown';
+import { Select } from 'src/common/components';
+import { styled, useTheme } from '@superset-ui/core';
+import Icons from 'src/components/Icons';
export interface Languages {
[key: string]: {
@@ -33,43 +34,81 @@ interface LanguagePickerProps {
languages: Languages;
}
+const dropdownWidth = 150;
+
+const StyledLabel = styled.div`
+ display: flex;
+ align-items: center;
+
+ & i {
+ margin-right: ${({ theme }) => theme.gridUnit}px;
+ }
+
+ & span {
+ display: block;
+ width: ${dropdownWidth}px;
+ word-wrap: break-word;
+ white-space: normal;
+ }
+`;
+
+const StyledFlag = styled.div`
+ margin-top: 2px;
+`;
+
+const StyledIcon = styled(Icons.TriangleDown)`
+ ${({ theme }) => `
+ margin-top: -${theme.gridUnit}px;
+ margin-left: -${theme.gridUnit * 2}px;
+ `}
+`;
+
export default function LanguagePicker({
locale,
languages,
}: LanguagePickerProps) {
- const [dropdownOpen, setDropdownOpen] = useState(false);
+ const theme = useTheme();
+ const [open, setOpen] = useState(false);
+
+ const options = Object.keys(languages).map(langKey => ({
+ label: (
+ <StyledLabel className="f16">
+ <i className={`flag ${languages[langKey].flag}`} />{' '}
+ <span>{languages[langKey].name}</span>
+ </StyledLabel>
+ ),
+ value: langKey,
+ flag: (
+ <StyledFlag className="f16">
+ <i className={`flag ${languages[langKey].flag}`} />
+ </StyledFlag>
+ ),
+ }));
return (
- <NavDropdown
- onMouseEnter={() => setDropdownOpen(true)}
- onMouseLeave={() => setDropdownOpen(false)}
- onToggle={value => setDropdownOpen(value)}
- open={dropdownOpen}
- id="locale-dropdown"
- title={
- <span className="f16">
- <i className={`flag ${languages[locale].flag}`} />
- </span>
+ <Select
+ defaultValue={locale}
+ open={open}
+ onMouseEnter={() => setOpen(true)}
+ onMouseLeave={() => setOpen(false)}
+ onDropdownVisibleChange={open => setOpen(open)}
+ bordered={false}
+ options={options}
+ suffixIcon={
+ <StyledIcon
+ iconColor={theme.colors.grayscale.base}
+ className="ant-select-suffix"
+ />
}
- data-test="language-picker"
- >
- <Menu
- onSelect={({ key }) => {
- window.location.href = languages[key].url;
- }}
- >
- {Object.keys(languages).map(langKey =>
- langKey === locale ? null : (
- <Menu.Item key={langKey}>
- {' '}
- <div className="f16">
- <i className={`flag ${languages[langKey].flag}`} /> -{' '}
- {languages[langKey].name}
- </div>
- </Menu.Item>
- ),
- )}
- </Menu>
- </NavDropdown>
+ listHeight={400}
+ dropdownAlign={{
+ offset: [-dropdownWidth, 0],
+ }}
+ optionLabelProp="flag"
+ dropdownMatchSelectWidth={false}
+ onChange={(value: string) => {
+ window.location.href = languages[value].url;
+ }}
+ />
);
}
diff --git a/superset-frontend/src/components/Menu/Menu.test.tsx
b/superset-frontend/src/components/Menu/Menu.test.tsx
index add4344..95f8bd5 100644
--- a/superset-frontend/src/components/Menu/Menu.test.tsx
+++ b/superset-frontend/src/components/Menu/Menu.test.tsx
@@ -303,7 +303,7 @@ test('should render the Documentation link when available',
async () => {
render(<Menu {...mockedProps} />);
userEvent.hover(screen.getByText('Settings'));
const doc = await screen.findByTitle('Documentation');
- expect(doc.firstChild).toHaveAttribute('href', documentation_url);
+ expect(doc).toHaveAttribute('href', documentation_url);
});
test('should render the Bug Report link when available', async () => {
@@ -314,8 +314,8 @@ test('should render the Bug Report link when available',
async () => {
} = mockedProps;
render(<Menu {...mockedProps} />);
- const bugReport = await screen.findByTitle('Report a Bug');
- expect(bugReport.firstChild).toHaveAttribute('href', bug_report_url);
+ const bugReport = await screen.findByTitle('Report a bug');
+ expect(bugReport).toHaveAttribute('href', bug_report_url);
});
test('should render the Login link when user is anonymous', () => {
@@ -332,5 +332,5 @@ test('should render the Login link when user is anonymous',
() => {
test('should render the Language Picker', () => {
render(<Menu {...mockedProps} />);
- expect(screen.getByTestId('language-picker')).toBeInTheDocument();
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/Menu/Menu.tsx
b/superset-frontend/src/components/Menu/Menu.tsx
index d938523..278c333 100644
--- a/superset-frontend/src/components/Menu/Menu.tsx
+++ b/superset-frontend/src/components/Menu/Menu.tsx
@@ -201,7 +201,7 @@ export function Menu({
return (
<StyledHeader className="top" id="main-menu" role="navigation">
<Row>
- <Col lg={19} md={19} sm={24} xs={24}>
+ <Col lg={12} md={24} sm={24} xs={24}>
<a className="navbar-brand" href={brand.path}>
<img width={brand.width} src={brand.icon} alt={brand.alt} />
</a>
@@ -210,7 +210,7 @@ export function Menu({
data-test="navbar-top"
className="main-nav"
>
- {menu.map((item, index) => {
+ {menu.map(item => {
const props = {
...item,
isFrontendRoute: isFrontendRoute(item.url),
@@ -230,7 +230,7 @@ export function Menu({
})}
</DropdownMenu>
</Col>
- <Col lg={5} md={5} sm={24} xs={24}>
+ <Col lg={12} md={24} sm={24} xs={24}>
<RightMenu
settings={settings}
navbarRight={navbarRight}
diff --git a/superset-frontend/src/components/Menu/MenuRight.tsx
b/superset-frontend/src/components/Menu/MenuRight.tsx
index 26985d6..638d5f7 100644
--- a/superset-frontend/src/components/Menu/MenuRight.tsx
+++ b/superset-frontend/src/components/Menu/MenuRight.tsx
@@ -44,7 +44,7 @@ export const dropdownItems = [
const versionInfoStyles = (theme: SupersetTheme) => css`
padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 4}px
- ${theme.gridUnit * 1.5}px ${theme.gridUnit * 7}px;
+ ${theme.gridUnit * 4}px ${theme.gridUnit * 7}px;
color: ${theme.colors.grayscale.base};
font-size: ${theme.typography.sizes.xs}px;
white-space: nowrap;
@@ -53,6 +53,15 @@ const StyledI = styled.div`
color: ${({ theme }) => theme.colors.primary.dark1};
`;
+const StyledDiv = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ min-width: 360px;
+ margin-right: ${({ theme }) => theme.gridUnit * 4}px;
+`;
+
const { SubMenu } = Menu;
interface RightMenuProps {
@@ -66,116 +75,119 @@ const RightMenu = ({
navbarRight,
isFrontendRoute,
}: RightMenuProps) => (
- <Menu className="navbar-right" mode="horizontal">
- {!navbarRight.user_is_anonymous && (
- <SubMenu
- data-test="new-dropdown"
- title={<StyledI data-test="new-dropdown-icon" className="fa fa-plus"
/>}
- icon={<Icon name="triangle-down" />}
- >
- {dropdownItems.map((menu, i) => (
- <Menu.Item key={i}>
- <a href={menu.url}>
- <i
- data-test={`menu-item-${menu.label}`}
- className={`fa ${menu.icon}`}
- />{' '}
- {menu.label}
- </a>
- </Menu.Item>
- ))}
- </SubMenu>
- )}
- <SubMenu title="Settings" icon={<Icon name="triangle-down" />}>
- {settings.map((section, index) => [
- <Menu.ItemGroup key={`${section.label}`} title={section.label}>
- {section.childs?.map(child => {
- if (typeof child !== 'string') {
- return (
- <Menu.Item key={`${child.label}`}>
- {isFrontendRoute(child.url) ? (
- <Link to={child.url || ''}>{child.label}</Link>
- ) : (
- <a href={child.url}>{child.label}</a>
- )}
- </Menu.Item>
- );
- }
- return null;
- })}
- </Menu.ItemGroup>,
- index < settings.length - 1 && <Menu.Divider />,
- ])}
-
- {!navbarRight.user_is_anonymous && [
- <Menu.Divider key="user-divider" />,
- <Menu.ItemGroup key="user-section" title={t('User')}>
- {navbarRight.user_profile_url && (
- <Menu.Item key="profile">
- <a href={navbarRight.user_profile_url}>{t('Profile')}</a>
+ <StyledDiv>
+ <Menu mode="horizontal">
+ {!navbarRight.user_is_anonymous && (
+ <SubMenu
+ data-test="new-dropdown"
+ title={
+ <StyledI data-test="new-dropdown-icon" className="fa fa-plus" />
+ }
+ icon={<Icon name="triangle-down" />}
+ >
+ {dropdownItems.map(menu => (
+ <Menu.Item key={menu.label}>
+ <a href={menu.url}>
+ <i
+ data-test={`menu-item-${menu.label}`}
+ className={`fa ${menu.icon}`}
+ />{' '}
+ {menu.label}
+ </a>
</Menu.Item>
- )}
- <Menu.Item key="info">
- <a href={navbarRight.user_info_url}>{t('Info')}</a>
- </Menu.Item>
- <Menu.Item key="logout">
- <a href={navbarRight.user_logout_url}>{t('Logout')}</a>
- </Menu.Item>
- </Menu.ItemGroup>,
- ]}
- {(navbarRight.version_string || navbarRight.version_sha) && [
- <Menu.Divider key="version-info-divider" />,
- <Menu.ItemGroup key="about-section" title={t('About')}>
- <div className="about-section">
- {navbarRight.version_string && (
- <li css={versionInfoStyles}>
- Version: {navbarRight.version_string}
- </li>
- )}
- {navbarRight.version_sha && (
- <li css={versionInfoStyles}>SHA: {navbarRight.version_sha}</li>
+ ))}
+ </SubMenu>
+ )}
+ <SubMenu title="Settings" icon={<Icon name="triangle-down" />}>
+ {settings.map((section, index) => [
+ <Menu.ItemGroup key={`${section.label}`} title={section.label}>
+ {section.childs?.map(child => {
+ if (typeof child !== 'string') {
+ return (
+ <Menu.Item key={`${child.label}`}>
+ {isFrontendRoute(child.url) ? (
+ <Link to={child.url || ''}>{child.label}</Link>
+ ) : (
+ <a href={child.url}>{child.label}</a>
+ )}
+ </Menu.Item>
+ );
+ }
+ return null;
+ })}
+ </Menu.ItemGroup>,
+ index < settings.length - 1 && <Menu.Divider />,
+ ])}
+
+ {!navbarRight.user_is_anonymous && [
+ <Menu.Divider key="user-divider" />,
+ <Menu.ItemGroup key="user-section" title={t('User')}>
+ {navbarRight.user_profile_url && (
+ <Menu.Item key="profile">
+ <a href={navbarRight.user_profile_url}>{t('Profile')}</a>
+ </Menu.Item>
)}
- </div>
- </Menu.ItemGroup>,
- ]}
- </SubMenu>
+ <Menu.Item key="info">
+ <a href={navbarRight.user_info_url}>{t('Info')}</a>
+ </Menu.Item>
+ <Menu.Item key="logout">
+ <a href={navbarRight.user_logout_url}>{t('Logout')}</a>
+ </Menu.Item>
+ </Menu.ItemGroup>,
+ ]}
+ {(navbarRight.version_string || navbarRight.version_sha) && [
+ <Menu.Divider key="version-info-divider" />,
+ <Menu.ItemGroup key="about-section" title={t('About')}>
+ <div className="about-section">
+ {navbarRight.version_string && (
+ <div css={versionInfoStyles}>
+ Version: {navbarRight.version_string}
+ </div>
+ )}
+ {navbarRight.version_sha && (
+ <div css={versionInfoStyles}>
+ SHA: {navbarRight.version_sha}
+ </div>
+ )}
+ </div>
+ </Menu.ItemGroup>,
+ ]}
+ </SubMenu>
+ </Menu>
{navbarRight.documentation_url && (
- <Menu.Item title="Documentation">
- <a
- href={navbarRight.documentation_url}
- target="_blank"
- rel="noreferrer"
- >
- <i className="fa fa-question" />
-
- </a>
- </Menu.Item>
+ <a
+ href={navbarRight.documentation_url}
+ target="_blank"
+ rel="noreferrer"
+ title={t('Documentation')}
+ >
+ <i className="fa fa-question" />
+
+ </a>
)}
{navbarRight.bug_report_url && (
- <Menu.Item title="Report a Bug">
- <a href={navbarRight.bug_report_url} target="_blank" rel="noreferrer">
- <i className="fa fa-bug" />
- </a>
-
- </Menu.Item>
+ <a
+ href={navbarRight.bug_report_url}
+ target="_blank"
+ rel="noreferrer"
+ title={t('Report a bug')}
+ >
+ <i className="fa fa-bug" />
+ </a>
)}
{navbarRight.show_language_picker && (
- <Menu.Item>
- <LanguagePicker
- locale={navbarRight.locale}
- languages={navbarRight.languages}
- />
- </Menu.Item>
+ <LanguagePicker
+ locale={navbarRight.locale}
+ languages={navbarRight.languages}
+ />
)}
{navbarRight.user_is_anonymous && (
- <Menu.Item>
- <a href={navbarRight.user_login_url}>
- <i className="fa fa-fw fa-sign-in" />
- {t('Login')}
- </a>
- </Menu.Item>
+ <a href={navbarRight.user_login_url}>
+ <i className="fa fa-fw fa-sign-in" />
+ {t('Login')}
+ </a>
)}
- </Menu>
+ </StyledDiv>
);
export default RightMenu;
diff --git a/superset-frontend/src/components/NavDropdown/index.tsx
b/superset-frontend/src/components/NavDropdown/index.tsx
deleted file mode 100644
index 74d473c..0000000
--- a/superset-frontend/src/components/NavDropdown/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * 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 } from '@superset-ui/core';
-import { NavDropdown as ReactBootstrapNavDropdown } from 'react-bootstrap';
-
-const NavDropdown = styled(ReactBootstrapNavDropdown)`
- &.dropdown > a.dropdown-toggle {
- padding-right: ${({ theme }) => theme.gridUnit * 6}px;
- }
- & > a {
- transition: background-color ${({ theme }) => theme.transitionTiming}s;
- }
- &.dropdown.open > a.dropdown-toggle {
- background: ${({ theme }) => theme.colors.primary.light4};
- }
-
- :after {
- content: '';
- height: ${({ theme }) => theme.gridUnit * 6}px;
- width: ${({ theme }) => theme.gridUnit * 6}px;
- background: url('/static/assets/images/icons/triangle_down.svg');
- background-size: contain;
- background-position: center center;
- background-repeat: no-repeat;
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- right: ${({ theme }) => theme.gridUnit}px;
- transition: opacity ${({ theme }) => theme.transitionTiming}s;
- opacity: ${({ theme }) => theme.opacity.mediumLight};
- pointer-events: none;
- }
- &:hover,
- &.active {
- &:after {
- opacity: ${({ theme }) => theme.opacity.mediumHeavy};
- }
- }
- .dropdown-menu {
- padding: ${({ theme }) => theme.gridUnit}px 0;
- top: 100%;
- border: none;
- }
-`;
-
-export default NavDropdown;
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index 6322e88..fa75d78 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -18,9 +18,8 @@
*/
/* eslint-env browser */
import cx from 'classnames';
-import React, { FC, SyntheticEvent, useEffect, useState } from 'react';
+import React, { FC, useEffect, useState } from 'react';
import { Sticky, StickyContainer } from 'react-sticky';
-import { TabContainer } from 'react-bootstrap';
import { JsonObject, styled } from '@superset-ui/core';
import ErrorBoundary from 'src/components/ErrorBoundary';
import BuilderComponentPane from
'src/dashboard/components/BuilderComponentPane';
@@ -129,7 +128,9 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const handleChangeTab = ({
pathToTabIndex,
- }: SyntheticEvent<TabContainer, Event> & { pathToTabIndex: string[] }) => {
+ }: {
+ pathToTabIndex: string[];
+ }) => {
dispatch(setDirectPathToChild(pathToTabIndex));
};