1
Fork 0

Finish mobile form

This commit is contained in:
Hadeed 2024-06-08 11:52:17 +04:00
parent 0dc2abfdca
commit 012eeb3b8e
4 changed files with 147 additions and 51 deletions

16
package-lock.json generated
View file

@ -11,6 +11,7 @@
"clsx": "^2.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"tailwind-merge": "^2.3.0"
},
"devDependencies": {
@ -2489,6 +2490,21 @@
"react": "^18.3.1"
}
},
"node_modules/react-hook-form": {
"version": "7.51.5",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
"integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View file

@ -11,6 +11,7 @@
"clsx": "^2.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"tailwind-merge": "^2.3.0"
},
"devDependencies": {

View file

@ -2,6 +2,7 @@ import * as Checkbox from "@radix-ui/react-checkbox";
import * as RadioGroup from "@radix-ui/react-radio-group";
import * as Toast from "@radix-ui/react-toast";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import icon_checkbox_checked from "../assets/icon-checkbox-checked.svg";
import icon_checkbox_unchecked from "../assets/icon-checkbox-unchecked.svg";
@ -10,24 +11,42 @@ import icon_check from "../assets/check.svg";
import { Button } from "./components/button";
import { Input } from "./components/input";
const InputField = ({ label, type = "text", required = false, ...props }) => {
const InputField = ({
label,
type = "text",
required = false,
register,
errors,
...props
}) => {
const _label = label.toLowerCase().replace(/ /g, "_");
const error = errors[_label]?.type == "required";
return (
<label className="group block cursor-pointer space-y-100">
<div className="space-x-100 text-body-sm text-grey-900 group-focus-within:text-green-600">
<label className="group block cursor-pointer space-y-100 text-body-sm">
<div className="space-x-100 text-grey-900 group-focus-within:text-green-600">
<span>{label}</span>
{required && <span className="text-green-600">*</span>}
</div>
<Input
name={label.toLowerCase().replace(/ /g, "_")}
type={type}
required={required}
{...register(_label, { required })}
aria-invalid={error}
{...props}
/>
{error && <p className="text-red">This field is required</p>}
</label>
);
};
export default function App() {
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm();
const [toastOpen, setToastOpen] = useState(false);
return (
@ -35,13 +54,34 @@ export default function App() {
<Toast.Provider>
<Toast.Viewport className="left-0 top-0 fixed flex w-full justify-center p-300" />
<div className="rounded-2xl bg-white p-300 text-grey-900">
<form action="">
<form
onSubmit={handleSubmit((data) => {
console.log(data);
setToastOpen(true);
})}
>
<h1 className="text-heading">Contact Us</h1>
<div className="mt-400 space-y-300">
<InputField label="First Name" required />
<InputField label="Last Name" required />
<InputField label="Email Address" type="email" required />
<InputField
register={register}
errors={errors}
label="First Name"
required
/>
<InputField
register={register}
errors={errors}
label="Last Name"
required
/>
<InputField
register={register}
errors={errors}
label="Email Address"
type="email"
required
/>
<div className="space-y-200">
<div className="space-x-100">
@ -49,53 +89,92 @@ export default function App() {
<span className="text-body-sm text-green-600">*</span>
</div>
<RadioGroup.Root className="space-y-200" name="query_type">
{["General Enquiry", "Support Request"].map((label) => (
<RadioGroup.Item
className="group w-full rounded-lg border-[1px] border-grey-500 px-300 py-150 text-left hover:border-green-600 data-[state='checked']:border-green-600 data-[state='checked']:bg-green-200"
value={label.toLowerCase().replace(/ /g, "_")}
key={label}
<Controller
control={control}
name="query_type"
rules={{ required: true }}
render={({
field: { onChange, value },
fieldState: { invalid },
}) => (
<RadioGroup.Root
className="space-y-200"
value={value}
onValueChange={onChange}
>
<label className="flex cursor-pointer items-center justify-start gap-150 hover:text-green-600 group-data-[state='checked']:text-green-600">
<span className="relative size-300 rounded-full border-2 border-solid border-grey-500 bg-transparent group-data-[state='checked']:border-green-600">
<span className="absolute left-1/2 top-1/2 size-150 -translate-x-1/2 -translate-y-1/2 transform rounded-full bg-transparent group-data-[state='checked']:bg-green-600"></span>
</span>
<span className="text-body-md text-grey-900 group-hover:text-green-600 group-data-[state='checked']:text-green-600">
{label}
</span>
</label>
</RadioGroup.Item>
))}
</RadioGroup.Root>
{["General Enquiry", "Support Request"].map((label) => (
<RadioGroup.Item
className="group w-full rounded-lg border-[1px] border-grey-500 px-300 py-150 text-left hover:border-green-600 data-[state='checked']:border-green-600 data-[state='checked']:bg-green-200"
value={label.toLowerCase().replace(/ /g, "_")}
key={label}
>
<label className="flex cursor-pointer items-center justify-start gap-150 hover:text-green-600 group-data-[state='checked']:text-green-600">
<span className="relative size-300 rounded-full border-2 border-solid border-grey-500 bg-transparent group-data-[state='checked']:border-green-600">
<span className="absolute left-1/2 top-1/2 size-150 -translate-x-1/2 -translate-y-1/2 transform rounded-full bg-transparent group-data-[state='checked']:bg-green-600"></span>
</span>
<span className="text-body-md text-grey-900 group-hover:text-green-600 group-data-[state='checked']:text-green-600">
{label}
</span>
</label>
</RadioGroup.Item>
))}
{invalid && (
<p className="text-body-sm text-red">
Please select a query type
</p>
)}
</RadioGroup.Root>
)}
/>
</div>
<InputField label="Message" type="textarea" rows="4" required />
<InputField
register={register}
errors={errors}
label="Message"
type="textarea"
rows="4"
required
/>
</div>
<label className="my-500 flex cursor-pointer items-center">
<Checkbox.Root
className="size-300 flex-shrink-0"
style={{ backgroundImage: `url('${icon_checkbox_unchecked}')` }}
required
>
<Checkbox.Indicator>
<img src={icon_checkbox_checked} />
</Checkbox.Indicator>
</Checkbox.Root>
<div className="mx-200 space-x-100 text-body-sm text-grey-900">
<span>I consent to being contacted by the team</span>
<span className="text-green-600">*</span>
</div>
</label>
<Controller
control={control}
name="consent_given"
rules={{ validate: (value) => value }}
render={({
field: { onChange, value },
fieldState: { invalid },
}) => (
<div className="my-500 space-y-100 ">
<label className="flex cursor-pointer items-center">
<Checkbox.Root
checked={value}
onCheckedChange={onChange}
className="size-300 flex-shrink-0"
style={{
backgroundImage: `url('${icon_checkbox_unchecked}')`,
}}
>
<Checkbox.Indicator>
<img src={icon_checkbox_checked} />
</Checkbox.Indicator>
</Checkbox.Root>
<div className="mx-200 space-x-100 text-body-sm text-grey-900">
<span>I consent to being contacted by the team</span>
<span className="text-green-600">*</span>
</div>
</label>
{invalid && (
<p className="text-body-sm text-red">
To submit this form, please consent to being contacted
</p>
)}
</div>
)}
/>
<Button
type="button"
onClick={() => {
setToastOpen((toastOpen) => !toastOpen);
}}
>
Submit
</Button>
<Button type="submit">Submit</Button>
</form>
</div>
<Toast.Root

View file

@ -6,7 +6,7 @@ const Input = React.forwardRef(({ type, className, ...props }, ref) => {
return (
<Component
className={cn(
"block w-full cursor-pointer rounded-lg border-[1px] border-grey-500 px-300 py-150 text-body-md focus:border-green-600 group-hover:border-green-600",
"block w-full cursor-pointer rounded-lg border-[1px] border-grey-500 px-300 py-150 text-body-md focus:border-green-600 group-hover:border-green-600 aria-[invalid='true']:border-red",
className,
)}
type={type}