-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(form): add inputs, labels, and error indicators
This change adds the basic password and email inputs, along with components that can be used to build other inputs.
- Loading branch information
1 parent
6ffb91d
commit 665e47b
Showing
6 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"use client"; | ||
import { useEffect, useState } from "react"; | ||
|
||
import Expandable from "./expandable"; | ||
|
||
export default function FormError({ | ||
name, | ||
error, | ||
}: { | ||
name: string; | ||
error?: string; | ||
}) { | ||
const [frozenError, setError] = useState<string>(); | ||
useEffect(() => { | ||
if (!error) { | ||
const timeout = setTimeout(() => (setError(""), 200)); | ||
return () => { | ||
clearTimeout(timeout); | ||
}; | ||
} else { | ||
setError(error); | ||
} | ||
}, [error]); | ||
return ( | ||
<> | ||
<Expandable expanded={!!error}> | ||
<div className="pt-1 font-bold text-destructive" id={`${name}-error`}> | ||
{frozenError} | ||
</div> | ||
</Expandable> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { cn } from "@/lib/utils"; | ||
|
||
export default function InputLabel({ | ||
name, | ||
label, | ||
required, | ||
error, | ||
}: { | ||
name: string; | ||
label?: string; | ||
required?: boolean; | ||
error?: string; | ||
}) { | ||
return ( | ||
<> | ||
<label | ||
className={cn( | ||
"mb-[0.5rem] block text-[1.125rem] font-medium m:mb-[1.1875rem] m:text-[1.5rem]", | ||
error ? "text-error" : "" | ||
)} | ||
htmlFor={name} | ||
> | ||
{label} {required && <span className="text-error ml-1">*</span>} | ||
</label> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"use client"; | ||
import { useState } from "react"; | ||
|
||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { cn } from "@/lib/utils"; | ||
|
||
export default function EmailInput() { | ||
const [submitActive] = useState<boolean>(false); | ||
return ( | ||
<> | ||
<Label className="grid gap-2" htmlFor="email"> | ||
<span>Email</span> | ||
<Input | ||
type="email" | ||
id="email" | ||
placeholder="example@example.com" | ||
className={cn( | ||
"transition-[border,background-color,box-shadow]", | ||
"text-base lg:text-xl", | ||
"h-[3.75rem] border-[1px] px-6 outline-none ring-0 ring-[transparent]", | ||
"focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-[transparent] focus-visible:ring-offset-0" | ||
)} | ||
disabled={submitActive} | ||
/> | ||
</Label> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"use client"; | ||
import React, { useRef } from "react"; | ||
|
||
import { cn } from "@/lib/utils"; | ||
|
||
export default function Expandable({ | ||
id, | ||
expanded, | ||
className, | ||
children, | ||
}: { | ||
id?: string; | ||
className?: string; | ||
expanded: boolean; | ||
children: React.ReactNode; | ||
}) { | ||
const element = useRef<HTMLDivElement>(null); | ||
return ( | ||
<> | ||
<div | ||
className={cn( | ||
"!m-0 w-full origin-top duration-200", | ||
!expanded && "invisible h-0 -translate-y-2 scale-y-75 opacity-0", | ||
className | ||
)} | ||
id={id} | ||
ref={element} | ||
aria-hidden={!expanded} | ||
> | ||
{children} | ||
</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
"use client"; | ||
import Link from "next/link"; | ||
import { useRef, useState } from "react"; | ||
import { FaEye } from "react-icons/fa6"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { cn } from "@/lib/utils"; | ||
|
||
export default function PasswordInput() { | ||
const [submitActive] = useState<boolean>(false); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const [passwordStatus, setPasswordStatus] = useState<boolean>(false); | ||
const changeInputStatusAndFocus = () => { | ||
setPasswordStatus(!passwordStatus); | ||
inputRef.current!.focus(); | ||
}; | ||
return ( | ||
<> | ||
<div className="relative grid gap-2"> | ||
<div className="flex items-center"> | ||
<Label htmlFor="password">Password</Label> | ||
<Link className="ml-auto inline-block text-sm underline" href="#"> | ||
Forgot your password? | ||
</Link> | ||
</div> | ||
<Input | ||
type={!passwordStatus ? "password" : "text"} | ||
id="password" | ||
placeholder="******" | ||
className={cn( | ||
"transition-[border,background-color,box-shadow]", | ||
"text-base lg:text-xl", | ||
"h-[3.75rem] border-[1px] px-6 outline-none ring-0 ring-[transparent]", | ||
"focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-[transparent] focus-visible:ring-offset-0" | ||
)} | ||
disabled={submitActive} | ||
ref={inputRef} | ||
/> | ||
<Button | ||
className="absolute right-4 top-11 h-7 w-7" | ||
size="icon" | ||
variant="ghost" | ||
type="button" | ||
onClick={changeInputStatusAndFocus} | ||
> | ||
<FaEye className="h-4 w-4" /> | ||
<span className="sr-only">Toggle password visibility</span> | ||
</Button> | ||
</div> | ||
<div className="flex items-center justify-between gap-1"> | ||
<div className="h-1 w-1/4 rounded-full bg-foreground" /> | ||
<div className="h-1 w-1/4 rounded-full bg-foreground" /> | ||
<div className="h-1 w-1/4 rounded-full bg-foreground" /> | ||
<div className="h-1 w-1/4 rounded-full bg-foreground" /> | ||
</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"use client"; | ||
|
||
import { useRef, useState } from "react"; | ||
import { FaEye } from "react-icons/fa6"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { cn } from "@/lib/utils"; | ||
|
||
export default function RePasswordInput() { | ||
const [submitActive] = useState<boolean>(false); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const [passwordStatus, setPasswordStatus] = useState<boolean>(false); | ||
const changeInputStatusAndFocus = () => { | ||
setPasswordStatus(!passwordStatus); | ||
inputRef.current!.focus(); | ||
}; | ||
return ( | ||
<> | ||
<div className="relative mt-4 grid gap-2"> | ||
<Label htmlFor="re-password">Re-type Password</Label> | ||
<Input | ||
type={!passwordStatus ? "password" : "text"} | ||
id="re-password" | ||
placeholder="******" | ||
className={cn( | ||
"transition-[border,background-color,box-shadow]", | ||
"text-base lg:text-xl", | ||
"h-[3.75rem] border-[1px] px-6 outline-none ring-0 ring-[transparent]", | ||
"focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-[transparent] focus-visible:ring-offset-0" | ||
)} | ||
disabled={submitActive} | ||
/> | ||
<Button | ||
className="absolute right-4 top-[2.25rem] h-7 w-7" | ||
size="icon" | ||
variant="ghost" | ||
type="button" | ||
onClick={changeInputStatusAndFocus} | ||
> | ||
<FaEye className="h-4 w-4" /> | ||
<span className="sr-only">Toggle password visibility</span> | ||
</Button> | ||
</div> | ||
</> | ||
); | ||
} |