Skip to content

Commit 8fa3bd7

Browse files
authored
[ui] Restore wide-screen left nav docking (#25177)
## Summary & Motivation Restore the behavior of the left nav on wide viewports: when open, the nav does not slide over the main content. It can also be permanently kept open, via a localStorage value. This is a selective revert of #24976. The "small screen" behavior (sliding nav) and "wide screen" behavior (docked nav) distinction will be restored. ## How I Tested These Changes View app in wide screen and narrow screen. Verify that open/close of left nav works correctly, and that in the wide screen version, the preference is preserved through reload. Verify in both legacy nav and new nav. (This affects top navigation and other pages.) ## Changelog [ui] Restore docked left nav behavior for wide viewports.
1 parent 6dbd81a commit 8fa3bd7

File tree

5 files changed

+91
-19
lines changed

5 files changed

+91
-19
lines changed

js_modules/dagster-ui/packages/ui-core/src/app/App.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import styled from 'styled-components';
77
import {getFeatureFlags, setFeatureFlags, useFeatureFlags} from './Flags';
88
import {LayoutContext} from './LayoutProvider';
99
import {useStateWithStorage} from '../hooks/useStateWithStorage';
10-
import {LeftNav} from '../nav/LeftNav';
10+
import {LEFT_NAV_WIDTH, LeftNav} from '../nav/LeftNav';
1111

1212
interface Props {
1313
banner?: React.ReactNode;
@@ -25,13 +25,15 @@ export const App = ({banner, children}: Props) => {
2525
);
2626

2727
const onClickMain = React.useCallback(() => {
28-
nav.close();
28+
if (nav.isSmallScreen) {
29+
nav.close();
30+
}
2931
}, [nav]);
3032

3133
return (
3234
<Container>
3335
<LeftNav />
34-
<Main $navOpen={nav.isOpen} onClick={onClickMain}>
36+
<Main $smallScreen={nav.isSmallScreen} $navOpen={nav.isOpen} onClick={onClickMain}>
3537
<div>{banner}</div>
3638
{!flagLegacyNav && !didDismissNavAlert ? (
3739
<ExperimentalNavAlert setDidDismissNavAlert={setDidDismissNavAlert} />
@@ -88,12 +90,25 @@ const ExperimentalNavAlert = (props: AlertProps) => {
8890
);
8991
};
9092

91-
const Main = styled.div<{$navOpen: boolean}>`
93+
const Main = styled.div<{$smallScreen: boolean; $navOpen: boolean}>`
9294
height: 100%;
9395
z-index: 1;
9496
display: flex;
9597
flex-direction: column;
96-
width: 100%;
98+
99+
${({$navOpen, $smallScreen}) => {
100+
if ($smallScreen || !$navOpen) {
101+
return `
102+
margin-left: 0;
103+
width: 100%;
104+
`;
105+
}
106+
107+
return `
108+
margin-left: ${LEFT_NAV_WIDTH}px;
109+
width: calc(100% - ${LEFT_NAV_WIDTH}px);
110+
`;
111+
}}
97112
`;
98113

99114
const Container = styled.div`

js_modules/dagster-ui/packages/ui-core/src/app/AppTopNav/AppTopNav.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useRepositoryLocationReload,
1111
} from '../../nav/useRepositoryLocationReload';
1212
import {SearchDialog} from '../../search/SearchDialog';
13+
import {useFeatureFlags} from '../Flags';
1314
import {LayoutContext} from '../LayoutProvider';
1415
import {ShortcutHandler} from '../ShortcutHandler';
1516
import {WebSocketStatus} from '../WebSocketProvider';
@@ -55,6 +56,7 @@ export const AppTopNav = ({children, allowGlobalReload = false}: Props) => {
5556

5657
export const AppTopNavLogo = () => {
5758
const {nav} = React.useContext(LayoutContext);
59+
const {flagSettingsPage} = useFeatureFlags();
5860
const navButton = React.useRef<null | HTMLButtonElement>(null);
5961

6062
const onToggle = React.useCallback(() => {
@@ -73,7 +75,7 @@ export const AppTopNavLogo = () => {
7375

7476
return (
7577
<LogoContainer>
76-
{nav.canOpen ? (
78+
{!flagSettingsPage && nav.canOpen ? (
7779
<ShortcutHandler
7880
onShortcut={() => onToggle()}
7981
shortcutLabel="."

js_modules/dagster-ui/packages/ui-core/src/app/LayoutProvider.tsx

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
import * as React from 'react';
22
import {useLocation} from 'react-router-dom';
33

4+
import {useStateWithStorage} from '../hooks/useStateWithStorage';
5+
6+
function useMatchMedia(query: string) {
7+
const match = React.useRef(matchMedia(query));
8+
const [result, setResult] = React.useState(match.current.matches);
9+
10+
React.useEffect(() => {
11+
const matcher = match.current;
12+
const onChange = () => setResult(matcher.matches);
13+
matcher.addEventListener('change', onChange);
14+
return () => {
15+
matcher.removeEventListener('change', onChange);
16+
};
17+
}, [query]);
18+
19+
return result;
20+
}
21+
422
type LayoutContextValue = {
523
nav: {
624
canOpen: boolean;
725
isOpen: boolean;
26+
isSmallScreen: boolean;
827
open: () => void;
928
close: () => void;
1029
setCanOpen: (canOpen: boolean) => void;
@@ -15,41 +34,64 @@ export const LayoutContext = React.createContext<LayoutContextValue>({
1534
nav: {
1635
canOpen: true,
1736
isOpen: false,
37+
isSmallScreen: false,
1838
open: () => {},
1939
close: () => {},
2040
setCanOpen: (_canOpen: boolean) => {},
2141
},
2242
});
2343

44+
const STORAGE_KEY = 'large-screen-nav-open';
45+
2446
export const LayoutProvider = (props: {children: React.ReactNode}) => {
25-
const [navOpen, setNavOpen] = React.useState(false);
47+
const [navOpenIfLargeScreen, setNavOpenIfLargeScreen] = useStateWithStorage(
48+
STORAGE_KEY,
49+
(json: any) => {
50+
if (typeof json !== 'boolean') {
51+
return false;
52+
}
53+
return json;
54+
},
55+
);
56+
57+
const [navOpenIfSmallScreen, setNavOpenIfSmallScreen] = React.useState(false);
2658
const location = useLocation();
59+
const isSmallScreen = useMatchMedia('(max-width: 1440px)');
2760

2861
const open = React.useCallback(() => {
29-
setNavOpen(true);
30-
}, []);
62+
setNavOpenIfSmallScreen(true);
63+
if (!isSmallScreen) {
64+
setNavOpenIfLargeScreen(true);
65+
}
66+
}, [isSmallScreen, setNavOpenIfLargeScreen]);
3167

3268
const close = React.useCallback(() => {
33-
setNavOpen(false);
34-
}, []);
69+
setNavOpenIfSmallScreen(false);
70+
if (!isSmallScreen) {
71+
setNavOpenIfLargeScreen(false);
72+
}
73+
}, [isSmallScreen, setNavOpenIfLargeScreen]);
3574

3675
React.useEffect(() => {
37-
setNavOpen(false);
76+
setNavOpenIfSmallScreen(false);
3877
}, [location]);
3978

79+
const isOpen = isSmallScreen ? navOpenIfSmallScreen : navOpenIfLargeScreen;
80+
4081
const [canOpen, setCanOpen] = React.useState(true);
4182

4283
const value = React.useMemo(
4384
() => ({
4485
nav: {
45-
isOpen: canOpen && navOpen,
86+
isOpen: canOpen && isOpen,
87+
isSmallScreen,
4688
open,
4789
close,
4890
canOpen,
4991
setCanOpen,
5092
},
5193
}),
52-
[canOpen, navOpen, open, close],
94+
[isOpen, isSmallScreen, open, close, canOpen, setCanOpen],
5395
);
5496

5597
return <LayoutContext.Provider value={value}>{props.children}</LayoutContext.Provider>;

js_modules/dagster-ui/packages/ui-core/src/app/UserSettingsButton.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ import {Icon} from '@dagster-io/ui-components';
22
import {useState} from 'react';
33
import {useVisibleFeatureFlagRows} from 'shared/app/useVisibleFeatureFlagRows.oss';
44

5+
import {useFeatureFlags} from './Flags';
56
import {TopNavButton} from './TopNavButton';
67
import {UserSettingsDialog} from './UserSettingsDialog/UserSettingsDialog';
78

89
export const UserSettingsButton = () => {
10+
const {flagSettingsPage} = useFeatureFlags();
911
const [isOpen, setIsOpen] = useState(false);
12+
1013
const visibleFlags = useVisibleFeatureFlagRows();
1114

15+
if (flagSettingsPage) {
16+
return null;
17+
}
18+
1219
return (
1320
<>
1421
<TopNavButton onClick={() => setIsOpen(true)} title="User settings">

js_modules/dagster-ui/packages/ui-core/src/nav/LeftNav.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,33 @@ export const LeftNav = () => {
1414
}
1515

1616
return (
17-
<LeftNavContainer $open={nav.isOpen}>
17+
<LeftNavContainer $open={nav.isOpen} $smallScreen={nav.isSmallScreen}>
1818
{wasEverOpen.current ? <LeftNavRepositorySection /> : null}
1919
</LeftNavContainer>
2020
);
2121
};
2222

2323
export const LEFT_NAV_WIDTH = 332;
2424

25-
const LeftNavContainer = styled.div<{$open: boolean}>`
25+
const LeftNavContainer = styled.div<{$open: boolean; $smallScreen: boolean}>`
2626
position: fixed;
2727
z-index: 2;
2828
top: 64px;
2929
bottom: 0;
3030
left: 0;
3131
width: ${LEFT_NAV_WIDTH}px;
32-
display: flex;
32+
display: ${({$open, $smallScreen}) => ($open || $smallScreen ? 'flex' : 'none')};
3333
flex-shrink: 0;
3434
flex-direction: column;
3535
justify-content: start;
3636
background: ${Colors.backgroundDefault()};
3737
box-shadow: 1px 0px 0px ${Colors.keylineDefault()};
38-
transform: translateX(${({$open}) => ($open ? '0' : `-${LEFT_NAV_WIDTH}px`)});
39-
transition: transform 150ms ease-in-out;
38+
39+
${(p) =>
40+
p.$smallScreen
41+
? `
42+
transform: translateX(${p.$open ? '0' : `-${LEFT_NAV_WIDTH}px`});
43+
transition: transform 150ms ease-in-out;
44+
`
45+
: ``}
4046
`;

0 commit comments

Comments
 (0)