diff --git a/webapp/app.ironcalc.com/frontend/src/App.tsx b/webapp/app.ironcalc.com/frontend/src/App.tsx index d2c72fc..d3efebf 100644 --- a/webapp/app.ironcalc.com/frontend/src/App.tsx +++ b/webapp/app.ironcalc.com/frontend/src/App.tsx @@ -1,7 +1,8 @@ import "./App.css"; import styled from "@emotion/styled"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { FileBar } from "./components/FileBar"; +import LeftDrawer from "./components/LeftDrawer/LeftDrawer"; import { get_documentation_model, get_model, @@ -10,6 +11,8 @@ import { import { createNewModel, deleteSelectedModel, + getModelsMetadata, + getSelectedUuid, loadModelFromStorageOrCreate, saveModelToStorage, saveSelectedModelInStorage, @@ -21,6 +24,14 @@ import { IronCalc, IronCalcIcon, Model, init } from "@ironcalc/workbook"; function App() { const [model, setModel] = useState(null); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [modelsMetadata, setModelsMetadata] = useState(getModelsMetadata()); + const [selectedUuid, setSelectedUuid] = useState(getSelectedUuid()); + + const refreshModelsData = useCallback(() => { + setModelsMetadata(getModelsMetadata()); + setSelectedUuid(getSelectedUuid()); + }, []); useEffect(() => { async function start() { @@ -38,6 +49,7 @@ function App() { const importedModel = Model.from_bytes(model_bytes); localStorage.removeItem("selected"); setModel(importedModel); + refreshModelsData(); } catch (e) { alert("Model not found, or failed to load"); } @@ -47,6 +59,7 @@ function App() { const importedModel = Model.from_bytes(model_bytes); localStorage.removeItem("selected"); setModel(importedModel); + refreshModelsData(); } catch (e) { alert("Example file not found, or failed to load"); } @@ -54,10 +67,11 @@ function App() { // try to load from local storage const newModel = loadModelFromStorageOrCreate(); setModel(newModel); + refreshModelsData(); } } start(); - }, []); + }, [refreshModelsData]); if (!model) { return ( @@ -79,48 +93,80 @@ function App() { // We could use context for model, but the problem is that it should initialized to null. // Passing the property down makes sure it is always defined. + // Handlers for model changes that also update our models state + const handleNewModel = () => { + const newModel = createNewModel(); + setModel(newModel); + refreshModelsData(); + }; + + const handleSetModel = (uuid: string) => { + const newModel = selectModelFromStorage(uuid); + if (newModel) { + setModel(newModel); + refreshModelsData(); + } + }; + + const handleDeleteModel = () => { + const newModel = deleteSelectedModel(); + if (newModel) { + setModel(newModel); + refreshModelsData(); + } + }; + return ( - - { - const blob = await uploadFile(arrayBuffer, fileName); - - const bytes = new Uint8Array(await blob.arrayBuffer()); - const newModel = Model.from_bytes(bytes); - saveModelToStorage(newModel); - - setModel(newModel); - }} - newModel={() => { - setModel(createNewModel()); - }} - setModel={(uuid: string) => { - const newModel = selectModelFromStorage(uuid); - if (newModel) { - setModel(newModel); - } - }} - onDelete={() => { - const newModel = deleteSelectedModel(); - if (newModel) { - setModel(newModel); - } - }} + + setIsDrawerOpen(false)} + newModel={handleNewModel} + setModel={handleSetModel} + models={modelsMetadata} + selectedUuid={selectedUuid} + setDeleteDialogOpen={() => {}} /> - - + + + { + const blob = await uploadFile(arrayBuffer, fileName); + const bytes = new Uint8Array(await blob.arrayBuffer()); + const newModel = Model.from_bytes(bytes); + saveModelToStorage(newModel); + setModel(newModel); + refreshModelsData(); + }} + newModel={handleNewModel} + setModel={handleSetModel} + onDelete={handleDeleteModel} + isDrawerOpen={isDrawerOpen} + setIsDrawerOpen={setIsDrawerOpen} + refreshModelsData={refreshModelsData} + /> + + + ); } -const Wrapper = styled("div")` - margin: 0px; - padding: 0px; +const AppContainer = styled("div")` + display: flex; width: 100%; height: 100%; + position: relative; + overflow: hidden; +`; + +const MainContent = styled("div")<{ isDrawerOpen: boolean }>` + margin-left: ${({ isDrawerOpen }) => (isDrawerOpen ? "0px" : "-264px")}; + transition: margin-left 0.3s ease; + width: ${({ isDrawerOpen }) => + isDrawerOpen ? "calc(100% - 264px)" : "100%"}; display: flex; flex-direction: column; - position: absolute; `; const Loading = styled("div")` diff --git a/webapp/app.ironcalc.com/frontend/src/components/FileBar.tsx b/webapp/app.ironcalc.com/frontend/src/components/FileBar.tsx index 924ca18..074eb61 100644 --- a/webapp/app.ironcalc.com/frontend/src/components/FileBar.tsx +++ b/webapp/app.ironcalc.com/frontend/src/components/FileBar.tsx @@ -1,8 +1,9 @@ import styled from "@emotion/styled"; import type { Model } from "@ironcalc/workbook"; -import { IronCalcIcon, IronCalcLogo } from "@ironcalc/workbook"; +import { Button, IconButton } from "@mui/material"; +import { PanelLeftClose, PanelLeftOpen } from "lucide-react"; import { useLayoutEffect, useRef, useState } from "react"; -import { FileMenu } from "./FileMenu"; +import { DesktopMenu, MobileMenu } from "./FileMenu"; import { ShareButton } from "./ShareButton"; import ShareWorkbookDialog from "./ShareWorkbookDialog"; import { WorkbookTitle } from "./WorkbookTitle"; @@ -29,11 +30,15 @@ export function FileBar(properties: { setModel: (key: string) => void; onModelUpload: (blob: ArrayBuffer, fileName: string) => Promise; onDelete: () => void; + isDrawerOpen: boolean; + setIsDrawerOpen: (open: boolean) => void; + refreshModelsData: () => void; // Add this new prop }) { const [isDialogOpen, setIsDialogOpen] = useState(false); const spacerRef = useRef(null); const [maxTitleWidth, setMaxTitleWidth] = useState(0); const width = useWindowWidth(); + const fileButtonRef = useRef(null); // biome-ignore lint/correctness/useExhaustiveDependencies: We need to update the maxTitleWidth when the width changes useLayoutEffect(() => { @@ -44,34 +49,54 @@ export function FileBar(properties: { } }, [width]); + // Common handler functions for both menu types + const handleDownload = async () => { + const model = properties.model; + const bytes = model.toBytes(); + const fileName = model.getName(); + await downloadModel(bytes, fileName); + }; + return ( - - - - { - const model = properties.model; - const bytes = model.toBytes(); - const fileName = model.getName(); - await downloadModel(bytes, fileName); - }} - onDelete={properties.onDelete} - /> - window.open("https://docs.ironcalc.com", "_blank")} + properties.setIsDrawerOpen(!properties.isDrawerOpen)} + disableRipple > - Help - + {properties.isDrawerOpen ? : } + + + + window.open("https://docs.ironcalc.com", "_blank")} + > + Help + + + + + + { properties.model.setName(name); updateNameSelectedWorkbook(properties.model, name); + properties.refreshModelsData(); }} maxWidth={maxTitleWidth} /> @@ -91,12 +116,8 @@ export function FileBar(properties: { ); } -// We want the workbook title to be exactly an the center of the page, -// so we need an absolute position const WorkbookTitleWrapper = styled("div")` - position: absolute; - left: 50%; - transform: translateX(-50%); + position: relative; `; // The "Spacer" component occupies as much space as possible between the menu and the share button @@ -104,51 +125,83 @@ const Spacer = styled("div")` flex-grow: 1; `; -const StyledDesktopLogo = styled(IronCalcLogo)` - width: 120px; - margin-left: 12px; - @media (max-width: 769px) { - display: none; - } -`; - -const StyledIronCalcIcon = styled(IronCalcIcon)` - width: 36px; - margin-left: 10px; - @media (min-width: 769px) { - display: none; - } -`; - -const HelpButton = styled("div")` - display: flex; - align-items: center; - font-size: 12px; - font-family: Inter; +const DrawerButton = styled(IconButton)` + margin-left: 8px; + height: 32px; + width: 32px; padding: 8px; border-radius: 4px; - cursor: pointer; + svg { + stroke-width: 2px; + stroke: #757575; + width: 16px; + height: 16px; + } &:hover { background-color: #f2f2f2; } -`; - -const Divider = styled("div")` - margin: 0px 8px 0px 16px; - height: 12px; - border-left: 1px solid #e0e0e0; + &:active { + background-color: #e0e0e0; + } `; // The container must be relative positioned so we can position the title absolutely const FileBarWrapper = styled("div")` position: relative; height: 60px; + min-height: 60px; width: 100%; background: #fff; display: flex; align-items: center; border-bottom: 1px solid #e0e0e0; justify-content: space-between; + box-sizing: border-box; +`; + +const DesktopButtonsWrapper = styled("div")` + display: flex; + gap: 4px; + margin-left: 8px; + @media (max-width: 600px) { + display: none; + } +`; + +const MobileButtonsWrapper = styled("div")` + display: flex; + gap: 4px; + @media (min-width: 601px) { + display: none; + } + @media (max-width: 600px) { + display: flex; + } +`; + +const FileBarButtonContainer = styled("div")` + position: relative; + display: inline-block; +`; + +const FileBarButton = styled(Button)` + display: flex; + flex-direction: row; + align-items: center; + font-size: 12px; + height: 32px; + width: auto; + padding: 4px 8px; + font-weight: 400; + min-width: 0px; + text-transform: capitalize; + color: #333333; + &:hover { + background-color: #f2f2f2; + } + &:active { + background-color: #e0e0e0; + } `; const DialogContainer = styled("div")` diff --git a/webapp/app.ironcalc.com/frontend/src/components/FileMenu.tsx b/webapp/app.ironcalc.com/frontend/src/components/FileMenu.tsx index 7866f84..33e514a 100644 --- a/webapp/app.ironcalc.com/frontend/src/components/FileMenu.tsx +++ b/webapp/app.ironcalc.com/frontend/src/components/FileMenu.tsx @@ -1,76 +1,165 @@ import styled from "@emotion/styled"; -import { Menu, MenuItem, Modal } from "@mui/material"; -import { Check, FileDown, FileUp, Plus, Trash2 } from "lucide-react"; +import { Button, IconButton, Menu, MenuItem, Modal } from "@mui/material"; +import { + ChevronRight, + EllipsisVertical, + FileDown, + FileUp, + Plus, + Trash2, +} from "lucide-react"; import { useRef, useState } from "react"; import DeleteWorkbookDialog from "./DeleteWorkbookDialog"; import UploadFileDialog from "./UploadFileDialog"; import { getModelsMetadata, getSelectedUuid } from "./storage"; +export function DesktopMenu(props: { + newModel: () => void; + setModel: (key: string) => void; + onDownload: () => void; + onModelUpload: (blob: ArrayBuffer, fileName: string) => Promise; + onDelete: () => void; +}) { + const [isFileMenuOpen, setFileMenuOpen] = useState(false); + const anchorElement = useRef( + null as unknown as HTMLButtonElement, + ); + + return ( + <> + setFileMenuOpen(!isFileMenuOpen)} + ref={anchorElement} + disableRipple + isOpen={isFileMenuOpen} + > + File + + {}} + anchorElement={anchorElement} + /> + + ); +} + +export function MobileMenu(props: { + newModel: () => void; + setModel: (key: string) => void; + onDownload: () => void; + onModelUpload: (blob: ArrayBuffer, fileName: string) => Promise; + onDelete: () => void; +}) { + const [isMobileMenuOpen, setMobileMenuOpen] = useState(false); + const [isFileMenuOpen, setFileMenuOpen] = useState(false); + const anchorElement = useRef( + null as unknown as HTMLButtonElement, + ); + const [fileMenuAnchorEl, setFileMenuAnchorEl] = useState( + null, + ); + + return ( + <> + setMobileMenuOpen(true)} + ref={anchorElement} + disableRipple + > + + + setMobileMenuOpen(false)} + anchorEl={anchorElement.current} + > + { + setFileMenuOpen(true); + setFileMenuAnchorEl(event.currentTarget); + }} + disableRipple + > + File + + + + { + window.open("https://docs.ironcalc.com", "_blank"); + setMobileMenuOpen(false); + }} + disableRipple + > + Help + + + + + ); +} + export function FileMenu(props: { newModel: () => void; setModel: (key: string) => void; onDownload: () => void; onModelUpload: (blob: ArrayBuffer, fileName: string) => Promise; onDelete: () => void; + isFileMenuOpen: boolean; + setFileMenuOpen: (open: boolean) => void; + setMobileMenuOpen: (open: boolean) => void; + anchorElement: React.RefObject; }) { - const [isMenuOpen, setMenuOpen] = useState(false); const [isImportMenuOpen, setImportMenuOpen] = useState(false); - const anchorElement = useRef(null); const models = getModelsMetadata(); - const uuids = Object.keys(models); const selectedUuid = getSelectedUuid(); const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); - const elements = []; - for (const uuid of uuids) { - elements.push( - { - props.setModel(uuid); - setMenuOpen(false); - }} - > - - {uuid === selectedUuid ? : ""} - - - {models[uuid]} - - , - ); - } - return ( <> - setMenuOpen(true)} - ref={anchorElement} - > - File - - setMenuOpen(false)} - anchorEl={anchorElement.current} - sx={{ - "& .MuiPaper-root": { borderRadius: "8px", padding: "4px 0px" }, - "& .MuiList-root": { padding: "0" }, + props.setFileMenuOpen(false)} + anchorEl={props.anchorElement.current} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + // To prevent closing parent menu when interacting with submenu + onMouseLeave={() => { + if (!isImportMenuOpen && !isDeleteDialogOpen) { + props.setFileMenuOpen(false); + } }} - - // anchorOrigin={properties.anchorOrigin} > { props.newModel(); - setMenuOpen(false); + props.setFileMenuOpen(false); + props.setMobileMenuOpen(false); }} + disableRipple > New @@ -78,30 +167,37 @@ export function FileMenu(props: { { setImportMenuOpen(true); - setMenuOpen(false); + props.setFileMenuOpen(false); + props.setMobileMenuOpen(false); }} + disableRipple > Import - + { + props.onDownload(); + props.setMobileMenuOpen(false); + }} + disableRipple + > - - Download (.xlsx) - + Download (.xlsx) + { setDeleteDialogOpen(true); - setMenuOpen(false); + props.setFileMenuOpen(false); + props.setMobileMenuOpen(false); }} + disableRipple > Delete workbook - - {elements} - + { @@ -133,6 +229,46 @@ export function FileMenu(props: { ); } +const MenuButton = styled(IconButton)` + height: 32px; + width: 32px; + padding: 8px; + border-radius: 4px; + svg { + stroke-width: 2px; + stroke: #757575; + width: 16px; + height: 16px; + } + &:hover { + background-color: #f2f2f2; + } + &:active { + background-color: #e0e0e0; + } +`; + +const FileBarButton = styled(Button)<{ isOpen: boolean }>` + display: flex; + flex-direction: row; + align-items: center; + font-size: 12px; + height: 32px; + width: auto; + padding: 4px 8px; + font-weight: 400; + min-width: 0px; + text-transform: capitalize; + color: #333333; + background-color: ${({ isOpen }) => (isOpen ? "#f2f2f2" : "none")}; + &:hover { + background-color: #f2f2f2; + } + &:active { + background-color: #e0e0e0; + } +`; + const StyledPlus = styled(Plus)` width: 16px; height: 16px; @@ -161,13 +297,6 @@ const StyledTrash = styled(Trash2)` padding-right: 10px; `; -const StyledCheck = styled(Check)` - width: 16px; - height: 16px; - color: #333333; - padding-right: 10px; -`; - const MenuDivider = styled("div")` width: 100%; margin: auto; @@ -179,6 +308,7 @@ const MenuDivider = styled("div")` const MenuItemText = styled("div")` color: #000; font-size: 12px; + flex-grow: 1; `; const MenuItemWrapper = styled(MenuItem)` @@ -191,23 +321,19 @@ const MenuItemWrapper = styled(MenuItem)` border-radius: 4px; padding: 8px; height: 32px; -`; - -const FileMenuWrapper = styled("div")` - display: flex; - align-items: center; - font-size: 12px; - font-family: Inter; - padding: 8px; - border-radius: 4px; - cursor: pointer; - &:hover { - background-color: #f2f2f2; + min-height: 32px; + svg { + width: 16px; + height: 16px; } `; -const CheckIndicator = styled("span")` - display: flex; - justify-content: center; - min-width: 26px; +const StyledMenu = styled(Menu)` + .MuiPaper-root { + border-radius: 8px; + padding: 4px 0px; + }, + .MuiList-root { + padding: 0; + }, `; diff --git a/webapp/app.ironcalc.com/frontend/src/components/LeftDrawer/LeftDrawer.tsx b/webapp/app.ironcalc.com/frontend/src/components/LeftDrawer/LeftDrawer.tsx new file mode 100644 index 0000000..e5405f4 --- /dev/null +++ b/webapp/app.ironcalc.com/frontend/src/components/LeftDrawer/LeftDrawer.tsx @@ -0,0 +1,384 @@ +import styled from "@emotion/styled"; +import { IronCalcLogo } from "@ironcalc/workbook"; +import { Avatar, Drawer, IconButton, Menu, MenuItem } from "@mui/material"; +import { + EllipsisVertical, + FileDown, + FileSpreadsheet, + Plus, + Trash2, +} from "lucide-react"; +import type React from "react"; +import { useState } from "react"; +import UserMenu from "../UserMenu"; + +interface LeftDrawerProps { + open: boolean; + onClose: () => void; + newModel: () => void; + setModel: (key: string) => void; + models: { [key: string]: string }; + selectedUuid: string | null; +} + +const LeftDrawer: React.FC = ({ + open, + onClose, + newModel, + setModel, + models, + selectedUuid, +}) => { + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [selectedWorkbookUuid, setSelectedWorkbookUuid] = useState< + string | null + >(null); + const [userMenuAnchorEl, setUserMenuAnchorEl] = useState( + null, + ); + + const handleMenuOpen = ( + event: React.MouseEvent, + uuid: string, + ) => { + console.log("Menu open", uuid); + event.stopPropagation(); + setSelectedWorkbookUuid(uuid); + setMenuAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setMenuAnchorEl(null); + setSelectedWorkbookUuid(null); + }; + + const handleUserMenuOpen = (event: React.MouseEvent) => { + setUserMenuAnchorEl(event.currentTarget); + }; + + const handleUserMenuClose = () => { + setUserMenuAnchorEl(null); + }; + + const elements = Object.keys(models) + .reverse() + .map((uuid) => { + const isMenuOpen = menuAnchorEl !== null && selectedWorkbookUuid === uuid; + return ( + { + setModel(uuid); + }} + selected={uuid === selectedUuid} + disableRipple + > + + + + {models[uuid]} + handleMenuOpen(e, uuid)} + disableRipple + isOpen={isMenuOpen} + > + + + + + { + handleMenuClose(); + }} + disableRipple + > + + Download (.xlsx) + + + { + handleMenuClose(); + }} + disableRipple + > + + Delete workbook + + + + ); + }); + + return ( + + + + { + newModel(); + }} + > + + + + + Your workbooks + {elements} + + + + + Nikola Tesla + + { + console.log("Preferences clicked"); + handleUserMenuClose(); + }} + onLogout={() => { + console.log("Logout clicked"); + handleUserMenuClose(); + }} + /> + + + ); +}; + +const DrawerWrapper = styled(Drawer)` + width: 264px; + height: 100%; + flex-shrink: 0; + font-family: "Inter", sans-serif; + + .MuiDrawer-paper { + width: 264px; + background-color: #f5f5f5; + overflow: hidden; + border-right: 1px solid #e0e0e0; + } +`; + +const DrawerHeader = styled("div")` + display: flex; + align-items: center; + padding: 12px 8px 12px 16px; + justify-content: space-between; + max-height: 60px; + min-height: 60px; + border-bottom: 1px solid #e0e0e0; + box-sizing: border-box; +`; + +const StyledDesktopLogo = styled(IronCalcLogo)` + width: 120px; + height: 28px; +`; + +const AddButton = styled(IconButton)` + background: none; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + height: 32px; + width: 32px; + border-radius: 4px; + margin-left: 10px; + color: #333333; + stroke-width: 2px; + &:hover { + background-color: #e0e0e0; + } +`; + +const PlusIcon = styled(Plus)` + width: 16px; + height: 16px; +`; + +const DrawerContent = styled("div")` + display: flex; + flex-direction: column; + gap: 4px; + padding: 16px 12px; + height: 100%; + overflow: scroll; + font-size: 12px; +`; + +const DrawerContentTitle = styled("div")` + font-weight: 600; + color: #9e9e9e; + margin-bottom: 8px; + padding: 0px 8px; +`; + +const StorageIndicator = styled("div")` + height: 16px; + width: 16px; + svg { + height: 16px; + width: 16px; + stroke: #9e9e9e; + } +`; + +const EllipsisButton = styled(IconButton)<{ isOpen: boolean }>` + background: none; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 4px; + height: 24px; + width: 24px; + border-radius: 4px; + color: #333333; + stroke-width: 2px; + background-color: ${({ isOpen }) => (isOpen ? "#E0E0E0" : "none")}; + opacity: ${({ isOpen }) => (isOpen ? "1" : "0.5")}; + transition: opacity 0.3s, background-color 0.3s; + &:hover { + background: none; + opacity: 1; + } + &:active { + background: #bdbdbd; + opacity: 1; + } +`; + +const WorkbookListItem = styled(MenuItem)<{ selected: boolean }>` + display: flex; + gap: 8px; + justify-content: flex-start; + font-size: 14px; + width: 100%; + min-width: 172px; + border-radius: 8px; + padding: 8px 4px 8px 8px; + height: 32px; + min-height: 32px; + transition: gap 0.5s; + background-color: ${({ selected }) => + selected ? "#e0e0e0 !important" : "transparent"}; +`; + +const WorkbookListText = styled("div")` + color: #000; + font-size: 12px; + width: 100%; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +`; + +const StyledMenu = styled(Menu)` + .MuiPaper-root { + border-radius: 8px; + padding: 4px 0px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.01); + }, + .MuiList-root { + padding: 0; + }, +`; + +const MenuDivider = styled("div")` + width: 100%; + margin: auto; + margin-top: 4px; + margin-bottom: 4px; + border-top: 1px solid #eeeeee; +`; + +const MenuItemWrapper = styled(MenuItem)` + display: flex; + justify-content: flex-start; + font-size: 12px; + width: calc(100% - 8px); + min-width: 140px; + margin: 0px 4px; + border-radius: 4px; + padding: 8px; + height: 32px; + gap: 8px; + svg { + width: 16px; + height: 16px; + } +`; + +const DrawerFooter = styled("div")` + display: none; + align-items: center; + padding: 12px; + justify-content: space-between; + max-height: 60px; + height: 60px; + border-top: 1px solid #e0e0e0; + box-sizing: border-box; +`; + +const UserWrapper = styled(MenuItem)<{ selected: boolean }>` + display: flex; + align-items: center; + gap: 8px; + flex-grow: 1; + padding: 8px; + border-radius: 8px; + max-width: 100%; + background-color: ${({ selected }) => + selected ? "#e0e0e0 !important" : "transparent"}; +`; + +const StyledAvatar = styled(Avatar)` + font-size: 14px; +`; + +const Username = styled("div")` + font-size: 12px; + flex-grow: 1; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +`; + +export default LeftDrawer; diff --git a/webapp/app.ironcalc.com/frontend/src/components/UserMenu.tsx b/webapp/app.ironcalc.com/frontend/src/components/UserMenu.tsx new file mode 100644 index 0000000..94eb92f --- /dev/null +++ b/webapp/app.ironcalc.com/frontend/src/components/UserMenu.tsx @@ -0,0 +1,90 @@ +import styled from "@emotion/styled"; +import { Menu, MenuItem } from "@mui/material"; +import { LogOut, Settings } from "lucide-react"; + +interface UserMenuProps { + anchorEl: null | HTMLElement; + onClose: () => void; + onPreferences: () => void; + onLogout: () => void; +} + +const UserMenu: React.FC = ({ + anchorEl, + onClose, + onPreferences, + onLogout, +}) => { + return ( + + + + Preferences + + + + + Log out + + + ); +}; + +const StyledMenu = styled(Menu)` + & .MuiPaper-root { + border-radius: 8px; + padding: 4px 0px; + margin-top: -4px; + margin-left: 4px; + } + & .MuiList-root { + padding: 0; + } +`; + +const MenuItemText = styled("div")` + color: #000; + font-size: 12px; + flex-grow: 1; +`; + +const MenuItemWrapper = styled(MenuItem)` + display: flex; + justify-content: flex-start; + font-size: 14px; + width: calc(100% - 8px); + min-width: 172px; + margin: 0px 4px; + border-radius: 4px; + padding: 8px; + height: 32px; + svg { + width: 16px; + height: 16px; + } +`; + +const MenuDivider = styled("div")` + width: 100%; + margin: auto; + margin-top: 4px; + margin-bottom: 4px; + border-top: 1px solid #eeeeee; +`; + +export default UserMenu; diff --git a/webapp/app.ironcalc.com/frontend/src/components/WorkbookTitle.tsx b/webapp/app.ironcalc.com/frontend/src/components/WorkbookTitle.tsx index 1a151d4..bc12216 100644 --- a/webapp/app.ironcalc.com/frontend/src/components/WorkbookTitle.tsx +++ b/webapp/app.ironcalc.com/frontend/src/components/WorkbookTitle.tsx @@ -72,10 +72,10 @@ export function WorkbookTitle(properties: { } const Container = styled("div")` - text-align: center; - padding: 8px; + text-align: left; + padding: 6px 4px; font-size: 14px; - font-weight: 700; + font-weight: 600; font-family: Inter; `; @@ -108,7 +108,7 @@ const TitleInput = styled("input")` background-color: #f2f2f2; } &:focus { - border: 1px solid grey; + outline: 1px solid grey; } font-weight: inherit; font-family: inherit;