From e0fc1a98ac0249bdceb217d7372bff3c6e2b0c2e Mon Sep 17 00:00:00 2001 From: "Daniel S. Hansen" Date: Wed, 9 Jul 2025 15:20:29 -0700 Subject: [PATCH] Add initial widget functionality using react-grid-layout and react-resizable --- client/package-lock.json | 92 ++++++++++++++++++++++++++++++- client/package.json | 2 + client/src/App.jsx | 3 +- client/src/components/Widget.jsx | 52 +++++++++++++++++ client/src/components/sidebar.jsx | 2 +- client/src/main.jsx | 2 + 6 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 client/src/components/Widget.jsx diff --git a/client/package-lock.json b/client/package-lock.json index 5ea559a..e8ead07 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16,7 +16,9 @@ "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-grid-layout": "^1.5.2", "react-icons": "^5.5.0", + "react-resizable": "^3.0.5", "tailwindcss": "^4.1.11" }, "devDependencies": { @@ -2775,6 +2777,15 @@ "node": ">=18" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3141,6 +3152,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3838,6 +3855,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -3971,7 +4000,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4170,6 +4198,17 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", @@ -4211,6 +4250,38 @@ "react": "^19.1.0" } }, + "node_modules/react-draggable": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", + "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-grid-layout": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.2.tgz", + "integrity": "sha512-vT7xmQqszTT+sQw/LfisrEO4le1EPNnSEMVHy6sBZyzS3yGkMywdOd+5iEFFwQwt0NSaGkxuRmYwa1JsP6OJdw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "fast-equals": "^4.0.3", + "prop-types": "^15.8.1", + "react-draggable": "^4.4.6", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -4224,6 +4295,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "license": "MIT", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, "node_modules/recrawl-sync": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recrawl-sync/-/recrawl-sync-2.2.3.tgz", @@ -4243,6 +4327,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", diff --git a/client/package.json b/client/package.json index 78e0538..af7fe94 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-grid-layout": "^1.5.2", "react-icons": "^5.5.0", + "react-resizable": "^3.0.5", "tailwindcss": "^4.1.11" }, "devDependencies": { diff --git a/client/src/App.jsx b/client/src/App.jsx index 28aa3a4..8a0a2b9 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,6 +1,7 @@ import { useState } from 'react' import Header from './components/header.jsx'; import Sidebar from './components/sidebar.jsx'; +import Widget from './components/Widget.jsx' import { Grid, GridItem } from "@chakra-ui/react" import { Box } from "@chakra-ui/react" @@ -25,7 +26,7 @@ function App() { height="100%" width="100%" > - {/* */} + diff --git a/client/src/components/Widget.jsx b/client/src/components/Widget.jsx new file mode 100644 index 0000000..a23327d --- /dev/null +++ b/client/src/components/Widget.jsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import GridLayout from 'react-grid-layout'; + +export default function Widget() { + // Define widgets initial position and size + const [layout, setLayout] = useState([ + { i: 'myWidget1', x: 0, y: 0, w: 3, h: 3, minH: 3, static: false }, + { i: 'myWidget2', x: 3, y: 0, w: 3, h: 3, minH: 3, static: false }, + { i: 'myWidget3', x: 6, y: 0, w: 3, h: 3, minH: 3, static: false } + ]); + + const onLayoutChange = (newLayout) => { + setLayout(newLayout); + console.log('Updated layout:', newLayout) + }; + + return ( + +
+
+ VDAP Widget +
+
+ This is the widget content - drag or resize me! +
+
+
+ VDAP Widget +
+
+ This is the widget content - drag or resize me! +
+
+
+ VDAP Widget +
+
+ This is the widget content - drag or resize me! +
+
+
+ ); +} diff --git a/client/src/components/sidebar.jsx b/client/src/components/sidebar.jsx index d1d742c..894bbf4 100644 --- a/client/src/components/sidebar.jsx +++ b/client/src/components/sidebar.jsx @@ -1,7 +1,7 @@ import { Box, Button, createIcon, CloseButton, IconButton, Stack } from "@chakra-ui/react"; import { FaVolcano } from "react-icons/fa6"; import { RxHamburgerMenu } from "react-icons/rx"; -import { useState } from "react" +import { useState } from "react"; // ICONS export const PushPin = createIcon({ diff --git a/client/src/main.jsx b/client/src/main.jsx index 2c02800..ff458ce 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -2,6 +2,8 @@ import { Provider } from "./components/ui/provider" import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' +import 'react-grid-layout/css/styles.css' +import 'react-resizable/css/styles.css' import App from './App.jsx' createRoot(document.getElementById('root')).render(