Skip to content

Commit

Permalink
feat(form): add inputs, labels, and error indicators
Browse files Browse the repository at this point in the history
This change adds the basic password and email inputs, along with components that can be used to
build other inputs.
  • Loading branch information
rikhall1515 committed Apr 26, 2024
1 parent 6ffb91d commit 665e47b
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 0 deletions.
33 changes: 33 additions & 0 deletions components/forms/InputError.tsx
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>
</>
);
}
27 changes: 27 additions & 0 deletions components/forms/InputLabel.tsx
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>
</>
);
}
29 changes: 29 additions & 0 deletions components/forms/email.tsx
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>
</>
);
}
34 changes: 34 additions & 0 deletions components/forms/expandable.tsx
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>
</>
);
}
60 changes: 60 additions & 0 deletions components/forms/password.tsx
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>
</>
);
}
48 changes: 48 additions & 0 deletions components/forms/rePassword.tsx
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>
</>
);
}

0 comments on commit 665e47b

Please sign in to comment.