diff --git a/app/package-lock.json b/app/package-lock.json index b14b0af..bb966e5 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -43,6 +43,7 @@ "r3f-perf": "^7.2.3", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", + "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-router-dom": "^7.4.0", "react-scripts": "5.0.1", diff --git a/app/package.json b/app/package.json index c7590c0..405a33a 100644 --- a/app/package.json +++ b/app/package.json @@ -38,6 +38,7 @@ "r3f-perf": "^7.2.3", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", + "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-router-dom": "^7.4.0", "react-scripts": "5.0.1", diff --git a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 2453dcd..233b479 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -179,28 +179,30 @@ const BlockEditor: React.FC = ({
Size
- { - updateBlockSize(selectedBlock, { - ...currentBlock.size!, - width: Number(newValue), // Make sure to convert the string back to a number here - }); - }} - /> - { - updateBlockSize(selectedBlock, { - ...currentBlock.size!, - height: Number(newValue), - }); - }} - /> +
+ { + updateBlockSize(selectedBlock, { + ...currentBlock.size!, + width: Number(newValue), // Make sure to convert the string back to a number here + }); + }} + /> + { + updateBlockSize(selectedBlock, { + ...currentBlock.size!, + height: Number(newValue), + }); + }} + /> +
diff --git a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index c43b2ca..7e3a20d 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx @@ -607,7 +607,7 @@ const ElementEditor: React.FC = ({
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && ( -
+
{singleSourceFields.map((field) => ( = ({ )} {selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && ( -
+
{multipleValueFields.map((field) => ( void; } +const hexToRgb = (hex: string) => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } + : { r: 0, g: 0, b: 0 }; +}; + +const rgbToHex = (r: number, g: number, b: number) => { + return ( + "#" + + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase() + ); +}; + export const Color: React.FC = ({ value, onChange, @@ -21,50 +41,180 @@ export const Color: React.FC = ({ onFocus, onUpdate, }) => { - const handleTextChange = (e: React.ChangeEvent) => { - onChange(e.target.value); + const [isOpen, setIsOpen] = useState(false); + const popoverRef = useRef(null); + const containerRef = useRef(null); + const [localValue, setLocalValue] = useState(value); + + // Sync local value when prop changes + useEffect(() => { + setLocalValue(value); + }, [value]); + + const handleChange = (newColor: string) => { + setLocalValue(newColor); + onChange(newColor); }; - const handleColorPickerChange = (e: React.ChangeEvent) => { - onChange(e.target.value); - }; + const handleRgbChange = (part: "r" | "g" | "b", val: string) => { + const rgb = hexToRgb(localValue); + let num = parseInt(val, 10); + if (isNaN(num)) num = 0; + if (num < 0) num = 0; + if (num > 255) num = 255; - const handleSubmit = (e?: React.FormEvent) => { - if (e) e.preventDefault(); - if (onUpdate) { - onUpdate(value); - } + rgb[part] = num; + const newHex = rgbToHex(rgb.r, rgb.g, rgb.b); + handleChange(newHex); }; const handleBlur = (e: React.FocusEvent) => { - handleSubmit(); if (onBlur) onBlur(e); + if (onUpdate) onUpdate(localValue); }; + useOuterClick( + () => setIsOpen(false), + ["color-picker-popover", "color-trigger"], + isOpen + ); + + const rgb = hexToRgb(localValue); + return ( -
+
{label &&
{label}
}
- !disabled && setIsOpen(!isOpen)} + style={{ + backgroundColor: localValue, + width: "52px", + height: "22px", + display: "inline-block", + borderRadius: "24px", + border: "1px solid var(--border-color, #ccc)", + position: "relative", + }} + /> + handleChange(e.target.value)} + onBlur={handleBlur} + onFocus={onFocus} + disabled={disabled} /> -
- -
+ + {isOpen && ( +
+ + + + +
+ {/* Eyedropper placeholder icon */} +
+ + + +
+ +
+ +
+ {/* Small hex input representation usually goes here, but we have separated RGB below */} +
+
+ + {/* RGB Inputs */} +
+ {["r", "g", "b"].map((key) => ( +
+ handleRgbChange(key as "r" | "g" | "b", e.target.value)} + style={{ + width: "100%", + backgroundColor: "#333", + border: "1px solid #444", + borderRadius: "4px", + color: "#eee", + textAlign: "center", + fontSize: "12px", + padding: "4px 0", + appearance: "textfield", + }} + /> + + {key} + +
+ ))} +
+
+ )}
); }; diff --git a/app/src/styles/components/_input.scss b/app/src/styles/components/_input.scss index cb64933..7152089 100644 --- a/app/src/styles/components/_input.scss +++ b/app/src/styles/components/_input.scss @@ -246,6 +246,7 @@ input[type="number"] { .icon { height: auto; + border-radius: 4px; } } diff --git a/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss b/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss index 3812765..6d1e200 100644 --- a/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss +++ b/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss @@ -357,6 +357,7 @@ .select-type { display: flex; + gap: 6px; .type { flex: 1; @@ -547,7 +548,6 @@ .type-switch { display: flex; - padding: 6px 12px; .type { flex: 1; @@ -670,8 +670,8 @@ .icon { display: flex; - padding: 3px; - border-radius: 2px; + padding: 5px 4px; + border-radius: 6px; cursor: pointer; &.active {