always show undo & redo and improve styling

This commit is contained in:
Ryan Di
2025-09-24 14:36:47 +10:00
parent ce6a9549e1
commit 1bf169b4e9
10 changed files with 43 additions and 39 deletions

View File

@@ -331,7 +331,8 @@ export const actionDeleteSelected = register({
!isSomeElementSelected(getNonDeletedElements(elements), appState) !isSomeElementSelected(getNonDeletedElements(elements), appState)
} }
style={{ style={{
...(appState.stylesPanelMode === "mobile" ...(appState.stylesPanelMode === "mobile" &&
appState.openPopup !== "compactOtherProperties"
? MOBILE_ACTION_BUTTON_BG ? MOBILE_ACTION_BUTTON_BG
: {}), : {}),
}} }}

View File

@@ -120,7 +120,8 @@ export const actionDuplicateSelection = register({
!isSomeElementSelected(getNonDeletedElements(elements), appState) !isSomeElementSelected(getNonDeletedElements(elements), appState)
} }
style={{ style={{
...(appState.stylesPanelMode === "mobile" ...(appState.stylesPanelMode === "mobile" &&
appState.openPopup !== "compactOtherProperties"
? MOBILE_ACTION_BUTTON_BG ? MOBILE_ACTION_BUTTON_BG
: {}), : {}),
}} }}

View File

@@ -192,6 +192,9 @@
background: transparent; background: transparent;
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-lg);
box-shadow: none; box-shadow: none;
overflow: none;
scrollbar-width: none;
-ms-overflow-style: none;
} }
.shape-actions-theme-scope { .shape-actions-theme-scope {

View File

@@ -328,14 +328,7 @@ const CombinedShapeProperties = ({
hasBackground(element.type) && !isTransparent(element.backgroundColor), hasBackground(element.type) && !isTransparent(element.backgroundColor),
); );
const shouldShowCombinedProperties = const shouldShowCombinedProperties = targetElements.length > 0;
showFillIcons ||
hasStrokeWidth(appState.activeTool.type) ||
targetElements.some((element) => hasStrokeWidth(element.type)) ||
hasStrokeStyle(appState.activeTool.type) ||
targetElements.some((element) => hasStrokeStyle(element.type)) ||
canChangeRoundness(appState.activeTool.type) ||
targetElements.some((element) => canChangeRoundness(element.type));
const isOpen = appState.openPopup === "compactStrokeStyles"; const isOpen = appState.openPopup === "compactStrokeStyles";
@@ -606,7 +599,6 @@ const CombinedExtraActions = ({
setAppState, setAppState,
container, container,
app, app,
showRedo,
showDuplicate, showDuplicate,
showDelete, showDelete,
}: { }: {
@@ -616,7 +608,6 @@ const CombinedExtraActions = ({
setAppState: React.Component<any, AppState>["setState"]; setAppState: React.Component<any, AppState>["setState"];
container: HTMLDivElement | null; container: HTMLDivElement | null;
app: AppClassProperties; app: AppClassProperties;
showRedo?: boolean;
showDuplicate?: boolean; showDuplicate?: boolean;
showDelete?: boolean; showDelete?: boolean;
}) => { }) => {
@@ -740,11 +731,10 @@ const CombinedExtraActions = ({
<div className="buttonList"> <div className="buttonList">
{renderAction("group")} {renderAction("group")}
{renderAction("ungroup")} {renderAction("ungroup")}
{showRedo && renderAction("redo")}
{showDuplicate && renderAction("duplicateSelection")}
{showDelete && renderAction("deleteSelectedElements")}
{showLinkIcon && renderAction("hyperlink")} {showLinkIcon && renderAction("hyperlink")}
{showCropEditorAction && renderAction("cropEditor")} {showCropEditorAction && renderAction("cropEditor")}
{showDuplicate && renderAction("duplicateSelection")}
{showDelete && renderAction("deleteSelectedElements")}
</div> </div>
</fieldset> </fieldset>
</div> </div>
@@ -907,19 +897,22 @@ export const MobileShapeActions = ({
const { container } = useExcalidrawContainer(); const { container } = useExcalidrawContainer();
const mobileActionsRef = useRef<HTMLDivElement>(null); const mobileActionsRef = useRef<HTMLDivElement>(null);
const width = mobileActionsRef.current?.getBoundingClientRect()?.width ?? 0; const ACTIONS_WIDTH =
mobileActionsRef.current?.getBoundingClientRect()?.width ?? 0;
// 7 actions + 2 for undo/redo
const MIN_ACTIONS = 9;
const WIDTH = 36;
const GAP = 6; const GAP = 6;
const WIDTH = 32;
// max 7 actions + undo const MIN_WIDTH = MIN_ACTIONS * WIDTH + (MIN_ACTIONS - 1) * GAP;
const MIN_WIDTH = 8 * WIDTH + 7 * GAP;
const ADDITIONAL_WIDTH = WIDTH + GAP; const ADDITIONAL_WIDTH = WIDTH + GAP;
const showRedoOutside = width >= MIN_WIDTH + 1 * ADDITIONAL_WIDTH; const showDeleteOutside = ACTIONS_WIDTH >= MIN_WIDTH + ADDITIONAL_WIDTH;
const showDeleteOutside = width >= 2 * MIN_WIDTH; const showDuplicateOutside =
const showDuplicateOutside = width >= MIN_WIDTH + 3 * ADDITIONAL_WIDTH; ACTIONS_WIDTH >= MIN_WIDTH + 2 * ADDITIONAL_WIDTH;
return ( return (
<Island <Island
@@ -930,8 +923,8 @@ export const MobileShapeActions = ({
padding: 0, padding: 0,
zIndex: 2, zIndex: 2,
backgroundColor: "transparent", backgroundColor: "transparent",
height: WIDTH * 1.25, height: WIDTH * 1.35,
marginBottom: 2, marginBottom: 4,
alignItems: "center", alignItems: "center",
gap: GAP, gap: GAP,
pointerEvents: "none", pointerEvents: "none",
@@ -1004,7 +997,6 @@ export const MobileShapeActions = ({
setAppState={setAppState} setAppState={setAppState}
container={container} container={container}
app={app} app={app}
showRedo={!showRedoOutside}
showDuplicate={!showDuplicateOutside} showDuplicate={!showDuplicateOutside}
showDelete={!showDeleteOutside} showDelete={!showDeleteOutside}
/> />
@@ -1017,9 +1009,7 @@ export const MobileShapeActions = ({
}} }}
> >
<div className="compact-action-item">{renderAction("undo")}</div> <div className="compact-action-item">{renderAction("undo")}</div>
{showRedoOutside && (
<div className="compact-action-item">{renderAction("redo")}</div> <div className="compact-action-item">{renderAction("redo")}</div>
)}
{showDuplicateOutside && ( {showDuplicateOutside && (
<div className="compact-action-item"> <div className="compact-action-item">
{renderAction("duplicateSelection")} {renderAction("duplicateSelection")}

View File

@@ -1,5 +1,7 @@
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { MOBILE_ACTION_BUTTON_BG } from "@excalidraw/common";
import type { FontFamilyValues } from "@excalidraw/element/types"; import type { FontFamilyValues } from "@excalidraw/element/types";
import { t } from "../../i18n"; import { t } from "../../i18n";
@@ -23,11 +25,9 @@ export const FontPickerTrigger = ({
const compactStyle = compactMode const compactStyle = compactMode
? { ? {
background: "rgba(255, 255, 255, 0.1)", ...MOBILE_ACTION_BUTTON_BG,
backdropFilter: "blur(20px)", width: "2rem",
WebkitBackdropFilter: "blur(20px)", height: "2rem",
width: "2.25rem",
height: "2.25rem",
} }
: {}; : {};

View File

@@ -6,7 +6,7 @@
display: flex; display: flex;
flex: 1; flex: 1;
align-items: center; align-items: center;
padding: 4px; padding: 0px;
gap: 4px; gap: 4px;
border-radius: var(--space-factor); border-radius: var(--space-factor);
overflow-x: auto; overflow-x: auto;

View File

@@ -50,6 +50,7 @@ export const ToolPopover = ({
}: ToolPopoverProps) => { }: ToolPopoverProps) => {
const [isPopupOpen, setIsPopupOpen] = useState(false); const [isPopupOpen, setIsPopupOpen] = useState(false);
const currentType = activeTool.type; const currentType = activeTool.type;
const SIDE_OFFSET = 32 / 2 + 10;
// if currentType is not in options, close popup // if currentType is not in options, close popup
if (!options.some((o) => o.type === currentType) && isPopupOpen) { if (!options.some((o) => o.type === currentType) && isPopupOpen) {
@@ -87,7 +88,10 @@ export const ToolPopover = ({
/> />
</Popover.Trigger> </Popover.Trigger>
<Popover.Content className="tool-popover-content" sideOffset={28}> <Popover.Content
className="tool-popover-content"
sideOffset={SIDE_OFFSET}
>
{options.map(({ type, icon, title }) => ( {options.map(({ type, icon, title }) => (
<ToolButton <ToolButton
className={clsx(className, { className={clsx(className, {

View File

@@ -245,7 +245,7 @@ body.excalidraw-cursor-resize * {
position: absolute; position: absolute;
// account for margins // account for margins
width: calc(100% - 28px); width: calc(100% - 28px);
max-width: 400px; max-width: 450px;
bottom: 0; bottom: 0;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);

View File

@@ -42,7 +42,7 @@
--lg-button-size: 2.25rem; --lg-button-size: 2.25rem;
--lg-icon-size: 1rem; --lg-icon-size: 1rem;
--editor-container-padding: 1rem; --editor-container-padding: 1rem;
--mobile-action-button-size: 2.25rem; --mobile-action-button-size: 2rem;
@include isMobile { @include isMobile {
--editor-container-padding: 0.75rem; --editor-container-padding: 0.75rem;

View File

@@ -122,6 +122,11 @@
color: var(--button-color, var(--color-on-primary-container)); color: var(--button-color, var(--color-on-primary-container));
} }
} }
@include isMobile() {
width: var(--mobile-action-button-size, var(--default-button-size));
height: var(--mobile-action-button-size, var(--default-button-size));
}
} }
@mixin outlineButtonIconStyles { @mixin outlineButtonIconStyles {
@@ -183,8 +188,8 @@
border: none; border: none;
box-shadow: 0 0 0 1px var(--color-surface-lowest); box-shadow: 0 0 0 1px var(--color-surface-lowest);
background-color: var(--color-surface-low); background-color: var(--color-surface-low);
width: var(--moobile-action-button-size, 2.25rem); width: var(--mobile-action-button-size, 2rem);
height: var(--moobile-action-button-size, 2.25rem); height: var(--mobile-action-button-size, 2rem);
&:active { &:active {
box-shadow: 0 0 0 1px var(--color-brand-active); box-shadow: 0 0 0 1px var(--color-brand-active);