First commit

This commit is contained in:
dyzulk
2025-12-30 12:11:04 +07:00
commit 34dc111344
322 changed files with 31972 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import React, { FC, ReactNode, FormEvent } from "react";
interface FormProps {
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
children: ReactNode;
className?: string;
}
const Form: FC<FormProps> = ({ onSubmit, children, className }) => {
return (
<form
onSubmit={(event) => {
event.preventDefault(); // Prevent default form submission
onSubmit(event);
}}
className={` ${className}`} // Default spacing between form fields
>
{children}
</form>
);
};
export default Form;

View File

@@ -0,0 +1,27 @@
import React, { FC, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
interface LabelProps {
htmlFor?: string;
children: ReactNode;
className?: string;
}
const Label: FC<LabelProps> = ({ htmlFor, children, className }) => {
return (
<label
htmlFor={htmlFor}
className={twMerge(
// Default classes that apply by default
"mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400",
// User-defined className that can override the default margin
className
)}
>
{children}
</label>
);
};
export default Label;

View File

@@ -0,0 +1,166 @@
import React, { useState } from "react";
interface Option {
value: string;
text: string;
selected: boolean;
}
interface MultiSelectProps {
label: string;
options: Option[];
defaultSelected?: string[];
onChange?: (selected: string[]) => void;
disabled?: boolean;
}
const MultiSelect: React.FC<MultiSelectProps> = ({
label,
options,
defaultSelected = [],
onChange,
disabled = false,
}) => {
const [selectedOptions, setSelectedOptions] =
useState<string[]>(defaultSelected);
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
if (disabled) return;
setIsOpen((prev) => !prev);
};
const handleSelect = (optionValue: string) => {
const newSelectedOptions = selectedOptions.includes(optionValue)
? selectedOptions.filter((value) => value !== optionValue)
: [...selectedOptions, optionValue];
setSelectedOptions(newSelectedOptions);
if (onChange) onChange(newSelectedOptions);
};
const removeOption = (index: number, value: string) => {
const newSelectedOptions = selectedOptions.filter((opt) => opt !== value);
setSelectedOptions(newSelectedOptions);
if (onChange) onChange(newSelectedOptions);
};
const selectedValuesText = selectedOptions.map(
(value) => options.find((option) => option.value === value)?.text || ""
);
return (
<div className="w-full">
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
{label}
</label>
<div className="relative z-20 inline-block w-full">
<div className="relative flex flex-col items-center">
<div onClick={toggleDropdown} className="w-full">
<div className="mb-2 flex h-11 rounded-lg border border-gray-300 py-1.5 pl-3 pr-3 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300">
<div className="flex flex-wrap flex-auto gap-2">
{selectedValuesText.length > 0 ? (
selectedValuesText.map((text, index) => (
<div
key={index}
className="group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800"
>
<span className="flex-initial max-w-full">{text}</span>
<div className="flex flex-row-reverse flex-auto">
<div
onClick={() =>
removeOption(index, selectedOptions[index])
}
className="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
>
<svg
className="fill-current"
role="button"
width="14"
height="14"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.40717 4.46881C3.11428 4.17591 3.11428 3.70104 3.40717 3.40815C3.70006 3.11525 4.17494 3.11525 4.46783 3.40815L6.99943 5.93975L9.53095 3.40822C9.82385 3.11533 10.2987 3.11533 10.5916 3.40822C10.8845 3.70112 10.8845 4.17599 10.5916 4.46888L8.06009 7.00041L10.5916 9.53193C10.8845 9.82482 10.8845 10.2997 10.5916 10.5926C10.2987 10.8855 9.82385 10.8855 9.53095 10.5926L6.99943 8.06107L4.46783 10.5927C4.17494 10.8856 3.70006 10.8856 3.40717 10.5927C3.11428 10.2998 3.11428 9.8249 3.40717 9.53201L5.93877 7.00041L3.40717 4.46881Z"
/>
</svg>
</div>
</div>
</div>
))
) : (
<input
placeholder="Select option"
className="w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90"
readOnly
value="Select option"
/>
)}
</div>
<div className="flex items-center py-1 pl-1 pr-1 w-7">
<button
type="button"
onClick={toggleDropdown}
className="w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400"
>
<svg
className={`stroke-current ${isOpen ? "rotate-180" : ""}`}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.39551L10.0001 12.6038L15.2084 7.39551"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
</div>
</div>
{isOpen && (
<div
className="absolute left-0 z-40 w-full overflow-y-auto bg-white rounded-lg shadow-sm top-full max-h-select dark:bg-gray-900"
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col">
{options.map((option, index) => (
<div key={index}>
<div
className={`hover:bg-primary/5 w-full cursor-pointer rounded-t border-b border-gray-200 dark:border-gray-800`}
onClick={() => handleSelect(option.value)}
>
<div
className={`relative flex w-full items-center p-2 pl-2 ${
selectedOptions.includes(option.value)
? "bg-primary/10"
: ""
}`}
>
<div className="mx-2 leading-6 text-gray-800 dark:text-white/90">
{option.text}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default MultiSelect;

View File

@@ -0,0 +1,64 @@
import React, { useState } from "react";
interface Option {
value: string;
label: string;
}
interface SelectProps {
options: Option[];
placeholder?: string;
onChange: (value: string) => void;
className?: string;
defaultValue?: string;
}
const Select: React.FC<SelectProps> = ({
options,
placeholder = "Select an option",
onChange,
className = "",
defaultValue = "",
}) => {
// Manage the selected value
const [selectedValue, setSelectedValue] = useState<string>(defaultValue);
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
setSelectedValue(value);
onChange(value); // Trigger parent handler
};
return (
<select
className={`h-11 w-full appearance-none rounded-lg border border-gray-300 px-4 py-2.5 pr-11 text-sm shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${
selectedValue
? "text-gray-800 dark:text-white/90"
: "text-gray-400 dark:text-white/30"
} ${className}`}
value={selectedValue}
onChange={handleChange}
>
{/* Placeholder option */}
<option
value=""
disabled
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{placeholder}
</option>
{/* Map over options */}
{options.map((option) => (
<option
key={option.value}
value={option.value}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{option.label}
</option>
))}
</select>
);
};
export default Select;

View File

@@ -0,0 +1,60 @@
import { useEffect } from 'react';
import flatpickr from 'flatpickr';
import 'flatpickr/dist/flatpickr.css';
import Label from './Label';
import { CalenderIcon } from '../../icons';
import Hook = flatpickr.Options.Hook;
import DateOption = flatpickr.Options.DateOption;
type PropsType = {
id: string;
mode?: "single" | "multiple" | "range" | "time";
onChange?: Hook | Hook[];
defaultDate?: DateOption;
label?: string;
placeholder?: string;
};
export default function DatePicker({
id,
mode,
onChange,
label,
defaultDate,
placeholder,
}: PropsType) {
useEffect(() => {
const flatPickr = flatpickr(`#${id}`, {
mode: mode || "single",
static: true,
monthSelectorType: "static",
dateFormat: "Y-m-d",
defaultDate,
onChange,
});
return () => {
if (!Array.isArray(flatPickr)) {
flatPickr.destroy();
}
};
}, [mode, onChange, id, defaultDate]);
return (
<div>
{label && <Label htmlFor={id}>{label}</Label>}
<div className="relative">
<input
id={id}
placeholder={placeholder}
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<CalenderIcon className="size-6" />
</span>
</div>
</div>
);
}

View File

@@ -0,0 +1,37 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Checkbox from "../input/Checkbox";
export default function CheckboxComponents() {
const [isChecked, setIsChecked] = useState(false);
const [isCheckedTwo, setIsCheckedTwo] = useState(true);
const [isCheckedDisabled, setIsCheckedDisabled] = useState(false);
return (
<ComponentCard title="Checkbox">
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<Checkbox checked={isChecked} onChange={setIsChecked} />
<span className="block text-sm font-medium text-gray-700 dark:text-gray-400">
Default
</span>
</div>
<div className="flex items-center gap-3">
<Checkbox
checked={isCheckedTwo}
onChange={setIsCheckedTwo}
label="Checked"
/>
</div>
<div className="flex items-center gap-3">
<Checkbox
checked={isCheckedDisabled}
onChange={setIsCheckedDisabled}
disabled
label="Disabled"
/>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,120 @@
"use client";
import React, { useState } from 'react';
import ComponentCard from '../../common/ComponentCard';
import Label from '../Label';
import Input from '../input/InputField';
import Select from '../Select';
import { ChevronDownIcon, EyeCloseIcon, EyeIcon, TimeIcon } from '../../../icons';
import DatePicker from '@/components/form/date-picker';
export default function DefaultInputs() {
const [showPassword, setShowPassword] = useState(false);
const options = [
{ value: "marketing", label: "Marketing" },
{ value: "template", label: "Template" },
{ value: "development", label: "Development" },
];
const handleSelectChange = (value: string) => {
console.log("Selected value:", value);
};
return (
<ComponentCard title="Default Inputs">
<div className="space-y-6">
<div>
<Label>Input</Label>
<Input type="text" />
</div>
<div>
<Label>Input with Placeholder</Label>
<Input type="text" placeholder="info@gmail.com" />
</div>
<div>
<Label>Select Input</Label>
<div className="relative">
<Select
options={options}
placeholder="Select an option"
onChange={handleSelectChange}
className="dark:bg-dark-900"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<ChevronDownIcon/>
</span>
</div>
</div>
<div>
<Label>Password Input</Label>
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
/>
<button
onClick={() => setShowPassword(!showPassword)}
className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
>
{showPassword ? (
<EyeIcon className="fill-gray-500 dark:fill-gray-400" />
) : (
<EyeCloseIcon className="fill-gray-500 dark:fill-gray-400" />
)}
</button>
</div>
</div>
<div>
<DatePicker
id="date-picker"
label="Date Picker Input"
placeholder="Select a date"
onChange={(dates, currentDateString) => {
// Handle your logic
console.log({ dates, currentDateString });
}}
/>
</div>
<div>
<Label htmlFor="tm">Time Picker Input</Label>
<div className="relative">
<Input
type="time"
id="tm"
name="tm"
onChange={(e) => console.log(e.target.value)}
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<TimeIcon />
</span>
</div>
</div>
<div>
<Label htmlFor="tm">Input with Payment</Label>
<div className="relative">
<Input
type="text"
placeholder="Card number"
className="pl-[62px]"
/>
<span className="absolute left-0 top-1/2 flex h-11 w-[46px] -translate-y-1/2 items-center justify-center border-r border-gray-200 dark:border-gray-800">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="6.25" cy="10" r="5.625" fill="#E80B26" />
<circle cx="13.75" cy="10" r="5.625" fill="#F59D31" />
<path
d="M10 14.1924C11.1508 13.1625 11.875 11.6657 11.875 9.99979C11.875 8.33383 11.1508 6.8371 10 5.80713C8.84918 6.8371 8.125 8.33383 8.125 9.99979C8.125 11.6657 8.84918 13.1625 10 14.1924Z"
fill="#FC6020"
/>
</svg>
</span>
</div>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,77 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import { useDropzone } from "react-dropzone";
const DropzoneComponent: React.FC = () => {
const onDrop = (acceptedFiles: File[]) => {
console.log("Files dropped:", acceptedFiles);
// Handle file uploads here
};
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
"image/png": [],
"image/jpeg": [],
"image/webp": [],
"image/svg+xml": [],
},
});
return (
<ComponentCard title="Dropzone">
<div className="transition border border-gray-300 border-dashed cursor-pointer dark:hover:border-brand-500 dark:border-gray-700 rounded-xl hover:border-brand-500">
<form
{...getRootProps()}
className={`dropzone rounded-xl border-dashed border-gray-300 p-7 lg:p-10
${
isDragActive
? "border-brand-500 bg-gray-100 dark:bg-gray-800"
: "border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
}
`}
id="demo-upload"
>
{/* Hidden Input */}
<input {...getInputProps()} />
<div className="dz-message flex flex-col items-center m-0!">
{/* Icon Container */}
<div className="mb-[22px] flex justify-center">
<div className="flex h-[68px] w-[68px] items-center justify-center rounded-full bg-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-400">
<svg
className="fill-current"
width="29"
height="28"
viewBox="0 0 29 28"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.5019 3.91699C14.2852 3.91699 14.0899 4.00891 13.953 4.15589L8.57363 9.53186C8.28065 9.82466 8.2805 10.2995 8.5733 10.5925C8.8661 10.8855 9.34097 10.8857 9.63396 10.5929L13.7519 6.47752V18.667C13.7519 19.0812 14.0877 19.417 14.5019 19.417C14.9161 19.417 15.2519 19.0812 15.2519 18.667V6.48234L19.3653 10.5929C19.6583 10.8857 20.1332 10.8855 20.426 10.5925C20.7188 10.2995 20.7186 9.82463 20.4256 9.53184L15.0838 4.19378C14.9463 4.02488 14.7367 3.91699 14.5019 3.91699ZM5.91626 18.667C5.91626 18.2528 5.58047 17.917 5.16626 17.917C4.75205 17.917 4.41626 18.2528 4.41626 18.667V21.8337C4.41626 23.0763 5.42362 24.0837 6.66626 24.0837H22.3339C23.5766 24.0837 24.5839 23.0763 24.5839 21.8337V18.667C24.5839 18.2528 24.2482 17.917 23.8339 17.917C23.4197 17.917 23.0839 18.2528 23.0839 18.667V21.8337C23.0839 22.2479 22.7482 22.5837 22.3339 22.5837H6.66626C6.25205 22.5837 5.91626 22.2479 5.91626 21.8337V18.667Z"
/>
</svg>
</div>
</div>
{/* Text Content */}
<h4 className="mb-3 font-semibold text-gray-800 text-theme-xl dark:text-white/90">
{isDragActive ? "Drop Files Here" : "Drag & Drop Files Here"}
</h4>
<span className=" text-center mb-5 block w-full max-w-[290px] text-sm text-gray-700 dark:text-gray-400">
Drag and drop your PNG, JPG, WebP, SVG images here or browse
</span>
<span className="font-medium underline text-theme-sm text-brand-500">
Browse File
</span>
</div>
</form>
</div>
</ComponentCard>
);
};
export default DropzoneComponent;

View File

@@ -0,0 +1,23 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import FileInput from "../input/FileInput";
import Label from "../Label";
export default function FileInputExample() {
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
console.log("Selected file:", file.name);
}
};
return (
<ComponentCard title="File Input">
<div>
<Label>Upload file</Label>
<FileInput onChange={handleFileChange} className="custom-class" />
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,56 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Label from "../Label";
import Input from "../input/InputField";
import { EnvelopeIcon } from "../../../icons";
import PhoneInput from "../group-input/PhoneInput";
export default function InputGroup() {
const countries = [
{ code: "US", label: "+1" },
{ code: "GB", label: "+44" },
{ code: "CA", label: "+1" },
{ code: "AU", label: "+61" },
];
const handlePhoneNumberChange = (phoneNumber: string) => {
console.log("Updated phone number:", phoneNumber);
};
return (
<ComponentCard title="Input Group">
<div className="space-y-6">
<div>
<Label>Email</Label>
<div className="relative">
<Input
placeholder="info@gmail.com"
type="text"
className="pl-[62px]"
/>
<span className="absolute left-0 top-1/2 -translate-y-1/2 border-r border-gray-200 px-3.5 py-3 text-gray-500 dark:border-gray-800 dark:text-gray-400">
<EnvelopeIcon />
</span>
</div>
</div>
<div>
<Label>Phone</Label>
<PhoneInput
selectPosition="start"
countries={countries}
placeholder="+1 (555) 000-0000"
onChange={handlePhoneNumberChange}
/>
</div>{" "}
<div>
<Label>Phone</Label>
<PhoneInput
selectPosition="end"
countries={countries}
placeholder="+1 (555) 000-0000"
onChange={handlePhoneNumberChange}
/>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,70 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Input from "../input/InputField";
import Label from "../Label";
export default function InputStates() {
const [email, setEmail] = useState("");
const [error, setError] = useState(false);
// Simulate a validation check
const validateEmail = (value: string) => {
const isValidEmail =
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
setError(!isValidEmail);
return isValidEmail;
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
validateEmail(value);
};
return (
<ComponentCard
title="Input States"
desc="Validation styles for error, success and disabled states on form controls."
>
<div className="space-y-5 sm:space-y-6">
{/* Error Input */}
<div>
<Label>Email</Label>
<Input
type="email"
defaultValue={email}
error={error}
onChange={handleEmailChange}
placeholder="Enter your email"
hint={error ? "This is an invalid email address." : ""}
/>
</div>
{/* Success Input */}
<div>
<Label>Email</Label>
<Input
type="email"
defaultValue={email}
success={!error}
onChange={handleEmailChange}
placeholder="Enter your email"
hint={!error ? "Valid email!" : ""}
/>
</div>
{/* Disabled Input */}
<div>
<Label>Email</Label>
<Input
type="text"
defaultValue="disabled@example.com"
disabled={true}
placeholder="Disabled email"
hint="This field is disabled."
/>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,43 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Radio from "../input/Radio";
export default function RadioButtons() {
const [selectedValue, setSelectedValue] = useState<string>("option2");
const handleRadioChange = (value: string) => {
setSelectedValue(value);
};
return (
<ComponentCard title="Radio Buttons">
<div className="flex flex-wrap items-center gap-8">
<Radio
id="radio1"
name="group1"
value="option1"
checked={selectedValue === "option1"}
onChange={handleRadioChange}
label="Default"
/>
<Radio
id="radio2"
name="group1"
value="option2"
checked={selectedValue === "option2"}
onChange={handleRadioChange}
label="Selected"
/>
<Radio
id="radio3"
name="group1"
value="option3"
checked={selectedValue === "option3"}
onChange={handleRadioChange}
label="Disabled"
disabled={true}
/>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,61 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Label from "../Label";
import Select from "../Select";
import MultiSelect from "../MultiSelect";
import { ChevronDownIcon } from "@/icons";
export default function SelectInputs() {
const options = [
{ value: "marketing", label: "Marketing" },
{ value: "template", label: "Template" },
{ value: "development", label: "Development" },
];
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const handleSelectChange = (value: string) => {
console.log("Selected value:", value);
};
const multiOptions = [
{ value: "1", text: "Option 1", selected: false },
{ value: "2", text: "Option 2", selected: false },
{ value: "3", text: "Option 3", selected: false },
{ value: "4", text: "Option 4", selected: false },
{ value: "5", text: "Option 5", selected: false },
];
return (
<ComponentCard title="Select Inputs">
<div className="space-y-6">
<div>
<Label>Select Input</Label>
<div className="relative">
<Select
options={options}
placeholder="Select Option"
onChange={handleSelectChange}
className="dark:bg-dark-900"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<ChevronDownIcon/>
</span>
</div>
</div>
<div className="relative">
<MultiSelect
label="Multiple Select Options"
options={multiOptions}
defaultSelected={["1", "3"]}
onChange={(values) => setSelectedValues(values)}
/>
<p className="sr-only">
Selected Values: {selectedValues.join(", ")}
</p>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,43 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import TextArea from "../input/TextArea";
import Label from "../Label";
export default function TextAreaInput() {
const [message, setMessage] = useState("");
const [messageTwo, setMessageTwo] = useState("");
return (
<ComponentCard title="Textarea input field">
<div className="space-y-6">
{/* Default TextArea */}
<div>
<Label>Description</Label>
<TextArea
value={message}
onChange={(value) => setMessage(value)}
rows={6}
/>
</div>
{/* Disabled TextArea */}
<div>
<Label>Description</Label>
<TextArea rows={6} disabled />
</div>
{/* Error TextArea */}
<div>
<Label>Description</Label>
<TextArea
rows={6}
value={messageTwo}
error
onChange={(value) => setMessageTwo(value)}
hint="Please enter a valid message."
/>
</div>
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,42 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Switch from "../switch/Switch";
export default function ToggleSwitch() {
const handleSwitchChange = (checked: boolean) => {
console.log("Switch is now:", checked ? "ON" : "OFF");
};
return (
<ComponentCard title="Toggle switch input">
<div className="flex gap-4">
<Switch
label="Default"
defaultChecked={true}
onChange={handleSwitchChange}
/>
<Switch
label="Checked"
defaultChecked={true}
onChange={handleSwitchChange}
/>
<Switch label="Disabled" disabled={true} />
</div>{" "}
<div className="flex gap-4">
<Switch
label="Default"
defaultChecked={true}
onChange={handleSwitchChange}
color="gray"
/>
<Switch
label="Checked"
defaultChecked={true}
onChange={handleSwitchChange}
color="gray"
/>
<Switch label="Disabled" disabled={true} color="gray" />
</div>
</ComponentCard>
);
}

View File

@@ -0,0 +1,141 @@
"use client";
import React, { useState } from "react";
interface CountryCode {
code: string;
label: string;
}
interface PhoneInputProps {
countries: CountryCode[];
placeholder?: string;
onChange?: (phoneNumber: string) => void;
selectPosition?: "start" | "end"; // New prop for dropdown position
}
const PhoneInput: React.FC<PhoneInputProps> = ({
countries,
placeholder = "+1 (555) 000-0000",
onChange,
selectPosition = "start", // Default position is 'start'
}) => {
const [selectedCountry, setSelectedCountry] = useState<string>("US");
const [phoneNumber, setPhoneNumber] = useState<string>("+1");
const countryCodes: Record<string, string> = countries.reduce(
(acc, { code, label }) => ({ ...acc, [code]: label }),
{}
);
const handleCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newCountry = e.target.value;
setSelectedCountry(newCountry);
setPhoneNumber(countryCodes[newCountry]);
if (onChange) {
onChange(countryCodes[newCountry]);
}
};
const handlePhoneNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newPhoneNumber = e.target.value;
setPhoneNumber(newPhoneNumber);
if (onChange) {
onChange(newPhoneNumber);
}
};
return (
<div className="relative flex">
{/* Dropdown position: Start */}
{selectPosition === "start" && (
<div className="absolute">
<select
value={selectedCountry}
onChange={handleCountryChange}
className="appearance-none bg-none rounded-l-lg border-0 border-r border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{country.code}
</option>
))}
</select>
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none bg-none right-3 dark:text-gray-400">
<svg
className="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</div>
)}
{/* Input field */}
<input
type="tel"
value={phoneNumber}
onChange={handlePhoneNumberChange}
placeholder={placeholder}
className={`dark:bg-dark-900 h-11 w-full ${
selectPosition === "start" ? "pl-[84px]" : "pr-[84px]"
} rounded-lg border border-gray-300 bg-transparent py-3 px-4 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800`}
/>
{/* Dropdown position: End */}
{selectPosition === "end" && (
<div className="absolute right-0">
<select
value={selectedCountry}
onChange={handleCountryChange}
className="appearance-none bg-none rounded-r-lg border-0 border-l border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{country.code}
</option>
))}
</select>
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none right-3 dark:text-gray-400">
<svg
className="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</div>
)}
</div>
);
};
export default PhoneInput;

View File

@@ -0,0 +1,82 @@
import type React from "react";
interface CheckboxProps {
label?: string;
checked: boolean;
className?: string;
id?: string;
onChange: (checked: boolean) => void;
disabled?: boolean;
}
const Checkbox: React.FC<CheckboxProps> = ({
label,
checked,
id,
onChange,
className = "",
disabled = false,
}) => {
return (
<label
className={`flex items-center space-x-3 group cursor-pointer ${
disabled ? "cursor-not-allowed opacity-60" : ""
}`}
>
<div className="relative w-5 h-5">
<input
id={id}
type="checkbox"
className={`w-5 h-5 appearance-none cursor-pointer dark:border-gray-700 border border-gray-300 checked:border-transparent rounded-md checked:bg-brand-500 disabled:opacity-60
${className}`}
checked={checked}
onChange={(e) => onChange(e.target.checked)}
disabled={disabled}
/>
{checked && (
<svg
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="white"
strokeWidth="1.94437"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
{disabled && (
<svg
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
>
<path
d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
stroke="#E4E7EC"
strokeWidth="2.33333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
{label && (
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
{label}
</span>
)}
</label>
);
};
export default Checkbox;

View File

@@ -0,0 +1,18 @@
import React, { FC } from "react";
interface FileInputProps {
className?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const FileInput: FC<FileInputProps> = ({ className, onChange }) => {
return (
<input
type="file"
className={`focus:border-ring-brand-300 h-11 w-full overflow-hidden rounded-lg border border-gray-300 bg-transparent text-sm text-gray-500 shadow-theme-xs transition-colors file:mr-5 file:border-collapse file:cursor-pointer file:rounded-l-lg file:border-0 file:border-r file:border-solid file:border-gray-200 file:bg-gray-50 file:py-3 file:pl-3.5 file:pr-3 file:text-sm file:text-gray-700 placeholder:text-gray-400 hover:file:bg-gray-100 focus:outline-hidden focus:file:ring-brand-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400 dark:text-white/90 dark:file:border-gray-800 dark:file:bg-white/[0.03] dark:file:text-gray-400 dark:placeholder:text-gray-400 ${className}`}
onChange={onChange}
/>
);
};
export default FileInput;

View File

@@ -0,0 +1,96 @@
import React, { FC } from "react";
interface InputProps {
type?: "text" | "number" | "email" | "password" | "date" | "time" | string;
id?: string;
name?: string;
placeholder?: string;
defaultValue?: string | number;
value?: string | number;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
min?: string;
max?: string;
step?: number;
disabled?: boolean;
required?: boolean;
success?: boolean;
error?: boolean;
hint?: string; // Optional hint text
maxLength?: number;
autoFocus?: boolean;
}
const Input: FC<InputProps> = ({
type = "text",
id,
name,
placeholder,
defaultValue,
value,
onChange,
className = "",
min,
max,
step,
disabled = false,
required = false,
success = false,
error = false,
hint,
maxLength,
autoFocus,
}) => {
// Determine input styles based on state (disabled, success, error)
let inputClasses = `h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${className}`;
// Add styles for the different states
if (disabled) {
inputClasses += ` text-gray-500 border-gray-300 cursor-not-allowed dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700`;
} else if (error) {
inputClasses += ` text-error-800 border-error-500 focus:ring-3 focus:ring-error-500/10 dark:text-error-400 dark:border-error-500`;
} else if (success) {
inputClasses += ` text-success-500 border-success-400 focus:ring-success-500/10 focus:border-success-300 dark:text-success-400 dark:border-success-500`;
} else {
inputClasses += ` bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800`;
}
return (
<div className="relative">
<input
type={type}
id={id}
name={name}
placeholder={placeholder}
defaultValue={defaultValue}
value={value}
onChange={onChange}
min={min}
max={max}
step={step}
disabled={disabled}
required={required}
maxLength={maxLength}
autoFocus={autoFocus}
className={inputClasses}
/>
{/* Optional Hint Text */}
{hint && (
<p
className={`mt-1.5 text-xs ${
error
? "text-error-500"
: success
? "text-success-500"
: "text-gray-500"
}`}
>
{hint}
</p>
)}
</div>
);
};
export default Input;

View File

@@ -0,0 +1,65 @@
import React from "react";
interface RadioProps {
id: string; // Unique ID for the radio button
name: string; // Radio group name
value: string; // Value of the radio button
checked: boolean; // Whether the radio button is checked
label: string; // Label for the radio button
onChange: (value: string) => void; // Handler for value change
className?: string; // Optional additional classes
disabled?: boolean; // Optional disabled state for the radio button
}
const Radio: React.FC<RadioProps> = ({
id,
name,
value,
checked,
label,
onChange,
className = "",
disabled = false,
}) => {
return (
<label
htmlFor={id}
className={`relative flex cursor-pointer select-none items-center gap-3 text-sm font-medium ${
disabled
? "text-gray-300 dark:text-gray-600 cursor-not-allowed"
: "text-gray-700 dark:text-gray-400"
} ${className}`}
>
<input
id={id}
name={name}
type="radio"
value={value}
checked={checked}
onChange={() => !disabled && onChange(value)} // Prevent onChange when disabled
className="sr-only"
disabled={disabled} // Disable input
/>
<span
className={`flex h-5 w-5 items-center justify-center rounded-full border-[1.25px] ${
checked
? "border-brand-500 bg-brand-500"
: "bg-transparent border-gray-300 dark:border-gray-700"
} ${
disabled
? "bg-gray-100 dark:bg-gray-700 border-gray-200 dark:border-gray-700"
: ""
}`}
>
<span
className={`h-2 w-2 rounded-full bg-white ${
checked ? "block" : "hidden"
}`}
></span>
</span>
{label}
</label>
);
};
export default Radio;

View File

@@ -0,0 +1,59 @@
import React from "react";
interface RadioProps {
id: string; // Unique ID for the radio button
name: string; // Group name for the radio button
value: string; // Value of the radio button
checked: boolean; // Whether the radio button is checked
label: string; // Label text for the radio button
onChange: (value: string) => void; // Handler for when the radio button is toggled
className?: string; // Optional custom classes for styling
}
const RadioSm: React.FC<RadioProps> = ({
id,
name,
value,
checked,
label,
onChange,
className = "",
}) => {
return (
<label
htmlFor={id}
className={`flex cursor-pointer select-none items-center text-sm text-gray-700 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors ${className}`}
>
<span className="relative">
{/* Hidden Input */}
<input
type="radio"
id={id}
name={name}
value={value}
checked={checked}
onChange={() => onChange(value)}
className="sr-only"
/>
{/* Styled Radio Circle */}
<span
className={`mr-2 flex h-4 w-4 items-center justify-center rounded-full border ${
checked
? "border-brand-500 bg-brand-500"
: "bg-transparent border-gray-300 dark:border-gray-700"
}`}
>
{/* Inner Dot */}
<span
className={`h-1.5 w-1.5 rounded-full ${
checked ? "bg-white" : "bg-white dark:bg-[#1e2636]"
}`}
></span>
</span>
</span>
{label}
</label>
);
};
export default RadioSm;

View File

@@ -0,0 +1,63 @@
import React from "react";
interface TextareaProps {
placeholder?: string; // Placeholder text
rows?: number; // Number of rows
value?: string; // Current value
onChange?: (value: string) => void; // Change handler
className?: string; // Additional CSS classes
disabled?: boolean; // Disabled state
error?: boolean; // Error state
hint?: string; // Hint text to display
}
const TextArea: React.FC<TextareaProps> = ({
placeholder = "Enter your message", // Default placeholder
rows = 3, // Default number of rows
value = "", // Default value
onChange, // Callback for changes
className = "", // Additional custom styles
disabled = false, // Disabled state
error = false, // Error state
hint = "", // Default hint text
}) => {
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (onChange) {
onChange(e.target.value);
}
};
let textareaClasses = `w-full rounded-lg border px-4 py-2.5 text-sm shadow-theme-xs focus:outline-hidden ${className}`;
if (disabled) {
textareaClasses += ` bg-gray-100 opacity-50 text-gray-500 border-gray-300 cursor-not-allowed dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700`;
} else if (error) {
textareaClasses += ` bg-transparent text-gray-400 border-gray-300 focus:border-error-300 focus:ring-3 focus:ring-error-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-error-800`;
} else {
textareaClasses += ` bg-transparent text-gray-400 border-gray-300 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800`;
}
return (
<div className="relative">
<textarea
placeholder={placeholder}
rows={rows}
value={value}
onChange={handleChange}
disabled={disabled}
className={textareaClasses}
/>
{hint && (
<p
className={`mt-2 text-sm ${
error ? "text-error-500" : "text-gray-500 dark:text-gray-400"
}`}
>
{hint}
</p>
)}
</div>
);
};
export default TextArea;

View File

@@ -0,0 +1,77 @@
"use client";
import React, { useState, useEffect } from "react";
interface SwitchProps {
label: string;
defaultChecked?: boolean;
disabled?: boolean;
onChange?: (checked: boolean) => void;
color?: "blue" | "gray"; // Added prop to toggle color theme
}
const Switch: React.FC<SwitchProps> = ({
label,
defaultChecked = false,
disabled = false,
onChange,
color = "blue", // Default to blue color
}) => {
const [isChecked, setIsChecked] = useState(defaultChecked);
useEffect(() => {
setIsChecked(defaultChecked);
}, [defaultChecked]);
const handleToggle = () => {
if (disabled) return;
const newCheckedState = !isChecked;
setIsChecked(newCheckedState);
if (onChange) {
onChange(newCheckedState);
}
};
const switchColors =
color === "blue"
? {
background: isChecked
? "bg-brand-500 "
: "bg-gray-200 dark:bg-white/10", // Blue version
knob: isChecked
? "translate-x-full bg-white"
: "translate-x-0 bg-white",
}
: {
background: isChecked
? "bg-gray-800 dark:bg-white/10"
: "bg-gray-200 dark:bg-white/10", // Gray version
knob: isChecked
? "translate-x-full bg-white"
: "translate-x-0 bg-white",
};
return (
<label
className={`flex cursor-pointer select-none items-center gap-3 text-sm font-medium ${
disabled ? "text-gray-400" : "text-gray-700 dark:text-gray-400"
}`}
onClick={handleToggle} // Toggle when the label itself is clicked
>
<div className="relative">
<div
className={`block transition duration-150 ease-linear h-6 w-11 rounded-full ${
disabled
? "bg-gray-100 pointer-events-none dark:bg-gray-800"
: switchColors.background
}`}
></div>
<div
className={`absolute left-0.5 top-0.5 h-5 w-5 rounded-full shadow-theme-sm duration-150 ease-linear transform ${switchColors.knob}`}
></div>
</div>
{label}
</label>
);
};
export default Switch;