Skip to content

Commit

Permalink
Merge pull request #7 from UofA-Blueprint/ASC-64
Browse files Browse the repository at this point in the history
ASC-64: Input Code
  • Loading branch information
PaulHo0501 authored Mar 2, 2024
2 parents b3d9457 + 93778c7 commit d63c8d0
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 14 deletions.
15 changes: 8 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

// routes

Expand All @@ -12,6 +9,7 @@ import SearchBarTest from "./routes/SearchBarTestRoute";
import DropdownItem from "@/components/DropdownItem";
import SortDropdownListTestRoute from "@/routes/SortDropdownListTestRoute";
import NavigationTest from "@/routes/NavigationTest";
import InputCodeTest from "@/routes/InputCodeTest";
import ToastTestRoute from "@/routes/ToastTestRoute";
import MediaUploadZoneTestRoute from "@/routes/MediaUploadZoneTestRoute";

Expand All @@ -25,8 +23,8 @@ const router = createBrowserRouter([
element: <Test />,
},
{
path:"/SearchBarTestRoute",
element: <SearchBarTest />
path: "/SearchBarTestRoute",
element: <SearchBarTest />,
},
{
path: "/testButton",
Expand All @@ -40,12 +38,15 @@ const router = createBrowserRouter([
path: "/nav-test",
element: <NavigationTest />,
},
{
path: "/input-code-test",
element: <InputCodeTest />,
},
{
path: "/upload-file-test",
element: <MediaUploadZoneTestRoute />,
},
{

path: "/toast-test",
element: <ToastTestRoute />,
},
Expand Down
5 changes: 5 additions & 0 deletions src/assets/images/error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 126 additions & 0 deletions src/components/InputCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//#region Imports
import { twMerge } from "tailwind-merge";
import { useRef, useState } from "react";
import { WarningCircle } from "@phosphor-icons/react";
//#endregion

//#region Interfaces
interface InputCodeProps {
/**
* Indicates whether the input field has an error.
* If true, a red border and small information will be displayed.
*/
error: boolean;

/**
* The label of the input field.
* Defaults to an empty string if not provided.
*/
label?: string;

/**
* Indicates whether the input field is required.
* If true, an asterisk will be displayed after the label,
* and the text length will be checked to ensure it is not 0.
*/
required: boolean;
className?: string;
}
//#endregion

/**
* Renders an input code field component.
*
* @component
* @param {object} props - The component props.
* @param {boolean} props.error - Indicates whether the input field has an error.
* @param {string} props.label - The label of the input field. Defaults to an empty string if not provided.
* @param {boolean} props.required - Indicates whether the input field is required.
* @param {string} props.className - Additional CSS class name for the component.
* @returns {JSX.Element} The rendered input code component.
*/
export function InputCode({
error,
label = "",
required,
className,
}: InputCodeProps): JSX.Element {
const inputRefs = Array.from({ length: 6 }, () =>
useRef<HTMLInputElement | null>(null)
);

const [input, setInput] = useState<Record<number, string>>({
0: "",
1: "",
2: "",
3: "",
4: "",
5: "",
});
const [value, setValue] = useState<string>("");

//#region Functions
/**
* This function handles inputs based on the selected key.
* @param {React.KeyboardEvent<HTMLInputElement>} e - The keyboard event.
* @param {number} index - The index of the input field.
*/
const handleKeyDown = (
e: React.KeyboardEvent<HTMLInputElement>,
index: number
) => {
if (e.key === "Backspace" && index > 0) {
inputRefs[index]?.current?.value === ""
? inputRefs[index - 1]?.current?.focus()
: setInput((prev) => ({ ...prev, [index]: "" }));
} else if (e.key === "Tab" && index > 0) {
setInput((prev) => ({ ...prev, [index]: value }));
setValue("");
} else if (index < 5 && value !== "") {
setInput((prev) => ({ ...prev, [index]: value }));
inputRefs[index + 1]?.current?.focus();
setValue("");
}
};
//#endregion

return (
<div
className={twMerge(
"flex flex-col gap-y-2 md:gap-y-3 text-neutrals-dark-500 text-body-reg",
className
)}
>
{/* Title section */}
<div className="flex gap-x-1 items-center">
<p className="text-base capitalize">{label}</p>
{required && <p className="text-status-red-main">*</p>}
</div>

{/* Input Code Section */}
<div className="flex gap-x-2">
{[0, 1, 2, 3, 4, 5].map((_, index) => (
<input
key={index}
ref={inputRefs[index]}
type="text"
maxLength={1}
className={`w-10 h-14 md:w-12 md:h-16 text-center text-2xl md:text-3xl rounded-md bg-neutrals-light-300 border-2 border-status-red-main ${
error ? "border-status-red-main" : "border-none"
}`}
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, index)}
/>
))}
</div>

{/* Error Message */}
{error && (
<div className="flex text-body-sm items-center gap-x-1 text-status-red-main">
<WarningCircle size={20} />
<p>Error passcode</p>
</div>
)}
</div>
);
}
20 changes: 14 additions & 6 deletions src/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import logoUrl from "@/assets/images/asc_logo.svg";

//#region Interface
interface NavigationBarProps {
userType?: string;
/**
* The type of user.
* - "user" for regular users.
* - "admin" for admin users.
*/
userType?: "user" | "admin";
className?: string;
}
//#endregion
Expand All @@ -24,28 +29,31 @@ function signOut() {
* @param {string} props.className - The additional CSS class name.
* @returns {JSX.Element} The rendered navigation bar.
*/
export default function NavigationBar({
export function NavigationBar({
userType = "user",
className,
}: NavigationBarProps): JSX.Element {
return (
<nav
className={twMerge(
"flex justify-between items-center font-bod py-2 md:py-1 px-8 md:px-16 text-sm sm:text-base borer border-b-2 w-full",
"flex justify-between items-center font-bod py-2 md:py-1 px-8 md:px-16 borer border-b-2 w-full md:text-body-reg",
className
)}
>
{/* Logo */}
<a href="/">
<img src={logoUrl} alt="ASC Logo" />
<img
src={logoUrl}
alt="ASC Logo"
/>
</a>

{/* Navigation bar */}
<div className="space-x-[40px]">
<div className="space-x-[40px] flex">
{userType === "admin" ? (
<a
href="/dashboard"
className="hover:text-primary-main transition ease-in-out duration-75"
className="hover:text-primary-main hidden sm:block transition ease-in-out duration-75"
>
Members
</a>
Expand Down
21 changes: 21 additions & 0 deletions src/routes/InputCodeTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { InputCode } from "@/components/InputCode";

function NavigationTest() {
return (
<div className="flex flex-col gap-y-12 w-full h-screen items-center justify-center">
{/* TODO: Passing inputs */}
<InputCode
label="Input Code"
error={false}
required={false}
/>
<InputCode
label="Passcode"
error={true}
required={true}
/>
</div>
);
}

export default NavigationTest;
2 changes: 1 addition & 1 deletion src/routes/NavigationTest.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import NavigationBar from "../components/NavigationBar";
import { NavigationBar } from "../components/NavigationBar";

function NavigationTest() {
return (
Expand Down

0 comments on commit d63c8d0

Please sign in to comment.