From d95a4d300f6ddd386f3e0710ce9cdfde90dfbcff Mon Sep 17 00:00:00 2001
From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com>
Date: Thu, 13 May 2021 18:54:09 -0300
Subject: [PATCH] refactor: Bootstrap to AntD - NavDropdown (#14557)
---
.../src/components/EditableTitle/index.tsx | 9 +-
.../components/Menu/LanguagePicker.test.tsx | 13 +-
.../src/components/Menu/LanguagePicker.tsx | 105 ++++++---
.../src/components/Menu/Menu.test.tsx | 8 +-
.../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(-)
delete mode 100644 superset-frontend/src/components/NavDropdown/index.tsx
diff --git a/superset-frontend/src/components/EditableTitle/index.tsx b/superset-frontend/src/components/EditableTitle/index.tsx
index f1685daca48f..ec2245b05ef8 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 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 ad494d620ca8..c82cd5069827 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();
- 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();
- 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 63f6e6bae1bf..ec271d064a94 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: (
+
+ {' '}
+ {languages[langKey].name}
+
+ ),
+ value: langKey,
+ flag: (
+
+
+
+ ),
+ }));
return (
- setDropdownOpen(true)}
- onMouseLeave={() => setDropdownOpen(false)}
- onToggle={value => setDropdownOpen(value)}
- open={dropdownOpen}
- id="locale-dropdown"
- title={
-
-
-
+
+ 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 add4344b4d14..95f8bd53d9cb 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(
);
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();
- 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();
- 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 d9385230996f..278c3337d6ad 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 (
-
+
@@ -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({
})}
-
+
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) => (
-
{navbarRight.documentation_url && (
-
-
-
-
-
-
+
+
+
+
)}
{navbarRight.bug_report_url && (
-
-
-
-
-
-
+
+
+
)}
{navbarRight.show_language_picker && (
-
-
-
+
)}
{navbarRight.user_is_anonymous && (
-
-
-
- {t('Login')}
-
-
+
+
+ {t('Login')}
+
)}
-
+
);
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 74d473c47824..000000000000
--- 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 6322e8842b1d..fa75d780890c 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 = () => {
const handleChangeTab = ({
pathToTabIndex,
- }: SyntheticEvent & { pathToTabIndex: string[] }) => {
+ }: {
+ pathToTabIndex: string[];
+ }) => {
dispatch(setDirectPathToChild(pathToTabIndex));
};