Merge pull request #703 from mirumee/ref/sidebar-mobile
Improve sidebar responsiveness
This commit is contained in:
commit
acfd04e81a
18 changed files with 623 additions and 194 deletions
|
@ -1373,14 +1373,6 @@
|
|||
"src_dot_components_dot_AddressEdit_dot_944851093": {
|
||||
"string": "Country area"
|
||||
},
|
||||
"src_dot_components_dot_AppLayout_dot_21332146": {
|
||||
"context": "button",
|
||||
"string": "Log out"
|
||||
},
|
||||
"src_dot_components_dot_AppLayout_dot_248888005": {
|
||||
"context": "button",
|
||||
"string": "Account Settings"
|
||||
},
|
||||
"src_dot_components_dot_AppStatus_dot_1624959454": {
|
||||
"string": "If you want to disable this App please uncheck the box below."
|
||||
},
|
||||
|
@ -1864,6 +1856,14 @@
|
|||
"src_dot_components_dot_Timeline_dot_3028189627": {
|
||||
"string": "Leave your note here..."
|
||||
},
|
||||
"src_dot_components_dot_UserChip_dot_21332146": {
|
||||
"context": "button",
|
||||
"string": "Log out"
|
||||
},
|
||||
"src_dot_components_dot_UserChip_dot_248888005": {
|
||||
"context": "button",
|
||||
"string": "Account Settings"
|
||||
},
|
||||
"src_dot_components_dot_VisibilityCard_dot_1311467573": {
|
||||
"string": "Show in product listings"
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@ const useStyles = makeStyles(
|
|||
marginTop: theme.spacing(0.5),
|
||||
transition: theme.transitions.duration.standard + "ms",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "none"
|
||||
margin: theme.spacing(4, 0, 3, 0)
|
||||
}
|
||||
},
|
||||
skeleton: {
|
||||
|
@ -45,10 +45,7 @@ const useStyles = makeStyles(
|
|||
color: "inherit",
|
||||
flex: 1,
|
||||
marginLeft: theme.spacing(),
|
||||
textTransform: "uppercase",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "none"
|
||||
}
|
||||
textTransform: "uppercase"
|
||||
}
|
||||
}),
|
||||
{ name: "AppHeader" }
|
||||
|
|
|
@ -1,23 +1,14 @@
|
|||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
import Grow from "@material-ui/core/Grow";
|
||||
import LinearProgress from "@material-ui/core/LinearProgress";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Menu from "@material-ui/core/MenuList";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Popper from "@material-ui/core/Popper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import useMediaQuery from "@material-ui/core/useMediaQuery";
|
||||
import { createConfigurationMenu } from "@saleor/configuration";
|
||||
import useAppState from "@saleor/hooks/useAppState";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
import useRouter from "use-react-router";
|
||||
|
||||
import Container from "../Container";
|
||||
|
@ -25,6 +16,8 @@ import ErrorPage from "../ErrorPage";
|
|||
import Navigator from "../Navigator";
|
||||
import NavigatorButton from "../NavigatorButton/NavigatorButton";
|
||||
import SideBar from "../SideBar";
|
||||
import SideBarDrawer from "../SideBarDrawer/SideBarDrawer";
|
||||
import UserChip from "../UserChip";
|
||||
import AppActionContext from "./AppActionContext";
|
||||
import AppHeaderContext from "./AppHeaderContext";
|
||||
import { appLoaderHeight } from "./consts";
|
||||
|
@ -52,80 +45,37 @@ const useStyles = makeStyles(
|
|||
height: appLoaderHeight,
|
||||
marginBottom: theme.spacing(4)
|
||||
},
|
||||
arrow: {
|
||||
marginLeft: theme.spacing(2),
|
||||
transition: theme.transitions.duration.standard + "ms"
|
||||
},
|
||||
avatar: {
|
||||
"&&": {
|
||||
height: 32,
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
|
||||
content: {
|
||||
flex: 1
|
||||
},
|
||||
darkThemeSwitch: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
marginRight: -theme.spacing(1.5)
|
||||
marginRight: theme.spacing(1)
|
||||
},
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
header: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
height: 88,
|
||||
marginBottom: 0
|
||||
height: "auto"
|
||||
},
|
||||
display: "flex",
|
||||
height: 40,
|
||||
marginBottom: theme.spacing(3)
|
||||
},
|
||||
menu: {
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
padding: "25px 20px"
|
||||
},
|
||||
menuSmall: {
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
overflow: "hidden",
|
||||
padding: 25
|
||||
},
|
||||
popover: {
|
||||
zIndex: 1
|
||||
},
|
||||
|
||||
root: {
|
||||
display: "flex",
|
||||
width: `100%`
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
},
|
||||
spacer: {
|
||||
flex: 1
|
||||
},
|
||||
userBar: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
alignItems: "flex-end",
|
||||
flexDirection: "column-reverse",
|
||||
overflow: "hidden"
|
||||
},
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
},
|
||||
userChip: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: 24,
|
||||
color: theme.palette.text.primary,
|
||||
height: 40,
|
||||
padding: theme.spacing(0.5)
|
||||
},
|
||||
userMenuContainer: {
|
||||
position: "relative"
|
||||
},
|
||||
userMenuItem: {
|
||||
textAlign: "right"
|
||||
},
|
||||
|
||||
view: {
|
||||
flex: 1,
|
||||
flexGrow: 1,
|
||||
|
@ -151,16 +101,15 @@ interface AppLayoutProps {
|
|||
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
const classes = useStyles({});
|
||||
const { isDark, toggleTheme } = useTheme();
|
||||
const [isMenuOpened, setMenuState] = React.useState(false);
|
||||
const appActionAnchor = React.useRef<HTMLDivElement>();
|
||||
const appHeaderAnchor = React.useRef<HTMLDivElement>();
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const { logout, user } = useUser();
|
||||
const navigate = useNavigator();
|
||||
const intl = useIntl();
|
||||
const [appState, dispatchAppState] = useAppState();
|
||||
const { location } = useRouter();
|
||||
const [isNavigatorVisible, setNavigatorVisibility] = React.useState(false);
|
||||
const isMdUp = useMediaQuery((theme: Theme) => theme.breakpoints.up("md"));
|
||||
|
||||
const menuStructure = createMenuStructure(intl);
|
||||
const configurationMenu = createConfigurationMenu(intl);
|
||||
|
@ -175,16 +124,6 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
)
|
||||
);
|
||||
|
||||
const handleLogout = () => {
|
||||
setMenuState(false);
|
||||
logout();
|
||||
};
|
||||
|
||||
const handleViewerProfile = () => {
|
||||
setMenuState(false);
|
||||
navigate(staffMemberDetailsUrl(user.id));
|
||||
};
|
||||
|
||||
const handleErrorBack = () => {
|
||||
navigate("/");
|
||||
dispatchAppState({
|
||||
|
@ -204,13 +143,15 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
<AppHeaderContext.Provider value={appHeaderAnchor}>
|
||||
<AppActionContext.Provider value={appActionAnchor}>
|
||||
<div className={classes.root}>
|
||||
<SideBar
|
||||
menuItems={menuStructure}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={renderConfigure}
|
||||
onMenuItemClick={navigate}
|
||||
/>
|
||||
{isMdUp && (
|
||||
<SideBar
|
||||
menuItems={menuStructure}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={renderConfigure}
|
||||
onMenuItemClick={navigate}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.content}>
|
||||
{appState.loading ? (
|
||||
<LinearProgress className={classes.appLoader} color="primary" />
|
||||
|
@ -221,7 +162,16 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
<div>
|
||||
<Container>
|
||||
<div className={classes.header}>
|
||||
<div ref={appHeaderAnchor} />
|
||||
{isMdUp && <div ref={appHeaderAnchor} />}
|
||||
{!isMdUp && (
|
||||
<SideBarDrawer
|
||||
menuItems={menuStructure}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={renderConfigure}
|
||||
onMenuItemClick={navigate}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
<div className={classes.userBar}>
|
||||
<ThemeSwitch
|
||||
|
@ -235,82 +185,16 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
.includes("mac")}
|
||||
onClick={() => setNavigatorVisibility(true)}
|
||||
/>
|
||||
<div className={classes.userMenuContainer} ref={anchor}>
|
||||
<Chip
|
||||
avatar={
|
||||
user.avatar && (
|
||||
<Avatar alt="user" src={user.avatar.url} />
|
||||
)
|
||||
}
|
||||
classes={{
|
||||
avatar: classes.avatar
|
||||
}}
|
||||
className={classes.userChip}
|
||||
label={
|
||||
<>
|
||||
{user.email}
|
||||
<ArrowDropdown
|
||||
className={classNames(classes.arrow, {
|
||||
[classes.rotate]: isMenuOpened
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onClick={() => setMenuState(!isMenuOpened)}
|
||||
data-test="userMenu"
|
||||
/>
|
||||
<Popper
|
||||
className={classes.popover}
|
||||
open={isMenuOpened}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
placement="bottom-end"
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === "bottom"
|
||||
? "right top"
|
||||
: "right bottom"
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setMenuState(false)}
|
||||
mouseEvent="onClick"
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem
|
||||
className={classes.userMenuItem}
|
||||
onClick={handleViewerProfile}
|
||||
data-test="accountSettingsButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Account Settings"
|
||||
description="button"
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classes.userMenuItem}
|
||||
onClick={handleLogout}
|
||||
data-test="logOutButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Log out"
|
||||
description="button"
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</div>
|
||||
<UserChip
|
||||
onLogout={logout}
|
||||
onProfileClick={() =>
|
||||
navigate(staffMemberDetailsUrl(user.id))
|
||||
}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isMdUp && <div ref={appHeaderAnchor} />}
|
||||
</Container>
|
||||
</div>
|
||||
<main className={classes.view}>
|
||||
|
|
|
@ -49,6 +49,9 @@ const useStyles = makeStyles(
|
|||
overflow: "hidden"
|
||||
},
|
||||
root: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
height: "auto"
|
||||
},
|
||||
height: 500,
|
||||
maxWidth: 600,
|
||||
outline: 0,
|
||||
|
|
|
@ -66,6 +66,10 @@ const useStyles = makeStyles(
|
|||
"&:not(:hover)": {
|
||||
backgroundColor: theme.palette.background.paper
|
||||
},
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
border: "none",
|
||||
borderRadius: 16
|
||||
},
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
height: 40,
|
||||
marginRight: theme.spacing(2),
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
import { ButtonProps } from "@material-ui/core/Button";
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import ArrowIcon from "@material-ui/icons/ArrowBack";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import SquareButton from "../SquareButton";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
arrow: {
|
||||
transition: theme.transitions.duration.shortest + "ms"
|
||||
},
|
||||
root: {
|
||||
"&:hover, &:focus": {
|
||||
background: "#daedeb"
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: 16,
|
||||
color: theme.palette.primary.main,
|
||||
height: 48,
|
||||
transition: theme.transitions.duration.shortest + "ms",
|
||||
width: 48
|
||||
},
|
||||
shrunk: {
|
||||
transform: "scaleX(-1)"
|
||||
}
|
||||
|
@ -34,21 +24,17 @@ export interface ExpandButtonProps extends ButtonProps {
|
|||
isShrunk: boolean;
|
||||
}
|
||||
|
||||
const ExpandButton: React.FC<ExpandButtonProps> = ({
|
||||
className,
|
||||
isShrunk,
|
||||
...rest
|
||||
}) => {
|
||||
const ExpandButton: React.FC<ExpandButtonProps> = ({ isShrunk, ...rest }) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<ButtonBase className={classNames(classes.root, className)} {...rest}>
|
||||
<SquareButton {...rest}>
|
||||
<ArrowIcon
|
||||
className={classNames(classes.arrow, {
|
||||
[classes.shrunk]: isShrunk
|
||||
})}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</SquareButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { sectionNames } from "@saleor/intl";
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { useIntl } from "react-intl";
|
||||
import { IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import { IMenuItem } from "../AppLayout/menuStructure";
|
||||
import ExpandButton from "./ExpandButton";
|
||||
|
@ -55,6 +55,14 @@ export interface IActiveSubMenu {
|
|||
label: string | null;
|
||||
}
|
||||
|
||||
export const getConfigureMenuItem = (intl: IntlShape): IMenuItem => ({
|
||||
ariaLabel: "configure",
|
||||
icon: configurationIcon,
|
||||
label: intl.formatMessage(sectionNames.configuration),
|
||||
testingContextId: "configure",
|
||||
url: configurationMenuUrl
|
||||
});
|
||||
|
||||
const SideBar: React.FC<SideBarProps> = ({
|
||||
location,
|
||||
menuItems,
|
||||
|
@ -65,6 +73,7 @@ const SideBar: React.FC<SideBarProps> = ({
|
|||
const classes = useStyles({});
|
||||
const [isShrunk, setShrink] = useLocalStorage("isMenuSmall", false);
|
||||
const intl = useIntl();
|
||||
const configureMenuItem = getConfigureMenuItem(intl);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -107,13 +116,7 @@ const SideBar: React.FC<SideBarProps> = ({
|
|||
)
|
||||
}
|
||||
isMenuShrunk={isShrunk}
|
||||
menuItem={{
|
||||
ariaLabel: "configure",
|
||||
icon: configurationIcon,
|
||||
label: intl.formatMessage(sectionNames.configuration),
|
||||
testingContextId: "configure",
|
||||
url: configurationMenuUrl
|
||||
}}
|
||||
menuItem={configureMenuItem}
|
||||
onClick={onMenuItemClick}
|
||||
/>
|
||||
)}
|
||||
|
|
32
src/components/SideBarDrawer/MenuItemBtn.tsx
Normal file
32
src/components/SideBarDrawer/MenuItemBtn.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
|
||||
import { IMenuItem } from "../AppLayout/menuStructure";
|
||||
import useStyles from "./styles";
|
||||
|
||||
export interface MenuItemBtnProps {
|
||||
menuItem: IMenuItem;
|
||||
onClick: UseNavigatorResult;
|
||||
}
|
||||
const MenuItemBtn: React.FC<MenuItemBtnProps> = ({ menuItem, onClick }) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classes.menuItemBtn}
|
||||
data-test="menu-item-label"
|
||||
data-test-id={menuItem.testingContextId}
|
||||
onClick={() => onClick(menuItem.url)}
|
||||
>
|
||||
{menuItem.icon && <SVG className={classes.icon} src={menuItem.icon} />}
|
||||
<Typography aria-label={menuItem.ariaLabel} className={classes.label}>
|
||||
{menuItem.label}
|
||||
</Typography>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
MenuItemBtn.displayName = "MenuItemBtn";
|
||||
export default MenuItemBtn;
|
37
src/components/SideBarDrawer/SideBarDrawer.stories.tsx
Normal file
37
src/components/SideBarDrawer/SideBarDrawer.stories.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { staffMember } from "@saleor/staff/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { config } from "@test/intl";
|
||||
import React from "react";
|
||||
import { createIntl } from "react-intl";
|
||||
|
||||
import createMenuStructure from "../AppLayout/menuStructure";
|
||||
import SideBarDrawer from "./SideBarDrawer";
|
||||
|
||||
const intl = createIntl(config);
|
||||
const user = {
|
||||
__typename: staffMember.__typename,
|
||||
avatar: {
|
||||
__typename: staffMember.avatar.__typename,
|
||||
url: staffMember.avatar.url
|
||||
},
|
||||
email: staffMember.email,
|
||||
firstName: "Adam Evan",
|
||||
id: staffMember.id,
|
||||
isStaff: true,
|
||||
lastName: "Newton",
|
||||
note: null,
|
||||
userPermissions: staffMember.userPermissions
|
||||
};
|
||||
|
||||
storiesOf("Generics / Mobile Side Menu", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<SideBarDrawer
|
||||
location="/"
|
||||
menuItems={createMenuStructure(intl)}
|
||||
onMenuItemClick={() => undefined}
|
||||
renderConfigure={true}
|
||||
user={user}
|
||||
/>
|
||||
));
|
132
src/components/SideBarDrawer/SideBarDrawer.tsx
Normal file
132
src/components/SideBarDrawer/SideBarDrawer.tsx
Normal file
|
@ -0,0 +1,132 @@
|
|||
import logoLight from "@assets/images/logo-sidebar-light.svg";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import ArrowLeftIcon from "@material-ui/icons/ArrowLeft";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { IMenuItem } from "../AppLayout/menuStructure";
|
||||
import { getConfigureMenuItem, SideBarProps } from "../SideBar/SideBar";
|
||||
import SquareButton from "../SquareButton";
|
||||
import MenuItemBtn from "./MenuItemBtn";
|
||||
import useStyles from "./styles";
|
||||
|
||||
export type SideBarDrawerProps = SideBarProps;
|
||||
|
||||
const SideBarDrawer: React.FC<SideBarDrawerProps> = ({
|
||||
menuItems,
|
||||
onMenuItemClick,
|
||||
renderConfigure,
|
||||
user
|
||||
}) => {
|
||||
const [isOpened, setOpened] = React.useState(false);
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const [activeMenu, setActiveMenu] = React.useState<IMenuItem>(null);
|
||||
const [showSubmenu, setShowSubmenu] = React.useState(false);
|
||||
const container = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const configureMenuItem = getConfigureMenuItem(intl);
|
||||
|
||||
const handleMenuItemClick = (url: string) => {
|
||||
setOpened(false);
|
||||
setShowSubmenu(false);
|
||||
onMenuItemClick(url);
|
||||
};
|
||||
|
||||
const handleMenuItemWithChildrenClick = (menuItem: IMenuItem) => {
|
||||
setActiveMenu(menuItem);
|
||||
setShowSubmenu(true);
|
||||
container.current.scrollTo({
|
||||
top: 0
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SquareButton onClick={() => setOpened(true)}>
|
||||
<MenuIcon />
|
||||
</SquareButton>
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.root
|
||||
}}
|
||||
open={isOpened}
|
||||
onClose={() => setOpened(false)}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.container, {
|
||||
[classes.containerSubMenu]: showSubmenu
|
||||
})}
|
||||
ref={container}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.innerContainer, {
|
||||
[classes.secondaryContentActive]: showSubmenu
|
||||
})}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
<SVG className={classes.logo} src={logoLight} />
|
||||
{menuItems.map(menuItem => {
|
||||
if (
|
||||
menuItem.permission &&
|
||||
!user.userPermissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemBtn
|
||||
menuItem={menuItem}
|
||||
onClick={
|
||||
menuItem.children
|
||||
? () => handleMenuItemWithChildrenClick(menuItem)
|
||||
: handleMenuItemClick
|
||||
}
|
||||
key={menuItem.ariaLabel}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{renderConfigure && (
|
||||
<MenuItemBtn
|
||||
menuItem={configureMenuItem}
|
||||
onClick={handleMenuItemClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{activeMenu && (
|
||||
<div className={classes.content}>
|
||||
<div className={classes.subMenuTopBar}>
|
||||
<div className={classes.activeMenuLabel}>
|
||||
<SVG className={classes.icon} src={activeMenu.icon} />
|
||||
<Typography className={classes.label}>
|
||||
{activeMenu.label}
|
||||
</Typography>
|
||||
</div>
|
||||
<SquareButton onClick={() => setShowSubmenu(false)}>
|
||||
<ArrowLeftIcon />
|
||||
</SquareButton>
|
||||
</div>
|
||||
{activeMenu.children.map(subMenuItem => (
|
||||
<MenuItemBtn
|
||||
menuItem={subMenuItem}
|
||||
onClick={handleMenuItemClick}
|
||||
key={subMenuItem.ariaLabel}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
SideBarDrawer.displayName = "SideBarDrawer";
|
||||
export default SideBarDrawer;
|
0
src/components/SideBarDrawer/index.ts
Normal file
0
src/components/SideBarDrawer/index.ts
Normal file
68
src/components/SideBarDrawer/styles.ts
Normal file
68
src/components/SideBarDrawer/styles.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
activeMenuLabel: {
|
||||
display: "flex"
|
||||
},
|
||||
container: {
|
||||
overflowX: "hidden",
|
||||
width: "100%"
|
||||
},
|
||||
containerSubMenu: {
|
||||
"&$container": {
|
||||
overflow: "hidden"
|
||||
}
|
||||
},
|
||||
content: {
|
||||
width: "50%"
|
||||
},
|
||||
icon: {
|
||||
marginRight: theme.spacing(2)
|
||||
},
|
||||
innerContainer: {
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
right: 0,
|
||||
transition: theme.transitions.duration.short + "ms",
|
||||
width: "200%"
|
||||
},
|
||||
label: {
|
||||
fontWeight: "bold"
|
||||
},
|
||||
logo: {
|
||||
display: "block",
|
||||
marginBottom: theme.spacing(4)
|
||||
},
|
||||
menuItemBtn: {
|
||||
alignItems: "center",
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: theme.palette.text.secondary,
|
||||
display: "flex",
|
||||
marginBottom: theme.spacing(3),
|
||||
padding: 0
|
||||
},
|
||||
root: {
|
||||
background: theme.palette.background.default,
|
||||
borderBottomRightRadius: 32,
|
||||
borderTopRightRadius: 32,
|
||||
padding: theme.spacing(3),
|
||||
width: 260
|
||||
},
|
||||
secondaryContentActive: {
|
||||
right: "100%"
|
||||
},
|
||||
subMenuTopBar: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(3)
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "SideBarDrawer"
|
||||
}
|
||||
);
|
||||
|
||||
export default useStyles;
|
14
src/components/SquareButton/SquareButton.stories.tsx
Normal file
14
src/components/SquareButton/SquareButton.stories.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import SquareButton from "./SquareButton";
|
||||
|
||||
storiesOf("Generics / Square Button", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<SquareButton>
|
||||
<CloseIcon />
|
||||
</SquareButton>
|
||||
));
|
34
src/components/SquareButton/SquareButton.tsx
Normal file
34
src/components/SquareButton/SquareButton.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import ButtonBase, { ButtonBaseProps } from "@material-ui/core/ButtonBase";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
root: {
|
||||
"&:hover, &:focus": {
|
||||
background: "#daedeb"
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: 16,
|
||||
color: theme.palette.primary.main,
|
||||
height: 48,
|
||||
transition: theme.transitions.duration.shortest + "ms",
|
||||
width: 48
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "ExpandButton"
|
||||
}
|
||||
);
|
||||
|
||||
const SquareButton: React.FC<ButtonBaseProps> = ({ className, ...rest }) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<ButtonBase className={classNames(classes.root, className)} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
SquareButton.displayName = "SquareButton";
|
||||
export default SquareButton;
|
2
src/components/SquareButton/index.ts
Normal file
2
src/components/SquareButton/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./SquareButton";
|
||||
export { default } from "./SquareButton";
|
183
src/components/UserChip/UserChip.tsx
Normal file
183
src/components/UserChip/UserChip.tsx
Normal file
|
@ -0,0 +1,183 @@
|
|||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
import Grow from "@material-ui/core/Grow";
|
||||
import Hidden from "@material-ui/core/Hidden";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Menu from "@material-ui/core/MenuList";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Popper from "@material-ui/core/Popper";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import { User } from "@saleor/fragments/types/User";
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import { getUserInitials, getUserName } from "@saleor/misc";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
arrow: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
marginLeft: 0
|
||||
},
|
||||
marginLeft: theme.spacing(2),
|
||||
transition: theme.transitions.duration.standard + "ms"
|
||||
},
|
||||
avatar: {
|
||||
"&&": {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
height: 40,
|
||||
width: 40
|
||||
},
|
||||
height: 32,
|
||||
width: 32
|
||||
}
|
||||
},
|
||||
avatarInitials: {
|
||||
color: theme.palette.primary.contrastText
|
||||
},
|
||||
avatarPlaceholder: {
|
||||
alignItems: "center",
|
||||
background: theme.palette.primary.main,
|
||||
borderRadius: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center"
|
||||
},
|
||||
popover: {
|
||||
marginTop: theme.spacing(2),
|
||||
zIndex: 1
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
},
|
||||
userChip: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
height: 48
|
||||
},
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: 24,
|
||||
color: theme.palette.text.primary,
|
||||
height: 40,
|
||||
padding: theme.spacing(0.5)
|
||||
},
|
||||
userMenuContainer: {
|
||||
position: "relative"
|
||||
},
|
||||
userMenuItem: {
|
||||
textAlign: "right"
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "UserChip"
|
||||
}
|
||||
);
|
||||
|
||||
export interface UserChipProps {
|
||||
user: User;
|
||||
onLogout: () => void;
|
||||
onProfileClick: () => void;
|
||||
}
|
||||
|
||||
const UserChip: React.FC<UserChipProps> = ({
|
||||
user,
|
||||
onLogout,
|
||||
onProfileClick
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const [isMenuOpened, setMenuState] = React.useState(false);
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
|
||||
const handleLogout = () => {
|
||||
setMenuState(false);
|
||||
onLogout();
|
||||
};
|
||||
|
||||
const handleViewerProfile = () => {
|
||||
setMenuState(false);
|
||||
onProfileClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.userMenuContainer} ref={anchor}>
|
||||
<Chip
|
||||
avatar={
|
||||
user.avatar ? (
|
||||
<Avatar alt="user" src={user.avatar.url} />
|
||||
) : (
|
||||
<div className={classes.avatarPlaceholder}>
|
||||
<div className={classes.avatarInitials}>
|
||||
{getUserInitials(user)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
classes={{
|
||||
avatar: classes.avatar
|
||||
}}
|
||||
className={classes.userChip}
|
||||
label={
|
||||
<>
|
||||
<Hidden smDown>{getUserName(user, true)}</Hidden>
|
||||
<ArrowDropdown
|
||||
className={classNames(classes.arrow, {
|
||||
[classes.rotate]: isMenuOpened
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onClick={() => setMenuState(!isMenuOpened)}
|
||||
data-test="userMenu"
|
||||
/>
|
||||
<Popper
|
||||
className={classes.popover}
|
||||
open={isMenuOpened}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
placement="bottom-end"
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === "bottom" ? "right top" : "right bottom"
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setMenuState(false)}
|
||||
mouseEvent="onClick"
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem
|
||||
className={classes.userMenuItem}
|
||||
onClick={handleViewerProfile}
|
||||
data-test="accountSettingsButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Account Settings"
|
||||
description="button"
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={classes.userMenuItem}
|
||||
onClick={handleLogout}
|
||||
data-test="logOutButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Log out"
|
||||
description="button"
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
UserChip.displayName = "UserChip";
|
||||
export default UserChip;
|
2
src/components/UserChip/index.ts
Normal file
2
src/components/UserChip/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./UserChip";
|
||||
export { default } from "./UserChip";
|
|
@ -4753,6 +4753,30 @@ exports[`Storyshots Generics / Metadata loading 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Generics / Mobile Side Menu default 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root-id ExpandButton-root-id"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root-id"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Generics / Money formatting default 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
|
@ -9526,6 +9550,30 @@ exports[`Storyshots Generics / Skeleton default 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Generics / Square Button default 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root-id ExpandButton-root-id"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root-id"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Generics / StatusLabel when error 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
|
|
Loading…
Reference in a new issue