diff --git a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx index 62161ef..b082a18 100644 --- a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx +++ b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx @@ -14,6 +14,8 @@ import { ArrowUpToLine, Bold, ChevronDown, + ChevronLeft, + ChevronRight, DecimalsArrowLeft, DecimalsArrowRight, Euro, @@ -36,7 +38,7 @@ import { Undo2, WrapText, } from "lucide-react"; -import { useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ArrowMiddleFromLine } from "../../icons"; import { theme } from "../../theme"; @@ -94,443 +96,494 @@ function Toolbar(properties: ToolbarProperties) { const [fillColorPickerOpen, setFillColorPickerOpen] = useState(false); const [borderPickerOpen, setBorderPickerOpen] = useState(false); const [nameManagerDialogOpen, setNameManagerDialogOpen] = useState(false); + const [showLeftArrow, setShowLeftArrow] = useState(false); + const [showRightArrow, setShowRightArrow] = useState(false); const fontColorButton = useRef(null); const fillColorButton = useRef(null); const borderButton = useRef(null); + const toolbarRef = useRef(null); const { t } = useTranslation(); const { canEdit } = properties; + const scrollLeft = () => + toolbarRef.current?.scrollBy({ left: -200, behavior: "smooth" }); + const scrollRight = () => + toolbarRef.current?.scrollBy({ left: 200, behavior: "smooth" }); + + const updateArrows = useCallback(() => { + if (!toolbarRef.current) return; + const { scrollLeft, scrollWidth, clientWidth } = toolbarRef.current; + setShowLeftArrow(scrollLeft > 0); + setShowRightArrow(scrollLeft < scrollWidth - clientWidth); + }, []); + + useEffect(() => { + const toolbar = toolbarRef.current; + if (!toolbar) return; + + updateArrows(); + toolbar.addEventListener("scroll", updateArrows); + return () => toolbar.removeEventListener("scroll", updateArrows); + }, [updateArrows]); + return ( - - {/* History/Edit Group */} - - + {showLeftArrow && ( + - - - - - - + + + )} + + {/* History/Edit Group */} + + + + + + + + - + - {/* Format Tools Group */} - - - - - { - properties.onClearFormatting(); - }} - disabled={!canEdit} - title={t("toolbar.clear_formatting")} - > - - - + {/* Format Tools Group */} + + + + + { + properties.onClearFormatting(); + }} + disabled={!canEdit} + title={t("toolbar.clear_formatting")} + > + + + - + - {/* Number Format Group */} - - { - properties.onNumberFormatPicked(NumberFormats.CURRENCY_EUR); - }} - disabled={!canEdit} - title={t("toolbar.euro")} - > - - - { - properties.onNumberFormatPicked(NumberFormats.PERCENTAGE); - }} - disabled={!canEdit} - title={t("toolbar.percentage")} - > - - - { - properties.onNumberFormatPicked( - decreaseDecimalPlaces(properties.numFmt), - ); - }} - disabled={!canEdit} - title={t("toolbar.decimal_places_decrease")} - > - - - { - properties.onNumberFormatPicked( - increaseDecimalPlaces(properties.numFmt), - ); - }} - disabled={!canEdit} - title={t("toolbar.decimal_places_increase")} - > - - - { - properties.onNumberFormatPicked(numberFmt); - }} - onExited={(): void => {}} - anchorOrigin={{ - horizontal: 20, // Aligning the menu to the middle of FormatButton - vertical: "bottom", - }} - > + {/* Number Format Group */} + + { + properties.onNumberFormatPicked(NumberFormats.CURRENCY_EUR); + }} + disabled={!canEdit} + title={t("toolbar.euro")} + > + + + { + properties.onNumberFormatPicked(NumberFormats.PERCENTAGE); + }} + disabled={!canEdit} + title={t("toolbar.percentage")} + > + + + { + properties.onNumberFormatPicked( + decreaseDecimalPlaces(properties.numFmt), + ); + }} + disabled={!canEdit} + title={t("toolbar.decimal_places_decrease")} + > + + + { + properties.onNumberFormatPicked( + increaseDecimalPlaces(properties.numFmt), + ); + }} + disabled={!canEdit} + title={t("toolbar.decimal_places_increase")} + > + + + { + properties.onNumberFormatPicked(numberFmt); + }} + onExited={(): void => {}} + anchorOrigin={{ + horizontal: 20, // Aligning the menu to the middle of FormatButton + vertical: "bottom", + }} + > + + {"123"} + + + + + + + + {/* Font Size Group */} + { + properties.onIncreaseFontSize(-1); }} + title={t("toolbar.decrease_font_size")} > - {"123"} - + - - + {properties.fontSize} + { + properties.onIncreaseFontSize(1); + }} + title={t("toolbar.increase_font_size")} + > + + + - + - {/* Font Size Group */} - - { - properties.onIncreaseFontSize(-1); + {/* Text Style Group */} + + properties.onToggleBold(!properties.bold)} + disabled={!canEdit} + title={t("toolbar.bold")} + > + + + properties.onToggleItalic(!properties.italic)} + disabled={!canEdit} + title={t("toolbar.italic")} + > + + + properties.onToggleUnderline(!properties.underline)} + disabled={!canEdit} + title={t("toolbar.underline")} + > + + + properties.onToggleStrike(!properties.strike)} + disabled={!canEdit} + title={t("toolbar.strike_through")} + > + + + + + + + {/* Color & Border Group */} + + setFontColorPickerOpen(true)} + > + + + + setFillColorPickerOpen(true)} + > + + + + setBorderPickerOpen(true)} + ref={borderButton} + disabled={!canEdit} + title={t("toolbar.borders.title")} + > + + + + + + + {/* Alignment Group */} + + + properties.onToggleHorizontalAlign( + properties.horizontalAlign === "left" ? "general" : "left", + ) + } + disabled={!canEdit} + title={t("toolbar.align_left")} + > + + + + properties.onToggleHorizontalAlign( + properties.horizontalAlign === "center" ? "general" : "center", + ) + } + disabled={!canEdit} + title={t("toolbar.align_center")} + > + + + + properties.onToggleHorizontalAlign( + properties.horizontalAlign === "right" ? "general" : "right", + ) + } + disabled={!canEdit} + title={t("toolbar.align_right")} + > + + + properties.onToggleVerticalAlign("top")} + disabled={!canEdit} + title={t("toolbar.vertical_align_top")} + > + + + properties.onToggleVerticalAlign("center")} + disabled={!canEdit} + title={t("toolbar.vertical_align_middle")} + > + + + properties.onToggleVerticalAlign("bottom")} + disabled={!canEdit} + title={t("toolbar.vertical_align_bottom")} + > + + + { + properties.onToggleWrapText(!properties.wrapText); + }} + disabled={!canEdit} + title={t("toolbar.wrap_text")} + > + + + + + + + {/* View & Tools Group */} + + + properties.onToggleShowGridLines(!properties.showGridLines) + } + disabled={!canEdit} + title={t("toolbar.show_hide_grid_lines")} + > + {properties.showGridLines ? : } + + { + setNameManagerDialogOpen(true); + }} + disabled={!canEdit} + title={t("toolbar.name_manager")} + > + + + { + properties.onDownloadPNG(); + }} + disabled={!canEdit} + title={t("toolbar.selected_png")} + > + + + + + { + properties.onTextColorPicked(color); + setFontColorPickerOpen(false); }} - title={t("toolbar.decrease_font_size")} - > - - - {properties.fontSize} - { - properties.onIncreaseFontSize(1); + onClose={() => { + setFontColorPickerOpen(false); }} - title={t("toolbar.increase_font_size")} - > - - - - - - - {/* Text Style Group */} - - properties.onToggleBold(!properties.bold)} - disabled={!canEdit} - title={t("toolbar.bold")} - > - - - properties.onToggleItalic(!properties.italic)} - disabled={!canEdit} - title={t("toolbar.italic")} - > - - - properties.onToggleUnderline(!properties.underline)} - disabled={!canEdit} - title={t("toolbar.underline")} - > - - - properties.onToggleStrike(!properties.strike)} - disabled={!canEdit} - title={t("toolbar.strike_through")} - > - - - - - - - {/* Color & Border Group */} - - setFontColorPickerOpen(true)} - > - - - - setFillColorPickerOpen(true)} - > - - - - setBorderPickerOpen(true)} - ref={borderButton} - disabled={!canEdit} - title={t("toolbar.borders.title")} - > - - - - - - - {/* Alignment Group */} - - - properties.onToggleHorizontalAlign( - properties.horizontalAlign === "left" ? "general" : "left", - ) - } - disabled={!canEdit} - title={t("toolbar.align_left")} - > - - - - properties.onToggleHorizontalAlign( - properties.horizontalAlign === "center" ? "general" : "center", - ) - } - disabled={!canEdit} - title={t("toolbar.align_center")} - > - - - - properties.onToggleHorizontalAlign( - properties.horizontalAlign === "right" ? "general" : "right", - ) - } - disabled={!canEdit} - title={t("toolbar.align_right")} - > - - - properties.onToggleVerticalAlign("top")} - disabled={!canEdit} - title={t("toolbar.vertical_align_top")} - > - - - properties.onToggleVerticalAlign("center")} - disabled={!canEdit} - title={t("toolbar.vertical_align_middle")} - > - - - properties.onToggleVerticalAlign("bottom")} - disabled={!canEdit} - title={t("toolbar.vertical_align_bottom")} - > - - - { - properties.onToggleWrapText(!properties.wrapText); + anchorEl={fontColorButton} + open={fontColorPickerOpen} + anchorOrigin={{ vertical: "bottom", horizontal: "left" }} + transformOrigin={{ vertical: "top", horizontal: "left" }} + /> + { + if (color !== null) { + properties.onFillColorPicked(color); + } + setFillColorPickerOpen(false); }} - disabled={!canEdit} - title={t("toolbar.wrap_text")} - > - - - - - - - {/* View & Tools Group */} - - - properties.onToggleShowGridLines(!properties.showGridLines) - } - disabled={!canEdit} - title={t("toolbar.show_hide_grid_lines")} - > - {properties.showGridLines ? : } - - { - setNameManagerDialogOpen(true); + onClose={() => { + setFillColorPickerOpen(false); }} - disabled={!canEdit} - title={t("toolbar.name_manager")} - > - - - { - properties.onDownloadPNG(); + anchorEl={fillColorButton} + open={fillColorPickerOpen} + anchorOrigin={{ vertical: "bottom", horizontal: "left" }} + transformOrigin={{ vertical: "top", horizontal: "left" }} + /> + { + properties.onBorderChanged(border); }} - disabled={!canEdit} - title={t("toolbar.selected_png")} + onClose={() => { + setBorderPickerOpen(false); + }} + anchorEl={borderButton} + open={borderPickerOpen} + /> + { + setNameManagerDialogOpen(false); + }} + model={properties.nameManagerProperties} + /> + + {showRightArrow && ( + - - - - - { - properties.onTextColorPicked(color); - setFontColorPickerOpen(false); - }} - onClose={() => { - setFontColorPickerOpen(false); - }} - anchorEl={fontColorButton} - open={fontColorPickerOpen} - anchorOrigin={{ vertical: "bottom", horizontal: "left" }} - transformOrigin={{ vertical: "top", horizontal: "left" }} - /> - { - if (color !== null) { - properties.onFillColorPicked(color); - } - setFillColorPickerOpen(false); - }} - onClose={() => { - setFillColorPickerOpen(false); - }} - anchorEl={fillColorButton} - open={fillColorPickerOpen} - anchorOrigin={{ vertical: "bottom", horizontal: "left" }} - transformOrigin={{ vertical: "top", horizontal: "left" }} - /> - { - properties.onBorderChanged(border); - }} - onClose={() => { - setBorderPickerOpen(false); - }} - anchorEl={borderButton} - open={borderPickerOpen} - /> - { - setNameManagerDialogOpen(false); - }} - model={properties.nameManagerProperties} - /> - + + + )} + ); } -const ToolbarContainer = styled("div")` +const ToolbarWrapper = styled("div")` + position: relative; display: flex; - flex-shrink: 0; align-items: center; background: ${({ theme }) => theme.palette.background.paper}; height: ${TOOLBAR_HEIGHT}px; - line-height: ${TOOLBAR_HEIGHT}px; border-bottom: 1px solid ${({ theme }) => theme.palette.grey["300"]}; - font-family: Inter; border-radius: 4px 4px 0px 0px; +`; + +const ToolbarContainer = styled("div")` + display: flex; + flex: 1; + align-items: center; overflow-x: auto; padding: 0px 12px; gap: 4px; scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } `; type TypeButtonProperties = { $pressed: boolean }; @@ -617,4 +670,33 @@ const ButtonGroup = styled("div")({ gap: "4px", }); +type ScrollArrowProps = { $direction: "left" | "right" }; +const ScrollArrow = styled("button", { + shouldForwardProp: (prop) => prop !== "$direction", +})(({ $direction }) => ({ + position: "absolute", + top: "50%", + transform: "translateY(-50%)", + [$direction]: "0px", + zIndex: 10, + width: "24px", + height: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center", + backgroundColor: "white", + border: + $direction === "left" + ? `none; border-right: 1px solid ${theme.palette.grey["300"]};` + : `none; border-left: 1px solid ${theme.palette.grey["300"]};`, + cursor: "pointer", + "&:hover": { + backgroundColor: theme.palette.grey["100"], + }, + svg: { + width: "16px", + height: "16px", + }, +})); + export default Toolbar; diff --git a/webapp/IronCalc/src/locale/en_us.json b/webapp/IronCalc/src/locale/en_us.json index ee4ffeb..3e87d43 100644 --- a/webapp/IronCalc/src/locale/en_us.json +++ b/webapp/IronCalc/src/locale/en_us.json @@ -27,6 +27,8 @@ "vertical_align_top": "Align top", "selected_png": "Export Selected area as PNG", "wrap_text": "Wrap text", + "scroll_left": "Scroll left", + "scroll_right": "Scroll right", "format_menu": { "auto": "Auto", "number": "Number",