mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-19 05:45:01 -04:00
[ENG-380] Interface code structure improvement (#581)
* beginnings of app directory * settings mostly good * colocate way more components * flatten components folder * reexport QueryClientProvider from client * move CodeBlock back to interface * colocate Explorer, KeyManager + more * goddamn captialisation * get toasts out of components * please eslint * no more src directory * $ instead of : * added back RowHeader component * fix settings modal padding * more spacing, less margin * fix sidebar locations button * fix tags sidebar link * clean up back button * added margin to explorer context menu to prevent contact with edge of viewport * don't export QueryClientProvider from @sd/client * basic guidelines * import interface correctly * remove old demo data * fix onboarding layout * fix onboarding navigation * fix key manager settings button --------- Co-authored-by: Jamie Pine <ijamespine@me.com>
This commit is contained in:
48
interface/components/AlertDialog.tsx
Normal file
48
interface/components/AlertDialog.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Clipboard } from 'phosphor-react';
|
||||
import { Button, Dialog, Input, UseDialogProps, dialogManager, useDialog } from '@sd/ui';
|
||||
import { useZodForm, z } from '@sd/ui/src/forms';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
title: string; // dialog title
|
||||
description?: string; // description of the dialog
|
||||
value: string; // value to be displayed as text or in an input box
|
||||
label?: string; // button label
|
||||
inputBox?: boolean; // whether the dialog should display the `value` in a disabled input box or as text
|
||||
}
|
||||
|
||||
const AlertDialog = (props: Props) => {
|
||||
const dialog = useDialog(props);
|
||||
const form = useZodForm({ schema: z.object({}) });
|
||||
// maybe a copy-to-clipboard button would be beneficial too
|
||||
return (
|
||||
<Dialog
|
||||
form={form}
|
||||
onSubmit={form.handleSubmit(() => {})}
|
||||
dialog={dialog}
|
||||
description={props.description}
|
||||
ctaLabel={props.label !== undefined ? props.label : 'Done'}
|
||||
>
|
||||
{props.inputBox && (
|
||||
<div className="relative mt-3 flex grow">
|
||||
<Input value={props.value} disabled className="grow !py-0.5" />
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(props.value);
|
||||
}}
|
||||
size="icon"
|
||||
className="absolute right-[5px] top-[5px] border-none"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!props.inputBox && <div className="text-sm">{props.value}</div>}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export function showAlertDialog(props: Omit<Props, 'id'>) {
|
||||
dialogManager.create((dp) => <AlertDialog {...dp} {...props} />);
|
||||
}
|
||||
22
interface/components/Codeblock.tsx
Normal file
22
interface/components/Codeblock.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import ReactJson, { ReactJsonViewProps } from 'react-json-view';
|
||||
|
||||
export type CodeBlockProps = ReactJsonViewProps;
|
||||
|
||||
export const CodeBlock = (props: CodeBlockProps) => {
|
||||
return (
|
||||
<ReactJson
|
||||
enableClipboard={false}
|
||||
displayDataTypes={false}
|
||||
theme="ocean"
|
||||
style={{
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#101016',
|
||||
border: 1,
|
||||
borderColor: '#1E1E27',
|
||||
borderStyle: 'solid'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
39
interface/components/ColorPicker.tsx
Normal file
39
interface/components/ColorPicker.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { HexColorPicker } from 'react-colorful';
|
||||
import { UseControllerProps, useController } from 'react-hook-form';
|
||||
import useClickOutside from '~/hooks/useClickOutside';
|
||||
|
||||
interface Props extends UseControllerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default ({ className, ...props }: Props) => {
|
||||
const { field } = useController(props);
|
||||
const popover = useRef<HTMLDivElement | null>(null);
|
||||
const [isOpen, toggle] = useState(false);
|
||||
|
||||
const close = useCallback(() => toggle(false), []);
|
||||
useClickOutside(popover, close);
|
||||
|
||||
return (
|
||||
<div className={clsx('relative mt-3 flex items-center', className)}>
|
||||
<div
|
||||
className={clsx('h-4 w-4 rounded-full shadow', isOpen && 'dark:border-gray-500')}
|
||||
style={{ backgroundColor: field.value }}
|
||||
onClick={() => toggle(true)}
|
||||
/>
|
||||
{/* <span className="inline ml-2 text-sm text-gray-200">Pick Color</span> */}
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
style={{ top: 'calc(100% + 7px)' }}
|
||||
className="absolute left-0 rounded-md shadow"
|
||||
ref={popover}
|
||||
>
|
||||
<HexColorPicker color={field.value} onChange={field.onChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
14
interface/components/DragRegion.tsx
Normal file
14
interface/components/DragRegion.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PropsWithChildren, forwardRef } from 'react';
|
||||
import { cx } from '@sd/ui';
|
||||
|
||||
export default forwardRef<HTMLDivElement, PropsWithChildren & { className?: string }>(
|
||||
(props, ref) => (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={cx('flex h-5 w-full flex-shrink-0', props.className)}
|
||||
ref={ref}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
15
interface/components/SubtleButton.tsx
Normal file
15
interface/components/SubtleButton.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ReactComponent as Ellipsis } from '@sd/assets/svgs/ellipsis.svg';
|
||||
import { Button, tw } from '@sd/ui';
|
||||
|
||||
export const SubtleButton = (props: { icon?: React.FC }) => {
|
||||
const Icon = props.icon ?? Ellipsis;
|
||||
|
||||
return (
|
||||
<Button className="!p-[5px]" variant="subtle">
|
||||
{/* @ts-expect-error */}
|
||||
<Icon weight="bold" className="h-3 w-3" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubtleButtonContainer = tw.div`opacity-0 text-ink-faint group-hover:opacity-30 hover:!opacity-100`;
|
||||
75
interface/components/TrafficLights.tsx
Normal file
75
interface/components/TrafficLights.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import closeIconPath from '@sd/assets/svgs/macos_close.svg';
|
||||
import fullscreenIconPath from '@sd/assets/svgs/macos_fullscreen.svg';
|
||||
import minimizeIconPath from '@sd/assets/svgs/macos_minimize.svg';
|
||||
import clsx from 'clsx';
|
||||
import { ComponentProps, HTMLAttributes, useEffect, useRef } from 'react';
|
||||
import { useFocusState } from '~/hooks/useFocusState';
|
||||
|
||||
export interface TrafficLightsProps extends ComponentProps<'div'> {
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
onFullscreen?: () => void;
|
||||
}
|
||||
|
||||
export function MacTrafficLights(props: TrafficLightsProps) {
|
||||
const { onClose, onMinimize, onFullscreen, className } = props;
|
||||
const [focused] = useFocusState();
|
||||
|
||||
return (
|
||||
<div data-tauri-drag-region className={clsx('group flex flex-row space-x-[7.5px]', className)}>
|
||||
<TrafficLight type="close" onClick={onClose} colorful={focused ?? false} />
|
||||
<TrafficLight type="minimize" onClick={onMinimize} colorful={focused ?? false} />
|
||||
<TrafficLight type="fullscreen" onClick={onFullscreen} colorful={focused ?? false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TrafficLightProps {
|
||||
type: 'close' | 'minimize' | 'fullscreen';
|
||||
colorful: boolean;
|
||||
onClick?: HTMLAttributes<HTMLDivElement>['onClick'];
|
||||
}
|
||||
|
||||
function TrafficLight(props: TrafficLightProps) {
|
||||
const { onClick = () => undefined, colorful = false, type } = props;
|
||||
const iconPath = useRef<string>(closeIconPath);
|
||||
|
||||
useEffect(() => {
|
||||
switch (type) {
|
||||
case 'close':
|
||||
iconPath.current = closeIconPath;
|
||||
break;
|
||||
case 'minimize':
|
||||
iconPath.current = minimizeIconPath;
|
||||
break;
|
||||
case 'fullscreen':
|
||||
iconPath.current = fullscreenIconPath;
|
||||
break;
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'box-content flex h-[12px] w-[12px] items-center justify-center rounded-full border-[0.5px] border-transparent bg-[#CDCED0] dark:bg-[#2B2C2F]',
|
||||
{
|
||||
'border-red-900 !bg-[#EC6A5E] active:hover:!bg-red-700 dark:active:hover:!bg-red-300':
|
||||
type === 'close' && colorful,
|
||||
'group-hover:!bg-[#EC6A5E] ': type === 'close',
|
||||
'border-yellow-900 !bg-[#F4BE4F] active:hover:!bg-yellow-600 dark:active:hover:!bg-yellow-200':
|
||||
type === 'minimize' && colorful,
|
||||
'group-hover:!bg-[#F4BE4F]': type === 'minimize',
|
||||
'border-green-900 !bg-[#61C253] active:hover:!bg-green-700 dark:active:hover:!bg-green-300':
|
||||
type === 'fullscreen' && colorful,
|
||||
' group-hover:!bg-[#61C253] ': type === 'fullscreen'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src={iconPath.current}
|
||||
className="pointer-events-none opacity-0 group-hover:opacity-100 group-active:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user