Skip to content

Commit

Permalink
feat(4746): add password input components
Browse files Browse the repository at this point in the history
  • Loading branch information
junminahn committed Jan 24, 2025
1 parent 71b1dd9 commit c148ac6
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 3 deletions.
54 changes: 54 additions & 0 deletions app/app/examples/ui/(components)/password-input/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@mantine/core';
import { FormProvider, useForm } from 'react-hook-form';
import { z } from 'zod';
import HookFormPasswordInput from '@/components/generic/input/HookFormPasswordInput';
import { GlobalRole } from '@/constants';
import createClientPage from '@/core/client-page';

const validationSchema = z.object({
username: z.string().min(1).max(100),
password: z.string().min(1).max(100),
});

const Page = createClientPage({
roles: [GlobalRole.User],
});
export default Page(() => {
const methods = useForm({
resolver: zodResolver(validationSchema),
defaultValues: {},
});

return (
<>
<h1 className="font-bold text-2xl mt-4 mb-5">Password Input</h1>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(console.log)} autoComplete="off">
<div className="grid grid-cols-1 md:grid-cols-3 md:gap-4 md:py-2">
<HookFormPasswordInput
label="Username"
name="username"
placeholder="Enter username..."
required
classNames={{ wrapper: 'col-span-1' }}
/>
<HookFormPasswordInput
label="Password"
name="password"
placeholder="Enter password..."
required
classNames={{ wrapper: 'col-span-1' }}
/>
</div>

<Button variant="success" type="submit" className="mt-1">
Submit
</Button>
</form>
</FormProvider>
</>
);
});
4 changes: 4 additions & 0 deletions app/app/examples/ui/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const pages = [
href: 'ui/text-input',
label: 'Text Input',
},
{
href: 'ui/password-input',
label: 'Password Input',
},
{
href: 'ui/textarea',
label: 'Textarea',
Expand Down
94 changes: 94 additions & 0 deletions app/components/generic/input/FormPasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client';

import { PasswordInput, PasswordInputProps } from '@mantine/core';
import _kebabCase from 'lodash-es/kebabCase';
import { InputHTMLAttributes } from 'react';
import { cn } from '@/utils/js';
import Label from '../Label';

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
type OtherProps = Omit<InputProps, 'size'>;

const inputClasslist = [
'block',
'border-0',
'disabled:bg-slate-50',
'disabled:text-slate-500',
'disabled:cursor-not-allowed',
'focus:ring-2',
'focus:ring-indigo-600',
'invalid:ring-pink-600',
'invalid:text-pink-600',
'placeholder:text-gray-400',
'py-2',
'ring-1',
'ring-gray-300',
'ring-inset',
'rounded-md',
'shadow-sm',
'sm:leading-6',
'sm:text-sm',
'text-gray-900',
'w-full',
];

const inputClass = inputClasslist.join(' ');

export interface FormPasswordInputProps extends PasswordInputProps {
id?: string;
name: string;
label?: string;
inputProps?: OtherProps;
classNames?: {
wrapper?: string;
label?: string;
input?: string;
};
copyable?: boolean;
onCopy?: () => void;
info?: string;
}

export default function FormPasswordInput({
id,
name,
label,
type = 'text',
classNames,
required,
disabled,
inputProps = {},
copyable = false,
onCopy,
info,
...others
}: FormPasswordInputProps) {
if (!id) id = _kebabCase(name);

return (
<div className={cn('text-input', classNames?.wrapper)}>
{label && (
<Label
htmlFor={id}
className={classNames?.label}
required={required}
copyable={copyable}
onCopy={onCopy}
info={info}
>
{label}
</Label>
)}
<PasswordInput
id={id}
name={name}
disabled={disabled}
autoComplete="off"
{...inputProps}
{...others}
variant="unstyled"
classNames={{ input: 'overflow-visible', innerInput: cn(inputClass, classNames?.input) }}
/>
</div>
);
}
53 changes: 53 additions & 0 deletions app/components/generic/input/HookFormPasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client';

import _get from 'lodash-es/get';
import { FieldValues, RegisterOptions, Path, useFormContext } from 'react-hook-form';
import { cn } from '@/utils/js';
import FormError from '../FormError';
import FormPasswordInput, { FormPasswordInputProps } from './FormPasswordInput';

export default function HookFormPasswordInput<T extends FieldValues>({
id,
name,
options,
label,
error,
classNames,
disabled,
...others
}: Omit<FormPasswordInputProps, 'name' | 'inputProps'> & {
name: Path<T>;
options?: RegisterOptions<T, Path<T>> | undefined;
error?: string;
}) {
const {
register,
formState: { errors },
} = useFormContext<T>();

const formError = _get(errors, name);
const _error = error ?? (formError && String(formError?.message));
const showError = !!formError && !disabled;

return (
<div className={classNames?.wrapper}>
<FormPasswordInput
id={id}
name={name}
label={label}
disabled={disabled}
{...others}
classNames={{
label: cn(classNames?.label, {
'text-pink-600': showError,
}),
input: cn(classNames?.input, {
'ring-pink-600 text-pink-600': showError,
}),
}}
inputProps={register(name, options)}
/>
{showError && <FormError field={name} className="mt-1" message={_error} />}
</div>
);
}
7 changes: 4 additions & 3 deletions app/components/private-cloud/sections/Webhooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Code } from '@mantine/core';
import { RequestType } from '@prisma/client';
import _get from 'lodash-es/get';
import ExternalLink from '@/components/generic/button/ExternalLink';
import HookFormPasswordInput from '@/components/generic/input/HookFormPasswordInput';
import HookFormTextInput from '@/components/generic/input/HookFormTextInput';
import { cn } from '@/utils/js';

Expand Down Expand Up @@ -49,22 +50,22 @@ export default function Webhooks({ disabled, className }: { disabled: boolean; c
error="Please provide a valid HTTPS URL"
classNames={{ wrapper: 'col-span-full mt-2' }}
/>
<HookFormTextInput
<HookFormPasswordInput
label="Secret"
name="secret"
placeholder="Enter Webhook Secret"
disabled={disabled}
classNames={{ wrapper: 'col-span-full mt-2' }}
/>
<div className="flex justify-between gap-2 mt-2">
<HookFormTextInput
<HookFormPasswordInput
label="Username"
name="username"
placeholder="Enter Webhook Username"
disabled={disabled}
classNames={{ wrapper: 'w-1/2' }}
/>
<HookFormTextInput
<HookFormPasswordInput
label="Password"
name="password"
placeholder="Enter Webhook Password"
Expand Down

0 comments on commit c148ac6

Please sign in to comment.