mirror of https://github.com/buster-so/buster.git
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import React, { useMemo } from 'react';
|
|
import { type SelectItem } from './Select';
|
|
import { selectVariants } from './SelectBase';
|
|
import { cn } from '@/lib/classMerge';
|
|
import { Dropdown, type DropdownItem } from '../dropdown/Dropdown';
|
|
import { VariantProps } from 'class-variance-authority';
|
|
import { useMemoizedFn } from 'ahooks';
|
|
import { InputTag } from '../inputs/InputTag';
|
|
|
|
interface SelectMultipleProps extends VariantProps<typeof selectVariants> {
|
|
items: SelectItem[];
|
|
onChange: (item: string[]) => void;
|
|
className?: string;
|
|
placeholder?: string;
|
|
value: string[];
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
|
|
({
|
|
items: itemsProp,
|
|
onChange,
|
|
className,
|
|
placeholder = 'Select items...',
|
|
size = 'default',
|
|
variant = 'default',
|
|
value,
|
|
disabled
|
|
}) => {
|
|
const selectedRecord = useMemo(() => {
|
|
return itemsProp.reduce<Record<string, boolean>>((acc, item) => {
|
|
acc[item.value] = value.includes(item.value);
|
|
return acc;
|
|
}, {});
|
|
}, [value]);
|
|
|
|
const handleRemoveTag = (valueToRemove: string) => {
|
|
const newSelected = itemsProp
|
|
.filter((item) => item.value !== valueToRemove && selectedRecord[item.value])
|
|
.map((item) => item.value);
|
|
onChange(newSelected);
|
|
};
|
|
|
|
const handleSelect = useMemoizedFn((itemId: string) => {
|
|
const item = itemsProp.find((item) => item.value === itemId);
|
|
if (item) {
|
|
if (selectedRecord[item.value]) {
|
|
handleRemoveTag(item.value);
|
|
} else {
|
|
const newSelected = itemsProp
|
|
.filter((item) => selectedRecord[item.value])
|
|
.map((item) => item.value);
|
|
onChange([...newSelected, item.value]);
|
|
}
|
|
}
|
|
});
|
|
|
|
const items = useMemo(() => {
|
|
return itemsProp.map((item) => ({
|
|
...item,
|
|
selected: selectedRecord[item.value]
|
|
}));
|
|
}, [itemsProp, selectedRecord]);
|
|
|
|
const selectedItems: DropdownItem[] = useMemo(() => {
|
|
return items.filter((item) => item.selected);
|
|
}, [items]);
|
|
|
|
return (
|
|
<Dropdown
|
|
items={items}
|
|
onSelect={handleSelect}
|
|
selectType="multiple"
|
|
align="start"
|
|
modal={false}
|
|
className="w-[var(--radix-dropdown-menu-trigger-width)] max-w-full!">
|
|
<div
|
|
className={cn(
|
|
selectVariants({ variant, size }),
|
|
'relative overflow-hidden pr-0',
|
|
selectedItems.length > 0 && 'pl-1!',
|
|
disabled && 'cursor-not-allowed opacity-80',
|
|
className
|
|
)}>
|
|
<div className="scrollbar-hide flex h-full flex-nowrap items-center gap-1 overflow-x-auto">
|
|
{selectedItems.map((item) => (
|
|
<InputTag
|
|
key={item.value}
|
|
label={item.label}
|
|
value={item.value}
|
|
disabled={disabled}
|
|
onRemove={handleRemoveTag}
|
|
/>
|
|
))}
|
|
{selectedItems.length === 0 && (
|
|
<span className="text-gray-light text-sm">{placeholder}</span>
|
|
)}
|
|
</div>
|
|
{selectedItems.length > 0 && (
|
|
<div className="from-background via-background/80 pointer-events-none absolute top-0 right-0 z-10 h-full w-8 bg-gradient-to-l to-transparent" />
|
|
)}
|
|
</div>
|
|
</Dropdown>
|
|
);
|
|
}
|
|
);
|
|
SelectMultiple.displayName = 'SelectMultiple';
|