new 2 custom files added

This commit is contained in:
MohamedHasan07
2026-06-20 11:08:32 +05:30
parent bba8dc9864
commit 36c1524a5d
4 changed files with 171 additions and 0 deletions
+11
View File
@@ -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",
+1
View File
@@ -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",
+90
View File
@@ -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<typeof OTPInput> & {
containerClassName?: string;
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn(
"flex items-center gap-2 has-disabled:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
);
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-group"
className={cn("flex items-center gap-2", className)}
{...props}
/>
);
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<"div"> & {
index: number;
}) {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"relative flex h-10 w-10 items-center justify-center border border-input bg-background text-sm transition-all",
"first:rounded-l-md first:border-l last:rounded-r-md",
"data-[active=true]:border-primary data-[active=true]:ring-2 data-[active=true]:ring-primary/20",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div>
)}
</div>
);
}
function InputOTPSeparator({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-separator"
className={cn("text-muted-foreground", className)}
role="separator"
{...props}
>
<MinusIcon className="h-4 w-4" />
</div>
);
}
export {
InputOTP,
InputOTPGroup,
InputOTPSlot,
InputOTPSeparator,
};
+69
View File
@@ -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<LoaderProps> = ({
size = 'md',
text,
className = '',
}) => {
return (
<div className={`flex flex-col items-center justify-center p-4 ${className}`}>
<Loader2 className={`${sizeClasses[size]} animate-spin text-primary`} />
{text && (
<p className="mt-2 text-sm text-gray-500 font-medium">{text}</p>
)}
</div>
);
};
/**
* Page Loader - Full screen overlay for initial loading or route transitions
*/
export const PageLoader: React.FC<LoaderProps> = ({
size = 'lg',
text = 'Loading...',
className = '',
}) => {
return (
<div
className={`fixed inset-0 z-[9999] flex flex-col items-center justify-center
bg-white/90 backdrop-blur-sm ${className}`}
>
<div className="relative flex flex-col items-center">
<Loader2 className={`${sizeClasses[size]} animate-spin text-primary`} />
{/* Optional subtle ring effect */}
<div className={`absolute inset-0 ${sizeClasses[size]} border-4 border-gray-200 rounded-full`} />
</div>
{text && (
<p className="mt-6 text-sm font-medium text-gray-600 tracking-wide animate-pulse">
{text}
</p>
)}
</div>
);
};
// Default export (most commonly used)
export default CustomLoader;