feat: Refactor DataSourceSelector to use structured options and implement RegularDropDownID for improved dropdown functionality
This commit is contained in:
@@ -359,7 +359,13 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
<div className="design-section">
|
<div className="design-section">
|
||||||
<DataSourceSelector
|
<DataSourceSelector
|
||||||
label={"Chart Type"}
|
label={"Chart Type"}
|
||||||
options={["Line Chart", "Bar Chart", "Pie Chart", "Area Chart", "Radar Chart"]}
|
options={[
|
||||||
|
{ id: "line", label: "Line Chart" },
|
||||||
|
{ id: "bar", label: "Bar Chart" },
|
||||||
|
{ id: "pie", label: "Pie Chart" },
|
||||||
|
{ id: "area", label: "Area Chart" },
|
||||||
|
{ id: "radar", label: "Radar Chart" },
|
||||||
|
]}
|
||||||
onSelect={(newValue) => {
|
onSelect={(newValue) => {
|
||||||
if (newValue === "Line Chart") {
|
if (newValue === "Line Chart") {
|
||||||
updateGraphType(selectedBlock, selectedElement, "line");
|
updateGraphType(selectedBlock, selectedElement, "line");
|
||||||
@@ -568,7 +574,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
<div className="data-details">
|
<div className="data-details">
|
||||||
{element?.type === "label-value" && (
|
{element?.type === "label-value" && (
|
||||||
<div className="data-wrapper">
|
<div className="data-wrapper">
|
||||||
<InputWithDropDown label="Title" value={`title`} placeholder={"Label 1"} min={0.1} step={0.1} max={2} onChange={() => {}} />
|
<InputWithDropDown label="Title" value={`title`} placeholder={"Label 1"} min={0.1} step={0.1} max={2} onChange={() => { }} />
|
||||||
<div className="data">
|
<div className="data">
|
||||||
<DataDetailedDropdown
|
<DataDetailedDropdown
|
||||||
title="Data Source"
|
title="Data Source"
|
||||||
@@ -628,7 +634,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
{selectDataMapping === "singleMachine" && (
|
{selectDataMapping === "singleMachine" && (
|
||||||
<div className="fields-wrapper">
|
<div className="fields-wrapper">
|
||||||
{singleFields.map((field) => (
|
{singleFields.map((field) => (
|
||||||
<DataSourceSelector key={field.id} label={field.label} options={["1h", "2h", "12h"]} onSelect={() => {}} showEyeDropper={!!field.showEyeDropper} />
|
<DataSourceSelector
|
||||||
|
key={field.id}
|
||||||
|
label={field.label}
|
||||||
|
options={[
|
||||||
|
{ id: "global", label: "Global" }
|
||||||
|
]}
|
||||||
|
onSelect={() => { }} showEyeDropper={!!field.showEyeDropper}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="add-field" onClick={addField}>
|
<div className="add-field" onClick={addField}>
|
||||||
@@ -645,7 +658,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
{multipleFields.map((field) => (
|
{multipleFields.map((field) => (
|
||||||
<div className="datas" key={field.id}>
|
<div className="datas" key={field.id}>
|
||||||
<div className="datas__class">
|
<div className="datas__class">
|
||||||
<DataSourceSelector label={field.label} options={["1h", "2h", "12h"]} onSelect={() => {}} showEyeDropper={field.label !== "Common Value"} />
|
<DataSourceSelector
|
||||||
|
label={field.label}
|
||||||
|
options={[
|
||||||
|
{ id: "global", label: "Global" }
|
||||||
|
]}
|
||||||
|
onSelect={() => { }}
|
||||||
|
showEyeDropper={field.label !== "Common Value"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ function MainScene() {
|
|||||||
{!selectedUser && (
|
{!selectedUser && (
|
||||||
<>
|
<>
|
||||||
<KeyPressListener />
|
<KeyPressListener />
|
||||||
{!createNewWindow && loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
{/* {!createNewWindow && loadingProgress > 0 && <LoadingPage progress={loadingProgress} />} */}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
<>
|
<>
|
||||||
{!toggleView && !isComparing && <ModuleToggle />}
|
{!toggleView && !isComparing && <ModuleToggle />}
|
||||||
@@ -254,7 +254,7 @@ function MainScene() {
|
|||||||
}
|
}
|
||||||
onDragOver={(event) => event.preventDefault()}
|
onDragOver={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<Scene layout="Main Layout" />
|
{/* <Scene layout="Main Layout" /> */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import RegularDropDown from "./RegularDropDown";
|
import RegularDropDown from "./RegularDropDown";
|
||||||
import { EyeDroperIcon } from "../../icons/ExportCommonIcons";
|
import { EyeDroperIcon } from "../../icons/ExportCommonIcons";
|
||||||
|
import RegularDropDownID from "./RegularDropDownID";
|
||||||
|
|
||||||
type DataSourceSelectorProps = {
|
type DataSourceSelectorProps = {
|
||||||
label?: string;
|
label?: string;
|
||||||
options: string[];
|
options: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}[];
|
||||||
selected?: string;
|
selected?: string;
|
||||||
onSelect: (value: string) => void;
|
onSelect: (value: string) => void;
|
||||||
eyeDropperActive?: boolean; // initial state
|
eyeDropperActive?: boolean; // initial state
|
||||||
@@ -26,7 +30,7 @@ const DataSourceSelector: React.FC<DataSourceSelectorProps> = ({
|
|||||||
<div className="datas__label">{label}</div>
|
<div className="datas__label">{label}</div>
|
||||||
|
|
||||||
<div className="datas__class">
|
<div className="datas__class">
|
||||||
<RegularDropDown
|
<RegularDropDownID
|
||||||
header={selected || "Select value"}
|
header={selected || "Select value"}
|
||||||
options={options}
|
options={options}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
|||||||
106
app/src/components/ui/inputs/RegularDropDownID.tsx
Normal file
106
app/src/components/ui/inputs/RegularDropDownID.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
interface DropdownOption {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownProps {
|
||||||
|
header: string;
|
||||||
|
options: DropdownOption[];
|
||||||
|
onSelect: (optionId: string) => void;
|
||||||
|
search?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
onChange?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegularDropDownID: React.FC<DropdownProps> = ({
|
||||||
|
header,
|
||||||
|
options,
|
||||||
|
onSelect,
|
||||||
|
search = true,
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [filteredOptions, setFilteredOptions] = useState<DropdownOption[]>(options);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [position, setPosition] = useState<{ top: number; left: number; width: number }>({ top: 0, left: 0, width: 0 });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setSelectedOption(null);
|
||||||
|
setSearchTerm("");
|
||||||
|
setFilteredOptions(options);
|
||||||
|
}
|
||||||
|
}, [isOpen, options]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOption(null);
|
||||||
|
setSearchTerm("");
|
||||||
|
setFilteredOptions(options);
|
||||||
|
}, [header, options]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && dropdownRef.current) {
|
||||||
|
const rect = dropdownRef.current.getBoundingClientRect();
|
||||||
|
setPosition({ top: rect.bottom + window.scrollY, left: rect.left + window.scrollX, width: rect.width });
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const toggleDropdown = () => setIsOpen((p) => !p);
|
||||||
|
|
||||||
|
const handleOptionClick = (opt: DropdownOption) => {
|
||||||
|
setSelectedOption(opt.label);
|
||||||
|
onSelect(opt.id);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const term = e.target.value;
|
||||||
|
setSearchTerm(term);
|
||||||
|
setFilteredOptions(options.filter(o => o.label.toLowerCase().includes(term.toLowerCase())));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="regularDropdown-container" ref={dropdownRef} onPointerLeave={() => setIsOpen(false)}>
|
||||||
|
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||||
|
<div className="key">{selectedOption || header}</div>
|
||||||
|
<div className="icon">▾</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && createPortal(
|
||||||
|
<div className="dropdown-options" style={{ position: "absolute", top: position.top, left: position.left, width: position.width, zIndex: 9999 }}>
|
||||||
|
{search && (
|
||||||
|
<div className="dropdown-search">
|
||||||
|
<input type="text" placeholder="Search..." value={searchTerm} onChange={handleSearchChange} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{filteredOptions.length > 0 ? (
|
||||||
|
filteredOptions.map((opt) => (
|
||||||
|
<div className="option" key={opt.id} onClick={() => handleOptionClick(opt)} title={opt.label}>
|
||||||
|
{opt.label}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-options">No options found</div>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RegularDropDownID
|
||||||
Reference in New Issue
Block a user