Skip to content

Commit e29611b

Browse files
fix(header): improve navbar scroll visibility logic
1 parent b6f364e commit e29611b

5 files changed

Lines changed: 132 additions & 94 deletions

File tree

‎.env.development‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ REACT_APP_NHOST_VERSION=v1
77
REACT_APP_NHOST_ENDPOINT=graphql
88
REACT_APP_PLAY_WEB_SVC=https://api.reactplay.io/.netlify/functions/server
99
DISABLE_ESLINT_PLUGIN=true
10-
REACT_APP_ACTIVITIES_ON=true
10+
REACT_APP_ACTIVITIES_ON=false
1111
REACT_APP_ACTIVITY_ID=hackrplay
1212
REACT_APP_DADJOKES_URL=https://jokeapi-v2.p.rapidapi.com/joke/
1313
REACT_APP_DADJOKES_APIKEY='b71df95c75msha446fab91d0e935p1d0262jsn1d938cb85502'

‎.eslintrc.js‎

Lines changed: 88 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,88 @@
1-
module.exports = {
2-
env: {
3-
browser: true,
4-
node: true,
5-
es2021: true,
6-
jest: true
7-
},
8-
extends: [
9-
'eslint:recommended',
10-
'plugin:@typescript-eslint/recommended',
11-
'plugin:prettier/recommended'
12-
],
13-
parser: '@typescript-eslint/parser',
14-
parserOptions: {
15-
ecmaFeatures: {
16-
jsx: true
17-
},
18-
ecmaVersion: 'latest',
19-
sourceType: 'module'
20-
},
21-
plugins: ['react-hooks', 'react', '@typescript-eslint', 'prettier'],
22-
ignorePatterns: ['**/plays/index.js'],
23-
24-
rules: {
25-
// Note: you must disable the base rule as it can report incorrect errors
26-
"no-unused-vars": "off",
27-
"@typescript-eslint/no-unused-vars": "warn",
28-
29-
'import/extensions': 0,
30-
31-
'import/no-named-as-default-member': 0,
32-
'react/prop-types': 0,
33-
'react/display-name': 0,
34-
'react/react-in-jsx-scope': 0,
35-
36-
// type - any would be very handy in few cases.
37-
// The validation shouldn't be at linters end rather at code review end
38-
'@typescript-eslint/no-explicit-any': 0,
39-
40-
// Empty functions are handy for creating protocol not execution
41-
'@typescript-eslint/no-empty-function': 0,
42-
43-
// Require a whitespace at the beginning of a comment
44-
'spaced-comment': ['error', 'always'],
45-
46-
// Maximum line length for comments
47-
// Trailing comments allowed beyond maximum line length
48-
'max-len': ['error', { code: 400, comments: 200, ignoreTrailingComments: true }],
49-
50-
// Require PascalCase for user-defined JSX components
51-
'react/jsx-pascal-case': ['error'],
52-
53-
// Console logs cannot be committed.
54-
'no-console': ['error', { allow: ['warn', 'error'] }],
55-
56-
// Require props to be sorted
57-
'react/jsx-sort-props': ['error', { callbacksLast: true, shorthandFirst: true }],
58-
59-
// Omit boolean prop value when set to true
60-
'react/jsx-boolean-value': ['error', 'never'],
61-
62-
// Require self closing tags in JSX/HTML
63-
'react/self-closing-comp': ['error', { component: true, html: true }],
64-
65-
// Disallow unnecessary curly braces in JSX
66-
'react/jsx-curly-brace-presence': ['error', 'never'],
67-
68-
// Require blank line before certain statements
69-
'padding-line-between-statements': [
70-
'error',
71-
{ blankLine: 'always', prev: '*', next: 'function' },
72-
{ blankLine: 'always', prev: '*', next: 'class' },
73-
{ blankLine: 'always', prev: '*', next: 'export' },
74-
// Ignore consecutive export statements
75-
{ blankLine: 'any', prev: 'export', next: 'export' },
76-
{ blankLine: 'always', prev: '*', next: 'return' },
77-
{ blankLine: 'always', prev: '*', next: 'break' },
78-
{ blankLine: 'always', prev: '*', next: 'continue' },
79-
{ blankLine: 'always', prev: '*', next: 'throw' }
80-
],
81-
'prettier/prettier': ['error', { endOfLine: 'auto' }]
82-
},
83-
settings: {
84-
react: {
85-
version: 'detect'
86-
}
87-
}
88-
};
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
node: true,
5+
es2021: true,
6+
jest: true
7+
},
8+
extends: [
9+
'eslint:recommended',
10+
'plugin:@typescript-eslint/recommended',
11+
'plugin:prettier/recommended'
12+
],
13+
parser: '@typescript-eslint/parser',
14+
parserOptions: {
15+
ecmaFeatures: {
16+
jsx: true
17+
},
18+
ecmaVersion: 'latest',
19+
sourceType: 'module'
20+
},
21+
plugins: ['react-hooks', 'react', '@typescript-eslint', 'prettier'],
22+
ignorePatterns: ['**/plays/index.js'],
23+
24+
rules: {
25+
// Note: you must disable the base rule as it can report incorrect errors
26+
'no-unused-vars': 'off',
27+
'@typescript-eslint/no-unused-vars': 'warn',
28+
29+
'import/extensions': 0,
30+
31+
'import/no-named-as-default-member': 0,
32+
'react/prop-types': 0,
33+
'react/display-name': 0,
34+
'react/react-in-jsx-scope': 0,
35+
36+
// type - any would be very handy in few cases.
37+
// The validation shouldn't be at linters end rather at code review end
38+
'@typescript-eslint/no-explicit-any': 0,
39+
40+
// Empty functions are handy for creating protocol not execution
41+
'@typescript-eslint/no-empty-function': 0,
42+
43+
// Require a whitespace at the beginning of a comment
44+
'spaced-comment': ['error', 'always'],
45+
46+
// Maximum line length for comments
47+
// Trailing comments allowed beyond maximum line length
48+
'max-len': ['error', { code: 400, comments: 200, ignoreTrailingComments: true }],
49+
50+
// Require PascalCase for user-defined JSX components
51+
'react/jsx-pascal-case': ['error'],
52+
53+
// Console logs cannot be committed.
54+
'no-console': ['error', { allow: ['warn', 'error'] }],
55+
56+
// Require props to be sorted
57+
'react/jsx-sort-props': ['error', { callbacksLast: true, shorthandFirst: true }],
58+
59+
// Omit boolean prop value when set to true
60+
'react/jsx-boolean-value': ['error', 'never'],
61+
62+
// Require self closing tags in JSX/HTML
63+
'react/self-closing-comp': ['error', { component: true, html: true }],
64+
65+
// Disallow unnecessary curly braces in JSX
66+
'react/jsx-curly-brace-presence': ['error', 'never'],
67+
68+
// Require blank line before certain statements
69+
'padding-line-between-statements': [
70+
'error',
71+
{ blankLine: 'always', prev: '*', next: 'function' },
72+
{ blankLine: 'always', prev: '*', next: 'class' },
73+
{ blankLine: 'always', prev: '*', next: 'export' },
74+
// Ignore consecutive export statements
75+
{ blankLine: 'any', prev: 'export', next: 'export' },
76+
{ blankLine: 'always', prev: '*', next: 'return' },
77+
{ blankLine: 'always', prev: '*', next: 'break' },
78+
{ blankLine: 'always', prev: '*', next: 'continue' },
79+
{ blankLine: 'always', prev: '*', next: 'throw' }
80+
],
81+
'prettier/prettier': ['error', { endOfLine: 'auto' }]
82+
},
83+
settings: {
84+
react: {
85+
version: 'detect'
86+
}
87+
}
88+
};

‎src/common/header/Header.jsx‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import HeaderNav from './HeaderNav';
2-
import { useEffect, useState } from 'react';
2+
import { useEffect, useState, useRef } from 'react';
33
import { Link, useLocation } from 'react-router-dom';
44
import Countdown from 'react-countdown';
55
import './header.css';
@@ -10,6 +10,34 @@ const Header = () => {
1010
const pathName = location.pathname;
1111

1212
const [reset, setReset] = useState({ search: false, filter: false });
13+
const [visible, setVisible] = useState(true);
14+
const lastScrollY = useRef(0);
15+
16+
useEffect(() => {
17+
const handleScroll = () => {
18+
const current = window.scrollY;
19+
20+
const hero = document.getElementById('hero');
21+
const heroHeight = hero ? hero.offsetHeight : 0;
22+
23+
// if inside hero → always visible
24+
if (current < heroHeight) {
25+
setVisible(true);
26+
} else {
27+
if (current > lastScrollY.current) {
28+
setVisible(false); // down
29+
} else {
30+
setVisible(true); // up
31+
}
32+
}
33+
34+
lastScrollY.current = current;
35+
};
36+
37+
window.addEventListener('scroll', handleScroll);
38+
39+
return () => window.removeEventListener('scroll', handleScroll);
40+
}, []);
1341

1442
useEffect(() => {
1543
if (location.state) {
@@ -100,7 +128,7 @@ const Header = () => {
100128
<Countdown date={new Date(1675209600000)} renderer={activityTimerRenderer} />
101129
)}
102130
<header
103-
className={`app-header ${
131+
className={`app-header ${visible ? 'nav-visible' : 'nav-hidden'} ${
104132
showHideBits.setHeaderStyle
105133
? ''
106134
: ` app-header-home ${

‎src/common/header/header.css‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.app-header {
2-
position: relative;
2+
position: fixed;
33
top: 0;
4+
transition: transform 0.25s ease-in-out;
45
right: 0;
56
left: 0;
67
z-index: 10;
@@ -14,14 +15,23 @@
1415
padding: 0 1rem 0 0.6rem;
1516
}
1617

18+
.nav-visible {
19+
transform: translateY(0);
20+
}
21+
22+
.nav-hidden {
23+
transform: translateY(-100%);
24+
}
25+
26+
1727
@media screen and (max-width: 768px) {
1828
.app-header {
1929
padding: 0 1rem 0 0.2rem;
2030
}
2131
}
2232

2333
.app-header-home {
24-
position: sticky;
34+
/* position: sticky; */
2535
}
2636

2737
.app-header-home.app-header-home--promo {

‎src/common/home/Home.jsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import DefaultBanner from 'common/defaultBanner/DefaultBanner';
1212
const Home = () => {
1313
return (
1414
<main>
15-
<section className="app-home-body">
15+
<section className="app-home-body" id="hero">
1616
<DefaultBanner />
1717
</section>
1818
<section className="home-features">

0 commit comments

Comments
 (0)