From ecbaeb17013d963df5d513149cda12861d382188 Mon Sep 17 00:00:00 2001 From: dwelle <5153846+dwelle@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:30:26 +0200 Subject: [PATCH] add Preferences default menu item --- excalidraw-app/components/AppMainMenu.tsx | 1 + packages/excalidraw/actions/shortcuts.ts | 4 +- packages/excalidraw/components/Actions.tsx | 2 +- .../components/FontPicker/FontPickerList.tsx | 29 ++-- .../FontPicker/FontPickerListItem.tsx | 151 ++++++++++++++++++ packages/excalidraw/components/HelpDialog.tsx | 5 +- .../components/TTDDialog/TTDDialogTrigger.tsx | 2 +- .../components/dropdownMenu/DropdownMenu.scss | 4 +- .../components/dropdownMenu/DropdownMenu.tsx | 4 +- .../dropdownMenu/DropdownMenuContent.tsx | 6 +- .../dropdownMenu/DropdownMenuItem.tsx | 26 +-- .../dropdownMenu/DropdownMenuItemContent.tsx | 11 +- .../dropdownMenu/DropdownMenuSubItem.tsx | 7 +- .../dropdownMenu/DropdownMenuTrigger.tsx | 3 +- packages/excalidraw/components/icons.tsx | 18 +++ .../components/main-menu/DefaultItems.tsx | 85 +++++++++- packages/excalidraw/locales/en.json | 4 +- 17 files changed, 306 insertions(+), 56 deletions(-) create mode 100644 packages/excalidraw/components/FontPicker/FontPickerListItem.tsx diff --git a/excalidraw-app/components/AppMainMenu.tsx b/excalidraw-app/components/AppMainMenu.tsx index cd0aca268..2a88b6fee 100644 --- a/excalidraw-app/components/AppMainMenu.tsx +++ b/excalidraw-app/components/AppMainMenu.tsx @@ -37,6 +37,7 @@ export const AppMainMenu: React.FC<{ )} + diff --git a/packages/excalidraw/actions/shortcuts.ts b/packages/excalidraw/actions/shortcuts.ts index 1a13f1703..f3e1971b9 100644 --- a/packages/excalidraw/actions/shortcuts.ts +++ b/packages/excalidraw/actions/shortcuts.ts @@ -54,7 +54,8 @@ export type ShortcutName = | "saveScene" | "imageExport" | "commandPalette" - | "searchMenu"; + | "searchMenu" + | "toolLock"; const shortcutMap: Record = { toggleTheme: [getShortcutKey("Shift+Alt+D")], @@ -116,6 +117,7 @@ const shortcutMap: Record = { toggleShortcuts: [getShortcutKey("?")], searchMenu: [getShortcutKey("CtrlOrCmd+F")], wrapSelectionInFrame: [], + toolLock: [getShortcutKey("Q")], }; export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => { diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index 6a884ec81..12f724b08 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -451,10 +451,10 @@ export const ShapesSwitcher = ({ app.onMagicframeToolSelect()} icon={MagicIcon} + badge={AI} data-testid="toolbar-magicframe" > {t("toolBar.magicframe")} - AI )} diff --git a/packages/excalidraw/components/FontPicker/FontPickerList.tsx b/packages/excalidraw/components/FontPicker/FontPickerList.tsx index 6faab2917..e0b67a7fe 100644 --- a/packages/excalidraw/components/FontPicker/FontPickerList.tsx +++ b/packages/excalidraw/components/FontPicker/FontPickerList.tsx @@ -25,10 +25,6 @@ import { PropertiesPopover } from "../PropertiesPopover"; import { QuickSearch } from "../QuickSearch"; import { ScrollableList } from "../ScrollableList"; import DropdownMenuGroup from "../dropdownMenu/DropdownMenuGroup"; -import DropdownMenuItem, { - DropDownMenuItemBadgeType, - DropDownMenuItemBadge, -} from "../dropdownMenu/DropdownMenuItem"; import { FontFamilyCodeIcon, FontFamilyHeadingIcon, @@ -36,8 +32,15 @@ import { FreedrawIcon, } from "../icons"; +import { Ellipsify } from "../Ellipsify"; + import { fontPickerKeyHandler } from "./keyboardNavHandlers"; +import { + FontPickerListItem, + FontPickerListItemBadgeType, +} from "./FontPickerListItem"; + import type { JSX } from "react"; export interface FontDescriptor { @@ -46,7 +49,7 @@ export interface FontDescriptor { text: string; deprecated?: true; badge?: { - type: ValueOf; + type: ValueOf; placeholder: string; }; } @@ -112,7 +115,7 @@ export const FontPickerList = React.memo( Object.assign(fontDescriptor, { deprecated: metadata.deprecated, badge: { - type: DropDownMenuItemBadgeType.RED, + type: FontPickerListItemBadgeType.RED, placeholder: t("fontList.badge.old"), }, }); @@ -227,7 +230,7 @@ export const FontPickerList = React.memo( ); const renderFont = (font: FontDescriptor, index: number) => ( - { - onSelect(Number(e.currentTarget.value)); + onSelect={() => { + onSelect(font.value); }} onMouseMove={() => { if (hoveredFont?.value !== font.value) { @@ -248,13 +251,13 @@ export const FontPickerList = React.memo( } }} > - {font.text} + {font.text} {font.badge && ( - + {font.badge.placeholder} - + )} - + ); const groups = []; diff --git a/packages/excalidraw/components/FontPicker/FontPickerListItem.tsx b/packages/excalidraw/components/FontPicker/FontPickerListItem.tsx new file mode 100644 index 000000000..bf2757d1d --- /dev/null +++ b/packages/excalidraw/components/FontPicker/FontPickerListItem.tsx @@ -0,0 +1,151 @@ +import React, { useEffect, useRef } from "react"; + +import { THEME } from "@excalidraw/common"; + +import type { ValueOf } from "@excalidraw/common/utility-types"; + +import { Button } from "../Button"; + +import { useExcalidrawAppState } from "../App"; + +import { useDevice } from "../App"; + +import { getDropdownMenuItemClassName } from "../dropdownMenu/common"; + +import type { JSX } from "react"; + +const MenuItemContent = ({ + textStyle, + icon, + shortcut, + children, +}: { + icon?: React.ReactNode; + shortcut?: string; + textStyle?: React.CSSProperties; + children: React.ReactNode; +}) => { + const device = useDevice(); + return ( + <> + {icon &&
{icon}
} +
+ {children} +
+ {shortcut && !device.editor.isMobile && ( +
{shortcut}
+ )} + + ); +}; + +export const FontPickerListItem = ({ + icon, + value, + order, + children, + shortcut, + className, + hovered, + selected, + textStyle, + onSelect, + onClick, + ...rest +}: { + icon?: JSX.Element; + value?: string | number | undefined; + order?: number; + onSelect: (event: React.MouseEvent) => void; + children: React.ReactNode; + shortcut?: string; + hovered?: boolean; + selected?: boolean; + textStyle?: React.CSSProperties; + className?: string; +} & Omit, "onSelect">) => { + const ref = useRef(null); + + useEffect(() => { + if (hovered) { + if (order === 0) { + // scroll into the first item differently, so it's visible what is above (i.e. group title) + ref.current?.scrollIntoView({ block: "end" }); + } else { + ref.current?.scrollIntoView({ block: "nearest" }); + } + } + }, [hovered, order]); + + return ( +
+ +
+ ); +}; +FontPickerListItem.displayName = "FontPickerListItem"; + +export const FontPickerListItemBadgeType = { + GREEN: "green", + RED: "red", + BLUE: "blue", +} as const; + +export const FontPickerListItemBadge = ({ + type = FontPickerListItemBadgeType.BLUE, + children, +}: { + type?: ValueOf; + children: React.ReactNode; +}) => { + const { theme } = useExcalidrawAppState(); + const style = { + display: "inline-flex", + marginLeft: "auto", + padding: "2px 4px", + borderRadius: 6, + fontSize: 9, + fontFamily: "Cascadia, monospace", + border: theme === THEME.LIGHT ? "1.5px solid white" : "none", + }; + + switch (type) { + case FontPickerListItemBadgeType.GREEN: + Object.assign(style, { + backgroundColor: "var(--background-color-badge)", + color: "var(--color-badge)", + }); + break; + case FontPickerListItemBadgeType.RED: + Object.assign(style, { + backgroundColor: "pink", + color: "darkred", + }); + break; + case FontPickerListItemBadgeType.BLUE: + default: + Object.assign(style, { + background: "var(--color-promo)", + color: "var(--color-surface-lowest)", + }); + } + + return ( +
+ {children} +
+ ); +}; +FontPickerListItemBadge.displayName = "DropdownMenuItemBadge"; + +FontPickerListItem.Badge = FontPickerListItemBadge; diff --git a/packages/excalidraw/components/HelpDialog.tsx b/packages/excalidraw/components/HelpDialog.tsx index 2cc8de0b7..1b7548303 100644 --- a/packages/excalidraw/components/HelpDialog.tsx +++ b/packages/excalidraw/components/HelpDialog.tsx @@ -238,7 +238,10 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => { shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]} isOr={true} /> - + AI} > {children ?? t("labels.textToDiagram")} - AI ); diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss index 6c3c973f3..66a202d24 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss @@ -7,6 +7,7 @@ .dropdown-menu { max-width: 16rem; + margin-top: 0.25rem; &__submenu-trigger { &[aria-expanded="true"] { @@ -60,6 +61,7 @@ .dropdown-menu-item-base { display: flex; + padding: 0 0.625rem; column-gap: 0.625rem; font-size: 0.875rem; color: var(--color-on-surface); @@ -125,7 +127,7 @@ border: 1px solid transparent; align-items: center; cursor: pointer; - border-radius: var(--border-radius-sm); + border-radius: var(--border-radius-md); flex: 1 0 auto; @media screen and (min-width: 1921px) { diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx index bef1a1b96..0a6b42b71 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx @@ -1,5 +1,7 @@ import React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; + import DropdownMenuContent from "./DropdownMenuContent"; import DropdownMenuGroup from "./DropdownMenuGroup"; import DropdownMenuItem from "./DropdownMenuItem"; @@ -14,8 +16,6 @@ import { import "./DropdownMenu.scss"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; - const DropdownMenu = ({ children, open, diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx index da05d3731..82c1821cf 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useRef } from "react"; import { EVENT, KEYS } from "@excalidraw/common"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; + import { useOutsideClick } from "../../hooks/useOutsideClick"; import { useStable } from "../../hooks/useStable"; import { useDevice } from "../App"; @@ -11,8 +13,6 @@ import Stack from "../Stack"; import { DropdownMenuContentPropsContext } from "./common"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; - const MenuContent = ({ children, onClickOutside, @@ -89,7 +89,7 @@ const MenuContent = ({ ) : ( {children} diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx index f24f1c45b..5d45a1c3d 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useRef } from "react"; import { THEME } from "@excalidraw/common"; @@ -22,53 +22,41 @@ import type { JSX } from "react"; const DropdownMenuItem = ({ icon, value, + badge, order, children, shortcut, className, - hovered, selected, - textStyle, onSelect, onClick, ...rest }: { icon?: JSX.Element; + badge?: React.ReactNode; value?: string | number | undefined; order?: number; onSelect?: (event: Event) => void; children: React.ReactNode; shortcut?: string; - hovered?: boolean; + selected?: boolean; - textStyle?: React.CSSProperties; + className?: string; } & Omit, "onSelect">) => { const handleClick = useHandleDropdownMenuItemClick(onClick, onSelect); const ref = useRef(null); - useEffect(() => { - if (hovered) { - if (order === 0) { - // scroll into the first item differently, so it's visible what is above (i.e. group title) - ref.current?.scrollIntoView({ block: "end" }); - } else { - ref.current?.scrollIntoView({ block: "nearest" }); - } - } - }, [hovered, order]); - return ( diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx index aea13230b..5a7f4f2ed 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx @@ -2,25 +2,24 @@ import { useDevice } from "../App"; import { Ellipsify } from "../Ellipsify"; -import type { JSX } from "react"; - const MenuItemContent = ({ - textStyle, icon, + badge, shortcut, children, }: { - icon?: JSX.Element; + icon?: React.ReactNode; shortcut?: string; - textStyle?: React.CSSProperties; children: React.ReactNode; + badge?: React.ReactNode; }) => { const device = useDevice(); return ( <> {icon &&
{icon}
} -
+
{children} + {badge}
{shortcut && !device.editor.isMobile && (
{shortcut}
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuSubItem.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuSubItem.tsx index 0f760509f..c580ead69 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuSubItem.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuSubItem.tsx @@ -8,8 +8,6 @@ import { useHandleDropdownMenuItemClick, } from "./common"; -import type { JSX } from "react"; - const DropdownMenuSubItem = ({ icon, onSelect, @@ -18,7 +16,7 @@ const DropdownMenuSubItem = ({ className, ...rest }: { - icon?: JSX.Element; + icon?: React.ReactNode; onSelect: (event: Event) => void; children: React.ReactNode; shortcut?: string; @@ -30,8 +28,7 @@ const DropdownMenuSubItem = ({