Skip to content

Commit 5f4ae5b

Browse files
committed
[pr] nested dropdown
1 parent 33dce22 commit 5f4ae5b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1370
-425
lines changed

excalidraw-app/components/AppMainMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const AppMainMenu: React.FC<{
7777
</MainMenu.Item>
7878
)}
7979
<MainMenu.Separator />
80+
<MainMenu.DefaultItems.Preferences />
8081
<MainMenu.DefaultItems.ToggleTheme
8182
allowSystemTheme
8283
theme={props.theme}

packages/common/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const CLASSES = {
108108
CONVERT_ELEMENT_TYPE_POPUP: "ConvertElementTypePopup",
109109
SHAPE_ACTIONS_THEME_SCOPE: "shape-actions-theme-scope",
110110
FRAME_NAME: "frame-name",
111+
DROPDOWN_MENU_EVENT_WRAPPER: "dropdown-menu-event-wrapper",
111112
};
112113

113114
export const FONT_SIZES = {

packages/excalidraw/actions/shortcuts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export type ShortcutName =
5555
| "saveScene"
5656
| "imageExport"
5757
| "commandPalette"
58-
| "searchMenu";
58+
| "searchMenu"
59+
| "toolLock";
5960

6061
const shortcutMap: Record<ShortcutName, string[]> = {
6162
toggleTheme: [getShortcutKey("Shift+Alt+D")],
@@ -117,6 +118,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
117118
toggleShortcuts: [getShortcutKey("?")],
118119
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
119120
wrapSelectionInFrame: [],
121+
toolLock: [getShortcutKey("Q")],
120122
};
121123

122124
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {

packages/excalidraw/components/Actions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import clsx from "clsx";
22
import { useRef, useState } from "react";
3-
import * as Popover from "@radix-ui/react-popover";
3+
import { Popover } from "radix-ui";
44

55
import {
66
CLASSES,

packages/excalidraw/components/ColorPicker/ColorPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Popover from "@radix-ui/react-popover";
1+
import { Popover } from "radix-ui";
22
import clsx from "clsx";
33
import { useRef, useEffect } from "react";
44

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { KEYS } from "@excalidraw/common";
2+
3+
import { Excalidraw } from "../..";
4+
import { Keyboard } from "../../tests/helpers/ui";
5+
import { act, render } from "../../tests/test-utils";
6+
7+
describe("FontPicker", () => {
8+
it("should be able to open font picker", async () => {
9+
(global as any).ResizeObserver =
10+
(global as any).ResizeObserver ||
11+
class ResizeObserver {
12+
observe() {}
13+
unobserve() {}
14+
disconnect() {}
15+
};
16+
17+
const { queryByTestId } = await render(
18+
<Excalidraw handleKeyboardGlobally={true} />,
19+
);
20+
21+
Keyboard.keyPress(KEYS.T);
22+
23+
const fontPickerTrigger = queryByTestId("font-family-show-fonts");
24+
25+
expect(fontPickerTrigger).not.toBeNull();
26+
27+
act(() => {
28+
fontPickerTrigger!.click();
29+
});
30+
});
31+
});

packages/excalidraw/components/FontPicker/FontPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Popover from "@radix-ui/react-popover";
1+
import { Popover } from "radix-ui";
22
import clsx from "clsx";
33
import React, { useCallback, useMemo } from "react";
44

packages/excalidraw/components/FontPicker/FontPickerList.tsx

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ import { PropertiesPopover } from "../PropertiesPopover";
3030
import { QuickSearch } from "../QuickSearch";
3131
import { ScrollableList } from "../ScrollableList";
3232
import DropdownMenuGroup from "../dropdownMenu/DropdownMenuGroup";
33-
import DropdownMenuItem, {
33+
import {
3434
DropDownMenuItemBadgeType,
3535
DropDownMenuItemBadge,
3636
} from "../dropdownMenu/DropdownMenuItem";
37+
import MenuItemContent from "../dropdownMenu/DropdownMenuItemContent";
38+
import { getDropdownMenuItemClassName } from "../dropdownMenu/common";
3739
import {
3840
FontFamilyCodeIcon,
3941
FontFamilyHeadingIcon,
@@ -269,55 +271,88 @@ export const FontPickerList = React.memo(
269271
[filteredFonts, sceneFamilies],
270272
);
271273

272-
const renderFont = (font: FontDescriptor, index: number) => (
273-
<DropdownMenuItem
274-
key={font.value}
275-
icon={font.icon}
276-
value={font.value}
277-
order={index}
278-
textStyle={{
279-
fontFamily: getFontFamilyString({ fontFamily: font.value }),
280-
}}
281-
hovered={font.value === hoveredFont?.value}
282-
selected={font.value === selectedFontFamily}
283-
// allow to tab between search and selected font
284-
tabIndex={font.value === selectedFontFamily ? 0 : -1}
285-
onClick={(e) => {
286-
wrappedOnSelect(Number(e.currentTarget.value));
287-
}}
288-
onMouseMove={() => {
289-
if (hoveredFont?.value !== font.value) {
290-
onHover(font.value);
291-
}
292-
}}
293-
badge={
294-
font.badge && (
295-
<DropDownMenuItemBadge type={font.badge.type}>
296-
{font.badge.placeholder}
297-
</DropDownMenuItemBadge>
298-
)
274+
const FontPickerListItem = ({
275+
font,
276+
order,
277+
}: {
278+
font: FontDescriptor;
279+
order: number;
280+
}) => {
281+
const ref = useRef<HTMLButtonElement>(null);
282+
const isHovered = font.value === hoveredFont?.value;
283+
const isSelected = font.value === selectedFontFamily;
284+
285+
useEffect(() => {
286+
if (!isHovered) {
287+
return;
299288
}
300-
>
301-
{font.text}
302-
</DropdownMenuItem>
303-
);
289+
if (order === 0) {
290+
// scroll into the first item differently, so it's visible what is above (i.e. group title)
291+
ref.current?.scrollIntoView?.({ block: "end" });
292+
} else {
293+
ref.current?.scrollIntoView?.({ block: "nearest" });
294+
}
295+
}, [isHovered, order]);
296+
297+
return (
298+
<button
299+
ref={ref}
300+
type="button"
301+
value={font.value}
302+
className={getDropdownMenuItemClassName("", isSelected, isHovered)}
303+
title={font.text}
304+
// allow to tab between search and selected font
305+
tabIndex={isSelected ? 0 : -1}
306+
onClick={(e) => {
307+
wrappedOnSelect(Number(e.currentTarget.value));
308+
}}
309+
onMouseMove={() => {
310+
if (hoveredFont?.value !== font.value) {
311+
onHover(font.value);
312+
}
313+
}}
314+
>
315+
<MenuItemContent
316+
icon={font.icon}
317+
badge={
318+
font.badge && (
319+
<DropDownMenuItemBadge type={font.badge.type}>
320+
{font.badge.placeholder}
321+
</DropDownMenuItemBadge>
322+
)
323+
}
324+
textStyle={{
325+
fontFamily: getFontFamilyString({ fontFamily: font.value }),
326+
}}
327+
>
328+
{font.text}
329+
</MenuItemContent>
330+
</button>
331+
);
332+
};
304333

305334
const groups = [];
306335

307336
if (sceneFilteredFonts.length) {
308337
groups.push(
309338
<DropdownMenuGroup title={t("fontList.sceneFonts")} key="group_1">
310-
{sceneFilteredFonts.map(renderFont)}
339+
{sceneFilteredFonts.map((font, index) => (
340+
<FontPickerListItem key={font.value} font={font} order={index} />
341+
))}
311342
</DropdownMenuGroup>,
312343
);
313344
}
314345

315346
if (availableFilteredFonts.length) {
316347
groups.push(
317348
<DropdownMenuGroup title={t("fontList.availableFonts")} key="group_2">
318-
{availableFilteredFonts.map((font, index) =>
319-
renderFont(font, index + sceneFilteredFonts.length),
320-
)}
349+
{availableFilteredFonts.map((font, index) => (
350+
<FontPickerListItem
351+
key={font.value}
352+
font={font}
353+
order={index + sceneFilteredFonts.length}
354+
/>
355+
))}
321356
</DropdownMenuGroup>,
322357
);
323358
}

packages/excalidraw/components/FontPicker/FontPickerTrigger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Popover from "@radix-ui/react-popover";
1+
import { Popover } from "radix-ui";
22

33
import { MOBILE_ACTION_BUTTON_BG } from "@excalidraw/common";
44

packages/excalidraw/components/IconPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Popover from "@radix-ui/react-popover";
1+
import { Popover } from "radix-ui";
22
import clsx from "clsx";
33
import React, { useEffect } from "react";
44

0 commit comments

Comments
 (0)