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", "clsx": "^2.1.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"tailwind-merge": "^2.3.0" "tailwind-merge": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
@ -2489,6 +2490,21 @@
"react": "^18.3.1" "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": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View file

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

View file

@ -6,7 +6,7 @@ const Input = React.forwardRef(({ type, className, ...props }, ref) => {
return ( return (
<Component <Component
className={cn( 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, className,
)} )}
type={type} type={type}