Skip to content

Commit ccc0394

Browse files
committed
use default scroll lock, simple visual scrollbar replacement
1 parent c857763 commit ccc0394

File tree

7 files changed

+72
-129
lines changed

7 files changed

+72
-129
lines changed

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "lab-dash-backend",
33
"main": "index.js",
44
"scripts": {
5-
"dev": "PORT=5000 nodemon",
5+
"dev": "NODE_ENV=development PORT=5000 nodemon",
66
"start": "node ./index.js",
77
"build": "NODE_ENV=production node esbuild.config.js",
88
"lint": "eslint .",

frontend/src/App.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@ import { Box, Paper } from '@mui/material';
44
import { useEffect } from 'react';
55
import { Route, Routes, useNavigate } from 'react-router-dom';
66

7-
import { DashApi } from './api/dash-api';
8-
import GlobalCustomScrollbar from './components/custom-mui/GlobalCustomScrollbar';
97
import { SetupForm } from './components/forms/SetupForm';
8+
import { GlobalCustomScrollbar } from './components/GlobalCustomScrollbar';
109
import { WithNav } from './components/navbar/WithNav';
1110
import { ScrollToTop } from './components/ScrollToTop';
1211
import { BACKEND_URL } from './constants/constants';
13-
import { AppContextProvider } from './context/AppContextProvider';
1412
import { useAppContext } from './context/useAppContext';
13+
import { useMobilePointer } from './hooks/useMobilePointer';
1514
import { DashboardPage } from './pages/DashboardPage';
1615
import { LoginPage } from './pages/LoginPage';
1716
import { SettingsPage } from './pages/SettingsPage';
1817
import { styles } from './theme/styles';
19-
import { theme } from './theme/theme';
2018

2119
const SetupPage = () => {
22-
const { isFirstTimeSetup, setupComplete, setSetupComplete, checkLoginStatus } = useAppContext();
20+
const { isFirstTimeSetup, setSetupComplete } = useAppContext();
2321

2422
// Show loading state while checking
2523
if (isFirstTimeSetup === null) {
@@ -45,13 +43,11 @@ export const App = () => {
4543
isFirstTimeSetup,
4644
setupComplete,
4745
setSetupComplete,
48-
refreshDashboard,
49-
checkLoginStatus,
50-
isLoggedIn,
5146
pages
5247
} = useAppContext();
5348

5449
const navigate = useNavigate();
50+
const isMobilePointer = useMobilePointer();
5551

5652
// Check if setup is complete based on the config
5753
useEffect(() => {
@@ -172,7 +168,7 @@ export const App = () => {
172168
{globalStyles}
173169
<div id='background-container' />
174170
<ScrollToTop />
175-
{/* <GlobalCustomScrollbar /> */}
171+
{!isMobilePointer && <GlobalCustomScrollbar />}
176172
<Routes>
177173
<Route element={<WithNav />}>
178174
<Route path='/' element={isFirstTimeSetup && !setupComplete ? <SetupPage /> : <DashboardPage />}/>

frontend/src/components/custom-mui/GlobalCustomScrollbar.tsx renamed to frontend/src/components/GlobalCustomScrollbar.tsx

Lines changed: 20 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { Box, styled } from '@mui/material';
22
import React, { useCallback, useEffect, useRef, useState } from 'react';
3-
import { RemoveScroll } from 'react-remove-scroll';
43

54
const ScrollbarTrack = styled(Box, {
65
shouldForwardProp: (prop) => prop !== 'visible',
76
})<{ visible: boolean }>(({ visible }) => ({
87
position: 'fixed',
98
top: 0,
109
right: 0,
11-
width: '10px', // Standard scrollbar width (was 8px)
10+
width: '10px',
1211
height: '100vh',
1312
background: 'transparent',
1413
opacity: visible ? 1 : 0,
@@ -41,12 +40,11 @@ const ScrollbarThumb = styled(Box, {
4140
},
4241
}));
4342

44-
const GlobalCustomScrollbar: React.FC = () => {
43+
export const GlobalCustomScrollbar: React.FC = () => {
4544
const [isScrollbarVisible, setIsScrollbarVisible] = useState(false);
4645
const [thumbHeight, setThumbHeight] = useState(0);
4746
const [thumbTop, setThumbTop] = useState(0);
4847
const [isDragging, setIsDragging] = useState(false);
49-
const [isScrollLocked, setIsScrollLocked] = useState(false);
5048
const [dragStart, setDragStart] = useState({ y: 0, scrollTop: 0 });
5149
const [justFinishedDragging, setJustFinishedDragging] = useState(false);
5250
const [postDragAutoHideActive, setPostDragAutoHideActive] = useState(false);
@@ -55,63 +53,8 @@ const GlobalCustomScrollbar: React.FC = () => {
5553
const postDragTimeoutRef = useRef<number>();
5654
const isMouseOverScrollbarRef = useRef(false);
5755

58-
// Check if scrolling should be disabled based on open overlays
59-
const checkAndUpdateScrollLock = useCallback(() => {
60-
// First check specifically for MuiAutocomplete dropdowns
61-
const autocompleteDropdown = document.querySelector('.MuiAutocomplete-popper, .MuiAutocomplete-listbox');
62-
if (autocompleteDropdown) {
63-
// Hide the custom scrollbar but don't apply our scroll lock
64-
// (RemoveScroll from SearchBar handles background scroll prevention)
65-
setIsScrollLocked(false);
66-
return true; // Hide scrollbar but don't use our scroll lock
67-
}
68-
69-
// Check for MUI backdrop (drawer, modal, etc.)
70-
const backdrop = document.querySelector('.MuiBackdrop-root');
71-
if (backdrop) {
72-
setIsScrollLocked(true);
73-
return true;
74-
}
75-
76-
// Check for open drawer
77-
const drawer = document.querySelector('[role="presentation"]');
78-
if (drawer) {
79-
setIsScrollLocked(true);
80-
return true;
81-
}
82-
83-
// Check for open select/menu
84-
const menu = document.querySelector('[role="listbox"], [role="menu"]');
85-
if (menu) {
86-
setIsScrollLocked(true);
87-
return true;
88-
}
89-
90-
// Check for SweetAlert popup
91-
const swalPopup = document.querySelector('.swal2-container');
92-
if (swalPopup) {
93-
setIsScrollLocked(true);
94-
return true;
95-
}
96-
97-
// Check for any modal or dialog
98-
const modal = document.querySelector('[role="dialog"]');
99-
if (modal) {
100-
setIsScrollLocked(true);
101-
return true;
102-
}
103-
104-
setIsScrollLocked(false);
105-
return false;
106-
}, []);
10756

10857
const updateScrollbar = useCallback(() => {
109-
// Hide scrollbar if scrolling is disabled
110-
if (checkAndUpdateScrollLock()) {
111-
setIsScrollbarVisible(false);
112-
return;
113-
}
114-
11558
const scrollHeight = document.documentElement.scrollHeight;
11659
const clientHeight = window.innerHeight;
11760
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
@@ -135,7 +78,7 @@ const GlobalCustomScrollbar: React.FC = () => {
13578

13679
setThumbHeight(adjustedThumbHeight);
13780
setThumbTop(newThumbTop);
138-
}, [checkAndUpdateScrollLock]);
81+
}, []);
13982

14083
const tryHideScrollbar = useCallback(() => {
14184
if (!isDragging && !isMouseOverScrollbarRef.current) {
@@ -175,8 +118,7 @@ const GlobalCustomScrollbar: React.FC = () => {
175118
}, [updateScrollbar, showScrollbar, isDragging, justFinishedDragging, postDragAutoHideActive]);
176119

177120
const handleMouseEnter = useCallback(() => {
178-
// Don't show scrollbar if scrolling is disabled
179-
if (checkAndUpdateScrollLock()) return;
121+
180122

181123
isMouseOverScrollbarRef.current = true;
182124
// Reset the auto-hide timer when mouse enters scrollbar
@@ -186,7 +128,7 @@ const GlobalCustomScrollbar: React.FC = () => {
186128
setIsScrollbarVisible(true);
187129
// Start a fresh 2-second timer
188130
hideTimeoutRef.current = window.setTimeout(tryHideScrollbar, 2000);
189-
}, [tryHideScrollbar, checkAndUpdateScrollLock]);
131+
}, [tryHideScrollbar]);
190132

191133
const handleMouseLeave = useCallback(() => {
192134
isMouseOverScrollbarRef.current = false;
@@ -201,9 +143,6 @@ const GlobalCustomScrollbar: React.FC = () => {
201143
}, [isDragging, tryHideScrollbar]);
202144

203145
const handleThumbMouseDown = useCallback((e: React.MouseEvent) => {
204-
// Don't allow dragging if scrolling is disabled
205-
if (checkAndUpdateScrollLock()) return;
206-
207146
e.preventDefault();
208147

209148
// Temporarily disable smooth scrolling during drag
@@ -218,7 +157,7 @@ const GlobalCustomScrollbar: React.FC = () => {
218157

219158
// Store the original scroll behavior to restore later
220159
(window as Window & { originalScrollBehavior?: string }).originalScrollBehavior = originalScrollBehavior;
221-
}, [checkAndUpdateScrollLock]);
160+
}, []);
222161

223162
const handleMouseMove = useCallback((e: MouseEvent) => {
224163
if (!isDragging) return;
@@ -277,9 +216,6 @@ const GlobalCustomScrollbar: React.FC = () => {
277216
}, [isDragging, tryHideScrollbar]);
278217

279218
const handleTrackClick = useCallback((e: React.MouseEvent) => {
280-
// Don't allow track clicks if scrolling is disabled
281-
if (checkAndUpdateScrollLock()) return;
282-
283219
if (e.target === e.currentTarget) {
284220
const rect = e.currentTarget.getBoundingClientRect();
285221
const clickY = e.clientY - rect.top;
@@ -293,7 +229,7 @@ const GlobalCustomScrollbar: React.FC = () => {
293229
behavior: 'smooth'
294230
});
295231
}
296-
}, [checkAndUpdateScrollLock]);
232+
}, []);
297233

298234
useEffect(() => {
299235
const handleGlobalMouseMove = (e: MouseEvent) => handleMouseMove(e);
@@ -345,22 +281,18 @@ const GlobalCustomScrollbar: React.FC = () => {
345281
}
346282

347283
return (
348-
<RemoveScroll enabled={isScrollLocked}>
349-
<ScrollbarTrack
350-
visible={isScrollbarVisible}
351-
onMouseEnter={handleMouseEnter}
352-
onMouseLeave={handleMouseLeave}
353-
onClick={handleTrackClick}
354-
>
355-
<ScrollbarThumb
356-
height={thumbHeight}
357-
top={thumbTop}
358-
isDragging={isDragging}
359-
onMouseDown={handleThumbMouseDown}
360-
/>
361-
</ScrollbarTrack>
362-
</RemoveScroll>
284+
<ScrollbarTrack
285+
visible={isScrollbarVisible}
286+
onMouseEnter={handleMouseEnter}
287+
onMouseLeave={handleMouseLeave}
288+
onClick={handleTrackClick}
289+
>
290+
<ScrollbarThumb
291+
height={thumbHeight}
292+
top={thumbTop}
293+
isDragging={isDragging}
294+
onMouseDown={handleThumbMouseDown}
295+
/>
296+
</ScrollbarTrack>
363297
);
364298
};
365-
366-
export default GlobalCustomScrollbar;

frontend/src/constants/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const initialItems = [
1212
];
1313

1414
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || (
15-
import.meta.env.PROD ? '' : 'http://localhost:5000'
15+
import.meta.env.PROD
16+
? ''
17+
: `http://${window.location.hostname}:5000`
1618
);
1719

1820
export const FIFTEEN_MIN_IN_MS = 900000;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect, useState } from 'react';
2+
3+
/**
4+
* Custom hook to detect if the device has a coarse pointer (mobile/tablet)
5+
* @returns boolean - true if the device has a coarse pointer (mobile/tablet), false for fine pointer (mouse/trackpad)
6+
*/
7+
export const useMobilePointer = (): boolean => {
8+
const [isMobilePointer, setIsMobilePointer] = useState(false);
9+
10+
useEffect(() => {
11+
const detectAndSetPointerType = () => {
12+
const mediaQuery = window.matchMedia('(pointer: coarse)');
13+
setIsMobilePointer(mediaQuery.matches);
14+
};
15+
16+
// Initial detection
17+
detectAndSetPointerType();
18+
19+
// Listen for changes (e.g., device rotation or external mouse connection)
20+
const mediaQuery = window.matchMedia('(pointer: coarse)');
21+
const handleChange = () => detectAndSetPointerType();
22+
23+
if (mediaQuery.addEventListener) {
24+
mediaQuery.addEventListener('change', handleChange);
25+
} else {
26+
// Fallback for older browsers
27+
mediaQuery.addListener(handleChange);
28+
}
29+
30+
return () => {
31+
if (mediaQuery.removeEventListener) {
32+
mediaQuery.removeEventListener('change', handleChange);
33+
} else {
34+
// Fallback for older browsers
35+
mediaQuery.removeListener(handleChange);
36+
}
37+
};
38+
}, []);
39+
40+
return isMobilePointer;
41+
};

frontend/src/theme/theme.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -189,35 +189,7 @@ export const theme = createTheme({
189189
},
190190
},
191191
},
192-
},
193-
// Disable MUI's native scroll lock - we'll use react-remove-scroll instead
194-
MuiModal: {
195-
defaultProps: {
196-
disableScrollLock: true,
197-
},
198-
},
199-
MuiDrawer: {
200-
defaultProps: {
201-
disableScrollLock: true,
202-
},
203-
},
204-
MuiMenu: {
205-
defaultProps: {
206-
disableScrollLock: true,
207-
},
208-
},
209-
MuiPopover: {
210-
defaultProps: {
211-
disableScrollLock: true,
212-
},
213-
},
214-
MuiSelect: {
215-
defaultProps: {
216-
MenuProps: {
217-
disableScrollLock: true,
218-
},
219-
},
220-
},
192+
}
221193
},
222194
});
223195

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lab-dash",
3-
"version": "1.3.1-beta.1",
3+
"version": "1.3.1-beta.2",
44
"description": "This is an open-source user interface designed to manage your server and homelab",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)