[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:
nikec 2023-03-31 12:34:52 +02:00 committed by GitHub
parent 5782af36eb
commit fc830ff922
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 107 additions and 93 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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)}
/>
);
};

View file

@ -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>
);
};

View file

@ -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>
);
});