Skip to content

Commit a336fcd

Browse files
authored
style: implement new design for menu (#30)
1 parent 78ab839 commit a336fcd

File tree

11 files changed

+231
-68
lines changed

11 files changed

+231
-68
lines changed

.storybook/preview.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ export const parameters = {
1818
docs: {
1919
theme: storybookTheme,
2020
},
21+
layout: 'centered',
2122
};

src/assets/OpenDEXLogo.png

-43.4 KB
Binary file not shown.

src/assets/OpenDEXLogo.svg

Lines changed: 42 additions & 0 deletions
Loading
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
import { makeStyles } from "@material-ui/core/styles";
3+
import { openDexLogo } from "../../utils/svgIcons";
4+
5+
const useStyles = makeStyles(() => ({
6+
logoContainer: {
7+
paddingTop: "30px",
8+
width: "100%",
9+
},
10+
logo: {
11+
height: "75px",
12+
width: "100%",
13+
},
14+
}));
15+
16+
export const OpenDexMainLogo: React.FunctionComponent = () => {
17+
const classes = useStyles();
18+
19+
return (
20+
<div className={classes.logoContainer}>
21+
<img src={openDexLogo} alt="OpenDEX" className={classes.logo} />
22+
</div>
23+
);
24+
};

src/common/components/navigation/Menu.tsx

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React from "react";
1+
import React, { useEffect, useState } from "react";
22
import Drawer from "@material-ui/core/Drawer";
3-
import { Grid, Typography } from "@material-ui/core";
3+
import { Grid } from "@material-ui/core";
44
import List from "@material-ui/core/List";
55
import AccountBalanceWalletOutlinedIcon from "@material-ui/icons/AccountBalanceWalletOutlined";
66
import CachedIcon from "@material-ui/icons/Cached";
@@ -9,7 +9,8 @@ import RemoveRedEyeOutlinedIcon from "@material-ui/icons/RemoveRedEyeOutlined";
99
import SettingsIcon from "@material-ui/icons/Settings";
1010
import SportsEsportsIcon from "@material-ui/icons/SportsEsports";
1111
import TrendingUpIcon from "@material-ui/icons/TrendingUp";
12-
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
12+
import { makeStyles } from "@material-ui/core/styles";
13+
import { useLocation, useRouteMatch } from "react-router-dom";
1314
import { Path } from "../../../router/Path";
1415
import MenuItem, { MenuItemProps } from "./MenuItem";
1516
import Overview from "../../../dashboard/overview/Overview";
@@ -20,6 +21,7 @@ import Console from "../../../dashboard/console/Console";
2021
import { isElectron, sendMessageToParent } from "../../utils/appUtil";
2122
import Button from "../input/buttons/Button";
2223
import Settings from "../../../settings/Settings";
24+
import { OpenDexMainLogo } from "../icons/OpenDexMainLogo";
2325

2426
export const menuItems: MenuItemProps[] = [
2527
{
@@ -53,27 +55,44 @@ export const menuItems: MenuItemProps[] = [
5355
component: Console,
5456
icon: SportsEsportsIcon,
5557
},
58+
{
59+
path: Path.SETTINGS,
60+
text: "Settings",
61+
component: <Settings />,
62+
icon: SettingsIcon,
63+
},
5664
];
5765

58-
export const drawerWidth = 200;
66+
export const drawerWidth = 250;
5967

60-
const useStyles = makeStyles((theme: Theme) =>
61-
createStyles({
62-
drawerPaper: {
63-
width: drawerWidth,
64-
justifyContent: "space-between",
65-
},
66-
menuContainer: {
67-
width: "100%",
68-
},
69-
header: {
70-
padding: "16px",
71-
},
72-
drawerButton: {
73-
margin: theme.spacing(2),
74-
},
75-
})
76-
);
68+
const useStyles = makeStyles((theme) => ({
69+
drawerPaper: {
70+
width: drawerWidth,
71+
justifyContent: "space-between",
72+
background: "linear-gradient(#101013, #1c2027, #1a1112, #171011)",
73+
border: "none",
74+
},
75+
menuContainer: {
76+
width: "100%",
77+
paddingLeft: "10px",
78+
},
79+
drawerButton: {
80+
margin: theme.spacing(2),
81+
},
82+
borderRadiusContainer: {
83+
height: "25px",
84+
},
85+
borderRadiusOfTopContainer: {
86+
borderRadius: "0px 0px 25px 0px",
87+
boxShadow: "25px 0px 0px #0c0c0c",
88+
transition: "box-shadow 0.1s ease",
89+
},
90+
borderRadiusOfBottomContainer: {
91+
borderRadius: "0px 25px 0px 0px",
92+
boxShadow: "25px 0px 0px #0c0c0c",
93+
transition: "box-shadow 0.1s ease",
94+
},
95+
}));
7796

7897
export interface MenuProps {
7998
syncInProgress: boolean;
@@ -82,6 +101,22 @@ export interface MenuProps {
82101

83102
export const Menu: React.FunctionComponent<MenuProps> = (props) => {
84103
const classes = useStyles();
104+
const { url } = useRouteMatch();
105+
const { pathname } = useLocation();
106+
const [selectedIndex, setSelectedIndex] = useState(0);
107+
108+
const handleSetSelectedIndex = () => {
109+
const activeItem = menuItems.findIndex(
110+
(item) =>
111+
pathname === `${url}${item.path}` ||
112+
pathname.startsWith(`${url}${item.path}/`)
113+
);
114+
setSelectedIndex(activeItem);
115+
};
116+
117+
useEffect(() => {
118+
handleSetSelectedIndex();
119+
});
85120

86121
const disconnect = (): void => {
87122
sendMessageToParent("disconnect");
@@ -96,17 +131,21 @@ export const Menu: React.FunctionComponent<MenuProps> = (props) => {
96131
anchor="left"
97132
>
98133
<Grid container item>
99-
<Typography
100-
className={classes.header}
101-
variant="overline"
102-
component="p"
103-
color="textSecondary"
104-
>
105-
OpenDEX
106-
</Typography>
134+
<OpenDexMainLogo />
107135
<List className={classes.menuContainer}>
108-
{menuItems.map((item) => (
136+
<div
137+
className={
138+
selectedIndex === 0
139+
? `${classes.borderRadiusOfTopContainer} ${classes.borderRadiusContainer}`
140+
: classes.borderRadiusContainer
141+
}
142+
></div>
143+
144+
{menuItems.map((item, i) => (
109145
<MenuItem
146+
isBeforeSelected={i + 1 === selectedIndex && selectedIndex !== -1}
147+
isAfterSelected={i - 1 === selectedIndex && selectedIndex !== -1}
148+
selected={i === selectedIndex}
110149
path={item.path}
111150
text={item.text}
112151
component={item.component}
@@ -117,17 +156,16 @@ export const Menu: React.FunctionComponent<MenuProps> = (props) => {
117156
tooltipTextRows={props.menuItemTooltipMsg}
118157
/>
119158
))}
159+
<div
160+
className={
161+
selectedIndex === menuItems.length - 1
162+
? `${classes.borderRadiusOfBottomContainer} ${classes.borderRadiusContainer}`
163+
: classes.borderRadiusContainer
164+
}
165+
></div>
120166
</List>
121167
</Grid>
122168
<Grid container item direction="column" justify="flex-end">
123-
<Grid item container>
124-
<MenuItem
125-
path={Path.SETTINGS}
126-
text={"Settings"}
127-
component={Settings}
128-
icon={SettingsIcon}
129-
/>
130-
</Grid>
131169
{isElectron() && (
132170
<Grid item container justify="center">
133171
<Button

src/common/components/navigation/MenuItem.tsx

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,81 @@
1-
import {
2-
ListItemIcon,
3-
Tooltip,
4-
Theme,
5-
createStyles,
6-
makeStyles,
7-
} from "@material-ui/core";
1+
import { ListItemIcon, Tooltip, makeStyles, Theme } from "@material-ui/core";
82
import ListItem from "@material-ui/core/ListItem";
93
import ListItemText from "@material-ui/core/ListItemText";
104
import React, { ComponentClass, ElementType, ReactElement } from "react";
11-
import { NavLink, useLocation, useRouteMatch } from "react-router-dom";
5+
import { NavLink, useRouteMatch } from "react-router-dom";
126
import { Path } from "../../../router/Path";
137

148
export type MenuItemProps = {
159
path: Path;
1610
text: string;
17-
component: ComponentClass<any> | (() => ReactElement);
11+
component:
12+
| ComponentClass<any>
13+
| (() => ReactElement)
14+
| React.ReactElement<any>;
1815
icon: ElementType;
1916
isFallback?: boolean;
2017
isDisabled?: boolean;
2118
tooltipTextRows?: string[];
19+
isBeforeSelected?: boolean;
20+
isAfterSelected?: boolean;
21+
selected?: boolean;
2222
};
2323

24-
const useStyles = makeStyles((theme: Theme) =>
25-
createStyles({
26-
disabled: {
27-
color: theme.palette.text.disabled,
28-
},
29-
})
30-
);
24+
const getListItemColor = (theme: Theme, props: MenuItemProps) => {
25+
if (props.isDisabled) {
26+
return theme.palette.text.disabled;
27+
} else if (props.selected) {
28+
return "#d7d9e2";
29+
} else {
30+
return "#9a9ca4";
31+
}
32+
};
33+
34+
const getListItemBorderRadius = (props: MenuItemProps) => {
35+
if (props.isBeforeSelected) {
36+
return "25px 0px 25px 25px";
37+
} else if (props.isAfterSelected) {
38+
return "25px 25px 0px 25px";
39+
} else {
40+
return "25px 0px 0px 25px";
41+
}
42+
};
43+
44+
const getListItemBoxShadow = (props: MenuItemProps) => {
45+
if (props.isBeforeSelected) {
46+
return "30px 0px 0px #0c0c0c";
47+
} else if (props.isAfterSelected) {
48+
return "25px 0px 0px #0c0c0c";
49+
} else {
50+
return "";
51+
}
52+
};
53+
54+
const getMuiListItemHoverFocusProperties = (props: MenuItemProps) => ({
55+
color: !props.isDisabled ? "#d7d9e2" : "",
56+
backgroundColor: !props.selected ? "transparent" : "#0c0c0c",
57+
});
58+
59+
const useStyles = makeStyles((theme) => ({
60+
listItem: (props: MenuItemProps) => ({
61+
height: "50px",
62+
color: getListItemColor(theme, props),
63+
borderRadius: getListItemBorderRadius(props),
64+
boxShadow: getListItemBoxShadow(props),
65+
backgroundColor: props.selected ? "#0c0c0c" : "transparent",
66+
"&.MuiListItem-root:hover": getMuiListItemHoverFocusProperties(props),
67+
"&.Mui-focusVisible": getMuiListItemHoverFocusProperties(props),
68+
}),
69+
listIcon: {
70+
color: "inherit",
71+
},
72+
}));
3173

3274
function MenuItem(props: MenuItemProps): ReactElement {
3375
const { url } = useRouteMatch();
34-
const { pathname } = useLocation();
35-
const classes = useStyles();
76+
const classes = useStyles(props);
3677
const navigateTo = `${url}${props.path}`;
37-
38-
const isCurrentLocation = (): boolean => {
39-
return navigateTo === pathname || (!!props.isFallback && url === pathname);
40-
};
78+
const buttonProps = props.isDisabled ? {} : { disableRipple: true };
4179

4280
return (
4381
<Tooltip
@@ -49,14 +87,14 @@ function MenuItem(props: MenuItemProps): ReactElement {
4987
}
5088
>
5189
<ListItem
52-
className={props.isDisabled ? classes.disabled : ""}
90+
className={classes.listItem}
5391
button={!props.isDisabled as any}
5492
component={props.isDisabled ? "div" : NavLink}
5593
to={navigateTo}
56-
selected={isCurrentLocation()}
94+
{...buttonProps}
5795
>
58-
<ListItemIcon>
59-
<props.icon color={props.isDisabled ? "disabled" : "inherit"} />
96+
<ListItemIcon className={classes.listIcon}>
97+
<props.icon />
6098
</ListItemIcon>
6199
<ListItemText primary={props.text} />
62100
</ListItem>

src/common/utils/svgIcons.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import openDexLogo from "../../assets/OpenDEXLogo.svg";
2+
3+
export { openDexLogo };

src/dashboard/Dashboard.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ import SetupWarning from "./SetupWarning";
2929
import { isOpendexdReady } from "../common/utils/serviceUtil";
3030
import { ServiceStore, SERVICE_STORE } from "../stores/serviceStore";
3131
import { inject, observer } from "mobx-react";
32-
import { Menu, menuItems } from "../common/components/navigation/Menu";
32+
import {
33+
Menu,
34+
menuItems,
35+
drawerWidth,
36+
} from "../common/components/navigation/Menu";
3337

3438
type DashboardProps = {
3539
serviceStore?: ServiceStore;
3640
};
3741

38-
export const drawerWidth = 200;
39-
4042
const useStyles = makeStyles((theme: Theme) =>
4143
createStyles({
4244
content: {

0 commit comments

Comments
 (0)