diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index d813b3b52d158..c48594d304841 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -31,6 +31,7 @@ import StyledModal from 'src/components/Modal';
import Mousetrap from 'mousetrap';
import Button from 'src/components/Button';
import Timer from 'src/components/Timer';
+import ResizableSidebar from 'src/components/ResizableSidebar';
import { AntdDropdown, AntdSwitch } from 'src/components';
import { Input } from 'src/components/Input';
import { Menu } from 'src/components/Menu';
@@ -60,6 +61,7 @@ import {
SQL_EDITOR_GUTTER_HEIGHT,
SQL_EDITOR_GUTTER_MARGIN,
SQL_TOOLBAR_HEIGHT,
+ SQL_EDITOR_LEFTBAR_WIDTH,
} from 'src/SqlLab/constants';
import {
getItem,
@@ -127,6 +129,15 @@ const StyledToolbar = styled.div`
}
`;
+const StyledSidebar = styled.div`
+ flex: 0 0 ${({ width }) => width}px;
+ width: ${({ width }) => width}px;
+ padding: ${({ hide }) => (hide ? 0 : 10)}px;
+ border-right: 1px solid
+ ${({ theme, hide }) =>
+ hide ? 'transparent' : theme.colors.grayscale.light2};
+`;
+
const propTypes = {
actions: PropTypes.object.isRequired,
database: PropTypes.object,
@@ -674,7 +685,6 @@ class SqlEditor extends React.PureComponent {
this.state.createAs === CtasEnum.VIEW
? 'Specify name to CREATE VIEW AS schema in: public'
: 'Specify name to CREATE TABLE AS schema in: public';
-
const leftBarStateClass = this.props.hideLeftBar
? 'schemaPane-exit-done'
: 'schemaPane-enter-done';
@@ -685,15 +695,28 @@ class SqlEditor extends React.PureComponent {
in={!this.props.hideLeftBar}
timeout={300}
>
-
-
-
+
+ {adjustedWidth => (
+
+
+
+ )}
+
{this.state.showEmptyState ? (
theme.colors.primary.base};
+ }
+
+ .sidebar-resizer {
+ // @z-index-above-sticky-header (100) + 1 = 101
+ z-index: 101;
+ }
+
+ .sidebar-resizer::after {
+ display: block;
+ content: '';
+ width: 1px;
+ height: 100%;
+ margin: 0 auto;
+ }
+`;
+
+type Props = {
+ id: string;
+ initialWidth: number;
+ enable: boolean;
+ minWidth?: number;
+ maxWidth?: number;
+ children: (width: number) => React.ReactNode;
+};
+
+const ResizableSidebar: React.FC = ({
+ id,
+ initialWidth,
+ minWidth,
+ maxWidth,
+ enable,
+ children,
+}) => {
+ const [width, setWidth] = useStoredSidebarWidth(id, initialWidth);
+
+ return (
+ <>
+
+ setWidth(width + d.width)}
+ />
+
+ {children(width)}
+ >
+ );
+};
+
+export default ResizableSidebar;
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts
similarity index 68%
rename from superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts
rename to superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts
index 1a9da6658a720..347cfd8b9ae30 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts
+++ b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts
@@ -22,10 +22,11 @@ import {
setItem,
getItem,
} from 'src/utils/localStorageHelpers';
-import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants';
-import useStoredFilterBarWidth from './useStoredFilterBarWidth';
+import useStoredSidebarWidth from './useStoredSidebarWidth';
-describe('useStoredFilterBarWidth', () => {
+const INITIAL_WIDTH = 300;
+
+describe('useStoredSidebarWidth', () => {
beforeEach(() => {
localStorage.clear();
});
@@ -34,22 +35,26 @@ describe('useStoredFilterBarWidth', () => {
localStorage.clear();
});
- it('returns a default filterBar width by OPEN_FILTER_BAR_WIDTH', () => {
- const dashboardId = '123';
- const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId));
+ it('returns a default filterBar width by initialWidth', () => {
+ const id = '123';
+ const { result } = renderHook(() =>
+ useStoredSidebarWidth(id, INITIAL_WIDTH),
+ );
const [actualWidth] = result.current;
- expect(actualWidth).toEqual(OPEN_FILTER_BAR_WIDTH);
+ expect(actualWidth).toEqual(INITIAL_WIDTH);
});
it('returns a stored filterBar width from localStorage', () => {
- const dashboardId = '123';
+ const id = '123';
const expectedWidth = 378;
- setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, {
- [dashboardId]: expectedWidth,
+ setItem(LocalStorageKeys.common__resizable_sidebar_widths, {
+ [id]: expectedWidth,
'456': 250,
});
- const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId));
+ const { result } = renderHook(() =>
+ useStoredSidebarWidth(id, INITIAL_WIDTH),
+ );
const [actualWidth] = result.current;
expect(actualWidth).toEqual(expectedWidth);
@@ -57,15 +62,17 @@ describe('useStoredFilterBarWidth', () => {
});
it('returns a setter for filterBar width that stores the state in localStorage together', () => {
- const dashboardId = '123';
+ const id = '123';
const expectedWidth = 378;
const otherDashboardId = '456';
const otherDashboardWidth = 253;
- setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, {
- [dashboardId]: 300,
+ setItem(LocalStorageKeys.common__resizable_sidebar_widths, {
+ [id]: 300,
[otherDashboardId]: otherDashboardWidth,
});
- const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId));
+ const { result } = renderHook(() =>
+ useStoredSidebarWidth(id, INITIAL_WIDTH),
+ );
const [prevWidth, setter] = result.current;
expect(prevWidth).toEqual(300);
@@ -74,10 +81,10 @@ describe('useStoredFilterBarWidth', () => {
const updatedWidth = result.current[0];
const widthsMap = getItem(
- LocalStorageKeys.dashboard__custom_filter_bar_widths,
+ LocalStorageKeys.common__resizable_sidebar_widths,
{},
);
- expect(widthsMap[dashboardId]).toEqual(expectedWidth);
+ expect(widthsMap[id]).toEqual(expectedWidth);
expect(widthsMap[otherDashboardId]).toEqual(otherDashboardWidth);
expect(updatedWidth).toEqual(expectedWidth);
expect(updatedWidth).not.toEqual(250);
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts
similarity index 62%
rename from superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts
rename to superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts
index 0cbdc74182222..4448d2ec52a3f 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts
+++ b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts
@@ -22,30 +22,30 @@ import {
setItem,
getItem,
} from 'src/utils/localStorageHelpers';
-import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants';
-export default function useStoredFilterBarWidth(dashboardId: string) {
+export default function useStoredSidebarWidth(
+ id: string,
+ initialWidth: number,
+) {
const widthsMapRef = useRef>();
- const [filterBarWidth, setFilterBarWidth] = useState(
- OPEN_FILTER_BAR_WIDTH,
- );
+ const [sidebarWidth, setSidebarWidth] = useState(initialWidth);
useEffect(() => {
widthsMapRef.current =
widthsMapRef.current ??
- getItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, {});
- if (widthsMapRef.current[dashboardId]) {
- setFilterBarWidth(widthsMapRef.current[dashboardId]);
+ getItem(LocalStorageKeys.common__resizable_sidebar_widths, {});
+ if (widthsMapRef.current[id]) {
+ setSidebarWidth(widthsMapRef.current[id]);
}
- }, [dashboardId]);
+ }, [id]);
- function setStoredFilterBarWidth(updatedWidth: number) {
- setFilterBarWidth(updatedWidth);
- setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, {
+ function setStoredSidebarWidth(updatedWidth: number) {
+ setSidebarWidth(updatedWidth);
+ setItem(LocalStorageKeys.common__resizable_sidebar_widths, {
...widthsMapRef.current,
- [dashboardId]: updatedWidth,
+ [id]: updatedWidth,
});
}
- return [filterBarWidth, setStoredFilterBarWidth] as const;
+ return [sidebarWidth, setStoredSidebarWidth] as const;
}
diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx
index a4291690c74a9..d285b61a4c6f0 100644
--- a/superset-frontend/src/components/TableSelector/index.tsx
+++ b/superset-frontend/src/components/TableSelector/index.tsx
@@ -39,12 +39,14 @@ import { useToasts } from 'src/components/MessageToasts/withToasts';
import { SchemaOption } from 'src/SqlLab/types';
import { useTables, Table } from 'src/hooks/apiResources';
+const REFRESH_WIDTH = 30;
+
const TableSelectorWrapper = styled.div`
${({ theme }) => `
.refresh {
display: flex;
align-items: center;
- width: 30px;
+ width: ${REFRESH_WIDTH}px;
margin-left: ${theme.gridUnit}px;
margin-top: ${theme.gridUnit * 5}px;
}
@@ -66,6 +68,7 @@ const TableSelectorWrapper = styled.div`
.select {
flex: 1;
+ max-width: calc(100% - ${theme.gridUnit + REFRESH_WIDTH}px)
}
`}
`;
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index b64a96109276e..97d7b2a50e57c 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -23,7 +23,7 @@ import { render } from 'spec/helpers/testing-library';
import { fireEvent, within } from '@testing-library/react';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/DashboardBuilder';
-import useStoredFilterBarWidth from 'src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth';
+import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
import {
fetchFaveStar,
setActiveTabs,
@@ -46,7 +46,7 @@ jest.mock('src/dashboard/actions/dashboardState', () => ({
setDirectPathToChild: jest.fn(),
}));
jest.mock('src/featureFlags');
-jest.mock('src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth');
+jest.mock('src/components/ResizableSidebar/useStoredSidebarWidth');
// mock following dependant components to fix the prop warnings
jest.mock('src/components/Icons/Icon', () => () => (
@@ -98,7 +98,7 @@ describe('DashboardBuilder', () => {
activeTabsStub = (setActiveTabs as jest.Mock).mockReturnValue({
type: 'mock-action',
});
- (useStoredFilterBarWidth as jest.Mock).mockImplementation(() => [
+ (useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
100,
jest.fn(),
]);
@@ -108,7 +108,7 @@ describe('DashboardBuilder', () => {
afterAll(() => {
favStarStub.mockReset();
activeTabsStub.mockReset();
- (useStoredFilterBarWidth as jest.Mock).mockReset();
+ (useStoredSidebarWidth as jest.Mock).mockReset();
});
function setup(overrideState = {}, overrideStore?: Store) {
@@ -259,10 +259,10 @@ describe('DashboardBuilder', () => {
(isFeatureEnabled as jest.Mock).mockReset();
});
- it('should set FilterBar width by useStoredFilterBarWidth', () => {
+ it('should set FilterBar width by useStoredSidebarWidth', () => {
const expectedValue = 200;
const setter = jest.fn();
- (useStoredFilterBarWidth as jest.Mock).mockImplementation(() => [
+ (useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
expectedValue,
setter,
]);
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index fb062490b3e5a..85e42d9032359 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -26,7 +26,6 @@ import React, {
useMemo,
useRef,
} from 'react';
-import { Resizable } from 're-resizable';
import { JsonObject, styled, css, t } from '@superset-ui/core';
import { Global } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
@@ -62,6 +61,7 @@ import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar';
import Loading from 'src/components/Loading';
import { EmptyStateBig } from 'src/components/EmptyState';
import { useUiConfig } from 'src/components/UiConfigContext';
+import ResizableSidebar from 'src/components/ResizableSidebar';
import {
BUILDER_SIDEPANEL_WIDTH,
CLOSED_FILTER_BAR_WIDTH,
@@ -74,7 +74,6 @@ import {
import { shouldFocusTabs, getRootLevelTabsComponent } from './utils';
import DashboardContainer from './DashboardContainer';
import { useNativeFilters } from './state';
-import useStoredFilterBarWidth from './useStoredFilterBarWidth';
type DashboardBuilderProps = {};
@@ -220,27 +219,6 @@ const StyledDashboardContent = styled.div<{
}
`;
-const ResizableFilterBarWrapper = styled.div`
- position: absolute;
-
- :hover .filterbar-resizer::after {
- background-color: ${({ theme }) => theme.colors.primary.base};
- }
-
- .filterbar-resizer {
- // @z-index-above-sticky-header (100) + 1 = 101
- z-index: 101;
- }
-
- .filterbar-resizer::after {
- display: block;
- content: '';
- width: 1px;
- height: 100%;
- margin: 0 auto;
- }
-`;
-
const DashboardBuilder: FC = () => {
const dispatch = useDispatch();
const uiConfig = useUiConfig();
@@ -327,13 +305,6 @@ const DashboardBuilder: FC = () => {
nativeFiltersEnabled,
} = useNativeFilters();
- const [adjustedFilterBarWidth, setAdjustedFilterBarWidth] =
- useStoredFilterBarWidth(dashboardId);
-
- const filterBarWidth = dashboardFiltersOpen
- ? adjustedFilterBarWidth
- : CLOSED_FILTER_BAR_WIDTH;
-
const [containerRef, isSticky] = useElementOnScreen({
threshold: [1],
});
@@ -425,35 +396,38 @@ const DashboardBuilder: FC = () => {
{nativeFiltersEnabled && !editMode && (
<>
-
-
- setAdjustedFilterBarWidth(filterBarWidth + d.width)
- }
- />
-
-
-
-
- {
+ const filterBarWidth = dashboardFiltersOpen
+ ? adjustedWidth
+ : CLOSED_FILTER_BAR_WIDTH;
+ return (
+
-
-
-
+ data-test="dashboard-filters-panel"
+ >
+
+
+
+
+
+
+ );
+ }}
+
>
)}
diff --git a/superset-frontend/src/utils/localStorageHelpers.ts b/superset-frontend/src/utils/localStorageHelpers.ts
index ba9f1015cb468..b6dad501f5af4 100644
--- a/superset-frontend/src/utils/localStorageHelpers.ts
+++ b/superset-frontend/src/utils/localStorageHelpers.ts
@@ -55,6 +55,7 @@ export enum LocalStorageKeys {
explore__data_table_original_formatted_time_columns = 'explore__data_table_original_formatted_time_columns',
dashboard__custom_filter_bar_widths = 'dashboard__custom_filter_bar_widths',
dashboard__explore_context = 'dashboard__explore_context',
+ common__resizable_sidebar_widths = 'common__resizable_sidebar_widths',
}
export type LocalStorageValues = {
@@ -73,6 +74,7 @@ export type LocalStorageValues = {
explore__data_table_original_formatted_time_columns: Record;
dashboard__custom_filter_bar_widths: Record;
dashboard__explore_context: Record;
+ common__resizable_sidebar_widths: Record;
};
/*