2025-03-25 12:04:20 +00:00
|
|
|
import React, { useState, useRef, useEffect } from "react";
|
|
|
|
|
|
|
|
// Dropdown Item Component
|
|
|
|
const DropdownItem = ({
|
|
|
|
label,
|
|
|
|
href,
|
|
|
|
onClick,
|
|
|
|
}: {
|
|
|
|
label: string;
|
|
|
|
href?: string;
|
|
|
|
onClick?: () => void;
|
|
|
|
}) => (
|
|
|
|
<a
|
|
|
|
href={href || "#"}
|
|
|
|
className="dropdown-item"
|
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
onClick?.();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{label}
|
|
|
|
</a>
|
|
|
|
);
|
|
|
|
|
|
|
|
// Nested Dropdown Component
|
|
|
|
const NestedDropdown = ({
|
|
|
|
label,
|
|
|
|
children,
|
|
|
|
onSelect,
|
|
|
|
}: {
|
|
|
|
label: string;
|
|
|
|
children: React.ReactNode;
|
|
|
|
onSelect: (selectedLabel: string) => void;
|
|
|
|
}) => {
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="nested-dropdown">
|
|
|
|
{/* Dropdown Trigger */}
|
|
|
|
<div
|
|
|
|
className={`dropdown-trigger ${open ? "open" : ""}`}
|
|
|
|
onClick={() => setOpen(!open)} // Toggle submenu on click
|
|
|
|
>
|
|
|
|
{label} <span className="icon">{open ? "▼" : "▶"}</span>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Submenu */}
|
|
|
|
{open && (
|
|
|
|
<div className="submenu">
|
|
|
|
{React.Children.map(children, (child) => {
|
|
|
|
if (React.isValidElement(child)) {
|
|
|
|
// Clone the element and pass the `onSelect` prop only if it's expected
|
|
|
|
return React.cloneElement(child as React.ReactElement<any>, { onSelect });
|
|
|
|
}
|
|
|
|
return child; // Return non-element children as-is
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Recursive Function to Render Nested Data
|
|
|
|
const renderNestedData = (
|
|
|
|
data: Record<string, any>,
|
|
|
|
onSelect: (selectedLabel: string) => void
|
|
|
|
) => {
|
|
|
|
return Object.entries(data).map(([key, value]) => {
|
|
|
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
|
|
// If the value is an object, render it as a nested dropdown
|
|
|
|
return (
|
|
|
|
<NestedDropdown key={key} label={key} onSelect={onSelect}>
|
|
|
|
{renderNestedData(value, onSelect)}
|
|
|
|
</NestedDropdown>
|
|
|
|
);
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
// If the value is an array, render each item as a dropdown item
|
|
|
|
return value.map((item, index) => (
|
|
|
|
<DropdownItem key={index} label={item} onClick={() => onSelect(item)} />
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
// If the value is a simple string, render it as a dropdown item
|
|
|
|
return (
|
|
|
|
<DropdownItem key={key} label={value} onClick={() => onSelect(value)} />
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Main Multi-Level Dropdown Component
|
|
|
|
const MultiLevelDropdown = ({ data }: { data: Record<string, any> }) => {
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const [selectedLabel, setSelectedLabel] = useState("Dropdown trigger");
|
|
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
// Handle outer click to close the dropdown
|
|
|
|
useEffect(() => {
|
|
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
|
|
if (
|
|
|
|
dropdownRef.current &&
|
|
|
|
!dropdownRef.current.contains(event.target as Node)
|
|
|
|
) {
|
|
|
|
setOpen(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
|
|
return () => {
|
|
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
// Handle selection of an item
|
|
|
|
const handleSelect = (selectedLabel: string) => {
|
|
|
|
setSelectedLabel(selectedLabel); // Update the dropdown trigger text
|
|
|
|
setOpen(false); // Close the dropdown
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="multi-level-dropdown" ref={dropdownRef}>
|
|
|
|
{/* Dropdown Trigger Button */}
|
|
|
|
<button
|
|
|
|
className={`dropdown-button ${open ? "open" : ""}`}
|
|
|
|
onClick={() => setOpen(!open)} // Toggle main menu on click
|
|
|
|
>
|
|
|
|
{selectedLabel} <span className="icon">▾</span>
|
|
|
|
</button>
|
|
|
|
|
|
|
|
{/* Dropdown Menu */}
|
|
|
|
{open && (
|
|
|
|
<div className="dropdown-menu">
|
|
|
|
<div className="dropdown-content">
|
|
|
|
{renderNestedData(data, handleSelect)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-03-25 06:17:41 +00:00
|
|
|
export default MultiLevelDropdown;
|