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" />
-          &nbsp;
-        </a>
-      </Menu.Item>
+      <a
+        href={navbarRight.documentation_url}
+        target="_blank"
+        rel="noreferrer"
+        title={t('Documentation')}
+      >
+        <i className="fa fa-question" />
+        &nbsp;
+      </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>
-        &nbsp;
-      </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));
   };
 

Reply via email to