fix styling

This commit is contained in:
Ryan Di
2025-09-22 18:44:57 +10:00
parent 9b68439712
commit 620c84383d
17 changed files with 182 additions and 107 deletions

View File

@@ -536,3 +536,10 @@ export enum UserIdleState {
export const LINE_POLYGON_POINT_MERGE_DISTANCE = 20;
export const DOUBLE_TAP_POSITION_THRESHOLD = 35;
// glass background for mobile action buttons
export const MOBILE_ACTION_BUTTON_BG = {
background: "rgba(255, 255, 255, 0.1)",
backdropFilter: "blur(20px)",
WebkitBackdropFilter: "blur(20px)",
} as const;

View File

@@ -1,4 +1,8 @@
import { KEYS, updateActiveTool } from "@excalidraw/common";
import {
KEYS,
MOBILE_ACTION_BUTTON_BG,
updateActiveTool,
} from "@excalidraw/common";
import { getNonDeletedElements } from "@excalidraw/element";
import { fixBindingsAfterDeletion } from "@excalidraw/element";
@@ -326,6 +330,11 @@ export const actionDeleteSelected = register({
disabled={
!isSomeElementSelected(getNonDeletedElements(elements), appState)
}
style={{
...(appState.stylesPanelMode === "mobile"
? MOBILE_ACTION_BUTTON_BG
: {}),
}}
/>
),
});

View File

@@ -1,6 +1,7 @@
import {
DEFAULT_GRID_SIZE,
KEYS,
MOBILE_ACTION_BUTTON_BG,
arrayToMap,
getShortcutKey,
} from "@excalidraw/common";
@@ -118,6 +119,11 @@ export const actionDuplicateSelection = register({
disabled={
!isSomeElementSelected(getNonDeletedElements(elements), appState)
}
style={{
...(appState.stylesPanelMode === "mobile"
? MOBILE_ACTION_BUTTON_BG
: {}),
}}
/>
),
});

View File

@@ -1,4 +1,10 @@
import { isWindows, KEYS, matchKey, arrayToMap } from "@excalidraw/common";
import {
isWindows,
KEYS,
matchKey,
arrayToMap,
MOBILE_ACTION_BUTTON_BG,
} from "@excalidraw/common";
import { CaptureUpdateAction } from "@excalidraw/element";
@@ -67,7 +73,7 @@ export const createUndoAction: ActionCreator = (history) => ({
),
keyTest: (event) =>
event[KEYS.CTRL_OR_CMD] && matchKey(event, KEYS.Z) && !event.shiftKey,
PanelComponent: ({ updateData, data }) => {
PanelComponent: ({ appState, updateData, data }) => {
const { isUndoStackEmpty } = useEmitter<HistoryChangedEvent>(
history.onHistoryChangedEmitter,
new HistoryChangedEvent(
@@ -85,6 +91,11 @@ export const createUndoAction: ActionCreator = (history) => ({
size={data?.size || "medium"}
disabled={isUndoStackEmpty}
data-testid="button-undo"
style={{
...(appState.stylesPanelMode === "mobile"
? MOBILE_ACTION_BUTTON_BG
: {}),
}}
/>
);
},
@@ -103,7 +114,7 @@ export const createRedoAction: ActionCreator = (history) => ({
keyTest: (event) =>
(event[KEYS.CTRL_OR_CMD] && event.shiftKey && matchKey(event, KEYS.Z)) ||
(isWindows && event.ctrlKey && !event.shiftKey && matchKey(event, KEYS.Y)),
PanelComponent: ({ updateData, data }) => {
PanelComponent: ({ appState, updateData, data }) => {
const { isRedoStackEmpty } = useEmitter(
history.onHistoryChangedEmitter,
new HistoryChangedEvent(
@@ -121,6 +132,11 @@ export const createRedoAction: ActionCreator = (history) => ({
size={data?.size || "medium"}
disabled={isRedoStackEmpty}
data-testid="button-redo"
style={{
...(appState.stylesPanelMode === "mobile"
? MOBILE_ACTION_BUTTON_BG
: {}),
}}
/>
);
},

View File

@@ -106,6 +106,7 @@
justify-content: center;
align-items: center;
min-height: 2.5rem;
pointer-events: auto;
--default-button-size: 2rem;
@@ -114,7 +115,6 @@
height: 1.625rem;
border: none;
border-radius: var(--border-radius-lg);
background: transparent;
color: var(--color-on-surface);
cursor: pointer;
display: flex;
@@ -122,21 +122,17 @@
justify-content: center;
transition: all 0.2s ease;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
svg {
width: 1rem;
height: 1rem;
flex: 0 0 auto;
}
&:hover {
background: var(--button-hover-bg, var(--island-bg-color));
border-color: var(
--button-hover-border,
var(--button-border, var(--default-border-color))
);
}
&:active {
&.active {
background: var(--button-active-bg, var(--island-bg-color));
border-color: var(--button-active-border, var(--color-primary-darkest));
}
@@ -168,9 +164,15 @@
}
}
.ToolIcon {
.ToolIcon__icon {
width: 1.625rem;
height: 1.625rem;
&:hover {
background-color: transparent;
}
}
}
}

View File

@@ -328,7 +328,7 @@ const CombinedShapeProperties = ({
hasBackground(element.type) && !isTransparent(element.backgroundColor),
);
const showShowCombinedProperties =
const shouldShowCombinedProperties =
showFillIcons ||
hasStrokeWidth(appState.activeTool.type) ||
targetElements.some((element) => hasStrokeWidth(element.type)) ||
@@ -337,14 +337,16 @@ const CombinedShapeProperties = ({
canChangeRoundness(appState.activeTool.type) ||
targetElements.some((element) => canChangeRoundness(element.type));
if (!showShowCombinedProperties) {
const isOpen = appState.openPopup === "compactStrokeStyles";
if (!shouldShowCombinedProperties) {
return null;
}
return (
<div className="compact-action-item">
<Popover.Root
open={appState.openPopup === "compactStrokeStyles"}
open={isOpen}
onOpenChange={(open) => {
if (open) {
setAppState({ openPopup: "compactStrokeStyles" });
@@ -356,24 +358,23 @@ const CombinedShapeProperties = ({
<Popover.Trigger asChild>
<button
type="button"
className="compact-action-button properties-trigger"
className={clsx("compact-action-button properties-trigger", {
active: isOpen,
})}
title={t("labels.stroke")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setAppState({
openPopup:
appState.openPopup === "compactStrokeStyles"
? null
: "compactStrokeStyles",
openPopup: isOpen ? null : "compactStrokeStyles",
});
}}
>
{adjustmentsIcon}
</button>
</Popover.Trigger>
{appState.openPopup === "compactStrokeStyles" && (
{isOpen && (
<PropertiesPopover
className={PROPERTIES_CLASSES}
container={container}
@@ -428,6 +429,7 @@ const CombinedArrowProperties = ({
const showShowArrowProperties =
toolIsArrow(appState.activeTool.type) ||
targetElements.some((element) => toolIsArrow(element.type));
const isOpen = appState.openPopup === "compactArrowProperties";
if (!showShowArrowProperties) {
return null;
@@ -436,7 +438,7 @@ const CombinedArrowProperties = ({
return (
<div className="compact-action-item">
<Popover.Root
open={appState.openPopup === "compactArrowProperties"}
open={isOpen}
onOpenChange={(open) => {
if (open) {
setAppState({ openPopup: "compactArrowProperties" });
@@ -448,17 +450,16 @@ const CombinedArrowProperties = ({
<Popover.Trigger asChild>
<button
type="button"
className="compact-action-button properties-trigger"
className={clsx("compact-action-button properties-trigger", {
active: isOpen,
})}
title={t("labels.arrowtypes")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setAppState({
openPopup:
appState.openPopup === "compactArrowProperties"
? null
: "compactArrowProperties",
openPopup: isOpen ? null : "compactArrowProperties",
});
}}
>
@@ -492,7 +493,7 @@ const CombinedArrowProperties = ({
})()}
</button>
</Popover.Trigger>
{appState.openPopup === "compactArrowProperties" && (
{isOpen && (
<PropertiesPopover
container={container}
className="properties-content"
@@ -523,11 +524,12 @@ const CombinedTextProperties = ({
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
}) => {
const { saveCaretPosition, restoreCaretPosition } = useTextEditorFocus();
const isOpen = appState.openPopup === "compactTextProperties";
return (
<div className="compact-action-item">
<Popover.Root
open={appState.openPopup === "compactTextProperties"}
open={isOpen}
onOpenChange={(open) => {
if (open) {
if (appState.editingTextElement) {
@@ -545,13 +547,15 @@ const CombinedTextProperties = ({
<Popover.Trigger asChild>
<button
type="button"
className="compact-action-button properties-trigger"
className={clsx("compact-action-button properties-trigger", {
active: isOpen,
})}
title={t("labels.textAlign")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (appState.openPopup === "compactTextProperties") {
if (isOpen) {
setAppState({ openPopup: null });
} else {
if (appState.editingTextElement) {
@@ -629,6 +633,7 @@ const CombinedExtraActions = ({
}
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
const isOpen = appState.openPopup === "compactOtherProperties";
if (isEditingTextOrNewElement || targetElements.length === 0) {
return null;
@@ -637,7 +642,7 @@ const CombinedExtraActions = ({
return (
<div className="compact-action-item">
<Popover.Root
open={appState.openPopup === "compactOtherProperties"}
open={isOpen}
onOpenChange={(open) => {
if (open) {
setAppState({ openPopup: "compactOtherProperties" });
@@ -649,23 +654,22 @@ const CombinedExtraActions = ({
<Popover.Trigger asChild>
<button
type="button"
className="compact-action-button properties-trigger"
className={clsx("compact-action-button properties-trigger", {
active: isOpen,
})}
title={t("labels.actions")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setAppState({
openPopup:
appState.openPopup === "compactOtherProperties"
? null
: "compactOtherProperties",
openPopup: isOpen ? null : "compactOtherProperties",
});
}}
>
{DotsHorizontalIcon}
</button>
</Popover.Trigger>
{appState.openPopup === "compactOtherProperties" && (
{isOpen && (
<PropertiesPopover
className={PROPERTIES_CLASSES}
container={container}
@@ -798,12 +802,7 @@ export const CompactShapeActions = ({
<div className="compact-shape-actions">
{/* Stroke Color */}
{canChangeStrokeColor(appState, targetElements) && (
<div
className={clsx("compact-action-item")}
style={{
marginRight: 4,
}}
>
<div className={clsx("compact-action-item")}>
{renderAction("changeStrokeColor")}
</div>
)}
@@ -925,6 +924,7 @@ export const MobileShapeActions = ({
height: WIDTH * 1.75,
alignItems: "center",
gap: GAP,
pointerEvents: "none",
}}
ref={mobileActionsRef}
>

View File

@@ -7,6 +7,12 @@
}
}
.color-picker__title {
padding: 0 0.5rem;
font-size: 0.875rem;
text-align: left;
}
.color-picker__heading {
padding: 0 0.5rem;
font-size: 0.75rem;

View File

@@ -18,7 +18,7 @@ import { useExcalidrawContainer } from "../App";
import { ButtonSeparator } from "../ButtonSeparator";
import { activeEyeDropperAtom } from "../EyeDropper";
import { PropertiesPopover } from "../PropertiesPopover";
import { backgroundIcon, slashIcon, strokeIcon } from "../icons";
import { slashIcon, strokeIcon } from "../icons";
import {
saveCaretPosition,
restoreCaretPosition,
@@ -213,6 +213,10 @@ const ColorPickerPopupContent = ({
type={type}
elements={elements}
updateData={updateData}
showTitle={
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile"
}
>
{colorInputJSX}
</Picker>
@@ -272,20 +276,8 @@ const ColorPickerTrigger = ({
onClick={handleClick}
>
<div className="color-picker__button-outline">{!color && slashIcon}</div>
{compactMode && color && (
{compactMode && color && mode === "stroke" && (
<div className="color-picker__button-background">
{mode === "background" ? (
<span
style={{
color:
color && isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD)
? "#fff"
: "#111",
}}
>
{backgroundIcon}
</span>
) : (
<span
style={{
color:
@@ -296,7 +288,6 @@ const ColorPickerTrigger = ({
>
{strokeIcon}
</span>
)}
</div>
)}
</Popover.Trigger>

View File

@@ -37,6 +37,7 @@ interface PickerProps {
palette: ColorPaletteCustom;
updateData: (formData?: any) => void;
children?: React.ReactNode;
showTitle?: boolean;
onEyeDropperToggle: (force?: boolean) => void;
onEscape: (event: React.KeyboardEvent | KeyboardEvent) => void;
}
@@ -51,11 +52,20 @@ export const Picker = React.forwardRef(
palette,
updateData,
children,
showTitle,
onEyeDropperToggle,
onEscape,
}: PickerProps,
ref,
) => {
const title = showTitle
? type === "elementStroke"
? t("labels.stroke")
: type === "elementBackground"
? t("labels.background")
: null
: null;
const [customColors] = React.useState(() => {
if (type === "canvasBackground") {
return [];
@@ -154,6 +164,8 @@ export const Picker = React.forwardRef(
// to allow focusing by clicking but not by tabbing
tabIndex={-1}
>
{title && <div className="color-picker__title">{title}</div>}
{!!customColors.length && (
<div>
<PickerHeading>

View File

@@ -21,6 +21,16 @@ export const FontPickerTrigger = ({
}: FontPickerTriggerProps) => {
const setAppState = useExcalidrawSetAppState();
const compactStyle = compactMode
? {
background: "rgba(255, 255, 255, 0.1)",
backdropFilter: "blur(20px)",
WebkitBackdropFilter: "blur(20px)",
width: "1.625rem",
height: "1.625rem",
}
: {};
return (
<Popover.Trigger asChild>
<div data-openpopup="fontFamily" className="properties-trigger">
@@ -39,8 +49,7 @@ export const FontPickerTrigger = ({
}}
style={{
border: "none",
width: compactMode ? "1.625rem" : undefined,
height: compactMode ? "1.625rem" : undefined,
...compactStyle,
}}
/>
</div>

View File

@@ -18,7 +18,7 @@ type LockIconProps = {
export const HandButton = (props: LockIconProps) => {
return (
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx("Shape", { fillable: false, active: props.checked })}
type="radio"
icon={handIcon}
name="editor-current-shape"

View File

@@ -92,17 +92,8 @@ export const MobileMenu = ({
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 16,
}}
>
<MainMenuTunnel.Out />
</div>
{topRightUI ? topRightUI : <DefaultSidebarTriggerTunnel.Out />}
</div>
);

View File

@@ -39,6 +39,15 @@
.ToolIcon__icon {
width: 2rem;
height: 2rem;
&:hover {
background-color: transparent;
}
}
&.active {
background: var(--button-active-bg, var(--island-bg-color));
border-color: var(--button-active-border, var(--color-primary-darkest));
}
svg {

View File

@@ -190,7 +190,6 @@ export const MobileToolBar = ({
options={SELECTION_TOOLS}
activeTool={activeTool}
defaultOption={app.defaultSelectionTool}
className="Selection"
namePrefix="selectionType"
title={capitalizeString(t("toolBar.selection"))}
data-testid="toolbar-selection"
@@ -210,7 +209,9 @@ export const MobileToolBar = ({
{/* Free Draw */}
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx({
active: activeTool.type === "freedraw",
})}
type="radio"
icon={FreedrawIcon}
checked={activeTool.type === "freedraw"}
@@ -223,7 +224,9 @@ export const MobileToolBar = ({
{/* Eraser */}
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx({
active: activeTool.type === "eraser",
})}
type="radio"
icon={EraserIcon}
checked={activeTool.type === "eraser"}
@@ -296,7 +299,9 @@ export const MobileToolBar = ({
{/* Image */}
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx({
active: activeTool.type === "image",
})}
type="radio"
icon={ImageIcon}
checked={activeTool.type === "image"}
@@ -310,7 +315,9 @@ export const MobileToolBar = ({
{/* Text Tool */}
{showTextToolOutside && (
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx({
active: activeTool.type === "text",
})}
type="radio"
icon={TextIcon}
checked={activeTool.type === "text"}
@@ -325,7 +332,7 @@ export const MobileToolBar = ({
{/* Frame Tool */}
{showFrameToolOutside && (
<ToolButton
className={clsx("Shape", { fillable: false })}
className={clsx({ active: frameToolSelected })}
type="radio"
icon={frameToolIcon}
checked={frameToolSelected}

View File

@@ -48,11 +48,12 @@ export const ToolTypePopup = ({
const updatePosition = () => {
const triggerRect = triggerElement.getBoundingClientRect();
const panelRect = panelRef.current?.getBoundingClientRect();
const panelWidth = panelRect?.width ?? 0;
const panelHeight = panelRect?.height ?? 0;
setPanelPosition({
x: triggerRect.x - panelWidth / 2,
x: triggerRect.left - panelWidth / 2,
y: panelHeight + 8,
});
};
@@ -92,9 +93,9 @@ export const ToolTypePopup = ({
tabIndex={-1}
style={{
position: "fixed",
bottom: `${panelPosition.y}px`,
top: `${-10}px`,
left: `${panelPosition.x}px`,
zIndex: 2,
zIndex: 9999,
}}
className={CLASSES.CONVERT_ELEMENT_TYPE_POPUP}
>
@@ -161,7 +162,13 @@ export const ToolWithPopup = ({
<div style={{ position: "relative" }}>
<div ref={setTriggerRef}>
<ToolButton
className={clsx(className, { fillable })}
className={clsx(className, {
fillable,
active:
isActive ||
isPopupOpen ||
options.some((o) => o.type === activeTool.type),
})}
type="radio"
icon={displayedOption.icon}
checked={isActive}

View File

@@ -3,7 +3,7 @@
.excalidraw {
.dropdown-menu {
position: absolute;
top: 100%;
top: 2.5rem;
margin-top: 0.5rem;
&--placement-top {
@@ -14,32 +14,35 @@
}
&--mobile {
left: 0;
width: 100%;
row-gap: 0.75rem;
// When main menu is in the top toolbar, position relative to trigger
&.main-menu-dropdown {
position: fixed;
left: 1rem;
top: 3.5rem;
min-width: 280px;
max-width: calc(100vw - 2rem);
min-width: 232px;
max-width: calc(100vw - var(--editor-container-padding) * 2);
margin-top: 0;
margin-bottom: 0;
z-index: var(--zIndex-layerUI);
@media screen and (orientation: landscape) {
max-width: 232px;
}
}
.dropdown-menu-container {
padding: 8px 8px;
box-sizing: border-box;
// background-color: var(--island-bg-color);
max-height: calc(
100svh - var(--editor-container-padding) * 2 - 2.25rem
);
box-shadow: var(--shadow-island);
border-radius: var(--border-radius-lg);
position: relative;
transition: box-shadow 0.5s ease-in-out;
display: flex;
flex-direction: column;
overflow-y: auto;
&.zen-mode {
box-shadow: none;
@@ -49,7 +52,7 @@
.dropdown-menu-container {
background-color: var(--island-bg-color);
max-height: calc(100vh - 150px);
overflow-y: auto;
--gap: 2;
}

View File

@@ -246,7 +246,7 @@ body.excalidraw-cursor-resize * {
left: 50%;
transform: translateX(-50%);
--bar-padding: calc(4 * var(--space-factor));
z-index: 4;
z-index: 3;
display: flex;
flex-direction: column;