@activecollab/components
Version:
ActiveCollab Components
277 lines (264 loc) • 14.5 kB
JavaScript
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