merged realTimeVisulaization with main
This commit is contained in:
@@ -1,94 +1,94 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Bar data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Bar data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import { useMemo } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple)
|
||||
borderColor: "#ffffff", // Keeping border color white
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Line data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
import { useMemo } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple)
|
||||
borderColor: "#ffffff", // Keeping border color white
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Line data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
import { useMemo } from "react";
|
||||
import { Pie } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const PieChartComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
// Access the CSS variable for the primary accent color
|
||||
const accentColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--accent-color")
|
||||
.trim();
|
||||
|
||||
console.log("accentColor: ", accentColor);
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Dataset",
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
backgroundColor: ["#6f42c1"],
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Pie data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default PieChartComponent;
|
||||
import { useMemo } from "react";
|
||||
import { Pie } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const PieChartComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
// Access the CSS variable for the primary accent color
|
||||
const accentColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--accent-color")
|
||||
.trim();
|
||||
|
||||
console.log("accentColor: ", accentColor);
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Dataset",
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
backgroundColor: ["#6f42c1"],
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Pie data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default PieChartComponent;
|
||||
|
||||
@@ -1,179 +1,179 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Widget } from "../../../store/useWidgetStore";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface DisplayZoneProps {
|
||||
zonesData: {
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
};
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
zonesData,
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Ref for the container element
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Example state for selectedOption and options (adjust based on your actual use case)
|
||||
const [selectedOption, setSelectedOption] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
console.log('setSelectedOption: ', setSelectedOption);
|
||||
const [options, setOptions] = React.useState<string[]>([]);
|
||||
console.log('setOptions: ', setOptions);
|
||||
|
||||
// Scroll to the selected option when it changes
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && selectedOption) {
|
||||
// Handle scrolling to the selected option
|
||||
const index = options.findIndex((option) => {
|
||||
const formattedOption = formatOptionName(option);
|
||||
const selectedFormattedOption =
|
||||
selectedOption?.split("_")[1] || selectedOption;
|
||||
return formattedOption === selectedFormattedOption;
|
||||
});
|
||||
|
||||
if (index !== -1) {
|
||||
const optionElement = container.children[index] as HTMLElement;
|
||||
if (optionElement) {
|
||||
optionElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedOption, options]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
if (container) {
|
||||
container.scrollBy({
|
||||
left: event.deltaY * 2, // Adjust the multiplier for faster scrolling
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let isDragging = false;
|
||||
let startX: number;
|
||||
let scrollLeft: number;
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
isDragging = true;
|
||||
startX = event.pageX - (container?.offsetLeft || 0);
|
||||
scrollLeft = container?.scrollLeft || 0;
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !container) return;
|
||||
event.preventDefault();
|
||||
const x = event.pageX - (container.offsetLeft || 0);
|
||||
const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging
|
||||
container.scrollLeft = scrollLeft - walk;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
if (container) {
|
||||
container.addEventListener("wheel", handleWheel, { passive: false });
|
||||
container.addEventListener("mousedown", handleMouseDown);
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
container.addEventListener("mouseup", handleMouseUp);
|
||||
container.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener("wheel", handleWheel);
|
||||
container.removeEventListener("mousedown", handleMouseDown);
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
container.removeEventListener("mouseup", handleMouseUp);
|
||||
container.removeEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Helper function to format option names (customize as needed)
|
||||
const formatOptionName = (option: string): string => {
|
||||
// Replace underscores with spaces and capitalize the first letter
|
||||
return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`zoon-wrapper ${
|
||||
selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${
|
||||
selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
...zonesData[zoneName],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Widget } from "../../../store/useWidgetStore";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface DisplayZoneProps {
|
||||
zonesData: {
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
};
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
zonesData,
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Ref for the container element
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Example state for selectedOption and options (adjust based on your actual use case)
|
||||
const [selectedOption, setSelectedOption] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
// console.log('setSelectedOption: ', setSelectedOption);
|
||||
const [options, setOptions] = React.useState<string[]>([]);
|
||||
// console.log('setOptions: ', setOptions);
|
||||
|
||||
// Scroll to the selected option when it changes
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && selectedOption) {
|
||||
// Handle scrolling to the selected option
|
||||
const index = options.findIndex((option) => {
|
||||
const formattedOption = formatOptionName(option);
|
||||
const selectedFormattedOption =
|
||||
selectedOption?.split("_")[1] || selectedOption;
|
||||
return formattedOption === selectedFormattedOption;
|
||||
});
|
||||
|
||||
if (index !== -1) {
|
||||
const optionElement = container.children[index] as HTMLElement;
|
||||
if (optionElement) {
|
||||
optionElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedOption, options]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
if (container) {
|
||||
container.scrollBy({
|
||||
left: event.deltaY * 2, // Adjust the multiplier for faster scrolling
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let isDragging = false;
|
||||
let startX: number;
|
||||
let scrollLeft: number;
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
isDragging = true;
|
||||
startX = event.pageX - (container?.offsetLeft || 0);
|
||||
scrollLeft = container?.scrollLeft || 0;
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !container) return;
|
||||
event.preventDefault();
|
||||
const x = event.pageX - (container.offsetLeft || 0);
|
||||
const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging
|
||||
container.scrollLeft = scrollLeft - walk;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
if (container) {
|
||||
container.addEventListener("wheel", handleWheel, { passive: false });
|
||||
container.addEventListener("mousedown", handleMouseDown);
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
container.addEventListener("mouseup", handleMouseUp);
|
||||
container.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener("wheel", handleWheel);
|
||||
container.removeEventListener("mousedown", handleMouseDown);
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
container.removeEventListener("mouseup", handleMouseUp);
|
||||
container.removeEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Helper function to format option names (customize as needed)
|
||||
const formatOptionName = (option: string): string => {
|
||||
// Replace underscores with spaces and capitalize the first letter
|
||||
return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`zoon-wrapper ${
|
||||
selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${
|
||||
selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
...zonesData[zoneName],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayZone;
|
||||
@@ -1,76 +1,76 @@
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./RenameInput";
|
||||
|
||||
type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
activeOption?: string; // The currently active dropdown option
|
||||
onClick?: () => void;
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
activeOption,
|
||||
onClick,
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
}) => {
|
||||
const separatedWords = label
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((word) => word.trim())
|
||||
.toString();
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
{editableLabel ? (
|
||||
<RenameInput value={label} />
|
||||
) : (
|
||||
<label htmlFor={separatedWords} className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => {
|
||||
setOpenDropdown(true);
|
||||
}}
|
||||
>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={"dropdown-option"}
|
||||
onClick={onClick}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./RenameInput";
|
||||
|
||||
type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
activeOption?: string; // The currently active dropdown option
|
||||
onClick?: () => void;
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
activeOption,
|
||||
onClick,
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
}) => {
|
||||
const separatedWords = label
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((word) => word.trim())
|
||||
.toString();
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
{editableLabel ? (
|
||||
<RenameInput value={label} />
|
||||
) : (
|
||||
<label htmlFor={separatedWords} className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => {
|
||||
setOpenDropdown(true);
|
||||
}}
|
||||
>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={"dropdown-option"}
|
||||
onClick={onClick}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import React, { useState } from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
type LabledDropdownProps = {
|
||||
defaultOption: string; // Initial active option
|
||||
options: string[]; // Array of dropdown options
|
||||
};
|
||||
|
||||
const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options }) => {
|
||||
const [activeOption, setActiveOption] = useState(defaultOption); // State for active option
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option); // Update the active option state
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
<div className="label">Type</div>
|
||||
<RegularDropDown
|
||||
header={activeOption} // Display the current active option
|
||||
options={options} // Use the options from props
|
||||
onSelect={handleSelect} // Handle option selection
|
||||
search = {false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabledDropdown;
|
||||
import React, { useState } from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
type LabledDropdownProps = {
|
||||
defaultOption: string; // Initial active option
|
||||
options: string[]; // Array of dropdown options
|
||||
};
|
||||
|
||||
const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options }) => {
|
||||
const [activeOption, setActiveOption] = useState(defaultOption); // State for active option
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option); // Update the active option state
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
<div className="label">Type</div>
|
||||
<RegularDropDown
|
||||
header={activeOption} // Display the current active option
|
||||
options={options} // Use the options from props
|
||||
onSelect={handleSelect} // Handle option selection
|
||||
search = {false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabledDropdown;
|
||||
|
||||
@@ -1,141 +1,141 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiLevelDropdown;
|
||||
@@ -1,127 +1,127 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
search?: boolean;
|
||||
onClick?: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
search?: boolean;
|
||||
onClick?: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
|
||||
Reference in New Issue
Block a user