Add left nav (#4705)

* Automated translation update ( bf08a04cda )

* Add Layout component wip

* finish layout implementation

* convert header to left nav

* better nav item spacing

* return source files to original glory

* lint fix

* Remove height limit on login screen

* fix top spacing on 3-panel layout

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Brent Clark <brent@backboneiq.com>
This commit is contained in:
Brent Clark 2023-03-15 22:45:55 -05:00 committed by GitHub
parent cab5f29b57
commit cef99cba71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 280 additions and 201 deletions

View file

@ -0,0 +1,31 @@
.layout {
height: 100%;
max-height: 100%;
width: 100%;
max-width: 100%;
display: flex;
flex-flow: row nowrap;
overflow: hidden;
}
.layout--no-height-limit {
height: initial;
max-height: initial;
}
.bottom-bar__container {
background: #555;
height: 50px;
width: 100%;
}
.page__body {
flex: 1;
max-height: calc(100% - 50px);
}
.page {
display: flex;
flex-flow: column;
width: 100%;
}

View file

@ -0,0 +1,39 @@
import LeftNav from './LeftNav';
import './Layout.css'
function Layout(props:LayoutProps) {
const { children, className, showNav = true, noHeightLimit = false } = props;
const containerClasses = ['layout']
if (noHeightLimit === true) {
containerClasses.push('layout--no-height-limit')
}
return (
<div className={containerClasses.join(" ")}>
{showNav && <LeftNav />}
<section className="page">
<div className={`page__body ${className}`}>
{children}
</div>
{showNav && <BottomBar />}
</section>
</div>
)
}
function BottomBar(props) {
return (
<div className="bottom-bar__container">
</div>
)
}
interface LayoutProps {
showNav?: boolean;
children: any;
className?: string;
noHeightLimit?: boolean
}
export default Layout;

View file

@ -0,0 +1,128 @@
.LeftNav__container {
background: #7033DB;
width: 100px;
min-width: 100px;
height: 100%;
}
.LeftNav__logo {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 0;
}
.LeftNav__logo a {
line-height: 1;
}
.LeftNav__logo img {
height: 32px;
}
.LeftNav-content {
color: white;
}
.LeftNav-serverDetails {
font-size: 12px;
}
.LeftNav-server__indicator {
display: inline-block;
height: 12px;
width: 12px;
background: red;
border: 1px solid;
border-radius: 50%;
margin-left: 10px;
}
.LeftNav-nav {
}
.LeftNav-nav__links {
display: flex;
flex-flow: column;
align-items: center;
gap: 16px;
}
.LeftNav-nav__link {
position: relative;
height: 100%;
}
.LeftNav-nav__link:hover {
background: rgba(0, 0, 0, .125);
}
.LeftNav-nav__link:hover .LeftNav-nav__link-menu {
display: block;
}
.LeftNav-nav__link-btn {
display: flex;
height: 100%;
width: 100%;
align-items: center;
padding: 5px 20px;
font-weight: bold;
}
.LeftNav-nav__link-btn__icon {
margin-left: 5px;
}
.LeftNav-nav__link-menu {
display: none;
position: absolute;
bottom: 0;
transform: translateY(100%);
min-width: 150px;
background: #3f51b5;
box-shadow: 1px 1px 2px 0px black;
z-index: 1;
}
.LeftNav-nav__link-menu__item {
padding: 0 !important;
}
.LeftNav-nav__link-menu__btn {
padding: 6px 16px;
width: 100%;
color: white;
display: flex;
justify-content: space-between;
}
.LeftNav-nav__actions {
display: flex;
justify-content: center;
}
.LeftNav-nav__action {
}
.LeftNav-nav__action button {
color: white;
}
.temp-subnav__rooms {
display: flex;
align-items: center;
font-size: 10px;
padding: 5px;
}
.temp-chip {
margin-left: 5px;
text-decoration: none;
}
.temp-chip > div {
cursor: inherit;
}

View file

@ -0,0 +1,195 @@
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { NavLink, useNavigate, generatePath } from 'react-router-dom';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import CloseIcon from '@mui/icons-material/Close';
import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline';
import MenuRoundedIcon from '@mui/icons-material/MenuRounded';
import * as _ from 'lodash';
import { AuthenticationService, RoomsService } from 'api';
import { CardImportDialog } from 'dialogs';
import { Images } from 'images';
import { RoomsSelectors, ServerSelectors } from 'store';
import { Room, RouteEnum, User } from 'types';
import './LeftNav.css';
const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => {
const navigate = useNavigate();
const [state, setState] = useState<LeftNavState>({
anchorEl: null,
showCardImportDialog: false,
options: [],
});
useEffect(() => {
let options: string[] = [
'Account',
'Replays',
];
if (user && AuthenticationService.isModerator(user)) {
options = [
...options,
'Administration',
'Logs'
];
}
setState(s => ({ ...s, options }));
}, [user]);
const handleMenuOpen = (event) => {
setState(s => ({ ...s, anchorEl: event.target }));
}
const handleMenuItemClick = (option: string) => {
const route = RouteEnum[option.toUpperCase()];
navigate(generatePath(route));
}
const handleMenuClose = () => {
setState(s => ({ ...s, anchorEl: null }));
}
const leaveRoom = (event, roomId) => {
event.preventDefault();
RoomsService.leaveRoom(roomId);
};
const openImportCardWizard = () => {
setState(s => ({ ...s, showCardImportDialog: true }));
handleMenuClose();
}
const closeImportCardWizard = () => {
setState(s => ({ ...s, showCardImportDialog: false }));
}
return (
<div className="LeftNav__container">
<div>
<div className="LeftNav__logo">
<NavLink to={RouteEnum.SERVER}>
<img src={Images.Logo} alt="logo" />
</NavLink>
{ AuthenticationService.isConnected(serverState) && (
<span className="LeftNav-server__indicator"></span>
) }
</div>
{ AuthenticationService.isConnected(serverState) && (
<div className="LeftNav-content">
<nav className="LeftNav-nav">
<nav className="LeftNav-nav__links">
<div className="LeftNav-nav__link">
<NavLink
className="LeftNav-nav__link-btn"
to={
joinedRooms.length
? generatePath(RouteEnum.ROOM, { roomId: joinedRooms[0].roomId.toString() })
: RouteEnum.SERVER
}
>
Rooms
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
</NavLink>
<div className="LeftNav-nav__link-menu">
{joinedRooms.map(({ name, roomId }) => (
<div className="LeftNav-nav__link-menu__item" key={roomId}>
<NavLink className="LeftNav-nav__link-menu__btn" to={ generatePath(RouteEnum.ROOM, { roomId: roomId.toString() }) }>
{name}
<IconButton size="small" edge="end" onClick={event => leaveRoom(event, roomId)}>
<CloseIcon style={{ fontSize: 10, color: 'white' }} />
</IconButton>
</NavLink>
</div>
))}
</div>
</div>
<div className="LeftNav-nav__link">
<NavLink className="LeftNav-nav__link-btn" to={ RouteEnum.GAME }>
Games
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
</NavLink>
</div>
<div className="LeftNav-nav__link">
<NavLink className="LeftNav-nav__link-btn" to={ RouteEnum.DECKS }>
Decks
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
</NavLink>
</div>
</nav>
<div className="LeftNav-nav__actions">
<div className="LeftNav-nav__action">
<IconButton size="large">
<MailOutlineRoundedIcon style={{ color: 'inherit' }} />
</IconButton>
</div>
<div className="LeftNav-nav__action">
<IconButton onClick={handleMenuOpen} size="large">
<MenuRoundedIcon style={{ color: 'inherit' }} />
</IconButton>
<Menu
anchorEl={state.anchorEl}
keepMounted
open={!!state.anchorEl}
onClose={() => handleMenuClose()}
PaperProps={{
style: {
marginTop: '32px',
width: '20ch',
},
}}
>
{state.options.map((option) => (
<MenuItem key={option} onClick={(event) => handleMenuItemClick(option)}>
{option}
</MenuItem>
))}
<MenuItem key='Import Cards' onClick={(event) => openImportCardWizard()}>
Import Cards
</MenuItem>
</Menu>
</div>
</div>
</nav>
</div>
) }
</div>
<CardImportDialog
isOpen={state.showCardImportDialog}
handleClose={closeImportCardWizard}
></CardImportDialog>
</div>
);
}
interface LeftNavProps {
serverState: number;
server: string;
user: User;
joinedRooms: Room[];
showNav?: boolean;
}
interface LeftNavState {
anchorEl: Element;
showCardImportDialog: boolean;
options: string[];
}
const mapStateToProps = state => ({
serverState: ServerSelectors.getState(state),
server: ServerSelectors.getName(state),
user: ServerSelectors.getUser(state),
joinedRooms: RoomsSelectors.getJoinedRooms(state),
});
export default connect(mapStateToProps)(LeftNav);

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB