base structure

This commit is contained in:
hamid zarghami
2025-08-30 09:39:23 +03:30
commit d19bf516e5
101 changed files with 9955 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import { type ButtonHTMLAttributes, type FC, memo, type ReactNode } from 'react'
import MoonLoader from "react-spinners/MoonLoader"
import { type XOR } from '../helpers/types'
import { clx } from '../helpers/utils'
type Props = {
className?: string;
isLoading?: boolean,
} & ButtonHTMLAttributes<HTMLButtonElement> &
XOR<{ children: ReactNode }, { label: string }>;
const Button: FC<Props> = memo((props: Props) => {
const buttonClass = clx(
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full',
props.disabled && 'cursor-not-allowed opacity-60',
props.className
);
return (
<button disabled={props.isLoading} {...props} className={`${buttonClass} ${props.className}`} >
{
props.isLoading ?
<MoonLoader color="white" size={16} />
:
props.label || props.children
}
</button>
)
})
export default Button
+79
View File
@@ -0,0 +1,79 @@
import { useState, useEffect, type FC } from 'react';
import DatePicker from 'react-multi-date-picker';
import persian from 'react-date-object/calendars/persian';
import persian_fa from 'react-date-object/locales/persian_fa';
import DateObject from 'react-date-object';
import CalenderIcon from '../assets/images/calendar.svg'
import { clx } from '../helpers/utils';
type Props = {
onChange: (date: string) => void;
defaulValue?: string;
error_text?: string;
placeholder: string;
reset?: boolean;
isDateTime?: boolean;
className?: string;
label?: string
};
const DatePickerComponent: FC<Props> = (props: Props) => {
const [value, setValue] = useState<DateObject | null>(null);
useEffect(() => {
if (props.reset) {
setValue(null);
}
}, [props.reset]);
useEffect(() => {
if (value) {
const formattedDate = `${value.year}/${value.month.number}/${value.day}`;
props.onChange(formattedDate);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
useEffect(() => {
if (props.defaulValue && !value) {
const defaultDate = new DateObject({
date: props.defaulValue,
calendar: persian,
locale: persian_fa,
});
setValue(defaultDate);
}
}, [props.defaulValue, value]);
return (
<div className="w-full">
{props.label &&
<div className='text-sm'>
{props.label}
</div>}
<div className={clx(
'relative ',
props.label && 'mt-1.5'
)}>
<DatePicker
placeholder={props.placeholder}
value={value}
onChange={(date) => setValue(date as DateObject)}
calendar={persian}
locale={persian_fa}
calendarPosition="bottom-right"
className={`rmdp-mobile ${props.className}`}
/>
{props.error_text && props.error_text !== '' && (
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium">
{props.error_text}
</div>
)}
<img src={CalenderIcon} className='absolute top-0 bottom-0 my-auto left-2' />
</div>
</div>
);
};
export default DatePickerComponent;
+57
View File
@@ -0,0 +1,57 @@
import { type FC, Fragment, type ReactNode, useEffect } from 'react'
import HeaderModal from './HeaderModal'
interface Props {
open: boolean,
close: () => void,
children: ReactNode,
isHeader?: boolean,
title_header?: string,
width?: number
}
const DefaulModal: FC<Props> = (props: Props) => {
useEffect(() => {
if (props.open) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'auto'
}
}, [props.open])
return (
<Fragment>
{
props.open && (
<Fragment>
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-x-hidden overflow-y-auto fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
<div className='relative xl:h-full h-[80%] bottom-0 left-0 flex xl:items-center sm:h-auto w-full xl:my-6 xl:p-2'>
<div className='border-0 h-auto p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'>
{
props.isHeader && props.title_header &&
<div className='pb-6 border-b border-white/20'>
<div className='h-[5px] w-[200px] mx-auto bg-[#D1D3D7] rounded-full mb-4 xl:hidden'></div>
<HeaderModal close={props.close} label={props.title_header} />
</div>
}
{props.children}
</div>
</div>
</div>
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 modalGlass inset-0 z-50 '></div>
</Fragment>
)
}
</Fragment>
)
}
export default DefaulModal
+15
View File
@@ -0,0 +1,15 @@
import { type FC } from 'react'
type Props = {
errorText: string
}
const Error: FC<Props> = (props: Props) => {
return (
<div className='mt-1.5 font-normal text-red-500 text-[11px]'>
{props.errorText}
</div>
)
}
export default Error
+21
View File
@@ -0,0 +1,21 @@
import { type FC } from 'react'
import XIcon from '../assets/images/close-circle.svg'
type Props = {
label: string,
close: () => void,
}
const HeaderModal: FC<Props> = (props: Props) => {
return (
<div className='flex justify-between items-center'>
<div className='text-sm'>{props.label}</div>
<div className='size-7 rounded-full bg-white bg-opacity-35 flex justify-center items-center'>
<img src={XIcon} alt='close' className='w-4 h-4' onClick={props.close} />
</div>
</div>
)
}
export default HeaderModal
+115
View File
@@ -0,0 +1,115 @@
import { type FC, type InputHTMLAttributes, useEffect, useState } from 'react'
import { clx } from '../helpers/utils';
import EyeIcon from '../assets/images/eye.svg'
import { SearchNormal } from 'iconsax-react';
import Error from './Error';
type Variant = "floating_outlined" | "primary" | "search";
type Props = {
label?: string;
className?: string;
variant?: Variant;
error_text?: string;
onEnter?: () => void;
unit?: string;
seprator?: boolean;
isNotRequired?: boolean;
onChangeSearchFinal?: (value: string) => void;
} & InputHTMLAttributes<HTMLInputElement>
const formatNumber = (value: string | number): string => {
if (!value) return '';
const inputValue = String(value).replace(/,/g, '');
return inputValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const Input: FC<Props> = (props: Props) => {
const [formattedValue, setFormattedValue] = useState<string>(
props.value ? formatNumber(props.value as string) : ''
);
const [showPassword, setShowPassword] = useState<boolean>(false)
const [search, setSearch] = useState<string>('')
const inputClass = clx(
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
props.readOnly && 'bg-gray-100 border-0 text-description',
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
props.className
);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (props.seprator) {
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
const formatted = formatNumber(inputValue);
// به‌روزرسانی مقدار قالب‌بندی‌شده
setFormattedValue(formatted);
// ارسال مقدار خام به `onChange` والد
props.onChange?.({
...event,
target: {
...event.target,
value: inputValue,
},
});
} else {
props.onChange?.(event);
}
};
useEffect(() => {
if (props.value) {
setFormattedValue(formatNumber(props.value as string));
} else {
setFormattedValue('');
}
}, [props.value])
useEffect(() => {
if (props.variant === 'search' && props.onChangeSearchFinal) {
const timeout = setTimeout(() => {
props.onChangeSearchFinal?.(search)
}, 1000)
return () => clearTimeout(timeout)
}
}, [search, props])
return (
<div className='w-full'>
<label className='text-sm'>
{props.label}
</label>
<div className='w-full relative mt-1'>
<input {...props} onChange={(e) => {
setSearch(e.target.value)
handleInputChange(e)
}} value={props.seprator ? formattedValue : props.value} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
{
props.type === 'password' &&
<img onClick={() => setShowPassword((oldValue) => !oldValue)} src={EyeIcon} className='w-5 absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
}
{
props.variant === 'search' &&
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
}
{
props.error_text &&
<Error
errorText={props.error_text}
/>
}
</div>
</div>
)
}
export default Input
+67
View File
@@ -0,0 +1,67 @@
import { type FC, useState } from 'react'
import DefaulModal from './DefaulModal'
import { useTranslation } from 'react-i18next'
import Button from './Button'
import Textarea from './Textarea'
type Props = {
isLoading?: boolean,
close: () => void,
isOpen: boolean,
onConfrim: (text?: string) => void,
label?: string,
isHasDescription?: boolean
}
const ModalConfrim: FC<Props> = (props: Props) => {
const { t } = useTranslation('global')
const [description, setDescription] = useState<string>('')
return (
<DefaulModal
open={props.isOpen}
close={props.close}
title_header={t('confrim.subject')}
isHeader
>
<div className='mt-6'>
<div className='text-sm text-center'>
{
props.label ?
props.label
:
t('confrim.content')
}
{
props.isHasDescription &&
<div className='mt-4'>
<Textarea
placeholder={t('description')}
onChange={(e) => setDescription(e.target.value)}
value={description}
className='bg-transparent border border-gray-500 rounded-xl w-[300px] p-2'
/>
</div>
}
</div>
<div className='flex gap-4 justify-center mt-10'>
<Button
label={t('confrim.yes')}
onClick={() => props.onConfrim(props.isHasDescription ? description : undefined)}
isLoading={props.isLoading}
/>
<Button
label={t('confrim.cancel')}
className='bg-transparent text-black border border-primary'
onClick={props.close}
/>
</div>
</div>
</DefaulModal>
)
}
export default ModalConfrim
+19
View File
@@ -0,0 +1,19 @@
import { type FC } from 'react'
import Logo from '../assets/images/logo_orig.svg'
import { useTranslation } from 'react-i18next'
const PageLoading: FC = () => {
const { t } = useTranslation('global')
return (
<div className='flex bg-transparent w-fit mx-auto flex-col gap-4 items-center'>
<img src={Logo} alt='logo' className='w-28' />
<div className='text-xs text-gray-700'>
{t('loading')}
</div>
</div>
)
}
export default PageLoading
+99
View File
@@ -0,0 +1,99 @@
import React from "react";
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}
const Pagination: React.FC<PaginationProps> = ({
currentPage,
totalPages,
onPageChange,
}) => {
const getPageNumbers = () => {
const pageNumbers: (number | string)[] = [];
const maxVisiblePages = 5; // تعداد حداکثری صفحات قابل مشاهده
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
if (currentPage > 3) {
pageNumbers.push(1);
if (currentPage > 4) {
pageNumbers.push("...");
}
}
const start = Math.max(2, currentPage - 1);
const end = Math.min(totalPages - 1, currentPage + 1);
for (let i = start; i <= end; i++) {
pageNumbers.push(i);
}
if (currentPage < totalPages - 2) {
if (currentPage < totalPages - 3) {
pageNumbers.push("...");
}
pageNumbers.push(totalPages);
}
}
return pageNumbers;
};
const pageNumbers = getPageNumbers();
return (
<div className="flex gap-2 text-xs justify-center items-center space-x-2 mt-4">
{/* دکمه قبلی */}
{/* <button
className={`px-3 py-1 rounded-md ${currentPage === 1
? "text-gray-400 cursor-not-allowed"
: "text-blue-600 hover:bg-gray-100"
}`}
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
disabled={currentPage === 1}
>
قبلی
</button> */}
{/* شماره صفحات */}
{pageNumbers.map((page, index) =>
typeof page === "number" ? (
<button
key={index}
className={`size-8 rounded-md ${currentPage === page
? "bg-primary text-white"
: "text-primary bg-[#EAECF5] hover:bg-gray-100"
}`}
onClick={() => onPageChange(page)}
>
{page}
</button>
) : (
<span key={index} className="px-3 py-1 text-gray-500">
{page}
</span>
)
)}
{/* دکمه بعدی */}
{/* <button
className={`px-3 py-1 rounded-md ${currentPage === totalPages
? "text-gray-400 cursor-not-allowed"
: "text-blue-600 hover:bg-gray-100"
}`}
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
بعدی
</button> */}
</div>
);
};
export default Pagination;
+20
View File
@@ -0,0 +1,20 @@
import { type FC } from 'react'
type Props = {
isActive: boolean,
value: string | boolean,
onChange: (value: string | boolean) => void
}
const Radio: FC<Props> = (props: Props) => {
return (
<div onClick={() => props.onChange(props.value)} className='size-4 cursor-pointer rounded-full bg-[#EAEDF5] flex justify-center items-center'>
{
props.isActive &&
<div className='size-2 bg-black rounded-full'></div>
}
</div>
)
}
export default Radio
+31
View File
@@ -0,0 +1,31 @@
import { type FC } from 'react'
import Radio from './Radio'
type Props = {
items: {
label: string
value: string | boolean
}[]
selected: string | boolean
onChange: (value: string | boolean) => void
}
const RadioGroup: FC<Props> = (props: Props) => {
return (
<div className='flex gap-5 items-center text-xs'>
{
props.items.map((item, index) => (
<div key={index} className='flex gap-2 items-center'>
<Radio value={item.value} onChange={props.onChange} isActive={item.value === props.selected} />
<div className='mt-0.5'>
{item.label}
</div>
</div>
))
}
</div>
)
}
export default RadioGroup
+62
View File
@@ -0,0 +1,62 @@
import { type FC, type SelectHTMLAttributes } from 'react'
import { clx } from '../helpers/utils'
import { ArrowDown2 } from 'iconsax-react'
import Error from './Error'
export type ItemsSelectType = {
value: string,
label: string,
}
type Props = {
className?: string,
items: ItemsSelectType[],
error_text?: string,
placeholder?: string,
label?: string,
} & SelectHTMLAttributes<HTMLSelectElement>
const Select: FC<Props> = (props: Props) => {
return (
<div className='w-full relative'>
{
props.label &&
<label className='text-sm'>
{props.label}
</label>
}
<select {...props} className={clx(
'w-full text-black block border appearance-none border-border px-2.5 h-10 text-sm rounded-2.5 bg-gray',
props.className,
props.label && 'mt-1'
)}>
{
props.placeholder &&
<option value="" disabled selected>{props.placeholder}</option>
}
{
props.items?.map((item) => {
return (
<option key={item.value} value={item.value}>
{item.label}
</option>
)
})
}
</select>
<ArrowDown2 size={16} color='black' className={clx(
'absolute z-0 top-3 left-2',
props.label && 'top-10'
)} />
{
props.error_text && props.error_text !== '' ?
<Error
errorText={props.error_text}
/>
: null
}
</div>
)
}
export default Select
+19
View File
@@ -0,0 +1,19 @@
import { type FC } from 'react'
const ServiceSection: FC = () => {
return (
<div className='flex gap-4'>
<div className='size-10 rounded-xl bg-green-300'></div>
<div>
<div className='text-sm'>
دی منو
</div>
<div className='text-xs text-description'>
منو رستوران
</div>
</div>
</div>
)
}
export default ServiceSection
+15
View File
@@ -0,0 +1,15 @@
import { type FC } from 'react'
type Props = {
color: string
}
const StatusCircle: FC<Props> = (props: Props) => {
return (
<div style={{ background: props.color }} className='size-1.5 rounded-full'>
</div>
)
}
export default StatusCircle
+22
View File
@@ -0,0 +1,22 @@
import { type FC } from 'react'
import { clx } from '../helpers/utils'
type Props = {
variant: 'success' | 'error' | 'warning',
text: string,
}
const StatusWithText: FC<Props> = (props: Props) => {
return (
<div className={clx(
'w-fit py-1 px-2 text-xs h-fit rounded-full',
props.variant === 'success' && 'bg-green-100 text-success',
props.variant === 'error' && 'bg-red-100 text-red-400',
props.variant === 'warning' && 'bg-yellow-100 text-yellow-400',
)}>
{props.text}
</div>
)
}
export default StatusWithText
+50
View File
@@ -0,0 +1,50 @@
import { type FC, type ReactNode } from 'react'
import { clx } from '../helpers/utils'
import { Swiper, SwiperSlide } from 'swiper/react'
type Item = {
icon: ReactNode,
label: string,
value: string,
}
type Props = {
items: Item[],
onChange: (value: string) => void,
active: string,
}
const Tabs: FC<Props> = (props: Props) => {
const SWIPER_SLIDE_STYLE = {
overflow: 'visible !important',
display: 'flex',
}
return (
<Swiper
slidesPerView='auto'
spaceBetween={30}
className='px-10 max-w-full w-fit items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-10 flex h-[80px] rounded-[32px] bg-white bg-opacity-45'>
{
props.items.map((item: Item, index: number) => {
return (
<SwiperSlide style={SWIPER_SLIDE_STYLE} onClick={() => props.onChange(item.value)} key={item.value} className={clx(
'flex flex-col max-w-fit mt-[15px] items-center gap-2 cursor-pointer',
index === 0 && 'pr-[30px]',
index === props.items.length - 1 && 'pl-[30px]',
props.active === item.value && 'text-black'
)}>
{item.icon}
<div className='text-xs'>
{item.label}
</div>
</SwiperSlide>
)
})
}
</Swiper>
)
}
export default Tabs
+22
View File
@@ -0,0 +1,22 @@
import { type FC, type ReactNode } from 'react'
interface Props {
text: string,
children?: ReactNode,
dir?: string,
}
const Td: FC<Props> = (props: Props) => {
return (
<td className='td' style={{ direction: props.dir === "ltr" ? "ltr" : "rtl" }}>
{
props.text ?
props.text
:
props.children
}
</td>
)
}
export default Td
+39
View File
@@ -0,0 +1,39 @@
import { type FC, type InputHTMLAttributes } from 'react'
import Error from './Error'
import { clx } from '../helpers/utils'
type Props = {
label?: string,
error_text?: string
} & InputHTMLAttributes<HTMLTextAreaElement>
const Textarea: FC<Props> = (props: Props) => {
return (
<div className='w-full relative'>
{
props.label &&
<div className='text-sm' >
{props.label}
</div>
}
<textarea
{...props}
className={clx(
'border border-border leading-7 w-full rounded-xl px-4 py-3 min-h-[100px] mt-1 text-xs',
props.className
)}
>
</textarea>
{
props.error_text &&
<Error errorText={props.error_text} />
}
</div>
)
}
export default Textarea
+18
View File
@@ -0,0 +1,18 @@
import { type FC } from 'react'
type Props = {
title: string,
}
const TitleLine: FC<Props> = (props: Props) => {
return (
<div className='w-full text-sm flex items-center gap-4'>
<div className='whitespace-nowrap'>{props.title}</div>
<svg style={{ width: '100%', height: 1 }} xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<line x1="0" y1="0" x2="100%" y2="0" stroke="#aaa" strokeWidth="1" strokeDasharray="5 3" />
</svg>
</div>
)
}
export default TitleLine
+30
View File
@@ -0,0 +1,30 @@
import { Trash } from 'iconsax-react'
import { type FC, useState } from 'react'
import ModalConfrim from './ModalConfrim'
interface Props {
onDelete: () => void,
isLoading?: boolean
}
const TrashWithConfrim: FC<Props> = ({ onDelete, isLoading = false }) => {
const [isConfirm, setIsConfirm] = useState(false)
return (
<div>
<Trash
onClick={() => setIsConfirm(true)}
className='size-5'
color='#888'
/>
<ModalConfrim
isOpen={isConfirm}
close={() => setIsConfirm(false)}
onConfrim={() => onDelete()}
isLoading={isLoading}
/>
</div>
)
}
export default TrashWithConfrim
+91
View File
@@ -0,0 +1,91 @@
import { type FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDropzone } from 'react-dropzone'
import { CloseCircle } from 'iconsax-react'
type Props = {
label: string,
isMultiple?: boolean,
onChange?: (file: File[]) => void;
isReset?: boolean;
}
const UploadBox: FC<Props> = (props: Props) => {
const { t } = useTranslation('global')
const [files, setFiles] = useState<File[]>([])
const onDrop = useCallback((acceptedFiles: File[]) => {
if (props.isMultiple) {
const array = [...files]
array.push(acceptedFiles[0])
setFiles(array)
if (props.onChange) {
props.onChange(array);
}
} else {
setFiles([acceptedFiles[0]])
if (props.onChange) {
props.onChange([acceptedFiles[0]])
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [files])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
const handleRemove = (index: number) => {
const array = [...files]
array.splice(index, 1)
setFiles(array)
if (props.onChange) {
props.onChange(array)
}
}
useEffect(() => {
if (props.isReset) {
setFiles([])
}
}, [props.isReset])
return (
<div>
<div className='text-sm'>{props.label}</div>
<div className='w-full h-10 mt-1 border border-border rounded-xl items-center px-2 flex gap-2.5'>
<button {...getRootProps()} className=' w-fit h-7 rounded-lg text-xs px-6 bg-secondary'>
<input {...getInputProps()} />
{t('select_file')}
</button>
<div className='lg:flex gap-2 hidden'>
{
files?.map((item, index) => (
<div key={index} className='flex bg-gray-200 py-1.5 px-2 rounded-lg items-center gap-1'>
<CloseCircle onClick={() => handleRemove(index)} size={16} color='red' />
<div className='text-xs'>{item.name}</div>
</div>
))
}
</div>
</div>
<div className='lg:hidden gap-2 flex flex-wrap mt-4'>
{
files?.map((item, index) => (
<div key={index} className='flex bg-gray-200 py-1.5 px-2 rounded-lg items-center gap-1'>
<CloseCircle onClick={() => handleRemove(index)} size={16} color='red' />
<div className='text-xs'>{item.name}</div>
</div>
))
}
</div>
</div>
)
}
export default UploadBox
+154
View File
@@ -0,0 +1,154 @@
import { CloseCircle, DocumentUpload, Gallery } from 'iconsax-react'
import { type FC, useCallback, useEffect, useState } from 'react'
import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { clx } from '../helpers/utils';
type Props = {
label: string;
onChange: (file: File[]) => void;
isMultiple?: boolean;
isFile?: boolean,
preview?: string[],
onChangePreview?: (preview: string[]) => void,
getCover?: (url: string) => void,
coverUrl?: string
}
const UploadBoxDraggble: FC<Props> = (props: Props) => {
const { t } = useTranslation('global')
const [files, setFiles] = useState<File[]>([])
const [perviews, setPerviews] = useState<string[]>(props.preview ? props.preview : [])
const [isFill, setIsFill] = useState<boolean>(false)
const [cover, setCover] = useState<string>(props.coverUrl ? props.coverUrl : '')
const onDrop = useCallback((acceptedFiles: File[]) => {
if (props.isMultiple) {
const array = [...files]
array.push(acceptedFiles[0])
setFiles(array)
props.onChange(array)
} else {
setFiles([acceptedFiles[0]])
props.onChange([acceptedFiles[0]])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [files])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
const handleDelete = (index: number) => {
const array = [...files]
array.splice(index, 1)
setFiles(array)
props.onChange(array)
}
const handleDeletePreview = (index: number) => {
const array = [...perviews];
array.splice(index, 1);
setPerviews(array);
if (props.onChangePreview) {
props.onChangePreview(array);
}
}
useEffect(() => {
if (props.preview && props.preview.length > 0 && !isFill) {
setPerviews(props.preview)
console.log('preview', props.preview);
setIsFill(true)
}
}, [props.preview, isFill])
useEffect(() => {
setCover(props.coverUrl ? props.coverUrl : '')
}, [props.coverUrl])
return (
<div>
<div {...getRootProps()} className='w-full py-8 border border-dashed border-description flex flex-col justify-center items-center gap-4 rounded-2xl'>
<input {...getInputProps()} />
<Gallery
className='size-8'
color='#8C90A3'
/>
<div className='text-description text-xs'>
{props.label}
</div>
<div className='flex items-center gap-2'>
<DocumentUpload
className='size-4'
color='black'
/>
<div className='text-xs'>{t('upload')}</div>
</div>
</div>
{
files.length > 0 || perviews ? (
<div className='mt-4 flex gap-4 items-center'>
{
perviews && perviews.map((item, index) => {
return (
<div key={index} className='flex items-center gap-2'>
<div key={index} className='flex relative items-center gap-2'>
<img onClick={() => {
if (props.getCover) {
setCover(item)
props.getCover(item)
}
}} src={item}
className={clx(
'size-10 rounded-full object-cover',
cover === item ? 'border-2 border-red-400' : ''
)} />
<div onClick={() => handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
<CloseCircle className='size-4 ' color='red' />
</div>
</div>
</div>
)
})
}
{
files.map((file, index) => {
if (props.isFile) {
return (
<div key={index} className='flex border p-2 rounded-lg items-center gap-2'>
<div className='flex relative items-center gap-2'>
<div className='text-xs'>{file.name}</div>
<div className='absolute -left-4 -top-4 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
</div>
</div>
</div>
)
}
else
return (
<div key={index} className='flex items-center gap-2'>
<div key={index} className='flex relative items-center gap-2'>
<img src={URL.createObjectURL(file)} alt={file.name} className='size-10 rounded-full object-cover' />
<div className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
</div>
</div>
</div>
)
})
}
</div>
) : null
}
</div>
)
}
export default UploadBoxDraggble