feat: introduce font picker (#8012)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Marcel Mraz 2024-07-25 18:55:55 +02:00 committed by GitHub
parent 4c5408263c
commit 62228e0bbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
120 changed files with 3390 additions and 1106 deletions

View file

@ -4,7 +4,7 @@
.dropdown-menu {
position: absolute;
top: 100%;
margin-top: 0.25rem;
margin-top: 0.5rem;
&--mobile {
left: 0;
@ -35,21 +35,69 @@
.dropdown-menu-item-base {
display: flex;
padding: 0 0.625rem;
column-gap: 0.625rem;
font-size: 0.875rem;
color: var(--color-on-surface);
width: 100%;
box-sizing: border-box;
font-weight: normal;
font-weight: 400;
font-family: inherit;
}
&.manual-hover {
// disable built-in hover due to keyboard navigation
.dropdown-menu-item {
&:hover {
background-color: transparent;
}
&--hovered {
background-color: var(--button-hover-bg) !important;
}
&--selected {
background-color: var(--color-primary-light) !important;
}
}
}
&.fonts {
margin-top: 1rem;
// display max 7 items per list, where each has 2rem (2.25) height and 1px margin top & bottom
// count in 2 groups, where each allocates 1.3*0.75rem font-size and 0.5rem margin bottom, plus one extra 1rem margin top
max-height: calc(7 * (2rem + 2px) + 2 * (0.5rem + 1.3 * 0.75rem) + 1rem);
@media screen and (min-width: 1921px) {
max-height: calc(
7 * (2.25rem + 2px) + 2 * (0.5rem + 1.3 * 0.75rem) + 1rem
);
}
.dropdown-menu-item-base {
display: inline-flex;
}
.dropdown-menu-group:not(:first-child) {
margin-top: 1rem;
}
.dropdown-menu-group-title {
font-size: 0.75rem;
text-align: left;
font-weight: 400;
margin: 0 0 0.5rem;
line-height: 1.3;
}
}
.dropdown-menu-item {
height: 2rem;
margin: 1px;
padding: 0 0.5rem;
width: calc(100% - 2px);
background-color: transparent;
border: 1px solid transparent;
align-items: center;
height: 2rem;
cursor: pointer;
border-radius: var(--border-radius-md);
@ -57,11 +105,6 @@
height: 2.25rem;
}
&--selected {
background: var(--color-primary-light);
--icon-fill-color: var(--color-primary-darker);
}
&__text {
display: flex;
align-items: center;
@ -83,6 +126,11 @@
}
}
&--selected {
background: var(--color-primary-light);
--icon-fill-color: var(--color-primary-darker);
}
&:hover {
background-color: var(--button-hover-bg);
text-decoration: none;

View file

@ -1,37 +1,62 @@
import React from "react";
import React, { useEffect, useRef } from "react";
import {
getDropdownMenuItemClassName,
useHandleDropdownMenuItemClick,
} from "./common";
import MenuItemContent from "./DropdownMenuItemContent";
import { useExcalidrawAppState } from "../App";
import { THEME } from "../../constants";
import type { ValueOf } from "../../utility-types";
const DropdownMenuItem = ({
icon,
onSelect,
value,
order,
children,
shortcut,
className,
hovered,
selected,
textStyle,
onSelect,
onClick,
...rest
}: {
icon?: JSX.Element;
onSelect: (event: Event) => void;
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<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
const handleClick = useHandleDropdownMenuItemClick(rest.onClick, onSelect);
const handleClick = useHandleDropdownMenuItemClick(onClick, onSelect);
const ref = useRef<HTMLButtonElement>(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 (
<button
{...rest}
ref={ref}
value={value}
onClick={handleClick}
type="button"
className={getDropdownMenuItemClassName(className, selected)}
className={getDropdownMenuItemClassName(className, selected, hovered)}
title={rest.title ?? rest["aria-label"]}
>
<MenuItemContent icon={icon} shortcut={shortcut}>
<MenuItemContent textStyle={textStyle} icon={icon} shortcut={shortcut}>
{children}
</MenuItemContent>
</button>
@ -39,24 +64,53 @@ const DropdownMenuItem = ({
};
DropdownMenuItem.displayName = "DropdownMenuItem";
export const DropDownMenuItemBadgeType = {
GREEN: "green",
RED: "red",
BLUE: "blue",
} as const;
export const DropDownMenuItemBadge = ({
type = DropDownMenuItemBadgeType.BLUE,
children,
}: {
type?: ValueOf<typeof DropDownMenuItemBadgeType>;
children: React.ReactNode;
}) => {
return (
<div
style={{
display: "inline-flex",
marginLeft: "auto",
padding: "2px 4px",
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 DropDownMenuItemBadgeType.GREEN:
Object.assign(style, {
backgroundColor: "var(--background-color-badge)",
color: "var(--color-badge)",
});
break;
case DropDownMenuItemBadgeType.RED:
Object.assign(style, {
backgroundColor: "pink",
color: "darkred",
});
break;
case DropDownMenuItemBadgeType.BLUE:
default:
Object.assign(style, {
background: "var(--color-promo)",
color: "var(--color-surface-lowest)",
borderRadius: 6,
fontSize: 9,
fontFamily: "Cascadia, monospace",
}}
>
});
}
return (
<div className="DropDownMenuItemBadge" style={style}>
{children}
</div>
);

View file

@ -1,19 +1,23 @@
import { useDevice } from "../App";
const MenuItemContent = ({
textStyle,
icon,
shortcut,
children,
}: {
icon?: JSX.Element;
shortcut?: string;
textStyle?: React.CSSProperties;
children: React.ReactNode;
}) => {
const device = useDevice();
return (
<>
<div className="dropdown-menu-item__icon">{icon}</div>
<div className="dropdown-menu-item__text">{children}</div>
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
<div style={textStyle} className="dropdown-menu-item__text">
{children}
</div>
{shortcut && !device.editor.isMobile && (
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
)}

View file

@ -9,9 +9,11 @@ export const DropdownMenuContentPropsContext = React.createContext<{
export const getDropdownMenuItemClassName = (
className = "",
selected = false,
hovered = false,
) => {
return `dropdown-menu-item dropdown-menu-item-base ${className} ${
selected ? "dropdown-menu-item--selected" : ""
return `dropdown-menu-item dropdown-menu-item-base ${className}
${selected ? "dropdown-menu-item--selected" : ""} ${
hovered ? "dropdown-menu-item--hovered" : ""
}`.trim();
};