import { preventDefault, useApp } from '@tldraw/editor' import classNames from 'classnames' import React from 'react' import { track, useValue } from 'signia-react' import { useBreakpoint } from '../../hooks/useBreakpoint' import { useReadonly } from '../../hooks/useReadonly' import { ToolbarItem, useToolbarSchema } from '../../hooks/useToolbarSchema' import { ToolItem } from '../../hooks/useTools' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { ActionsMenu } from '../ActionsMenu' import { DuplicateButton } from '../DuplicateButton' import { MobileStylePanel } from '../MobileStylePanel' import { RedoButton } from '../RedoButton' import { TrashButton } from '../TrashButton' import { UndoButton } from '../UndoButton' import { Button } from '../primitives/Button' import * as M from '../primitives/DropdownMenu' import { kbdStr } from '../primitives/shared' import { ToggleToolLockedButton } from './ToggleToolLockedButton' /** @public */ export const Toolbar = function Toolbar() { const app = useApp() const msg = useTranslation() const breakpoint = useBreakpoint() const rMostRecentlyActiveDropdownItem = React.useRef(undefined) const isReadOnly = useReadonly() const toolbarItems = useToolbarSchema() const activeToolId = useValue('current tool id', () => app.currentToolId, [app]) const isHandTool = activeToolId === 'hand' const geoState = useValue('geo', () => (app.props ? app.props.geo : undefined), [app]) const showEditingTools = !isReadOnly const showExtraActions = !(isReadOnly || isHandTool) const getTitle = (item: ToolItem) => item.label ? `${msg(item.label)} ${item.kbd ? kbdStr(item.kbd) : ''}` : '' const activeToolbarItem = toolbarItems.find((item) => { return isActiveToolItem(item.toolItem, activeToolId, geoState) }) const { itemsInPanel, itemsInDropdown, dropdownFirstItem } = React.useMemo(() => { const itemsInPanel: ToolbarItem[] = [] const itemsInDropdown: ToolbarItem[] = [] let dropdownFirstItem: ToolbarItem | undefined const overflowIndex = Math.min(8, 5 + breakpoint) for (let i = 4; i < toolbarItems.length; i++) { const item = toolbarItems[i] if (i < overflowIndex) { // Items below the overflow index will always be in the panel itemsInPanel.push(item) } else { // Items above will be in the dropdown menu unless the item // is active (or was the most recently selected active item) if (item === activeToolbarItem) { // If the dropdown item is active, make it the dropdownFirstItem dropdownFirstItem = item } // Otherwise, add it to the items in dropdown menu itemsInDropdown.push(item) } } if (dropdownFirstItem) { // noop } else { // If we don't have a currently active dropdown item, use the most // recently active dropdown item as the current dropdown first item. // If haven't ever had a most recently active dropdown item, then // make the first item in the dropdown menu the most recently // active dropdown item. if (!rMostRecentlyActiveDropdownItem.current) { rMostRecentlyActiveDropdownItem.current = itemsInDropdown[0] } dropdownFirstItem = rMostRecentlyActiveDropdownItem.current // If the most recently active dropdown item is no longer in the // dropdown (because the breakpoint has changed) then make the // first item in the dropdown menu the most recently active // dropdown item. if (!itemsInDropdown.includes(dropdownFirstItem)) { dropdownFirstItem = itemsInDropdown[0] } } // We want this ref set to remember which item from the current // set of dropdown items was most recently active rMostRecentlyActiveDropdownItem.current = dropdownFirstItem if (itemsInDropdown.length <= 2) { itemsInPanel.push(...itemsInDropdown) itemsInDropdown.length = 0 } return { itemsInPanel, itemsInDropdown, dropdownFirstItem } }, [toolbarItems, activeToolbarItem, breakpoint]) return (
{!isReadOnly && (
{breakpoint < 5 && (
)}
)}
{/* Select / Hand */} {toolbarItems.slice(0, 2).map(({ toolItem }) => { return ( ) })} {showEditingTools && ( <> {/* Draw / Eraser */}
{toolbarItems.slice(2, 4).map(({ toolItem }) => ( ))} {/* Everything Else */}
{itemsInPanel.map(({ toolItem }) => ( ))} {/* Overflowing Shapes */} {itemsInDropdown.length ? ( <> {/* Last selected (or first) item from the overflow */} {/* The dropdown to select everything else */}
{breakpoint < 5 && !isReadOnly && (
)}
) } const OverflowToolsContent = track(function OverflowToolsContent({ toolbarItems, }: { toolbarItems: ToolbarItem[] }) { const msg = useTranslation() return (
{toolbarItems.map(({ toolItem: { id, meta, kbd, label, onSelect, icon } }) => { return ( ) })}
) }) function ToolbarButton({ item, title, isSelected, }: { item: ToolItem title: string isSelected: boolean }) { return (