feat: Add new UI input components for color, detailed dropdown, and data source selection, alongside foundational simulation dashboard components and styles.
This commit is contained in:
@@ -229,7 +229,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
|
|
||||||
{singleValueFields.length < totalValueOptions && (
|
{singleValueFields.length < totalValueOptions && (
|
||||||
<div className="add-field" onClick={addField}>
|
<div className="add-field" onClick={addField}>
|
||||||
<div className="icon">
|
<div className="add-icon">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className="label">Add Field</div>
|
<div className="label">Add Field</div>
|
||||||
@@ -292,7 +292,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
|
|
||||||
{multipleSourceFields.length < totalAssetOptions && (
|
{multipleSourceFields.length < totalAssetOptions && (
|
||||||
<div className="add-field" onClick={addField}>
|
<div className="add-field" onClick={addField}>
|
||||||
<div className="icon">
|
<div className="add-icon">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className="label">Add Field</div>
|
<div className="label">Add Field</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import { HexColorPicker } from "react-colorful";
|
import { HexColorPicker } from "react-colorful";
|
||||||
import { useOuterClick } from "../../../utils/useOuterClick";
|
import { useOuterClick } from "../../../utils/useOuterClick";
|
||||||
|
|
||||||
@@ -25,25 +26,14 @@ const hexToRgb = (hex: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rgbToHex = (r: number, g: number, b: number) => {
|
const rgbToHex = (r: number, g: number, b: number) => {
|
||||||
return (
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
||||||
"#" +
|
|
||||||
((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Color: React.FC<ColorProps> = ({
|
export const Color: React.FC<ColorProps> = ({ value, onChange, label = "Color", className = "", disabled = false, onBlur, onFocus, onUpdate }) => {
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
label = "Color",
|
|
||||||
className = "",
|
|
||||||
disabled = false,
|
|
||||||
onBlur,
|
|
||||||
onFocus,
|
|
||||||
onUpdate,
|
|
||||||
}) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const popoverRef = useRef<HTMLDivElement>(null);
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [coords, setCoords] = useState<{ top: number; left: number }>({ top: 0, left: 0 });
|
||||||
const [localValue, setLocalValue] = useState(value);
|
const [localValue, setLocalValue] = useState(value);
|
||||||
|
|
||||||
// Sync local value when prop changes
|
// Sync local value when prop changes
|
||||||
@@ -73,11 +63,42 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
if (onUpdate) onUpdate(localValue);
|
if (onUpdate) onUpdate(localValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
useOuterClick(
|
// Calculate position before opening or when needed
|
||||||
() => setIsOpen(false),
|
const updatePosition = () => {
|
||||||
["color-picker-popover", "color-trigger"],
|
if (containerRef.current) {
|
||||||
isOpen
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
);
|
setCoords({
|
||||||
|
top: rect.top + window.scrollY - 8,
|
||||||
|
left: rect.left + window.scrollX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOpen = () => {
|
||||||
|
if (disabled) return;
|
||||||
|
if (!isOpen) {
|
||||||
|
updatePosition();
|
||||||
|
setIsOpen(true);
|
||||||
|
} else {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useOuterClick(() => setIsOpen(false), ["color-picker-popover", "color-trigger"], isOpen);
|
||||||
|
|
||||||
|
// Update position on window resize/scroll if open
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
const handleResize = () => updatePosition();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
window.addEventListener("scroll", handleResize, true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
window.removeEventListener("scroll", handleResize, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
const rgb = hexToRgb(localValue);
|
const rgb = hexToRgb(localValue);
|
||||||
|
|
||||||
@@ -87,7 +108,7 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
<div className="left">
|
<div className="left">
|
||||||
<div
|
<div
|
||||||
className="color-trigger cursor-pointer"
|
className="color-trigger cursor-pointer"
|
||||||
onClick={() => !disabled && setIsOpen(!isOpen)}
|
onClick={toggleOpen}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: localValue,
|
backgroundColor: localValue,
|
||||||
width: "52px",
|
width: "52px",
|
||||||
@@ -98,33 +119,27 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input type="text" className="colorValue" value={localValue} onChange={(e) => handleChange(e.target.value)} onBlur={handleBlur} onFocus={onFocus} disabled={disabled} />
|
||||||
type="text"
|
|
||||||
className="colorValue"
|
|
||||||
value={localValue}
|
|
||||||
onChange={(e) => handleChange(e.target.value)}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
onFocus={onFocus}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen &&
|
||||||
|
createPortal(
|
||||||
<div
|
<div
|
||||||
ref={popoverRef}
|
ref={popoverRef}
|
||||||
className="color-picker-popover"
|
className="color-picker-popover"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "100%",
|
top: coords.top,
|
||||||
left: "0",
|
left: coords.left,
|
||||||
zIndex: 1000,
|
transform: "translateY(-100%)",
|
||||||
marginTop: "8px",
|
zIndex: 9999,
|
||||||
backgroundColor: "#1a1a1a", // Deep dark background
|
backgroundColor: "#1a1a1a", // Deep dark background
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
padding: "12px",
|
padding: "12px",
|
||||||
boxShadow: "0 10px 30px rgba(0,0,0,0.5)",
|
boxShadow: "0 10px 30px rgba(0,0,0,0.5)",
|
||||||
width: "240px",
|
width: "240px",
|
||||||
border: "1px solid #333",
|
border: "1px solid #333",
|
||||||
|
marginTop: "0px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<style>{`
|
<style>{`
|
||||||
@@ -149,12 +164,14 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
|
|
||||||
<HexColorPicker color={localValue} onChange={handleChange} />
|
<HexColorPicker color={localValue} onChange={handleChange} />
|
||||||
|
|
||||||
<div style={{
|
<div
|
||||||
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginTop: "12px",
|
marginTop: "12px",
|
||||||
gap: "10px"
|
gap: "10px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{/* Eyedropper placeholder icon */}
|
{/* Eyedropper placeholder icon */}
|
||||||
<div style={{ color: "#aaa" }}>
|
<div style={{ color: "#aaa" }}>
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||||
@@ -162,15 +179,17 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div
|
||||||
|
style={{
|
||||||
width: "32px",
|
width: "32px",
|
||||||
height: "32px",
|
height: "32px",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
backgroundColor: localValue,
|
backgroundColor: localValue,
|
||||||
border: "2px solid #fff",
|
border: "2px solid #fff",
|
||||||
boxShadow: "0 0 0 1px #333",
|
boxShadow: "0 0 0 1px #333",
|
||||||
flexShrink: 0
|
flexShrink: 0,
|
||||||
}}></div>
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
<div style={{ flexGrow: 1, display: "flex", flexDirection: "column", gap: "2px" }}>
|
<div style={{ flexGrow: 1, display: "flex", flexDirection: "column", gap: "2px" }}>
|
||||||
{/* Small hex input representation usually goes here, but we have separated RGB below */}
|
{/* Small hex input representation usually goes here, but we have separated RGB below */}
|
||||||
@@ -178,12 +197,14 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RGB Inputs */}
|
{/* RGB Inputs */}
|
||||||
<div style={{
|
<div
|
||||||
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "1fr 1fr 1fr",
|
gridTemplateColumns: "1fr 1fr 1fr",
|
||||||
gap: "8px",
|
gap: "8px",
|
||||||
marginTop: "12px"
|
marginTop: "12px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{["r", "g", "b"].map((key) => (
|
{["r", "g", "b"].map((key) => (
|
||||||
<div key={key} style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
|
<div key={key} style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||||||
<input
|
<input
|
||||||
@@ -202,18 +223,21 @@ export const Color: React.FC<ColorProps> = ({
|
|||||||
appearance: "textfield",
|
appearance: "textfield",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{
|
<span
|
||||||
|
style={{
|
||||||
color: "#888",
|
color: "#888",
|
||||||
fontSize: "10px",
|
fontSize: "10px",
|
||||||
marginTop: "4px",
|
marginTop: "4px",
|
||||||
textTransform: "uppercase"
|
textTransform: "uppercase",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{key}
|
{key}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
document.body
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const DataDetailedDropdown: React.FC<DataDetailedDropdownProps> = ({ title, plac
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{eyedroper && (
|
{eyedroper && (
|
||||||
<div className={`icon ${isEyeActive ? "active" : ""}`} onClick={handleEyeClick}>
|
<div className={`add-icon ${isEyeActive ? "active" : ""}`} onClick={handleEyeClick}>
|
||||||
<EyeDroperIcon isActive={isEyeActive} />
|
<EyeDroperIcon isActive={isEyeActive} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const DataSourceSelector: React.FC<DataSourceSelectorProps> = ({ label = "Data S
|
|||||||
<RegularDropDownID header={selected || "Select value"} options={options} onSelect={onSelect} search={false} />
|
<RegularDropDownID header={selected || "Select value"} options={options} onSelect={onSelect} search={false} />
|
||||||
|
|
||||||
{showEyeDropper && (
|
{showEyeDropper && (
|
||||||
<div className={`icon ${isEyeActive ? "active" : ""}`} onClick={handleEyeClick} style={{ cursor: "pointer" }}>
|
<div className={`add-icon ${isEyeActive ? "active" : ""}`} onClick={handleEyeClick} style={{ cursor: "pointer" }}>
|
||||||
<EyeDroperIcon isActive={isEyeActive} />
|
<EyeDroperIcon isActive={isEyeActive} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
*> {
|
* > {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,7 +640,8 @@
|
|||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
text-align: center;
|
padding: 4px 6px 8px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields-wrapper {
|
.fields-wrapper {
|
||||||
@@ -661,6 +662,7 @@
|
|||||||
|
|
||||||
.datas__label {
|
.datas__label {
|
||||||
flex: 0.8;
|
flex: 0.8;
|
||||||
|
min-width: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datas__class {
|
.datas__class {
|
||||||
@@ -668,12 +670,20 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
.icon {
|
.add-icon,
|
||||||
|
.delete {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px 4px;
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-input);
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: var(--background-color-button);
|
background: var(--background-color-button);
|
||||||
}
|
}
|
||||||
@@ -681,9 +691,17 @@
|
|||||||
|
|
||||||
.delete {
|
.delete {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: var(--log-error-background-color);
|
||||||
|
path {
|
||||||
|
stroke: var(--log-error-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.regularDropdown-container {
|
.regularDropdown-container {
|
||||||
|
max-width: 106px;
|
||||||
|
width: 106px;
|
||||||
.icon {
|
.icon {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -709,7 +727,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.appearance {
|
.appearance {
|
||||||
.design-datas-wrapper {
|
.design-datas-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user