kontenhumas-fe/components/form/common/DynamicArray.tsx

172 lines
5.8 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon, CopyIcon, ArrowUpIcon, ArrowDownIcon } from "@/components/icons";
import { FormField } from "./FormField";
interface DynamicArrayProps<T> {
items: T[];
onItemsChange: (items: T[]) => void;
renderItem: (item: T, index: number, onUpdate: (item: T) => void, onDelete: () => void) => React.ReactNode;
addItemLabel?: string;
emptyStateMessage?: string;
allowReorder?: boolean;
allowDuplicate?: boolean;
maxItems?: number;
className?: string;
}
export function DynamicArray<T>({
items,
onItemsChange,
renderItem,
addItemLabel = "Add Item",
emptyStateMessage = "No items added yet",
allowReorder = true,
allowDuplicate = true,
maxItems,
className = "",
}: DynamicArrayProps<T>) {
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set());
const addItem = () => {
if (maxItems && items.length >= maxItems) return;
const newItem = {} as T;
onItemsChange([...items, newItem]);
};
const updateItem = (index: number, updatedItem: T) => {
const newItems = [...items];
newItems[index] = updatedItem;
onItemsChange(newItems);
};
const deleteItem = (index: number) => {
const newItems = items.filter((_, i) => i !== index);
onItemsChange(newItems);
};
const duplicateItem = (index: number) => {
if (maxItems && items.length >= maxItems) return;
const duplicatedItem = { ...items[index] };
const newItems = [...items];
newItems.splice(index + 1, 0, duplicatedItem);
onItemsChange(newItems);
};
const moveItem = (index: number, direction: 'up' | 'down') => {
const newItems = [...items];
const targetIndex = direction === 'up' ? index - 1 : index + 1;
if (targetIndex < 0 || targetIndex >= items.length) return;
[newItems[index], newItems[targetIndex]] = [newItems[targetIndex], newItems[index]];
onItemsChange(newItems);
};
const toggleExpanded = (index: number) => {
const newExpanded = new Set(expandedItems);
if (newExpanded.has(index)) {
newExpanded.delete(index);
} else {
newExpanded.add(index);
}
setExpandedItems(newExpanded);
};
return (
<div className={`space-y-4 ${className}`}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Items ({items.length})</h3>
<Button
type="button"
onClick={addItem}
disabled={maxItems ? items.length >= maxItems : false}
className="flex items-center gap-2"
>
<PlusIcon className="h-4 w-4" />
{addItemLabel}
</Button>
</div>
{items.length === 0 ? (
<Card>
<CardContent className="flex items-center justify-center py-8">
<p className="text-gray-500">{emptyStateMessage}</p>
</CardContent>
</Card>
) : (
<div className="space-y-3">
{items.map((item, index) => (
<Card key={index} className="border border-gray-200">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base">
Item {index + 1}
</CardTitle>
<div className="flex items-center gap-2">
{allowReorder && (
<>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => moveItem(index, 'up')}
disabled={index === 0}
>
<ArrowUpIcon className="h-4 w-4" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => moveItem(index, 'down')}
disabled={index === items.length - 1}
>
<ArrowDownIcon className="h-4 w-4" />
</Button>
</>
)}
{allowDuplicate && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => duplicateItem(index)}
disabled={maxItems ? items.length >= maxItems : false}
>
<CopyIcon className="h-4 w-4" />
</Button>
)}
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => deleteItem(index)}
className="text-red-600 hover:text-red-700"
>
<TrashIcon className="h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
{renderItem(item, index, (updatedItem) => updateItem(index, updatedItem), () => deleteItem(index))}
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}