Skip to content

Commit 1346bb3

Browse files
committed
Initial commit
0 parents  commit 1346bb3

13 files changed

+23443
-0
lines changed

.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# dependencies
2+
/node_modules
3+
/.pnp
4+
.pnp.js
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# More Menu
2+
3+
A demo for display extra menu items dynamically in More menu, in React
4+
5+
`git clone [email protected]:imranhsayed/more-menu-react.git`
6+
7+
`npm install`
8+
9+
`npm start`

package-lock.json

+12,228
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "more-menu",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"react": "^16.8.6",
7+
"react-dom": "^16.8.6",
8+
"react-scripts": "3.0.0",
9+
"styled-components": "^4.2.0"
10+
},
11+
"scripts": {
12+
"start": "react-scripts start",
13+
"build": "react-scripts build",
14+
"test": "react-scripts test",
15+
"eject": "react-scripts eject"
16+
},
17+
"eslintConfig": {
18+
"extends": "react-app"
19+
},
20+
"browserslist": {
21+
"production": [
22+
">0.2%",
23+
"not dead",
24+
"not op_mini all"
25+
],
26+
"development": [
27+
"last 1 chrome version",
28+
"last 1 firefox version",
29+
"last 1 safari version"
30+
]
31+
}
32+
}

public/index.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<meta name="theme-color" content="#000000" />
7+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
8+
<title>More Menu Demo</title>
9+
</head>
10+
<body>
11+
<noscript>You need to enable JavaScript to run this app.</noscript>
12+
<div id="root"></div>
13+
</body>
14+
</html>

src/App.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import Header from "./components/Header";
3+
4+
function App() {
5+
return (
6+
<div>
7+
<Header/>
8+
</div>
9+
);
10+
}
11+
12+
export default App;

src/App.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './App';
4+
5+
it('renders without crashing', () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<App />, div);
8+
ReactDOM.unmountComponentAtNode(div);
9+
});

src/components/Header.js

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import React from 'react';
2+
import { menuData } from "./menu-data";
3+
import {
4+
DesktopNavContainer,
5+
DesktopHeaderMain,
6+
DesktopNavWrap,
7+
DesktopNavList,
8+
DesktopNavLink,
9+
DesktopMoreListContainer,
10+
DesktopMoreListItem,
11+
DesktopMoreListLink,
12+
} from "./MenuElements";
13+
14+
class Header extends React.Component {
15+
16+
constructor(props) {
17+
super(props);
18+
this.state = {
19+
currentIndex: '',
20+
hiddenItems: [],
21+
};
22+
this.primaryContainer = React.createRef();
23+
this.moreEl = React.createRef();
24+
this.secondaryContainer = React.createRef();
25+
}
26+
27+
componentDidMount() {
28+
// Adapt Immediately on load
29+
this.doAdapt();
30+
31+
// Then call doAdapt() when window is resized.
32+
window.addEventListener('resize', this.doAdapt);
33+
}
34+
35+
componentWillUnmount() {
36+
window.removeEventListener('resize', this.doAdapt);
37+
}
38+
39+
/**
40+
* Gets called on window resize and handles menu items visibility.
41+
*/
42+
doAdapt = () => {
43+
// Return on smaller screens
44+
if (window.innerWidth < 700) {
45+
return;
46+
}
47+
48+
let moreBtnWidth = this.moreEl.current
49+
? this.moreEl.current.offsetWidth
50+
: 0;
51+
const primaryContainerWidth = this.primaryContainer.current
52+
? this.primaryContainer.current.offsetWidth
53+
: 0;
54+
const primaryItems = this.primaryContainer.current
55+
? this.primaryContainer.current.childNodes
56+
: [];
57+
const allListItems = this.primaryContainer.current
58+
? this.primaryContainer.current.querySelectorAll('li')
59+
: [];
60+
61+
// Reveal all list-items first.
62+
if (allListItems.length) {
63+
allListItems.forEach((item) => {
64+
item.classList.remove('--hidden');
65+
});
66+
}
67+
68+
/**
69+
* Check if the item fits in the container, hide the item if not and save its index for later use.
70+
* @type {number}
71+
*/
72+
if (primaryItems.length) {
73+
primaryItems.forEach((item, index) => {
74+
let isSecondaryItem = item.classList.contains('-more');
75+
76+
// Check if its not the secondary Item( more element )
77+
if (!isSecondaryItem) {
78+
/**
79+
* If the (itemWidth + moreBtnWidth) is less/equal to the (container width - 160px for menu icon and searchicon),
80+
* increment the moreBtnWidth by adding item width.
81+
* Remove this index from the hiddenItems Array, if it exists.
82+
*/
83+
if (moreBtnWidth + item.offsetWidth <= primaryContainerWidth - 160) {
84+
moreBtnWidth += item.offsetWidth;
85+
86+
let newArray = this.state.hiddenItems;
87+
let removableIndex = newArray.indexOf(index);
88+
if (removableIndex > -1) {
89+
newArray.splice(removableIndex, 1);
90+
}
91+
const distinctArray = [...new Set(newArray)];
92+
this.setState({ hiddenItems: distinctArray });
93+
} else {
94+
/**
95+
* If the itemWidth + moreBtnWidth is greater than the (container width - 160px for menu icon and searchicon),
96+
* hide that item
97+
* Add this index from the hiddenItems Array, if it does not exit already.
98+
*/
99+
item.classList.add('--hidden');
100+
101+
let newArray = this.state.hiddenItems;
102+
newArray.push(index);
103+
const distinctArray = [...new Set(newArray)];
104+
this.setState({ hiddenItems: distinctArray });
105+
}
106+
}
107+
});
108+
}
109+
110+
// If there are no hidden items then hide More Element.
111+
if (!this.state.hiddenItems.length && this.moreEl.current) {
112+
this.moreEl.current.classList.add('--hidden');
113+
}
114+
115+
// Hide the equivalent items from the secondary list that remained visible in the primary one
116+
else {
117+
let secondaryItems = this.secondaryContainer.current
118+
? this.secondaryContainer.current.childNodes
119+
: [];
120+
if (secondaryItems.length) {
121+
secondaryItems.forEach((item, index) => {
122+
if (!this.state.hiddenItems.includes(index)) {
123+
item.classList.add('--hidden');
124+
}
125+
});
126+
}
127+
}
128+
};
129+
130+
/**
131+
* Render all menu items under More
132+
*/
133+
renderMoreItem = () => {
134+
return (
135+
<DesktopNavList ref={this.moreEl} className="-more --hidden">
136+
<DesktopNavLink className="cts-more-element" href="#">
137+
More
138+
</DesktopNavLink>
139+
<DesktopMoreListContainer
140+
ref={this.secondaryContainer}
141+
className={`-secondary`}>
142+
{menuData.map((menuItem) => (
143+
<DesktopMoreListItem key={menuItem.url}>
144+
<DesktopMoreListLink href={menuItem.url}>
145+
{menuItem.label}
146+
</DesktopMoreListLink>
147+
</DesktopMoreListItem>
148+
))}
149+
</DesktopMoreListContainer>
150+
</DesktopNavList>
151+
);
152+
};
153+
154+
render() {
155+
return(
156+
<DesktopHeaderMain>
157+
<DesktopNavWrap>
158+
<DesktopNavContainer ref={this.primaryContainer}>
159+
{menuData.map((item) => (
160+
<DesktopNavList key={item.url} className="primary-items">
161+
<DesktopNavLink
162+
className="cts-primary-link"
163+
href={item.url}>
164+
{item.label}
165+
</DesktopNavLink>
166+
</DesktopNavList>
167+
))}
168+
{this.renderMoreItem()}
169+
</DesktopNavContainer>
170+
</DesktopNavWrap>
171+
</DesktopHeaderMain>
172+
)
173+
}
174+
}
175+
176+
export default Header;

0 commit comments

Comments
 (0)