+
+
+
);
}
diff --git a/superset-frontend/src/components/Menu/MenuObject.tsx b/superset-frontend/src/components/Menu/MenuObject.tsx
deleted file mode 100644
index 012172d8910..00000000000
--- a/superset-frontend/src/components/Menu/MenuObject.tsx
+++ /dev/null
@@ -1,94 +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 React, { useState } from 'react';
-import { Link } from 'react-router-dom';
-import { NavItem } from 'react-bootstrap';
-import { Menu } from 'src/common/components';
-import NavDropdown from '../NavDropdown';
-
-export interface MenuObjectChildProps {
- label: string;
- name?: string;
- icon: string;
- index: number;
- url?: string;
- isFrontendRoute?: boolean;
-}
-
-export interface MenuObjectProps extends MenuObjectChildProps {
- childs?: (MenuObjectChildProps | string)[];
- isHeader?: boolean;
-}
-
-export default function MenuObject({
- label,
- childs,
- url,
- index,
- isFrontendRoute,
-}: MenuObjectProps) {
- const [dropdownOpen, setDropdownOpen] = useState(false);
- if (url && isFrontendRoute) {
- return (
-
-
- {label}
-
-
- );
- }
- if (url) {
- return (
-
- {label}
-
- );
- }
-
- return (
- setDropdownOpen(true)}
- onMouseLeave={() => setDropdownOpen(false)}
- onToggle={value => setDropdownOpen(value)}
- open={dropdownOpen}
- >
-
-
- );
-}
diff --git a/superset-frontend/src/components/Menu/MenuRight.tsx b/superset-frontend/src/components/Menu/MenuRight.tsx
new file mode 100644
index 00000000000..26985d6d71c
--- /dev/null
+++ b/superset-frontend/src/components/Menu/MenuRight.tsx
@@ -0,0 +1,181 @@
+/**
+ * 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 { MainNav as Menu } from 'src/common/components';
+import { t, styled, css, SupersetTheme } from '@superset-ui/core';
+import { Link } from 'react-router-dom';
+import Icon from 'src/components/Icon';
+import LanguagePicker from './LanguagePicker';
+import { NavBarProps, MenuObjectProps } from './Menu';
+
+export const dropdownItems = [
+ {
+ label: t('SQL query'),
+ url: '/superset/sqllab',
+ icon: 'fa-fw fa-search',
+ },
+ {
+ label: t('Chart'),
+ url: '/chart/add',
+ icon: 'fa-fw fa-bar-chart',
+ },
+ {
+ label: t('Dashboard'),
+ url: '/dashboard/new',
+ icon: 'fa-fw fa-dashboard',
+ },
+];
+
+const versionInfoStyles = (theme: SupersetTheme) => css`
+ padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 4}px
+ ${theme.gridUnit * 1.5}px ${theme.gridUnit * 7}px;
+ color: ${theme.colors.grayscale.base};
+ font-size: ${theme.typography.sizes.xs}px;
+ white-space: nowrap;
+`;
+const StyledI = styled.div`
+ color: ${({ theme }) => theme.colors.primary.dark1};
+`;
+
+const { SubMenu } = Menu;
+
+interface RightMenuProps {
+ settings: MenuObjectProps[];
+ navbarRight: NavBarProps;
+ isFrontendRoute: (path?: string) => boolean;
+}
+
+const RightMenu = ({
+ settings,
+ navbarRight,
+ isFrontendRoute,
+}: RightMenuProps) => (
+
+);
+
+export default RightMenu;
diff --git a/superset-frontend/src/components/Menu/NewMenu.test.tsx b/superset-frontend/src/components/Menu/NewMenu.test.tsx
deleted file mode 100644
index b325370cec3..00000000000
--- a/superset-frontend/src/components/Menu/NewMenu.test.tsx
+++ /dev/null
@@ -1,34 +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 React from 'react';
-import { render, screen } from 'spec/helpers/testing-library';
-import NewMenu, { dropdownItems } from './NewMenu';
-
-test('should render', () => {
- const { container } = render();
- expect(container).toBeInTheDocument();
-});
-
-test('should render the dropdown items', () => {
- render();
- dropdownItems.forEach(item => {
- expect(screen.getByText(item.label)).toHaveAttribute('href', item.url);
- expect(screen.getByTestId(`menu-item-${item.label}`)).toBeInTheDocument();
- });
-});
diff --git a/superset-frontend/src/components/Menu/NewMenu.tsx b/superset-frontend/src/components/Menu/NewMenu.tsx
deleted file mode 100644
index 8ff46d29538..00000000000
--- a/superset-frontend/src/components/Menu/NewMenu.tsx
+++ /dev/null
@@ -1,73 +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 React, { useState } from 'react';
-import { t, styled } from '@superset-ui/core';
-import { Menu } from 'src/common/components';
-import NavDropdown from 'src/components/NavDropdown';
-
-export const dropdownItems = [
- {
- label: t('SQL query'),
- url: '/superset/sqllab',
- icon: 'fa-fw fa-search',
- },
- {
- label: t('Chart'),
- url: '/chart/add',
- icon: 'fa-fw fa-bar-chart',
- },
- {
- label: t('Dashboard'),
- url: '/dashboard/new',
- icon: 'fa-fw fa-dashboard',
- },
-];
-const StyledI = styled.div`
- color: ${({ theme }) => theme.colors.primary.dark1};
-`;
-
-export default function NewMenu() {
- const [dropdownOpen, setDropdownOpen] = useState(false);
-
- return (
- }
- onMouseEnter={() => setDropdownOpen(true)}
- onMouseLeave={() => setDropdownOpen(false)}
- onToggle={value => setDropdownOpen(value)}
- open={dropdownOpen}
- >
-
-
- );
-}
diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx
index f8f9e3a9d34..27f4b86d894 100644
--- a/superset-frontend/src/components/Menu/SubMenu.tsx
+++ b/superset-frontend/src/components/Menu/SubMenu.tsx
@@ -16,35 +16,65 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { ReactNode } from 'react';
+import React, { ReactNode, useState, useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { styled } from '@superset-ui/core';
import cx from 'classnames';
-import { Nav, Navbar } from 'react-bootstrap';
+import { debounce } from 'lodash';
+import { Col, Row } from 'antd';
+import { Menu, MenuMode } from 'src/common/components';
import Button, { OnClickHandler } from 'src/components/Button';
-const StyledHeader = styled.header`
+const StyledHeader = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
- .navbar {
- margin-bottom: 0;
- }
- .navbar-header .navbar-brand {
+ .header {
font-weight: ${({ theme }) => theme.typography.weights.bold};
margin-right: ${({ theme }) => theme.gridUnit * 3}px;
+ text-align: left;
+ font-size: 18px;
+ padding: ${({ theme }) => theme.gridUnit * 3}px;
+ display: inline-block;
+ line-height: ${({ theme }) => theme.gridUnit * 9}px;
}
- .navbar-right {
+ .nav-right {
display: flex;
align-items: center;
- padding: 8px 0;
- margin-right: 0;
+ padding: 14px 0;
+ margin-right: ${({ theme }) => theme.gridUnit * 3}px;
+ float: right;
}
- .navbar-nav {
+ .nav-right-collapse {
+ display: flex;
+ align-items: center;
+ padding: 14px 0;
+ margin-right: 0;
+ float: left;
+ padding-left: 10px;
+ }
+ .menu {
+ background-color: white;
+ .ant-menu-horizontal {
+ line-height: inherit;
+ .ant-menu-item {
+ &:hover {
+ border-bottom: none;
+ }
+ }
+ }
+ .ant-menu {
+ padding: ${({ theme }) => theme.gridUnit * 4}px 0px;
+ }
+ }
+
+ .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item {
+ margin: 0 ${({ theme }) => theme.gridUnit + 1}px;
+ }
+
+ .menu .ant-menu-item {
li {
a,
div {
font-size: ${({ theme }) => theme.typography.sizes.s}px;
- padding: ${({ theme }) => theme.gridUnit * 2}px 0;
- margin: ${({ theme }) => theme.gridUnit * 2}px;
color: ${({ theme }) => theme.colors.secondary.dark1};
a {
@@ -68,23 +98,23 @@ const StyledHeader = styled.header`
border-bottom: none;
border-radius: ${({ theme }) => theme.borderRadius}px;
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
- }
- }
- .navbar-inverse {
- .navbar-nav {
- & > .active > a {
- background: ${({ theme }) => theme.colors.secondary.light4};
- &:hover,
- &:focus {
- background: ${({ theme }) => theme.colors.secondary.light4};
- }
- }
+ text-decoration: none;
}
}
.btn-link {
padding: 10px 0;
}
+ .ant-menu-horizontal {
+ border: none;
+ }
+ @media (max-width: 767px) {
+ .header,
+ .nav-right {
+ float: left;
+ padding-left: ${({ theme }) => theme.gridUnit * 2}px;
+ }
+ }
`;
type MenuChild = {
@@ -119,9 +149,16 @@ export interface SubMenuProps {
* ONLY set usesRouter to true if SubMenu is wrapped in a react-router ;
* otherwise, a 'You should not use outside a ' error will be thrown */
usesRouter?: boolean;
+ color?: string;
+ headerSize?: number;
}
-const SubMenu: React.FunctionComponent = props => {
+const SubMenuComponent: React.FunctionComponent = props => {
+ const [showMenu, setMenu] = useState('horizontal');
+ const [navRightStyle, setNavRightStyle] = useState('nav-right');
+ const [navRightCol, setNavRightCol] = useState(8);
+
+ const { headerSize = 2 } = props;
let hasHistory = true;
// If no parent component exists, useHistory throws an error
try {
@@ -130,64 +167,101 @@ const SubMenu: React.FunctionComponent = props => {
// If error is thrown, we know not to use in render
hasHistory = false;
}
+
+ useEffect(() => {
+ function handleResize() {
+ if (window.innerWidth <= 767) setMenu('inline');
+ else setMenu('horizontal');
+
+ if (
+ props.buttons &&
+ props.buttons.length >= 3 &&
+ window.innerWidth >= 795
+ ) {
+ setNavRightCol(8);
+ setNavRightStyle('nav-right');
+ } else if (
+ props.buttons &&
+ props.buttons.length >= 3 &&
+ window.innerWidth <= 795
+ ) {
+ setNavRightCol(24);
+ setNavRightStyle('nav-right-collapse');
+ }
+ }
+ handleResize();
+ const resize = debounce(handleResize, 10);
+ window.addEventListener('resize', resize);
+ return () => window.removeEventListener('resize', resize);
+ }, [props.buttons]);
+
+ const offset = props.name ? headerSize : 0;
+
return (
-
-
- {props.name}
-
-
-
);
};
-export default SubMenu;
+export default SubMenuComponent;
diff --git a/superset-frontend/src/dashboard/stylesheets/dashboard.less b/superset-frontend/src/dashboard/stylesheets/dashboard.less
index 432dd4b71b9..a3409c4d48b 100644
--- a/superset-frontend/src/dashboard/stylesheets/dashboard.less
+++ b/superset-frontend/src/dashboard/stylesheets/dashboard.less
@@ -18,7 +18,7 @@
*/
/* header has mysterious extra margin */
header.top {
- margin-bottom: -20px;
+ margin-bottom: 2px;
}
body {
diff --git a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx
index 55f7da5a4c3..f3dc3a873d2 100644
--- a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx
+++ b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayersList.tsx
@@ -344,7 +344,11 @@ function AnnotationLayersList({
return (
<>
-
+
-
+ {
expect(wrapper.find(ActivityTable)).toExist();
});
it('renders tabs with three buttons', () => {
- expect(wrapper.find('li')).toHaveLength(3);
+ expect(wrapper.find('li.no-router')).toHaveLength(3);
});
it('renders ActivityCards', async () => {
expect(wrapper.find('ListViewCard')).toExist();
@@ -119,7 +119,7 @@ describe('ActivityTable', () => {
expect(chartCall).toHaveLength(1);
expect(dashboardCall).toHaveLength(1);
});
- it('show empty state if there is data', () => {
+ it('show empty state if there is no data', () => {
const activityProps = {
activeChild: 'Created',
activityData: {},
diff --git a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
index 7cea906aa7d..8df7f12489a 100644
--- a/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/ActivityTable.tsx
@@ -239,14 +239,12 @@ export default function ActivityTable({
// eslint-disable-next-line react/no-children-prop
tabs={tabs}
/>
- <>
- {activityData[activeChild]?.length > 0 ||
- (activeChild === 'Edited' && editedObjs && editedObjs.length > 0) ? (
- {renderActivity()}
- ) : (
-
- )}
- >
+ {activityData[activeChild]?.length > 0 ||
+ (activeChild === 'Edited' && editedObjs && editedObjs.length > 0) ? (
+ {renderActivity()}
+ ) : (
+
+ )}
>
);
}
diff --git a/superset-frontend/src/views/CRUD/welcome/DashboardTable.test.tsx b/superset-frontend/src/views/CRUD/welcome/DashboardTable.test.tsx
index 4146ab80f8f..078b14d0e21 100644
--- a/superset-frontend/src/views/CRUD/welcome/DashboardTable.test.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/DashboardTable.test.tsx
@@ -71,7 +71,7 @@ describe('DashboardTable', () => {
it('render a submenu with clickable tabs and buttons', async () => {
expect(wrapper.find('SubMenu')).toExist();
- expect(wrapper.find('li')).toHaveLength(2);
+ expect(wrapper.find('li.no-router')).toHaveLength(2);
expect(wrapper.find('Button')).toHaveLength(4);
act(() => {
const handler = wrapper.find('li.no-router a').at(1).prop('onClick');
diff --git a/superset-frontend/src/views/CRUD/welcome/SavedQueries.test.tsx b/superset-frontend/src/views/CRUD/welcome/SavedQueries.test.tsx
index 8a57b0991f9..058e990887e 100644
--- a/superset-frontend/src/views/CRUD/welcome/SavedQueries.test.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/SavedQueries.test.tsx
@@ -105,7 +105,7 @@ describe('SavedQueries', () => {
it('renders a submenu with clickable tables and buttons', async () => {
expect(wrapper.find(SubMenu)).toExist();
- expect(wrapper.find('li')).toHaveLength(1);
+ expect(wrapper.find('li.no-router')).toHaveLength(1);
expect(wrapper.find('button')).toHaveLength(2);
clickTab(0);
await waitForComponentToPaint(wrapper);
diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
index 83719c489e3..4f394c93ea9 100644
--- a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
@@ -55,7 +55,7 @@ export interface ActivityData {
const WelcomeContainer = styled.div`
background-color: ${({ theme }) => theme.colors.grayscale.light4};
- nav {
+ .ant-row.menu {
margin-top: -15px;
background-color: ${({ theme }) => theme.colors.grayscale.light4};
&:after {
@@ -64,25 +64,18 @@ const WelcomeContainer = styled.div`
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
margin: 0px ${({ theme }) => theme.gridUnit * 6}px;
position: relative;
+ width: 100%;
${[mq[1]]} {
margin-top: 5px;
margin: 0px 2px;
}
}
- .nav.navbar-nav {
- & > li:nth-of-type(1),
- & > li:nth-of-type(2),
- & > li:nth-of-type(3) {
- margin-top: ${({ theme }) => theme.gridUnit * 2}px;
- }
+ .ant-menu.ant-menu-light.ant-menu-root.ant-menu-horizontal {
+ padding-left: ${({ theme }) => theme.gridUnit * 10}px;
}
button {
padding: 3px 21px;
}
- .navbar-right {
- position: relative;
- top: 11px;
- }
}
.ant-card.ant-card-bordered {
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
@@ -92,7 +85,6 @@ const WelcomeContainer = styled.div`
const WelcomeNav = styled.div`
height: 50px;
background-color: white;
- margin-top: ${({ theme }) => theme.gridUnit * -4 - 1}px;
.navbar-brand {
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
font-weight: ${({ theme }) => theme.typography.weights.bold};
diff --git a/superset-frontend/stylesheets/superset.less b/superset-frontend/stylesheets/superset.less
index ecfdd017f61..4c5cc224a35 100644
--- a/superset-frontend/stylesheets/superset.less
+++ b/superset-frontend/stylesheets/superset.less
@@ -582,3 +582,28 @@ hr {
background-image: url('../images/icons/error_solid_small_red.svg') !important;
background-position: -2px center !important;
}
+
+// AntD overrides since these are injected as inline styles and can't
+// be overriden in emotion
+.ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light.ant-menu-submenu-placement-bottomLeft {
+ top: 51px !important;
+ margin-left: -2px !important;
+ @media (max-width: 767px) {
+ top: 269px !important;
+ }
+ & > .ant-menu {
+ border-radius: 0px !important;
+ }
+}
+
+.ant-menu-submenu.ant-menu-submenu-popup.ant-menu.ant-menu-light {
+ top: 51px !important;
+ border-radius: 0px !important;
+ @media (max-width: 767px) {
+ top: 269px !important;
+ }
+}
+
+.ant-dropdown.ant-dropdown-placement-bottomRight {
+ top: 133px !important;
+}