diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 88ea9ae01..105c9bf12 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -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" }, diff --git a/src/components/AppHeader/AppHeader.tsx b/src/components/AppHeader/AppHeader.tsx index f4bdc4ab4..7f85264dd 100644 --- a/src/components/AppHeader/AppHeader.tsx +++ b/src/components/AppHeader/AppHeader.tsx @@ -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" } diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 2f6041ee2..891ef3f09 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -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 = ({ children }) => { const classes = useStyles({}); const { isDark, toggleTheme } = useTheme(); - const [isMenuOpened, setMenuState] = React.useState(false); const appActionAnchor = React.useRef(); const appHeaderAnchor = React.useRef(); - const anchor = React.useRef(); 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 = ({ 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 = ({ children }) => {
- + {isMdUp && ( + + )}
{appState.loading ? ( @@ -221,7 +162,16 @@ const AppLayout: React.FC = ({ children }) => {
-
+ {isMdUp &&
} + {!isMdUp && ( + + )}
= ({ children }) => { .includes("mac")} onClick={() => setNavigatorVisibility(true)} /> -
- - ) - } - classes={{ - avatar: classes.avatar - }} - className={classes.userChip} - label={ - <> - {user.email} - - - } - onClick={() => setMenuState(!isMenuOpened)} - data-test="userMenu" - /> - - {({ TransitionProps, placement }) => ( - - - setMenuState(false)} - mouseEvent="onClick" - > - - - - - - - - - - - - )} - -
+ + navigate(staffMemberDetailsUrl(user.id)) + } + user={user} + />
+ {!isMdUp &&
}
diff --git a/src/components/Navigator/Navigator.tsx b/src/components/Navigator/Navigator.tsx index 569eede26..203c39b5e 100644 --- a/src/components/Navigator/Navigator.tsx +++ b/src/components/Navigator/Navigator.tsx @@ -49,6 +49,9 @@ const useStyles = makeStyles( overflow: "hidden" }, root: { + [theme.breakpoints.down("sm")]: { + height: "auto" + }, height: 500, maxWidth: 600, outline: 0, diff --git a/src/components/NavigatorButton/NavigatorButton.tsx b/src/components/NavigatorButton/NavigatorButton.tsx index ad3189280..5e3c07341 100644 --- a/src/components/NavigatorButton/NavigatorButton.tsx +++ b/src/components/NavigatorButton/NavigatorButton.tsx @@ -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), diff --git a/src/components/SideBar/ExpandButton.tsx b/src/components/SideBar/ExpandButton.tsx index 9a9550537..379d6be95 100644 --- a/src/components/SideBar/ExpandButton.tsx +++ b/src/components/SideBar/ExpandButton.tsx @@ -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 = ({ - className, - isShrunk, - ...rest -}) => { +const ExpandButton: React.FC = ({ isShrunk, ...rest }) => { const classes = useStyles({}); return ( - + - + ); }; diff --git a/src/components/SideBar/SideBar.tsx b/src/components/SideBar/SideBar.tsx index 04570317e..77b4f3ee4 100644 --- a/src/components/SideBar/SideBar.tsx +++ b/src/components/SideBar/SideBar.tsx @@ -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 = ({ location, menuItems, @@ -65,6 +73,7 @@ const SideBar: React.FC = ({ const classes = useStyles({}); const [isShrunk, setShrink] = useLocalStorage("isMenuSmall", false); const intl = useIntl(); + const configureMenuItem = getConfigureMenuItem(intl); return (
= ({ ) } isMenuShrunk={isShrunk} - menuItem={{ - ariaLabel: "configure", - icon: configurationIcon, - label: intl.formatMessage(sectionNames.configuration), - testingContextId: "configure", - url: configurationMenuUrl - }} + menuItem={configureMenuItem} onClick={onMenuItemClick} /> )} diff --git a/src/components/SideBarDrawer/MenuItemBtn.tsx b/src/components/SideBarDrawer/MenuItemBtn.tsx new file mode 100644 index 000000000..9013d5f36 --- /dev/null +++ b/src/components/SideBarDrawer/MenuItemBtn.tsx @@ -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 = ({ menuItem, onClick }) => { + const classes = useStyles({}); + + return ( + + ); +}; + +MenuItemBtn.displayName = "MenuItemBtn"; +export default MenuItemBtn; diff --git a/src/components/SideBarDrawer/SideBarDrawer.stories.tsx b/src/components/SideBarDrawer/SideBarDrawer.stories.tsx new file mode 100644 index 000000000..057ee7b90 --- /dev/null +++ b/src/components/SideBarDrawer/SideBarDrawer.stories.tsx @@ -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", () => ( + undefined} + renderConfigure={true} + user={user} + /> + )); diff --git a/src/components/SideBarDrawer/SideBarDrawer.tsx b/src/components/SideBarDrawer/SideBarDrawer.tsx new file mode 100644 index 000000000..e7963401c --- /dev/null +++ b/src/components/SideBarDrawer/SideBarDrawer.tsx @@ -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 = ({ + menuItems, + onMenuItemClick, + renderConfigure, + user +}) => { + const [isOpened, setOpened] = React.useState(false); + const classes = useStyles({}); + const intl = useIntl(); + const [activeMenu, setActiveMenu] = React.useState(null); + const [showSubmenu, setShowSubmenu] = React.useState(false); + const container = React.useRef(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 ( + <> + setOpened(true)}> + + + setOpened(false)} + > +
+
+
+ + {menuItems.map(menuItem => { + if ( + menuItem.permission && + !user.userPermissions + .map(perm => perm.code) + .includes(menuItem.permission) + ) { + return null; + } + + return ( + handleMenuItemWithChildrenClick(menuItem) + : handleMenuItemClick + } + key={menuItem.ariaLabel} + /> + ); + })} + {renderConfigure && ( + + )} +
+ {activeMenu && ( +
+
+
+ + + {activeMenu.label} + +
+ setShowSubmenu(false)}> + + +
+ {activeMenu.children.map(subMenuItem => ( + + ))} +
+ )} +
+
+
+ + ); +}; + +SideBarDrawer.displayName = "SideBarDrawer"; +export default SideBarDrawer; diff --git a/src/components/SideBarDrawer/index.ts b/src/components/SideBarDrawer/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/SideBarDrawer/styles.ts b/src/components/SideBarDrawer/styles.ts new file mode 100644 index 000000000..92cda2a00 --- /dev/null +++ b/src/components/SideBarDrawer/styles.ts @@ -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; diff --git a/src/components/SquareButton/SquareButton.stories.tsx b/src/components/SquareButton/SquareButton.stories.tsx new file mode 100644 index 000000000..2458c5d9f --- /dev/null +++ b/src/components/SquareButton/SquareButton.stories.tsx @@ -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", () => ( + + + + )); diff --git a/src/components/SquareButton/SquareButton.tsx b/src/components/SquareButton/SquareButton.tsx new file mode 100644 index 000000000..5e69a2fd9 --- /dev/null +++ b/src/components/SquareButton/SquareButton.tsx @@ -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 = ({ className, ...rest }) => { + const classes = useStyles({}); + + return ( + + ); +}; + +SquareButton.displayName = "SquareButton"; +export default SquareButton; diff --git a/src/components/SquareButton/index.ts b/src/components/SquareButton/index.ts new file mode 100644 index 000000000..7f406f96b --- /dev/null +++ b/src/components/SquareButton/index.ts @@ -0,0 +1,2 @@ +export * from "./SquareButton"; +export { default } from "./SquareButton"; diff --git a/src/components/UserChip/UserChip.tsx b/src/components/UserChip/UserChip.tsx new file mode 100644 index 000000000..165499ea7 --- /dev/null +++ b/src/components/UserChip/UserChip.tsx @@ -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 = ({ + user, + onLogout, + onProfileClick +}) => { + const classes = useStyles({}); + const [isMenuOpened, setMenuState] = React.useState(false); + const anchor = React.useRef(); + + const handleLogout = () => { + setMenuState(false); + onLogout(); + }; + + const handleViewerProfile = () => { + setMenuState(false); + onProfileClick(); + }; + + return ( +
+ + ) : ( +
+
+ {getUserInitials(user)} +
+
+ ) + } + classes={{ + avatar: classes.avatar + }} + className={classes.userChip} + label={ + <> + {getUserName(user, true)} + + + } + onClick={() => setMenuState(!isMenuOpened)} + data-test="userMenu" + /> + + {({ TransitionProps, placement }) => ( + + + setMenuState(false)} + mouseEvent="onClick" + > + + + + + + + + + + + + )} + +
+ ); +}; +UserChip.displayName = "UserChip"; +export default UserChip; diff --git a/src/components/UserChip/index.ts b/src/components/UserChip/index.ts new file mode 100644 index 000000000..125a4544b --- /dev/null +++ b/src/components/UserChip/index.ts @@ -0,0 +1,2 @@ +export * from "./UserChip"; +export { default } from "./UserChip"; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 126c0eb58..af14341ef 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -4753,6 +4753,30 @@ exports[`Storyshots Generics / Metadata loading 1`] = `
`; +exports[`Storyshots Generics / Mobile Side Menu default 1`] = ` +
+ +
+`; + exports[`Storyshots Generics / Money formatting default 1`] = `
`; +exports[`Storyshots Generics / Square Button default 1`] = ` +
+ +
+`; + exports[`Storyshots Generics / StatusLabel when error 1`] = `