Merge remote-tracking branch 'origin/main' into general-fixes

This commit is contained in:
Oscar Beaumont 2022-10-17 11:46:45 +08:00
commit 447e2bbba6
31 changed files with 790 additions and 366 deletions

View file

@ -32,7 +32,7 @@ export function AppLayout() {
>
<Sidebar />
<div className="relative flex w-full h-screen max-h-screen bg-white dark:bg-gray-650">
<Suspense fallback={<p>Loading...</p>}>
<Suspense>
<Outlet />
</Suspense>
</div>
@ -60,7 +60,7 @@ function Toasts() {
// }, []);
return (
<div className="fixed flex right-0">
<div className="fixed right-0 flex">
<ToastPrimitive.Provider>
<>
{toasts.map((toast) => (
@ -81,7 +81,7 @@ function Toasts() {
)}
>
<div className="flex">
<div className="w-0 flex-1 flex items-center pl-5 py-4">
<div className="flex items-center flex-1 w-0 py-4 pl-5">
<div className="w-full radix">
<ToastPrimitive.Title className="text-sm font-medium text-gray-900 dark:text-gray-100">
{toast.title}
@ -95,11 +95,11 @@ function Toasts() {
</div>
<div className="flex">
<div className="flex flex-col px-3 py-2 space-y-1">
<div className="h-0 flex-1 flex">
<div className="flex flex-1 h-0">
{toast.actionButton && (
<ToastPrimitive.Action
altText="view now"
className="w-full border border-transparent rounded-lg px-3 py-2 flex items-center justify-center text-sm font-medium text-primary dark:text-primary hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75"
className="flex items-center justify-center w-full px-3 py-2 text-sm font-medium border border-transparent rounded-lg text-primary dark:text-primary hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75"
onClick={(e) => {
e.preventDefault();
toast.actionButton?.onClick();
@ -110,8 +110,8 @@ function Toasts() {
</ToastPrimitive.Action>
)}
</div>
<div className="h-0 flex-1 flex">
<ToastPrimitive.Close className="w-full border border-transparent rounded-lg px-3 py-2 flex items-center justify-center text-sm font-medium text-gray-700 dark:text-gray-100 hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75">
<div className="flex flex-1 h-0">
<ToastPrimitive.Close className="flex items-center justify-center w-full px-3 py-2 text-sm font-medium text-gray-700 border border-transparent rounded-lg dark:text-gray-100 hover:bg-white/10 focus:z-10 focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-opacity-75">
Dismiss
</ToastPrimitive.Close>
</div>

View file

@ -47,7 +47,7 @@ export function AppRouter() {
useInvalidateQuery();
return (
<Suspense fallback={<p>Loading...</p>}>
<Suspense>
<Routes>
<Route path="onboarding" element={<OnboardingScreen />} />
<Route element={<AppLayout />}>
@ -56,7 +56,7 @@ export function AppRouter() {
<Route
path="*"
element={
<h1 className="text-white p-4">
<h1 className="p-4 text-white">
Please select or create a library in the sidebar.
</h1>
}

View file

@ -1,11 +1,11 @@
import { KeyIcon } from '@heroicons/react/24/outline';
import { CogIcon, LockClosedIcon } from '@heroicons/react/24/solid';
import { Button } from '@sd/ui';
import { Loader } from '@sd/ui';
import { Cloud, Desktop, DeviceMobileCamera, DotsSixVertical, Laptop } from 'phosphor-react';
import { useState } from 'react';
import FileItem from '../explorer/FileItem';
import Loader from '../primitive/Loader';
import ProgressBar from '../primitive/ProgressBar';
import { Tooltip } from '../tooltip/Tooltip';

View file

@ -1,10 +1,9 @@
import { useBridgeMutation } from '@sd/client';
import { Input } from '@sd/ui';
import { Dialog } from '@sd/ui';
import { useQueryClient } from '@tanstack/react-query';
import { PropsWithChildren, useState } from 'react';
import Dialog from '../layout/Dialog';
export default function CreateLibraryDialog({
children,
onSubmit

View file

@ -1,9 +1,8 @@
import { useBridgeMutation } from '@sd/client';
import { Dialog } from '@sd/ui';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import Dialog from '../layout/Dialog';
interface Props {
children: React.ReactNode;
libraryUuid: string;

View file

@ -47,7 +47,7 @@ export const Inspector = (props: Props) => {
});
return (
<div className="p-2 pr-1 overflow-x-hidden custom-scroll inspector-scroll pb-[55px]">
<div className="p-2 pr-1 w-full overflow-x-hidden custom-scroll inspector-scroll pb-[55px]">
{!!props.data && (
<>
<div className="flex bg-black items-center justify-center w-full h-64 mb-[10px] overflow-hidden rounded-lg ">

View file

@ -0,0 +1,205 @@
import { InformationCircleIcon } from '@heroicons/react/24/outline';
import {
EyeIcon,
EyeSlashIcon,
KeyIcon,
LockClosedIcon,
LockOpenIcon,
PlusIcon,
TrashIcon,
XMarkIcon
} from '@heroicons/react/24/solid';
import { Button, Input } from '@sd/ui';
import clsx from 'clsx';
import { Eject, EjectSimple, Plus } from 'phosphor-react';
import { useState } from 'react';
import { Toggle } from '../primitive';
import { DefaultProps } from '../primitive/types';
import { Tooltip } from '../tooltip/Tooltip';
export type KeyManagerProps = DefaultProps;
interface FakeKey {
id: string;
name: string;
mounted?: boolean;
locked?: boolean;
stats?: {
objectCount?: number;
containerCount?: number;
};
// Nodes this key is mounted on
nodes?: string[]; // will be node object
}
const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="mt-1 mb-1 text-xs font-semibold text-gray-300">{children}</div>
);
const Key: React.FC<{ data: FakeKey; index: number }> = ({ data, index }) => {
const odd = (index || 0) % 2 === 0;
return (
<div
className={clsx(
'flex items-center justify-between px-2 py-1.5 shadow-gray-900/20 text-sm text-gray-300 bg-gray-500/30 shadow-lg border-gray-500 rounded-lg'
// !odd && 'bg-opacity-10'
)}
>
<div className="flex items-center">
<KeyIcon
className={clsx(
'w-5 h-5 ml-1 mr-3',
data.mounted
? data.locked
? 'text-primary-600'
: 'text-primary-600'
: 'text-gray-400/80'
)}
/>
<div className="flex flex-col ">
<div className="flex flex-row items-center">
<div className="font-semibold">{data.name}</div>
{data.mounted && (
<div className="inline ml-2 px-1 text-[8pt] font-medium text-gray-300 bg-gray-500 rounded">
{data.nodes?.length || 0 > 0 ? `${data.nodes?.length || 0} nodes` : 'This node'}
</div>
)}
</div>
{/* <div className="text-xs text-gray-300 opacity-30">#{data.id}</div> */}
{/* {data.stats && (
<div className="flex flex-row space-x-3">
{data.stats.objectCount && (
<div className="text-[8pt] text-gray-300 opacity-30">
{data.stats.objectCount} Objects
</div>
)}
{data.stats.containerCount && (
<div className="text-[8pt] text-gray-300 opacity-30">
{data.stats.containerCount} Containers
</div>
)}
</div>
)} */}
</div>
</div>
<div className="space-x-1">
{data.mounted ? (
<>
<Tooltip label="Browse files">
<Button noPadding>
<EyeIcon className="w-4 h-4 text-gray-400" />
</Button>
</Tooltip>
{data.locked ? (
<Tooltip label="Unlock key">
<Button noPadding>
<LockClosedIcon className="w-4 h-4 text-gray-400" />
</Button>
</Tooltip>
) : (
<Tooltip label="Lock key">
<Button noPadding>
<LockOpenIcon className="w-4 h-4 text-gray-400" />
</Button>
</Tooltip>
)}
</>
) : (
<Tooltip label="Dismount key">
<Button noPadding>
<XMarkIcon className="w-4 h-4 text-gray-400" />
</Button>
</Tooltip>
)}
</div>
</div>
);
};
export function KeyManager(props: KeyManagerProps) {
const [showKey, setShowKey] = useState(false);
const [toggle, setToggle] = useState(false);
const CurrentEyeIcon = showKey ? EyeSlashIcon : EyeIcon;
return (
<div className="flex flex-col h-full">
<div className="p-3 pt-3">
<Heading>Mount key</Heading>
<div className="flex space-x-2">
<div className="relative flex flex-grow">
<Input autoFocus type={showKey ? 'text' : 'password'} className="flex-grow !py-0.5" />
<Button
onClick={() => setShowKey(!showKey)}
noBorder
noPadding
className="absolute right-[5px] top-[5px]"
>
<CurrentEyeIcon className="w-4 h-4" />
</Button>
</div>
<Tooltip className="flex" label="Mount key">
<Button variant="gray" noPadding>
<Plus weight="fill" className="w-4 h-4 mx-1" />
</Button>
</Tooltip>
</div>
<div className="flex flex-row items-center mt-3 mb-1">
<Toggle className="dark:bg-gray-400/30" size="sm" value={toggle} onChange={setToggle} />
<span className="ml-3 mt-[1px] font-medium text-xs">Sync with Library</span>
<Tooltip label="This key will be mounted on all devices running your Library">
<InformationCircleIcon className="w-4 h-4 ml-1.5 text-gray-400" />
</Tooltip>
</div>
{/* <p className="pt-1.5 ml-0.5 text-[8pt] leading-snug text-gray-300 opacity-50 w-[90%]">
Files encrypted with this key will be revealed and decrypted on the fly.
</p> */}
</div>
<hr className="border-gray-500" />
<div className="p-3 custom-scroll overlay-scroll">
<div className="">
<Heading>Mounted keys</Heading>
<div className="pt-1 space-y-1.5">
<Key
index={0}
data={{
id: 'af5570f5a1810b7a',
name: 'OBS Recordings',
mounted: true,
nodes: ['node1', 'node2'],
stats: { objectCount: 235, containerCount: 2 }
}}
/>
<Key
index={1}
data={{
id: 'af5570f5a1810b7a',
name: 'Unknown Key',
locked: true,
mounted: true,
stats: { objectCount: 45 }
}}
/>
<Key index={2} data={{ id: '7324695a52da67b1', name: 'Spacedrive Company' }} />
<Key index={3} data={{ id: 'b02303d68d05a562', name: 'Key 4' }} />
<Key index={3} data={{ id: 'b02303d68d05a562', name: 'Key 5' }} />
<Key index={3} data={{ id: 'b02303d68d05a562', name: 'Key 6' }} />
</div>
</div>
</div>
<div className="flex w-full p-2 bg-gray-600 border-t border-gray-500 rounded-b-md">
<Button size="sm" variant="gray">
Unmount All
</Button>
<div className="flex-grow" />
<Button size="sm" variant="gray">
Close
</Button>
</div>
</div>
);
}

View file

@ -1,63 +0,0 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Button } from '@sd/ui';
import clsx from 'clsx';
import { ReactNode } from 'react';
import Loader from '../primitive/Loader';
export interface DialogProps extends DialogPrimitive.DialogProps {
trigger: ReactNode;
ctaLabel?: string;
ctaDanger?: boolean;
ctaAction?: () => void;
title?: string;
description?: string;
children?: ReactNode;
loading?: boolean;
submitDisabled?: boolean;
}
export default function Dialog(props: DialogProps) {
return (
<DialogPrimitive.Root open={props.open} onOpenChange={props.onOpenChange}>
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed top-0 dialog-overlay bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]">
<DialogPrimitive.Content className="min-w-[300px] max-w-[400px] dialog-content rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep">
<form onSubmit={(e) => e.preventDefault()}>
<div className="p-5">
<DialogPrimitive.Title className="mb-2 font-bold">
{props.title}
</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-gray-300">
{props.description}
</DialogPrimitive.Description>
{props.children}
</div>
<div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550">
{props.loading && <Loader />}
<div className="flex-grow" />
<DialogPrimitive.Close asChild>
<Button loading={props.loading} disabled={props.loading} size="sm" variant="gray">
Close
</Button>
</DialogPrimitive.Close>
<Button
onClick={props.ctaAction}
type="submit"
size="sm"
loading={props.loading}
disabled={props.loading || props.submitDisabled}
variant={props.ctaDanger ? 'colored' : 'primary'}
className={clsx(props.ctaDanger && 'bg-red-500 border-red-500')}
>
{props.ctaLabel}
</Button>
</div>
</form>
</DialogPrimitive.Content>
</DialogPrimitive.Overlay>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
}

View file

@ -250,6 +250,7 @@ export function Sidebar() {
</NavLink>
<OverlayPanel
className="focus:outline-none"
transformOrigin="bottom left"
disabled={!library}
trigger={
<Button

View file

@ -5,11 +5,12 @@ import {
useExplorerStore,
useLibraryMutation
} from '@sd/client';
import { Dropdown } from '@sd/ui';
import { Dropdown, OverlayPanel } from '@sd/ui';
import clsx from 'clsx';
import {
Aperture,
ArrowsClockwise,
Cloud,
FilmStrip,
IconProps,
Image,
@ -26,47 +27,43 @@ import { useNavigate } from 'react-router-dom';
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
import { KeybindEvent } from '../../util/keybind';
import { KeyManager } from '../key/KeyManager';
import { Shortcut } from '../primitive/Shortcut';
import { DefaultProps } from '../primitive/types';
import { Tooltip } from '../tooltip/Tooltip';
export type TopBarProps = DefaultProps;
export interface TopBarButtonProps
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
export interface TopBarButtonProps {
icon: React.ComponentType<IconProps>;
group?: boolean;
active?: boolean;
left?: boolean;
right?: boolean;
className?: string;
onClick?: () => void;
}
const TopBarButton: React.FC<TopBarButtonProps> = ({
icon: Icon,
left,
right,
group,
active,
className,
...props
}) => {
return (
<button
{...props}
className={clsx(
'mr-[1px] flex py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 rounded-md transition-colors duration-100',
{
'rounded-r-none rounded-l-none': group && !left && !right,
'rounded-r-none': group && left,
'rounded-l-none': group && right,
'dark:bg-gray-500': active
},
className
)}
>
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
</button>
);
};
const TopBarButton = forwardRef<HTMLButtonElement, TopBarButtonProps>(
({ icon: Icon, left, right, group, active, className, ...props }, ref) => {
return (
<button
{...props}
ref={ref}
className={clsx(
'mr-[1px] flex py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 rounded-md open:dark:bg-gray-550 transition-colors duration-100 outline-none !cursor-normal',
{
'rounded-r-none rounded-l-none': group && !left && !right,
'rounded-r-none': group && left,
'rounded-l-none': group && right,
'dark:bg-gray-500': active
},
className
)}
>
<Icon weight={'regular'} className="m-0.5 w-5 h-5 text-gray-450 dark:text-gray-150" />
</button>
);
}
);
const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRef) => {
const {
@ -119,6 +116,8 @@ const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRe
);
});
export type TopBarProps = DefaultProps;
export const TopBar: React.FC<TopBarProps> = (props) => {
const platform = useOperatingSystem(false);
const os = useOperatingSystem(true);
@ -256,20 +255,24 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<SearchBar ref={searchRef} />
<div className="flex mx-8 space-x-2">
<Tooltip label="Major Key Alert">
<TopBarButton icon={Key} />
</Tooltip>
{/* <Tooltip label="Cloud">
<OverlayPanel
className="focus:outline-none"
trigger={
// <Tooltip label="Major Key Alert">
<TopBarButton icon={Key} />
// </Tooltip>
}
>
<div className="block w-[350px] h-[435px]">
<KeyManager />
</div>
</OverlayPanel>
<Tooltip label="Cloud">
<TopBarButton icon={Cloud} />
</Tooltip> */}
{/* <Tooltip label="Refresh">
<TopBarButton
icon={ArrowsClockwise}
onClick={() => {
// generateThumbsForLocation({ id: locationId, path: '' });
}}
/>
</Tooltip> */}
</Tooltip>
<Tooltip label="Refresh">
<TopBarButton icon={ArrowsClockwise} />
</Tooltip>
</div>
</div>
<div className="flex mr-3 space-x-2">
@ -279,7 +282,17 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
className="my-2"
icon={SidebarSimple}
/>
<Dropdown
<OverlayPanel
className="focus:outline-none"
trigger={
// <Tooltip label="Major Key Alert">
<TopBarButton icon={List} className="my-2" />
// </Tooltip>
}
>
<div className="block w-[250px] h-[335px]">{/* <KeyManager /> */}</div>
</OverlayPanel>
{/* <Dropdown
// className="absolute block h-6 w-44 top-2 right-4"
align="right"
items={[
@ -300,7 +313,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
]
]}
buttonComponent={<TopBarButton icon={List} />}
/>
/> */}
</div>
</div>
</>

View file

@ -1,13 +1,12 @@
import { TrashIcon } from '@heroicons/react/24/solid';
import { useLibraryMutation } from '@sd/client';
import { Location, Node } from '@sd/client';
import { Button } from '@sd/ui';
import { Button, Dialog } from '@sd/ui';
import clsx from 'clsx';
import { Repeat } from 'phosphor-react';
import React, { useState } from 'react';
import { Folder } from '../icons/Folder';
import Dialog from '../layout/Dialog';
interface LocationListItemProps {
location: Location & { node: Node };

View file

@ -5,6 +5,7 @@ export interface ToggleProps {
value: boolean;
onChange?: (newValue: boolean) => void;
size?: 'sm' | 'md';
className?: string;
}
export const Toggle: React.FC<ToggleProps> = (props) => {
@ -16,9 +17,10 @@ export const Toggle: React.FC<ToggleProps> = (props) => {
onChange={onChange}
className={clsx(
'transition relative flex-shrink-0 inline-flex items-center h-6 w-11 rounded-full bg-gray-200 dark:bg-gray-550',
props.className,
{
'bg-primary-500 dark:bg-primary-500': isEnabled,
'h-6 w-11': size === 'sm',
'!bg-primary-500 dark:!bg-primary-500': isEnabled,
'h-[20px] w-[35px]': size === 'sm',
'h-8 w-[55px]': size === 'md'
}
)}
@ -28,8 +30,9 @@ export const Toggle: React.FC<ToggleProps> = (props) => {
'transition inline-block w-4 h-4 transform bg-white rounded-full',
isEnabled ? 'translate-x-6' : 'translate-x-1',
{
'w-4 h-4': size === 'sm',
'w-3 h-3': size === 'sm',
'h-6 w-6': size === 'md',
'translate-x-5': size === 'sm' && isEnabled,
'translate-x-7': size === 'md' && isEnabled
}
)}

View file

@ -1,3 +1,3 @@
export const SettingsContainer = ({ children }: { children: React.ReactNode }) => (
<div className="flex flex-col flex-grow max-w-4xl space-y-6 w-ful">{children}</div>
<div className="flex flex-col flex-grow w-full max-w-4xl space-y-6">{children}</div>
);

View file

@ -1,3 +1,4 @@
import clsx from 'clsx';
import { ReactNode } from 'react';
interface SettingsHeaderProps {
@ -18,3 +19,16 @@ export const SettingsHeader: React.FC<SettingsHeaderProps> = (props) => {
</div>
);
};
export const SettingsIcon = ({ component: Icon, ...props }: any) => (
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
);
export const SettingsHeading: React.FC<{ className?: string; children: string }> = ({
children,
className
}) => (
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-400', className)}>
{children}
</div>
);

View file

@ -1,39 +0,0 @@
import clsx from 'clsx';
import { Outlet } from 'react-router';
interface SettingsScreenContainerProps {
children: React.ReactNode;
}
export const SettingsIcon = ({ component: Icon, ...props }: any) => (
<Icon weight="bold" {...props} className={clsx('w-4 h-4 mr-2', props.className)} />
);
export const SettingsHeading: React.FC<{ className?: string; children: string }> = ({
children,
className
}) => (
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-400', className)}>
{children}
</div>
);
export const SettingsScreenContainer: React.FC<SettingsScreenContainerProps> = (props) => {
return (
<div className="flex flex-row w-full">
<div className="h-full border-r max-w-[200px] flex-shrink-0 border-gray-100 w-60 dark:border-gray-550">
<div data-tauri-drag-region className="w-full h-7" />
<div className="p-5 pt-0">{props.children}</div>
</div>
<div className="w-full">
<div data-tauri-drag-region className="w-full h-7" />
<div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll">
<div className="flex flex-grow px-12 pb-5">
<Outlet />
<div className="block h-20" />
</div>
</div>
</div>
</div>
);
};

View file

@ -0,0 +1,84 @@
import { CogIcon, HeartIcon, KeyIcon, ShieldCheckIcon, TagIcon } from '@heroicons/react/24/outline';
import { BuildingLibraryIcon } from '@heroicons/react/24/solid';
import {
FlyingSaucer,
HardDrive,
KeyReturn,
PaintBrush,
PuzzlePiece,
Receipt,
ShareNetwork
} from 'phosphor-react';
import { SidebarLink } from '../layout/Sidebar';
import { SettingsHeading, SettingsIcon } from './SettingsHeader';
export const SettingsSidebar = () => {
return (
<div className="h-full border-r max-w-[200px] flex-shrink-0 border-gray-100 w-60 dark:border-gray-550">
<div data-tauri-drag-region className="w-full h-7" />
<div className="px-4 py-2.5">
<SettingsHeading className="!mt-0">Client</SettingsHeading>
<SidebarLink to="/settings/general">
<SettingsIcon component={CogIcon} />
General
</SidebarLink>
<SidebarLink to="/settings/libraries">
<SettingsIcon component={BuildingLibraryIcon} />
Libraries
</SidebarLink>
<SidebarLink to="/settings/privacy">
<SettingsIcon component={ShieldCheckIcon} />
Privacy
</SidebarLink>
<SidebarLink to="/settings/appearance">
<SettingsIcon component={PaintBrush} />
Appearance
</SidebarLink>
<SidebarLink to="/settings/keybindings">
<SettingsIcon component={KeyReturn} />
Keybindings
</SidebarLink>
<SidebarLink to="/settings/extensions">
<SettingsIcon component={PuzzlePiece} />
Extensions
</SidebarLink>
<SettingsHeading>Library</SettingsHeading>
<SidebarLink to="/settings/library">
<SettingsIcon component={CogIcon} />
General
</SidebarLink>
<SidebarLink to="/settings/nodes">
<SettingsIcon component={ShareNetwork} />
Nodes
</SidebarLink>
<SidebarLink to="/settings/locations">
<SettingsIcon component={HardDrive} />
Locations
</SidebarLink>
<SidebarLink to="/settings/tags">
<SettingsIcon component={TagIcon} />
Tags
</SidebarLink>
<SidebarLink to="/settings/keys">
<SettingsIcon component={KeyIcon} />
Keys
</SidebarLink>
<SettingsHeading>Resources</SettingsHeading>
<SidebarLink to="/settings/about">
<SettingsIcon component={FlyingSaucer} />
About
</SidebarLink>
<SidebarLink to="/settings/changelog">
<SettingsIcon component={Receipt} />
Changelog
</SidebarLink>
<SidebarLink to="/settings/support">
<SettingsIcon component={HeartIcon} />
Support
</SidebarLink>
</div>
</div>
);
};

View file

@ -1,23 +1,22 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
export const Tooltip = ({
children,
label,
position = 'bottom'
}: {
export interface TooltipProps {
children: React.ReactNode;
label: string;
position?: 'top' | 'right' | 'bottom' | 'left';
}) => {
className?: string;
}
export const Tooltip = ({ children, label, position = 'bottom', className }: TooltipProps) => {
return (
<TooltipPrimitive.Provider>
<TooltipPrimitive.Root>
<TooltipPrimitive.Trigger asChild>
<span>{children}</span>
<span className={className}>{children}</span>
</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content
side={position}
className="text-xs rounded px-2 py-1 mb-[2px] bg-gray-300 dark:!bg-gray-900 dark:text-gray-100"
className="text-xs max-w-[200px] z-50 text-center rounded px-2 py-1 mb-[2px] bg-gray-300 dark:!bg-gray-900 dark:text-gray-100"
>
<TooltipPrimitive.Arrow className="fill-gray-300 dark:!fill-gray-900" />
{label}

View file

@ -2,6 +2,7 @@ import { ExclamationCircleIcon, PlusIcon } from '@heroicons/react/24/solid';
import { useBridgeQuery, useLibraryQuery, usePlatform } from '@sd/client';
import { Statistics } from '@sd/client';
import { Button, Input } from '@sd/ui';
import { Dialog } from '@sd/ui';
import byteSize from 'byte-size';
import clsx from 'clsx';
import { useEffect } from 'react';
@ -10,7 +11,6 @@ import 'react-loading-skeleton/dist/skeleton.css';
import create from 'zustand';
import { Device } from '../components/device/Device';
import Dialog from '../components/layout/Dialog';
import useCounter from '../hooks/useCounter';
interface StatItemProps {
@ -123,6 +123,7 @@ export default function OverviewScreen() {
<div className="flex flex-col w-full h-screen overflow-x-hidden custom-scroll page-scroll">
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-5" />
{/* PAGE */}
<div className="flex flex-col w-full h-screen px-4">
{/* STAT HEADER */}
<div className="flex w-full">
@ -179,13 +180,9 @@ export default function OverviewScreen() {
</div>
</div>
<div className="flex flex-col pb-4 mt-4 space-y-4">
<Device name={`James' MacBook Pro`} size="1TB" locations={[]} type="desktop" />
<Device name={`James' iPhone 12`} size="47.7GB" locations={[]} type="phone" />
<Device name={`Spacedrive Server`} size="5GB" locations={[]} type="server" />
</div>
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
functional.
{/* <Device name={`James' MacBook Pro`} size="1TB" locations={[]} type="desktop" /> */}
{/* <Device name={`James' iPhone 12`} size="47.7GB" locations={[]} type="phone" />
<Device name={`Spacedrive Server`} size="5GB" locations={[]} type="server" /> */}
</div>
<div className="flex flex-shrink-0 w-full h-4" />
</div>

View file

@ -1,103 +1,23 @@
import { CogIcon, HeartIcon, KeyIcon, ShieldCheckIcon, TagIcon } from '@heroicons/react/24/outline';
import { BuildingLibraryIcon } from '@heroicons/react/24/solid';
import {
FlyingSaucer,
HardDrive,
KeyReturn,
PaintBrush,
PuzzlePiece,
Receipt,
ShareNetwork
} from 'phosphor-react';
import { Suspense } from 'react';
import { Outlet } from 'react-router';
import { SidebarLink } from '../../components/layout/Sidebar';
import {
SettingsHeading,
SettingsIcon,
SettingsScreenContainer
} from '../../components/settings/SettingsScreenContainer';
import { SettingsSidebar } from '../../components/settings/SettingsSidebar';
export default function SettingsScreen() {
return (
<SettingsScreenContainer>
<SettingsHeading className="!mt-0">Client</SettingsHeading>
<SidebarLink to="/settings/general">
<SettingsIcon component={CogIcon} />
General
</SidebarLink>
<SidebarLink to="/settings/libraries">
<SettingsIcon component={BuildingLibraryIcon} />
Libraries
</SidebarLink>
<SidebarLink to="/settings/privacy">
<SettingsIcon component={ShieldCheckIcon} />
Privacy
</SidebarLink>
<SidebarLink to="/settings/appearance">
<SettingsIcon component={PaintBrush} />
Appearance
</SidebarLink>
<SidebarLink to="/settings/keybindings">
<SettingsIcon component={KeyReturn} />
Keybindings
</SidebarLink>
<SidebarLink to="/settings/extensions">
<SettingsIcon component={PuzzlePiece} />
Extensions
</SidebarLink>
<SettingsHeading>Library</SettingsHeading>
<SidebarLink to="/settings/library">
<SettingsIcon component={CogIcon} />
General
</SidebarLink>
<SidebarLink to="/settings/nodes">
<SettingsIcon component={ShareNetwork} />
Nodes
</SidebarLink>
<SidebarLink to="/settings/locations">
<SettingsIcon component={HardDrive} />
Locations
</SidebarLink>
<SidebarLink to="/settings/tags">
<SettingsIcon component={TagIcon} />
Tags
</SidebarLink>
<SidebarLink to="/settings/keys">
<SettingsIcon component={KeyIcon} />
Keys
</SidebarLink>
{/* <SidebarLink to="/settings/backups">
<SettingsIcon component={DatabaseIcon} />
Backups
</SidebarLink>
<SidebarLink to="/settings/backups">
<SettingsIcon component={ShareNetwork} />
Sync
</SidebarLink> */}
{/* <SettingsHeading>Advanced</SettingsHeading>
<SidebarLink to="/settings/p2p">
<SettingsIcon component={ShareNetwork} />
Networking
</SidebarLink>
<SidebarLink to="/settings/experimental">
<SettingsIcon component={TerminalIcon} />
Developer
</SidebarLink> */}
<SettingsHeading>Resources</SettingsHeading>
<SidebarLink to="/settings/about">
<SettingsIcon component={FlyingSaucer} />
About
</SidebarLink>
<SidebarLink to="/settings/changelog">
<SettingsIcon component={Receipt} />
Changelog
</SidebarLink>
<SidebarLink to="/settings/support">
<SettingsIcon component={HeartIcon} />
Support
</SidebarLink>
</SettingsScreenContainer>
<div className="flex flex-row w-full">
<SettingsSidebar />
<div className="w-full">
<div data-tauri-drag-region className="w-full h-7" />
<div className="flex flex-grow-0 w-full h-full max-h-screen custom-scroll page-scroll">
<div className="flex flex-grow px-12 pb-5">
<Suspense>
<Outlet />
</Suspense>
<div className="block h-20" />
</div>
</div>
</div>
</div>
);
}

View file

@ -2,13 +2,13 @@ import { TrashIcon } from '@heroicons/react/24/outline';
import { Tag, useLibraryMutation, useLibraryQuery } from '@sd/client';
import { TagUpdateArgs } from '@sd/client';
import { Button, Input } from '@sd/ui';
import { Dialog } from '@sd/ui';
import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useDebounce } from 'rooks';
import Card from '../../../components/layout/Card';
import Dialog from '../../../components/layout/Dialog';
import { Toggle } from '../../../components/primitive';
import { InputContainer } from '../../../components/primitive/InputContainer';
import { PopoverPicker } from '../../../components/primitive/PopoverPicker';

View file

@ -78,6 +78,25 @@ body {
}
}
.overlay-scroll {
// overflow: overlay;
&::-webkit-scrollbar {
height: 6px;
width: 5px;
}
&::-webkit-scrollbar-track {
@apply bg-transparent my-[5px];
}
&::-webkit-scrollbar-thumb {
@apply rounded-[6px] opacity-0 bg-gray-300 dark:bg-gray-950 w-[5px];
}
&:hover {
&::-webkit-scrollbar-thumb {
@apply opacity-100;
}
}
}
@keyframes fadeIn {
from {
opacity: 0;

View file

@ -4,5 +4,5 @@
"target": "es2020",
"outDir": "./dist"
},
"include": ["src"]
"include": ["src", "../ui/src/Dialog.tsx", "../ui/src/Loader.tsx"]
}

View file

@ -1,62 +1,65 @@
{
"name": "@sd/ui",
"version": "0.0.0",
"license": "GPL-3.0-only",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./postcss": "./style/postcss.config.js",
"./tailwind": "./style/tailwind.js",
"./style": "./style/index.js",
"./style/style.scss": "./style/style.scss",
"./package.json": "./package.json"
},
"scripts": {
"build": "tsc",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook"
},
"dependencies": {
"@headlessui/react": "^1.7.3",
"@heroicons/react": "^2.0.12",
"@radix-ui/react-context-menu": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@tailwindcss/forms": "^0.5.3",
"class-variance-authority": "^0.2.3",
"clsx": "^1.2.1",
"phosphor-react": "^1.4.1",
"postcss": "^8.4.17",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "6.4.2",
"storybook": "^6.5.12",
"tailwindcss": "^3.1.8"
},
"devDependencies": {
"@babel/core": "^7.19.3",
"@sd/config": "workspace:*",
"@storybook/addon-actions": "^6.5.12",
"@storybook/addon-essentials": "^6.5.12",
"@storybook/addon-interactions": "^6.5.12",
"@storybook/addon-links": "^6.5.12",
"@storybook/addon-postcss": "2.0.0",
"@storybook/builder-webpack5": "^6.5.12",
"@storybook/manager-webpack5": "^6.5.12",
"@storybook/preset-scss": "^1.0.3",
"@storybook/react": "^6.5.12",
"@storybook/testing-library": "^0.0.13",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"autoprefixer": "^10.4.12",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"postcss-loader": "^7.0.1",
"sass": "^1.55.0",
"sass-loader": "^13.0.2",
"storybook-tailwind-dark-mode": "^1.0.15",
"style-loader": "^3.3.1",
"typescript": "^4.8.4"
}
"name": "@sd/ui",
"version": "0.0.0",
"license": "GPL-3.0-only",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./postcss": "./style/postcss.config.js",
"./tailwind": "./style/tailwind.js",
"./style": "./style/index.js",
"./style/style.scss": "./style/style.scss",
"./package.json": "./package.json"
},
"scripts": {
"build": "tsc",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook"
},
"dependencies": {
"@headlessui/react": "^1.7.3",
"@heroicons/react": "^2.0.12",
"@radix-ui/react-context-menu": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@tailwindcss/forms": "^0.5.3",
"class-variance-authority": "^0.2.3",
"clsx": "^1.2.1",
"phosphor-react": "^1.4.1",
"postcss": "^8.4.17",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "6.4.2",
"react-loading-icons": "^1.1.0",
"react-spring": "^9.5.5",
"storybook": "^6.5.12",
"tailwindcss": "^3.1.8"
},
"devDependencies": {
"@babel/core": "^7.19.3",
"@sd/config": "workspace:*",
"@storybook/addon-actions": "^6.5.12",
"@storybook/addon-essentials": "^6.5.12",
"@storybook/addon-interactions": "^6.5.12",
"@storybook/addon-links": "^6.5.12",
"@storybook/addon-postcss": "2.0.0",
"@storybook/builder-webpack5": "^6.5.12",
"@storybook/manager-webpack5": "^6.5.12",
"@storybook/preset-scss": "^1.0.3",
"@storybook/react": "^6.5.12",
"@storybook/testing-library": "^0.0.13",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"autoprefixer": "^10.4.12",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"postcss-loader": "^7.0.1",
"sass": "^1.55.0",
"sass-loader": "^13.0.2",
"storybook-tailwind-dark-mode": "^1.0.15",
"style-loader": "^3.3.1",
"typescript": "^4.8.4"
}
}

View file

@ -4,7 +4,7 @@ import { Link, LinkProps } from 'react-router-dom';
const sizes = {
default: 'py-1 px-3 text-md font-medium',
sm: 'py-1 px-2 text-sm font-medium'
sm: 'py-1 px-2 text-xs font-medium'
};
const variants = {
@ -118,14 +118,16 @@ export const Button = forwardRef<
ref
) => {
className = clsx(
'border rounded-md items-center transition-colors duration-100 cursor-default',
{ 'opacity-70': loading, '!p-1': noPadding },
{ 'justify-center': !justifyLeft },
'border rounded-md items-center transition-colors duration-100 cursor-default disabled:opacity-50 disabled:cursor-not-allowed',
{
'opacity-70': loading,
'!p-1': noPadding,
'justify-center': !justifyLeft,
'active:translate-y-[1px]': pressEffect,
'border-0': noBorder
},
sizes[size || 'default'],
variants[variant || 'default'],
{ 'active:translate-y-[1px]': pressEffect },
{ 'border-0': noBorder },
'disabled:opacity-50 disabled:cursor-not-allowed',
className
);

View file

@ -0,0 +1,99 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import clsx from 'clsx';
import { ReactNode, useState } from 'react';
import { animated, config, useTransition } from 'react-spring';
import { Button, Loader } from '../';
export interface DialogProps extends DialogPrimitive.DialogProps {
trigger: ReactNode;
ctaLabel?: string;
ctaDanger?: boolean;
ctaAction?: () => void;
title?: string;
description?: string;
children?: ReactNode;
transformOrigin?: string;
loading?: boolean;
submitDisabled?: boolean;
}
export function Dialog(props: DialogProps) {
const [open, setOpen] = useState(false);
const transitions = useTransition(open, {
from: {
opacity: 0,
transform: `translateY(20px)`,
transformOrigin: props.transformOrigin || 'bottom'
},
enter: { opacity: 1, transform: `translateY(0px)` },
leave: { opacity: 0, transform: `translateY(20px)` },
config: { mass: 0.4, tension: 200, friction: 10 }
});
return (
<DialogPrimitive.Root open={open} onOpenChange={setOpen}>
<DialogPrimitive.Trigger asChild>{props.trigger}</DialogPrimitive.Trigger>
{transitions(
(styles, show) =>
show && (
<DialogPrimitive.Portal forceMount>
<DialogPrimitive.Overlay asChild>
<animated.div
className="fixed top-0 bottom-0 left-0 right-0 z-50 grid overflow-y-auto bg-black bg-opacity-50 rounded-xl place-items-center m-[1px]"
style={{
opacity: styles.opacity
}}
>
<DialogPrimitive.Content forceMount asChild>
<animated.div
style={styles}
className="min-w-[300px] max-w-[400px] rounded-md bg-gray-650 text-white border border-gray-550 shadow-deep"
>
<form onSubmit={(e) => e.preventDefault()}>
<div className="p-5">
<DialogPrimitive.Title className="mb-2 font-bold">
{props.title}
</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-gray-300">
{props.description}
</DialogPrimitive.Description>
{props.children}
</div>
<div className="flex flex-row justify-end px-3 py-3 space-x-2 bg-gray-600 border-t border-gray-550">
{props.loading && <Loader />}
<div className="flex-grow" />
<DialogPrimitive.Close asChild>
<Button
loading={props.loading}
disabled={props.loading}
size="sm"
variant="gray"
>
Close
</Button>
</DialogPrimitive.Close>
<Button
onClick={props.ctaAction}
type="submit"
size="sm"
loading={props.loading}
disabled={props.loading || props.submitDisabled}
variant={props.ctaDanger ? 'colored' : 'primary'}
className={clsx(props.ctaDanger && 'bg-red-500 border-red-500')}
>
{props.ctaLabel}
</Button>
</div>
</form>
</animated.div>
</DialogPrimitive.Content>
</animated.div>
</DialogPrimitive.Overlay>
</DialogPrimitive.Portal>
)
)}
</DialogPrimitive.Root>
);
}

View file

@ -25,8 +25,7 @@ const variants = {
dark:text-white
placeholder-gray-300
`,
primary: ''
`
};
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {

View file

@ -1,7 +1,7 @@
import clsx from 'clsx';
import { Puff } from 'react-loading-icons';
export default function Loader(props: { className?: string }) {
export function Loader(props: { className?: string }) {
return (
<Puff
stroke="#2599FF"

View file

@ -1,15 +1,17 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import { PropsWithChildren, useState } from 'react';
import { animated, config, useTransition } from 'react-spring';
interface Props extends DropdownMenu.MenuContentProps {
trigger: React.ReactNode;
transformOrigin?: string;
disabled?: boolean;
}
const MENU_CLASSES = `
flex flex-col
min-w-[11rem] m-2 space-y-1
min-w-[11rem] z-50 m-2 space-y-1
text-left text-sm dark:text-gray-100 text-gray-800
bg-gray-50 border-gray-200 dark:bg-gray-600
border border-gray-300 dark:border-gray-500
@ -22,19 +24,40 @@ export const OverlayPanel = ({
trigger,
children,
disabled,
transformOrigin,
className,
...props
}: PropsWithChildren<Props>) => {
const [open, setOpen] = useState(false);
const transitions = useTransition(open, {
from: {
opacity: 0,
transform: `scale(${0.9})`,
transformOrigin: transformOrigin || 'top'
},
enter: { opacity: 1, transform: 'scale(1)' },
leave: { opacity: -0.5, transform: 'scale(0.95)' },
config: { mass: 0.4, tension: 200, friction: 10 }
});
return (
<DropdownMenu.Root>
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownMenu.Trigger disabled={disabled} asChild>
{trigger}
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content {...props} className={clsx(MENU_CLASSES, className)}>
{children}
</DropdownMenu.Content>
</DropdownMenu.Portal>
{transitions(
(styles, show) =>
show && (
<DropdownMenu.Portal forceMount>
<DropdownMenu.Content forceMount asChild>
<animated.div className={clsx(MENU_CLASSES, className)} style={styles}>
{children}
</animated.div>
</DropdownMenu.Content>
</DropdownMenu.Portal>
)
)}
</DropdownMenu.Root>
);
};

View file

@ -1,5 +1,7 @@
export * from './Button';
export * from './Dropdown';
export * from './Dialog';
export * from './Loader';
export * as ContextMenu from './ContextMenu';
export * from './OverlayPanel';
export * from './Input';

View file

@ -1,5 +1,5 @@
// const colors = require('tailwindcss/colors');
// const plugin = require('tailwindcss/plugin');
const plugin = require('tailwindcss/plugin');
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = function (app, options) {
@ -120,7 +120,13 @@ module.exports = function (app, options) {
variants: {
extend: {}
},
plugins: [require('@tailwindcss/forms')]
plugins: [
require('@tailwindcss/forms'),
plugin(({ addVariant }) => {
addVariant('open', '&[data-state="open"]');
addVariant('closed', '&[data-state="closed"]');
})
]
};
if (app === 'landing') {
config.plugins.push(require('@tailwindcss/typography'));

View file

@ -487,6 +487,7 @@ importers:
'@headlessui/react': ^1.7.3
'@heroicons/react': ^2.0.12
'@radix-ui/react-context-menu': ^1.0.0
'@radix-ui/react-dialog': ^1.0.0
'@radix-ui/react-dropdown-menu': ^1.0.0
'@sd/config': workspace:*
'@storybook/addon-actions': ^6.5.12
@ -514,7 +515,9 @@ importers:
postcss-loader: ^7.0.1
react: ^18.2.0
react-dom: ^18.2.0
react-loading-icons: ^1.1.0
react-router-dom: 6.4.2
react-spring: ^9.5.5
sass: ^1.55.0
sass-loader: ^13.0.2
storybook: ^6.5.12
@ -526,6 +529,7 @@ importers:
'@headlessui/react': 1.7.3_biqbaboplfbrettd7655fr4n2y
'@heroicons/react': 2.0.12_react@18.2.0
'@radix-ui/react-context-menu': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
'@radix-ui/react-dialog': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
'@radix-ui/react-dropdown-menu': 1.0.0_rj7ozvcq3uehdlnj3cbwzbi5ce
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
class-variance-authority: 0.2.3_typescript@4.8.4
@ -534,7 +538,9 @@ importers:
postcss: 8.4.17
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-loading-icons: 1.1.0
react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y
react-spring: 9.5.5_biqbaboplfbrettd7655fr4n2y
storybook: 6.5.12_yalvw3r2waubxycyb7k7qsruca
tailwindcss: 3.1.8
devDependencies:
@ -3602,6 +3608,117 @@ packages:
'@babel/runtime': 7.19.0
dev: false
/@react-spring/animated/9.5.5_react@18.2.0:
resolution: {integrity: sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/core/9.5.5_react@18.2.0:
resolution: {integrity: sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/rafz': 9.5.5
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/konva/9.5.5_react@18.2.0:
resolution: {integrity: sha512-0CNh+1vCIjNUklTFwMvxg+H83Jo2OWykBrdEA28ccmnpZgkQ8Kq5xyvaPFLzcDKV67OXHnaWiCYKpRbhLy2wng==}
peerDependencies:
konva: '>=2.6'
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-konva: ^16.8.0 || ^17.0.0
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/native/9.5.5_react@18.2.0:
resolution: {integrity: sha512-kauqmyJ8u7aVy2bBs22vl1SdB2i5uYIL4rP53k1KDWrFSqJh4j3efWkbTt9uzR5cMXuNVbkNo9OYVFUcQBz50A==}
peerDependencies:
react: ^16.8.0 || >=17.0.0 || >=18.0.0
react-native: '>=0.58'
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/rafz/9.5.5:
resolution: {integrity: sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw==}
dev: false
/@react-spring/shared/9.5.5_react@18.2.0:
resolution: {integrity: sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@react-spring/rafz': 9.5.5
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/three/9.5.5_react@18.2.0:
resolution: {integrity: sha512-9kTIaSceqFIl5EIrdwM7Z53o5I+9BGNVzbp4oZZYMao+GMAWOosnlQdDG5GeqNsIqfW9fZCEquGqagfKAxftcA==}
peerDependencies:
'@react-three/fiber': '>=6.0'
react: ^16.8.0 || ^17.0.0 || ^18.0.0
three: '>=0.126'
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
dev: false
/@react-spring/types/9.5.5:
resolution: {integrity: sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg==}
dev: false
/@react-spring/web/9.5.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-+moT8aDX/ho/XAhU+HRY9m0LVV9y9CK6NjSRaI+30Re150pB3iEip6QfnF4qnhSCQ5drpMF0XRXHgOTY/xbtFw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@react-spring/zdog/9.5.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-LZgjo2kLlGmUqfE2fdVnvLXz+4eYyQARRvB9KQ4PTEynaETTG89Xgn9YxLrh1p57DzH7gEmTGDZ5hEw3pWqu8g==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
react-zdog: '>=1.0'
zdog: '>=1.0'
dependencies:
'@react-spring/animated': 9.5.5_react@18.2.0
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/shared': 9.5.5_react@18.2.0
'@react-spring/types': 9.5.5
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/@remix-run/router/1.0.2:
resolution: {integrity: sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==}
engines: {node: '>=14'}
@ -7014,7 +7131,6 @@ packages:
normalize-range: 0.1.2
picocolors: 1.0.0
postcss-value-parser: 4.2.0
dev: false
/autoprefixer/10.4.12_postcss@8.4.17:
resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==}
@ -15226,6 +15342,30 @@ packages:
three: 0.123.0
dev: false
/react-spring/9.5.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-vMGVd2yjgxWcRCzoLn9AD1d24+WpunHBRg5DoehcRdiBocaOH6qgle0xN9C5LPplXfv4yIpS5QWGN5MKrWxSZg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@react-spring/core': 9.5.5_react@18.2.0
'@react-spring/konva': 9.5.5_react@18.2.0
'@react-spring/native': 9.5.5_react@18.2.0
'@react-spring/three': 9.5.5_react@18.2.0
'@react-spring/web': 9.5.5_biqbaboplfbrettd7655fr4n2y
'@react-spring/zdog': 9.5.5_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
transitivePeerDependencies:
- '@react-three/fiber'
- konva
- react-konva
- react-native
- react-zdog
- three
- zdog
dev: false
/react-style-singleton/2.2.1_iapumuv4e6jcjznwuxpf4tt22e:
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
@ -18152,7 +18292,7 @@ packages:
valtio: ^1.2.5
dependencies:
lodash: 4.17.21
valtio: 1.7.0_react@18.2.0+vite@3.1.4
valtio: 1.7.0
dev: false
/valtio/1.7.0: