From 53f882572e2701547e51f3a7d442b76f558e804a Mon Sep 17 00:00:00 2001 From: Nikhil Thomas Date: Sun, 19 Oct 2025 20:24:49 -0500 Subject: [PATCH 1/2] added lock icon and implemented disappearing header in desktop/mobile --- src/components/navbars/appNavBar.tsx | 86 +++++++++++++++++++++++- src/components/navbars/topbar/lock.tsx | 37 ++++++++++ src/components/navbars/topbar/topBar.tsx | 4 ++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/components/navbars/topbar/lock.tsx diff --git a/src/components/navbars/appNavBar.tsx b/src/components/navbars/appNavBar.tsx index a73f959..e088b5d 100644 --- a/src/components/navbars/appNavBar.tsx +++ b/src/components/navbars/appNavBar.tsx @@ -40,6 +40,81 @@ export default function AppNavBar(props) { const apiDisplayName = API_Name(apiStatus.currentApi); const accentColor = displayStatus.secondaryColor; + const TOP_TRIGGER_ZONE = 48; // px from top to reveal + const HIDE_TIMEOUT_MS = 2500; + const [topbarLocked, setTopbarLocked] = React.useState(false); + const [topbarVisible, setTopbarVisible] = React.useState(true); + const hideRef = React.useRef(null); + const pointerStartY = React.useRef(null); + const pointerId = React.useRef(null); + + const showTopbar = React.useCallback(() => { + setTopbarVisible(true); + if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current = null;} + if (!topbarLocked) { + hideRef.current = window.setTimeout(() => setTopbarVisible(false), HIDE_TIMEOUT_MS); + } + }, [topbarLocked]); + + React.useEffect(() => {showTopbar();}, [showTopbar]); //to activate immediately + + React.useEffect(() => { + const onPointerDown = (e: PointerEvent) => { + if(pointerId.current !== null) return; + + const y = e.clientY; + pointerId.current = e.pointerId; + pointerStartY.current = y; + + if (y <= TOP_TRIGGER_ZONE) { + showTopbar(); + } + }; + + const onPointerMove = (e: PointerEvent) => { + // proximity check + if (e.clientY <= TOP_TRIGGER_ZONE) { + showTopbar(); + } + // swipe down check + if (e.pointerId !== pointerId.current || pointerStartY.current === null) { + return; + } + const y = e.clientY + const delta = y - (pointerStartY.current ?? 0); + const SWIPE_THRESHOLD = 40; //how far to swipe + + if(pointerStartY.current <= TOP_TRIGGER_ZONE && delta >= SWIPE_THRESHOLD) { + showTopbar(); + + // reset + pointerStartY.current = null; + pointerId.current = null; + } + }; + + const onPointerUp = (e: PointerEvent) => { + if(e.pointerId === pointerId.current) { + pointerId.current = null; + pointerStartY.current = null; + } + }; + + window.addEventListener('pointerdown', onPointerDown, {passive: true }); + window.addEventListener('pointermove', onPointerMove, {passive: true }); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('pointercancel', onPointerUp); + + return () => { + window.removeEventListener('pointerdown', onPointerDown); + window.removeEventListener('pointermove', onPointerMove); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('pointercancel', onPointerUp); + }; + }, [showTopbar]); + + React.useEffect(() => () => {if (hideRef.current) window.clearTimeout(hideRef.current); }, []); + const theme = useTheme(); // const isMobile = useMediaQuery(theme.breakpoints.down('sm')); @@ -53,7 +128,7 @@ export default function AppNavBar(props) { return ( - + @@ -86,6 +161,15 @@ export default function AppNavBar(props) { apiDisplayName={apiDisplayName} listening={controlStatus.listening} menuVisible={displayStatus.menuVisible} + + topbarLocked={topbarLocked} + onTopBarToggle= {(v) => { + setTopbarLocked(v); + if(v) { + setTopbarVisible(true); + if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current=null; } + } + }} /> diff --git a/src/components/navbars/topbar/lock.tsx b/src/components/navbars/topbar/lock.tsx new file mode 100644 index 0000000..d99391f --- /dev/null +++ b/src/components/navbars/topbar/lock.tsx @@ -0,0 +1,37 @@ +import { LockIcon, LockOpenIcon, ThemeProvider, IconButton, Tooltip } from "../../../muiImports" +import * as React from 'react'; +import Theme from '../../theme' + +interface Props { + locked?: boolean; + onToggle?: (locked: boolean) => void; +} + +export default function LockToggle(props: Props) { + const {locked: lockedProp, onToggle} = props; + const [locked, setLocked] = React.useState(!!lockedProp); + + React.useEffect(()=>{ + if(typeof lockedProp === 'boolean') setLocked(lockedProp); + }, [lockedProp]); + + const handleClick = () => { + const next = !locked; + setLocked(next); + onToggle?.(next); + }; + const {myTheme} = Theme() + // color "primary" comes from Theme() + return ( +
+ + + + {locked ? : } + + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/navbars/topbar/topBar.tsx b/src/components/navbars/topbar/topBar.tsx index de3a20f..cee9de7 100644 --- a/src/components/navbars/topbar/topBar.tsx +++ b/src/components/navbars/topbar/topBar.tsx @@ -5,6 +5,7 @@ import ApiDropdown from './apiDropdown'; import Fullscreen from './fullScreen'; import Listening from './listening'; import QRCodeScreen from './qrCodeScreen'; +import LockToggle from './lock'; import MenuHider from './menuHider'; import TranscriptDownload from './transcriptDownload'; @@ -20,6 +21,8 @@ import { const iconSize = isMobile ? "small" : "medium"; + const {topbarLocked, onTopBarToggle } = props; + return ( } {/* {!isMobile && } */} + {} {} { } {!isMobile && } From 1fa396e11a40087c3516566fae35edab4fec7895 Mon Sep 17 00:00:00 2001 From: Nikhil Thomas Date: Fri, 24 Oct 2025 02:32:19 -0500 Subject: [PATCH 2/2] fixed topbar icon colors to be consistent and commented out unused imports (topBar) and variables (appNavBar) --- src/components/navbars/appNavBar.tsx | 16 ++++++++-------- src/components/navbars/topbar/apiDropdown.tsx | 10 +++++----- src/components/navbars/topbar/listening.tsx | 4 ++-- src/components/navbars/topbar/topBar.tsx | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/navbars/appNavBar.tsx b/src/components/navbars/appNavBar.tsx index f23c9f7..6c37725 100644 --- a/src/components/navbars/appNavBar.tsx +++ b/src/components/navbars/appNavBar.tsx @@ -23,14 +23,14 @@ import { useTheme } from '@mui/material'; -const currTheme = createTheme({ - palette: { - primary: { - main: '#ffffff', - contrastText: '#000000' - }, - }, -}); +// const currTheme = createTheme({ +// palette: { +// primary: { +// main: '#ffffff', +// contrastText: '#000000' +// }, +// }, +// }); export default function AppNavBar(props) { const [isDrawerOpen, setDrawerOpen] = React.useState(false); diff --git a/src/components/navbars/topbar/apiDropdown.tsx b/src/components/navbars/topbar/apiDropdown.tsx index 8d76c2b..e36b9c4 100644 --- a/src/components/navbars/topbar/apiDropdown.tsx +++ b/src/components/navbars/topbar/apiDropdown.tsx @@ -107,11 +107,11 @@ export default function ApiDropdown(props) { {isWhisperActive ? : null} {props.apiDisplayName} - - - {open ? : } - - + + + {open ? : } + + {props.listening ? - : - + : + } diff --git a/src/components/navbars/topbar/topBar.tsx b/src/components/navbars/topbar/topBar.tsx index cee9de7..386cc19 100644 --- a/src/components/navbars/topbar/topBar.tsx +++ b/src/components/navbars/topbar/topBar.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { Grid, Box } from '../../../muiImports'; +import { /*Grid,*/ Box } from '../../../muiImports'; import ApiDropdown from './apiDropdown'; import Fullscreen from './fullScreen'; import Listening from './listening'; import QRCodeScreen from './qrCodeScreen'; import LockToggle from './lock'; -import MenuHider from './menuHider'; +// import MenuHider from './menuHider'; import TranscriptDownload from './transcriptDownload'; import {