Skip to content

Commit 8a4d8af

Browse files
authored
Merge pull request #225 from scribear/ISSUE-198-Hiding-Header
added lock icon and implemented disappearing header in desktop/mobile
2 parents 03b1f20 + 1fa396e commit 8a4d8af

5 files changed

Lines changed: 143 additions & 18 deletions

File tree

src/components/navbars/appNavBar.tsx

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ import {
2323
useTheme
2424
} from '@mui/material';
2525

26-
const currTheme = createTheme({
27-
palette: {
28-
primary: {
29-
main: '#ffffff',
30-
contrastText: '#000000'
31-
},
32-
},
33-
});
26+
// const currTheme = createTheme({
27+
// palette: {
28+
// primary: {
29+
// main: '#ffffff',
30+
// contrastText: '#000000'
31+
// },
32+
// },
33+
// });
3434

3535
export default function AppNavBar(props) {
3636
const [isDrawerOpen, setDrawerOpen] = React.useState(false);
@@ -41,6 +41,81 @@ export default function AppNavBar(props) {
4141
const apiDisplayName = API_Name(apiStatus.currentApi);
4242
const accentColor = displayStatus.secondaryColor;
4343

44+
const TOP_TRIGGER_ZONE = 48; // px from top to reveal
45+
const HIDE_TIMEOUT_MS = 2500;
46+
const [topbarLocked, setTopbarLocked] = React.useState<boolean>(false);
47+
const [topbarVisible, setTopbarVisible] = React.useState<boolean>(true);
48+
const hideRef = React.useRef<number | null>(null);
49+
const pointerStartY = React.useRef<number | null>(null);
50+
const pointerId = React.useRef<number | null>(null);
51+
52+
const showTopbar = React.useCallback(() => {
53+
setTopbarVisible(true);
54+
if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current = null;}
55+
if (!topbarLocked) {
56+
hideRef.current = window.setTimeout(() => setTopbarVisible(false), HIDE_TIMEOUT_MS);
57+
}
58+
}, [topbarLocked]);
59+
60+
React.useEffect(() => {showTopbar();}, [showTopbar]); //to activate immediately
61+
62+
React.useEffect(() => {
63+
const onPointerDown = (e: PointerEvent) => {
64+
if(pointerId.current !== null) return;
65+
66+
const y = e.clientY;
67+
pointerId.current = e.pointerId;
68+
pointerStartY.current = y;
69+
70+
if (y <= TOP_TRIGGER_ZONE) {
71+
showTopbar();
72+
}
73+
};
74+
75+
const onPointerMove = (e: PointerEvent) => {
76+
// proximity check
77+
if (e.clientY <= TOP_TRIGGER_ZONE) {
78+
showTopbar();
79+
}
80+
// swipe down check
81+
if (e.pointerId !== pointerId.current || pointerStartY.current === null) {
82+
return;
83+
}
84+
const y = e.clientY
85+
const delta = y - (pointerStartY.current ?? 0);
86+
const SWIPE_THRESHOLD = 40; //how far to swipe
87+
88+
if(pointerStartY.current <= TOP_TRIGGER_ZONE && delta >= SWIPE_THRESHOLD) {
89+
showTopbar();
90+
91+
// reset
92+
pointerStartY.current = null;
93+
pointerId.current = null;
94+
}
95+
};
96+
97+
const onPointerUp = (e: PointerEvent) => {
98+
if(e.pointerId === pointerId.current) {
99+
pointerId.current = null;
100+
pointerStartY.current = null;
101+
}
102+
};
103+
104+
window.addEventListener('pointerdown', onPointerDown, {passive: true });
105+
window.addEventListener('pointermove', onPointerMove, {passive: true });
106+
window.addEventListener('pointerup', onPointerUp);
107+
window.addEventListener('pointercancel', onPointerUp);
108+
109+
return () => {
110+
window.removeEventListener('pointerdown', onPointerDown);
111+
window.removeEventListener('pointermove', onPointerMove);
112+
window.removeEventListener('pointerup', onPointerUp);
113+
window.removeEventListener('pointercancel', onPointerUp);
114+
};
115+
}, [showTopbar]);
116+
117+
React.useEffect(() => () => {if (hideRef.current) window.clearTimeout(hideRef.current); }, []);
118+
44119
const theme = useTheme();
45120
// const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
46121

@@ -54,7 +129,7 @@ export default function AppNavBar(props) {
54129

55130
return (
56131
<ThemeProvider theme={theme}>
57-
<AppBar position="fixed" id="topbar-wrapper" sx={{ transition: '0.6s', backgroundColor: accentColor }}>
132+
<AppBar position="fixed" id="topbar-wrapper" sx={{ transition: 'transform 240ms ease-in-out', transform: topbarVisible ? 'translateY(0)' : 'translateY(-100%)', backgroundColor: accentColor }}>
58133
<Toolbar sx={{ width: '100%', minHeight: 56, color: 'white' }}>
59134
<Grid container alignItems="center" justifyContent="space-between">
60135
<Grid container alignItems="center" justifyContent="space-between" sx={{ width: '100%' }}>
@@ -89,6 +164,15 @@ export default function AppNavBar(props) {
89164
apiDisplayName={apiDisplayName}
90165
listening={controlStatus.listening}
91166
menuVisible={displayStatus.menuVisible}
167+
168+
topbarLocked={topbarLocked}
169+
onTopBarToggle= {(v) => {
170+
setTopbarLocked(v);
171+
if(v) {
172+
setTopbarVisible(true);
173+
if (hideRef.current) {window.clearTimeout(hideRef.current); hideRef.current=null; }
174+
}
175+
}}
92176
/>
93177
</Grid>
94178
</Grid>

src/components/navbars/topbar/apiDropdown.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ export default function ApiDropdown(props) {
107107
{isWhisperActive ? <WhisperIcon /> : null}
108108
<span>{props.apiDisplayName}</span>
109109
<Tooltip title="API choice">
110-
<IconButton onClick={handleClick}>
111-
<ThemeProvider theme={currTheme}>
112-
{open ? <ExpandLess color="primary" fontSize="large" /> : <ExpandMore color="primary" fontSize="large" />}
113-
</ThemeProvider>
114-
</IconButton>
110+
<ThemeProvider theme={currTheme}>
111+
<IconButton color='primary' onClick={handleClick}>
112+
{open ? <ExpandLess fontSize="large" /> : <ExpandMore fontSize="large" />}
113+
</IconButton>
114+
</ThemeProvider>
115115
</Tooltip>
116116
</Box>
117117
<Menu

src/components/navbars/topbar/listening.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import * as React from 'react';
2929
{props.listening === false ? "Begin Listening" : "Pause Listening"}>
3030
<IconButton color="primary" onClick={handleClick}>
3131
{props.listening ?
32-
<MicIcon color="primary" fontSize="large"/> :
33-
<MicOffIcon color="primary" fontSize="large"/>
32+
<MicIcon fontSize="large"/> :
33+
<MicOffIcon fontSize="large"/>
3434
}
3535
</IconButton>
3636
</Tooltip>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { LockIcon, LockOpenIcon, ThemeProvider, IconButton, Tooltip } from "../../../muiImports"
2+
import * as React from 'react';
3+
import Theme from '../../theme'
4+
5+
interface Props {
6+
locked?: boolean;
7+
onToggle?: (locked: boolean) => void;
8+
}
9+
10+
export default function LockToggle(props: Props) {
11+
const {locked: lockedProp, onToggle} = props;
12+
const [locked, setLocked] = React.useState<boolean>(!!lockedProp);
13+
14+
React.useEffect(()=>{
15+
if(typeof lockedProp === 'boolean') setLocked(lockedProp);
16+
}, [lockedProp]);
17+
18+
const handleClick = () => {
19+
const next = !locked;
20+
setLocked(next);
21+
onToggle?.(next);
22+
};
23+
const {myTheme} = Theme()
24+
// color "primary" comes from Theme()
25+
return (
26+
<div>
27+
<ThemeProvider theme={myTheme}>
28+
<Tooltip title = {locked ? "Unlock (hide)" : "Lock (keep visible)"}>
29+
<IconButton color="primary" onClick={handleClick}>
30+
{locked ? <LockIcon /> : <LockOpenIcon />}
31+
</IconButton>
32+
</Tooltip>
33+
</ThemeProvider>
34+
35+
</div>
36+
);
37+
}

src/components/navbars/topbar/topBar.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as React from 'react';
2-
import { Grid, Box } from '../../../muiImports';
2+
import { /*Grid,*/ Box } from '../../../muiImports';
33

44
import ApiDropdown from './apiDropdown';
55
import Fullscreen from './fullScreen';
66
import Listening from './listening';
77
import QRCodeScreen from './qrCodeScreen';
8-
import MenuHider from './menuHider';
8+
import LockToggle from './lock';
9+
// import MenuHider from './menuHider';
910
import TranscriptDownload from './transcriptDownload';
1011

1112
import {
@@ -20,6 +21,8 @@ import {
2021

2122
const iconSize = isMobile ? "small" : "medium";
2223

24+
const {topbarLocked, onTopBarToggle } = props;
25+
2326
return (
2427
<Box sx={{
2528
display: 'flex',
@@ -37,6 +40,7 @@ import {
3740
{/* Only display if there is enough space */}
3841
{<Listening listening={props.listening} iconSize={iconSize} />}
3942
{/* {!isMobile && <MenuHider menuVisible={props.menuVisible} iconSize={iconSize} />} */}
43+
{<LockToggle locked = {topbarLocked} onToggle={onTopBarToggle}/>}
4044
{<TranscriptDownload />}
4145
{ <Fullscreen iconSize={iconSize} />}
4246
{!isMobile && <QRCodeScreen />}

0 commit comments

Comments
 (0)