diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index d813b3b52d..c48594d304 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 1a9da6658a..347cfd8b9a 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 0cbdc74182..4448d2ec52 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 a4291690c7..d285b61a4c 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 b64a961092..97d7b2a50e 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 fb062490b3..85e42d9032 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 ba9f1015cb..b6dad501f5 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; }; /*