mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 12:13:27 +00:00
[Desktop] Add label & error to form field (#659)
* Add label and error to form field * Add error styling * Update other form fields * Update FormField.tsx * Update Input.tsx * Move field state to hook * remove required
This commit is contained in:
parent
5782af36eb
commit
fc830ff922
|
@ -109,37 +109,41 @@ export default (props: UseDialogProps) => {
|
|||
<div className="mt-4 mb-3 grid w-full gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-2 text-xs font-bold">Content Salt (hex)</span>
|
||||
<div className="relative flex grow">
|
||||
<Input value={contentSalt} disabled className="grow !py-0.5" />
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(contentSalt);
|
||||
}}
|
||||
size="icon"
|
||||
className="absolute right-[5px] top-[5px] border-none"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
value={contentSalt}
|
||||
disabled
|
||||
right={
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(contentSalt);
|
||||
}}
|
||||
size="icon"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 mb-3 grid w-full gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-2 text-xs font-bold">Key Value</span>
|
||||
<div className="relative flex grow">
|
||||
<Input value={keyValue} disabled className="grow !py-0.5" />
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(keyValue);
|
||||
}}
|
||||
size="icon"
|
||||
className="absolute right-[5px] top-[5px] border-none"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
value={keyValue}
|
||||
disabled
|
||||
right={
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(keyValue);
|
||||
}}
|
||||
size="icon"
|
||||
>
|
||||
<Clipboard className="h-4 w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
|
|
@ -94,16 +94,14 @@ export const Component = () => {
|
|||
>
|
||||
<div className="flex space-x-4">
|
||||
<FlexCol>
|
||||
<Label>Display Name</Label>
|
||||
<Input {...form.register('displayName')} />
|
||||
<Input label="Display Name" {...form.register('displayName')} />
|
||||
<InfoText>
|
||||
The name of this Location, this is what will be displayed in the sidebar. Will not
|
||||
rename the actual folder on disk.
|
||||
</InfoText>
|
||||
</FlexCol>
|
||||
<FlexCol>
|
||||
<Label>Local Path</Label>
|
||||
<Input {...form.register('localPath')} />
|
||||
<Input label="Local Path" {...form.register('localPath')} />
|
||||
<InfoText>
|
||||
The path to this Location, this is where the files will be stored on disk.
|
||||
</InfoText>
|
||||
|
|
|
@ -144,22 +144,19 @@ export const AddLocationDialog = (props: Props) => {
|
|||
}
|
||||
ctaLabel="Add"
|
||||
>
|
||||
<div className="relative mb-3 flex flex-col">
|
||||
<p className="my-2 text-sm font-bold">Path:</p>
|
||||
<Input
|
||||
type="text"
|
||||
onClick={() =>
|
||||
openDirectoryPickerDialog(platform)
|
||||
.then((path) => path && form.setValue('path', path))
|
||||
.catch((error) => showAlertDialog({ title: 'Error', value: String(error) }))
|
||||
}
|
||||
readOnly={platform.platform !== 'web'}
|
||||
required
|
||||
className="cursor-pointer"
|
||||
size="md"
|
||||
{...form.register('path')}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label="Path:"
|
||||
readOnly={platform.platform !== 'web'}
|
||||
className="mb-3 cursor-pointer"
|
||||
size="md"
|
||||
required
|
||||
onClick={() =>
|
||||
openDirectoryPickerDialog(platform)
|
||||
.then((path) => path && form.setValue('path', path))
|
||||
.catch((error) => showAlertDialog({ title: 'Error', value: String(error) }))
|
||||
}
|
||||
{...form.register('path')}
|
||||
/>
|
||||
|
||||
<div className="relative flex flex-col">
|
||||
<p className="my-2 text-sm font-bold">File indexing rules:</p>
|
||||
|
|
|
@ -36,18 +36,15 @@ export default ({ tag, onDelete }: Props) => {
|
|||
return (
|
||||
<Form form={form}>
|
||||
<div className="mb-10 flex flex-row space-x-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-sm font-medium text-gray-700 dark:text-gray-100">Color</span>
|
||||
<Input
|
||||
className="w-28"
|
||||
icon={<ColorPicker control={form.control} name="color" />}
|
||||
{...form.register('color')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="mb-1 text-sm font-medium text-gray-700 dark:text-gray-100">Name</span>
|
||||
<Input {...form.register('name')} />
|
||||
</div>
|
||||
<Input
|
||||
label="Color"
|
||||
className="w-28"
|
||||
icon={<ColorPicker control={form.control} name="color" />}
|
||||
{...form.register('color')}
|
||||
/>
|
||||
|
||||
<Input label="Name" {...form.register('name')} />
|
||||
|
||||
<div className="flex grow" />
|
||||
<Button
|
||||
variant="gray"
|
||||
|
|
|
@ -99,10 +99,12 @@ export default (props: UseDialogProps) => {
|
|||
submitDisabled={!form.formState.isValid}
|
||||
ctaLabel="Create"
|
||||
>
|
||||
<div className="relative flex flex-col">
|
||||
<p className="my-2 text-sm font-bold">Library name</p>
|
||||
<Input placeholder="My Cool Library" {...form.register('name', { required: true })} />
|
||||
</div>
|
||||
<Input
|
||||
label="Library name"
|
||||
placeholder="My Cool Library"
|
||||
className="mt-2"
|
||||
{...form.register('name', { required: true })}
|
||||
/>
|
||||
|
||||
<div className="mt-3 mb-1 flex flex-row items-center">
|
||||
<div className="space-x-2">
|
||||
|
@ -125,10 +127,12 @@ export default (props: UseDialogProps) => {
|
|||
<p className="mt-2 mb-1 text-center text-[0.95rem] font-bold">Key Manager</p>
|
||||
<div className="my-1 h-[2px] w-full bg-gray-500" />
|
||||
|
||||
<p className="my-2 text-sm font-bold">Master password</p>
|
||||
<Input
|
||||
label="Master password"
|
||||
placeholder="Password"
|
||||
type={showMasterPassword1 ? 'text' : 'password'}
|
||||
className="mt-2"
|
||||
{...form.register('password')}
|
||||
right={
|
||||
<div className="flex">
|
||||
<Button
|
||||
|
@ -158,23 +162,22 @@ export default (props: UseDialogProps) => {
|
|||
</Button>
|
||||
</div>
|
||||
}
|
||||
{...form.register('password')}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex flex-col">
|
||||
<p className="my-2 text-sm font-bold">Master password (again)</p>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
type={showMasterPassword2 ? 'text' : 'password'}
|
||||
right={
|
||||
<Button onClick={() => setShowMasterPassword2(!showMasterPassword2)} size="icon">
|
||||
<MP2CurrentEyeIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
}
|
||||
{...form.register('password_validate')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Master password (again)"
|
||||
placeholder="Password"
|
||||
type={showMasterPassword2 ? 'text' : 'password'}
|
||||
className="mt-2"
|
||||
right={
|
||||
<Button onClick={() => setShowMasterPassword2(!showMasterPassword2)} size="icon">
|
||||
<MP2CurrentEyeIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
}
|
||||
{...form.register('password_validate')}
|
||||
/>
|
||||
|
||||
<div className="mt-4 mb-3 grid w-full grid-cols-2 gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-bold">Encryption</span>
|
||||
|
|
|
@ -27,6 +27,9 @@ export const inputStyles = cva(
|
|||
'focus-within:ring-app-selected/30 focus-within:border-app-divider/80'
|
||||
]
|
||||
},
|
||||
error: {
|
||||
true: 'border-red-500 focus-within:border-red-500 focus-within:ring-red-400/30'
|
||||
},
|
||||
size: {
|
||||
sm: 'h-[30px]',
|
||||
md: 'h-[34px]',
|
||||
|
@ -41,11 +44,11 @@ export const inputStyles = cva(
|
|||
);
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ variant, size, right, icon, iconPosition = 'left', className, required, ...props }, ref) => (
|
||||
({ variant, size, right, icon, iconPosition = 'left', className, error, ...props }, ref) => (
|
||||
<div
|
||||
className={clsx(
|
||||
'group flex',
|
||||
inputStyles({ variant, size: right && !size ? 'md' : size, className })
|
||||
inputStyles({ variant, size: right && !size ? 'md' : size, error, className })
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
@ -99,11 +102,11 @@ export const SearchInput = forwardRef<HTMLInputElement, InputProps>((props, ref)
|
|||
<Input {...props} ref={ref} icon={MagnifyingGlass} />
|
||||
));
|
||||
|
||||
export const TextArea = ({ size, variant, ...props }: TextareaProps) => {
|
||||
export const TextArea = ({ size, variant, error, ...props }: TextareaProps) => {
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
className={clsx('h-auto px-3 py-2', inputStyles({ size, variant }), props.className)}
|
||||
className={clsx('h-auto px-3 py-2', inputStyles({ size, variant, error }), props.className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,26 +3,37 @@ import { useFormContext } from 'react-hook-form';
|
|||
|
||||
export interface UseFormFieldProps extends PropsWithChildren {
|
||||
name: string;
|
||||
// label: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const useFormField = <P extends UseFormFieldProps>(props: P) => {
|
||||
const { name, ...otherProps } = props;
|
||||
const { name, label, className, ...otherProps } = props;
|
||||
const { formState, getFieldState } = useFormContext();
|
||||
const state = getFieldState(props.name, formState);
|
||||
const id = useId();
|
||||
|
||||
return {
|
||||
formFieldProps: { id, name },
|
||||
formFieldProps: { id, name, label, className, error: state.error?.message },
|
||||
childProps: { ...otherProps, id, name }
|
||||
};
|
||||
};
|
||||
|
||||
interface FormFieldProps {
|
||||
name: string;
|
||||
interface FormFieldProps extends UseFormFieldProps {
|
||||
id: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const FormField = ({ name, children }: PropsWithChildren<FormFieldProps>) => {
|
||||
const ctx = useFormContext();
|
||||
const _ = ctx.getFieldState(name);
|
||||
|
||||
return <>{children}</>;
|
||||
export const FormField = (props: FormFieldProps) => {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
{props.label && (
|
||||
<label htmlFor={props.id} className="mb-1 flex text-sm font-medium">
|
||||
{props.label}
|
||||
</label>
|
||||
)}
|
||||
{props.children}
|
||||
{props.error && <span className="mt-1 text-xs text-red-500">{props.error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import * as Root from '../Input';
|
||||
import { FormField, UseFormFieldProps, useFormField } from './FormField';
|
||||
|
||||
|
@ -11,7 +12,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||
|
||||
return (
|
||||
<FormField {...formFieldProps}>
|
||||
<Root.Input {...childProps} ref={ref} />
|
||||
<Root.Input {...childProps} ref={ref} error={formFieldProps.error !== undefined} />
|
||||
</FormField>
|
||||
);
|
||||
});
|
||||
|
@ -21,7 +22,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, InputProps>((props, re
|
|||
|
||||
return (
|
||||
<FormField {...formFieldProps}>
|
||||
<Root.PasswordInput {...childProps} ref={ref} />
|
||||
<Root.PasswordInput {...childProps} ref={ref} error={formFieldProps.error !== undefined} />
|
||||
</FormField>
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue