diff --git a/package-lock.json b/package-lock.json index 8b6cd76..a065602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@reduxjs/toolkit": "^2.12.0", "@tailwindcss/vite": "^4.3.1", "clsx": "^2.1.1", + "input-otp": "^1.4.2", "lucide-react": "^1.18.0", "react": "^19.2.6", "react-day-picker": "^10.0.1", @@ -3132,6 +3133,16 @@ "node": ">=0.8.19" } }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", diff --git a/package.json b/package.json index 57dd650..981cecc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@reduxjs/toolkit": "^2.12.0", "@tailwindcss/vite": "^4.3.1", "clsx": "^2.1.1", + "input-otp": "^1.4.2", "lucide-react": "^1.18.0", "react": "^19.2.6", "react-day-picker": "^10.0.1", diff --git a/src/components/customs/InputOtp.tsx b/src/components/customs/InputOtp.tsx index e69de29..d6d2f27 100644 --- a/src/components/customs/InputOtp.tsx +++ b/src/components/customs/InputOtp.tsx @@ -0,0 +1,90 @@ +"use client"; + +import * as React from "react"; +import { OTPInput, OTPInputContext } from "input-otp"; +import { MinusIcon } from "lucide-react"; + +import { cn } from "../../lib/utils"; + +function InputOTP({ + className, + containerClassName, + ...props +}: React.ComponentProps & { + containerClassName?: string; +}) { + return ( + + ); +} + +function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function InputOTPSlot({ + index, + className, + ...props +}: React.ComponentProps<"div"> & { + index: number; +}) { + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}; + + return ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ); +} + +function InputOTPSeparator({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ +
+ ); +} + +export { + InputOTP, + InputOTPGroup, + InputOTPSlot, + InputOTPSeparator, +}; \ No newline at end of file diff --git a/src/components/customs/Loader.tsx b/src/components/customs/Loader.tsx index e69de29..8ccc25d 100644 --- a/src/components/customs/Loader.tsx +++ b/src/components/customs/Loader.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Loader2 } from 'lucide-react'; + +interface LoaderProps { + size?: 'sm' | 'md' | 'lg' | 'xl'; + text?: string; + className?: string; + fullScreen?: boolean; // New prop for full page loader +} + +/** + * Size mapping for consistent loader dimensions + */ +const sizeClasses = { + sm: 'w-4 h-4', + md: 'w-8 h-8', + lg: 'w-12 h-12', + xl: 'w-16 h-16', +}; + +/** + * Inline Loader - Used inside components, cards, buttons, etc. + */ +export const CustomLoader: React.FC = ({ + size = 'md', + text, + className = '', +}) => { + return ( +
+ + {text && ( +

{text}

+ )} +
+ ); +}; + +/** + * Page Loader - Full screen overlay for initial loading or route transitions + */ +export const PageLoader: React.FC = ({ + size = 'lg', + text = 'Loading...', + className = '', +}) => { + return ( +
+
+ + + {/* Optional subtle ring effect */} +
+
+ + {text && ( +

+ {text} +

+ )} +
+ ); +}; + +// Default export (most commonly used) +export default CustomLoader; \ No newline at end of file