mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 21:41:52 +07:00
First commit
This commit is contained in:
23
src/components/form/Form.tsx
Normal file
23
src/components/form/Form.tsx
Normal 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;
|
||||
27
src/components/form/Label.tsx
Normal file
27
src/components/form/Label.tsx
Normal 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;
|
||||
166
src/components/form/MultiSelect.tsx
Normal file
166
src/components/form/MultiSelect.tsx
Normal 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;
|
||||
64
src/components/form/Select.tsx
Normal file
64
src/components/form/Select.tsx
Normal 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;
|
||||
60
src/components/form/date-picker.tsx
Normal file
60
src/components/form/date-picker.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
37
src/components/form/form-elements/CheckboxComponents.tsx
Normal file
37
src/components/form/form-elements/CheckboxComponents.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
120
src/components/form/form-elements/DefaultInputs.tsx
Normal file
120
src/components/form/form-elements/DefaultInputs.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
77
src/components/form/form-elements/DropZone.tsx
Normal file
77
src/components/form/form-elements/DropZone.tsx
Normal 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;
|
||||
23
src/components/form/form-elements/FileInputExample.tsx
Normal file
23
src/components/form/form-elements/FileInputExample.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
56
src/components/form/form-elements/InputGroup.tsx
Normal file
56
src/components/form/form-elements/InputGroup.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
70
src/components/form/form-elements/InputStates.tsx
Normal file
70
src/components/form/form-elements/InputStates.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
43
src/components/form/form-elements/RadioButtons.tsx
Normal file
43
src/components/form/form-elements/RadioButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
61
src/components/form/form-elements/SelectInputs.tsx
Normal file
61
src/components/form/form-elements/SelectInputs.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
43
src/components/form/form-elements/TextAreaInput.tsx
Normal file
43
src/components/form/form-elements/TextAreaInput.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
42
src/components/form/form-elements/ToggleSwitch.tsx
Normal file
42
src/components/form/form-elements/ToggleSwitch.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
141
src/components/form/group-input/PhoneInput.tsx
Normal file
141
src/components/form/group-input/PhoneInput.tsx
Normal 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;
|
||||
82
src/components/form/input/Checkbox.tsx
Normal file
82
src/components/form/input/Checkbox.tsx
Normal 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;
|
||||
18
src/components/form/input/FileInput.tsx
Normal file
18
src/components/form/input/FileInput.tsx
Normal 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;
|
||||
96
src/components/form/input/InputField.tsx
Normal file
96
src/components/form/input/InputField.tsx
Normal 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;
|
||||
65
src/components/form/input/Radio.tsx
Normal file
65
src/components/form/input/Radio.tsx
Normal 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;
|
||||
59
src/components/form/input/RadioSm.tsx
Normal file
59
src/components/form/input/RadioSm.tsx
Normal 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;
|
||||
63
src/components/form/input/TextArea.tsx
Normal file
63
src/components/form/input/TextArea.tsx
Normal 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;
|
||||
77
src/components/form/switch/Switch.tsx
Normal file
77
src/components/form/switch/Switch.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user