Fix compare tool file selection and other files improvements (#6133)

This commit is contained in:
Anthony Stirling
2026-04-20 12:53:37 +01:00
committed by GitHub
parent 30aff3236f
commit b4b196556d
15 changed files with 522 additions and 20 deletions
@@ -1,4 +1,5 @@
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
import { flushSync } from "react-dom";
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import {
@@ -306,8 +307,23 @@ const FileEditor = ({
// Insert files at the calculated position
newOrder.splice(insertIndex, 0, ...filesToMove);
// Update file order
reorderFiles(newOrder);
// Animate the reorder using the View Transitions API where available.
// Each FileEditorThumbnail carries a stable `view-transition-name`, so
// the browser snapshots each card before and after the DOM reorder and
// interpolates the positions automatically. `flushSync` forces React to
// apply the reorderFiles dispatch synchronously inside the transition
// callback so the BEFORE/AFTER snapshots capture the correct frames.
const applyReorder = () => reorderFiles(newOrder);
const docWithViewTransition = document as Document & {
startViewTransition?: (cb: () => void) => unknown;
};
if (typeof docWithViewTransition.startViewTransition === "function") {
docWithViewTransition.startViewTransition(() => {
flushSync(applyReorder);
});
} else {
applyReorder();
}
// Update status
const moveCount = filesToMove.length;
@@ -99,6 +99,7 @@ const FileEditorThumbnail = ({
// ---- Drag state ----
const [isDragging, setIsDragging] = useState(false);
const [isDragOver, setIsDragOver] = useState(false);
const dragElementRef = useRef<HTMLDivElement | null>(null);
const [showHoverMenu, setShowHoverMenu] = useState(false);
const isMobile = useIsMobile();
@@ -210,7 +211,10 @@ const FileEditorThumbnail = ({
const sourceData = source.data;
return sourceData.type === "file" && sourceData.fileId !== file.id;
},
onDragEnter: () => setIsDragOver(true),
onDragLeave: () => setIsDragOver(false),
onDrop: ({ source }) => {
setIsDragOver(false);
const sourceData = source.data;
if (sourceData.type === "file" && onReorderFiles) {
const sourceFileId = sourceData.fileId as FileId;
@@ -435,7 +439,22 @@ const FileEditorThumbnail = ({
data-selected={isSelected}
data-supported={isSupported}
className={`${styles.card} w-[18rem] h-[22rem] select-none flex flex-col shadow-sm transition-all relative`}
style={{ opacity: isDragging ? 0.9 : 1 }}
style={
{
opacity: isDragging ? 0.4 : 1,
outline: isDragOver
? "3px dashed var(--mantine-color-blue-5, #3b82f6)"
: undefined,
outlineOffset: isDragOver ? "2px" : undefined,
transform: isDragOver ? "scale(1.02)" : undefined,
transition:
"outline 120ms ease, transform 120ms ease, opacity 120ms ease",
// Tag each card with a stable, unique view-transition-name so the
// browser can animate the reorder (see FileEditor.handleReorderFiles,
// which dispatches reorderFiles inside document.startViewTransition).
viewTransitionName: `file-card-${file.id}`,
} as React.CSSProperties
}
tabIndex={0}
role="listitem"
aria-selected={isSelected}
@@ -277,7 +277,12 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
<Text size="sm" c="dimmed">
{t("fileManager.storageState", "Storage")}
</Text>
<Badge size="xs" variant="light" color="gray">
<Badge
size="xs"
variant="default"
c="dimmed"
style={{ opacity: 0.75 }}
>
{t("fileManager.localOnly", "Local only")}
</Badge>
</Group>
@@ -270,7 +270,12 @@ const FileListItem: React.FC<FileListItemProps> = ({
: t("storageShare.roleViewer", "Viewer")}
</Badge>
) : isLocalOnly ? (
<Badge size="xs" variant="light" color="gray">
<Badge
size="xs"
variant="default"
c="dimmed"
style={{ opacity: 0.75 }}
>
{t("fileManager.localOnly", "Local only")}
</Badge>
) : uploadEnabled && isOutOfSync ? (
@@ -138,3 +138,15 @@
.landing-btn-icon {
color: var(--accent-interactive) !important;
}
/* Dropzone accept/reject outlines. Mantine 8 no longer supports nested
* `&[data-accept]` selectors inside the `styles` prop object, so these are
* plain CSS attribute selectors on a class applied to the Dropzone root. */
.landing-dropzone[data-accept] {
outline: 2px dashed var(--accent-interactive);
outline-offset: 4px;
}
.landing-dropzone[data-reject] {
outline: 2px dashed var(--mantine-color-red-6);
outline-offset: 4px;
}
@@ -61,20 +61,12 @@ const LandingPage = () => {
activateOnClick={false}
enablePointerEvents
aria-label={terminology.dropFilesHere}
className="flex min-h-0 flex-1 cursor-default flex-col items-center justify-center border-none bg-transparent px-4 py-8 shadow-none outline-none"
className="landing-dropzone flex min-h-0 flex-1 cursor-default flex-col items-center justify-center border-none bg-transparent px-4 py-8 shadow-none outline-none"
styles={{
root: {
border: "none !important",
backgroundColor: "transparent",
overflow: "visible",
"&[data-accept]": {
outline: "2px dashed var(--accent-interactive)",
outlineOffset: 4,
},
"&[data-reject]": {
outline: "2px dashed var(--mantine-color-red-6)",
outlineOffset: 4,
},
},
inner: {
overflow: "visible",