Skip to content

Commit 8cd14fd

Browse files
committed
Merge branch 'master' into long-press-button-#66
2 parents 1cf27a0 + 316e495 commit 8cd14fd

File tree

6 files changed

+446
-38
lines changed

6 files changed

+446
-38
lines changed

.storybook/main.js

+30-30
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
22

3-
import {merge} from 'webpack-merge'
4-
const webpack=require("webpack");
3+
import { merge } from 'webpack-merge'
4+
const webpack = require("webpack");
55
const path = require("path");
66

77
const config = {
@@ -22,29 +22,29 @@ const config = {
2222
},
2323
webpackFinal: async (config) => {
2424

25-
const newConfig=merge(config,{
25+
const newConfig = merge(config, {
2626
module: {
2727
rules: [
28-
{
29-
test: /\.jsx$/,
30-
exclude: /node_modules/,
31-
//include: /(.*profile.*)/, // for some reason, webpack (4.25.1) will exclude files with names containing 'profile' (or 'profile-' not sure) so I has to explicitly include them
32-
loader: "babel-loader",
33-
},
34-
{
35-
test: /\.js$/,
36-
exclude: /node_modules/,
37-
loader: "babel-loader",
38-
},
39-
{
40-
test: /\.jsx$/,
41-
exclude: /node_modules/,
42-
loader: "babel-loader",
43-
}
28+
{
29+
test: /\.jsx$/,
30+
exclude: /node_modules/,
31+
//include: /(.*profile.*)/, // for some reason, webpack (4.25.1) will exclude files with names containing 'profile' (or 'profile-' not sure) so I has to explicitly include them
32+
loader: "babel-loader",
33+
},
34+
{
35+
test: /\.js$/,
36+
exclude: /node_modules/,
37+
loader: "babel-loader",
38+
},
39+
{
40+
test: /\.jsx$/,
41+
exclude: /node_modules/,
42+
loader: "babel-loader",
43+
}
4444
]
45-
},
45+
},
4646
resolve: {
47-
extensions: ['.*','.js','.jsx'],
47+
extensions: ['.*', '.js', '.jsx'],
4848
fallback: {
4949
"fs": false,
5050
"os": require.resolve("os-browserify/browser"),
@@ -58,16 +58,16 @@ const config = {
5858

5959
}
6060
},
61-
plugins:[
62-
new webpack.IgnorePlugin({resourceRegExp: /clustered|dateFile|file|fileSync|gelf|hipchat|logFacesAppender|loggly|logstashUDP|mailgun|multiprocess|slack|smtp/},/(.*log4js.*)/), // these appenders are require()ed by log4js but not used by this app
63-
new webpack.IgnorePlugin({resourceRegExp: /nodemailer/}), // not used in the client side - those should be move outside of the app directory
64-
61+
plugins: [
62+
new webpack.IgnorePlugin({ resourceRegExp: /clustered|dateFile|file|fileSync|gelf|hipchat|logFacesAppender|loggly|logstashUDP|mailgun|multiprocess|slack|smtp/ }, /(.*log4js.*)/), // these appenders are require()ed by log4js but not used by this app
63+
new webpack.IgnorePlugin({ resourceRegExp: /nodemailer/ }), // not used in the client side - those should be move outside of the app directory
64+
6565
// using a function because when this ran on heroku using just "../modules/client-side-model" failed
66-
new webpack.NormalModuleReplacementPlugin(/.+models\/.+/, resource => {
67-
resource.request = "../models/client-side-model";
68-
}),
69-
70-
new webpack.HotModuleReplacementPlugin() // DO NOT use --hot in the command line - it will cause a stack overflow on the client
66+
new webpack.NormalModuleReplacementPlugin(/.+models\/.+/, resource => {
67+
resource.request = "../models/client-side-model";
68+
}),
69+
70+
new webpack.HotModuleReplacementPlugin() // DO NOT use --hot in the command line - it will cause a stack overflow on the client
7171
]
7272
})
7373
return newConfig

app/components/theme.js

+9
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,28 @@ const Theme = {
66
success: '#005621',
77
lightSuccess: '#E6F3EB',
88
white: '#FFF',
9+
black: '#000',
910
borderGray: '#EBEBEB',
1011
title: '#1A1A1A',
12+
textPrimary: '#000000',
1113
textBrown: '#403105',
1214
encivYellow: '#FFC315',
1315
disableSecBorderGray: '#5D5D5C',
1416
disableTextBlack: '#343433',
1517
primaryButtonBlue: '#06335C',
1618
mouseDownPrimeBlue: '#01172C',
19+
hoverGray: '#B3B3B3',
20+
darkModeGray: '#343433',
21+
lightGray: '#F7F7F7',
22+
1723
},
1824
font: {
1925
fontFamily: 'Inter',
2026
fontStyle: 'normal',
27+
darkModeFont: '#FFFFFF',
2128
},
29+
condensedWidthBreakPoint: '40rem',
30+
maxPanelWidth: '90rem',
2231
}
2332

2433
export default Theme

app/components/top-nav-bar.jsx

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// https://github.com/EnCiv/civil-pursuit/issues/52
2+
3+
'use strict'
4+
5+
import React, { useState } from 'react';
6+
import cx from 'classnames';
7+
import { createUseStyles } from 'react-jss';
8+
import SvgEncivBlack from '../svgr/enciv-black';
9+
import SvgEncivWhite from '../svgr/enciv-white';
10+
11+
// Placeholder for the user-or-signup component
12+
const UserOrSignupPlaceholder = () => {
13+
return <div>Hello, Andrew! Logout</div>;
14+
};
15+
16+
17+
const TopNavBar = (props) => {
18+
const { className, menu, mode, defaultSelectedItem, ...otherProps } = props;
19+
const [isExpanded, setIsExpanded] = useState(false);
20+
const [selectedItem, setSelectedItem] = useState(defaultSelectedItem);
21+
const [openDropdown, setOpenDropdown] = useState(null);
22+
const handleMouseEnter = (index) => { setOpenDropdown(index) };
23+
const handleMouseLeave = () => { setOpenDropdown(null) };
24+
25+
const classes = useStylesFromThemeFunction(props);
26+
27+
const toggleMenu = () => {
28+
setIsExpanded(!isExpanded);
29+
};
30+
31+
const handleMenuItemClick = (item) => {
32+
item.func();
33+
setSelectedItem(item.name);
34+
};
35+
36+
const handleMenuDropdownClick = (item, index) => {
37+
setSelectedItem(item[0].name);
38+
item[index].func();
39+
};
40+
41+
const handleMobileMenuGroupClick = (item, index) => {
42+
item[0].func();
43+
44+
if (openDropdown === index) {
45+
setOpenDropdown(null);
46+
} else {
47+
setOpenDropdown(index);
48+
}
49+
};
50+
51+
return (
52+
<div className={cx(classes.componentWrapper, className)} {...otherProps}>
53+
<div className={classes.columnAligner}>
54+
<div className={`${classes.navBarContainer}`}>
55+
{mode === 'dark' ? <SvgEncivWhite className={classes.logo} /> :
56+
<SvgEncivBlack className={classes.logo} />}
57+
58+
{/* This is the computer menu */}
59+
<menu className={classes.menuContainer}>
60+
{menu && menu.map((item, index) => Array.isArray(item) ? (
61+
<li className={classes.menuList}>
62+
<div className={cx(classes.menuGroup, { [classes.selectedItem]: selectedItem === item[0].name })} key={index}
63+
onMouseEnter={() => handleMouseEnter(index)}
64+
onMouseLeave={() => handleMouseLeave()}>
65+
{item[0].name} {'\u25BE'}
66+
{openDropdown === index && (
67+
<div className={classes.dropdownMenu}>
68+
{item.slice(1).map((subItem, subIndex) => (
69+
<button key={subIndex} className={cx(classes.menuItem, { [classes.selectedItem]: selectedItem === subItem.name })}
70+
onClick={() => handleMenuItemClick(subItem)}>
71+
{subItem.name}
72+
</button>
73+
))}
74+
</div>
75+
)}
76+
</div>
77+
</li>
78+
) : (
79+
<li className={classes.menuList}>
80+
<button
81+
key={item.name}
82+
className={cx(classes.menuItem, { [classes.selectedItem]: selectedItem === item.name })}
83+
onClick={() => handleMenuItemClick(item)}>
84+
{item.name}
85+
</button>
86+
</li>
87+
))}
88+
</menu>
89+
90+
<div className={classes.userOrSignupContainer}>
91+
<UserOrSignupPlaceholder />
92+
</div>
93+
94+
<button className={classes.menuToggle} onClick={toggleMenu}>
95+
&#8801;
96+
</button>
97+
</div>
98+
99+
{/* This is the mobile menu */}
100+
{
101+
isExpanded ? <menu className={cx(classes.mobileMenuContainer)}>
102+
{menu && menu.map((item, index) => Array.isArray(item) ? (
103+
<li className={classes.menuList}>
104+
<div className={cx(classes.menuGroup, { [classes.selectedItem]: selectedItem === item[0].name })}
105+
key={index}
106+
onClick={() => handleMobileMenuGroupClick(item, index)}>
107+
{item[0].name} {'\u25BE'}
108+
{openDropdown === index && (
109+
<div className={classes.mobileDropdownMenu}>
110+
{item.slice(1).map((subItem, subIndex) => (
111+
<button key={subIndex} className={cx(classes.menuItem, { [classes.selectedItem]: selectedItem === subItem.name })}
112+
onClick={(event) => {
113+
event.stopPropagation();
114+
handleMenuItemClick(subItem);
115+
}}>
116+
{subItem.name}
117+
</button>
118+
))}
119+
</div>
120+
)}
121+
</div>
122+
</li>
123+
) : (
124+
<li className={classes.menuList}>
125+
<div
126+
key={item.name}
127+
className={cx(classes.menuItem, { [classes.selectedItem]: selectedItem === item.name })}
128+
onClick={() => handleMenuItemClick(item)}>
129+
{item.name}
130+
</div>
131+
</li>
132+
))}
133+
</menu> : null
134+
}
135+
</div >
136+
</div>
137+
);
138+
};
139+
140+
// Define the styles using the theme object
141+
const useStylesFromThemeFunction = createUseStyles(theme => ({
142+
componentWrapper: (props) => ({
143+
width: '100%',
144+
display: 'flex',
145+
justifyContent: 'center',
146+
alignItems: 'center',
147+
fontSize: '1rem',
148+
backgroundColor: props.mode === 'dark' ? theme.colors.darkModeGray : 'white',
149+
color: props.mode === 'dark' ? 'white' : 'defaultColor',
150+
}),
151+
152+
columnAligner: (props) => ({
153+
width: '100%',
154+
display: 'flex',
155+
flexDirection: 'column',
156+
justifyContent: 'center',
157+
alignItems: 'center',
158+
maxWidth: theme.maxPanelWidth,
159+
}),
160+
navBarContainer: {
161+
width: '80%',
162+
display: 'flex',
163+
justifyContent: 'space-between',
164+
alignItems: 'center',
165+
padding: '0.5rem',
166+
position: 'relative',
167+
},
168+
logo: {
169+
width: '8.5rem',
170+
height: 'auto',
171+
paddingBottom: '1.5rem',
172+
[`@media (max-width: ${theme.condensedWidthBreakPoint})`]: {
173+
width: '4rem',
174+
},
175+
},
176+
menuContainer: {
177+
display: 'flex',
178+
justifyContent: 'center',
179+
position: 'absolute',
180+
bottom: '10%',
181+
left: '50%',
182+
transform: 'translateX(-50%)',
183+
[`@media (max-width: ${theme.condensedWidthBreakPoint})`]: {
184+
display: 'none',
185+
},
186+
},
187+
mobileMenuContainer: (props) => ({
188+
display: 'flex',
189+
width: '80%',
190+
background: props.mode === 'dark' ? theme.colors.darkModeGray : theme.colors.encivYellow,
191+
flexDirection: 'column',
192+
justifyContent: 'center',
193+
[`@media (min-width: ${theme.condensedWidthBreakPoint})`]: {
194+
display: 'none',
195+
},
196+
}),
197+
menuGroup: {
198+
cursor: 'default',
199+
background: 'none',
200+
border: 'none',
201+
padding: '0.5rem 1rem',
202+
margin: '0 0.25rem',
203+
whiteSpace: 'nowrap',
204+
[`@media (max-width: ${theme.condensedWidthBreakPoint})`]: {
205+
cursor: 'pointer',
206+
},
207+
},
208+
dropdownMenu: (props) => ({
209+
position: 'absolute',
210+
background: props.mode === 'dark' ? 'grey' : theme.colors.encivYellow,
211+
display: 'flex',
212+
flexDirection: 'column',
213+
}),
214+
mobileDropdownMenu: {
215+
display: 'flex',
216+
flexDirection: 'column',
217+
padding: '0.25rem 0.25rem',
218+
},
219+
menuItem: {
220+
cursor: 'pointer',
221+
background: 'none',
222+
border: 'none',
223+
padding: '0.5rem 1rem',
224+
margin: '0 0.25rem',
225+
whiteSpace: 'nowrap',
226+
textAlign: 'left',
227+
'&:hover': {
228+
background: theme.colors.hoverGray,
229+
},
230+
},
231+
selectedItem: (props) => ({
232+
borderBottom: '0.125rem solid' + (props.mode === 'dark' ? theme.colors.white : theme.colors.black),
233+
}),
234+
userOrSignupContainer: {
235+
position: 'absolute',
236+
top: 0,
237+
right: 0,
238+
padding: '0.5rem',
239+
[`@media (max-width: ${theme.condensedWidthBreakPoint})`]: {
240+
display: 'none',
241+
},
242+
},
243+
menuToggle: (props) => ({
244+
width: '15%',
245+
height: 'auto',
246+
fontSize: '1.5rem',
247+
background: props.mode === 'dark' ? theme.colors.darkModeGray : 'white',
248+
border: 'none',
249+
display: 'flex',
250+
justifyContent: 'center',
251+
alignItems: 'center',
252+
[`@media (min-width: ${theme.condensedWidthBreakPoint})`]: {
253+
display: 'none'
254+
},
255+
'&:hover': {
256+
background: theme.colors.hoverGray,
257+
},
258+
}),
259+
menuList: {
260+
listStyle: 'none',
261+
},
262+
}));
263+
264+
export default TopNavBar

0 commit comments

Comments
 (0)