fix(dashboard): narrow empty drop area (#26313)

This commit is contained in:
JUST.in DO IT 2024-01-04 09:44:23 -08:00 committed by GitHub
parent d0ffe9af7c
commit 300ddaedf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 76 deletions

View File

@ -86,72 +86,10 @@ import {
import { getRootLevelTabsComponent, shouldFocusTabs } from './utils';
import DashboardContainer from './DashboardContainer';
import { useNativeFilters } from './state';
import DashboardWrapper from './DashboardWrapper';
type DashboardBuilderProps = {};
const StyledDiv = styled.div`
${({ theme }) => css`
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
flex: 1;
/* Special cases */
/* A row within a column has inset hover menu */
.dragdroppable-column .dragdroppable-row .hover-menu--left {
left: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
.dashboard-component-tabs {
position: relative;
}
/* A column within a column or tabs has inset hover menu */
.dragdroppable-column .dragdroppable-column .hover-menu--top,
.dashboard-component-tabs .dragdroppable-column .hover-menu--top {
top: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
/* move Tabs hover menu to top near actual Tabs */
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
top: 0;
transform: unset;
background: transparent;
}
/* push Chart actions to upper right */
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
.dragdroppable .dashboard-component-header .hover-menu--top {
right: ${theme.gridUnit * 2}px;
top: ${theme.gridUnit * 2}px;
background: transparent;
border: none;
transform: unset;
left: unset;
}
div:hover > .hover-menu-container .hover-menu,
.hover-menu-container .hover-menu:hover {
opacity: 1;
}
p {
margin: 0 0 ${theme.gridUnit * 2}px 0;
}
i.danger {
color: ${theme.colors.error.base};
}
i.warning {
color: ${theme.colors.alert.base};
}
`}
`;
// @z-index-above-dashboard-charts + 1 = 11
const FiltersPanel = styled.div<{ width: number; hidden: boolean }>`
grid-column: 1;
@ -317,7 +255,7 @@ const DashboardContentWrapper = styled.div`
width: 100%;
}
& > .empty-droptarget:first-child {
& > .empty-droptarget:first-child:not(.empty-droptarget--full) {
height: ${theme.gridUnit * 4}px;
top: -2px;
z-index: 10;
@ -640,7 +578,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
: theme.gridUnit * 8;
return (
<StyledDiv>
<DashboardWrapper>
{showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && (
<>
<ResizableSidebar
@ -749,7 +687,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
`}
/>
)}
</StyledDiv>
</DashboardWrapper>
);
};

View File

@ -0,0 +1,75 @@
/**
* 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 { fireEvent, render } from 'spec/helpers/testing-library';
import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
import DashboardWrapper from './DashboardWrapper';
test('should render children', () => {
const { getByTestId } = render(
<DashboardWrapper>
<div data-test="mock-children" />
</DashboardWrapper>,
{ useRedux: true, useDnd: true },
);
expect(getByTestId('mock-children')).toBeInTheDocument();
});
test('should update the style on dragging state', () => {
const defaultProps = {
label: <span>Test label</span>,
tooltipTitle: 'This is a tooltip title',
onRemove: jest.fn(),
onMoveLabel: jest.fn(),
onDropLabel: jest.fn(),
type: 'test',
index: 0,
};
const { container, getByText } = render(
<DashboardWrapper>
<OptionControlLabel
{...defaultProps}
index={1}
label={<span>Label 1</span>}
/>
<OptionControlLabel
{...defaultProps}
index={2}
label={<span>Label 2</span>}
/>
</DashboardWrapper>,
{
useRedux: true,
useDnd: true,
initialState: {
dashboardState: {
editMode: true,
},
},
},
);
expect(
container.getElementsByClassName('dragdroppable--dragging'),
).toHaveLength(0);
fireEvent.dragStart(getByText('Label 1'));
expect(
container.getElementsByClassName('dragdroppable--dragging'),
).toHaveLength(1);
});

View File

@ -0,0 +1,128 @@
/**
* 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, { useEffect } from 'react';
import { css, styled } from '@superset-ui/core';
import { RootState } from 'src/dashboard/types';
import { useSelector } from 'react-redux';
import { useDragDropManager } from 'react-dnd';
import classNames from 'classnames';
const StyledDiv = styled.div`
${({ theme }) => css`
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
flex: 1;
/* Special cases */
&.dragdroppable--dragging
.dashboard-component-tabs-content
> .empty-droptarget.empty-droptarget--full {
height: 100%;
}
/* A row within a column has inset hover menu */
.dragdroppable-column .dragdroppable-row .hover-menu--left {
left: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
.dashboard-component-tabs {
position: relative;
}
/* A column within a column or tabs has inset hover menu */
.dragdroppable-column .dragdroppable-column .hover-menu--top,
.dashboard-component-tabs .dragdroppable-column .hover-menu--top {
top: ${theme.gridUnit * -3}px;
background: ${theme.colors.grayscale.light5};
border: 1px solid ${theme.colors.grayscale.light2};
}
/* move Tabs hover menu to top near actual Tabs */
.dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
top: 0;
transform: unset;
background: transparent;
}
/* push Chart actions to upper right */
.dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
.dragdroppable .dashboard-component-header .hover-menu--top {
right: ${theme.gridUnit * 2}px;
top: ${theme.gridUnit * 2}px;
background: transparent;
border: none;
transform: unset;
left: unset;
}
div:hover > .hover-menu-container .hover-menu,
.hover-menu-container .hover-menu:hover {
opacity: 1;
}
p {
margin: 0 0 ${theme.gridUnit * 2}px 0;
}
i.danger {
color: ${theme.colors.error.base};
}
i.warning {
color: ${theme.colors.alert.base};
}
`}
`;
type Props = {};
const DashboardWrapper: React.FC<Props> = ({ children }) => {
const editMode = useSelector<RootState, boolean>(
state => state.dashboardState.editMode,
);
const dragDropManager = useDragDropManager();
const [isDragged, setIsDragged] = React.useState(
dragDropManager.getMonitor().isDragging(),
);
useEffect(() => {
const monitor = dragDropManager.getMonitor();
const unsub = monitor.subscribeToStateChange(() => {
setIsDragged(monitor.isDragging());
});
return () => {
unsub();
};
}, [dragDropManager]);
return (
<StyledDiv
className={classNames({
'dragdroppable--dragging': editMode && isDragged,
})}
>
{children}
</StyledDiv>
);
};
export default DashboardWrapper;

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { addAlpha, css, styled, t } from '@superset-ui/core';
import { EmptyStateBig } from 'src/components/EmptyState';
import { componentShape } from '../util/propShapes';
@ -76,10 +77,14 @@ const GridContent = styled.div`
& > .empty-droptarget:first-child {
height: ${theme.gridUnit * 12}px;
margin-top: ${theme.gridUnit * -6}px;
margin-bottom: ${theme.gridUnit * -6}px;
}
& > .empty-droptarget:only-child {
& > .empty-droptarget:last-child {
height: ${theme.gridUnit * 12}px;
margin-top: ${theme.gridUnit * -6}px;
}
& > .empty-droptarget.empty-droptarget--full:only-child {
height: 80vh;
}
`}
@ -270,10 +275,14 @@ class DashboardGrid extends React.PureComponent {
index={0}
orientation="column"
onDrop={this.handleTopDropTargetDrop}
className="empty-droptarget"
className={classNames({
'empty-droptarget': true,
'empty-droptarget--full':
gridComponent?.children?.length === 0,
})}
editMode
>
{renderDraggableContentBottom}
{renderDraggableContentTop}
</DragDroppable>
)}
{gridComponent?.children?.map((id, index) => (
@ -304,7 +313,7 @@ class DashboardGrid extends React.PureComponent {
className="empty-droptarget"
editMode
>
{renderDraggableContentTop}
{renderDraggableContentBottom}
</DragDroppable>
)}
{isResizing &&

View File

@ -90,6 +90,11 @@ const DragDroppableStyles = styled.div`
z-index: 10;
}
&.empty-droptarget--full > .drop-indicator--top {
height: 100%;
opacity: 0.3;
}
& {
.drop-indicator {
display: block;
@ -99,7 +104,7 @@ const DragDroppableStyles = styled.div`
}
.drop-indicator--top {
top: 0;
top: ${-theme.gridUnit - 2}px;
left: 0;
height: ${theme.gridUnit}px;
width: 100%;
@ -107,7 +112,7 @@ const DragDroppableStyles = styled.div`
}
.drop-indicator--bottom {
top: 100%;
bottom: ${-theme.gridUnit - 2}px;
left: 0;
height: ${theme.gridUnit}px;
width: 100%;
@ -116,7 +121,7 @@ const DragDroppableStyles = styled.div`
.drop-indicator--right {
top: 0;
left: 100%;
left: calc(100% - ${theme.gridUnit}px);
height: 100%;
width: ${theme.gridUnit}px;
min-height: ${theme.gridUnit * 4}px;

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { styled, t } from '@superset-ui/core';
@ -173,7 +174,10 @@ class Tab extends React.PureComponent {
depth={depth}
onDrop={this.handleTopDropTargetDrop}
editMode
className="empty-droptarget"
className={classNames({
'empty-droptarget': true,
'empty-droptarget--full': tabComponent.children.length === 0,
})}
>
{renderDraggableContentTop}
</DragDroppable>
@ -234,7 +238,7 @@ class Tab extends React.PureComponent {
/>
))}
{/* Make bottom of tab droppable */}
{editMode && (
{editMode && tabComponent.children.length > 0 && (
<DragDroppable
component={tabComponent}
parentComponent={tabParentComponent}