FIX[WebApp]: Rename navigation => SheetTabBar

Also rename all widgets in that folder to more standard names
This commit is contained in:
Nicolás Hatcher
2024-12-14 22:26:15 +01:00
committed by Nicolás Hatcher Andrés
parent fb764fed1c
commit 98dc1f3b06
8 changed files with 18 additions and 19 deletions

View File

@@ -0,0 +1,99 @@
import { styled } from "@mui/material";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { Check } from "lucide-react";
import type { SheetOptions } from "./types";
function isWhiteColor(color: string): boolean {
return ["#FFF", "#FFFFFF"].includes(color);
}
interface SheetListMenuProps {
open: boolean;
onClose: () => void;
anchorEl: HTMLButtonElement | null;
onSheetSelected: (index: number) => void;
sheetOptionsList: SheetOptions[];
selectedIndex: number;
}
const SheetListMenu = (properties: SheetListMenuProps) => {
const {
open,
onClose,
anchorEl,
onSheetSelected,
sheetOptionsList,
selectedIndex,
} = properties;
const hasColors = sheetOptionsList.some((tab) => !isWhiteColor(tab.color));
return (
<StyledMenu
open={open}
onClose={onClose}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "left",
}}
transformOrigin={{
vertical: "bottom",
horizontal: 6,
}}
>
{sheetOptionsList.map((tab, index) => (
<StyledMenuItem
key={tab.sheetId}
onClick={() => onSheetSelected(index)}
>
{index === selectedIndex ? (
<Check
style={{ width: "16px", height: "16px", marginRight: "8px" }}
/>
) : (
<div
style={{ width: "16px", height: "16px", marginRight: "8px" }}
/>
)}
{hasColors && <ItemColor style={{ backgroundColor: tab.color }} />}
<ItemName
style={{ fontWeight: index === selectedIndex ? "bold" : "normal" }}
>
{tab.name}
</ItemName>
</StyledMenuItem>
))}
</StyledMenu>
);
};
const StyledMenu = styled(Menu)({
"& .MuiPaper-root": {
borderRadius: 8,
padding: 4,
},
"& .MuiList-padding": {
padding: 0,
},
});
const StyledMenuItem = styled(MenuItem)({
padding: 8,
borderRadius: 4,
});
const ItemColor = styled("div")`
width: 12px;
height: 12px;
border-radius: 4px;
margin-right: 8px;
`;
const ItemName = styled("div")`
font-size: 12px;
color: #333;
`;
export default SheetListMenu;

View File

@@ -0,0 +1,153 @@
import { Dialog, TextField, styled } from "@mui/material";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { theme } from "../../theme";
interface SheetRenameDialogProps {
open: boolean;
onClose: () => void;
onNameChanged: (name: string) => void;
defaultName: string;
}
const SheetRenameDialog = (properties: SheetRenameDialogProps) => {
const { t } = useTranslation();
const [name, setName] = useState(properties.defaultName);
const handleClose = () => {
properties.onClose();
};
return (
<Dialog open={properties.open} onClose={properties.onClose}>
<StyledDialogTitle>
{t("sheet_rename.title")}
<Cross onClick={handleClose} onKeyDown={() => {}}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<title>Close</title>
<path
d="M12 4.5L4 12.5"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4 4.5L12 12.5"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Cross>
</StyledDialogTitle>
<StyledDialogContent>
<StyledTextField
autoFocus
defaultValue={properties.defaultName}
onClick={(event) => event.stopPropagation()}
onKeyDown={(event) => {
event.stopPropagation();
if (event.key === "Enter") {
properties.onNameChanged(name);
properties.onClose();
}
}}
onChange={(event) => {
setName(event.target.value);
}}
spellCheck="false"
onPaste={(event) => event.stopPropagation()}
/>
</StyledDialogContent>
<DialogFooter>
<StyledButton
onClick={() => {
properties.onNameChanged(name);
}}
>
{t("sheet_rename.rename")}
</StyledButton>
</DialogFooter>
</Dialog>
);
};
const StyledDialogTitle = styled("div")`
display: flex;
align-items: center;
height: 44px;
font-size: 14px;
font-weight: 500;
font-family: Inter;
padding: 0px 12px;
justify-content: space-between;
border-bottom: 1px solid ${theme.palette.grey["300"]};
`;
const Cross = styled("div")`
&:hover {
background-color: ${theme.palette.grey["100"]};
}
display: flex;
border-radius: 4px;
height: 24px;
width: 24px;
cursor: pointer;
align-items: center;
justify-content: center;
`;
const StyledDialogContent = styled("div")`
font-size: 12px;
margin: 12px;
`;
const StyledTextField = styled(TextField)`
width: 100%;
border-radius: 4px;
overflow: hidden;
& .MuiInputBase-input {
font-size: 14px;
padding: 10px;
border: 1px solid ${theme.palette.grey["300"]};
border-radius: 4px;
color: ${theme.palette.common.black};
background-color: ${theme.palette.common.white};
}
&:hover .MuiInputBase-input {
border: 1px solid ${theme.palette.grey["500"]};
}
`;
const DialogFooter = styled("div")`
color: #757575;
display: flex;
align-items: center;
border-top: 1px solid ${theme.palette.grey["300"]};
font-family: Inter;
justify-content: flex-end;
padding: 12px;
`;
const StyledButton = styled("div")`
cursor: pointer;
color: #ffffff;
background: #f2994a;
padding: 0px 10px;
height: 36px;
line-height: 36px;
border-radius: 4px;
display: flex;
align-items: center;
font-family: "Inter";
font-size: 14px;
&:hover {
background: #d68742;
}
`;
export default SheetRenameDialog;

View File

@@ -0,0 +1,162 @@
import { Button, Menu, MenuItem, styled } from "@mui/material";
import { ChevronDown } from "lucide-react";
import { useRef, useState } from "react";
import ColorPicker from "../colorPicker";
import { isInReferenceMode } from "../editor/util";
import type { WorkbookState } from "../workbookState";
import SheetRenameDialog from "./SheetRenameDialog";
interface SheetTabProps {
name: string;
color: string;
selected: boolean;
onSelected: () => void;
onColorChanged: (hex: string) => void;
onRenamed: (name: string) => void;
onDeleted: () => void;
workbookState: WorkbookState;
}
function SheetTab(props: SheetTabProps) {
const { name, color, selected, workbookState, onSelected } = props;
const [anchorEl, setAnchorEl] = useState<null | HTMLButtonElement>(null);
const [colorPickerOpen, setColorPickerOpen] = useState(false);
const colorButton = useRef(null);
const open = Boolean(anchorEl);
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
const handleCloseRenameDialog = () => {
setRenameDialogOpen(false);
};
const handleOpenRenameDialog = () => {
setRenameDialogOpen(true);
};
return (
<>
<Wrapper
style={{ borderBottomColor: color, fontWeight: selected ? 600 : 400 }}
onClick={(event) => {
onSelected();
event.stopPropagation();
event.preventDefault();
}}
onPointerDown={(event) => {
// If it is in browse mode stop he event
const cell = workbookState.getEditingCell();
if (cell && isInReferenceMode(cell.text, cell.cursorStart)) {
event.stopPropagation();
event.preventDefault();
}
}}
ref={colorButton}
>
<Name>{name}</Name>
<StyledButton onClick={handleOpen}>
<ChevronDown />
</StyledButton>
</Wrapper>
<StyledMenu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: "top",
horizontal: "left",
}}
transformOrigin={{
vertical: "bottom",
horizontal: 6,
}}
>
<StyledMenuItem
onClick={() => {
handleOpenRenameDialog();
handleClose();
}}
>
Rename
</StyledMenuItem>
<StyledMenuItem
onClick={() => {
setColorPickerOpen(true);
handleClose();
}}
>
Change Color
</StyledMenuItem>
<StyledMenuItem
onClick={() => {
props.onDeleted();
handleClose();
}}
>
{" "}
Delete
</StyledMenuItem>
</StyledMenu>
<SheetRenameDialog
open={renameDialogOpen}
onClose={handleCloseRenameDialog}
defaultName={name}
onNameChanged={(newName) => {
props.onRenamed(newName);
setRenameDialogOpen(false);
}}
/>
<ColorPicker
color={color}
onChange={(color): void => {
props.onColorChanged(color);
setColorPickerOpen(false);
}}
onClose={() => {
setColorPickerOpen(false);
}}
anchorEl={colorButton}
open={colorPickerOpen}
/>
</>
);
}
const StyledMenu = styled(Menu)``;
const StyledMenuItem = styled(MenuItem)`
font-size: 12px;
`;
const StyledButton = styled(Button)`
width: 15px;
height: 24px;
min-width: 0px;
padding: 0px;
color: inherit;
font-weight: inherit;
svg {
width: 15px;
height: 15px;
}
`;
const Wrapper = styled("div")`
display: flex;
margin-left: 20px;
border-bottom: 3px solid;
border-top: 3px solid white;
line-height: 34px;
align-items: center;
cursor: pointer;
`;
const Name = styled("div")`
font-size: 12px;
margin-right: 5px;
text-wrap: nowrap;
`;
export default SheetTab;

View File

@@ -0,0 +1,136 @@
import { styled } from "@mui/material";
import { Menu, Plus } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { theme } from "../../theme";
import { NAVIGATION_HEIGHT } from "../constants";
import { StyledButton } from "../toolbar";
import type { WorkbookState } from "../workbookState";
import SheetListMenu from "./SheetListMenu";
import SheetTab from "./SheetTab";
import type { SheetOptions } from "./types";
export interface SheetTabBarProps {
sheets: SheetOptions[];
selectedIndex: number;
workbookState: WorkbookState;
onSheetSelected: (index: number) => void;
onAddBlankSheet: () => void;
onSheetColorChanged: (hex: string) => void;
onSheetRenamed: (name: string) => void;
onSheetDeleted: () => void;
}
function SheetTabBar(props: SheetTabBarProps) {
const { t } = useTranslation();
const { workbookState, onSheetSelected, sheets, selectedIndex } = props;
const [anchorEl, setAnchorEl] = useState<null | HTMLButtonElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<Container>
<StyledButton
title={t("navigation.add_sheet")}
$pressed={false}
onClick={props.onAddBlankSheet}
>
<Plus />
</StyledButton>
<StyledButton
onClick={handleClick}
title={t("navigation.sheet_list")}
$pressed={false}
>
<Menu />
</StyledButton>
<Sheets>
<SheetInner>
{sheets.map((tab, index) => (
<SheetTab
key={tab.sheetId}
name={tab.name}
color={tab.color}
selected={index === selectedIndex}
onSelected={() => onSheetSelected(index)}
onColorChanged={(hex: string): void => {
props.onSheetColorChanged(hex);
}}
onRenamed={(name: string): void => {
props.onSheetRenamed(name);
}}
onDeleted={(): void => {
props.onSheetDeleted();
}}
workbookState={workbookState}
/>
))}
</SheetInner>
</Sheets>
<Advert href="https://www.ironcalc.com" target="_blank">
ironcalc.com
</Advert>
<SheetListMenu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
sheetOptionsList={sheets}
onSheetSelected={(index) => {
onSheetSelected(index);
handleClose();
}}
selectedIndex={selectedIndex}
/>
</Container>
);
}
// Note I have to specify the font-family in every component that can be considered stand-alone
const Container = styled("div")`
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
display: flex;
height: ${NAVIGATION_HEIGHT}px;
align-items: center;
padding-left: 12px;
font-family: Inter;
background-color: #fff;
border-top: 1px solid #e0e0e0;
`;
const Sheets = styled("div")`
flex-grow: 2;
overflow: hidden;
overflow-x: auto;
scrollbar-width: none;
`;
const SheetInner = styled("div")`
display: flex;
`;
const Advert = styled("a")`
display: flex;
align-items: center;
color: #f2994a;
padding: 0px 12px;
font-size: 12px;
text-decoration: none;
border-left: 1px solid ${theme.palette.grey["300"]};
transition: color 0.2s ease-in-out;
&:hover {
text-decoration: underline;
}
@media (max-width: 769px) {
height: 100%;
}
`;
export default SheetTabBar;

View File

@@ -0,0 +1 @@
export { default } from "./SheetTabBar";

View File

@@ -0,0 +1,5 @@
export interface SheetOptions {
name: string;
color: string;
sheetId: number;
}