diff --git a/webapp/IronCalc/src/components/SheetTabBar/SheetRenameDialog.tsx b/webapp/IronCalc/src/components/SheetTabBar/SheetRenameDialog.tsx
deleted file mode 100644
index b635322..0000000
--- a/webapp/IronCalc/src/components/SheetTabBar/SheetRenameDialog.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Dialog, styled, TextField } from "@mui/material";
-import { Check, X } from "lucide-react";
-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 (
-
- );
-};
-
-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["50"]};
- }
- display: flex;
- border-radius: 4px;
- height: 24px;
- width: 24px;
- cursor: pointer;
- align-items: center;
- justify-content: center;
- svg {
- width: 16px;
- height: 16px;
- stroke-width: 1.5;
- }
-`;
-
-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;
diff --git a/webapp/IronCalc/src/components/SheetTabBar/SheetTab.tsx b/webapp/IronCalc/src/components/SheetTabBar/SheetTab.tsx
index 69b019d..ca80e51 100644
--- a/webapp/IronCalc/src/components/SheetTabBar/SheetTab.tsx
+++ b/webapp/IronCalc/src/components/SheetTabBar/SheetTab.tsx
@@ -1,5 +1,5 @@
import type { MenuItemProps } from "@mui/material";
-import { Button, Menu, MenuItem, styled } from "@mui/material";
+import { Button, Input, Menu, MenuItem, styled } from "@mui/material";
import {
ChevronDown,
EyeOff,
@@ -7,14 +7,13 @@ import {
TextCursorInput,
Trash2,
} from "lucide-react";
-import { useRef, useState } from "react";
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { theme } from "../../theme";
import ColorPicker from "../ColorPicker/ColorPicker";
import { isInReferenceMode } from "../Editor/util";
import type { WorkbookState } from "../workbookState";
import SheetDeleteDialog from "./SheetDeleteDialog";
-import SheetRenameDialog from "./SheetRenameDialog";
interface SheetTabProps {
name: string;
@@ -48,14 +47,6 @@ function SheetTab(props: SheetTabProps) {
onSelected();
setAnchorEl(event.currentTarget);
};
- const [renameDialogOpen, setRenameDialogOpen] = useState(false);
- const handleCloseRenameDialog = () => {
- setRenameDialogOpen(false);
- };
-
- const handleOpenRenameDialog = () => {
- setRenameDialogOpen(true);
- };
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -66,19 +57,70 @@ function SheetTab(props: SheetTabProps) {
const handleCloseDeleteDialog = () => {
setDeleteDialogOpen(false);
};
+
+ const [isEditing, setIsEditing] = useState(false);
+ const [editingName, setEditingName] = useState(name);
+ const inputRef = useRef(null);
+ const measureRef = useRef(null);
+ const [inputWidth, setInputWidth] = useState(0);
+
+ useEffect(() => {
+ if (isEditing && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [isEditing]);
+
+ useEffect(() => {
+ if (!isEditing) {
+ setEditingName(name);
+ }
+ }, [name, isEditing]);
+
+ useLayoutEffect(() => {
+ if (isEditing && measureRef.current) {
+ void editingName;
+ const width = measureRef.current.offsetWidth;
+ setInputWidth(Math.max(width + 8, 6));
+ }
+ }, [editingName, isEditing]);
+
+ const handleStartEditing = () => {
+ setEditingName(name);
+ setInputWidth(Math.max(name.length * 7 + 8, 6));
+ setIsEditing(true);
+ };
+
+ const handleSave = () => {
+ if (editingName.trim() !== "") {
+ props.onRenamed(editingName.trim());
+ }
+ setIsEditing(false);
+ };
+
+ const handleCancel = () => {
+ setEditingName(name);
+ setIsEditing(false);
+ };
return (
<>
{
- onSelected();
+ if (!isEditing) {
+ onSelected();
+ }
event.stopPropagation();
event.preventDefault();
}}
+ onDoubleClick={(event: React.MouseEvent) => {
+ event.stopPropagation();
+ event.preventDefault();
+ handleStartEditing();
+ }}
onContextMenu={handleContextMenu}
onPointerDown={(event: React.PointerEvent) => {
- // If it is in browse mode stop he event
const cell = workbookState.getEditingCell();
if (cell && isInReferenceMode(cell.text, cell.cursorStart)) {
event.stopPropagation();
@@ -87,10 +129,46 @@ function SheetTab(props: SheetTabProps) {
}}
ref={colorButton}
>
- {name}
-
-
-
+ {isEditing ? (
+ <>
+ {editingName || " "}
+ setEditingName(e.target.value)}
+ style={{ width: `${inputWidth}px` }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ handleSave();
+ } else if (e.key === "Escape") {
+ e.preventDefault();
+ handleCancel();
+ }
+ e.stopPropagation();
+ }}
+ onBlur={() => {
+ handleSave();
+ }}
+ onClick={(e) => e.stopPropagation()}
+ spellCheck={false}
+ />
+
+
+
+ >
+ ) : (
+ <>
+ {name}
+
+
+
+ >
+ )}
{
- handleOpenRenameDialog();
+ handleStartEditing();
handleClose();
}}
>
@@ -145,15 +223,6 @@ function SheetTab(props: SheetTabProps) {
{t("sheet_tab.delete")}
- {
- props.onRenamed(newName);
- setRenameDialogOpen(false);
- }}
- />
`
margin-right: 12px;
border-bottom: 3px solid ${(props) => props.$color};
line-height: 37px;
- padding: 0px 4px;
+ padding: 0px 4px 0px 6px;
align-items: center;
cursor: pointer;
+ min-width: 40px;
font-weight: ${(props) => (props.$selected ? 600 : 400)};
background-color: ${(props) =>
- props.$selected ? `${theme.palette.grey[50]}80` : "transparent"};
+ props.$selected ? `${theme.palette.grey[50]}` : "transparent"};
+ &:hover {
+ background-color: ${theme.palette.grey[50]}80;
+ }
`;
-const StyledButton = styled(Button)<{ $active?: boolean }>`
+const StyledButton = styled(Button)<{ $active: boolean }>`
width: 16px;
height: 16px;
min-width: 0px;
@@ -236,6 +309,7 @@ const StyledButton = styled(Button)<{ $active?: boolean }>`
color: inherit;
font-weight: inherit;
border-radius: 4px;
+ flex-shrink: 0;
background-color: ${(props) =>
props.$active ? `${theme.palette.grey[300]}` : "transparent"};
&:hover {
@@ -244,6 +318,9 @@ const StyledButton = styled(Button)<{ $active?: boolean }>`
&:active {
background-color: ${theme.palette.grey[300]};
}
+ &:disabled {
+ pointer-events: none;
+ }
svg {
width: 14px;
height: 14px;
@@ -255,6 +332,51 @@ const Name = styled("div")`
margin-right: 5px;
text-wrap: nowrap;
user-select: none;
+ width: 100%;
+ text-align: center;
+`;
+
+const HiddenMeasure = styled("span")`
+ position: absolute;
+ visibility: hidden;
+ white-space: pre;
+ font-size: 12px;
+ font-family: Inter;
+ font-weight: inherit;
+ padding: 0;
+ margin: 0;
+ height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+`;
+
+const StyledInput = styled(Input)`
+ font-size: 12px;
+ font-family: Inter;
+ font-weight: inherit;
+ min-width: 6px;
+ margin-right: 2px;
+ outline-offset: 1px;
+ min-height: 100%;
+ flex-grow: 1;
+ & .MuiInputBase-input {
+ font-family: Inter;
+ font-weight: inherit;
+ padding: 6px 0px;
+ outline: 1px solid ${theme.palette.primary.main};
+ border-radius: 2px;
+ color: ${theme.palette.common.black};
+ text-align: center;
+ will-change: width;
+ &:focus {
+ border-color: ${theme.palette.primary.main};
+ }
+ }
+
+ &::before,
+ &::after {
+ display: none;
+ }
`;
const MenuDivider = styled("div")`
diff --git a/webapp/IronCalc/src/components/SheetTabBar/SheetTabBar.tsx b/webapp/IronCalc/src/components/SheetTabBar/SheetTabBar.tsx
index 21d3388..57a3823 100644
--- a/webapp/IronCalc/src/components/SheetTabBar/SheetTabBar.tsx
+++ b/webapp/IronCalc/src/components/SheetTabBar/SheetTabBar.tsx
@@ -137,6 +137,7 @@ const Sheets = styled("div")`
padding-left: 12px;
display: flex;
flex-direction: row;
+ height: 100%;
`;
const SheetInner = styled("div")`