diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js index a842264be2..a4c63505ab 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.js @@ -97,15 +97,24 @@ describe('Dashboard tabs', () => { cy.get('[data-test="dashboard-component-tabs"]') .first() - .find('[data-test="nav-list"]') - .children() + .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') .as('top-level-tabs'); - cy.get('@top-level-tabs').first().click().should('have.class', 'active'); - cy.get('@top-level-tabs').last().should('not.have.class', 'active'); + cy.get('@top-level-tabs') + .first() + .click() + .should('have.class', 'ant-tabs-tab-active'); + cy.get('@top-level-tabs') + .last() + .should('not.have.class', 'ant-tabs-tab-active'); - cy.get('@top-level-tabs').last().click().should('have.class', 'active'); - cy.get('@top-level-tabs').first().should('not.have.class', 'active'); + cy.get('@top-level-tabs') + .last() + .click() + .should('have.class', 'ant-tabs-tab-active'); + cy.get('@top-level-tabs') + .first() + .should('not.have.class', 'ant-tabs-tab-active'); }); it('should load charts when tab is visible', () => { @@ -128,8 +137,7 @@ describe('Dashboard tabs', () => { // click row level tab, see 1 more chart cy.get('[data-test="dashboard-component-tabs"]') .last() - .find('[data-test="nav-list"]') - .children() + .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') .as('row-level-tabs'); cy.get('@row-level-tabs').last().click(); @@ -141,8 +149,7 @@ describe('Dashboard tabs', () => { handleException(); cy.get('[data-test="dashboard-component-tabs"]') .first() - .find('[data-test="nav-list"]') - .children() + .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') .as('top-level-tabs'); cy.get('@top-level-tabs').last().click(); diff --git a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx index 98ad189007..eb90589644 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx @@ -179,7 +179,7 @@ describe('DashboardBuilder', () => { expect(wrapper.find(TabContainer).prop('activeKey')).toBe(0); wrapper - .find('.dashboard-component-tabs .nav-tabs a') + .find('.dashboard-component-tabs .ant-tabs .ant-tabs-tab') .at(1) .simulate('click'); diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx index c4d6c30403..689de44661 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx @@ -22,10 +22,8 @@ import { styledMount as mount } from 'spec/helpers/theming'; import sinon from 'sinon'; import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; -import DeleteComponentModal from 'src/dashboard/components/DeleteComponentModal'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; import EditableTitle from 'src/components/EditableTitle'; -import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; import Tab, { RENDER_TAB, RENDER_TAB_CONTENT, @@ -96,42 +94,6 @@ describe('Tabs', () => { 'New title', ); }); - - it('should render a WithPopoverMenu', () => { - const wrapper = setup(); - expect(wrapper.find(WithPopoverMenu)).toExist(); - }); - - it('should render a DeleteComponentModal when focused if its not the only tab', () => { - let wrapper = setup(); - wrapper.find(WithPopoverMenu).simulate('click'); // focus - expect(wrapper.find(DeleteComponentModal)).not.toExist(); - - wrapper = setup({ editMode: true }); - wrapper.find(WithPopoverMenu).simulate('click'); - expect(wrapper.find(DeleteComponentModal)).toExist(); - - wrapper = setup({ - editMode: true, - parentComponent: { - ...props.parentComponent, - children: props.parentComponent.children.slice(0, 1), - }, - }); - wrapper.find(WithPopoverMenu).simulate('click'); - expect(wrapper.find(DeleteComponentModal)).not.toExist(); - }); - - it('should show modal when clicked delete icon', () => { - const deleteComponent = sinon.spy(); - const wrapper = setup({ editMode: true, deleteComponent }); - wrapper.find(WithPopoverMenu).simulate('click'); // focus - wrapper.find('.icon-button').simulate('click'); - - const modal = document.getElementsByClassName('ant-modal'); - expect(modal).toHaveLength(1); - expect(deleteComponent.callCount).toBe(0); - }); }); describe('renderType=RENDER_TAB_CONTENT', () => { diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx index 28f4b4d3bc..63dc8000c4 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx @@ -18,11 +18,12 @@ */ import { Provider } from 'react-redux'; import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import sinon from 'sinon'; -import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap'; -import { supersetTheme, ThemeProvider } from '@superset-ui/core'; +import { LineEditableTabs } from 'src/common/components/Tabs'; +import { Modal } from 'src/common/components'; +import { styledMount as mount } from 'spec/helpers/theming'; import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; import HoverMenu from 'src/dashboard/components/menu/HoverMenu'; @@ -54,6 +55,7 @@ describe('Tabs', () => { deleteComponent() {}, updateComponents() {}, logEvent() {}, + setMountedTab() {}, }; function setup(overrideProps) { @@ -65,10 +67,6 @@ describe('Tabs', () => { , - { - wrappingComponent: ThemeProvider, - wrappingComponentProps: { theme: supersetTheme }, - }, ); return wrapper; } @@ -79,31 +77,23 @@ describe('Tabs', () => { expect(wrapper.find(DragDroppable)).toExist(); }); - it('should render BootstrapTabs', () => { + it('should render non-editable tabs', () => { const wrapper = setup(); - expect(wrapper.find(BootstrapTabs)).toExist(); + expect(wrapper.find(LineEditableTabs)).toExist(); + expect(wrapper.find('.ant-tabs-nav-add').exists()).toBeFalsy(); }); - it('should set animation=true, mountOnEnter=true, and unmounOnExit=false on BootstrapTabs for perf', () => { + it('should render a tab pane for each child', () => { const wrapper = setup(); - const tabProps = wrapper.find(BootstrapTabs).props(); - expect(tabProps.animation).toBe(true); - expect(tabProps.mountOnEnter).toBe(true); - expect(tabProps.unmountOnExit).toBe(false); - }); - - it('should render a BootstrapTab for each child', () => { - const wrapper = setup(); - expect(wrapper.find(BootstrapTab)).toHaveLength( + expect(wrapper.find(LineEditableTabs.TabPane)).toHaveLength( props.component.children.length, ); }); - it('should render an extra (+) BootstrapTab in editMode', () => { + it('should render editable tabs in editMode', () => { const wrapper = setup({ editMode: true }); - expect(wrapper.find(BootstrapTab)).toHaveLength( - props.component.children.length + 1, - ); + expect(wrapper.find(LineEditableTabs)).toExist(); + expect(wrapper.find('.ant-tabs-nav-add')).toExist(); }); it('should render a DashboardComponent for each child', () => { @@ -118,7 +108,7 @@ describe('Tabs', () => { const createComponent = sinon.spy(); const wrapper = setup({ editMode: true, createComponent }); wrapper - .find('.dashboard-component-tabs .nav-tabs a') + .find('[data-test="dashboard-component-tabs"] .ant-tabs-nav-add') .last() .simulate('click'); @@ -129,7 +119,7 @@ describe('Tabs', () => { const onChangeTab = sinon.spy(); const wrapper = setup({ editMode: true, onChangeTab }); wrapper - .find('.dashboard-component-tabs .nav-tabs a') + .find('[data-test="dashboard-component-tabs"] .ant-tabs-tab') .at(1) // will not call if it is already selected .simulate('click'); @@ -140,7 +130,9 @@ describe('Tabs', () => { const onChangeTab = sinon.spy(); const wrapper = setup({ editMode: true, onChangeTab }); wrapper - .find('.dashboard-component-tabs .nav-tabs a .short-link-trigger') + .find( + '[data-test="dashboard-component-tabs"] .ant-tabs-tab [data-test="short-link-button"]', + ) .at(1) // will not call if it is already selected .simulate('click'); @@ -186,4 +178,13 @@ describe('Tabs', () => { wrapper = shallow(); expect(wrapper.state('tabIndex')).toBe(1); }); + + it('should render Modal when clicked remove tab button', () => { + const deleteComponent = sinon.spy(); + const modalMock = jest.spyOn(Modal, 'confirm'); + const wrapper = setup({ editMode: true, deleteComponent }); + wrapper.find('.ant-tabs-tab-remove').at(0).simulate('click'); + expect(modalMock.mock.calls).toHaveLength(1); + expect(deleteComponent.callCount).toBe(0); + }); }); diff --git a/superset-frontend/src/common/components/Tabs/Tabs.tsx b/superset-frontend/src/common/components/Tabs/Tabs.tsx index 027f286665..de2fabe376 100644 --- a/superset-frontend/src/common/components/Tabs/Tabs.tsx +++ b/superset-frontend/src/common/components/Tabs/Tabs.tsx @@ -40,6 +40,24 @@ const StyledTabs = styled(AntdTabs, { &.ant-tabs-tab-active .ant-tabs-tab-btn { color: inherit; } + + &:hover { + .anchor-link-container { + cursor: pointer; + + .fa.fa-link { + visibility: visible; + } + } + } + + .short-link-trigger.btn { + padding: 0 ${({ theme }) => theme.gridUnit}px; + + & > .fa.fa-link { + top: 0; + } + } } ${({ fullWidth }) => @@ -124,15 +142,37 @@ EditableTabs.TabPane.defaultProps = { ), }; -const StyledCardTabs = styled(EditableTabs)``; +const StyledLineEditableTabs = styled(EditableTabs)` + &.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab { + margin: 0 ${({ theme }) => theme.gridUnit * 4}px; + padding: ${({ theme }) => `${theme.gridUnit * 3}px ${theme.gridUnit}px`}; + background: transparent; + border: none; + } -const CardTabs = Object.assign(StyledCardTabs, { + &.ant-tabs-card > .ant-tabs-nav .ant-tabs-ink-bar { + visibility: visible; + } + + .ant-tabs-tab-btn { + font-size: ${({ theme }) => theme.typography.sizes.m}px; + } + + .ant-tabs-tab-remove { + margin-left: 0; + padding-right: 0; + } + + .ant-tabs-nav-add { + min-width: unset !important; + background: transparent !important; + border: none !important; + } +`; + +const LineEditableTabs = Object.assign(StyledLineEditableTabs, { TabPane: StyledTabPane, }); -CardTabs.defaultProps = { - type: 'card', -}; - export default Tabs; -export { CardTabs, EditableTabs }; +export { EditableTabs, LineEditableTabs }; diff --git a/superset-frontend/src/components/URLShortLinkButton.jsx b/superset-frontend/src/components/URLShortLinkButton.jsx index f35525aa1e..625d2df4c7 100644 --- a/superset-frontend/src/components/URLShortLinkButton.jsx +++ b/superset-frontend/src/components/URLShortLinkButton.jsx @@ -48,7 +48,8 @@ class URLShortLinkButton extends React.Component { })); } - getCopyUrl() { + getCopyUrl(e) { + e.stopPropagation(); getShortUrl(this.props.url) .then(this.onShortUrlSuccess) .catch(this.props.addDangerToast); diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx index 54a9eb17ff..3772893bf9 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx @@ -101,7 +101,7 @@ class DashboardBuilder extends React.Component { static shouldFocusTabs(event, container) { // don't focus the tabs when we click on a tab return ( - event.target.tagName === 'UL' || + event.target.className === 'ant-tabs-nav-wrap' || (/icon-button/.test(event.target.className) && container.contains(event.target)) ); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx index c159d17ae2..97b128c97c 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx @@ -23,8 +23,6 @@ import DashboardComponent from '../../containers/DashboardComponent'; import DragDroppable from '../dnd/DragDroppable'; import EditableTitle from '../../../components/EditableTitle'; import AnchorLink from '../../../components/AnchorLink'; -import DeleteComponentModal from '../DeleteComponentModal'; -import WithPopoverMenu from '../menu/WithPopoverMenu'; import { componentShape } from '../../util/propShapes'; export const RENDER_TAB = 'RENDER_TAB'; @@ -39,7 +37,6 @@ const propTypes = { depth: PropTypes.number.isRequired, renderType: PropTypes.oneOf([RENDER_TAB, RENDER_TAB_CONTENT]).isRequired, onDropOnTab: PropTypes.func, - onDeleteTab: PropTypes.func, editMode: PropTypes.bool.isRequired, filters: PropTypes.object.isRequired, @@ -52,7 +49,6 @@ const propTypes = { // redux handleComponentDrop: PropTypes.func.isRequired, - deleteComponent: PropTypes.func.isRequired, updateComponents: PropTypes.func.isRequired, setDirectPathToChild: PropTypes.func.isRequired, }; @@ -61,7 +57,6 @@ const defaultProps = { availableColumnCount: 0, columnWidth: 0, onDropOnTab() {}, - onDeleteTab() {}, onResizeStart() {}, onResize() {}, onResizeStop() {}, @@ -70,21 +65,12 @@ const defaultProps = { export default class Tab extends React.PureComponent { constructor(props) { super(props); - this.state = { - isFocused: false, - }; - this.handleChangeFocus = this.handleChangeFocus.bind(this); this.handleChangeText = this.handleChangeText.bind(this); - this.handleDeleteComponent = this.handleDeleteComponent.bind(this); this.handleDrop = this.handleDrop.bind(this); this.handleTopDropTargetDrop = this.handleTopDropTargetDrop.bind(this); this.handleChangeTab = this.handleChangeTab.bind(this); } - handleChangeFocus(nextFocus) { - this.setState(() => ({ isFocused: nextFocus })); - } - handleChangeTab({ pathToTabIndex }) { this.props.setDirectPathToChild(pathToTabIndex); } @@ -104,12 +90,6 @@ export default class Tab extends React.PureComponent { } } - handleDeleteComponent() { - const { index, id, parentId } = this.props; - this.props.deleteComponent(id, parentId); - this.props.onDeleteTab(index); - } - handleDrop(dropResult) { this.props.handleComponentDrop(dropResult); this.props.onDropOnTab(dropResult); @@ -204,7 +184,6 @@ export default class Tab extends React.PureComponent { } renderTab() { - const { isFocused } = this.state; const { component, parentComponent, @@ -212,12 +191,8 @@ export default class Tab extends React.PureComponent { depth, editMode, filters, + isFocused, } = this.props; - const deleteTabIcon = ( -
- -
- ); return ( - {({ dropIndicatorProps, dragSourceRef }) => ( -
- , - ] - } - editMode={editMode} - > + {({ dropIndicatorProps, dragSourceRef }) => { + return ( +
= 5 ? 'left' : 'right'} /> )} - - {dropIndicatorProps &&
} -
- )} + {dropIndicatorProps &&
} +
+ ); + }} ); } diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx index e2fb8c6a80..bd185e39ef 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx @@ -18,8 +18,10 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap'; - +import { LineEditableTabs } from 'src/common/components/Tabs'; +import { LOG_ACTIONS_SELECT_DASHBOARD_TAB } from 'src/logger/LogUtils'; +import { Modal } from 'src/common/components'; +import { styled, t } from '@superset-ui/core'; import DragDroppable from '../dnd/DragDroppable'; import DragHandle from '../dnd/DragHandle'; import DashboardComponent from '../../containers/DashboardComponent'; @@ -32,9 +34,7 @@ import { componentShape } from '../../util/propShapes'; import { NEW_TAB_ID, DASHBOARD_ROOT_ID } from '../../util/constants'; import { RENDER_TAB, RENDER_TAB_CONTENT } from './Tab'; import { TAB_TYPE } from '../../util/componentTypes'; -import { LOG_ACTIONS_SELECT_DASHBOARD_TAB } from '../../../logger/LogUtils'; -const NEW_TAB_INDEX = -1; const MAX_TAB_COUNT = 10; const propTypes = { @@ -79,6 +79,38 @@ const defaultProps = { onResizeStop() {}, }; +const StyledTabsContainer = styled.div` + width: 100%; + background-color: ${({ theme }) => theme.colors.grayscale.light5}; + + .dashboard-component-tabs-content { + min-height: ${({ theme }) => theme.gridUnit * 12}px; + margin-top: ${({ theme }) => theme.gridUnit / 4}px; + position: relative; + } + + .drop-indicator--left { + left: ${({ theme }) => -theme.gridUnit * 3}px !important; + } + .drop-indicator--right { + left: ${({ theme }) => `calc(100% + ${theme.gridUnit * 6}px)`} !important; + } + + .drop-indicator--bottom, + .drop-indicator--top { + width: ${({ theme }) => `calc(100% + ${theme.gridUnit * 6}px)`} !important; + } + + .drop-indicator--top { + top: ${({ theme }) => theme.gridUnit * 2}px; + } + + .editable-title input { + cursor: pointer; + text-transform: uppercase; + } +`; + class Tabs extends React.PureComponent { constructor(props) { super(props); @@ -127,19 +159,32 @@ class Tabs extends React.PureComponent { } } - handleClickTab(tabIndex, ev) { - if (ev) { - const { target } = ev; - // special handler for clicking on anchor link icon (or whitespace nearby): - // will show short link popover but do not change tab - if (target && target.classList.contains('short-link-trigger')) { - return; - } - } + showDeleteConfirmModal = key => { + const { component, deleteComponent } = this.props; + Modal.confirm({ + title: t('Delete dashboard tab?'), + content: ( + + Deleting a tab will remove all content within it. You may still + reverse this action with the undo button (cmd + z) until you + save your changes. + + ), + onOk: () => { + deleteComponent(key, component.id); + const tabIndex = component.children.indexOf(key); + this.handleClickTab(Math.max(0, tabIndex - 1)); + }, + okType: 'danger', + okText: 'DELETE', + cancelText: 'CANCEL', + icon: null, + }); + }; + handleEdit = (key, action) => { const { component, createComponent } = this.props; - - if (tabIndex === NEW_TAB_INDEX) { + if (action === 'add') { createComponent({ destination: { id: component.id, @@ -151,7 +196,15 @@ class Tabs extends React.PureComponent { type: TAB_TYPE, }, }); - } else if (tabIndex !== this.state.tabIndex) { + } else if (action === 'remove') { + this.showDeleteConfirmModal(key); + } + }; + + handleClickTab(tabIndex) { + const { component, renderTabContent } = this.props; + + if (tabIndex !== this.state.tabIndex) { const pathToTabIndex = getDirectPathToTabIndex(component, tabIndex); const targetTabId = pathToTabIndex[pathToTabIndex.length - 1]; this.props.logEvent(LOG_ACTIONS_SELECT_DASHBOARD_TAB, { @@ -161,6 +214,11 @@ class Tabs extends React.PureComponent { this.props.onChangeTab({ pathToTabIndex }); } + if (renderTabContent) { + const tabIds = component.children; + const activeKey = tabIds[this.state.tabIndex]; + this.props.setMountedTab(activeKey); + } } handleDeleteComponent() { @@ -212,6 +270,8 @@ class Tabs extends React.PureComponent { const { tabIndex: selectedTabIndex } = this.state; const { children: tabIds } = tabsComponent; + const activeKey = tabIds[selectedTabIndex]; + return ( ( -
@@ -237,23 +297,21 @@ class Tabs extends React.PureComponent { )} - { + this.handleClickTab(tabIds.indexOf(key)); + }} + onEdit={this.handleEdit} + hideAdd={tabIds.length >= MAX_TAB_COUNT} data-test="nav-list" + type={editMode ? 'editable-card' : 'card'} > {tabIds.map((tabId, tabIndex) => ( - // react-bootstrap doesn't render a Tab if we move this to its own Tab.jsx so we - // use `renderType` to indicate what the DashboardComponent should render. This - // prevents us from passing the entire dashboard component lookup to render Tabs.jsx - } - onEntering={() => { - // Entering current tab, DOM is visible and has dimension - if (renderTabContent) { - this.props.setMountedTab(tabId); - } - }} > {renderTabContent && ( )} - + ))} - - {editMode && tabIds.length < MAX_TAB_COUNT && ( - } - /> - )} - + {/* don't indicate that a drop on root is allowed when tabs already exist */} {tabsDropIndicatorProps && parentComponent.id !== DASHBOARD_ROOT_ID && (
)} -
+ )} ); diff --git a/superset-frontend/src/dashboard/stylesheets/components/index.less b/superset-frontend/src/dashboard/stylesheets/components/index.less index 4669bd830b..d99e11df2a 100644 --- a/superset-frontend/src/dashboard/stylesheets/components/index.less +++ b/superset-frontend/src/dashboard/stylesheets/components/index.less @@ -22,5 +22,4 @@ @import './header.less'; @import './new-component.less'; @import './row.less'; -@import './tabs.less'; @import './markdown.less'; diff --git a/superset-frontend/src/dashboard/stylesheets/components/tabs.less b/superset-frontend/src/dashboard/stylesheets/components/tabs.less deleted file mode 100644 index 39987b0727..0000000000 --- a/superset-frontend/src/dashboard/stylesheets/components/tabs.less +++ /dev/null @@ -1,106 +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. - */ -.dashboard-component-tabs { - width: 100%; - background-color: @lightest; - - & .nav-tabs { - border-bottom: none; - - /* by moving padding from to
  • we can restrict the selected tab indicator to text width */ - & > li { - margin: 0 16px; - - & > a { - color: @almost-black; - border: none; - padding: 12px 0 14px 0; - font-size: @font-size-m; - margin-right: 0; - - &:hover { - border: none; - background: inherit; - color: @almost-black; - } - - &:focus { - outline: none; - background: @lightest; - } - } - - & .dragdroppable-tab[draggable='true'] { - cursor: move; - } - - & .drop-indicator { - top: -12px !important; - height: ~'calc(100% + 24px)' !important; - } - - & .drop-indicator--left { - left: -12px !important; - } - & .drop-indicator--right { - right: -12px !important; - } - - & .drop-indicator--bottom, - & .drop-indicator--top { - left: -12px !important; - width: ~'calc(100% + 24px)' !important; /* escape for .less */ - opacity: 0.4; - } - - & .fa-plus { - color: @gray-dark; - font-size: @font-size-m; - margin-top: 3px; - } - - & .editable-title input[type='button'] { - cursor: pointer; - } - } - - & li.active > a { - border: none; - - &:after { - content: ''; - position: absolute; - height: 3px; - width: 100%; - bottom: 0; - background: linear-gradient( - to right, - @indicator-color, - shade(@indicator-color, @colorstop-two) - ); - } - } - } - - & .dashboard-component-tabs-content { - min-height: 48px; - margin-top: 1px; - position: relative; - } -} diff --git a/superset-frontend/stylesheets/superset.less b/superset-frontend/stylesheets/superset.less index 4cd60805a9..cdd259b6d0 100644 --- a/superset-frontend/stylesheets/superset.less +++ b/superset-frontend/stylesheets/superset.less @@ -283,18 +283,12 @@ table.table-no-hover tr:hover { } } -.nav.nav-tabs li .anchor-link-container { - top: 0; - right: -32px; -} - .dashboard-component.dashboard-component-header .anchor-link-container { .fa.fa-link { font-size: @font-size-l; } } -.nav.nav-tabs li:hover, .dashboard-component.dashboard-component-header:hover { .anchor-link-container { cursor: pointer;