order detail
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-17 20:18:59 +03:30
parent dcb2c1326c
commit a5724369da
16 changed files with 481 additions and 105 deletions
+169
View File
@@ -0,0 +1,169 @@
import { useEffect, useRef, useState, type FC, type MouseEvent } from 'react'
import { ArrowDown2, CloseCircle, TickSquare } from 'iconsax-react'
import { clx } from '../helpers/utils'
import type { ItemsSelectType } from './Select'
type Props = {
className?: string
items: ItemsSelectType[]
value: string[]
onChange: (value: string[]) => void
onBlur?: () => void
error_text?: string
placeholder?: string
label?: string
disabled?: boolean
}
const MultiSelect: FC<Props> = ({
className,
items,
value,
onChange,
onBlur,
error_text,
placeholder = 'انتخاب کنید',
label,
disabled,
}) => {
const [isOpen, setIsOpen] = useState(false)
const wrapperRef = useRef<HTMLDivElement>(null)
const selectedValues = value ?? []
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
if (isOpen) {
setIsOpen(false)
onBlur?.()
}
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [isOpen, onBlur])
const selectedItems = items.filter((item) =>
selectedValues.includes(String(item.value)),
)
const toggleValue = (itemValue: string) => {
if (selectedValues.includes(itemValue)) {
onChange(selectedValues.filter((v) => v !== itemValue))
} else {
onChange([...selectedValues, itemValue])
}
}
const handleClear = (e: MouseEvent) => {
e.stopPropagation()
onChange([])
onBlur?.()
}
const displayText =
selectedItems.length === 0
? placeholder
: selectedItems.length <= 2
? selectedItems.map((item) => item.label).join('، ')
: `${selectedItems.length} مورد انتخاب شده`
return (
<div className={clx('w-full', className)} ref={wrapperRef}>
{label && (
<label className="text-sm text-primary-content">{label}</label>
)}
<div className="relative">
<button
type="button"
disabled={disabled}
onClick={() => setIsOpen((open) => !open)}
className={clx(
'w-full bg-white border border-border input-surface relative block appearance-none px-2.5 h-10 text-sm rounded-[10px] transition-colors text-right',
selectedItems.length === 0 && 'text-[#8c90a3]',
selectedItems.length > 0 && 'pl-10',
label && 'mt-1',
disabled && 'opacity-50 cursor-not-allowed',
)}
>
<span className="block truncate pr-6">{displayText}</span>
<ArrowDown2
size={16}
color="#8c90a3"
className="absolute z-0 top-3 left-2 pointer-events-none"
/>
</button>
{selectedItems.length > 0 && (
<button
type="button"
onClick={handleClear}
className="absolute top-0 bottom-0 my-auto left-8 z-10 flex items-center"
aria-label="پاک کردن"
>
<CloseCircle size={16} color="#8C90A3" />
</button>
)}
{isOpen && (
<div className="absolute z-20 top-full left-0 right-0 mt-1 bg-white border border-border rounded-[10px] shadow-lg max-h-60 overflow-auto">
{items.length === 0 ? (
<div className="px-3 py-2.5 text-xs text-[#8c90a3]">
موردی برای انتخاب نیست
</div>
) : (
<ul className="py-1">
{items.map((item) => {
const itemValue = String(item.value)
const isSelected = selectedValues.includes(itemValue)
return (
<li key={itemValue}>
<button
type="button"
onClick={() => toggleValue(itemValue)}
className={clx(
'w-full text-right px-3 py-2 text-sm hover:bg-muted transition-colors flex items-center justify-between gap-2',
isSelected && 'bg-blue-50 text-[#0037FF]',
)}
>
<span className="truncate">{item.label}</span>
{isSelected && (
<TickSquare size={16} color="#0037FF" className="shrink-0" />
)}
</button>
</li>
)
})}
</ul>
)}
</div>
)}
</div>
{selectedItems.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1.5">
{selectedItems.map((item) => (
<span
key={String(item.value)}
className="inline-flex items-center gap-1 rounded-lg border border-border bg-[#FBFCFF] px-2 py-1 text-xs text-[#1F2937]"
>
{item.label}
<button
type="button"
onClick={() => toggleValue(String(item.value))}
aria-label={`حذف ${item.label}`}
className="text-[#8c90a3] hover:text-red-500"
>
<CloseCircle size={12} color="currentColor" />
</button>
</span>
))}
</div>
)}
{error_text ? (
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium">
{error_text}
</div>
) : null}
</div>
)
}
export default MultiSelect