├── .DS_Store
├── .gitignore
├── .husky
└── pre-commit
├── .prettierrc
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── rhr-logo.png
├── rollup.config.js
└── src
├── components
├── Access
│ └── Access.js
├── CustomLink
│ └── CustomLink.js
├── Home
│ └── Home.js
├── MainMenu
│ ├── MainMenu.js
│ └── MainMenu.scss
├── MobileMenuToggle
│ ├── MobileMenuToggle.js
│ └── MobileMenuToggle.scss
└── SubMenu
│ └── SubMenu.js
├── index.js
├── styles.scss
└── variables.scss
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liubov-js/react-header-responsive/cdbc3761e6c332a3d18d8493b12561a9fc33f7e5/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .idea
4 | *.tgz
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx pretty-quick --staged
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Liubov Krivtsova
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | # react-header-responsive
15 |
16 | A responsive header menu for React applications with submenu support
17 |
18 | ### Features
19 |
20 | - [x] Menu & Submenu
21 | - [x] Desktop version
22 | - [x] Mobile version
23 | - [x] Tablet support
24 | - [x] Current link highlighting
25 | - [x] Custom anchor component support
26 | - [x] Server-side rendering (SSR) support
27 | - [x] Overlapping (display invisible header over content)
28 |
29 | ### Installation
30 |
31 | ```
32 | npm install react-header-responsive
33 | ```
34 |
35 | ### Usage
36 |
37 | | Property | Description |
38 | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39 | | `home` | _Optional._ Logo component |
40 | | `pages` | _Optional._ An array of objects with `name` and `link`, or `name` and `children` properties. If children is specified it is an array of objects with `name` and `link` properties |
41 | | `access` | _Optional._ Access component for login, registration, etc. |
42 | | `anchor` | _Optional._ Function with an anchor component |
43 | | `overlap` | _Optional._ Whether the header should overlap the content and be invisible |
44 | | `currentPath` | _Required for SSR only._ Used **only for server-side rendering (SSR)** instead of _window.location.pathname_ |
45 |
46 | ### Example
47 |
48 | The use of react-header-responsive in a real project can be seen [here](https://cucomm.com/).
49 |
50 | ```js
51 | import Header from 'react-header-responsive';
52 | import logo from './rhr-logo.png';
53 |
54 | function App() {
55 | const pages = [
56 | {
57 | name: 'About',
58 | link: '/about',
59 | },
60 | {
61 | name: 'Products',
62 | children: [
63 | {
64 | name: 'Product-1',
65 | link: '/product1',
66 | },
67 | {
68 | name: 'Product-2',
69 | link: '/product2',
70 | },
71 | {
72 | name: 'Product-3',
73 | link: '/product3',
74 | },
75 | ],
76 | },
77 | {
78 | name: 'Pricing',
79 | link: '/pricing',
80 | },
81 | ];
82 |
83 | const Access = () => {
84 | return (
85 |
86 | Sign Up
87 | Sign In
88 |
89 | );
90 | };
91 |
92 | return (
93 | <>
94 | }
96 | pages={pages}
97 | anchor={(link, name, className) => (
98 |
99 | {name}
100 |
101 | )}
102 | access={ }
103 | overlap
104 | />
105 | >
106 | );
107 | }
108 | ```
109 |
110 | ### Contributing
111 |
112 | 1. Clone repo.
113 | 2. Create / checkout feature/{name}, or fix/{name} branch.
114 | 3. Install deps: npm.
115 | 4. Make your changes.
116 | 5. Commit: git commit.
117 | 6. Push: git push.
118 | 7. Open PR targeting the main branch.
119 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-header-responsive",
3 | "version": "1.1.2",
4 | "description": "A responsive header for React apps",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "prepare": "husky install"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.12.10",
13 | "@babel/preset-env": "^7.20.2",
14 | "@babel/preset-react": "^7.12.10",
15 | "@rollup/plugin-node-resolve": "^11.1.1",
16 | "babel-loader": "^8.2.2",
17 | "husky": "^8.0.0",
18 | "prettier": "2.8.4",
19 | "pretty-quick": "^3.1.3",
20 | "react": "^18.2.0",
21 | "rollup": "^2.38.4",
22 | "rollup-plugin-babel": "^4.4.0",
23 | "rollup-plugin-peer-deps-external": "^2.2.4",
24 | "rollup-plugin-postcss": "^4.0.0",
25 | "rollup-plugin-terser": "^7.0.2"
26 | },
27 | "peerDependencies": {
28 | "react": "^18.2.0"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/liubov-js/react-header-responsive.git"
33 | },
34 | "author": "Liubov Krivtsova",
35 | "license": "MIT",
36 | "keywords": [
37 | "react",
38 | "header"
39 | ],
40 | "bugs": {
41 | "url": "https://github.com/liubov-js/react-header-responsive/issues"
42 | },
43 | "homepage": "https://github.com/liubov-js/react-header-responsive#readme"
44 | }
45 |
--------------------------------------------------------------------------------
/rhr-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liubov-js/react-header-responsive/cdbc3761e6c332a3d18d8493b12561a9fc33f7e5/rhr-logo.png
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import resolve from '@rollup/plugin-node-resolve';
3 | import external from 'rollup-plugin-peer-deps-external';
4 | import { terser } from 'rollup-plugin-terser';
5 | import postcss from 'rollup-plugin-postcss';
6 |
7 | export default [
8 | {
9 | input: './src/index.js',
10 | output: [
11 | {
12 | file: 'dist/index.js',
13 | format: 'cjs',
14 | },
15 | {
16 | file: 'dist/index.es.js',
17 | format: 'es',
18 | exports: 'named',
19 | },
20 | ],
21 | plugins: [
22 | postcss({
23 | plugins: [],
24 | minimize: true,
25 | }),
26 | babel({
27 | exclude: 'node_modules/**',
28 | presets: ['@babel/preset-react'],
29 | }),
30 | external(),
31 | resolve(),
32 | terser(),
33 | ],
34 | },
35 | ];
36 |
--------------------------------------------------------------------------------
/src/components/Access/Access.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Access = ({ access }) => {
4 | return {access}
;
5 | };
6 |
7 | export default Access;
8 |
--------------------------------------------------------------------------------
/src/components/CustomLink/CustomLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CustomLink = ({ page, subClass, currentPath, anchor }) => {
4 | const path = currentPath || window.location.pathname;
5 |
6 | if (anchor) {
7 | return anchor(
8 | page.link,
9 | page.name,
10 | `${subClass} ${
11 | page.link === path ? 'rhr-active-color' : 'rhr-common-color'
12 | }`
13 | );
14 | }
15 |
16 | return (
17 |
23 | {page.name}
24 |
25 | );
26 | };
27 |
28 | export default CustomLink;
29 |
--------------------------------------------------------------------------------
/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Home = ({ home }) => {
4 | return {home}
;
5 | };
6 |
7 | export default Home;
8 |
--------------------------------------------------------------------------------
/src/components/MainMenu/MainMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SubMenu from '../SubMenu/SubMenu';
3 | import CustomLink from '../CustomLink/CustomLink';
4 | import './MainMenu.scss';
5 |
6 | const MainMenu = ({ pages, access, anchor, currentPath }) => {
7 | return (
8 |
9 |
10 | {pages.map((el, i) => {
11 | if (!el.children) {
12 | return (
13 |
14 |
19 |
20 | );
21 | } else {
22 | return (
23 |
24 |
25 | {el.name}
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | })}
33 | {access}
34 |
35 |
36 | );
37 | };
38 |
39 | export default MainMenu;
40 |
--------------------------------------------------------------------------------
/src/components/MainMenu/MainMenu.scss:
--------------------------------------------------------------------------------
1 | @import '../../variables';
2 |
3 | nav {
4 | height: 100%;
5 | margin: 60px 40px 0;
6 | max-width: 100%;
7 | }
8 |
9 | nav ul {
10 | height: 20px;
11 | list-style: none;
12 | margin: 0;
13 | display: block;
14 | float: left;
15 | padding: 0;
16 | position: relative;
17 | width: 100%;
18 | }
19 |
20 | nav a:hover,
21 | nav span:hover {
22 | background-color: inherit;
23 | }
24 |
25 | nav a:link,
26 | nav a:visited,
27 | nav span {
28 | text-decoration: none;
29 | display: block;
30 | font-size: 20px;
31 | text-align: left;
32 | }
33 |
34 | nav ul ul {
35 | display: none;
36 | position: inherit;
37 | top: 100%;
38 | margin-left: 24px;
39 | }
40 |
41 | nav ul li:hover > ul {
42 | position: absolute;
43 | top: 60px;
44 | width: max-content;
45 | display: contents;
46 | }
47 |
48 | nav ul li ul li {
49 | min-width: 170px;
50 | float: none;
51 | display: list-item;
52 | position: relative;
53 | background-color: $background-color;
54 | padding-left: 24px;
55 | }
56 |
57 | .rhr-arrow {
58 | margin-left: 6px;
59 | margin-bottom: 3px;
60 | border: solid $common-link-color;
61 | border-width: 0 1px 1px 0;
62 | display: inline-block;
63 | padding: 3px;
64 | transform: rotate(45deg);
65 | }
66 |
67 | .rhr-caret {
68 | color: $common-link-color;
69 | cursor: pointer;
70 | user-select: none;
71 | line-height: $line-height-main-menu;
72 | }
73 |
74 | .rhr-active-color {
75 | color: $active-link-color;
76 | }
77 |
78 | .rhr-common-color {
79 | color: $common-link-color;
80 | }
81 |
82 | .rhr-sub-menu-link {
83 | padding: 0;
84 | line-height: $line-height-sub-menu;
85 | }
86 |
87 | .rhr-main-menu-link {
88 | line-height: $line-height-sub-menu;
89 | padding: 0;
90 | }
91 |
92 | .rhr-main-access {
93 | display: flex;
94 | justify-content: center;
95 | }
96 |
97 | /* DESKTOP */
98 | @media only screen and (min-width: 768px) {
99 | nav {
100 | margin: 0 0 0 15px;
101 | }
102 |
103 | nav a:hover,
104 | nav span:hover {
105 | color: $active-link-color;
106 | }
107 |
108 | nav span:hover > i {
109 | border: solid $active-link-color;
110 | border-width: 0 1px 1px 0;
111 | }
112 |
113 | nav ul li {
114 | margin: auto;
115 | display: inline-block;
116 | }
117 |
118 | nav ul li ul li {
119 | padding-left: 0;
120 | }
121 |
122 | nav ul {
123 | height: 100%;
124 | display: -webkit-box; /*flex;*/
125 | }
126 |
127 | nav ul ul {
128 | margin-left: 0;
129 | position: inherit;
130 | }
131 |
132 | .rhr-main-access {
133 | display: none;
134 | }
135 |
136 | nav ul li:hover > ul {
137 | display: block;
138 | }
139 |
140 | .rhr-sub-menu-link {
141 | padding: 0 15px;
142 | }
143 |
144 | .rhr-main-menu-link {
145 | line-height: $line-height-main-menu;
146 | padding: 0 15px;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/components/MobileMenuToggle/MobileMenuToggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './MobileMenuToggle.scss';
3 |
4 | const MobileMenuToggle = (props) => {
5 | return (
6 |
11 | );
12 | };
13 |
14 | export default MobileMenuToggle;
15 |
--------------------------------------------------------------------------------
/src/components/MobileMenuToggle/MobileMenuToggle.scss:
--------------------------------------------------------------------------------
1 | @import '../../variables';
2 |
3 | .rhr-menu-toggle {
4 | display: inline-block;
5 | cursor: pointer;
6 | }
7 |
8 | .rhr-bar-1,
9 | .rhr-bar-2,
10 | .rhr-bar-3 {
11 | width: 30px;
12 | height: 3px;
13 | background-color: $common-link-color;
14 | margin: 6px 0;
15 | transition: 0.4s;
16 | }
17 |
18 | .rhr-toggled {
19 | position: fixed;
20 | float: right;
21 | right: 13px;
22 | }
23 |
24 | .rhr-toggled .rhr-bar-1 {
25 | -webkit-transform: rotate(-45deg) translate(-6px, 4px);
26 | transform: rotate(-45deg) translate(-6px, 4px);
27 | }
28 |
29 | .rhr-toggled .rhr-bar-2 {
30 | opacity: 0;
31 | }
32 |
33 | .rhr-toggled .rhr-bar-3 {
34 | -webkit-transform: rotate(45deg) translate(-9px, -7px);
35 | transform: rotate(45deg) translate(-9px, -7px);
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/SubMenu/SubMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CustomLink from '../CustomLink/CustomLink';
3 |
4 | const SubMenu = ({ pages, anchor, currentPath }) => {
5 | return (
6 |
7 | {pages.map((ch, i) => {
8 | return (
9 |
10 |
15 |
16 | );
17 | })}
18 |
19 | );
20 | };
21 |
22 | export default SubMenu;
23 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import MainMenu from './components/MainMenu/MainMenu';
3 | import Home from './components/Home/Home';
4 | import Access from './components/Access/Access';
5 | import MobileMenuToggle from './components/MobileMenuToggle/MobileMenuToggle';
6 | import './styles.scss';
7 |
8 | const Header = ({ pages, anchor, home, access, currentPath, overlap }) => {
9 | const [isToggled, setIsToggled] = useState(false);
10 |
11 | const toggleMenu = () => {
12 | setIsToggled((prevState) => !prevState);
13 | };
14 |
15 | useEffect(() => {
16 | setIsToggled(false);
17 | }, [currentPath]);
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
29 | {pages && }
30 |
31 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default Header;
42 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .rhr-header {
4 | display: flex;
5 | justify-content: space-between;
6 | background-color: $background-color;
7 | height: 60px;
8 | width: 100%;
9 | font-family: 'Roboto Light', serif;
10 | z-index: 10000;
11 | }
12 |
13 | .rhr-overlap {
14 | position: absolute;
15 | background-color: rgb(0, 0, 0, 0);
16 | }
17 |
18 | .rhr-menu-container {
19 | z-index: 10000;
20 | position: fixed;
21 | max-width: 350px;
22 | width: 350px;
23 | height: 100%;
24 | background-color: $background-color;
25 | -webkit-font-smoothing: antialiased;
26 | transform-origin: 0 0;
27 | right: 0;
28 | /*transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1.0);*/
29 | }
30 |
31 | .rhr-menu-container-mobile-transform {
32 | transform: translate(100%, 0);
33 | }
34 |
35 | .rhr-logo {
36 | max-height: 100%;
37 | }
38 |
39 | .rhr-access,
40 | .rhr-home {
41 | height: 100%;
42 | display: flex;
43 | align-items: center;
44 | }
45 |
46 | .rhr-mobile-menu {
47 | z-index: 10000;
48 | margin: 13px;
49 | }
50 |
51 | .rhr-access-container {
52 | display: none;
53 | }
54 |
55 | @media only screen and (min-width: 768px) {
56 | .rhr-access-container {
57 | display: block;
58 | }
59 |
60 | .rhr-menu-container {
61 | z-index: 10000;
62 | position: relative;
63 | max-width: 100%;
64 | display: block;
65 | width: 100%;
66 | height: 0;
67 | margin: 0;
68 | padding: 0;
69 | }
70 |
71 | .rhr-menu-container-mobile-transform {
72 | transform: none;
73 | }
74 |
75 | .rhr-mobile-menu {
76 | display: none;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/variables.scss:
--------------------------------------------------------------------------------
1 | $active-link-color: orange;
2 | $common-link-color: rgb(0 60 95);
3 | $background-color: rgb(217 217 217);
4 | $line-height-main-menu: 60px;
5 | $line-height-sub-menu: 40px;
6 |
--------------------------------------------------------------------------------