mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-19 13:55:40 -04:00
[ENG-1182] Switch quick preview animation from react-spring to tailwindcss-animate (#1431)
* animate * radix-state-open
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { ArrowLeft, ArrowRight, DotsThree, Plus, SidebarSimple, X } from '@phosphor-icons/react';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
ButtonHTMLAttributes,
|
||||
@@ -38,9 +37,6 @@ import { FileThumb } from '../FilePath/Thumb';
|
||||
import { SingleItemMetadata } from '../Inspector';
|
||||
import { getQuickPreviewStore, useQuickPreviewStore } from './store';
|
||||
|
||||
const AnimatedDialogOverlay = animated(Dialog.Overlay);
|
||||
const AnimatedDialogContent = animated(Dialog.Content);
|
||||
|
||||
const iconKinds: ObjectKindKey[] = ['Audio', 'Folder', 'Executable', 'Unknown'];
|
||||
const textKinds: ObjectKindKey[] = ['Text', 'Config', 'Code'];
|
||||
const withoutBackgroundKinds: ObjectKindKey[] = [...iconKinds, ...textKinds, 'Document'];
|
||||
@@ -77,17 +73,6 @@ export const QuickPreview = () => {
|
||||
|
||||
const item = useMemo(() => items[itemIndex], [items, itemIndex]);
|
||||
|
||||
const transitions = useTransition(open, {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: `translateY(20px) scale(0.9)`,
|
||||
transformOrigin: 'center top'
|
||||
},
|
||||
enter: { opacity: 1, transform: `translateY(0px) scale(1)` },
|
||||
leave: { opacity: 0, immediate: true },
|
||||
config: { mass: 0.2, tension: 300, friction: 20, bounce: 0 }
|
||||
});
|
||||
|
||||
const renameFile = useLibraryMutation(['files.renameFile'], {
|
||||
onError: () => setNewName(null),
|
||||
onSuccess: () => rspc.queryClient.invalidateQueries(['search.paths'])
|
||||
@@ -181,268 +166,253 @@ export const QuickPreview = () => {
|
||||
));
|
||||
});
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
const { kind, ...itemData } = getExplorerItemData(item);
|
||||
|
||||
const name = newName || `${itemData.name}${itemData.extension ? `.${itemData.extension}` : ''}`;
|
||||
|
||||
const background = !withoutBackgroundKinds.includes(kind);
|
||||
const icon = iconKinds.includes(kind);
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={(open) => (getQuickPreviewStore().open = open)}>
|
||||
{transitions((styles, show) => {
|
||||
if (!show || !item) return null;
|
||||
<QuickPreviewContext.Provider value={{ background }}>
|
||||
<Dialog.Portal forceMount>
|
||||
<Dialog.Overlay
|
||||
className={clsx(
|
||||
'absolute inset-0 z-50',
|
||||
'radix-state-open:animate-in radix-state-open:fade-in-0',
|
||||
isDark ? 'bg-black/80' : 'bg-black/60'
|
||||
)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
/>
|
||||
|
||||
const { kind, ...itemData } = getExplorerItemData(item);
|
||||
|
||||
const name =
|
||||
newName ||
|
||||
`${itemData.name}${itemData.extension ? `.${itemData.extension}` : ''}`;
|
||||
|
||||
const background = !withoutBackgroundKinds.includes(kind);
|
||||
const icon = iconKinds.includes(kind);
|
||||
|
||||
return (
|
||||
<QuickPreviewContext.Provider value={{ background }}>
|
||||
<Dialog.Portal forceMount>
|
||||
<AnimatedDialogOverlay
|
||||
className={clsx(
|
||||
'absolute inset-0 z-50',
|
||||
isDark ? 'bg-black/80' : 'bg-black/60'
|
||||
<Dialog.Content
|
||||
className="fixed inset-[5%] z-50 outline-none radix-state-open:animate-in radix-state-open:fade-in-0 radix-state-open:zoom-in-95"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => isRenaming && e.preventDefault()}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex h-full overflow-hidden rounded-md border',
|
||||
isDark ? 'border-app-line/80' : 'border-app-line/10'
|
||||
)}
|
||||
>
|
||||
<div className="relative flex flex-1 flex-col overflow-hidden bg-app/80 backdrop-blur">
|
||||
{background && (
|
||||
<div className="absolute inset-0 overflow-hidden bg-black/90">
|
||||
<FileThumb
|
||||
data={item}
|
||||
cover={true}
|
||||
childClassName="opacity-75 blur-3xl scale-125"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
style={{ opacity: styles.opacity }}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
/>
|
||||
|
||||
<AnimatedDialogContent
|
||||
className="fixed inset-[5%] z-50 outline-none"
|
||||
style={styles}
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => isRenaming && e.preventDefault()}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex h-full overflow-hidden rounded-md border',
|
||||
isDark ? 'border-app-line/80' : 'border-app-line/10'
|
||||
'z-50 flex items-center p-2',
|
||||
background ? 'text-white' : 'text-ink'
|
||||
)}
|
||||
>
|
||||
<div className="relative flex flex-1 flex-col overflow-hidden bg-app/80 backdrop-blur">
|
||||
{background && (
|
||||
<div className="absolute inset-0 overflow-hidden bg-black/90">
|
||||
<FileThumb
|
||||
data={item}
|
||||
cover={true}
|
||||
childClassName="opacity-75 blur-3xl scale-125"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-1">
|
||||
<Tooltip label="Close">
|
||||
<Dialog.Close asChild>
|
||||
<IconButton>
|
||||
<X weight="bold" />
|
||||
</IconButton>
|
||||
</Dialog.Close>
|
||||
</Tooltip>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
'z-50 flex items-center p-2',
|
||||
background ? 'text-white' : 'text-ink'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-1">
|
||||
<Tooltip label="Close">
|
||||
<Dialog.Close asChild>
|
||||
<IconButton>
|
||||
<X weight="bold" />
|
||||
</IconButton>
|
||||
</Dialog.Close>
|
||||
{items.length > 1 && (
|
||||
<div className="ml-2 flex">
|
||||
<Tooltip label="Back">
|
||||
<IconButton
|
||||
disabled={!items[itemIndex - 1]}
|
||||
onClick={() =>
|
||||
changeCurrentItem(itemIndex - 1)
|
||||
}
|
||||
className="rounded-r-none"
|
||||
>
|
||||
<ArrowLeft weight="bold" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{items.length > 1 && (
|
||||
<div className="ml-2 flex">
|
||||
<Tooltip label="Back">
|
||||
<IconButton
|
||||
disabled={!items[itemIndex - 1]}
|
||||
onClick={() =>
|
||||
changeCurrentItem(itemIndex - 1)
|
||||
}
|
||||
className="rounded-r-none"
|
||||
>
|
||||
<ArrowLeft weight="bold" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Forward">
|
||||
<IconButton
|
||||
disabled={!items[itemIndex + 1]}
|
||||
onClick={() =>
|
||||
changeCurrentItem(itemIndex + 1)
|
||||
}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
<ArrowRight weight="bold" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex w-1/2 items-center justify-center truncate text-sm">
|
||||
{isRenaming && name ? (
|
||||
<RenameInput
|
||||
name={name}
|
||||
onRename={(newName) => {
|
||||
setIsRenaming(false);
|
||||
|
||||
if (
|
||||
!('id' in item.item) ||
|
||||
!newName ||
|
||||
newName === name
|
||||
)
|
||||
return;
|
||||
|
||||
const filePathData =
|
||||
getIndexedItemFilePath(item);
|
||||
|
||||
if (!filePathData) return;
|
||||
|
||||
const locationId =
|
||||
filePathData.location_id;
|
||||
|
||||
if (locationId === null) return;
|
||||
|
||||
renameFile.mutate({
|
||||
location_id: locationId,
|
||||
kind: {
|
||||
One: {
|
||||
from_file_path_id:
|
||||
item.item.id,
|
||||
to: newName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setNewName(newName);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip label={name} className="truncate">
|
||||
<span
|
||||
onClick={() =>
|
||||
name &&
|
||||
item.type !== 'NonIndexedPath' &&
|
||||
setIsRenaming(true)
|
||||
}
|
||||
className={clsx(
|
||||
item.type === 'NonIndexedPath'
|
||||
? 'cursor-default'
|
||||
: 'cursor-text'
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-end gap-1">
|
||||
{item.type !== 'NonIndexedPath' && (
|
||||
<DropdownMenu.Root
|
||||
trigger={
|
||||
<div className="flex">
|
||||
<Tooltip label="More">
|
||||
<IconButton>
|
||||
<DotsThree
|
||||
size={20}
|
||||
weight="bold"
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
onOpenChange={setIsContextMenuOpen}
|
||||
align="end"
|
||||
sideOffset={-10}
|
||||
>
|
||||
<ExplorerContextMenu items={[item]} custom>
|
||||
<Conditional
|
||||
items={[
|
||||
FilePathItems.OpenOrDownload,
|
||||
SharedItems.RevealInNativeExplorer
|
||||
]}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Item
|
||||
label="Rename"
|
||||
onClick={() =>
|
||||
name && setIsRenaming(true)
|
||||
}
|
||||
/>
|
||||
|
||||
<SeparatedConditional
|
||||
items={[ObjectItems.AssignTag]}
|
||||
/>
|
||||
|
||||
<Conditional
|
||||
items={[
|
||||
FilePathItems.CopyAsPath,
|
||||
FilePathItems.Crypto,
|
||||
FilePathItems.Compress,
|
||||
ObjectItems.ConvertObject,
|
||||
FilePathItems.SecureDelete
|
||||
]}
|
||||
>
|
||||
{(items) => (
|
||||
<DropdownMenu.SubMenu
|
||||
label="More actions..."
|
||||
icon={Plus}
|
||||
>
|
||||
{items}
|
||||
</DropdownMenu.SubMenu>
|
||||
)}
|
||||
</Conditional>
|
||||
|
||||
<SeparatedConditional
|
||||
items={[FilePathItems.Delete]}
|
||||
/>
|
||||
</ExplorerContextMenu>
|
||||
</DropdownMenu.Root>
|
||||
)}
|
||||
|
||||
<Tooltip label="Show details">
|
||||
<Tooltip label="Forward">
|
||||
<IconButton
|
||||
disabled={!items[itemIndex + 1]}
|
||||
onClick={() =>
|
||||
setShowMetadata(!showMetadata)
|
||||
changeCurrentItem(itemIndex + 1)
|
||||
}
|
||||
active={showMetadata}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
<SidebarSimple
|
||||
className="rotate-180"
|
||||
weight={
|
||||
showMetadata ? 'fill' : 'regular'
|
||||
}
|
||||
/>
|
||||
<ArrowRight weight="bold" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileThumb
|
||||
data={item}
|
||||
loadOriginal
|
||||
mediaControls
|
||||
className={clsx(
|
||||
'm-3 !w-auto flex-1 !overflow-hidden rounded',
|
||||
!background && !icon && 'bg-app-box shadow'
|
||||
)}
|
||||
childClassName={clsx(
|
||||
'rounded',
|
||||
kind === 'Text' && 'p-3',
|
||||
!icon && 'h-full',
|
||||
textKinds.includes(kind) && 'select-text'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showMetadata && (
|
||||
<div className="no-scrollbar w-64 shrink-0 border-l border-app-line bg-app-darkBox py-1">
|
||||
<SingleItemMetadata item={item} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-1/2 items-center justify-center truncate text-sm">
|
||||
{isRenaming && name ? (
|
||||
<RenameInput
|
||||
name={name}
|
||||
onRename={(newName) => {
|
||||
setIsRenaming(false);
|
||||
|
||||
if (
|
||||
!('id' in item.item) ||
|
||||
!newName ||
|
||||
newName === name
|
||||
)
|
||||
return;
|
||||
|
||||
const filePathData =
|
||||
getIndexedItemFilePath(item);
|
||||
|
||||
if (!filePathData) return;
|
||||
|
||||
const locationId = filePathData.location_id;
|
||||
|
||||
if (locationId === null) return;
|
||||
|
||||
renameFile.mutate({
|
||||
location_id: locationId,
|
||||
kind: {
|
||||
One: {
|
||||
from_file_path_id: item.item.id,
|
||||
to: newName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setNewName(newName);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip label={name} className="truncate">
|
||||
<span
|
||||
onClick={() =>
|
||||
name &&
|
||||
item.type !== 'NonIndexedPath' &&
|
||||
setIsRenaming(true)
|
||||
}
|
||||
className={clsx(
|
||||
item.type === 'NonIndexedPath'
|
||||
? 'cursor-default'
|
||||
: 'cursor-text'
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-end gap-1">
|
||||
{item.type !== 'NonIndexedPath' && (
|
||||
<DropdownMenu.Root
|
||||
trigger={
|
||||
<div className="flex">
|
||||
<Tooltip label="More">
|
||||
<IconButton>
|
||||
<DotsThree
|
||||
size={20}
|
||||
weight="bold"
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
onOpenChange={setIsContextMenuOpen}
|
||||
align="end"
|
||||
sideOffset={-10}
|
||||
>
|
||||
<ExplorerContextMenu items={[item]} custom>
|
||||
<Conditional
|
||||
items={[
|
||||
FilePathItems.OpenOrDownload,
|
||||
SharedItems.RevealInNativeExplorer
|
||||
]}
|
||||
/>
|
||||
|
||||
<DropdownMenu.Item
|
||||
label="Rename"
|
||||
onClick={() => name && setIsRenaming(true)}
|
||||
/>
|
||||
|
||||
<SeparatedConditional
|
||||
items={[ObjectItems.AssignTag]}
|
||||
/>
|
||||
|
||||
<Conditional
|
||||
items={[
|
||||
FilePathItems.CopyAsPath,
|
||||
FilePathItems.Crypto,
|
||||
FilePathItems.Compress,
|
||||
ObjectItems.ConvertObject,
|
||||
FilePathItems.SecureDelete
|
||||
]}
|
||||
>
|
||||
{(items) => (
|
||||
<DropdownMenu.SubMenu
|
||||
label="More actions..."
|
||||
icon={Plus}
|
||||
>
|
||||
{items}
|
||||
</DropdownMenu.SubMenu>
|
||||
)}
|
||||
</Conditional>
|
||||
|
||||
<SeparatedConditional
|
||||
items={[FilePathItems.Delete]}
|
||||
/>
|
||||
</ExplorerContextMenu>
|
||||
</DropdownMenu.Root>
|
||||
)}
|
||||
|
||||
<Tooltip label="Show details">
|
||||
<IconButton
|
||||
onClick={() => setShowMetadata(!showMetadata)}
|
||||
active={showMetadata}
|
||||
>
|
||||
<SidebarSimple
|
||||
className="rotate-180"
|
||||
weight={showMetadata ? 'fill' : 'regular'}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedDialogContent>
|
||||
</Dialog.Portal>
|
||||
</QuickPreviewContext.Provider>
|
||||
);
|
||||
})}
|
||||
|
||||
<FileThumb
|
||||
data={item}
|
||||
loadOriginal
|
||||
mediaControls
|
||||
className={clsx(
|
||||
'm-3 !w-auto flex-1 !overflow-hidden rounded',
|
||||
!background && !icon && 'bg-app-box shadow'
|
||||
)}
|
||||
childClassName={clsx(
|
||||
'rounded',
|
||||
kind === 'Text' && 'p-3',
|
||||
!icon && 'h-full',
|
||||
textKinds.includes(kind) && 'select-text'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showMetadata && (
|
||||
<div className="no-scrollbar w-64 shrink-0 border-l border-app-line bg-app-darkBox py-1">
|
||||
<SingleItemMetadata item={item} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</QuickPreviewContext.Provider>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user