satupeta-main/shared/components/forms/form-multi-select.tsx

111 lines
2.9 KiB
TypeScript
Raw Normal View History

2026-01-28 05:48:46 +00:00
"use client";
import { Button } from "@/shared/components/ui/button"
import { Checkbox } from "@/shared/components/ui/checkbox"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/shared/components/ui/popover"
import { useRef, useState } from "react";
interface MultiSelectProps {
name: string;
options?: any[];
value?: any[];
onChange: (e: { target: { name: string; value: string[] } }) => void;
placeholder?: string;
}
export default function FormMultiSelect({
name,
options = [],
value = [],
onChange,
placeholder = "Pilih data",
}: MultiSelectProps) {
const toggle = (val:string) => {
let newValue:string[];
if (value.includes(val)) {
newValue = value.filter((v) => v !== val)
} else {
newValue = [...value, val]
}
if (onChange) {
onChange({
target: {
name,
value: newValue,
},
})
}
}
const selectedLabels = options
.filter((o) => value.includes(o.value))
.map((o) => o.label)
.join(", ")
const scrollRef = useRef<HTMLDivElement | null>(null);
const [isAtBottom, setIsAtBottom] = useState(false);
const handleScroll = () => {
console.log('gtw');
const el = scrollRef.current;
if (!el) return;
const atBottom =
el.scrollTop + el.clientHeight >= el.scrollHeight - 2;
setIsAtBottom(atBottom);
};
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal border-gray-300 rounded-md px-2 shadow-none"
>
<div className="flex-1 overflow-x-auto whitespace-nowrap scrollbar-hide">
{value.length > 0 ? selectedLabels : placeholder}
</div>
</Button>
</PopoverTrigger>
<PopoverContent
ref={scrollRef}
onScroll={handleScroll}
side="bottom"
align="start"
sideOffset={4}
className="w-[var(--radix-popover-trigger-width)] max-h-60 overflow-y-auto z-50"
>
<div
className="space-y-2"
>
{options.map((opt) => (
<label
key={opt.value}
className="flex items-center space-x-2"
>
<Checkbox
checked={value.includes(opt.value)}
onCheckedChange={() => toggle(opt.value)}
/>
<span>{opt.label}</span>
</label>
))}
{!isAtBottom && (
<div className="arrow-symbol pt-0 pb-2 bg-white w-full text-center fixed bottom-0"></div>
)}
</div>
</PopoverContent>
</Popover>
)
}