mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 12:13:27 +00:00
[ENG-972] quick view improvements (#1233)
* quick preview improvements * fix ts * improvements * fix merge * non-indexed support * Update index.tsx * Update pnpm-lock.yaml * update quick preview * sidebar icon weight * fix thumb * ts * fix focus * remove usePortal * quick preview store * Update index.tsx * Update index.tsx * cleanup * add tooltip to name * hide nav buttons and match explorer nav buttons --------- Co-authored-by: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
This commit is contained in:
parent
353d8d9a5a
commit
99ccb8f8c7
|
@ -1,6 +1,7 @@
|
|||
import { Image, Package, Trash, TrashSimple } from 'phosphor-react';
|
||||
import { libraryClient, useLibraryContext, useLibraryMutation } from '@sd/client';
|
||||
import { ContextMenu, ModifierKeys, dialogManager, toast } from '@sd/ui';
|
||||
import { Menu } from '~/components/Menu';
|
||||
import { useKeybindFactory } from '~/hooks/useKeybindFactory';
|
||||
import { isNonEmpty } from '~/util';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
|
@ -28,7 +29,7 @@ export const Delete = new ConditionalItem({
|
|||
const keybind = useKeybindFactory();
|
||||
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
icon={Trash}
|
||||
label="Delete"
|
||||
variant="danger"
|
||||
|
@ -72,7 +73,7 @@ export const Compress = new ConditionalItem({
|
|||
const keybind = useKeybindFactory();
|
||||
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
label="Compress"
|
||||
icon={Package}
|
||||
keybind={keybind([ModifierKeys.Control], ['B'])}
|
||||
|
@ -156,7 +157,7 @@ export const SecureDelete = new ConditionalItem({
|
|||
return { locationId, selectedFilePaths };
|
||||
},
|
||||
Component: ({ locationId, selectedFilePaths }) => (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
variant="danger"
|
||||
label="Secure delete"
|
||||
icon={TrashSimple}
|
||||
|
@ -242,11 +243,11 @@ export const OpenOrDownload = new ConditionalItem({
|
|||
|
||||
const { library } = useLibraryContext();
|
||||
|
||||
if (platform === 'web') return <ContextMenu.Item label="Download" />;
|
||||
if (platform === 'web') return <Menu.Item label="Download" />;
|
||||
else
|
||||
return (
|
||||
<>
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
label="Open"
|
||||
keybind={keybind([ModifierKeys.Control], ['O'])}
|
||||
onClick={async () => {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Suspense } from 'react';
|
||||
import { useLibraryContext } from '@sd/client';
|
||||
import { ContextMenu, toast } from '@sd/ui';
|
||||
import { toast } from '@sd/ui';
|
||||
import { Menu } from '~/components/Menu';
|
||||
import { Platform, usePlatform } from '~/util/Platform';
|
||||
import { ConditionalItem } from '../ConditionalItem';
|
||||
import { useContextMenuContext } from '../context';
|
||||
|
@ -17,7 +18,7 @@ export default new ConditionalItem({
|
|||
return { getFilePathOpenWithApps, openFilePathWith };
|
||||
},
|
||||
Component: ({ getFilePathOpenWithApps, openFilePathWith }) => (
|
||||
<ContextMenu.SubMenu label="Open with">
|
||||
<Menu.SubMenu label="Open with">
|
||||
<Suspense>
|
||||
<Items
|
||||
actions={{
|
||||
|
@ -26,7 +27,7 @@ export default new ConditionalItem({
|
|||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</ContextMenu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -51,7 +52,7 @@ const Items = ({
|
|||
<>
|
||||
{Array.isArray(items.data) && items.data.length > 0 ? (
|
||||
items.data.map((data, id) => (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
key={id}
|
||||
onClick={async () => {
|
||||
try {
|
||||
|
@ -65,7 +66,7 @@ const Items = ({
|
|||
}}
|
||||
>
|
||||
{data.name}
|
||||
</ContextMenu.Item>
|
||||
</Menu.Item>
|
||||
))
|
||||
) : (
|
||||
<p className="w-full text-center text-sm text-gray-400"> No apps available </p>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useMemo } from 'react';
|
|||
import { ObjectKind, type ObjectKindEnum, useLibraryMutation } from '@sd/client';
|
||||
import { ContextMenu, toast } from '@sd/ui';
|
||||
import AssignTagMenuItems from '~/components/AssignTagMenuItems';
|
||||
import { Menu } from '~/components/Menu';
|
||||
import { isNonEmpty } from '~/util';
|
||||
import { ConditionalItem } from '../ConditionalItem';
|
||||
import { useContextMenuContext } from '../context';
|
||||
|
@ -47,9 +48,9 @@ export const AssignTag = new ConditionalItem({
|
|||
return { selectedObjects };
|
||||
},
|
||||
Component: ({ selectedObjects }) => (
|
||||
<ContextMenu.SubMenu label="Assign tag" icon={TagSimple}>
|
||||
<Menu.SubMenu label="Assign tag" icon={TagSimple}>
|
||||
<AssignTagMenuItems objects={selectedObjects} />
|
||||
</ContextMenu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -82,10 +83,10 @@ export const ConvertObject = new ConditionalItem({
|
|||
return { kind };
|
||||
},
|
||||
Component: ({ kind }) => (
|
||||
<ContextMenu.SubMenu label="Convert to" icon={ArrowBendUpRight}>
|
||||
<Menu.SubMenu label="Convert to" icon={ArrowBendUpRight}>
|
||||
{ObjectConversions[kind]?.map((ext) => (
|
||||
<ContextMenu.Item key={ext} label={ext} disabled />
|
||||
<Menu.Item key={ext} label={ext} disabled />
|
||||
))}
|
||||
</ContextMenu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { FileX, Share as ShareIcon } from 'phosphor-react';
|
||||
import { useMemo } from 'react';
|
||||
import { ContextMenu, ModifierKeys } from '@sd/ui';
|
||||
import { Menu } from '~/components/Menu';
|
||||
import { useKeybindFactory } from '~/hooks/useKeybindFactory';
|
||||
import { isNonEmpty } from '~/util';
|
||||
import { type Platform } from '~/util/Platform';
|
||||
import { useExplorerContext } from '../Context';
|
||||
import { getQuickPreviewStore } from '../QuickPreview/store';
|
||||
import { RevealInNativeExplorerBase } from '../RevealInNativeExplorer';
|
||||
import { useExplorerViewContext } from '../ViewContext';
|
||||
import { getExplorerStore, useExplorerStore } from '../store';
|
||||
|
@ -13,16 +15,12 @@ import { useContextMenuContext } from './context';
|
|||
|
||||
export const OpenQuickView = () => {
|
||||
const keybind = useKeybindFactory();
|
||||
const { selectedItems } = useContextMenuContext();
|
||||
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
label="Quick view"
|
||||
keybind={keybind([], [' '])}
|
||||
onClick={() =>
|
||||
// using [0] is not great
|
||||
(getExplorerStore().quickViewObject = selectedItems[0])
|
||||
}
|
||||
onClick={() => (getQuickPreviewStore().open = true)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -149,7 +147,7 @@ export const Deselect = new ConditionalItem({
|
|||
export const Share = () => {
|
||||
return (
|
||||
<>
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
label="Share"
|
||||
icon={ShareIcon}
|
||||
onClick={(e) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Plus } from 'phosphor-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
import { type PropsWithChildren, useMemo } from 'react';
|
||||
import { ExplorerItem } from '@sd/client';
|
||||
import { ContextMenu } from '@sd/ui';
|
||||
import { isNonEmpty } from '~/util';
|
||||
import { useExplorerContext } from '../Context';
|
||||
|
@ -13,7 +14,7 @@ export * as FilePathItems from './FilePath/Items';
|
|||
export * as ObjectItems from './Object/Items';
|
||||
export * as SharedItems from './SharedItems';
|
||||
|
||||
const Items = ({ children }: { children?: () => ReactNode }) => (
|
||||
const Items = ({ children }: PropsWithChildren) => (
|
||||
<>
|
||||
<Conditional items={[FilePathItems.OpenOrDownload]} />
|
||||
<SharedItems.OpenQuickView />
|
||||
|
@ -28,7 +29,8 @@ const Items = ({ children }: { children?: () => ReactNode }) => (
|
|||
SharedItems.Deselect
|
||||
]}
|
||||
/>
|
||||
{children?.()}
|
||||
|
||||
{children}
|
||||
|
||||
<ContextMenu.Separator />
|
||||
<SharedItems.Share />
|
||||
|
@ -56,15 +58,19 @@ const Items = ({ children }: { children?: () => ReactNode }) => (
|
|||
</>
|
||||
);
|
||||
|
||||
export default ({ children }: { children?: () => ReactNode }) => {
|
||||
export default (props: PropsWithChildren<{ items?: ExplorerItem[]; custom?: boolean }>) => {
|
||||
const explorer = useExplorerContext();
|
||||
|
||||
const selectedItems = useMemo(() => [...explorer.selectedItems], [explorer.selectedItems]);
|
||||
const selectedItems = useMemo(
|
||||
() => props.items || [...explorer.selectedItems],
|
||||
[explorer.selectedItems, props.items]
|
||||
);
|
||||
|
||||
if (!isNonEmpty(selectedItems)) return null;
|
||||
|
||||
return (
|
||||
<ContextMenuContextProvider selectedItems={selectedItems}>
|
||||
<Items>{children}</Items>
|
||||
{props.custom ? <>{props.children}</> : <Items>{props.children}</Items>}
|
||||
</ContextMenuContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -72,7 +78,7 @@ export default ({ children }: { children?: () => ReactNode }) => {
|
|||
/**
|
||||
* A `Conditional` that inserts a `<ContextMenu.Separator />` above its items.
|
||||
*/
|
||||
const SeparatedConditional = ({ items, children }: ConditionalGroupProps) => (
|
||||
export const SeparatedConditional = ({ items, children }: ConditionalGroupProps) => (
|
||||
<Conditional items={items}>
|
||||
{(c) => (
|
||||
<>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { ClipboardText } from 'phosphor-react';
|
||||
import { ContextMenu, toast } from '@sd/ui';
|
||||
import { toast } from '@sd/ui';
|
||||
import { Menu } from '~/components/Menu';
|
||||
|
||||
export const CopyAsPathBase = (
|
||||
props: { path: string } | { getPath: () => Promise<string | null> }
|
||||
) => {
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
label="Copy as path"
|
||||
icon={ClipboardText}
|
||||
onClick={async () => {
|
||||
|
|
|
@ -141,10 +141,9 @@ export const RenameTextBoxBase = forwardRef<HTMLDivElement | null, Props>(
|
|||
|
||||
// Rename or blur on Enter key
|
||||
useKey('Enter', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (allowRename) blur();
|
||||
else if (!disabled) {
|
||||
e.preventDefault();
|
||||
setAllowRename(true);
|
||||
explorerView.setIsRenaming(true);
|
||||
}
|
||||
|
|
|
@ -148,12 +148,12 @@ export const FileThumb = memo((props: ThumbProps) => {
|
|||
{(() => {
|
||||
if (!src) return;
|
||||
|
||||
const className = clsx(
|
||||
childClassName,
|
||||
const _childClassName =
|
||||
typeof props.childClassName === 'function'
|
||||
? props.childClassName(thumbType)
|
||||
: props.childClassName
|
||||
);
|
||||
: props.childClassName;
|
||||
|
||||
const className = clsx(childClassName, _childClassName);
|
||||
|
||||
switch (thumbType) {
|
||||
case ThumbType.Original: {
|
||||
|
@ -186,7 +186,7 @@ export const FileThumb = memo((props: ThumbProps) => {
|
|||
? 'overflow-hidden'
|
||||
: 'overflow-auto',
|
||||
className,
|
||||
props.frame && [frameClassName, '!bg-none']
|
||||
props.frame && [frameClassName, '!bg-none p-2']
|
||||
)}
|
||||
codeExtension={
|
||||
((itemData.kind === 'Code' ||
|
||||
|
@ -257,7 +257,10 @@ export const FileThumb = memo((props: ThumbProps) => {
|
|||
decoding={props.size ? 'async' : 'sync'}
|
||||
className={clsx(
|
||||
props.cover
|
||||
? 'min-h-full min-w-full object-cover object-center'
|
||||
? [
|
||||
'min-h-full min-w-full object-cover object-center',
|
||||
_childClassName
|
||||
]
|
||||
: className,
|
||||
props.frame && !(itemData.kind === 'Video' && props.blackBars)
|
||||
? frameClassName
|
||||
|
@ -289,7 +292,7 @@ export const FileThumb = memo((props: ThumbProps) => {
|
|||
onLoad={onLoad}
|
||||
onError={() => setLoaded(false)}
|
||||
decoding={props.size ? 'async' : 'sync'}
|
||||
className={childClassName}
|
||||
className={className}
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -41,6 +41,7 @@ import { useIsDark } from '~/hooks';
|
|||
import { isNonEmpty } from '~/util';
|
||||
import { useExplorerContext } from '../Context';
|
||||
import { FileThumb } from '../FilePath/Thumb';
|
||||
import { useQuickPreviewStore } from '../QuickPreview/store';
|
||||
import { useExplorerStore } from '../store';
|
||||
import { uniqueId, useExplorerItemData } from '../util';
|
||||
import FavoriteButton from './FavoriteButton';
|
||||
|
@ -48,10 +49,10 @@ import MediaData from './MediaData';
|
|||
import Note from './Note';
|
||||
|
||||
export const InfoPill = tw.span`inline border border-transparent px-1 text-[11px] font-medium shadow shadow-app-shade/5 bg-app-selected rounded-md text-ink-dull`;
|
||||
export const PlaceholderPill = tw.span`inline border px-1 text-[11px] shadow shadow-app-shade/10 rounded-md bg-transparent border-dashed border-app-active transition hover:text-ink-faint hover:border-ink-faint font-medium text-ink-faint/70`;
|
||||
export const PlaceholderPill = tw.span`cursor-default inline border px-1 text-[11px] shadow shadow-app-shade/10 rounded-md bg-transparent border-dashed border-app-active transition hover:text-ink-faint hover:border-ink-faint font-medium text-ink-faint/70`;
|
||||
|
||||
export const MetaContainer = tw.div`flex flex-col px-4 py-2 gap-1`;
|
||||
export const MetaTitle = tw.h5`text-xs font-bold`;
|
||||
export const MetaTitle = tw.h5`text-xs font-bold text-ink`;
|
||||
|
||||
export const INSPECTOR_WIDTH = 260;
|
||||
|
||||
|
@ -113,7 +114,7 @@ export const Inspector = forwardRef<HTMLDivElement, Props>(
|
|||
);
|
||||
|
||||
const Thumbnails = ({ items }: { items: ExplorerItem[] }) => {
|
||||
const explorerStore = useExplorerStore();
|
||||
const quickPreviewStore = useQuickPreviewStore();
|
||||
|
||||
const lastThreeItems = items.slice(-3).reverse();
|
||||
|
||||
|
@ -128,7 +129,7 @@ const Thumbnails = ({ items }: { items: ExplorerItem[] }) => {
|
|||
blackBars={thumbs.length === 1}
|
||||
blackBarsSize={16}
|
||||
extension={thumbs.length > 1}
|
||||
pauseVideo={!!explorerStore.quickViewObject || thumbs.length > 1}
|
||||
pauseVideo={quickPreviewStore.open || thumbs.length > 1}
|
||||
className={clsx(
|
||||
thumbs.length > 1 && '!absolute',
|
||||
i === 0 && thumbs.length > 1 && 'z-30 !h-[76%] !w-[76%]',
|
||||
|
@ -146,7 +147,7 @@ const Thumbnails = ({ items }: { items: ExplorerItem[] }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const SingleItemMetadata = ({ item }: { item: ExplorerItem }) => {
|
||||
export const SingleItemMetadata = ({ item }: { item: ExplorerItem }) => {
|
||||
const objectData = getItemObject(item);
|
||||
const readyToFetch = useIsFetchReady(item);
|
||||
const isNonIndexed = item.type === 'NonIndexedPath';
|
||||
|
@ -197,13 +198,13 @@ const SingleItemMetadata = ({ item }: { item: ExplorerItem }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<h3 className="truncate px-3 pb-1 pt-2 text-base font-bold">
|
||||
<h3 className="truncate px-3 pb-1 pt-2 text-base font-bold text-ink">
|
||||
{name}
|
||||
{extension && `.${extension}`}
|
||||
</h3>
|
||||
|
||||
{objectData && (
|
||||
<div className="mx-3 mb-0.5 mt-1 flex flex-row space-x-0.5">
|
||||
<div className="mx-3 mb-0.5 mt-1 flex flex-row space-x-0.5 text-ink">
|
||||
<Tooltip label="Favorite">
|
||||
<FavoriteButton data={objectData} />
|
||||
</Tooltip>
|
||||
|
|
|
@ -1,127 +1,587 @@
|
|||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import { X } from 'phosphor-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { subscribeKey } from 'valtio/utils';
|
||||
import { type ExplorerItem, getExplorerItemData } from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import { ArrowLeft, ArrowRight, DotsThree, Plus, SidebarSimple, X } from 'phosphor-react';
|
||||
import {
|
||||
ButtonHTMLAttributes,
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import {
|
||||
ObjectKindKey,
|
||||
getExplorerItemData,
|
||||
getIndexedItemFilePath,
|
||||
useLibraryContext,
|
||||
useLibraryMutation,
|
||||
useRspcLibraryContext,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import { DropdownMenu, Form, ModifierKeys, Tooltip, dialogManager, toast, z } from '@sd/ui';
|
||||
import { useIsDark, useOperatingSystem } from '~/hooks';
|
||||
import { useKeyBind } from '~/hooks/useKeyBind';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
import { useExplorerContext } from '../Context';
|
||||
import ExplorerContextMenu, {
|
||||
FilePathItems,
|
||||
ObjectItems,
|
||||
SeparatedConditional,
|
||||
SharedItems
|
||||
} from '../ContextMenu';
|
||||
import { Conditional } from '../ContextMenu/ConditionalItem';
|
||||
import DeleteDialog from '../FilePath/DeleteDialog';
|
||||
import { FileThumb } from '../FilePath/Thumb';
|
||||
import { getExplorerStore } from '../store';
|
||||
import { SingleItemMetadata } from '../Inspector';
|
||||
import { getQuickPreviewStore, useQuickPreviewStore } from './store';
|
||||
|
||||
const AnimatedDialogOverlay = animated(Dialog.Overlay);
|
||||
const AnimatedDialogContent = animated(Dialog.Content);
|
||||
|
||||
export interface QuickPreviewProps extends Dialog.DialogProps {
|
||||
transformOrigin?: string;
|
||||
}
|
||||
const heavyKinds: ObjectKindKey[] = ['Image', 'Video'];
|
||||
const iconKinds: ObjectKindKey[] = ['Audio', 'Folder', 'Executable', 'Unknown'];
|
||||
const withoutBackgroundKinds: ObjectKindKey[] = [
|
||||
...iconKinds,
|
||||
'Document',
|
||||
'Config',
|
||||
'Code',
|
||||
'Text'
|
||||
];
|
||||
|
||||
export function QuickPreview({ transformOrigin }: QuickPreviewProps) {
|
||||
const [explorerItem, setExplorerItem] = useState<null | ExplorerItem>(null);
|
||||
const explorerStore = getExplorerStore();
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const QuickPreviewContext = createContext<{ background: boolean } | null>(null);
|
||||
|
||||
/**
|
||||
* The useEffect hook with subscribe is used here, instead of useExplorerStore, because when
|
||||
* explorerStore.quickViewObject is set to null the component will not close immediately.
|
||||
* Instead, it will enter the beginning of the close transition and it must continue to display
|
||||
* content for a few more seconds due to the ongoing animation. To handle this, the open state
|
||||
* is decoupled from the store state, by assigning references to the required store properties
|
||||
* to render the component in the subscribe callback.
|
||||
*/
|
||||
useEffect(
|
||||
() =>
|
||||
subscribeKey(explorerStore, 'quickViewObject', () => {
|
||||
const { quickViewObject } = explorerStore;
|
||||
if (quickViewObject != null) {
|
||||
setIsOpen(true);
|
||||
setExplorerItem(quickViewObject);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}),
|
||||
[explorerStore]
|
||||
const useQuickPreviewContext = () => {
|
||||
const context = useContext(QuickPreviewContext);
|
||||
|
||||
if (!context) throw new Error('QuickPreviewContext.Provider not found!');
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export const QuickPreview = () => {
|
||||
const os = useOperatingSystem();
|
||||
const rspc = useRspcLibraryContext();
|
||||
const isDark = useIsDark();
|
||||
const { library } = useLibraryContext();
|
||||
const { openFilePaths, revealItems } = usePlatform();
|
||||
|
||||
const explorer = useExplorerContext();
|
||||
const { open, itemIndex } = useQuickPreviewStore();
|
||||
|
||||
const [loadOriginal, setLoadOriginal] = useState(false);
|
||||
const [showMetadata, setShowMetadata] = useState<boolean>(false);
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState<boolean>(false);
|
||||
const [isRenaming, setIsRenaming] = useState<boolean>(false);
|
||||
const [newName, setNewName] = useState<string | null>(null);
|
||||
|
||||
const items = useMemo(
|
||||
() => (open ? [...explorer.selectedItems] : []),
|
||||
[explorer.selectedItems, open]
|
||||
);
|
||||
|
||||
const transitions = useTransition(isOpen, {
|
||||
const item = useMemo(() => items[itemIndex], [items, itemIndex]);
|
||||
|
||||
const transitions = useTransition(open, {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: `translateY(20px) scale(0.9)`,
|
||||
transformOrigin: transformOrigin || 'center top'
|
||||
transformOrigin: 'center top'
|
||||
},
|
||||
enter: { opacity: 1, transform: `translateY(0px) scale(1)` },
|
||||
leave: { opacity: 0, transform: `translateY(40px) scale(0.9)` },
|
||||
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'])
|
||||
});
|
||||
|
||||
const changeCurrentItem = (index: number) => {
|
||||
if (!items[index]) return;
|
||||
|
||||
getQuickPreviewStore().itemIndex = index;
|
||||
setNewName(null);
|
||||
setLoadOriginal(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoadOriginal(false);
|
||||
|
||||
if (!open || !item) {
|
||||
getQuickPreviewStore().itemIndex = 0;
|
||||
setShowMetadata(false);
|
||||
setNewName(null);
|
||||
|
||||
if (!item) getQuickPreviewStore().open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const { kind } = getExplorerItemData(item);
|
||||
|
||||
if (iconKinds.includes(kind) || !heavyKinds.includes(kind)) {
|
||||
setLoadOriginal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => setLoadOriginal(true), 350);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [item, open]);
|
||||
|
||||
// Toggle quick preview
|
||||
useKeyBind(['space'], (e) => {
|
||||
if (isRenaming) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
getQuickPreviewStore().open = !open;
|
||||
});
|
||||
|
||||
// Move between items
|
||||
useKeyBind([['left'], ['right']], (e) => {
|
||||
if (isContextMenuOpen || isRenaming) return;
|
||||
changeCurrentItem(e.key === 'ArrowLeft' ? itemIndex - 1 : itemIndex + 1);
|
||||
});
|
||||
|
||||
// Toggle metadata
|
||||
useKeyBind([os === 'macOS' ? ModifierKeys.Meta : ModifierKeys.Control, 'i'], () =>
|
||||
setShowMetadata(!showMetadata)
|
||||
);
|
||||
|
||||
// Open file
|
||||
useKeyBind([os === 'macOS' ? ModifierKeys.Meta : ModifierKeys.Control, 'o'], () => {
|
||||
if (!item || !openFilePaths) return;
|
||||
|
||||
try {
|
||||
const path = getIndexedItemFilePath(item);
|
||||
|
||||
if (!path) throw 'No path found';
|
||||
|
||||
openFilePaths(library.uuid, [path.id]);
|
||||
} catch (error) {
|
||||
toast.error({
|
||||
title: 'Failed to open file',
|
||||
body: `Couldn't open file, due to an error: ${error}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Reveal in native explorer
|
||||
useKeyBind([os === 'macOS' ? ModifierKeys.Meta : ModifierKeys.Control, 'y'], () => {
|
||||
if (!item || !revealItems) return;
|
||||
|
||||
try {
|
||||
const id = item.type === 'Location' ? item.item.id : getIndexedItemFilePath(item)?.id;
|
||||
|
||||
if (!id) throw 'No id found';
|
||||
|
||||
revealItems(library.uuid, [
|
||||
{ ...(item.type === 'Location' ? { Location: { id } } : { FilePath: { id } }) }
|
||||
]);
|
||||
} catch (error) {
|
||||
toast.error({
|
||||
title: 'Failed to reveal',
|
||||
body: `Couldn't reveal file, due to an error: ${error}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Open delete dialog
|
||||
useKeyBind([os === 'macOS' ? ModifierKeys.Meta : ModifierKeys.Control, 'backspace'], () => {
|
||||
if (!item) return;
|
||||
|
||||
const path = getIndexedItemFilePath(item);
|
||||
|
||||
if (!path || path.location_id === null) return;
|
||||
|
||||
dialogManager.create((dp) => (
|
||||
<DeleteDialog {...dp} locationId={path.location_id!} pathIds={[path.id]} />
|
||||
));
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog.Root
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsOpen(open);
|
||||
if (!open) explorerStore.quickViewObject = null;
|
||||
}}
|
||||
>
|
||||
{transitions((styles, show) => {
|
||||
if (!show || explorerItem == null) return null;
|
||||
<Dialog.Root open={open} onOpenChange={(open) => (getQuickPreviewStore().open = open)}>
|
||||
{transitions((styles, show) => {
|
||||
if (!show || !item) return null;
|
||||
|
||||
const { name } = getExplorerItemData(explorerItem);
|
||||
const { kind, ...itemData } = getExplorerItemData(item);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog.Portal forceMount>
|
||||
<AnimatedDialogOverlay
|
||||
style={{
|
||||
opacity: styles.opacity
|
||||
}}
|
||||
className="z-49 absolute inset-0 m-[1px] grid place-items-center overflow-y-auto rounded-xl bg-app/50"
|
||||
/>
|
||||
<AnimatedDialogContent
|
||||
style={styles}
|
||||
className="!pointer-events-none absolute inset-0 z-50 grid h-screen place-items-center"
|
||||
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'
|
||||
)}
|
||||
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'
|
||||
)}
|
||||
>
|
||||
<div className="!pointer-events-auto flex h-5/6 max-h-screen w-11/12 flex-col overflow-y-auto rounded-md border border-app-line bg-app-box text-ink shadow-app-shade">
|
||||
<nav className="relative flex w-full flex-row">
|
||||
<Dialog.Close
|
||||
asChild
|
||||
className="absolute m-2"
|
||||
aria-label="Close"
|
||||
>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="flex flex-row"
|
||||
>
|
||||
<X
|
||||
weight="bold"
|
||||
className=" h-3 w-3 text-ink-faint"
|
||||
<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={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>
|
||||
</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);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-1 text-tiny font-medium text-ink-faint">
|
||||
ESC
|
||||
</span>
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Title className="mx-auto my-2 font-bold">
|
||||
Preview -{' '}
|
||||
<span className="inline-block max-w-xs truncate align-sub text-sm text-ink-dull">
|
||||
{name || 'Unknown Object'}
|
||||
</span>
|
||||
</Dialog.Title>
|
||||
</nav>
|
||||
<div className="flex h-full w-full shrink items-center justify-center overflow-hidden">
|
||||
) : (
|
||||
<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>
|
||||
|
||||
{loadOriginal && (
|
||||
<FileThumb
|
||||
data={explorerItem}
|
||||
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'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AnimatedDialogContent>
|
||||
</Dialog.Portal>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Dialog.Root>
|
||||
</>
|
||||
|
||||
{showMetadata && (
|
||||
<div className="no-scrollbar w-64 shrink-0 border-l border-app-line bg-app-darkBox py-1">
|
||||
<SingleItemMetadata item={item} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AnimatedDialogContent>
|
||||
</Dialog.Portal>
|
||||
</QuickPreviewContext.Provider>
|
||||
);
|
||||
})}
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
interface RenameInputProps {
|
||||
name: string;
|
||||
onRename: (name: string) => void;
|
||||
}
|
||||
|
||||
const RenameInput = ({ name, onRename }: RenameInputProps) => {
|
||||
const isDark = useIsDark();
|
||||
|
||||
const os = useOperatingSystem();
|
||||
|
||||
const quickPreview = useQuickPreviewContext();
|
||||
|
||||
const _ref = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const form = useZodForm({ schema: z.object({ name: z.string() }), defaultValues: { name } });
|
||||
|
||||
const onSubmit = form.handleSubmit(({ name }) => onRename(name));
|
||||
|
||||
const { ref, ...register } = form.register('name', {
|
||||
onBlur: onSubmit
|
||||
});
|
||||
|
||||
const highlightName = useCallback(() => {
|
||||
const endRange = name.lastIndexOf('.');
|
||||
setTimeout(() => _ref.current?.setSelectionRange(0, endRange || name.length));
|
||||
}, [name]);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
switch (e.key) {
|
||||
case 'Tab': {
|
||||
e.preventDefault();
|
||||
onSubmit();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Escape': {
|
||||
form.reset();
|
||||
onSubmit();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'z': {
|
||||
if (os === 'macOS' ? e.metaKey : e.ctrlKey) {
|
||||
form.reset();
|
||||
highlightName();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (document.activeElement !== _ref.current) highlightName();
|
||||
}, [highlightName]);
|
||||
|
||||
return (
|
||||
<Form form={form} onSubmit={onSubmit} className="w-1/2">
|
||||
<input
|
||||
autoFocus
|
||||
autoCorrect="off"
|
||||
className={clsx(
|
||||
'w-full rounded border px-2 py-1 text-center outline-none',
|
||||
quickPreview.background
|
||||
? 'border-white/[.12] bg-white/10 backdrop-blur-sm'
|
||||
: isDark
|
||||
? 'border-app-line bg-app-input'
|
||||
: 'border-black/[.075] bg-black/[.075]'
|
||||
)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => highlightName()}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
_ref.current = e;
|
||||
}}
|
||||
{...register}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const IconButton = ({
|
||||
className,
|
||||
active,
|
||||
...props
|
||||
}: ButtonHTMLAttributes<HTMLButtonElement> & { active?: boolean }) => {
|
||||
const isDark = useIsDark();
|
||||
|
||||
const quickPreview = useQuickPreviewContext();
|
||||
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
'text-md inline-flex h-[30px] w-[30px] items-center justify-center rounded opacity-80 outline-none backdrop-blur-none',
|
||||
'hover:opacity-100 hover:backdrop-blur',
|
||||
'focus:opacity-100 focus:backdrop-blur',
|
||||
'disabled:pointer-events-none disabled:opacity-40',
|
||||
isDark || quickPreview.background
|
||||
? quickPreview.background
|
||||
? 'hover:bg-white/[.15] focus:bg-white/[.15]'
|
||||
: 'hover:bg-app-box focus:bg-app-box'
|
||||
: 'hover:bg-black/[.075] focus:bg-black/[.075]',
|
||||
active && [
|
||||
'!opacity-100 backdrop-blur',
|
||||
isDark || quickPreview.background
|
||||
? quickPreview.background
|
||||
? 'bg-white/[.15]'
|
||||
: 'bg-app-box'
|
||||
: 'bg-black/[.075]'
|
||||
],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
9
interface/app/$libraryId/Explorer/QuickPreview/store.ts
Normal file
9
interface/app/$libraryId/Explorer/QuickPreview/store.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { proxy, useSnapshot } from 'valtio';
|
||||
|
||||
const store = proxy({
|
||||
open: false,
|
||||
itemIndex: 0
|
||||
});
|
||||
|
||||
export const useQuickPreviewStore = () => useSnapshot(store);
|
||||
export const getQuickPreviewStore = () => store;
|
|
@ -1,5 +1,6 @@
|
|||
import { useLibraryContext } from '@sd/client';
|
||||
import { ContextMenu, ModifierKeys } from '@sd/ui';
|
||||
import { ModifierKeys } from '@sd/ui';
|
||||
import { Menu } from '~/components/Menu';
|
||||
import { useOperatingSystem } from '~/hooks';
|
||||
import { useKeybindFactory } from '~/hooks/useKeybindFactory';
|
||||
import { NonEmptyArray } from '~/util';
|
||||
|
@ -24,7 +25,7 @@ export const RevealInNativeExplorerBase = (props: { items: RevealItems }) => {
|
|||
const osFileBrowserName = lookup[os] ?? 'file manager';
|
||||
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
<Menu.Item
|
||||
label={`Reveal in ${osFileBrowserName}`}
|
||||
keybind={keybind([ModifierKeys.Control], ['Y'])}
|
||||
onClick={() => revealItems(library.uuid, props.items)}
|
||||
|
|
|
@ -217,10 +217,10 @@ export default ({ children }: { children: RenderItem }) => {
|
|||
}, [explorer.selectedItems]);
|
||||
|
||||
useKey(['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'], (e) => {
|
||||
if (explorer.selectedItems.size > 0) e.preventDefault();
|
||||
|
||||
if (!explorerView.selectable) return;
|
||||
|
||||
if (explorer.selectedItems.size > 0) e.preventDefault();
|
||||
|
||||
const lastItem = activeItem.current;
|
||||
if (!lastItem) return;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { type ExplorerItem, byteSize, getItemFilePath, getItemLocation } from '@
|
|||
import { ViewItem } from '.';
|
||||
import { useExplorerContext } from '../Context';
|
||||
import { FileThumb } from '../FilePath/Thumb';
|
||||
import { useQuickPreviewStore } from '../QuickPreview/store';
|
||||
import { useExplorerViewContext } from '../ViewContext';
|
||||
import GridList from './GridList';
|
||||
import RenamableItemText from './RenamableItemText';
|
||||
|
@ -67,6 +68,7 @@ const GridViewItem = memo(({ data, selected, cut, isRenaming, renamable }: GridV
|
|||
export default () => {
|
||||
const explorer = useExplorerContext();
|
||||
const explorerView = useExplorerViewContext();
|
||||
const quickPreviewStore = useQuickPreviewStore();
|
||||
|
||||
return (
|
||||
<GridList>
|
||||
|
@ -76,7 +78,7 @@ export default () => {
|
|||
selected={selected}
|
||||
cut={cut}
|
||||
isRenaming={explorerView.isRenaming}
|
||||
renamable={explorer.selectedItems.size === 1}
|
||||
renamable={explorer.selectedItems.size === 1 && !quickPreviewStore.open}
|
||||
/>
|
||||
)}
|
||||
</GridList>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Button } from '@sd/ui';
|
|||
import { ViewItem } from '.';
|
||||
import { useExplorerContext } from '../Context';
|
||||
import { FileThumb } from '../FilePath/Thumb';
|
||||
import { getExplorerStore, useExplorerStore } from '../store';
|
||||
import { getQuickPreviewStore } from '../QuickPreview/store';
|
||||
import GridList from './GridList';
|
||||
|
||||
interface MediaViewItemProps {
|
||||
|
@ -44,7 +44,7 @@ const MediaViewItem = memo(({ data, selected, cut }: MediaViewItemProps) => {
|
|||
variant="gray"
|
||||
size="icon"
|
||||
className="absolute right-2 top-2 hidden rounded-full shadow group-hover:block"
|
||||
onClick={() => (getExplorerStore().quickViewObject = data)}
|
||||
onClick={() => (getQuickPreviewStore().open = true)}
|
||||
>
|
||||
<ArrowsOutSimple />
|
||||
</Button>
|
||||
|
|
|
@ -32,6 +32,7 @@ import CreateDialog from '../../settings/library/tags/CreateDialog';
|
|||
import { useExplorerContext } from '../Context';
|
||||
import { QuickPreview } from '../QuickPreview';
|
||||
import { useQuickPreviewContext } from '../QuickPreview/Context';
|
||||
import { getQuickPreviewStore, useQuickPreviewStore } from '../QuickPreview/store';
|
||||
import { type ExplorerViewContext, ViewContext, useExplorerViewContext } from '../ViewContext';
|
||||
import { useExplorerConfigStore } from '../config';
|
||||
import { getExplorerStore } from '../store';
|
||||
|
@ -57,10 +58,17 @@ export const ViewItem = ({ data, children, ...props }: ViewItemProps) => {
|
|||
const updateAccessTime = useLibraryMutation('files.updateAccessTime');
|
||||
|
||||
const onDoubleClick = async () => {
|
||||
const selectedItems = [...explorer.selectedItems].reduce(
|
||||
(items, item) => {
|
||||
const selectedItems = [...explorer.selectedItems];
|
||||
|
||||
if (!isNonEmpty(selectedItems)) return;
|
||||
|
||||
let itemIndex = 0;
|
||||
const items = selectedItems.reduce(
|
||||
(items, item, i) => {
|
||||
const sameAsClicked = uniqueId(data) === uniqueId(item);
|
||||
|
||||
if (sameAsClicked) itemIndex = i;
|
||||
|
||||
switch (item.type) {
|
||||
case 'Location': {
|
||||
items.locations.splice(sameAsClicked ? 0 : -1, 0, item.item);
|
||||
|
@ -99,32 +107,31 @@ export const ViewItem = ({ data, children, ...props }: ViewItemProps) => {
|
|||
}
|
||||
);
|
||||
|
||||
if (selectedItems.paths.length > 0 && !explorerView.isRenaming) {
|
||||
if (items.paths.length > 0 && !explorerView.isRenaming) {
|
||||
if (explorerConfig.openOnDoubleClick && openFilePaths) {
|
||||
updateAccessTime
|
||||
.mutateAsync(
|
||||
selectedItems.paths.map(({ object_id }) => object_id!).filter(Boolean)
|
||||
)
|
||||
.mutateAsync(items.paths.map(({ object_id }) => object_id!).filter(Boolean))
|
||||
.catch(console.error);
|
||||
|
||||
try {
|
||||
await openFilePaths(
|
||||
library.uuid,
|
||||
selectedItems.paths.map(({ id }) => id)
|
||||
items.paths.map(({ id }) => id)
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error({ title: 'Failed to open file', body: `Error: ${error}.` });
|
||||
}
|
||||
} else if (!explorerConfig.openOnDoubleClick) {
|
||||
if (data.type !== 'Location' && !(isPath(data) && data.item.is_dir)) {
|
||||
getExplorerStore().quickViewObject = data;
|
||||
getQuickPreviewStore().itemIndex = itemIndex;
|
||||
getQuickPreviewStore().open = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItems.dirs.length > 0) {
|
||||
const [item] = selectedItems.dirs;
|
||||
if (items.dirs.length > 0) {
|
||||
const [item] = items.dirs;
|
||||
if (item) {
|
||||
navigate({
|
||||
pathname: `../location/${item.location_id}`,
|
||||
|
@ -136,8 +143,8 @@ export const ViewItem = ({ data, children, ...props }: ViewItemProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (selectedItems.locations.length > 0) {
|
||||
const [location] = selectedItems.locations;
|
||||
if (items.locations.length > 0) {
|
||||
const [location] = items.locations;
|
||||
if (location) {
|
||||
navigate({
|
||||
pathname: `../location/${location.id}`,
|
||||
|
@ -149,8 +156,8 @@ export const ViewItem = ({ data, children, ...props }: ViewItemProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (selectedItems.non_indexed.length > 0) {
|
||||
const [non_indexed] = selectedItems.non_indexed;
|
||||
if (items.non_indexed.length > 0) {
|
||||
const [non_indexed] = items.non_indexed;
|
||||
if (non_indexed) {
|
||||
navigate({
|
||||
search: createSearchParams({ path: non_indexed.path }).toString()
|
||||
|
@ -189,8 +196,9 @@ export interface ExplorerViewProps
|
|||
|
||||
export default memo(({ className, style, emptyNotice, ...contextProps }: ExplorerViewProps) => {
|
||||
const explorer = useExplorerContext();
|
||||
const quickPreviewStore = useQuickPreviewStore();
|
||||
|
||||
const quickPreviewCtx = useQuickPreviewContext();
|
||||
const quickPreview = useQuickPreviewContext();
|
||||
|
||||
const { layoutMode } = explorer.useSettingsSnapshot();
|
||||
|
||||
|
@ -200,20 +208,9 @@ export default memo(({ className, style, emptyNotice, ...contextProps }: Explore
|
|||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
|
||||
useKeyDownHandlers({
|
||||
isRenaming
|
||||
disabled: isRenaming || quickPreviewStore.open
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// using .next() is not great
|
||||
const explorerStore = getExplorerStore();
|
||||
const selectedItem = explorer.selectedItems.values().next().value as
|
||||
| ExplorerItem
|
||||
| undefined;
|
||||
if (explorerStore.quickViewObject != null && selectedItem) {
|
||||
explorerStore.quickViewObject = selectedItem;
|
||||
}
|
||||
}, [explorer.selectedItems]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
@ -228,17 +225,18 @@ export default memo(({ className, style, emptyNotice, ...contextProps }: Explore
|
|||
>
|
||||
{explorer.items === null || (explorer.items && explorer.items.length > 0) ? (
|
||||
<ViewContext.Provider
|
||||
value={
|
||||
{
|
||||
...contextProps,
|
||||
selectable:
|
||||
explorer.selectable && !isContextMenuOpen && !isRenaming,
|
||||
setIsContextMenuOpen,
|
||||
isRenaming,
|
||||
setIsRenaming,
|
||||
ref
|
||||
} as ExplorerViewContext
|
||||
}
|
||||
value={{
|
||||
...contextProps,
|
||||
selectable:
|
||||
explorer.selectable &&
|
||||
!isContextMenuOpen &&
|
||||
!isRenaming &&
|
||||
!quickPreviewStore.open,
|
||||
setIsContextMenuOpen,
|
||||
isRenaming,
|
||||
setIsRenaming,
|
||||
ref
|
||||
}}
|
||||
>
|
||||
{layoutMode === 'grid' && <GridView />}
|
||||
{layoutMode === 'list' && <ListView />}
|
||||
|
@ -248,7 +246,8 @@ export default memo(({ className, style, emptyNotice, ...contextProps }: Explore
|
|||
emptyNotice
|
||||
)}
|
||||
</div>
|
||||
{quickPreviewCtx.ref && createPortal(<QuickPreview />, quickPreviewCtx.ref)}
|
||||
|
||||
{quickPreview.ref && createPortal(<QuickPreview />, quickPreview.ref)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -284,7 +283,7 @@ export const EmptyNotice = (props: { icon?: Icon | ReactNode; message?: ReactNod
|
|||
);
|
||||
};
|
||||
|
||||
const useKeyDownHandlers = ({ isRenaming }: { isRenaming: boolean }) => {
|
||||
const useKeyDownHandlers = ({ disabled }: { disabled: boolean }) => {
|
||||
const explorer = useExplorerContext();
|
||||
|
||||
const os = useOperatingSystem();
|
||||
|
@ -316,7 +315,7 @@ const useKeyDownHandlers = ({ isRenaming }: { isRenaming: boolean }) => {
|
|||
const handleOpenShortcut = useCallback(
|
||||
async (event: KeyboardEvent) => {
|
||||
if (
|
||||
event.code.toUpperCase() !== 'O' ||
|
||||
event.key.toUpperCase() !== 'O' ||
|
||||
!event.getModifierState(
|
||||
os === 'macOS' ? ModifierKeys.Meta : ModifierKeys.Control
|
||||
) ||
|
||||
|
@ -345,22 +344,6 @@ const useKeyDownHandlers = ({ isRenaming }: { isRenaming: boolean }) => {
|
|||
[os, library.uuid, openFilePaths, explorer.selectedItems]
|
||||
);
|
||||
|
||||
const handleOpenQuickPreview = useCallback(
|
||||
async (event: KeyboardEvent) => {
|
||||
if (event.key !== ' ') return;
|
||||
if (!getExplorerStore().quickViewObject) {
|
||||
// ENG-973 - Don't use Set -> Array -> First Item
|
||||
const items = [...explorer.selectedItems];
|
||||
if (!isNonEmpty(items)) return;
|
||||
|
||||
getExplorerStore().quickViewObject = items[0];
|
||||
} else {
|
||||
getExplorerStore().quickViewObject = null;
|
||||
}
|
||||
},
|
||||
[explorer.selectedItems]
|
||||
);
|
||||
|
||||
const handleExplorerShortcut = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (
|
||||
|
@ -375,23 +358,12 @@ const useKeyDownHandlers = ({ isRenaming }: { isRenaming: boolean }) => {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handlers = [
|
||||
handleNewTag,
|
||||
handleOpenShortcut,
|
||||
handleOpenQuickPreview,
|
||||
handleExplorerShortcut
|
||||
];
|
||||
const handlers = [handleNewTag, handleOpenShortcut, handleExplorerShortcut];
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (isRenaming) return;
|
||||
if (event.repeat || disabled) return;
|
||||
for (const handler of handlers) handler(event);
|
||||
};
|
||||
document.body.addEventListener('keydown', handler);
|
||||
return () => document.body.removeEventListener('keydown', handler);
|
||||
}, [
|
||||
isRenaming,
|
||||
handleNewTag,
|
||||
handleOpenShortcut,
|
||||
handleOpenQuickPreview,
|
||||
handleExplorerShortcut
|
||||
]);
|
||||
}, [disabled, handleNewTag, handleOpenShortcut, handleExplorerShortcut]);
|
||||
};
|
||||
|
|
|
@ -106,7 +106,6 @@ const state = {
|
|||
mediaPlayerVolume: 0.7,
|
||||
newThumbnails: proxySet() as Set<string>,
|
||||
cutCopyState: { type: 'Idle' } as CutCopyState,
|
||||
quickViewObject: null as ExplorerItem | null,
|
||||
isDragging: false,
|
||||
gridGap: 8
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ export const Component = () => {
|
|||
className={settings.layoutMode === 'list' ? 'min-w-0' : undefined}
|
||||
contextMenu={
|
||||
<ContextMenu>
|
||||
{() => <Conditional items={[ObjectItems.RemoveFromRecents]} />}
|
||||
<Conditional items={[ObjectItems.RemoveFromRecents]} />
|
||||
</ContextMenu>
|
||||
}
|
||||
emptyNotice={
|
||||
|
|
|
@ -3,18 +3,12 @@ import clsx from 'clsx';
|
|||
import { Plus } from 'phosphor-react';
|
||||
import { useRef } from 'react';
|
||||
import { Object, useLibraryMutation, useLibraryQuery, usePlausibleEvent } from '@sd/client';
|
||||
import {
|
||||
ContextMenu,
|
||||
DropdownMenu,
|
||||
ModifierKeys,
|
||||
dialogManager,
|
||||
useContextMenu,
|
||||
useDropdownMenu
|
||||
} from '@sd/ui';
|
||||
import { ModifierKeys, dialogManager } from '@sd/ui';
|
||||
import CreateDialog from '~/app/$libraryId/settings/library/tags/CreateDialog';
|
||||
import { useOperatingSystem } from '~/hooks';
|
||||
import { useScrolled } from '~/hooks/useScrolled';
|
||||
import { keybindForOs } from '~/util/keybinds';
|
||||
import { Menu } from './Menu';
|
||||
|
||||
export default (props: { objects: Object[] }) => {
|
||||
const os = useOperatingSystem();
|
||||
|
@ -44,11 +38,6 @@ export default (props: { objects: Object[] }) => {
|
|||
|
||||
const { isScrolled } = useScrolled(parentRef, 10);
|
||||
|
||||
const isDropdownMenu = useDropdownMenu();
|
||||
const isContextMenu = useContextMenu();
|
||||
const Menu = isDropdownMenu ? DropdownMenu : isContextMenu ? ContextMenu : undefined;
|
||||
|
||||
if (!Menu) return null;
|
||||
return (
|
||||
<>
|
||||
<Menu.Item
|
||||
|
|
48
interface/components/Menu.tsx
Normal file
48
interface/components/Menu.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { useMemo } from 'react';
|
||||
import { ContextMenu, DropdownMenu, useContextMenuContext, useDropdownMenuContext } from '@sd/ui';
|
||||
|
||||
export const useMenu = (): typeof DropdownMenu | typeof ContextMenu | undefined => {
|
||||
const isDropdownMenu = useDropdownMenuContext();
|
||||
const isContextMenu = useContextMenuContext();
|
||||
|
||||
const menu = useMemo(
|
||||
() => (isDropdownMenu ? DropdownMenu : isContextMenu ? ContextMenu : undefined),
|
||||
[isDropdownMenu, isContextMenu]
|
||||
);
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
||||
const Separator = (
|
||||
props: Parameters<typeof ContextMenu.Separator | typeof DropdownMenu.Separator>[0]
|
||||
) => {
|
||||
const Menu = useMenu();
|
||||
|
||||
if (!Menu) return null;
|
||||
|
||||
return <Menu.Separator {...props} />;
|
||||
};
|
||||
|
||||
const SubMenu = (
|
||||
props: Parameters<typeof ContextMenu.SubMenu | typeof DropdownMenu.SubMenu>[0]
|
||||
) => {
|
||||
const Menu = useMenu();
|
||||
|
||||
if (!Menu) return null;
|
||||
|
||||
return <Menu.SubMenu {...props} />;
|
||||
};
|
||||
|
||||
const Item = (props: Parameters<typeof ContextMenu.Item | typeof DropdownMenu.Item>[0]) => {
|
||||
const ContextMenu = useMenu();
|
||||
|
||||
if (!ContextMenu) return null;
|
||||
|
||||
return <ContextMenu.Item {...props} />;
|
||||
};
|
||||
|
||||
export const Menu = {
|
||||
Item,
|
||||
Separator,
|
||||
SubMenu
|
||||
};
|
|
@ -70,6 +70,7 @@ export const TextViewer = memo(
|
|||
ref={ref}
|
||||
tabIndex={0}
|
||||
className={clsx(
|
||||
'text-ink',
|
||||
className,
|
||||
highlight && ['relative !pl-[3.8em]', `language-${highlight.language}`]
|
||||
)}
|
||||
|
|
35
interface/hooks/useKeyBind.ts
Normal file
35
interface/hooks/useKeyBind.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { DependencyList } from 'react';
|
||||
import { HotkeyCallback, Options, useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
interface UseKeyBindOptions extends Options {
|
||||
repeatable?: boolean;
|
||||
}
|
||||
|
||||
type UseKeyBindOptionsOrDependencyArray = UseKeyBindOptions | DependencyList;
|
||||
|
||||
export const useKeyBind = (
|
||||
keys: string | string[] | string[][],
|
||||
callback: HotkeyCallback,
|
||||
options?: UseKeyBindOptionsOrDependencyArray,
|
||||
dependencies?: UseKeyBindOptionsOrDependencyArray
|
||||
) => {
|
||||
const keyCombination = Array.isArray(keys)
|
||||
? Array.isArray(keys[0])
|
||||
? keys.map((k) => (k as string[]).join('+'))
|
||||
: keys.join('+')
|
||||
: keys;
|
||||
|
||||
const repeatable =
|
||||
typeof options === 'object' && 'repeatable' in options
|
||||
? options.repeatable
|
||||
: typeof dependencies === 'object' && 'repeatable' in dependencies
|
||||
? dependencies.repeatable
|
||||
: false;
|
||||
|
||||
return useHotkeys(
|
||||
keyCombination,
|
||||
(e, k) => (repeatable || !e.repeat) && callback(e, k),
|
||||
options,
|
||||
dependencies
|
||||
);
|
||||
};
|
|
@ -55,6 +55,7 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "~7.45.2",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-qr-code": "^2.0.11",
|
||||
|
|
|
@ -12,6 +12,14 @@ export function getItemFilePath(data: ExplorerItem) {
|
|||
return (data.type === 'Object' && data.item.file_paths[0]) || null;
|
||||
}
|
||||
|
||||
export function getIndexedItemFilePath(data: ExplorerItem) {
|
||||
return data.type === 'Path'
|
||||
? data.item
|
||||
: data.type === 'Object'
|
||||
? data.item.file_paths[0] ?? null
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getItemLocation(data: ExplorerItem) {
|
||||
return data.type === 'Location' ? data.item : null;
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
"@headlessui/tailwindcss": "^0.1.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
"@radix-ui/react-context-menu": "^1.0.0",
|
||||
"@radix-ui/react-context-menu": "^2.1.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-radio-group": "^1.1.0",
|
||||
"@radix-ui/react-select": "^1.1.2",
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as RadixCM from '@radix-ui/react-context-menu';
|
|||
import { VariantProps, cva } from 'class-variance-authority';
|
||||
import clsx from 'clsx';
|
||||
import { CaretRight, Icon, IconProps } from 'phosphor-react';
|
||||
import { PropsWithChildren, Suspense, createContext, useContext } from 'react';
|
||||
import { ContextType, PropsWithChildren, Suspense, createContext, useContext } from 'react';
|
||||
|
||||
interface ContextMenuProps extends RadixCM.MenuContentProps {
|
||||
trigger: React.ReactNode;
|
||||
|
@ -19,8 +19,17 @@ export const contextMenuClassNames = clsx(
|
|||
'animate-in fade-in'
|
||||
);
|
||||
|
||||
const context = createContext<boolean>(false);
|
||||
export const useContextMenu = () => useContext(context);
|
||||
const ContextMenuContext = createContext<boolean | null>(null);
|
||||
|
||||
export const useContextMenuContext = <T extends boolean>({ suspense }: { suspense?: T } = {}) => {
|
||||
const ctx = useContext(ContextMenuContext);
|
||||
|
||||
if (suspense && ctx === null) throw new Error('ContextMenuContext.Provider not found!');
|
||||
|
||||
return ctx as T extends true
|
||||
? NonNullable<ContextType<typeof ContextMenuContext>>
|
||||
: NonNullable<ContextType<typeof ContextMenuContext>> | undefined;
|
||||
};
|
||||
|
||||
const Root = ({
|
||||
trigger,
|
||||
|
@ -37,7 +46,9 @@ const Root = ({
|
|||
</RadixCM.Trigger>
|
||||
<RadixCM.Portal>
|
||||
<RadixCM.Content className={clsx(contextMenuClassNames, className)} {...props}>
|
||||
<context.Provider value={true}>{children}</context.Provider>
|
||||
<ContextMenuContext.Provider value={true}>
|
||||
{children}
|
||||
</ContextMenuContext.Provider>
|
||||
</RadixCM.Content>
|
||||
</RadixCM.Portal>
|
||||
</RadixCM.Root>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import * as RadixDM from '@radix-ui/react-dropdown-menu';
|
||||
import clsx from 'clsx';
|
||||
import React, {
|
||||
ContextType,
|
||||
PropsWithChildren,
|
||||
Suspense,
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useRef,
|
||||
|
@ -17,24 +19,38 @@ import {
|
|||
contextMenuSeparatorClassNames
|
||||
} from './ContextMenu';
|
||||
|
||||
interface DropdownMenuProps extends RadixDM.MenuContentProps {
|
||||
interface DropdownMenuProps
|
||||
extends RadixDM.MenuContentProps,
|
||||
Pick<RadixDM.DropdownMenuProps, 'onOpenChange'> {
|
||||
trigger: React.ReactNode;
|
||||
triggerClassName?: string;
|
||||
alignToTrigger?: boolean;
|
||||
}
|
||||
|
||||
const context = React.createContext<boolean>(false);
|
||||
export const useDropdownMenu = () => useContext(context);
|
||||
const DropdownMenuContext = createContext<boolean | null>(null);
|
||||
|
||||
export const useDropdownMenuContext = <T extends boolean>({ suspense }: { suspense?: T } = {}) => {
|
||||
const ctx = useContext(DropdownMenuContext);
|
||||
|
||||
if (suspense && ctx === null) throw new Error('DropdownMenuContext.Provider not found!');
|
||||
|
||||
return ctx as T extends true
|
||||
? NonNullable<ContextType<typeof DropdownMenuContext>>
|
||||
: NonNullable<ContextType<typeof DropdownMenuContext>> | undefined;
|
||||
};
|
||||
|
||||
const Root = (props: PropsWithChildren<DropdownMenuProps>) => {
|
||||
const {
|
||||
alignToTrigger,
|
||||
onOpenChange,
|
||||
trigger,
|
||||
triggerClassName,
|
||||
asChild = true,
|
||||
className,
|
||||
children,
|
||||
...contentProps
|
||||
} = props;
|
||||
|
||||
const Root = ({
|
||||
trigger,
|
||||
children,
|
||||
className,
|
||||
asChild = true,
|
||||
triggerClassName,
|
||||
alignToTrigger,
|
||||
...props
|
||||
}: PropsWithChildren<DropdownMenuProps>) => {
|
||||
const [width, setWidth] = useState<number>();
|
||||
|
||||
const measureRef = useCallback(
|
||||
|
@ -45,8 +61,8 @@ const Root = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<RadixDM.Root>
|
||||
<RadixDM.Trigger ref={measureRef} asChild={asChild} className={triggerClassName}>
|
||||
<RadixDM.Root onOpenChange={onOpenChange}>
|
||||
<RadixDM.Trigger ref={measureRef} className={triggerClassName} asChild={asChild}>
|
||||
{trigger}
|
||||
</RadixDM.Trigger>
|
||||
<RadixDM.Portal>
|
||||
|
@ -54,9 +70,11 @@ const Root = ({
|
|||
className={clsx(contextMenuClassNames, width && '!min-w-0', className)}
|
||||
align="start"
|
||||
style={{ width }}
|
||||
{...props}
|
||||
{...contentProps}
|
||||
>
|
||||
<context.Provider value={true}>{children}</context.Provider>
|
||||
<DropdownMenuContext.Provider value={true}>
|
||||
{children}
|
||||
</DropdownMenuContext.Provider>
|
||||
</RadixDM.Content>
|
||||
</RadixDM.Portal>
|
||||
</RadixDM.Root>
|
||||
|
|
|
@ -8,11 +8,12 @@ export interface TooltipProps extends PropsWithChildren {
|
|||
className?: string;
|
||||
tooltipClassName?: string;
|
||||
asChild?: boolean;
|
||||
hoverable?: boolean;
|
||||
}
|
||||
|
||||
export const Tooltip = ({ position = 'bottom', ...props }: TooltipProps) => {
|
||||
export const Tooltip = ({ position = 'bottom', hoverable = true, ...props }: TooltipProps) => {
|
||||
return (
|
||||
<TooltipPrimitive.Provider>
|
||||
<TooltipPrimitive.Provider disableHoverableContent={!hoverable}>
|
||||
<TooltipPrimitive.Root>
|
||||
<TooltipPrimitive.Trigger asChild>
|
||||
{props.asChild ? (
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
export { cva, cx } from 'class-variance-authority';
|
||||
export * from './Button';
|
||||
export * from './CheckBox';
|
||||
export { ContextMenu, useContextMenu } from './ContextMenu';
|
||||
export { DropdownMenu, useDropdownMenu } from './DropdownMenu';
|
||||
export { ContextMenu, useContextMenuContext } from './ContextMenu';
|
||||
export { DropdownMenu, useDropdownMenuContext } from './DropdownMenu';
|
||||
export * from './Dialog';
|
||||
export * as Dropdown from './Dropdown';
|
||||
export * from './Input';
|
||||
|
|
299
pnpm-lock.yaml
299
pnpm-lock.yaml
|
@ -727,6 +727,9 @@ importers:
|
|||
react-hook-form:
|
||||
specifier: ~7.45.2
|
||||
version: 7.45.2(react@18.2.0)
|
||||
react-hotkeys-hook:
|
||||
specifier: ^4.4.1
|
||||
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
|
||||
react-json-view:
|
||||
specifier: ^1.21.3
|
||||
version: 1.21.3(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -933,14 +936,14 @@ importers:
|
|||
specifier: ^1.0.3
|
||||
version: 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-context-menu':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^2.1.4
|
||||
version: 2.1.4(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-popover':
|
||||
specifier: ^1.0.6
|
||||
version: 1.0.6(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -6385,18 +6388,6 @@ packages:
|
|||
'@babel/runtime': 7.22.11
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-arrow@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-arrow@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1yientwXqXcErDHEv8av9ZVNEBldH8L9scVR3is20lL+jOCfcJyMFZFEY5cgIrgexsq1qggSXqiEL/d/4f+QXA==}
|
||||
peerDependencies:
|
||||
|
@ -6449,21 +6440,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collection@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collection@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==}
|
||||
peerDependencies:
|
||||
|
@ -6526,23 +6502,30 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-context-menu@1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-JkwOgdXwErwEEpsmgu0Ob8zD3gzWS1brPXnNGPyZEtR6/EYyDgruQYKiihXVsCrPCdrNUHawop9I1+6JTdXPTA==}
|
||||
/@radix-ui/react-context-menu@2.1.4(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-HVHLUtZOBiR2Fh5l07qQ9y0IgX4dGZF0S9Gwdk4CVA+DL9afSphvFNa4nRiw6RNgb6quwLV4dLPF/gFDvNaOcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-menu': 1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-menu': 2.0.5(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@types/react': 18.0.38
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-context@1.0.0(react@18.2.0):
|
||||
|
@ -6641,24 +6624,31 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dropdown-menu@1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==}
|
||||
/@radix-ui/react-dropdown-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xdOrZzOTocqqkCkYo8yRPCib5OkTkqN7lqNCdxwPOdE466DOaNl4N8PkUIlsXthQvW5Wwkd+aEmWpfWlBoDPEw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-menu': 1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-menu': 2.0.5(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@types/react': 18.0.38
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards@1.0.0(react@18.2.0):
|
||||
|
@ -6684,20 +6674,6 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-scope@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-scope@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Ej2MQTit8IWJiS2uuujGUmxXjF/y5xZptIIQnyd2JHLwtV0R2j9NRVoRj/1j/gJ7e3REdaBw4Hjf4a1ImhkZcQ==}
|
||||
peerDependencies:
|
||||
|
@ -6760,35 +6736,42 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-menu@1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==}
|
||||
/@radix-ui/react-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Gw4f9pwdH+w5w+49k0gLjN0PfRDHvxmAgG16AbyJZ7zhwZ6PBHKtWohvnSwfusfnK3L68dpBREHpVkj8wEM7ZA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-collection': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-popper': 1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.2(@types/react@18.0.38)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.38)(react@18.2.0)
|
||||
'@types/react': 18.0.38
|
||||
'@types/react-dom': 18.2.4
|
||||
aria-hidden: 1.2.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.4(@types/react@18.0.38)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
react-remove-scroll: 2.5.5(@types/react@18.0.38)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popover@1.0.6(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
|
@ -6826,28 +6809,6 @@ packages:
|
|||
react-remove-scroll: 2.5.5(@types/react@18.0.38)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popper@1.0.0(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@floating-ui/react-dom': 0.7.2(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-arrow': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-rect': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/rect': 1.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popper@1.0.1(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-J4Vj7k3k+EHNWgcKrE+BLlQfpewxA7Zd76h5I0bIa+/EqaIZ3DuwrbPj49O3wqN+STnXsBuxiHLiF0iU3yfovw==}
|
||||
peerDependencies:
|
||||
|
@ -6900,18 +6861,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-NY2vUWI5WENgAT1nfC6JS7RU5xRYBfjZVLq0HmgEN1Ezy3rk/UruMV4+Rd0F40PEaFC5SrLS1ixYvcYIQrb4Ig==}
|
||||
peerDependencies:
|
||||
|
@ -6980,18 +6929,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-primitive@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-slot': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-primitive@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==}
|
||||
peerDependencies:
|
||||
|
@ -7071,26 +7008,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-roving-focus@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lHvO4MhvoWpeNbiJAoyDsEtbKqP2jkkdwsMVJ3kfqbkC71J/aXE6Th6gkZA1xHEqSku+t+UgoDjvE7Z3gsBpcg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-collection': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-roving-focus@1.0.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-TB76u5TIxKpqMpUAuYH2VqMhHYKa+4Vs1NHygo/llLvlffN6mLVsFhz0AnSFlSBAvTBYVHYAkHAyEt7x1gPJOA==}
|
||||
peerDependencies:
|
||||
|
@ -7257,16 +7174,6 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.0.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
|
||||
peerDependencies:
|
||||
|
@ -12750,12 +12657,12 @@ packages:
|
|||
peerDependencies:
|
||||
webpack: ^5.0.0
|
||||
dependencies:
|
||||
icss-utils: 5.1.0(postcss@8.4.23)
|
||||
postcss: 8.4.23
|
||||
postcss-modules-extract-imports: 3.0.0(postcss@8.4.23)
|
||||
postcss-modules-local-by-default: 4.0.3(postcss@8.4.23)
|
||||
postcss-modules-scope: 3.0.0(postcss@8.4.23)
|
||||
postcss-modules-values: 4.0.0(postcss@8.4.23)
|
||||
icss-utils: 5.1.0(postcss@8.4.28)
|
||||
postcss: 8.4.28
|
||||
postcss-modules-extract-imports: 3.0.0(postcss@8.4.28)
|
||||
postcss-modules-local-by-default: 4.0.3(postcss@8.4.28)
|
||||
postcss-modules-scope: 3.0.0(postcss@8.4.28)
|
||||
postcss-modules-values: 4.0.0(postcss@8.4.28)
|
||||
postcss-value-parser: 4.2.0
|
||||
semver: 7.5.4
|
||||
webpack: 5.88.2(esbuild@0.17.19)
|
||||
|
@ -15789,13 +15696,13 @@ packages:
|
|||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
/icss-utils@5.1.0(postcss@8.4.23):
|
||||
/icss-utils@5.1.0(postcss@8.4.28):
|
||||
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
|
||||
engines: {node: ^10 || ^12 || >= 14}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.28
|
||||
dev: false
|
||||
|
||||
/ieee754@1.2.1:
|
||||
|
@ -19487,45 +19394,45 @@ packages:
|
|||
webpack: 5.88.2(esbuild@0.17.19)
|
||||
dev: false
|
||||
|
||||
/postcss-modules-extract-imports@3.0.0(postcss@8.4.23):
|
||||
/postcss-modules-extract-imports@3.0.0(postcss@8.4.28):
|
||||
resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==}
|
||||
engines: {node: ^10 || ^12 || >= 14}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.28
|
||||
dev: false
|
||||
|
||||
/postcss-modules-local-by-default@4.0.3(postcss@8.4.23):
|
||||
/postcss-modules-local-by-default@4.0.3(postcss@8.4.28):
|
||||
resolution: {integrity: sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==}
|
||||
engines: {node: ^10 || ^12 || >= 14}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
icss-utils: 5.1.0(postcss@8.4.23)
|
||||
postcss: 8.4.23
|
||||
icss-utils: 5.1.0(postcss@8.4.28)
|
||||
postcss: 8.4.28
|
||||
postcss-selector-parser: 6.0.13
|
||||
postcss-value-parser: 4.2.0
|
||||
dev: false
|
||||
|
||||
/postcss-modules-scope@3.0.0(postcss@8.4.23):
|
||||
/postcss-modules-scope@3.0.0(postcss@8.4.28):
|
||||
resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==}
|
||||
engines: {node: ^10 || ^12 || >= 14}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.28
|
||||
postcss-selector-parser: 6.0.13
|
||||
dev: false
|
||||
|
||||
/postcss-modules-values@4.0.0(postcss@8.4.23):
|
||||
/postcss-modules-values@4.0.0(postcss@8.4.28):
|
||||
resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
|
||||
engines: {node: ^10 || ^12 || >= 14}
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
icss-utils: 5.1.0(postcss@8.4.23)
|
||||
postcss: 8.4.23
|
||||
icss-utils: 5.1.0(postcss@8.4.28)
|
||||
postcss: 8.4.28
|
||||
dev: false
|
||||
|
||||
/postcss-nested@6.0.1(postcss@8.4.23):
|
||||
|
@ -20146,6 +20053,16 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-hotkeys-hook@4.4.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.1'
|
||||
react-dom: '>=16.8.1'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-inspector@6.0.2(react@18.2.0):
|
||||
resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==}
|
||||
peerDependencies:
|
||||
|
@ -20438,25 +20355,6 @@ packages:
|
|||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll@2.5.4(@types/react@18.0.38)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.38
|
||||
react: 18.2.0
|
||||
react-remove-scroll-bar: 2.3.4(@types/react@18.0.38)(react@18.2.0)
|
||||
react-style-singleton: 2.2.1(@types/react@18.0.38)(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
use-callback-ref: 1.3.0(@types/react@18.0.38)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.0.38)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll@2.5.5(@types/react@18.0.38)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -21030,7 +20928,7 @@ packages:
|
|||
adjust-sourcemap-loader: 4.0.0
|
||||
convert-source-map: 1.9.0
|
||||
loader-utils: 2.0.4
|
||||
postcss: 8.4.23
|
||||
postcss: 8.4.28
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
|
@ -23663,6 +23561,7 @@ packages:
|
|||
rollup: 3.28.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@4.3.9(less@4.2.0):
|
||||
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
|
||||
|
|
Loading…
Reference in a new issue