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 ( - - - {t("sheet_rename.title")} - event.key === "Enter" && properties.onClose()} - > - - - - - event.stopPropagation()} - onKeyDown={(event) => { - event.stopPropagation(); - if (event.key === "Enter") { - properties.onNameChanged(name); - properties.onClose(); - } else if (event.key === "Escape") { - properties.onClose(); - } - }} - onChange={(event) => { - setName(event.target.value); - }} - spellCheck="false" - onPaste={(event) => event.stopPropagation()} - onCopy={(event) => event.stopPropagation()} - onCut={(event) => event.stopPropagation()} - /> - - - { - properties.onNameChanged(name); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - properties.onNameChanged(name); - properties.onClose(); - } - }} - tabIndex={0} - > - - {t("sheet_rename.rename")} - - - - ); -}; - -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")`