UNPKG

@activecollab/components

Version:

ActiveCollab Components

277 lines (264 loc) • 14.5 kB
import React, { useCallback, useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { MainLogo } from "./MainLogo"; import { BREAKPOINT, SM, TRANSITION, XL } from "./tokens"; import { IconButton } from "../../components/IconButton"; import { ActivityIcon, CalendarIcon, ClockStopwatchIndicatorIcon, CloseIcon, CollapsIcon, EstimatesIcon, InvoicesIcon, MobileMenuIcon, MyWorkIcon, NotificationBellIcon, PeopleIcon, ProjectsIcon, ReportTimeIcon, ReportsIcon, RocketIcon, SearchIcon } from "../../components/Icons"; import { Modal } from "../../components/Modal"; import { Paper } from "../../components/Paper"; import { Tooltip } from "../../components/Tooltip"; import { Body2, Caption1 } from "../../components/Typography"; import { DARK_THEME_SELECTOR, useResizeObserver } from "../../utils"; const MENU_ITEMS = [{ icon: RocketIcon, text: "Getting Started" }, { icon: ProjectsIcon, text: "Projects", active: true }, { icon: MyWorkIcon, text: "My Work" }, { icon: ActivityIcon, text: "Activity" }, { icon: CalendarIcon, text: "Calendar" }, { icon: PeopleIcon, text: "People" }, { icon: ReportTimeIcon, text: "Time" }, { icon: ReportsIcon, text: "Reports" }, { icon: InvoicesIcon, text: "Invoices" }, { icon: EstimatesIcon, text: "Estimates" }]; /* ------------------------------------------------------------------ */ /* Layout shell (mirrors Layout.js) */ /* ------------------------------------------------------------------ */ const StyledLayout = styled.div.withConfig({ displayName: "PresentationShell__StyledLayout", componentId: "sc-12j90o1-0" })(["height:100vh;display:flex;flex-direction:column;background-color:var(--body-bg-main);"]); const StyledContentRow = styled.div.withConfig({ displayName: "PresentationShell__StyledContentRow", componentId: "sc-12j90o1-1" })(["position:relative;display:flex;flex-direction:row-reverse;flex:1 1 0%;flex-grow:1;overflow-y:auto;"]); const StyledRightWindow = styled.div.withConfig({ displayName: "PresentationShell__StyledRightWindow", componentId: "sc-12j90o1-2" })(["position:relative;display:flex;flex-direction:column;flex:1 1 0%;flex-grow:1;width:100%;height:100%;overflow-y:auto;padding:0 16px;@media (min-width:", "){padding-right:32px;}"], SM); /* ------------------------------------------------------------------ */ /* Main menu (mirrors MainMenu.js + MainMenu.less) */ /* ------------------------------------------------------------------ */ const StyledMainMenu = styled.div.withConfig({ displayName: "PresentationShell__StyledMainMenu", componentId: "sc-12j90o1-3" })(["position:relative;z-index:1;display:none;flex-direction:column;height:100%;margin-right:4px;flex-shrink:0;width:240px;transition:width ", ";@media (min-width:", "){display:flex;}&.collapsed{width:72px;}.menu-item-text,.menu-section,#logotype{transition:all ", ";}&.collapsed .menu-item-text{opacity:0;visibility:hidden;width:0;margin-left:0;}&.collapsed .menu-section{transform:scaleY(0);opacity:0;max-height:0;margin-bottom:0;}&.collapsed #logotype{transform:translateX(-5%);opacity:0;}&.collapsed .collapse-expand-menu svg{transform:rotate(180deg);}&:not(.mobile){@media (max-width:1279.98px){width:72px;.menu-item-text{opacity:0;visibility:hidden;width:0;margin-left:0;}.menu-section{transform:scaleY(0);opacity:0;max-height:0;margin-bottom:0;}#logotype{transform:translateX(-5%);opacity:0;}}}&.mobile{display:flex;position:absolute;left:0;top:0;width:240px;background-color:var(--page-paper-main);}"], TRANSITION, SM, TRANSITION); const StyledMenuHeader = styled.div.withConfig({ displayName: "PresentationShell__StyledMenuHeader", componentId: "sc-12j90o1-4" })(["position:relative;height:68px;display:flex;align-items:center;flex-shrink:0;overflow:hidden;.main-logo{position:absolute;left:20px;color:var(--color-secondary);path{fill:var(--color-secondary);}}.collapse-expand-menu{position:absolute;right:4px;display:none;@media (min-width:", "){display:flex;}}"], XL); const StyledMenuScroll = styled.div.withConfig({ displayName: "PresentationShell__StyledMenuScroll", componentId: "sc-12j90o1-5" })(["display:flex;flex-direction:column;flex-grow:1;min-height:0;margin:12px 0;overflow-y:auto;overflow-x:hidden;"]); const StyledMenuSection = styled.div.withConfig({ displayName: "PresentationShell__StyledMenuSection", componentId: "sc-12j90o1-6" })(["display:flex;align-items:center;margin:0 12px 8px;padding:0 4px 0 12px;"]); const StyledMenuItem = styled.a.withConfig({ displayName: "PresentationShell__StyledMenuItem", componentId: "sc-12j90o1-7" })(["display:flex;align-items:center;height:40px;padding:0 4px 0 12px;margin:0 12px 2px;border-radius:8px;cursor:pointer;text-decoration:none;svg{flex-shrink:0;fill:var(--color-theme-700);opacity:0.7;}.menu-item-text{margin-left:12px;min-width:0;overflow:hidden;text-overflow:ellipsis;}&:hover{transition:all ", ";background-color:var(--color-theme-300);svg{fill:var(--color-theme-900);opacity:1;}.menu-item-text{color:var(--color-theme-900);}}&.active{background-color:var(--color-secondary-200);svg{fill:var(--color-secondary);opacity:1;}.menu-item-text{color:var(--color-secondary);}}"], TRANSITION); const StyledMobileMenuClose = styled(IconButton).withConfig({ displayName: "PresentationShell__StyledMobileMenuClose", componentId: "sc-12j90o1-8" })(["position:absolute;right:16px;"]); /* ------------------------------------------------------------------ */ /* Top bars */ /* ------------------------------------------------------------------ */ const StyledMobileMenuHeader = styled.div.withConfig({ displayName: "PresentationShell__StyledMobileMenuHeader", componentId: "sc-12j90o1-9" })(["display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background-color:var(--color-primary);z-index:50;@media (min-width:", "){display:none;}.mobile-menu-icon{fill:var(--color-theme-300);}"], SM); const StyledIconRow = styled.div.withConfig({ displayName: "PresentationShell__StyledIconRow", componentId: "sc-12j90o1-10" })(["display:flex;align-items:center;"]); const StyledTopRightControls = styled.div.withConfig({ displayName: "PresentationShell__StyledTopRightControls", componentId: "sc-12j90o1-11" })(["position:absolute;top:0;right:0;display:none;align-items:center;justify-content:flex-end;padding:12px 32px 12px 0;@media (min-width:", "){display:flex;}"], SM); const StyledHeaderAvatar = styled.div.withConfig({ displayName: "PresentationShell__StyledHeaderAvatar", componentId: "sc-12j90o1-12" })(["width:28px;height:28px;border-radius:50%;background-color:var(--color-secondary);color:var(--only-white);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;margin:0 8px;flex-shrink:0;cursor:pointer;", " &{color:var(--page-paper-main);}"], DARK_THEME_SELECTOR); /* ------------------------------------------------------------------ */ /* Page region (mirrors Page.jsx / PageBody.jsx) */ /* ------------------------------------------------------------------ */ const StyledPage = styled.div.withConfig({ displayName: "PresentationShell__StyledPage", componentId: "sc-12j90o1-13" })(["display:flex;flex-direction:column;flex-grow:1;"]); const StyledPageBody = styled.div.withConfig({ displayName: "PresentationShell__StyledPageBody", componentId: "sc-12j90o1-14" })(["display:flex;flex-direction:column;flex-grow:1;margin-bottom:32px;min-height:0;"]); /** The paper card that holds page content (used by list view & empty pages). */ export const StyledPagePaper = styled(Paper).withConfig({ displayName: "PresentationShell__StyledPagePaper", componentId: "sc-12j90o1-15" })(["flex-grow:1;width:100%;"]); /* ------------------------------------------------------------------ */ /* Mocked layout building blocks */ /* ------------------------------------------------------------------ */ const MenuItemsMock = _ref => { let _ref$mobile = _ref.mobile, mobile = _ref$mobile === void 0 ? false : _ref$mobile; return /*#__PURE__*/React.createElement(StyledMenuScroll, null, /*#__PURE__*/React.createElement(StyledMenuSection, { className: "menu-section" }, /*#__PURE__*/React.createElement(Caption1, { weight: "bold", color: "tertiary" }, "MENU")), MENU_ITEMS.map(_ref2 => { let Icon = _ref2.icon, text = _ref2.text, active = _ref2.active; return /*#__PURE__*/React.createElement(Tooltip, { key: text, title: text, placement: "right", disable: mobile }, /*#__PURE__*/React.createElement(StyledMenuItem, { className: active ? "active" : undefined }, /*#__PURE__*/React.createElement(Icon, null), /*#__PURE__*/React.createElement(Body2, { weight: "medium", color: "secondary", whitespace: "no-wrap", className: "menu-item-text" }, text))); })); }; const MainMenuMock = _ref3 => { let collapsed = _ref3.collapsed, onToggleCollapsed = _ref3.onToggleCollapsed, footer = _ref3.footer; return /*#__PURE__*/React.createElement(StyledMainMenu, { className: collapsed ? "collapsed" : undefined, "data-testid": "main-menu" }, /*#__PURE__*/React.createElement(StyledMenuHeader, null, /*#__PURE__*/React.createElement("a", { className: "main-logo" }, /*#__PURE__*/React.createElement(MainLogo, null)), /*#__PURE__*/React.createElement("div", { className: "collapse-expand-menu" }, /*#__PURE__*/React.createElement(IconButton, { variant: "text gray", size: "small", onClick: onToggleCollapsed }, /*#__PURE__*/React.createElement(CollapsIcon, null)))), /*#__PURE__*/React.createElement(MenuItemsMock, null), footer); }; const MainMenuMobileMock = _ref4 => { let open = _ref4.open, close = _ref4.close, footer = _ref4.footer; return open ? /*#__PURE__*/React.createElement(Modal, { open: open, onClose: close }, /*#__PURE__*/React.createElement(StyledMainMenu, { className: "mobile", "data-testid": "main-menu-mobile" }, /*#__PURE__*/React.createElement(StyledMenuHeader, null, /*#__PURE__*/React.createElement("a", { className: "main-logo" }, /*#__PURE__*/React.createElement(MainLogo, null)), /*#__PURE__*/React.createElement(StyledMobileMenuClose, { onClick: close, variant: "text gray" }, /*#__PURE__*/React.createElement(CloseIcon, null))), /*#__PURE__*/React.createElement(MenuItemsMock, { mobile: true }), footer)) : null; }; const MobileMenuHeaderMock = _ref5 => { let onOpenMobileMenu = _ref5.onOpenMobileMenu; return /*#__PURE__*/React.createElement(StyledMobileMenuHeader, null, /*#__PURE__*/React.createElement(IconButton, { size: "big", variant: "circle raised", onClick: onOpenMobileMenu }, /*#__PURE__*/React.createElement(MobileMenuIcon, { className: "mobile-menu-icon" })), /*#__PURE__*/React.createElement(StyledIconRow, null, /*#__PURE__*/React.createElement(IconButton, { variant: "text gray" }, /*#__PURE__*/React.createElement(SearchIcon, null)), /*#__PURE__*/React.createElement(StyledHeaderAvatar, null, "JS"), /*#__PURE__*/React.createElement(IconButton, { variant: "text gray" }, /*#__PURE__*/React.createElement(NotificationBellIcon, null)))); }; const TopRightControlsMock = () => /*#__PURE__*/React.createElement(StyledTopRightControls, null, /*#__PURE__*/React.createElement(IconButton, { variant: "text gray" }, /*#__PURE__*/React.createElement(ClockStopwatchIndicatorIcon, null)), /*#__PURE__*/React.createElement(IconButton, { variant: "text gray" }, /*#__PURE__*/React.createElement(SearchIcon, null)), /*#__PURE__*/React.createElement(StyledHeaderAvatar, null, "JS"), /*#__PURE__*/React.createElement(IconButton, { variant: "text gray" }, /*#__PURE__*/React.createElement(NotificationBellIcon, null))); /* ------------------------------------------------------------------ */ /* PresentationShell */ /* ------------------------------------------------------------------ */ /** * The full application shell shared by every presentation mock-up: the main * menu on the left (auto-collapsing below 1280px, modal below 640px), the * top-right controls and the scrollable page area. Pass the page header via * `header` and the body via `children`. */ export const PresentationShell = _ref6 => { let header = _ref6.header, children = _ref6.children, floating = _ref6.floating, menuFooter = _ref6.menuFooter; const _useState = useState(false), mobileMenuOpen = _useState[0], setMobileMenuOpen = _useState[1]; const _useState2 = useState(false), collapsed = _useState2[0], setCollapsed = _useState2[1]; const _useState3 = useState(), isMobileView = _useState3[0], setIsMobileView = _useState3[1]; const ref = useRef(null); const dimensions = useResizeObserver(ref); const width = dimensions == null ? void 0 : dimensions.width; useEffect(() => { if (width) { if (width < BREAKPOINT) { setIsMobileView(true); } else { setIsMobileView(false); setMobileMenuOpen(false); } } }, [width]); const handleOpenMobileMenu = useCallback(() => setMobileMenuOpen(true), []); const handleCloseMobileMenu = useCallback(() => setMobileMenuOpen(false), []); const handleToggleCollapsed = useCallback(() => setCollapsed(prev => !prev), []); return /*#__PURE__*/React.createElement(StyledLayout, { ref: ref }, isMobileView ? /*#__PURE__*/React.createElement(MobileMenuHeaderMock, { onOpenMobileMenu: handleOpenMobileMenu }) : null, /*#__PURE__*/React.createElement(StyledContentRow, null, /*#__PURE__*/React.createElement(StyledRightWindow, { id: "right-window" }, /*#__PURE__*/React.createElement(StyledPage, null, header, /*#__PURE__*/React.createElement(StyledPageBody, null, children)), isMobileView === false ? /*#__PURE__*/React.createElement(TopRightControlsMock, null) : null), isMobileView ? /*#__PURE__*/React.createElement(MainMenuMobileMock, { open: mobileMenuOpen, close: handleCloseMobileMenu, footer: menuFooter }) : /*#__PURE__*/React.createElement(MainMenuMock, { collapsed: collapsed, onToggleCollapsed: handleToggleCollapsed, footer: menuFooter })), floating); }; PresentationShell.displayName = "PresentationShell"; //# sourceMappingURL=PresentationShell.js.map